1-4 C多态特性

发布于:2025-02-12 ⋅ 阅读:(117) ⋅ 点赞:(0)

前言:


面向对象的基本特性:

  • 封装,
  • 继承,
  • 多态

我们以图形的绘制来讲解面向对象当中的多态特性

我们有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 ,执行的是第三步中设置的方法。


网站公告

今日签到

点亮在社区的每一天
去签到