[闲谈]C语言的面向对象
C语言的面向对象
文章目录
- C语言的面向对象
- 一、面向对象编程的核心概念
- 1. 封装
- 2. 继承
- 3. 多态
- 二、C语言实现封装的方法
- 1. 定义结构体封装数据
- 2. 实现成员方法
- 3. 初始化对象
- 4.应用场景
- 5.注意事项
- 三、模拟继承的两种模式详解
- 1. 组合模式(Composition Pattern)
- 2. 函数指针表(Virtual Table Pattern)
- 四、多态的实现技巧
- 1.实现原理
- 2.典型实现示例
- 五、设计模式在C中的示例
- 1.工厂模式
- 六、实际应用案例
- 七、优势与局限性分析
- 1.优势
- 2.局限性
- 八、总结
每天枯燥的学习工作以及一板一眼的技术文章,确实让人觉得乏味。今天略有闲情,跟大家车一扯淡,谈谈C语言的面向对象,就当茶余饭后的龙门阵吧。
一、面向对象编程的核心概念
面向对象编成 因为其安全性、可维护性好、代码复用性强、结构清晰、扩展性强、模块化、设计简化、复杂度低、代码精简冗余少等优点,几乎所有的高级语言都采用这种思维。
1. 封装
封装是指将数据(属性)和操作数据的方法(行为)绑定在一起,形成一个独立的单元(即对象)。通过访问修饰符(如private
、public
、protected
)控制数据的可见性,隐藏内部实现细节,仅对外暴露必要的接口。
示例:
public class BankAccount {private double balance; // 私有属性,外部无法直接访问public void deposit(double amount) { // 公开方法,提供存款功能if (amount > 0) {balance += amount;}}public double getBalance() { // 公开方法,提供查询余额功能return balance;}
}
应用场景:
- 保护数据完整性(如限制非法操作)。
- 简化外部调用(使用者无需关心内部逻辑)。
2. 继承
继承允许子类(派生类)复用父类(基类)的属性和方法,同时可以扩展或重写父类的功能。通过extends
关键字实现单继承,或通过接口实现多继承的效果。
示例:
public class Animal { // 父类public void eat() {System.out.println("Animal is eating.");}
}public class Dog extends Animal { // 子类@Overridepublic void eat() { // 方法重写System.out.println("Dog is eating bones.");}public void bark() { // 子类扩展方法System.out.println("Dog is barking.");}
}
特点:
- 减少代码冗余(如共有的
id
、name
属性可定义在父类)。 - 支持层次化设计(如
Animal
→Mammal
→Dog
)。
3. 多态
多态指同一接口(如方法名)在不同上下文中表现出不同的行为。实现方式包括:
- 编译时多态:方法重载(同一类中同名不同参的方法)。
- 运行时多态:方法重写(子类覆盖父类方法,通过父类引用调用子类对象)。
示例:
Animal myAnimal = new Dog(); // 父类引用指向子类对象
myAnimal.eat(); // 输出 "Dog is eating bones."(实际调用子类方法)
应用场景:
- 设计通用接口(如
Shape
类定义draw()
方法,子类Circle
、Square
实现不同绘制逻辑)。 - 增强代码灵活性(如通过参数传递父类类型,兼容所有子类)。
关系总结:
封装是基础,继承是扩展,多态是行为多样性表现。三者共同构成面向对象编程的核心特性。
二、C语言实现封装的方法
在C语言中,虽然没有面向对象编程语言中的类(class)概念,但可以通过结构体(struct
)和函数指针模拟封装特性。具体实现步骤如下:
1. 定义结构体封装数据
使用结构体将相关数据成员组合在一起,作为对象的属性。
typedef struct {int value; // 数据成员void (*print)(void* self); // 函数指针,模拟成员方法
} Object;
2. 实现成员方法
定义具体的函数实现,通过类型转换访问结构体成员。
// 成员方法的实现
void print_impl(void* self) {Object* obj = (Object*)self; // 将void指针转换为结构体指针printf("Value: %d\n", obj->value); // 访问结构体成员
}
3. 初始化对象
创建结构体实例并绑定成员方法,模拟构造函数。
int main() {Object obj = {.value = 42, // 初始化数据成员.print = print_impl // 绑定成员方法};obj.print(&obj); // 调用成员方法return 0;
}
4.应用场景
- 模块化开发:将相关数据和操作封装在结构体中,通过函数指针实现方法绑定。例如,在开发网络协议栈时,可将TCP连接的状态、收发缓冲区等数据与
connect()
、send()
等操作封装为TCPConnection
结构体,避免使用全局变量导致代码耦合度高、难以维护的问题。 - 模拟对象行为:适用于嵌入式开发等需要轻量级对象模型的场景。例如在RTOS中,任务对象可通过结构体封装任务栈指针、优先级等属性,并绑定
start()
、yield()
等方法指针,实现任务调度功能。 - 硬件抽象层:将设备驱动的寄存器操作封装为结构体方法,例如
I2C_Device
结构体可包含read()
、write()
等函数指针,支持多设备实例化。
5.注意事项
- 手动管理
this
指针:需通过void* self
参数显式传递上下文。例如:typedef struct {int value;void (*print)(void* self); } Counter; void Counter_print(void* self) {Counter* obj = (Counter*)self;printf("Value: %d\n", obj->value); }
- 类型安全校验:函数指针需与实现严格匹配,建议使用
typedef
定义方法签名。例如:typedef void (*PrintFunc)(void*); struct Widget {PrintFunc print; // 确保所有实现函数均符合此签名 };
- 初始化完整性:必须在创建实例时显式绑定所有函数指针,遗漏会导致运行时崩溃。可采用工厂函数统一初始化:
Counter* Counter_new() {Counter* obj = malloc(sizeof(Counter));obj->print = Counter_print; // 显式绑定方法return obj; }
通过这种方式,可以在C语言中实现类似面向对象的封装特性,尤其适合资源受限但需要代码复用的场景。
三、模拟继承的两种模式详解
1. 组合模式(Composition Pattern)
组合模式通过在子类结构体中直接包含父类实例来实现继承关系。这是C语言中最常用的模拟继承方式,具有以下特点:
实现方式:
- 父类成员必须作为子类结构体的第一个成员
- 通过强制类型转换实现向上转型
- 需要手动维护初始化顺序
典型应用场景:
- 需要明确父子关系的场景
- 对性能要求较高的场合
- 需要严格控制内存布局的情况
示例代码详细说明:
// 父类定义
typedef struct {int id;char name[32];
} Object;// 子类定义
typedef struct {Object base; // 必须作为第一个成员int extra_data;float custom_value;
} Child;// 使用方法
void demo() {Child c;c.base.id = 100; // 访问父类成员c.extra_data = 200; // 访问子类特有成员// 向上转型Object* obj = (Object*)&c;printf("ID: %d\n", obj->id);
}
2. 函数指针表(Virtual Table Pattern)
函数指针表模式通过虚表(vtable)实现多态特性,允许运行时动态绑定方法。这种模式更接近真正的面向对象继承。
核心组件:
- 虚表结构:包含函数指针集合
- 对象结构:包含指向虚表的指针
- 实现类:提供具体的函数实现
详细实现示例:
// 定义虚表结构
typedef struct {void (*print)(void*);void (*save)(void*);int (*calculate)(void*, int);
} VTable;// 基类结构
typedef struct {VTable* vtable;int id;
} BaseObject;// 子类实现
void Child_print(void* self) {BaseObject* obj = (BaseObject*)self;printf("Object ID: %d\n", obj->id);
}// 创建虚表实例
static VTable child_vtable = {.print = Child_print,.save = NULL,.calculate = NULL
};// 使用方法
void demo_vtable() {BaseObject obj;obj.vtable = &child_vtable;obj.id = 123;// 通过虚表调用方法obj.vtable->print(&obj);
}
两种模式对比:
-
组合模式:
- 优点:内存效率高,访问速度快
- 缺点:缺乏动态多态性
-
函数指针表:
- 优点:支持运行时多态
- 缺点:额外的内存开销,调用间接性导致性能损失
实际项目中,开发者可以根据具体需求选择合适的方式,或者组合使用这两种模式来实现更复杂的对象关系。
四、多态的实现技巧
多态是面向对象编程的核心特性之一,它允许不同对象对同一消息做出不同的响应。在C语言中,虽然原生不支持面向对象特性,但可以通过结构体和函数指针模拟实现多态效果。
1.实现原理
通过类型检查和函数指针的运行时绑定实现多态:
- 类型检查:在结构体中存储类型标识字段,用于区分不同子类
- 虚函数表:使用函数指针成员模拟虚函数,子类实现不同的函数逻辑
- 动态绑定:运行时根据实际对象类型调用对应的函数实现
2.典型实现示例
typedef struct {int type; // 类型标识void (*print)(void*); // 虚函数指针
} Object;void process_object(Object* obj) {obj->print(obj); // 动态调用不同实现
}// 具体子类实现
typedef struct {Object base;char* name;
} Person;void person_print(void* self) {Person* p = (Person*)self;printf("Person: %s\n", p->name);
}// 另一个子类
typedef struct {Object base;int id;
} Product;void product_print(void* self) {Product* p = (Product*)self;printf("Product ID: %d\n", p->id);
}
五、设计模式在C中的示例
1.工厂模式
核心思想:
工厂模式是一种创建型设计模式,它通过特定的工厂函数来创建对象,而不是直接调用构造函数。这种方式可以隐藏对象创建的具体细节,使代码更易于维护和扩展。
C语言实现细节:
由于C语言没有类的概念,我们可以通过结构体和函数指针来模拟面向对象的特性。工厂函数负责分配内存、初始化结构体字段,并设置方法指针。
完整示例:
#include <stdlib.h>// 定义对象结构体
typedef struct {int value;void (*print)(struct Object*); // 方法指针
} Object;// 打印方法的具体实现
void print_impl(Object* obj) {printf("Object value: %d\n", obj->value);
}// 工厂函数
Object* create_object(int val) {// 分配内存Object* obj = malloc(sizeof(Object));if (obj == NULL) {return NULL; // 内存分配失败处理}// 初始化字段obj->value = val;obj->print = print_impl; // 设置方法指针return obj;
}// 使用示例
int main() {Object* obj = create_object(42);if (obj != NULL) {obj->print(obj); // 调用对象方法free(obj); // 释放内存}return 0;
}
应用场景:
- 当对象创建过程较复杂时
- 需要统一管理对象创建时
- 需要隐藏具体实现细节时
- 需要支持多种相似对象的创建时
扩展说明:
可以通过增加枚举类型来实现简单工厂模式,根据不同类型创建不同对象:
typedef enum { TYPE_A, TYPE_B } ObjectType;Object* create_object_by_type(ObjectType type, int val) {Object* obj = malloc(sizeof(Object));// 根据不同类型进行不同初始化// ...return obj;
}
注意事项:
- 调用者需要负责释放工厂创建的对象
- 在嵌入式系统中使用时要注意内存管理
- 可以通过增加错误处理机制使工厂更健壮
六、实际应用案例
-
Linux内核中的设备驱动模型
Linux内核的设备驱动模型采用面向对象的设计思想,通过结构体和函数指针模拟类和方法的特性。核心数据结构struct device
和struct device_driver
分别表示设备和驱动,通过注册、匹配和绑定机制实现动态加载和管理。例如,一个USB设备驱动会定义自己的struct usb_driver
,并实现probe
、disconnect
等方法,内核在设备插入时自动调用匹配的驱动逻辑。这种设计提高了内核的可扩展性,使新增设备类型无需修改核心代码。 -
GTK图形库的事件处理机制
GTK基于信号和回调机制实现面向对象的事件处理。每个构件(如按钮、窗口)都是一个GObject派生类,通过g_signal_connect
将事件(如"clicked")与用户定义的函数绑定。例如,点击按钮时触发信号,调用注册的回调函数执行具体操作。GTK还支持事件冒泡和捕获,允许父容器处理子构件的事件,这种层级化设计体现了继承和多态的思想。 -
开源项目(如SQLite)对OOP的实践
SQLite虽用C语言实现,但通过模块化设计实践OOP原则。例如:- 封装:数据库核心操作(如
sqlite3_open
)隐藏内部状态,仅暴露接口。 - 继承:虚拟表(Virtual Table)机制允许用户自定义表类型,继承基础表的操作接口并重写
xCreate
等方法。 - 多态:同一SQL语句(如
SELECT
)可根据底层存储引擎(内存表、磁盘表)动态选择执行路径。
这种设计使SQLite在保持轻量级的同时支持高度可定制化。
- 封装:数据库核心操作(如
七、优势与局限性分析
1.优势
-
无运行时开销
- 通过静态类型检查和编译时优化,避免了动态类型语言常见的运行时类型检查开销
- 特别适合嵌入式系统、游戏引擎、高频交易等对性能要求极高的场景
- 示例:在STM32单片机开发中,静态类型可节省宝贵的CPU周期和内存资源
-
精细的内存管理控制
- 允许开发者直接操作内存地址,实现自定义内存分配策略
- 支持手动内存管理(如malloc/free)和智能指针等多种模式
- 典型应用:开发高性能数据库引擎时可精确控制内存池的分配与回收
2.局限性
-
语法冗长
- 需要显式声明所有变量类型,缺乏类型推断等现代语法特性
- 常见样板代码:为实现简单功能需要编写大量类型转换和接口定义
- 对比示例:Python用
[]
创建列表,而需要std::vector<int> vec;
的显式声明
-
多态实现复杂
- 需通过虚函数表、模板元编程等机制手动实现运行时多态
- 类型安全需开发者保证,容易产生
dynamic_cast
等不安全操作 - 典型问题:在GUI框架开发中,处理UI组件继承体系时需编写大量类型检查代码
八、总结
虽然面向思维编程优点非常突出,C语言也可以以面向对象思维编程,但是其相对冗长的语法、复杂的多态实现,在有C++甚至java等更高级编程语言以及硬件资源充足甚至过剩的情况下,除非操作系统内核、设备驱动等等对效率要求苛刻的场景下,应该没有人会刻意使用C语言去面向对象吧。最好注各位程序员少年郎,不缺对象!
研究学习不易,点赞易。
工作生活不易,收藏易,点收藏不迷茫 :)
相关文章:
[闲谈]C语言的面向对象
C语言的面向对象 文章目录 C语言的面向对象一、面向对象编程的核心概念1. 封装2. 继承3. 多态 二、C语言实现封装的方法1. 定义结构体封装数据2. 实现成员方法3. 初始化对象4.应用场景5.注意事项 三、模拟继承的两种模式详解1. 组合模式(Composition Pattern&#x…...

C 语言指针之手写内存深度剖析与手写库函数:带你从0开始手撸库 附录1.5 万字实战笔记
一、指针入门:从野指针到空指针 1.1 野指针的第一次暴击:沃日 哪里来的Segmentation Fault ?????? 刚学指针时写过一段让我及其楠甭的代码,我x了xx的,最后才发现是为…...

C#高级:Winform桌面开发中CheckedListBox的详解
一、基础设置 单击触发选择效果:需要选择下面这个为True 二、代码实现 1.设置数据源 /// <summary> /// 为CheckBoxList设置数据源 /// </summary> /// <param name"checkedListBox1"></param> /// <param name"data&…...
【Java学习笔记】final关键字
final 关键字 一、final 关键字介绍 1. final可以修饰类、属性、方法和局部变量 2. final 的使用场景 (1)类不能被继承时,可以使用final修饰 (2)类的某个属性不可以被更改,可以使用final修饰 ࿰…...

AI学习笔记二十八:使用ESP32 CAM和YOLOV5实现目标检测
若该文为原创文章,转载请注明原文出处。 最近在研究使用APP如何显示ESP32 CAM的摄像头数据,看到有人实现把ESP32 CAM的数据流上传,通过YOLOV5来检测,实现拉流推理,这里复现一下。 一、环境 arduino配置esp32-cam开发环…...

免费分享50本web全栈学习电子书
最近搞到一套非常不错的 Web 全栈电子书合集,整整 50 本,都是epub电子书格式,相当赞!作为一个被期末大作业和项目 ddl 追着跑的大学生,这套书真的救我狗命! 刚接触 Web 开发的时候,我天天对着空…...

【prometheus+Grafana篇】基于Prometheus+Grafana实现MySQL数据库的监控与可视化
💫《博主主页》: 🔎 CSDN主页 🔎 IF Club社区主页 🔥《擅长领域》:擅长阿里云AnalyticDB for MySQL(分布式数据仓库)、Oracle、MySQL、Linux、prometheus监控;并对SQLserver、NoSQL(MongoDB)有了…...

全链路解析:影刀RPA+Coze API自动化工作流实战指南
在数字化转型加速的今天,如何通过RPA与API的深度融合实现业务自动化提效,已成为企业降本增效的核心命题。本文以「影刀RPA」与「Coze API」的深度协作为例,系统性拆解从授权配置、数据交互到批量执行的完整技术链路,助你快速掌握跨…...

高阶数据结构——哈希表的实现
目录 1.概念引入 2.哈希的概念: 2.1 什么叫映射? 2.2 直接定址法 2.3 哈希冲突(哈希碰撞) 2.4 负载因子 2.5 哈希函数 2.5.1 除法散列法(除留余数法) 2.5.2 乘法散列法(了解)…...
window 显示驱动开发-报告渲染操作的可选支持
从 Windows 7 开始,显示微型端口驱动程序可以在 DXGK_PRESENTATIONCAPS 结构中设置其他成员,以指示驱动程序可以或不能支持的某些呈现操作。 从 Windows 7 开始,显示微型端口驱动程序可以通过 DXGK_PRESENTATIONCAPS 结构进一步声明其支持的…...

2025 年网络安全趋势报告
一、引言 自欧洲信息安全协会(Infosecurity Europe)首次举办活动的 30 年来,网络安全格局发生了翻天覆地的变化。如今,网络安全领导者必须应对众多威胁,维持法规合规性,并与董事会成员合作推进组织的网络安…...

uniapp 条件筛选
v3 版本 <template><view class"store flex "><view class"store_view"><view class"store_view_search flex jsb ac"><!-- <view class"store_view_search_select">全部</view> --><v…...

pytorch问题汇总
conda环境下 通过torch官网首页 pip安装 成功运行 后面通过conda安装了别的包 似乎因为什么版本问题 就不能用了 packages\torch_init_.py", line 245, in _load_dll_libraries raise err OSError: [WinError 127] 找不到指定的程序。 Error loading ackages\torch\lib\c…...

开发过的一个Coding项目
一、文档资料、人员培训: 1、文档资料管理:这个可以使用OnLineHelpDesk。 2、人员培训:可以参考Is an Online Medical Billing and Coding Program Right for You - MedicalBillingandCoding.org。 3、人员招聘、考核:可以在Onli…...

数据仓库维度建模详细过程
数据仓库的维度建模(Dimensional Modeling)是一种以业务用户理解为核心的设计方法,通过维度表和事实表组织数据,支持高效查询和分析。其核心目标是简化复杂业务逻辑,提升查询性能。以下是维度建模的详细过程࿱…...

python打卡day37
早停策略和模型权重保存 知识点回顾: 过拟合的判断:测试集和训练集同步打印指标模型的保存和加载 仅保存权重保存权重和模型保存全部信息checkpoint,还包含训练状态 早停策略 是否过拟合,可以通过同步打印训练集和测试集的loss曲线…...
Redis 5.0.10 集群部署实战(3 主 3 从,三台服务器)
本文详细介绍如何在三台服务器上部署 Redis 5.0.10 的集群(3 主 3 从),并为每个步骤、配置项和命令提供清晰的注释说明,确保生产环境部署稳定可靠。 1️⃣ 环境准备 目标架构:3 主 3 从,共 6 个节点,分布在 3 台服务器 服务器信息: 192.16.1.85 192.16.1.86 192.16.1.8…...

各个网络协议的依赖关系
网络协议的依赖关系 学习网络协议之间的依赖关系具有多方面重要作用,具体如下: 帮助理解网络工作原理 - 整体流程明晰:网络协议分层且相互依赖,如TCP/IP协议族,应用层协议依赖传输层的TCP或UDP协议来传输数据&#…...

OSC协议简介、工作原理、特点、数据的接收和发送
OSC协议简介 Open Sound Control(OSC) 是一种开放的、独立于传输的基于消息的协议,主要用于计算机、声音合成器和其他多媒体设备之间的通信。它提供了一种灵活且高效的方式来发送和接收参数化消息,特别适用于实时控制应用&#x…...

区块链可投会议CCF C--APSEC 2025 截止7.13 附录用率
Conference:32nd Asia-Pacific Software Engineering Conference (APSEC 2025) CCF level:CCF C Categories:软件工程/系统软件/程序设计语言 Year:2025 Conference time:December 2-5, 2025 in Macao SAR, China …...

【数字图像处理】_笔记
第一章 概述 1.1 什么是数字图像? 图像分为两大类:模拟图像与数字图像 模拟图像:通过某种物理(光、电)的强弱变化来记录图像上各个点的亮度信息 连续:从空间上和数值上是不间断的 举例&…...

从0开始学习R语言--Day10--时间序列分析数据
在数据分析中,我们经常会看到带有时间属性的数据,比如股价波动,各种商品销售数据,网站的网络用户活跃度等。一般来说,根据需求我们会分为两种,分析历史数据的特点和预测未来时间段的数据。 移动平均 移动平…...

基于开源链动2+1模式AI智能名片S2B2C商城小程序的产品驱动型增长策略研究
摘要:在数字化经济时代,产品驱动型增长(Product-Led Growth, PLG)已成为企业突破流量瓶颈、实现用户裂变的核心战略。本文以“开源链动21模式AI智能名片S2B2C商城小程序”(以下简称“链动AI-S2B2C系统”)为…...
每日算法 -【Swift 算法】反转整数的陷阱与解法:Swift 中的 32 位整数处理技巧
反转整数的陷阱与解法:Swift 中的 32 位整数处理技巧 在面试题和算法练习中,整数反转是一道非常经典的题目。乍一看很简单,但一旦加入“不能使用 64 位整数”和“不能溢出 32 位整数范围”这两个限制,问题就立刻变得有挑战性。本…...

使用 OpenCV 实现“随机镜面墙”——多镜片密铺的哈哈镜效果
1. 引言 “哈哈镜”是一种典型的图像变形效果,通过局部镜面反射产生扭曲的视觉趣味。在计算机视觉和图像处理领域,这类效果不仅有趣,还能用于艺术创作、交互装置、视觉特效等场景。 传统的“哈哈镜”往往是针对整张图像做某种镜像或扭曲变换…...

鸿蒙仓颉开发语言实战教程:页面跳转和传参
前两天分别实现了商城应用的首页和商品详情页面,今天要分享新的内容,就是这两个页面之间的相互跳转和传递参数。 首先我们需要两个页面。如果你的项目中还没有第二个页面,可以右键cangjie文件夹新建仓颉文件: 新建的文件里面没什…...
如何在Vue中实现延迟刷新列表:以Element UI的el-switch为例
如何在Vue中实现延迟刷新列表:以Element UI的el-switch为例 在开发过程中,我们经常需要根据用户操作或接口响应结果来更新页面数据。本文将以Element UI中的el-switch组件为例,介绍如何在状态切换后延迟1秒钟再调用刷新列表的方法࿰…...

最新Spring Security实战教程(十六)微服务间安全通信 - JWT令牌传递与校验机制
🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Micro麦可乐的博客 🐥《Docker实操教程》专栏以最新的Centos版本为基础进行Docker实操教程,入门到实战 🌺《RabbitMQ》…...
MDM在智能健身设备管理中的技术应用分析
近年来,随着智能硬件的普及和健身行业的数字化转型,越来越多健身房引入了Android系统的智能健身设备,如智能动感单车、智能跑步机、体测仪等。这些设备通过内嵌的安卓终端,实现了健身内容播放、用户交互、远程课程等功能ÿ…...
OSPF ABR汇总路由
一、OSPF ABR汇总配置(手工汇总) 📌 场景示例 假设ABR连接区域0和区域1,区域1内存在多个子网(如10.1.0.0/24、10.1.1.0/24),需将其手动汇总为10.0.0.0/8并通告至区域0。 🔧 配置命…...