C 语言面向对象

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


1.0 面向过程概念


当我们在编写程序时,通常采用以下步骤:

  1. 将问题的解法分解成若干步骤
  2. 使用函数分别实现这些步骤
  3. 依次调用这些函数

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


2.0 程序案例


cpp 复制代码
#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 封装特性


我们来看看学校里面最重要的主体是什么?是学生,学生肯定拥有很多属性,比如学生的学号、姓名、性别、考试分数等等。自然地,我们会声明一个结构体用于表示学生。

cpp 复制代码
typedef struct
{
    int id;
    char name[20];
    int gender;
    int mark;
}StudentInfo_t;

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


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

cpp 复制代码
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。

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

设置数据的方法

cpp 复制代码
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;
}

通过主函数进行调用

cpp 复制代码
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);
}

完整函数代码

cpp 复制代码
#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);
}

目前,函数可以直接操作数据了。但是,函数和数据依然是两个独立的部分。我们要将 函数和数据结合 到一起,这样,这个整体就能被称作 对象 ,函数可以称作属于这个对象的 方法当前我们可以吧结构体理解为我们的数据,函数可以理解为我们的方法,数据和方法结合在一起可以称之为对象


cpp 复制代码
对象.方法(对象指针,参数1,参数2, 参数3...)

接下来,我们举几个这种格式的例子:

cpp 复制代码
stu.setGender(&stu, "男"); 

以上代码中,对象为 stu ,方法为 setGender 。通过 对象 + + 方法 的形式,可以调用属于对
象 stu 的 setGender 方法。在方法的参数中传入性别 男 。这样,方法会把性别 男 转换为整形,并设置到对象 stu 的数据当中。

cpp 复制代码
const char* gender = stu.getGender(&stu); 

以上代码中, 对象为 stu ,方法为 getGender 。 通过对象 + 点 + 方法的形式,可以调用属于对
象 stu 的 getGender 方法。 getGender 方法从对象数据中获取整形表示的性别,并返回性别对应的字符 你好编程 串。 在 C 语言中, 若要实现对象 + 点 + 方法的形式,我们可以借助于函数指针。
在结构中,声明这3个函数的函数指针。


5.0 面向对象

在结构体中声明函数指针

cpp 复制代码
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

cpp 复制代码
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;
}

获取学生的性别

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

设置学生的性别

cpp 复制代码
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;
}

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

cpp 复制代码
void initStudent(struct student* s)
{
    s->setStudentId = setStudentId;
    s->getGender = getGender;
    s->setGender = setGender;
}

主函数相关代码

cpp 复制代码
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);
}

完整代码

cpp 复制代码
#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. 任课科目

声明一个结构体用于表示老师。

cpp 复制代码
struct teacher 
{
    int id; // 工号
    char name[20]; // 姓名
    int gender; // 性别
    char subject[20]; // 任课科目
};

比较一下学生和老师的结构体,看看它们之间有什么共同之处与不同之处。

cpp 复制代码
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 。

cpp 复制代码
struct person{
    int id; // 编号
    char name[20]; // 姓名
    int gender; // 性别
};

接下来,我们可以让老师和学生结构包含这个 person 对象。

cpp 复制代码
struct teacher {
    struct person super;
    char subject[20]; // 任课科目
};
struct student {
    struct person super;
    int mark; // 分数
};

让我们比较一下原有代码与现有代码

cpp 复制代码
// 原有代码
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. 减少重复代码
    1. 代码层次更清晰

由于 student 和 teacher 拥有 person 的一切,因此,我们可以说, student 与 teacher 均 继承
于 person 。 person 是 student 与 teacher 的父对象。 student 与 teacher 是 person 的子对象。


刚刚我们只讨论了数据,现在我们结合上方法一起讨论

cpp 复制代码
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方法,内部包含

cpp 复制代码
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 * 。

cpp 复制代码
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 中。这里也要对应的修改一下。

cpp 复制代码
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;
}

还有,别忘了给结构初始化函数指针。

cpp 复制代码
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函数调用

cpp 复制代码
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);
}

完整代码

cpp 复制代码
#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 多态


cpp 复制代码
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 * 。


cpp 复制代码
struct Shape {
void (*draw)(struct Shape *);
};

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


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

cpp 复制代码
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 是正确的。

cpp 复制代码
// 正确
struct Rect {
    struct Shape super;
    int left;
    int top;
    int right;
    int bottom;
};

而下面一样的声明 Rect 是错误的。

cpp 复制代码
// 错误
struct Rect {
    int left;
    int top;
    int right;
    int bottom;
    struct Shape super;
};

接着,我们需要修改各对象的初始化函数。将原有的 r->draw 改为 r->super.draw 。

cpp 复制代码
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*) 。
cpp 复制代码
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;
}

我们考虑一下怎样来使用这些对象。

cpp 复制代码
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 成员初始化为零。

cpp 复制代码
initRect(&r);
initCircle(&c);
initTriangle(&t); 

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

cpp 复制代码
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 的指针初始化。注意,这里也需要进行强制类型转换,否则初始化列表里面的指针类型和数组元素的指针类型不 一致。

cpp 复制代码
for (int i = 0; i < 3; i++)
{
    arrShape[i]->draw(arrShape[i]);
} 

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

完整代码实现

cpp 复制代码
#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语言教程编写,仅供学习参考

相关推荐
hopetomorrow5 分钟前
学习路之PHP--使用GROUP BY 发生错误 SELECT list is not in GROUP BY clause .......... 解决
开发语言·学习·php
小牛itbull15 分钟前
ReactPress vs VuePress vs WordPress
开发语言·javascript·reactpress
请叫我欧皇i24 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
闲暇部落26 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
GIS瞧葩菜35 分钟前
局部修改3dtiles子模型的位置。
开发语言·javascript·ecmascript
chnming198740 分钟前
STL关联式容器之set
开发语言·c++
带多刺的玫瑰44 分钟前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
熬夜学编程的小王1 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
GIS 数据栈1 小时前
每日一书 《基于ArcGIS的Python编程秘笈》
开发语言·python·arcgis
Mr.131 小时前
什么是 C++ 中的初始化列表?它的作用是什么?初始化列表和在构造函数体内赋值有什么区别?
开发语言·c++