如果使用过C++、C#、Java语言,一定知道面向对象编程,这些语言对面向对象编程的支持是语言级别的。C语言在语言级别不支持面向对象,那可以实现面向对象吗?其实面向对象是一种思想,而不是一种语言,很多初学者很容易把这个概念搞错!

面向对象编程(OOP)有三大特性:封装、继承、多态,这里以实现矩形与圆形计算面积为例来说明,C++代码如下:

 1#include <iostream>
 2using namespace std;
 3
 4typedef enum {
 5	Black,
 6	Red,
 7	White,
 8	Yellow,
 9}Color;
10
11class CShape {
12private:
13	const char* m_name;
14	Color color;
15
16public:
17	CShape(const char* name) {
18		m_name = name;
19		color = Black;
20		cout << "CShape Ctor:" << name << endl;
21	}
22	virtual int GetArea() = 0;
23	virtual ~CShape() {
24		cout << "CShape Dtor" << endl;
25	}
26};
27
28class CRect : public CShape {
29private:
30	int _w, _h;
31public:
32	CRect(int w, int h) : CShape("Rect"), _w(w), _h(h) {
33		cout << "CRect Ctor" << endl;
34	}
35
36	virtual ~CRect() {
37		cout << "CRect Dtor" << endl;
38	}
39
40	virtual int GetArea() {
41		cout << "CRect GetArea" << endl;
42		return _w * _h;
43	}
44};
45
46class CCircle : public CShape {
47private:
48	int _r;
49public:
50	CCircle(int r): CShape("Circle"), _r(r) {
51		cout << "CCircle Ctor" << endl;
52	}
53
54	virtual ~CCircle() {
55		cout << "CCircle Dtor" << endl;
56	}
57
58	virtual int GetArea() {
59		cout << "CCircle GetArea" << endl;
60		return 3.14 * _r * _r;
61	}
62};
63
64extern "C" void testCC() {
65	cout << "CRect Size:" << sizeof(CRect) << endl;
66	cout << "CCircle Size:" << sizeof(CCircle) << endl;
67	CRect* r = new CRect(10, 20);
68	int a1 = r->GetArea();
69	CCircle* c = new CCircle(10);
70	int a2 = c->GetArea();
71	delete r;
72	delete c;
73}

先定义了一个形状类CShape,类中定义了一个构造函数CShape、一个虚析构函数~CShape和一个纯虚函数GetArea,矩形以及圆形都继承CShape并各自实现了自己的构造函数、析构函数和计算面积的GetArea函数,调用GetArea函数会调用到各自的实现。

分别看一下CShape、CRect以及CCircle的内存布局:

在这里插入图片描述 在这里插入图片描述

在这里插入图片描述 可以看到CShape占24字节,CRect以及CCircle都占32字节。

C++很容易就在语言级别实现了面向对象编程,C语言在语言级别无法支持面向对象,就需要手动模拟。

先来看看Shape的定义,它与C++的CShape类等效:

 1typedef struct _Shape Shape;
 2typedef int (*ShapeGetArea)(Shape*);
 3typedef void (*ShapeDtor)(Shape*);
 4
 5// 定义Shape的虚函数,以实现多态
 6typedef struct {
 7	ShapeGetArea GetArea; // 计算面积
 8	ShapeDtor Dtor; // 析构函数
 9} vtShape;
10
11typedef enum {
12	Black,
13	Red,
14	White,
15	Yellow,
16}Color;
17
18struct _Shape {
19	const vtShape* vtb; // 指向虚函数表
20	// 公有变量
21	char* name;
22	Color color;
23};
24
25// Shape 的构造函数
26void shapeCtor(Shape* p, const char* name) {
27	printf("shapeCtor:%s\n", name);
28	p->name = strdup(name);
29}
30// Shape 的析构函数
31void shapeDtor(Shape* p) {
32	printf("shapeDtor:%s\n", p->name);
33	free(p->name);
34}

为什么Shape中使用一个vtShape指针,而不是把计算面积的函数指针直接放在Shape中? vtShape指针指向的是虚函数表,在代码中矩形与圆形的虚函数表各自只有唯一的一份,这样不管构建了多少实例,每个实例都只有一个指向虚函数表的指针,节约了内存空间。 这样就与C++的类的实现完全一致,可以看一下内存布局:

在这里插入图片描述

再来看矩形与圆形的头文件定义:

 1typedef struct {
 2	Shape; // 继承Shape
 3}Rect;
 4
 5typedef struct {
 6	Shape; // 继承Shape
 7}Circle;
 8
 9Rect* newRect(int w, int h);
10Circle* newCircle(int r);

矩形的实现:

 1
 2typedef struct {
 3	Rect; // 这里继承头文件中公开的Rect定义
 4	// 下面定义私有变量
 5	int w, h;
 6}realRect;
 7
 8// 计算矩形面积
 9static int RectArea(realRect* s) {
10	printf("Rect GetArea\n");
11	return s->w * s->h;
12}
13
14// 矩形的析构函数
15static void RectRelease(realRect* s) {
16	if (s) {
17		printf("Rect Dtor:%s\n", s->name);
18		shapeDtor((Shape*)s);
19		free(s);
20	}
21}
22
23// 矩形的虚函数表
24// 虚函数表只有唯一的一份,这样不管构建了多少实例,
25// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
26static const vtShape vtRect = {
27	.GetArea = (ShapeGetArea)RectArea,
28	.Dtor = (ShapeDtor)RectRelease,
29};
30
31Rect* newRect(int w, int h) {
32	// 以realRect大小分配内存
33	realRect* p = calloc(1, sizeof(realRect));
34	if (NULL == p)
35		return NULL;
36	// 调用基类的构造函数
37	shapeCtor((Shape*)p, "Rect");
38	// 设置虚函数表
39	p->vtb = &vtRect;
40	p->h = h;
41	p->w = w;
42	printf("Rect Ctor\n");
43	printf("Rect Size:%zd\n", sizeof(realRect));
44	return (Rect*)p;
45}

圆形的实现:

 1typedef struct {
 2	Circle; // 这里继承头文件中公开的Circle定义
 3	// 下面定义私有变量
 4	int r;
 5}realCircle;
 6
 7// 计算圆形面积
 8static int CircleArea(realCircle* s) {
 9	printf("Circle GetArea\n");
10	return (int)(3.14 * s->r * s->r);
11}
12
13// 圆形的析构函数
14static void CircleRelease(realCircle* s) {
15	if (s) {
16		printf("Circle Dtor:%s\n", s->name);
17		shapeDtor((Shape*)s);
18		free(s);
19	}
20}
21
22// 圆形的虚函数表
23// 虚函数表只有唯一的一份,这样不管构建了多少实例,
24// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
25static const vtShape vtCircle = {
26	.GetArea = (ShapeGetArea)CircleArea,
27	.Dtor = (ShapeDtor)CircleRelease,
28};
29
30Circle* newCircle(int r) {
31	realCircle* p = calloc(1, sizeof(realCircle));
32	if (NULL == p)
33		return NULL;
34
35	shapeCtor((Shape*)p, "Circle");
36	p->vtb = &vtCircle;
37	p->r = r;
38	printf("Circle Ctor\n");
39	printf("Circle Size:%zd\n", sizeof(realCircle));
40	return (Circle*)p;
41}

定义了好了后,就可以使用它们来计算面积了,示例代码:

 1Rect* r = newRect(10, 20);
 2Circle* c = newCircle(10);
 3r->color = Red;
 4c->color = Yellow;
 5const vtShape* rt = r->vtb;
 6const vtShape* ct = c->vtb;
 7int a1 = rt->GetArea((Shape*)r);
 8int a2 = ct->GetArea((Shape*)c);
 9rt->Dtor((Shape*)r);
10ct->Dtor((Shape*)c);

完整代码:

shape.h

 1#pragma once
 2
 3typedef struct _Shape Shape;
 4typedef int (*ShapeGetArea)(Shape*);
 5typedef void (*ShapeDtor)(Shape*);
 6
 7// 定义Shape的虚函数,以实现多态
 8typedef struct {
 9	ShapeGetArea GetArea;
10	ShapeDtor Dtor;
11} vtShape;
12
13typedef enum {
14	Black,
15	Red,
16	White,
17	Yellow,
18}Color;
19
20struct _Shape {
21	const vtShape* vtb; // 指向虚函数表
22	char* name;
23	Color color;
24};
25
26// Shape 的构造函数
27void shapeCtor(Shape* shape, const char* name);
28// Shape 的析构函数
29void shapeDtor(Shape* shape);
30
31typedef struct {
32	Shape; // 继承Shape
33}Rect;
34
35typedef struct {
36	Shape; // 继承Shape
37}Circle;
38
39Rect* newRect(int w, int h);
40Circle* newCircle(int r);

shape.c

  1#include <string.h>
  2#include <stdio.h>
  3#include <stdlib.h>
  4#include "shape.h"
  5
  6void shapeCtor(Shape* p, const char* name) {
  7	printf("shapeCtor:%s\n", name);
  8	p->name = strdup(name);
  9}
 10
 11void shapeDtor(Shape* p) {
 12	printf("shapeDtor:%s\n", p->name);
 13	free(p->name);
 14}
 15
 16
 17typedef struct {
 18	Rect; // 这里继承头文件中公开的Rect定义
 19	// 下面定义私有变量
 20	int w, h;
 21}realRect;
 22
 23// 计算矩形面积
 24static int RectArea(realRect* s) {
 25	printf("Rect GetArea\n");
 26	return s->w * s->h;
 27}
 28
 29// 矩形的析构函数
 30static void RectRelease(realRect* s) {
 31	if (s) {
 32		printf("Rect Dtor:%s\n", s->name);
 33		shapeDtor((Shape*)s);
 34		free(s);
 35	}
 36}
 37
 38// 矩形的虚函数表
 39// 虚函数表只有唯一的一份,这样不管构建了多少实例,
 40// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
 41static const vtShape vtRect = {
 42	.GetArea = (ShapeGetArea)RectArea,
 43	.Dtor = (ShapeDtor)RectRelease,
 44};
 45
 46Rect* newRect(int w, int h) {
 47	// 以realRect大小分配内存
 48	realRect* p = calloc(1, sizeof(realRect));
 49	if (NULL == p)
 50		return NULL;
 51	// 调用基类的构造函数
 52	shapeCtor((Shape*)p, "Rect");
 53	// 设置虚函数表
 54	p->vtb = &vtRect;
 55	p->h = h;
 56	p->w = w;
 57	printf("Rect Ctor\n");
 58	printf("Rect Size:%zd\n", sizeof(realRect));
 59	return (Rect*)p;
 60}
 61
 62
 63typedef struct {
 64	Circle; // 这里继承头文件中公开的Circle定义
 65	// 下面定义私有变量
 66	int r;
 67}realCircle;
 68
 69// 计算圆形面积
 70static int CircleArea(realCircle* s) {
 71	printf("Circle GetArea\n");
 72	return (int)(3.14 * s->r * s->r);
 73}
 74
 75// 圆形的析构函数
 76static void CircleRelease(realCircle* s) {
 77	if (s) {
 78		printf("Circle Dtor:%s\n", s->name);
 79		shapeDtor((Shape*)s);
 80		free(s);
 81	}
 82}
 83
 84// 圆形的虚函数表
 85// 虚函数表只有唯一的一份,这样不管构建了多少实例,
 86// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
 87static const vtShape vtCircle = {
 88	.GetArea = (ShapeGetArea)CircleArea,
 89	.Dtor = (ShapeDtor)CircleRelease,
 90};
 91
 92Circle* newCircle(int r) {
 93	realCircle* p = calloc(1, sizeof(realCircle));
 94	if (NULL == p)
 95		return NULL;
 96
 97	shapeCtor((Shape*)p, "Circle");
 98	p->vtb = &vtCircle;
 99	p->r = r;
100	printf("Circle Ctor\n");
101	printf("Circle Size:%zd\n", sizeof(realCircle));
102	return (Circle*)p;
103}

shape.cc

 1#include <iostream>
 2using namespace std;
 3
 4typedef enum {
 5	Black,
 6	Red,
 7	White,
 8	Yellow,
 9}Color;
10
11class CShape {
12private:
13	const char* m_name;
14	Color color;
15
16public:
17	CShape(const char* name) {
18		m_name = name;
19		color = Black;
20		cout << "CShape Ctor:" << name << endl;
21	}
22	virtual int GetArea() = 0;
23	virtual ~CShape() {
24		cout << "CShape Dtor" << endl;
25	}
26};
27
28class CRect : public CShape {
29private:
30	int _w, _h;
31public:
32	CRect(int w, int h) : CShape("Rect"), _w(w), _h(h) {
33		cout << "CRect Ctor" << endl;
34	}
35
36	virtual ~CRect() {
37		cout << "CRect Dtor" << endl;
38	}
39
40	virtual int GetArea() {
41		cout << "CRect GetArea" << endl;
42		return _w * _h;
43	}
44};
45
46class CCircle : public CShape {
47private:
48	int _r;
49public:
50	CCircle(int r): CShape("Circle"), _r(r) {
51		cout << "CCircle Ctor" << endl;
52	}
53
54	virtual ~CCircle() {
55		cout << "CCircle Dtor" << endl;
56	}
57
58	virtual int GetArea() {
59		cout << "CCircle GetArea" << endl;
60		return 3.14 * _r * _r;
61	}
62};
63
64extern "C" void testCC() {
65	cout << "CRect Size:" << sizeof(CRect) << endl;
66	cout << "CCircle Size:" << sizeof(CCircle) << endl;
67	CRect* r = new CRect(10, 20);
68	int a1 = r->GetArea();
69	CCircle* c = new CCircle(10);
70	int a2 = c->GetArea();
71	delete r;
72	delete c;
73}

main.c

 1#include "shape.h"
 2
 3void testCC();
 4
 5int main(){
 6	testCC();
 7	printf("\n\n");
 8	Rect* r = newRect(10, 20);
 9	Circle* c = newCircle(10);
10	r->color = Red;
11	c->color = Yellow;
12	const vtShape* rt = r->vtb;
13	const vtShape* ct = c->vtb;
14	int a1 = rt->GetArea((Shape*)r);
15	int a2 = ct->GetArea((Shape*)c);
16	rt->Dtor((Shape*)r);
17	ct->Dtor((Shape*)c);
18	return 0;
19}

CMakeLists.txt

 1cmake_minimum_required(VERSION 3.25.0)
 2project(cobj VERSION 0.1.0)
 3
 4if(CMAKE_C_COMPILER_ID MATCHES "Clang")
 5add_compile_options(
 6	-fms-extensions
 7	-Wno-microsoft-anon-tag
 8)
 9endif()
10
11add_executable(${PROJECT_NAME} ${SRC} shape.c shape.cc main.c)

运行结果:

在这里插入图片描述

这就是C实现面向对象的原理,如果有兴趣的读者可以看看glib中的gobject,不过要看懂它的代码,掌握原理是非常重要的!

如对你有帮助,欢迎点赞收藏!