C 语言中面向对象编程的实现
一、前言
在嵌入式开发和系统级编程领域,C/C++ 语言是使用最普及的。在 C++11 版本之前,它们的语法较为相似,但 C++ 提供了原生的面向对象(OOP)编程方式。
虽然 C++ 是从 C 语言发展而来的,但随着标准的演进,现代 C++ 已不仅仅是 C 的扩展,更像是一门全新的语言。然而,C++ 的学习曲线陡峭,涉及左值右值、模板元编程等复杂概念,并非短期能完全掌握。
详细阐述了在 C 语言中模拟面向对象编程的三种核心机制:封装、继承与多态。通过结构体嵌套实现继承,利用函数指针与虚表结构模拟多态行为,并结合 Linux 内核与 GLib 库的实际案例进行说明。文章还对比了 C 与 C++ 的差异,指出了该模式的优缺点及适用场景,旨在帮助开发者在不依赖 C++ 特性的环境下构建清晰的软件架构。

在嵌入式开发和系统级编程领域,C/C++ 语言是使用最普及的。在 C++11 版本之前,它们的语法较为相似,但 C++ 提供了原生的面向对象(OOP)编程方式。
虽然 C++ 是从 C 语言发展而来的,但随着标准的演进,现代 C++ 已不仅仅是 C 的扩展,更像是一门全新的语言。然而,C++ 的学习曲线陡峭,涉及左值右值、模板元编程等复杂概念,并非短期能完全掌握。
相比之下,C 语言具有轻量、高效、生态成熟等优点。理解如何在 C 语言中模拟面向对象的思想,不仅有助于深入理解内存模型和指针机制,还能在无法使用 C++ 的环境中灵活设计架构。
本文将探讨如何在 C 语言中利用结构体和函数指针实现封装、继承和多态三大核心特性。
程序的基本构成公式为:程序 = 数据结构 + 算法。
若要在 C 语言中模拟 OOP,需解决三个关键问题:
注意:如果一门语言只支持类而不支持多态,只能称为基于对象的,而非真正的面向对象。
C 语言中的 struct 天然支持数据封装。我们可以定义一个结构体代表对象,并配合函数操作该结构体。
#ifndef _ANIMAL_H_
#define _ANIMAL_H_
// 定义父类结构
typedef struct {
int age;
int weight;
} Animal;
// 构造函数声明
void Animal_Ctor(Animal *this, int age, int weight);
// 获取属性声明
int Animal_GetAge(Animal *this);
int Animal_GetWeight(Animal *this);
#endif
#include "Animal.h"
// 构造函数实现
void Animal_Ctor(Animal *this, int age, int weight) {
this->age = age;
this->weight = weight;
}
int Animal_GetAge(Animal *this) {
return this->age;
}
int Animal_GetWeight(Animal *this) {
return this->weight;
}
#include <stdio.h>
#include "Animal.h"
int main() {
// 栈上创建对象
Animal a;
// 构造对象
Animal_Ctor(&a, 1, 3);
printf("age = %d, weight = %d\n",
Animal_GetAge(&a),
Animal_GetWeight(&a));
return 0;
}
在此模式下,this 指针显式传递,编译器不会自动处理,这要求开发者手动管理对象地址。
C 语言没有直接的继承关键字,但可以通过在子类结构体的第一个成员放置父类结构体来实现。
#ifndef _DOG_H_
#define _DOG_H_
#include "Animal.h"
// 定义子类结构
typedef struct {
Animal parent; // 第一个位置放置父类结构
int legs; // 子类特有属性
} Dog;
// 构造函数声明
void Dog_Ctor(Dog *this, int age, int weight, int legs);
// 属性访问声明
int Dog_GetAge(Dog *this);
int Dog_GetLegs(Dog *this);
#endif
#include "Dog.h"
// 构造函数实现
void Dog_Ctor(Dog *this, int age, int weight, int legs) {
// 初始化父类部分
Animal_Ctor(&this->parent, age, weight);
// 初始化子类特有部分
this->legs = legs;
}
// 转发父类方法
int Dog_GetAge(Dog *this) {
return Animal_GetAge(&this->parent);
}
int Dog_GetLegs(Dog *this) {
return this->legs;
}
由于 Dog 结构体的第一个成员是 Animal,在内存布局上,Dog 对象的前半部分就是 Animal 的数据。这使得我们可以通过强制类型转换或指针运算访问父类部分,从而实现继承效果。
多态依赖于运行时绑定。在 C++ 中通过虚表(vtable)实现,在 C 中我们需要手动维护虚表指针和虚函数表。
#ifndef _ANIMAL_H_
#define _ANIMAL_H_
struct AnimalVTable; // 虚表前置声明
typedef struct {
struct AnimalVTable *vptr; // 虚表指针
int age;
int weight;
} Animal;
// 虚表定义
typedef struct {
void (*say)(Animal *this);
} AnimalVTable;
// 虚函数声明
void Animal_Say(Animal *this);
#endif
#include <assert.h>
#include "Animal.h"
// 默认虚函数实现(抽象)
static void _Animal_Say(Animal *this) {
assert(0); // 类似纯虚函数,防止直接调用
}
// 构造函数
void Animal_Ctor(Animal *this, int age, int weight) {
static struct AnimalVTable animal_vtbl = {_Animal_Say};
this->vptr = &animal_vtbl;
this->age = age;
this->weight = weight;
}
// 多态调用入口
void Animal_Say(Animal *this) {
if (this && this->vptr && this->vptr->say) {
this->vptr->say(this);
}
}
#include "Dog.h"
#include <stdio.h>
static void _Dog_Say(Dog *this) {
printf("dog says: woof!\n");
}
void Dog_Ctor(Dog *this, int age, int weight, int legs) {
// 先初始化父类
Animal_Ctor(&this->parent, age, weight);
// 覆盖虚表指针指向子类虚表
static struct AnimalVTable dog_vtbl = {_Dog_Say};
this->parent.vptr = &dog_vtbl;
this->legs = legs;
}
int main() {
Dog d;
Dog_Ctor(&d, 1, 3, 4);
// 父类指针指向子类对象
Animal *pa = &d.parent;
// 调用时会根据 vptr 指向实际执行子类函数
Animal_Say(pa);
return 0;
}
这种模式在底层系统中非常常见。
Linux 网络协议栈大量使用了这种模式。例如 Socket 结构体:
struct sock { ... };
struct inet_sock { struct sock sk; ... };
struct udp_sock { struct sock sk; ... };
inet_sock 和 udp_sock 的第一个成员都是 sock,实现了逻辑上的继承关系,便于统一处理网络套接字。
GLib 字符串处理也遵循此模式:
GString *g_string_append(GString *string, const gchar *val);
所有操作都接受对象指针作为第一个参数,符合面向对象的方法调用习惯。
尽管 C 语言可以模拟 OOP,但仍需注意以下限制:
Animal* 是否真的指向 Animal 对象,容易引发段错误。malloc/free 并在析构函数中释放资源。在 C 语言中实现面向对象编程,本质是利用结构体封装数据,利用函数指针实现多态。这种模式在嵌入式开发、操作系统内核及跨平台库设计中依然具有重要价值。熟练掌握这一技术,能帮助开发者更清晰地设计模块边界,提升代码的可维护性和扩展性。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online