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 oncetypedef 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.rhkb.cn/news/500872.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt监控系统放大招/历经十几年迭代完善/多屏幕辅屏预览/多层级设备树/网络登录和回放

一、前言说明 近期对视频监控系统做了比较大的更新升级&#xff0c;主要就是三点&#xff0c;第一点就是增加了辅屏预览&#xff0c;这个也是好多个客户需要的功能&#xff0c;海康的iVMS-4200客户端就有这个功能&#xff0c;方便在多个屏幕打开不同的视频进行查看&#xff0c…

基于feapder爬虫与flask前后端框架的天气数据可视化大屏

# 最近又到期末了&#xff0c;有需要的同学可以借鉴。 一、feapder爬虫 feapder是国产开发的新型爬虫框架&#xff0c;具有轻量且数据库操作方便、异常提醒等优秀特性。本次设计看来利用feapder进行爬虫操作&#xff0c;可以加快爬虫的速率&#xff0c;并且简化数据入库等操作…

数据挖掘——模型的评价

数据挖掘——模型的评价 模型的评价混淆矩阵ROC曲线如何构建ROC曲线 模型过分拟合和拟合不足减少泛化误差 模型的评价 混淆矩阵 准确率 a d a b c d \frac{ad}{abcd} abcdad​ T P T N T P T N F P F N \frac{TPTN}{TPTNFPFN} TPTNFPFNTPTN​ 其他度量&#xff1a; …

庐山派K230学习日记1 从点灯到吃灰

1 简介​ 庐山派以K230为主控芯片&#xff0c;支持三路摄像头同时输入&#xff0c;典型网络下的推理能力可达K210的13.7倍&#xff08;算力约为6TOPS&#xff09;。支持CanMV&#xff0c;可作为AI与边缘计算平台 K230简介 K230芯片集成了两颗RISC-V处理器核心&#xff0c;双核…

活动预告 |【Part2】Microsoft 安全在线技术公开课:安全性、合规性和身份基础知识

课程介绍 通过参加“Microsoft 安全在线技术公开课&#xff1a;安全性、合规性和身份基础知识”活动提升你的技能。在本次免费的介绍性活动中&#xff0c;你将获得所需的安全技能和培训&#xff0c;以创造影响力并利用机会推动职业发展。你将了解安全性、合规性和身份的基础知…

【PCIe 总线及设备入门学习专栏 4.5 -- PCIe Message and PCIe MSI】

文章目录 PCIe Message 与 MSIPCIe Message 和 MSI 的作用与关系MSI 的配置与寄存器MSI 和 ARM GIC 的关系示例&#xff1a;MSI 在 ARM GIC 的实际应用总结 PCIe Message 与 MSI 本文将介绍 PCIe message 的作用以及message 与 MSI 的关系&#xff0c;再介绍 MSI 如何配置以及…

C++11右值与列表初始化

1.列表初始化 C98传统的{} C98中一般数组和结构体可以用{}进行初始化。 struct Point {int _x;int _y; }; int main() {int array1[] { 1, 2, 3, 4, 5 };int array2[5] { 0 };Point p { 1, 2 };return 0; } C11中的{} C11以后统一初始化方式&#xff0c;想要实现一切对…

设计模式 创建型 建造者模式(Builder Pattern)与 常见技术框架应用 解析

单例模式&#xff08;Singleton Pattern&#xff09;&#xff0c;又称生成器模式&#xff0c;是一种对象构建模式。它主要用于构建复杂对象&#xff0c;通过将复杂对象的构建过程与其表示分离&#xff0c;使得同样的构建过程可以创建出具有不同表示的对象。该模式的核心思想是将…

什么是Redis哨兵机制?

大家好&#xff0c;我是锋哥。今天分享关于【什么是Redis哨兵机制&#xff1f;】面试题。希望对大家有帮助&#xff1b; 什么是Redis哨兵机制&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 Redis 哨兵&#xff08;Sentinel&#xff09;机制是 Redis 提…

全国计算机设计大赛大数据主题赛(和鲸赛道)经验分享

全国计算机设计大赛大数据主题赛&#xff08;和鲸赛道&#xff09;经验分享 这是“和鲸杯”辽宁省普通高等学校本科大学生计算机设计竞赛启动会汇报—大数据主题赛的文档总结。想要参加2025年此比赛的可以借鉴。 一、关于我 人工智能专业 计赛相关奖项&#xff1a; 2022年计…

STM32 + 移远EC800 4G通信模块数传

一、4G模块简述 EC800M-CN 是移远通信&#xff08;Quectel&#xff09;推出的一款高性能、超小尺寸的 LTE Cat 1 无线通信模块&#xff0c;专为 M2M&#xff08;机器对机器&#xff09;和 IoT&#xff08;物联网&#xff09;应用设计。它具有以下主要特点&#xff1a; 通信速率…

标准库以及HAL库——按键控制LED灯代码

按键控制LED本质还是控制GPIO,和点亮一个LED灯没什么区别 点亮一个LED灯&#xff1a;是直接控制输出引脚&#xff0c;GPIO初始化推挽输出即可 按键控制LED&#xff1a;是按键输入信号从而控制输出引脚&#xff0c;GPIO初始化推挽输出一个引脚以外还得加一个GPIO上拉输入 但是…

springboot525基于MVC框架自习室管理和预约系统设计与实现(论文+源码)_kaic

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装自习室管理和预约系统软件来发挥其高效地信息处理的作用&am…

Unity Canvas中显示粒子特效

首先在场景中新建一个粒子特效 修改一下参数 1.改变粒子特效的渲染层级,层级修改为UI层,由UI相机渲染 使用粒子特效的Sorting Layer ID和Order In Layer,Sorting Layer ID设置为UI(如果没有UI层则新建就好了),对UI进行排序 对于要显示在前的UI组件添加Canvas组件,设置O…

Linux下部署Redis集群 - 一主二从三哨兵模式

三台服务器redis一主二从三哨兵模式搭建 最近使用到了redis集群部署&#xff0c;使用一主二从三哨兵集群部署redis&#xff0c;将自己部署的过程中的使用心得分享给大家&#xff0c;希望大家以后部署的过程减少一些坑。 服务器准备 3台服务器 &#xff0c;确定主redis和从red…

服务器端请求伪造之基本介绍

一.服务器端请求伪造漏洞基础 1.客户端请求 客户端请求指的是由客户端设备&#xff08;如个人计算机、智能手机、平板电脑等&#xff09;或软件&#xff08;浏览器、各种APP&#xff09;发出的请求&#xff0c;以获取指定的网页、图片、视频或其他资源。比如当用户在浏览器中输…

akamai3.0反爬教程逆向分析9个视频汇总

目录 一、akamai2.0文章二、akamai3.0每月疑似改版点二、9个视频汇总如下 一、akamai2.0文章 文章1cookie反爬之akamai_2.0-上文章2cookie反爬之akamai_2.0-上文章3cookie反爬之akamai_2.0-上文章中akamai2.0对应调试html与js文件 二、akamai3.0每月疑似改版点 详细文字与2.…

2024年12月 Scratch 图形化(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Scratch图形化等级考试(1~4级)全部真题・点这里 一、单选题(共25题,共50分) 第 1 题 小猫初始位置和方向如下图所示,下面哪个选项能让小猫吃到老鼠?( ) A. B. C.

【74LS160+74LS273DW锁存器8位的使用频率计】2022-7-12

缘由 想知道这个数字频率计仿真哪里出现错误了&#xff0c;一直无法运行哎&#xff0c;如何解决&#xff1f;-运维-CSDN问答

系统思考—信任

《基业长青》作者指出&#xff1a;“在人生的重要十字路口&#xff0c;选择信任是一场赌注。信任带来的好处可能巨大&#xff0c;而失去信任的代价却相对有限。但如果选择不信任&#xff0c;最优秀的人才可能因失望而离开。” 在企业管理中&#xff0c;信任不仅是人际关系的纽…