前言:
面向对象的基本特性:
- 封装,
- 继承,
- 多态
我们以图形的绘制来讲解面向对象当中的多态特性
我们有3种图形,它们分别为:
- 矩形
- 圆形
- 三角形
我们把这3种图形均看做对象,抽象出这些图形需要的属性和共性
- 矩形:左上角坐标、右下角坐标
- 圆形:圆心x坐标、圆心y坐标、半径
- 三角形:三个顶点坐标
对应定义的结构体:也就是数据,表示不同图形需要的成员变量,为了能够在屏幕上绘制这些图形,每个图形都设置一个名为 draw 的方法。分别实现3个不同的绘制函数。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <easyx.h>
typedef struct Rect // 结构体
{
void (*draw)(rcct_t*); // 创建函数指针,用于接收函数绘制图形
int left;
int top;
int right;
int bottom;
}rcct_t;
typedef struct Circle // 圆形结构体
{
void (*draw)(circle_t*);
int x;
int y;
int r;
}circle_t;
typedef struct Triangle // 三角形结构体
{
void (*draw)(triangle_t*);
POINT P1;
POINT P2;
POINT P3;
}triangle_t;
绘制矩形:调用 easyx 中的 rectangle 函数,传入左上角坐标与右下角坐标。
void DrawRect(rcct_t *r) // 绘制矩形
{
rectangle(r->left, r->top, r->right, r->bottom);
}
绘制圆形: 调用 easyx 中的 circle 函数,传入圆心坐标与半径。
void DrawCircle(circle_t* c) // 绘制圆形
{
circle(c->x, c->y, c->r);
}
绘制三角形:调用 easyx 中的 line 函数,分别绘制点 p1 到 p2 的线段, p2 到 p3 的线段,以及 p3 到 p1 的线段。
void DrawTriangle(triangle_t* 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);
}
分别写3个初始化函数,用于给对象中的函数指针 draw 进行赋值。
void initRect(struct Rect* r) // 初始化函数给函数正确的指向
{
r->draw = DrawRect;
}
void initCircle(struct Circle* r)
{
r->draw = DrawCircle;
}
void initTriangle(struct Triangle* r)
{
r->draw = DrawTriangle;
}
int main()
{
initgraph(800, 600);
setaspectratio(1, -1);
setorigin(400, 300);
setbkcolor(WHITE);
setlinecolor(BLACK);
cleardevice();
rcct_t r = {-200, 200, 200, 0}; // 定义一个矩形结构体变量r
circle_t c = {0, 0, 100}; // 定义一个圆结构体变量c
triangle_t t = {
{0, 200}, {-200, 0}, {200, 0}}; // 定义一个三角形结构体变量t
initRect(&r); // 初始化矩形
initCircle(&c); // 初始化圆
initTriangle(&t); // 初始化三角形
drawRect(&r); // 绘制矩形
drawCircle(&c); // 绘制圆
drawTriangle(&t); // 绘制三角形
getchar(); // 等待用户按键
closegraph(); // 关闭绘图窗口
return 0;
}
创建一个 800 * 600 的绘图窗体,设置 x 轴正方向为从左到右, y 轴正方向为从下到上。将原点坐标从 窗体左上角更改为窗体中心。设置背景颜色为白色,描边颜色为黑色,并使用背景色刷新整个窗体。下 面分别声明矩形、圆形、三角形三个对象,并将需要的属性初始化。之后,三个对象分别调用各自 的 init 函数,为对象内的函数指针赋值。完成准备工作后,即可使用对象+点+方法的形式,调用各自 的 draw 方法绘制图形了
struct Rect
{
void (*draw)(struct Rect*);
int left;
int top;
int right;
int bottom;
};
struct Circle
{
void (*draw)(struct Circle*);
int x, y, 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 这个方法了。
注:父对象与子对象的内存排布必须重合
接着,我们需要修改各对象的初始化函数。将原有的 r->draw 改为 r->super.draw
void InitRect(struct Rect* r)
{
r->super.draw = DrawRect;
}
void InitRect(struct Circle* r)
{
r->super.draw = DrawCircle;
}
void InitRect(struct Triangle* r)
{
r->super.draw = DrawTriangle;
}
注意,这里还有一个问题,函数内赋值运算符左边的函数指针 r->super.draw 的类型 为 void (*)(struct Shape*) ,参数为 struct Shape * 。而赋值运算符右边的函数指针类型分别为:出现函数参数类型不一致的情况。
(void (*)(struct Shape*))
函数参数左右两边不一致做强制类型转换
void InitRect(struct Rect* r)
{
// 函数类型参数不一致,进行强制类型转换
r->super.draw = (void (*)(struct Shape*))DrawRect;
}
void InitCircle(struct Circle* r)
{
r->super.draw = (void (*)(struct Shape*))DrawCircle;
}
void InitTriangle(struct Triangle* r)
{
r->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 设置为各自对应的绘图函 数。
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 <stdio.h>
#include <easyx.h>
struct Rect
{
struct Shape super;
int left;
int top;
int right;
int bottom;
};
struct Circle
{
struct Shape super;
int x, y, r;
};
struct Triangle
{
struct Shape super;
POINT p1;
POINT p2;
POINT p3;
};
struct Shape
{
void (*draw)(struct Shape*);
};
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* r)
{
r->super.draw = (void (*)(struct Shape*))DrawCircle;
}
void InitTriangle(struct Triangle* r)
{
r->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 ,执行的是第三步中设置的方法。