当前位置: 首页 > article >正文

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,不过要看懂它的代码,掌握原理是非常重要的!

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


http://www.kler.cn/a/464895.html

相关文章:

  • 黑马跟学.苍穹外卖.Day01
  • 行为模式5.中介者模式-聊天室收发消息
  • Dubbo 关键知识点解析:负载均衡、容错、代理及相关框架对比
  • 渗透测试-非寻常漏洞案例
  • django StreamingHttpResponse fetchEventSource实现前后端流试返回数据并接收数据的完整详细过程
  • Appium 2.0:移动自动化测试的革新之旅
  • 高效管理deepin和Docker中的后台任务
  • 如何查看docker默认的网段的4种办法
  • [python SQLAlchemy数据库操作入门]-16.CTE:简化你的复杂查询
  • 计算机网络•自顶向下方法:路由选路算法
  • neo4j学习笔记
  • Kali Linux 和Xshell的安装和使用
  • java04 1个简单程序/ 输入输出/ 方法(子函数)/ 数组
  • Windows注册表的HKEY_CLASSES_ROOT是HKEY_LOCAL_MACHINE\SOFTWARE\Classes合并HKEY_CURRENT_USER\Software\Classes
  • HCIE-day9-OSPF2
  • 五年制物联网专业智能家居实训室建设方案
  • MySQL_增删改查基础
  • Webpack、Vite区别知多少?
  • 高等数学学习笔记 ☞ 数列与数列的极限
  • GXUOJ-算法-补题:22级《算法设计与分析》第一次课堂练习
  • Apache MINA 反序列化漏洞CVE-2024-52046
  • SpringMVC(五)实现文件上传
  • 数据挖掘教学指南:从基础到应用
  • 单片机端口操作和独立引脚操作
  • 【Vim Masterclass 笔记05】第 4 章:Vim 的帮助系统与同步练习
  • 《小型支付商城系统》项目(一)DDD架构入门