C 语言面向对象

news2024/11/26 23:48:52

面向对象的基本特性:封装,继承,多态


1.0 面向过程概念


当我们在编写程序时,通常采用以下步骤:
1. 将问题的解法分解成若干步骤
2. 使用函数分别实现这些步骤
3. 依次调用这些函数

这种编程风格的被称作 面向过程 。除了 面向过程 之外,还有一种被称作 面向对象 的编程风格被广泛使 用。
面向对象 采用基于对象的概念建立模型,对现实世界进行模拟,从而完成对问题的解决。
C 语言的语法并不直接支持面向对象风格的编程。但是,我们可以通过额外的代码,让 C 语言实现一些面向对象特性。

2.0 程序案例


#define  _CRT_SECURE_NO_WARNINGS
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

struct student
{
    int id;
    char name[20];
    int gender;
    int mark;
};

int MakeStudentId(int year, int classNum, int serialNum)
{
    // 创建一个char类型的数组
    char buffer[20];
    // 将三个变量转换为指定格式的字符串,存储在buffer数组中
    sprintf(buffer, "%d%d%d", year, classNum, serialNum);
    // 将字符串转换为整数
    int id = atoi(buffer);
    // 返回项目的id值
    return id;
}

const char* NumGenderToStrGender(int numGender)
{
    if (numGender == 0)
    {
        return "女";
    }
    else if (numGender == 1)
    {
        return "男";
    }
    return "NULL";
}

int StrGenderToNumGender(const char* strGender)
{
    int numGender;
    if (strcmp("男", strGender) == 0)
    {
        numGender = 1;
    }
    else if (strcmp("女", strGender) == 0)
    {
        numGender = 0;
    }
    else
    {
        numGender = -1;
    }
    return numGender;
}

int main()
{
    // 创建结构体变量
    struct student stu;

    stu.id = MakeStudentId(2024, 123, 26);
    strcpy(stu.name, "小明");
    stu.gender = StrGenderToNumGender("男");
    stu.mark = 98;

    printf("学号:%d\n", stu.id);
    printf("姓名: %s\n", stu.name);
    const char* gender = NumGenderToStrGender(stu.gender);
    printf("性别:%s\n", gender);
    printf("分数:%s\n", stu.mark);

    return 0;

}
现在,我们使用 面向过程 风格写了 3 个函数和一个结构体,并且调用了这些函数,将函数返回的结果赋值给了结构体。接下来,让我们以面向对象风格来重新审视这段代码。

3.0 面向对象

现在,我们使用 面向过程 风格写了 3 个函数和一个结构体,并且调用了这些函数,将函数返回的结果赋值给了结构体。接下来,让我们以面向对象风格来重新审视这段代码。
在面向对象风格中,结构体被看做 数据( data ,而操作数据的函数称作 方法( method 。目前函数和数据是分离的,函数并不直接操作数据,我们需要拿到函数返回的结果,再将其赋值给数据。
面向对 象风格编程的第一大特性 --- 封装 ,它希望 方法直接操作数据 ,并且将数据和方法 结合 在一起,它们构成 一个整体, 而这个整体被称作 对象
此外,还有一个方法命名上的规则。一般来说, 获取数据的方法会被命名为 getXXX ,设置数据的方法 会被命名为 setXXX 。

成员id的表示方式:


1. 将函数的第一个参数设置为 struct student * ,让函数直接操作 student 结构体。

2. 修改函数名,获取数据的方法命名为 getXXX ,设置数据的方法命名为 setXXX

4.0 封装特性


我们来看看学校里面最重要的主体是什么?是学生,学生肯定拥有很多属性,比如学生的学号、姓名、性别、考试分数等等。自然地,我们会声明一个结构体用于表示学生。
typedef struct
{
    int id;
    char name[20];
    int gender;
    int mark;
}StudentInfo_t;

注:将需要的信息封装为一个结构体内部包含学生的姓名,学号,性别,分数。


通过函数设置学生的id编号,函数可以通过结构体指针,直接操作结构体中的数据

void SetStudentId(StudentInfo_t* s, int year, int classNum, int serialNum)
{
    char buffer[20];
    sprintf(buffer, "%d%d%d", year, classNum, serialNum);
    int id = atoi(buffer);
    s->id = id;
}

获取学生的性别函数,在面向对象的编程方法中获取数据的函数被我们设置为GetXXX,设置数据的函数被我们设置为SetXXX。

const char* GetGender(StudentInfo_t* s)
{
    if (s->gender == 0)
    {
        return "女";
    }
    else if (s->gender == 1)
    {
        return "男";
    }
    return "未知";
}

设置数据的方法

void SetGender(StudentInfo_t* s, const char* strGender)
{
    int numGender;
    if (strcmp("男", strGender) == 0)
    {
        numGender = 1;
    }
    else if (strcmp("女", strGender) == 0)
    {
        numGender = 0;
    }
    else
    {
        numGender = -1;
    }
    s->gender = numGender;
}

通过主函数进行调用

int main()
{
    StudentInfo_t stu;
    SetStudentId(&stu, 2022, 123, 26);
    strcpy(stu.name, "小明");
    SetGender(&stu, "男");
    stu.mark = 98;
    // 打印这些数值
    printf("学号:%d\n", stu.id);
    printf("姓名:%s\n", stu.name);
    const char* gender = GetGender(&stu);
    printf("性别:%s\n", gender);
    printf("分数:%d\n", stu.mark);
}

完整函数代码

#define  _CRT_SECURE_NO_WARNINGS
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct
{
    int id;
    char name[20];
    int gender;
    int mark;
}StudentInfo_t;

void SetStudentId(StudentInfo_t* s, int year, int classNum, int serialNum)
{
    char buffer[20];
    sprintf(buffer, "%d%d%d", year, classNum, serialNum);
    int id = atoi(buffer);
    s->id = id;
}

const char* GetGender(StudentInfo_t* s)
{
    if (s->gender == 0)
    {
        return "女";
    }
    else if (s->gender == 1)
    {
        return "男";
    }
    return "未知";
}

void SetGender(StudentInfo_t* s, const char* strGender)
{
    int numGender;
    if (strcmp("男", strGender) == 0)
    {
        numGender = 1;
    }
    else if (strcmp("女", strGender) == 0)
    {
        numGender = 0;
    }
    else
    {
        numGender = -1;
    }
    s->gender = numGender;
}

int main()
{
    StudentInfo_t stu;
    SetStudentId(&stu, 2022, 123, 26);
    strcpy(stu.name, "小明");
    SetGender(&stu, "男");
    stu.mark = 98;
    // 打印这些数值
    printf("学号:%d\n", stu.id);
    printf("姓名:%s\n", stu.name);
    const char* gender = GetGender(&stu);
    printf("性别:%s\n", gender);
    printf("分数:%d\n", stu.mark);
}
目前,函数可以直接操作数据了。但是,函数和数据依然是两个独立的部分。我们要将 函数和数据结合 到一起,这样,这个整体就能被称作 对象 ,函数可以称作属于这个对象的 方法 当前我们可以吧结构体理解为我们的数据,函数可以理解为我们的方法,数据和方法结合在一起可以称之为对象

对象.方法(对象指针,参数1,参数2, 参数3...)
接下来,我们举几个这种格式的例子:
stu.setGender(&stu, "男"); 
以上代码中,对象为 stu ,方法为 setGender 。通过 对象 + + 方法 的形式,可以调用属于对
stu setGender 方法。在方法的参数中传入性别 。这样,方法会把性别 转换为整形,并设置到对象 stu 的数据当中。
const char* gender = stu.getGender(&stu); 
以上代码中, 对象为 stu ,方法为 getGender 。 通过对象 + 点 + 方法的形式,可以调用属于对
象 stu 的 getGender 方法。 getGender 方法从对象数据中获取整形表示的性别,并返回性别对应的字符 你好编程 串。 C 语言中, 若要实现对象 + 点 + 方法的形式,我们可以借助于函数指针。
在结构中,声明这3个函数的函数指针。

5.0 面向对象

在结构体中声明函数指针

struct student {
    void (*setStudentId)(struct student* s, int year, int classNum, int serialNum);
    const char* (*getGender)(struct student* s);
    void (*setGender)(struct student* s, const char* strGender);

    int id; // 学号
    char name[20]; // 姓名
    int gender; // 性别
    int mark; // 分数
};

这个时候可以将结构体作为一个对象看待,使用对象(结构体变量). 方法(函数)的方式进行参数的赋值和调用。


生成学生的id

void setStudentId(struct student* s, int year, int classNum, int serialNum)
{
    char buffer[20];
    sprintf(buffer, "%d%d%d", year, classNum, serialNum);
    int id = atoi(buffer);
    s->id = id;
}

获取学生的性别

const char* getGender(struct student* s)
{
    if (s->gender == 0)
    {
        return "女";
    }
    else if (s->gender == 1)
    {
        return "男";
    }
    return "未知";
}

设置学生的性别

void setGender(struct student* s, const char* strGender)
{
    int numGender;
    if (strcmp("男", strGender) == 0)
    {
        numGender = 1;
    }
    else if (strcmp("女", strGender) == 0)
    {
        numGender = 0;
    }
    else
    {
        numGender = -1;
    }
    s->gender = numGender;
}

初始化函数指针【对象初始化之后才能被调用】

void initStudent(struct student* s)
{
    s->setStudentId = setStudentId;
    s->getGender = getGender;
    s->setGender = setGender;
}

主函数相关代码

int main()
{
    struct student stu;
    // 初始化student
    initStudent(&stu);

    stu.setStudentId(&stu, 2022, 123, 26);
    strcpy(stu.name, "小明");
    stu.setGender(&stu, "男");
    stu.mark = 98;
    // 打印这些数值
    printf("学号:%d\n", stu.id);
    printf("姓名:%s\n", stu.name);
    const char* gender = stu.getGender(&stu);
    printf("性别:%s\n", gender);
    printf("分数:%d\n", stu.mark);
}

完整代码

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct student {
    void (*setStudentId)(struct student* s, int year, int classNum, int serialNum);
    const char* (*getGender)(struct student* s);
    void (*setGender)(struct student* s, const char* strGender);

    int id; // 学号
    char name[20]; // 姓名
    int gender; // 性别
    int mark; // 分数
};

void setStudentId(struct student* s, int year, int classNum, int serialNum)
{
    char buffer[20];
    sprintf(buffer, "%d%d%d", year, classNum, serialNum);
    int id = atoi(buffer);
    s->id = id;
}

const char* getGender(struct student* s)
{
    if (s->gender == 0)
    {
        return "女";
    }
    else if (s->gender == 1)
    {
        return "男";
    }
    return "未知";
}

void setGender(struct student* s, const char* strGender)
{
    int numGender;
    if (strcmp("男", strGender) == 0)
    {
        numGender = 1;
    }
    else if (strcmp("女", strGender) == 0)
    {
        numGender = 0;
    }
    else
    {
        numGender = -1;
    }
    s->gender = numGender;
}

void initStudent(struct student* s)
{
    s->setStudentId = setStudentId;
    s->getGender = getGender;
    s->setGender = setGender;
}

int main()
{
    struct student stu;
    // 初始化student
    initStudent(&stu);

    stu.setStudentId(&stu, 2022, 123, 26);
    strcpy(stu.name, "小明");
    stu.setGender(&stu, "男");
    stu.mark = 98;
    // 打印这些数值
    printf("学号:%d\n", stu.id);
    printf("姓名:%s\n", stu.name);
    const char* gender = stu.getGender(&stu);
    printf("性别:%s\n", gender);
    printf("分数:%d\n", stu.mark);
}

6.0 继承基本概念


除了学生之外,学校里面还需要有老师,老师也具有很多属性。例如:
  1. 工号
  2. 姓名
  3. 性别
  4. 任课科目
声明一个结构体用于表示老师。
struct teacher 
{
    int id; // 工号
    char name[20]; // 姓名
    int gender; // 性别
    char subject[20]; // 任课科目
};
比较一下学生和老师的结构体,看看它们之间有什么共同之处与不同之处。
struct teacher {
    int id; // 工号
    char name[20]; // 姓名
    int gender; // 性别
    char subject[20]; // 任课科目
};
struct student {
    int id; // 学号
    char name[20]; // 姓名
    int gender; // 性别
    int mark; // 分数
};
共同之处如下:
  1. 编号
  2. 姓名
  3. 性别
不同之处:
  1. 学生有考试分数
  2. 老师有任课科目
我们可以把两个结构体中的共同之处 抽象 出来,让它共同之处成为一个新的结构。这个结构体具有老师 和学生的共性,而老师与学生它们都是人,可以把这个结构体命名为 person
struct person{
    int id; // 编号
    char name[20]; // 姓名
    int gender; // 性别
};
接下来,我们可以让老师和学生结构包含这个 person 对象。
struct teacher {
    struct person super;
    char subject[20]; // 任课科目
};
struct student {
    struct person super;
    int mark; // 分数
};
让我们比较一下原有代码与现有代码
// 原有代码
struct teacher {
    int id; // 工号
    char name[20]; // 姓名
    int gender; // 性别
    char subject[20]; // 任课科目
};
struct student {
    int id; // 学号
    char name[20]; // 姓名
    int gender; // 性别
    int mark; // 分数
};
// 现有代码
struct person{
    int id; // 编号
    char name[20]; // 姓名
    int gender; // 性别
};
struct teacher {
    struct person super;
    char subject[20]; // 任课科目
};
struct student {
    struct person super;
    int mark; // 分数
};
原有代码中,老师和学生结构体中,均有 id name gender 三个变量。现有代码中,将这 3 个变量抽象成结构体 person 。这样一来,有两个好处:
  • 1. 减少重复代码
  • 2. 代码层次更清晰
由于 student teacher 拥有 person 的一切,因此,我们可以说, student teacher 继承
person person 是 student 与 teacher 的父对象。 student 与 teacher 是 person 的子对象。


刚刚我们只讨论了数据,现在我们结合上方法一起讨论
struct person{
    int id; // 编号
    char name[20]; // 姓名
    int gender; // 性别
};
struct teacher {
    struct person super;
    char subject[20]; // 任课科目
};
struct student {
    struct person super;
    int mark; // 分数
    void (*setStudentId)(struct student* s, int year, int classNum, int serialNum);
    const char* (*getGender)(struct student* s);
    void (*setGender)(struct student* s, const char* strGender);
};
之前我们为 student 写了 3 个方法
  • 设置性别
  • 获取性别
  • 设置学号
其中,性别相关的方法也属于共性的方法。可以把这两个函数指针移动到 person 对象里面去,注意,要把方法的第一个参数 struct student * 修改为 struct person * 。移动后,子对 student teacher 均可以使用这一对性别相关的方法。而设置学号的方法,为 student 独有的方 法,因此保持不变,依然将其放置在 student 对象内。

创建一个Person方法,内部包含

struct person 
{
    int id;
    char name[20];
    int gender;

    const char* (*getGender)(struct student* s);
    void (*setGender)(struct student* s, const char* strGender);
};

struct teacher 
{
    // 创建结构体成员变量
    struct person super;
    char subject[20];
};

struct student {
    void (*setStudentId)(struct student* s, int year, int classNum, int serialNum);

    struct person super;
    int mark; // 分数
};
对应上面的更改,函数 getGender setGender 的第一个参数也要由 struct student * 修改
struct person *
const char* getGender(struct person* p)
{
    if (p->gender == 0)
    {
        return "女";
    }
    else if (p->gender == 1)
    {
        return "男";
    }
    return "未知";
}

void setGender(struct person* p, const char* strGender)
{
    int numGender;
    if (strcmp("男", strGender) == 0)
    {
        numGender = 1;
    }
    else if (strcmp("女", strGender) == 0)
    {
        numGender = 0;
    }
    else
    {
        numGender = -1;
    }
    p->gender = numGender;
}
此外, setStudentId 函数中, id 成员,不在 student 中,而是在 student 中的 person 中。这里也要对应的修改一下。
void setStudentId(struct student* s, int year, int classNum, int serialNum)
{
    char buffer[20];
    sprintf(buffer, "%d%d%d", year, classNum, serialNum);
    int id = atoi(buffer);
    s->super.id = id;
}
还有,别忘了给结构初始化函数指针。
void initPerson(struct person* p) 
{
    p->getGender = getGender;
    p->setGender = setGender;
}

void initStudent(struct student* s)
{
    initPerson(&(s->super));
    s->setStudentId = setStudentId;
}

void initTeacher(struct teacher* t) 
{
    initPerson(&(t->super));
}

main函数调用

int main()
{
    struct student stu;
    // 初始化student
    initStudent(&stu);

    stu.setStudentId(&stu, 2022, 123, 26);
    strcpy(stu.super.name, "小明");
    stu.super.setGender(&stu.super, "男");
    stu.mark = 98;
    // 打印这些数值
    printf("学号:%d\n", stu.super.id);
    printf("姓名:%s\n", stu.super.name);
    const char* gender = stu.super.getGender(&stu.super);
    printf("性别:%s\n", gender);
    printf("分数:%d\n", stu.mark);
    putchar('\n');
    struct teacher t;
    // 初始化teacher
    initTeacher(&t);

    t.super.id = 12345;
    strcpy(t.super.name, "林老师");
    t.super.setGender(&t.super, "男");
    strcpy(t.subject, "C语言");
    // 打印这些数值
    printf("学号:%d\n", t.super.id);
    printf("姓名:%s\n", t.super.name);
    gender = t.super.getGender(&t.super);
    printf("性别:%s\n", gender);
    printf("科目:%s\n", t.subject);
}

完整代码

#define  _CRT_SECURE_NO_WARNINGS
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct person 
{
    int id;
    char name[20];
    int gender;

    const char* (*getGender)(struct student* s);
    void (*setGender)(struct student* s, const char* strGender);
};

struct teacher 
{
    // 创建结构体成员变量
    struct person super;
    char subject[20];
};

struct student {
    void (*setStudentId)(struct student* s, int year, int classNum, int serialNum);

    struct person super;
    int mark; // 分数
};

void setStudentId(struct student* s, int year, int classNum, int serialNum)
{
    char buffer[20];
    sprintf(buffer, "%d%d%d", year, classNum, serialNum);
    int id = atoi(buffer);
    s->super.id = id;
}

const char* getGender(struct person* p)
{
    if (p->gender == 0)
    {
        return "女";
    }
    else if (p->gender == 1)
    {
        return "男";
    }
    return "未知";
}

void setGender(struct person* p, const char* strGender)
{
    int numGender;
    if (strcmp("男", strGender) == 0)
    {
        numGender = 1;
    }
    else if (strcmp("女", strGender) == 0)
    {
        numGender = 0;
    }
    else
    {
        numGender = -1;
    }
    p->gender = numGender;
}

void initPerson(struct person* p) 
{
    p->getGender = getGender;
    p->setGender = setGender;
}

void initStudent(struct student* s)
{
    initPerson(&(s->super));
    s->setStudentId = setStudentId;
}

void initTeacher(struct teacher* t) 
{
    initPerson(&(t->super));
}

int main()
{
    struct student stu;
    // 初始化student
    initStudent(&stu);

    stu.setStudentId(&stu, 2022, 123, 26);
    strcpy(stu.super.name, "小明");
    stu.super.setGender(&stu.super, "男");
    stu.mark = 98;
    // 打印这些数值
    printf("学号:%d\n", stu.super.id);
    printf("姓名:%s\n", stu.super.name);
    const char* gender = stu.super.getGender(&stu.super);
    printf("性别:%s\n", gender);
    printf("分数:%d\n", stu.mark);
    putchar('\n');
    struct teacher t;
    // 初始化teacher
    initTeacher(&t);

    t.super.id = 12345;
    strcpy(t.super.name, "林老师");
    t.super.setGender(&t.super, "男");
    strcpy(t.subject, "C语言");
    // 打印这些数值
    printf("学号:%d\n", t.super.id);
    printf("姓名:%s\n", t.super.name);
    gender = t.super.getGender(&t.super);
    printf("性别:%s\n", gender);
    printf("科目:%s\n", t.subject);
}

程序运行结果


7.0 多态


struct Rect {
void (*draw)(struct Rect *);
    int left;
    int top;
    int right;
    int bottom;
};
struct Circle {
void (*draw)(struct Circle *);
    int x;
    int y;
    int r;
};
struct Triangle {
void (*draw)(struct Triangle *);
    POINT p1;
    POINT p2;
    POINT p3;
};
我们仔细观察这 3 个对象,看看它们分别有什么共性?可以发现,这 3 个对象,它们都有一个 draw 方法。那么,我们可以将 draw 这个方法抽象出来,单独放置到一个对象当中。由于这三个对象都是形 状。我们可以把单独抽象出来的对象,命名为 shape shape 对象中的 draw 方法,应当是一个共性的方 法,所以,它的参数应当设置为 struct Shape *

struct Shape {
void (*draw)(struct Shape *);
};

这是共性的结构体,可以称之为结构体对象


接下来,让 Rect Circle Triangle 三个对象分别都包含 Shape 对象。这样,它们就都能使
draw 这个方法了。

struct Rect {
    struct Shape super;
    int left;
    int top;
    int right;
    int bottom;
};
struct Circle {
    struct Shape super;
    int x;
    int y;
    int r;
};
struct Triangle {
    struct Shape super;
    POINT p1;
    POINT p2;
    POINT p3;
};
这里有一个需要注意的地方, 父对象与子对象的内存排布必须重合
例如:下图中,上面的两个对象内存排布可以重合。而下面的两个对象的内存排布无法重合。

如果父对象和子对象的内存排布不重合会出现错误


像下面一样的声明 Rect 是正确的。
// 正确
struct Rect {
    struct Shape super;
    int left;
    int top;
    int right;
    int bottom;
};
而下面一样的声明 Rect 是错误的。
// 错误
struct Rect {
    int left;
    int top;
    int right;
    int bottom;
    struct Shape super;
};
接着,我们需要修改各对象的初始化函数。将原有的 r->draw 改为 r->super.draw
void initRect(struct Rect* r)
{
    r->super.draw = drawRect;
}
void initCircle(struct Circle* c)
{
    c->super.draw = drawCircle;
}
void initTriangle(struct Triangle* t)
{
    t->super.draw = drawTriangle;
}
注意,这里还有一个问题,函数内赋值运算符左边的函数指针 r->super.draw 的类型 void (*)(struct Shape*) ,参数为 struct Shape * 。而赋值运算符右边的函数指针类型分别为:
  • void (*)(struct Rect*)
  • void (*)(struct Circle*)
  • void (*)(struct Triangle*)
函数指针参数类型不一致,无法进行赋值。我们可以把右边的函数指针强制类型转换为 void (*)(struct Shape*)
void initRect(struct Rect* r)
{
    r->super.draw = (void (*)(struct Shape*))drawRect;
}
void initCircle(struct Circle* c)
{
    c->super.draw = (void (*)(struct Shape*))drawCircle;
}
void initTriangle(struct Triangle* t)
{
    t->super.draw = (void (*)(struct Shape*))drawTriangle;
}
我们考虑一下怎样来使用这些对象。
struct Rect r = { {}, - 200, 200, 200, 0 };
struct Circle c = { {},0, 0, 100 };
struct Triangle t = { {}, {0, 200}, {-200, 0}, {200, 0} };
首先,声明 Rect Circle Triangle 3 个对象,并使用初始化列表将其初始化。注意,由于它们的第一个成员为 super ,所以,这里使用空列表 {} ,将 super 成员初始化为零。
initRect(&r);
initCircle(&c);
initTriangle(&t); 

让三个对象分别调用各自的初始化函数,给各自对象 super 成员中的 draw 设置为各自对应的绘图函数。

r.super.draw 设置为 drawRect
c.super.draw 设置为 drawCircle
t.super.draw 设置为 drawRTriangle
struct Shape *arrShape[3] = {
(struct Shape *)&r, (struct Shape*)&c, (struct Shape*)&t}; 
声明一个元素类型为 struct Shape * 的数组,元素个数为 3 。分别用 r 的指针, c 的指针, t 的指针初始化。注意,这里也需要进行强制类型转换,否则初始化列表里面的指针类型和数组元素的指针类型不 一致。
for (int i = 0; i < 3; i++)
{
    arrShape[i]->draw(arrShape[i]);
} 

到了关键的一步,使用循环,依次调用 draw 函数。由于3次循环中的 draw 函数分别为各个图形各自的 绘图函数。所以,虽然统一调用的是 draw ,但是,却可以执行它们各自的绘图函数。至此,不同实现 的方法,在此得到统一。

完整代码实现

#define  _CRT_SECURE_NO_WARNINGS
#include <stdint.h>
#include <stdio.h>
#include <easyx.h>
#include <stdlib.h>

struct Shape
{
	void (*draw)(struct Shape*);
};

struct Rect 
{
	struct Shape super;

	int left;
	int top;
	int right;
	int bottom;
};

struct Circle 
{
	struct Shape super;

	int x;
	int y;
	int r;
};

struct Triangle 
{
	struct Shape super;

	POINT p1;
	POINT p2;
	POINT p3;
};

void drawRect(struct Rect* r) 
{
	rectangle(r->left, r->top, r->right, r->bottom);
}

void drawCircle(struct Circle* c) 
{
	circle(c->x, c->y, c->r);
}

void drawTriangle(struct Triangle* t)
{
	line(t->p1.x, t->p1.y, t->p2.x, t->p2.y);
	line(t->p2.x, t->p2.y, t->p3.x, t->p3.y);
	line(t->p3.x, t->p3.y, t->p1.x, t->p1.y);
}

void InitRect(struct Rect* r)
{
	r->super.draw = (void(*)(struct Shape*)) drawRect;
}

void InitCircle(struct Circle* c)
{
	c->super.draw = (void(*)(struct Shape*))drawCircle;
}

void InitTriangle(struct Triangle* t)
{
	t->super.draw = (void(*)(struct Shape*))drawTriangle;
}

int main()
{
	initgraph(800, 600);
	setaspectratio(1, -1);
	setorigin(400, 300);

	setbkcolor(WHITE);
	setlinecolor(BLACK);
	cleardevice();

	struct Rect r = { {}, - 200, 200, 200, 0 };
	struct Circle c = { {},0, 0, 100 };
	struct Triangle t = { {}, {0, 200}, {-200, 0}, {200, 0} };
	
	InitRect(&r);
	InitCircle(&c);
	InitTriangle(&t);

	struct Shape* arrShape[3] =
	{
		(struct Shape*)&r,
		(struct Shape*)&c,
		(struct Shape*)&t
	};

	for (int i = 0; i < 3; i++) 
	{
		arrShape[i]->draw(arrShape[i]);
	}

	getchar();
	closegraph();
	return 0;
}


让我们回顾一下在之前实现多态的步骤:
1. 抽离出各个对象中共有的方法 draw ,将其单独放置在一个对象 Shape 内。
2. 各个对象均继承于 Shape 对象。
3. 将各个子对象中的 draw 方法,设置为各自的实现方法。
4. 声明一个 Shape 对象的指针,并将其赋值为一个子对象的指针。
5. 通过上述对象指针,调用方法共有方法 draw ,执行的是第三步中设置的方法。

注:参考你好编程C语言教程编写,仅供学习参考

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2248096.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

滑动窗口最大值(java)

题目描述 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输入&#xff1a;nums [1,3,-1,-3,5,3,6,7]…

拥抱极简主义前端开发:NoCss.js 引领无 CSS 编程潮流

在前端开发的世界里&#xff0c;我们总是在不断追寻更高效、更简洁的方式来构建令人惊艳的用户界面。而今天&#xff0c;我要向大家隆重介绍一款具有创新性的工具 ——NoCss.js&#xff0c;它将彻底颠覆你对传统前端开发的认知&#xff0c;引领我们进入一个全新的无 CSS 编程时…

配置Springboot+vue项目在ubuntu20.04

一、jdk1.8环境配置 (1) 安装jdk8&#xff1a; sudo apt-get install openjdk-8-jdk (2) 检查jdk是否安装成功&#xff1a; java -version(3) 设置JAVA_HOME&#xff1a; echo export JAVA_HOME/usr/lib/jvm/java-17-openjdk-amd64 >> ~/.bashrc echo export PATH$J…

Spring框架特性及包下载(Java EE 学习笔记04)

1 Spring 5的新特性 Spring 5是Spring当前最新的版本&#xff0c;与历史版本对比&#xff0c;Spring 5对Spring核心框架进行了修订和更新&#xff0c;增加了很多新特性&#xff0c;如支持响应式编程等。 更新JDK基线 因为Spring 5代码库运行于JDK 8之上&#xff0c;所以Spri…

软考教材重点内容 信息安全工程师 第 5 章 物理与环境安全技术

5.1.1 物理安全概念 传统上的物理安全也称为实体安全&#xff0c;是指包括环境、设备和记录介质在内的所有支持网络信息系统运行的硬件的总体安全&#xff0c;是网络信息系统安全、可靠、不间断运行的基本保证&#xff0c;并且确保在信息进行加工处理、服务、决策支持的过程中&…

「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用

1. 谷歌浏览器安装及使用流程 1.1 准备篡改猴扩展程序包。 因为谷歌浏览器的扩展商城打不开&#xff0c;所以需要准备一个篡改猴压缩包。 其他浏览器只需打开扩展商城搜索篡改猴即可。 没有压缩包的可以进我主页下载。 也可直接点击下载&#xff1a;Chrome浏览器篡改猴(油猴…

【案例学习】如何使用Minitab实现包装过程的自动化和改进

Masimo 是一家全球性的医疗技术公司&#xff0c;致力于开发和生产各种行业领先的监控技术&#xff0c;包括创新的测量、传感器和患者监护仪。在 Masimo Hospital Automation 平台的助力下&#xff0c;Masimo 的连接、自动化、远程医疗和远程监控解决方案正在改善医院内外的护理…

Git旧文件覆盖引发思考

一天&#xff0c;我的同事过来找到我&#xff0c;和我讲&#xff1a;张叫兽&#xff0c;大事不好&#xff0c;我的文件被人覆盖了。git是真的不好用啊 git不好用&#xff1f;文件被覆盖&#xff1b;瞬间我似乎知道了什么&#xff0c;让我想到了某位男明星的语法&#xff1a;他…

CSP/信奥赛C++语法基础刷题训练(23):洛谷P1217:[USACO1.5] 回文质数 Prime Palindromes

CSP/信奥赛C语法基础刷题训练&#xff08;23&#xff09;&#xff1a;洛谷P1217&#xff1a;[USACO1.5] 回文质数 Prime Palindromes 题目描述 因为 151 151 151 既是一个质数又是一个回文数&#xff08;从左到右和从右到左是看一样的&#xff09;&#xff0c;所以 151 151 …

嵌入式系统与OpenCV

目录 一、OpenCV 简介 二、嵌入式 OpenCV 的安装方法 1. Ubuntu 系统下的安装 2. 嵌入式 ARM 系统中的安装 3. Windows10 和树莓派系统下的安装 三、嵌入式 OpenCV 的性能优化 1. 介绍嵌入式平台上对 OpenCV 进行优化的必要性。 2. 利用嵌入式开发工具&#xff0c;如优…

SAP BC 记录一次因为HANA服务器内存满的问题

用户操作 DB02 进入hana数据库服务器 free -g 内存用完了 如下图 解决方案&#xff1a;增加内存 操作 关应用服务->关闭数据库服务->关闭hana服务器->加内存->起hana服务器->起hana服务->启动应用服务。

ArcGIS应用指南:ArcGIS制作局部放大地图

在地理信息系统&#xff08;GIS&#xff09;中&#xff0c;制作详细且美观的地图是一项重要的技能。地图制作不仅仅是简单地将地理数据可视化&#xff0c;还需要考虑地图的可读性和美观性。局部放大图是一种常见的地图设计技巧&#xff0c;用于展示特定区域的详细信息&#xff…

python画图|无坐标轴自由划线操作fig.add_artist(lines.Line2D()函数

【1】引言 新发现了一种自由划线操作函数&#xff0c;和大家共享。 【2】官网教程 点击下述代码&#xff0c;直达官网&#xff1a; https://matplotlib.org/stable/gallery/misc/fig_x.html#sphx-glr-gallery-misc-fig-x-py 官网代码非常简洁&#xff0c;我进行了解读。 …

深度解析:Nginx模块架构与工作机制的奥秘

文章目录 前言Nginx是什么?Ngnix特点&#xff1a; 一、Nginx模块与工作原理1.Nginx的模块1.1 Nginx模块常规的HTTP请求和响应的流程图:1.2 Nginx的模块从结构上分为如下三类&#xff1a;1.3 Nginx的模块从功能上分为如下三类: 2.Nginx的进程模型2.1 Nginx进程结构2.2 nginx进程…

抖音SEO矩阵系统:开发技术分享

市场环境剖析 短视频SEO矩阵系统是一种策略&#xff0c;旨在通过不同平台上的多个账号建立联系&#xff0c;整合同一品牌下的各平台粉丝流量。该系统通过遵循每个平台的规则和内容要求&#xff0c;输出企业和品牌形象&#xff0c;以矩阵形式增强粉丝基础并提升商业价值。抖音作…

面试经典 150 题:205,55

205. 同构字符串 【解题思路】 来自大佬Krahets 【参考代码】 class Solution { public:bool isIsomorphic(string s, string t) {map<char, char> Smap, Tmap;for(int i0; i<s.size(); i){char a s[i], b t[i];//map容器存在该字符&#xff0c;且不等于之前映射…

STM32 Keil5 attribute 关键字的用法

这篇文章记录一下STM32中attribute的用法。之前做项目的时候产品需要支持远程升级&#xff0c;要求版本只能向上迭代&#xff0c;不支持回退。当时想到的方案是把版本号放到bin文件的头部&#xff0c;设备端收到bin文件的首包部数据后判断是否满足升级要求&#xff0c;这里就可…

【Redis 缓存策略】更新、穿透、雪崩、击穿、布隆过滤

目录 缓存简单介绍 缓存更新策略 缓存更新需求 数据库缓存不一致解决方案 先操作缓存还是先操作数据库&#xff1f; 先删除缓存&#xff0c;再操作数据库 先操作数据库&#xff0c;再删除缓存 总结 删除缓存还是更新缓存&#xff1f; 保证缓存与数据库的操作的同时成功或失败 …

利用c语言详细介绍下栈的实现

数据结构中&#xff0c;栈是一种线性结构&#xff0c;数据元素遵循后进先出的原则。栈的一端为栈顶&#xff0c;一端为栈底或栈尾&#xff0c;数据只在栈顶端进行操作。新插入数据称为入栈或者压栈&#xff0c;删除数据叫做出栈或者退栈。 一、图文介绍 我们通过建立一个stack…

5.5 W5500 TCP服务端与客户端

文章目录 1、TCP介绍2、W5500简介2.1 关键函数socketlistensendgetSn_RX_RSRrecv自动心跳包检测getSn_SR 1、TCP介绍 TCP 服务端&#xff1a; 创建套接字[socket]&#xff1a;服务器首先创建一个套接字&#xff0c;这是网络通信的端点。绑定套接字[bind]&#xff1a;服务器将…