C语言面的向对象编程(OOP)
如果使用过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,不过要看懂它的代码,掌握原理是非常重要的!
如对你有帮助,欢迎点赞收藏!
- 原文作者:Witton
- 原文链接:https://wittonbell.github.io/posts/2025/2025-01-02-C语言面的向对象编程OOP/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议. 进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。