C语言之面向对象编程

面向对象编程(OOP)四大特征:抽象、封装、继承、多态。主流的面向对象编程语言(如C++、Java、C#等)都有完善的面向对象实现机制。

C语言是面向过程编程语言,但可以通过结构体和指针实现类似的面向对象语言功能。所以,我更倾向于将其理解为一种编程思想,而不是面向对象编程语言仅有的特性。

本文基于一个实例,演示如何在C语言中实现多态。

定义函数指针

1
2
3
4
5
6
//基类函数成员
typedef int (*fptrSet)(void *, int);typedef int (*fptrGet)(void *);
typedef void (*fptrDisplay)(void *);

//派生类新增函数成员
typedef double (*fptrGetArea)(void *);

定义Shape基类

(1)定义“基类”,包含数据成员和函数成员,其中函数成员用函数指针定义。

(2)实现函数成员,定义为独立的函数,参数为结构体指针。

(3)实现“构造函数”,以参数形式将数据成员传递进来,直接用(2)中定义的函数给函数成员赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//定义基类Shape
typedef struct _shape
{
//定义基类函数成员
struct
{
fptrSet setX;
fptrGet getX;
fptrSet setY;
fptrGet getY;
fptrDisplay display;
};
//定义基类数据成员
int x;
int y;
} Shape;

//函数成员的实现,独立函数还未绑定
void shapeDisplay(Shape *shape)
{
printf("Shape\n");
}
void shapeSetX(Shape *shape, int x)
{
shape->x = x;
}
void shapeSetY(Shape *shape, int y)
{
shape->y = y;
}
int shapeGetX(Shape *shape)
{
return shape->x;
}
int shapeGetY(Shape *shape)
{
return shape->y;
}
//定义构造函数:数据成员以参数传值,绑定独立的函数(为函数成员赋值)
Shape *shapeConstructor(int x, int y)
{
Shape *shape = (Shape *)malloc(sizeof(Shape));
shape->x = x;
shape->y = y;
shape->display = shapeDisplay;
shape->setX = shapeSetX;
shape->setY = shapeSetY;
shape->getX = shapeGetX;
shape->getY = shapeGetY;
return shape;
}

定义Rectangle派生类

(1)基于C++及Java等面向对象程序设计语言中派生类的特性来实现该派生类。

(2)测试以下几点:派生类覆盖基类函数成员display;派生类新增数据成员width、height;派生类新增函数成员func1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
   //定义派生类
typedef struct _rectangle
{
//维护一个基类成员变量,必须为变量,不能用指针
Shape base;

//派生类额外的函数成员
fptrGetArea func1;

//派生类额外的数据成员
int width;
int height;
} Rectangle;

//覆盖基类的dispalay方法
void rectangleDisplay(Rectangle *rectangle)
{
printf("Rectangle\n");
}

//实现派生类额外的函数成员
double getArea(Rectangle *rectangle)
{
return rectangle->width * rectangle->height;
}

//定义构造函数
Rectangle *rectangleConstructor(int width, int height, int x, int y)
{
Rectangle *rectangle = (Rectangle *)malloc(sizeof(Rectangle));

//初始化派生类中额外的成员
rectangle->width = width;
rectangle->height = height;

rectangle->func1 = getArea; //此处初始化新增函数成员

//初始化派生类中的基类变量
rectangle->base = (Shape){.x = x,
.y = y,
.display = rectangleDisplay, //此处覆盖基类函数成员
.setX = shapeSetX,
.setY = shapeSetY,
.getX = shapeGetX,
.getY = shapeGetY};
return rectangle;
}

测试

用基类指针指向派生类对象,调用基类方法时,可选择调用基类或派生类的方法实现,原理在于shapes[1]- >display会访问实际函数地址,如果该函数已被派生类覆盖,则调用派生类的版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main()
{
//构造三个对象
Shape *shapes[3];
shapes[0] = shapeConstructor(0, 0);shapes[1] = rectangleConstructor(100, 200, 50, 50);
shapes[2] = shapeConstructor(600, 600);

//输出三个对象
for (int i = 0; i < 3; i++)
{
shapes[i]->display(shapes[i]);printf("%d\n", shapes[i]->getX(shapes[i]));
}

//输出shapes[1]中display函数的地址,两者相等,是该多态访问成功的基础
printf("%p\n", ((Rectangle *)shapes[1])->base.display);
printf("%p\n", shapes[1]->display);

//输出shapes[1]的面积,对于派生类特有的函数,应该转变为派生类指针后再访问
printf("area=%lf\n", ((Rectangle *)shapes[1])->func1(shapes[1]));

return 0;
}

评论