C语言面向对象编程:模拟实现封装、继承、多态
一、面向对象基本概念
面向对象编程基于三个核心概念:封装、继承和多态。
1. 封装
将数据和操作这些数据的方法封装在一起,形成一个独立的单元(类)。这种机制隐藏了内部实现细节,只暴露必要的接口。
2. 继承
允许基于已存在的类(父类)定义新类(子类),子类可以复用父类的属性和方法,并可以添加新的属性和方法。
3. 多态
允许不同类的对象对同一消息作出响应,实现方法的动态绑定,提高了代码的灵活性和可扩展性。
二、C语言模拟封装实现
封装是面向对象编程的基础。在C语言中,我们可以使用结构体来封装数据,并通过函数指针来封装方法。
0. 头文件枚举值定义
typedef enum {HAL_OK = 0x00,HAL_ERROR = 0x01,
} HAL_StatusTypeDef;
1. String 类的实现
1. 结构体定义
// 定义String结构体,模拟类
typedef struct String {/* 成员函数 */// HAL_StatusTypeDef(*_init_string)(String* self, const char* str); // 构造函数(无法在对象初始化前通过对象调用)HAL_StatusTypeDef(*_destroy_string)(struct String* self); // 析构函数HAL_StatusTypeDef(*_PrintString)(const struct String* const self); // 打印String/* 成员变量 */char* _str; // 字符串size_t _size; // 长度size_t _capacity; // 容量
} String;
2. 构造函数
/* String对象构造函数 */
HAL_StatusTypeDef init_string(String* self, const char* str)
{// 初始化成员变量self->_size = strlen(str);self->_capacity = self->_size;self->_str = (char*)malloc(sizeof(char) * self->_size + 1);memcpy(self->_str, str, self->_size + 1);// 注入成员函数// self->_init_string = init_string;self->_destroy_string = destroy_string;self->_PrintString = PrintString;return HAL_OK;
}
说明:
构造函数负责初始化对象和注入方法
需要手动调用,模拟构造函数行为
3. 析构函数
/* String对象析构函数 */
HAL_StatusTypeDef destroy_string(String* self)
{free(self->_str);self->_str = NULL;return HAL_OK;
}
说明:
显式释放分配的内存
需要手动调用,模拟析构函数行为
4. 功能函数
/* String对象打印函数 */
HAL_StatusTypeDef PrintString(const String* const self)
{printf("%s\n", self->_str);return HAL_OK;
}
2. Person 类的实现
1. 结构体定义
// 定义Person结构体,模拟类
typedef struct Person {/* 成员函数 */// HAL_StatusTypeDef(*_init_person)(Person* self, char* name, int age); // 构造函数(无法在对象初始化前通过对象调用)HAL_StatusTypeDef(*_destroy_person)(struct Person* self); // 析构函数HAL_StatusTypeDef(*_speak)(const struct Person* const self); // 指向speak方法的指针/* 成员变量 */char* _name; // 姓名int _age; // 年龄
} Person;
2. 构造函数
/* Person对象构造函数 */
HAL_StatusTypeDef init_person(Person* self, char* name, int age) {// 初始化成员变量self->_name = name;self->_age = age;self->_speak = speak_func; // 函数名就是函数的地址,与&speak_func等价// self->_init_person = init_person; // 将构造函数的地址赋值给_init_personself->_destroy_person = destroy_person; // 将析构函数的地址赋值给_destroy_personreturn HAL_OK;
}
3. 析构函数
/* Person对象析构函数 */
HAL_StatusTypeDef destroy_person(Person* self) {self->_name = NULL;self->_age = 0;return HAL_OK;
}
4. 功能函数
// 定义speak方法
HAL_StatusTypeDef speak_func(const Person* const self) {printf("Hello, I am %s, %d years old.\n", self->_name, self->_age);return HAL_OK;
}
3. main测试代码
int main() {/* 封装类 */String s1; // 创建String实例init_string(&s1, "Hello world!"); // 构造函数s1._PrintString(&s1); // 调用功能函数s1._destroy_string(&s1); // 析构函数Person p1; // 创建Person实例init_person(&p1, "Alice", 30); // 构造函数p1._speak(&p1); // 调用功能函数p1._destroy_person(&p1); // 析构函数return 0;
}
说明:
演示了对象的创建、使用和销毁过程
展示了如何调用对象的方法
三、C语言模拟继承实现
1. 子类 Student 结构体定义
// 定义Student结构体,模拟类
typedef struct Student {/* 继承父类 */Person _Person; // 继承Person类// 放在子类结构体的第一个成员,这样可以通过(Person*)self获取父类的指针,传入父类的构造函数和析构函数/* 成员函数 */HAL_StatusTypeDef(*_print_score)(const struct Student* const self); // 打印分数HAL_StatusTypeDef(*_destroy_Student)(struct Student* self); // 析构函数/* 成员变量 */int _math_score; // 数学分数int _English_score; // 英语分数int _Chinese_score; // 语文分数
} Student;
说明:
通过结构体嵌套实现继承
父类作为子类的第一个成员,便于类型转换
2. Student类构造函数
/* 构造函数 */
HAL_StatusTypeDef init_Student(Student* const self, const char* name, int age, int math_score, int English_score, int Chinese_score)
{// 初始化父类结构体init_person((Person*)self, name, age);// 初始化成员变量self->_Chinese_score = Chinese_score;self->_English_score = English_score;self->_math_score = math_score;self->_print_score = print_score;self->_destroy_Student = destroy_Student;return HAL_OK;
}
说明:
先初始化父类部分,再初始化子类特有部分
通过类型转换将Student转换为Person
3. Student类析构函数
HAL_StatusTypeDef destroy_Student(Student* self) // 析构函数
{// 先析构子类self->_Chinese_score = 0;self->_English_score = 0;self->_math_score = 0;// 再析构父类self->_Person._destroy_person((Person*)self);return HAL_OK;
}
说明:
析构顺序:先子类后父类
模拟了继承体系中的析构过程
4. 功能函数实现
HAL_StatusTypeDef print_score(const Student* const self) // 打印分数
{printf("Math: %d\n", self->_math_score);printf("English: %d\n", self->_English_score);printf("Chinese: %d\n", self->_Chinese_score);return HAL_OK;
}
5. main测试代码
int main() {/* 继承 */Student stu2; // 创建Student实例init_Student(&stu2, "Bob", 20, 90, 85, 95); // 构造函数stu2._print_score(&stu2); // 打印成绩stu2._Person._speak(&stu2); // 说话函数,存在隐式类型转换,将子类结构体地址转化成父类结构体地址stu2._destroy_Student(&stu2); // 析构函数// 查看是否完成析构printf("stu2 math_score:%d\n", stu2._math_score);printf("stu2 name:%p\n", stu2._Person._name);return 0;
}
三、C语言模拟多态实现
1. 多态实现完整代码
#include <stdio.h>// 1. 定义“基类”Animal:包含通用属性(name)和“虚函数指针”(speak)
// 核心:用函数指针封装“共有的方法接口”,不同子类通过赋值不同函数实现差异化
typedef struct {const char* name; void (*speak)(const void* self); // 函数指针:指向“发声”方法(self指向具体对象)
} Animal;// 2. 定义“子类”Dog:通过嵌套基类Animal实现“继承”
// 逻辑:子类结构体包含基类结构体作为第一个成员,模拟“子类继承基类的属性和方法”
typedef struct {Animal base; const char* breed;
} Dog;// 3. 实现Dog的“发声”方法(对应基类的speak接口)
// 参数self:通用指针,指向具体的Dog对象(需强制转换)
void dog_speak(const void* self) {// 将通用指针转为Dog*,才能访问其嵌套的base成员const Dog* dog = (const Dog*)self;printf("[Dog] %s (breed: %s) barks: Wang Wang!\n", dog->base.name, dog->breed);
}// 4. 定义“子类”Cat:同样通过嵌套Animal实现“继承”
typedef struct {Animal base; const char* color;
} Cat;// 5. 实现Cat的“发声”方法(对应基类的speak接口)
void cat_speak(const void* self) {const Cat* cat = (const Cat*)self;printf("[Cat] %s (color: %s) meows: Miao Miao!\n", cat->base.name, cat->color);
}// 6. 封装“创建Dog对象”的函数(相当于构造函数)
// 功能:初始化Dog的属性,并将其base的speak指向Dog的实现
Dog* create_dog(const char* name, const char* breed) {// 简化示例:用静态变量避免堆内存管理(实际开发可改用malloc)static Dog dog;//Dog* dog = malloc(szieof(Dog)); 这时引用dog的成员要使用 ->dog.base.name = name; dog.base.speak = dog_speak; dog.breed = breed; return &dog;
}// 7. 封装“创建Cat对象”的函数(构造函数)
Cat* create_cat(const char* name, const char* color) {static Cat cat;//Cat* cat= malloc(szieof(Cat));cat.base.name = name; cat.base.speak = cat_speak; cat.color = color; return &cat;
}// 8. 通用接口函数:接收“基类指针”(Animal*),调用speak方法
// 核心:通过基类指针调用函数指针,实现“多态”——同一接口,不同实现
void animal_speak(const Animal* animal) {if (animal && animal->speak) { // 调用函数指针,传入具体对象(animal指向的实际是Dog/Cat的base成员)animal->speak(animal); }
}int main() {Dog* dog = create_dog("Buddy", "Golden Retriever");Cat* cat = create_cat("Luna", "White");// 多态体现:调用同一接口animal_speak,根据实际对象类型执行不同实现animal_speak(&dog->base); // 传入Dog的base成员(Animal类型),执行dog_speakanimal_speak(&cat->base); // 传入Cat的base成员(Animal类型),执行cat_speakreturn 0;
}
核心原理解析(C 语言模拟多态的 3 个关键)
C 语言没有类、继承、虚函数等面向对象特性,上述代码通过3 个核心技术模拟多态,本质是 “接口统一,实现分离”:
用 “结构体嵌套” 模拟 “继承关系”
面向对象中,子类继承基类的属性和方法;C 语言中,通过 “子类结构体嵌套基类结构体” 实现类似效果。
例如
Dog
结构体第一个成员是Animal base
,这意味着:Dog
对象的内存布局中,开头部分与Animal
完全一致(可直接将&dog->base
视为Animal*
指针)。Dog
可以 “继承”Animal
的属性(name
)和方法接口(speak
函数指针)。
子类可额外添加特有属性(如
Dog
的breed
、Cat
的color
),实现 “扩展”。
用 “函数指针” 模拟 “虚函数”(多态的核心)
面向对象的多态依赖 “虚函数”(基类声明虚函数,子类重写);C 语言中,用基类结构体中的函数指针模拟 “虚函数接口”。
基类
Animal
中的void (*speak)(const void* self)
是 “通用方法接口”,所有子类(Dog、Cat)都需实现对应的具体函数(dog_speak
、cat_speak
)。创建子类对象时(如
create_dog
函数),将基类的speak
函数指针绑定到子类的具体实现(dog->base.speak = dog_speak
),这一步相当于 “子类重写虚函数”。
用 “基类指针” 实现 “动态绑定”
多态的关键是 “运行时根据实际对象类型,调用对应实现”(动态绑定);C 语言中,通过 “基类指针(Animal)指向子类对象的基类成员*” 实现。
在
main
函数中,animal_speak(&dog->base)
和animal_speak(&cat->base)
调用的是同一个接口animal_speak
,但传入的Animal*
指针实际指向:&dog->base
:指向Dog
对象中的Animal
成员,其speak
绑定的是dog_speak
。&cat->base
:指向Cat
对象中的Animal
成员,其speak
绑定的是cat_speak
。
运行时,
animal->speak(animal)
会根据speak
指针实际指向的函数(dog_speak
或cat_speak
)执行,从而实现 “同一接口,不同行为” 的多态效果。
main()|| 创建对象+--> create_dog() --> Dog对象.base.speak = dog_speak|+--> create_cat() --> Cat对象.base.speak = cat_speak|| 多态调用+--> animal_speak(&dog->base)| || +--> animal->speak(animal) --> 实际调用 dog_speak(dog)|+--> animal_speak(&cat->base)|+--> animal->speak(animal) --> 实际调用 cat_speak(cat)
运行结果
[Dog] Buddy (breed: Golden Retriever) barks: Wang Wang!
[Cat] Luna (color: White) meows: Miao Miao!
核心总结
C 语言模拟多态的本质是 “用结构化思维手动实现面向对象的核心逻辑”,对比面向对象语言(如 C++):
面向对象概念(C++) | C语言模拟方式 |
基类(父类)/派生类(子类) | 结构体嵌套(子类包含基类结构体) |
虚函数 | 基类结构体中的函数指针 |
重写(覆盖)(Override) | 子类对象给基类函数指针赋值 |
动态绑定 | 基类指针指向子类的基类成员 |
这种方式的局限性:需要手动管理 “继承”(结构体嵌套)和 “虚函数绑定”(函数指针赋值),没有编译器自动支持;但通过统一接口封装不同实现,能让代码更灵活、可扩展。