C语言面的向对象编程(OOP)
如果使用过C++、C#、Java语言,一定知道面向对象编程,这些语言对面向对象编程的支持是语言级别的。C语言在语言级别不支持面向对象,那可以实现面向对象吗?其实面向对象是一种思想,而不是一种语言,很多初学者很容易把这个概念搞错!
面向对象编程(OOP)有三大特性:封装、继承、多态,这里以实现矩形与圆形计算面积为例来说明,C++代码如下:
#include <iostream>
using namespace std;
typedef enum {
Black,
Red,
White,
Yellow,
}Color;
class CShape {
private:
const char* m_name;
Color color;
public:
CShape(const char* name) {
m_name = name;
color = Black;
cout << "CShape Ctor:" << name << endl;
}
virtual int GetArea() = 0;
virtual ~CShape() {
cout << "CShape Dtor" << endl;
}
};
class CRect : public CShape {
private:
int _w, _h;
public:
CRect(int w, int h) : CShape("Rect"), _w(w), _h(h) {
cout << "CRect Ctor" << endl;
}
virtual ~CRect() {
cout << "CRect Dtor" << endl;
}
virtual int GetArea() {
cout << "CRect GetArea" << endl;
return _w * _h;
}
};
class CCircle : public CShape {
private:
int _r;
public:
CCircle(int r): CShape("Circle"), _r(r) {
cout << "CCircle Ctor" << endl;
}
virtual ~CCircle() {
cout << "CCircle Dtor" << endl;
}
virtual int GetArea() {
cout << "CCircle GetArea" << endl;
return 3.14 * _r * _r;
}
};
extern "C" void testCC() {
cout << "CRect Size:" << sizeof(CRect) << endl;
cout << "CCircle Size:" << sizeof(CCircle) << endl;
CRect* r = new CRect(10, 20);
int a1 = r->GetArea();
CCircle* c = new CCircle(10);
int a2 = c->GetArea();
delete r;
delete c;
}
先定义了一个形状类CShape
,类中定义了一个构造函数CShape
、一个虚析构函数~CShape
和一个纯虚函数GetArea
,矩形以及圆形都继承自CShape
并各自实现了自己的构造函数、析构函数和计算面积的GetArea
函数,调用GetArea
函数会调用到各自的实现。
分别看一下CShape、CRect以及CCircle的内存布局:
可以看到CShape占24字节,CRect以及CCircle都占32字节。
C++很容易就在语言级别实现了面向对象编程,C语言在语言级别无法支持面向对象,就需要手动模拟。
先来看看Shape的定义,它与C++的CShape类等效:
typedef struct _Shape Shape;
typedef int (*ShapeGetArea)(Shape*);
typedef void (*ShapeDtor)(Shape*);
// 定义Shape的虚函数,以实现多态
typedef struct {
ShapeGetArea GetArea; // 计算面积
ShapeDtor Dtor; // 析构函数
} vtShape;
typedef enum {
Black,
Red,
White,
Yellow,
}Color;
struct _Shape {
const vtShape* vtb; // 指向虚函数表
// 公有变量
char* name;
Color color;
};
// Shape 的构造函数
void shapeCtor(Shape* p, const char* name) {
printf("shapeCtor:%s\n", name);
p->name = strdup(name);
}
// Shape 的析构函数
void shapeDtor(Shape* p) {
printf("shapeDtor:%s\n", p->name);
free(p->name);
}
为什么Shape中使用一个vtShape指针,而不是把计算面积的函数指针直接放在Shape中? vtShape指针指向的是虚函数表,在代码中矩形与圆形的虚函数表各自只有唯一的一份,这样不管构建了多少实例,每个实例都只有一个指向虚函数表的指针,节约了内存空间。 这样就与C++的类的实现完全一致,可以看一下内存布局:
再来看矩形与圆形的头文件定义:
typedef struct {
Shape; // 继承Shape
}Rect;
typedef struct {
Shape; // 继承Shape
}Circle;
Rect* newRect(int w, int h);
Circle* newCircle(int r);
矩形的实现:
typedef struct {
Rect; // 这里继承头文件中公开的Rect定义
// 下面定义私有变量
int w, h;
}realRect;
// 计算矩形面积
static int RectArea(realRect* s) {
printf("Rect GetArea\n");
return s->w * s->h;
}
// 矩形的析构函数
static void RectRelease(realRect* s) {
if (s) {
printf("Rect Dtor:%s\n", s->name);
shapeDtor((Shape*)s);
free(s);
}
}
// 矩形的虚函数表
// 虚函数表只有唯一的一份,这样不管构建了多少实例,
// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
static const vtShape vtRect = {
.GetArea = (ShapeGetArea)RectArea,
.Dtor = (ShapeDtor)RectRelease,
};
Rect* newRect(int w, int h) {
// 以realRect大小分配内存
realRect* p = calloc(1, sizeof(realRect));
if (NULL == p)
return NULL;
// 调用基类的构造函数
shapeCtor((Shape*)p, "Rect");
// 设置虚函数表
p->vtb = &vtRect;
p->h = h;
p->w = w;
printf("Rect Ctor\n");
printf("Rect Size:%zd\n", sizeof(realRect));
return (Rect*)p;
}
圆形的实现:
typedef struct {
Circle; // 这里继承头文件中公开的Circle定义
// 下面定义私有变量
int r;
}realCircle;
// 计算圆形面积
static int CircleArea(realCircle* s) {
printf("Circle GetArea\n");
return (int)(3.14 * s->r * s->r);
}
// 圆形的析构函数
static void CircleRelease(realCircle* s) {
if (s) {
printf("Circle Dtor:%s\n", s->name);
shapeDtor((Shape*)s);
free(s);
}
}
// 圆形的虚函数表
// 虚函数表只有唯一的一份,这样不管构建了多少实例,
// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
static const vtShape vtCircle = {
.GetArea = (ShapeGetArea)CircleArea,
.Dtor = (ShapeDtor)CircleRelease,
};
Circle* newCircle(int r) {
realCircle* p = calloc(1, sizeof(realCircle));
if (NULL == p)
return NULL;
shapeCtor((Shape*)p, "Circle");
p->vtb = &vtCircle;
p->r = r;
printf("Circle Ctor\n");
printf("Circle Size:%zd\n", sizeof(realCircle));
return (Circle*)p;
}
定义了好了后,就可以使用它们来计算面积了,示例代码:
Rect* r = newRect(10, 20);
Circle* c = newCircle(10);
r->color = Red;
c->color = Yellow;
const vtShape* rt = r->vtb;
const vtShape* ct = c->vtb;
int a1 = rt->GetArea((Shape*)r);
int a2 = ct->GetArea((Shape*)c);
rt->Dtor((Shape*)r);
ct->Dtor((Shape*)c);
完整代码:
shape.h
:
#pragma once
typedef struct _Shape Shape;
typedef int (*ShapeGetArea)(Shape*);
typedef void (*ShapeDtor)(Shape*);
// 定义Shape的虚函数,以实现多态
typedef struct {
ShapeGetArea GetArea;
ShapeDtor Dtor;
} vtShape;
typedef enum {
Black,
Red,
White,
Yellow,
}Color;
struct _Shape {
const vtShape* vtb; // 指向虚函数表
char* name;
Color color;
};
// Shape 的构造函数
void shapeCtor(Shape* shape, const char* name);
// Shape 的析构函数
void shapeDtor(Shape* shape);
typedef struct {
Shape; // 继承Shape
}Rect;
typedef struct {
Shape; // 继承Shape
}Circle;
Rect* newRect(int w, int h);
Circle* newCircle(int r);
shape.c
:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "shape.h"
void shapeCtor(Shape* p, const char* name) {
printf("shapeCtor:%s\n", name);
p->name = strdup(name);
}
void shapeDtor(Shape* p) {
printf("shapeDtor:%s\n", p->name);
free(p->name);
}
typedef struct {
Rect; // 这里继承头文件中公开的Rect定义
// 下面定义私有变量
int w, h;
}realRect;
// 计算矩形面积
static int RectArea(realRect* s) {
printf("Rect GetArea\n");
return s->w * s->h;
}
// 矩形的析构函数
static void RectRelease(realRect* s) {
if (s) {
printf("Rect Dtor:%s\n", s->name);
shapeDtor((Shape*)s);
free(s);
}
}
// 矩形的虚函数表
// 虚函数表只有唯一的一份,这样不管构建了多少实例,
// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
static const vtShape vtRect = {
.GetArea = (ShapeGetArea)RectArea,
.Dtor = (ShapeDtor)RectRelease,
};
Rect* newRect(int w, int h) {
// 以realRect大小分配内存
realRect* p = calloc(1, sizeof(realRect));
if (NULL == p)
return NULL;
// 调用基类的构造函数
shapeCtor((Shape*)p, "Rect");
// 设置虚函数表
p->vtb = &vtRect;
p->h = h;
p->w = w;
printf("Rect Ctor\n");
printf("Rect Size:%zd\n", sizeof(realRect));
return (Rect*)p;
}
typedef struct {
Circle; // 这里继承头文件中公开的Circle定义
// 下面定义私有变量
int r;
}realCircle;
// 计算圆形面积
static int CircleArea(realCircle* s) {
printf("Circle GetArea\n");
return (int)(3.14 * s->r * s->r);
}
// 圆形的析构函数
static void CircleRelease(realCircle* s) {
if (s) {
printf("Circle Dtor:%s\n", s->name);
shapeDtor((Shape*)s);
free(s);
}
}
// 圆形的虚函数表
// 虚函数表只有唯一的一份,这样不管构建了多少实例,
// 每个实例都只有一个指向虚函数表的指针,节约了内存空间
static const vtShape vtCircle = {
.GetArea = (ShapeGetArea)CircleArea,
.Dtor = (ShapeDtor)CircleRelease,
};
Circle* newCircle(int r) {
realCircle* p = calloc(1, sizeof(realCircle));
if (NULL == p)
return NULL;
shapeCtor((Shape*)p, "Circle");
p->vtb = &vtCircle;
p->r = r;
printf("Circle Ctor\n");
printf("Circle Size:%zd\n", sizeof(realCircle));
return (Circle*)p;
}
shape.cc
:
#include <iostream>
using namespace std;
typedef enum {
Black,
Red,
White,
Yellow,
}Color;
class CShape {
private:
const char* m_name;
Color color;
public:
CShape(const char* name) {
m_name = name;
color = Black;
cout << "CShape Ctor:" << name << endl;
}
virtual int GetArea() = 0;
virtual ~CShape() {
cout << "CShape Dtor" << endl;
}
};
class CRect : public CShape {
private:
int _w, _h;
public:
CRect(int w, int h) : CShape("Rect"), _w(w), _h(h) {
cout << "CRect Ctor" << endl;
}
virtual ~CRect() {
cout << "CRect Dtor" << endl;
}
virtual int GetArea() {
cout << "CRect GetArea" << endl;
return _w * _h;
}
};
class CCircle : public CShape {
private:
int _r;
public:
CCircle(int r): CShape("Circle"), _r(r) {
cout << "CCircle Ctor" << endl;
}
virtual ~CCircle() {
cout << "CCircle Dtor" << endl;
}
virtual int GetArea() {
cout << "CCircle GetArea" << endl;
return 3.14 * _r * _r;
}
};
extern "C" void testCC() {
cout << "CRect Size:" << sizeof(CRect) << endl;
cout << "CCircle Size:" << sizeof(CCircle) << endl;
CRect* r = new CRect(10, 20);
int a1 = r->GetArea();
CCircle* c = new CCircle(10);
int a2 = c->GetArea();
delete r;
delete c;
}
main.c
:
#include "shape.h"
void testCC();
int main(){
testCC();
printf("\n\n");
Rect* r = newRect(10, 20);
Circle* c = newCircle(10);
r->color = Red;
c->color = Yellow;
const vtShape* rt = r->vtb;
const vtShape* ct = c->vtb;
int a1 = rt->GetArea((Shape*)r);
int a2 = ct->GetArea((Shape*)c);
rt->Dtor((Shape*)r);
ct->Dtor((Shape*)c);
return 0;
}
CMakeLists.txt
:
cmake_minimum_required(VERSION 3.25.0)
project(cobj VERSION 0.1.0)
if(CMAKE_C_COMPILER_ID MATCHES "Clang")
add_compile_options(
-fms-extensions
-Wno-microsoft-anon-tag
)
endif()
add_executable(${PROJECT_NAME} ${SRC} shape.c shape.cc main.c)
运行结果:
这就是C实现面向对象的原理,如果有兴趣的读者可以看看glib中的gobject,不过要看懂它的代码,掌握原理是非常重要的!
如对你有帮助,欢迎点赞收藏!