C语言面向对象编程:模拟实现封装、继承、多态

发布于:2025-09-10 ⋅ 阅读:(13) ⋅ 点赞:(0)

一、面向对象基本概念

面向对象编程基于三个核心概念:封装、继承和多态。

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_person
    self->_destroy_person = destroy_person; // 将析构函数的地址赋值给_destroy_person
    return 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_speak
    animal_speak(&cat->base); // 传入Cat的base成员(Animal类型),执行cat_speak

    return 0;
}

核心原理解析(C 语言模拟多态的 3 个关键)

C 语言没有类、继承、虚函数等面向对象特性,上述代码通过3 个核心技术模拟多态,本质是 “接口统一,实现分离”:

  1. 用 “结构体嵌套” 模拟 “继承关系”

  • 面向对象中,子类继承基类的属性和方法;C 语言中,通过 “子类结构体嵌套基类结构体” 实现类似效果。

    • 例如Dog结构体第一个成员是Animal base,这意味着:

      • Dog对象的内存布局中,开头部分与Animal完全一致(可直接将&dog->base视为Animal*指针)。

      • Dog可以 “继承”Animal的属性(name)和方法接口(speak函数指针)。

    • 子类可额外添加特有属性(如DogbreedCatcolor),实现 “扩展”。

  1. 用 “函数指针” 模拟 “虚函数”(多态的核心)

  • 面向对象的多态依赖 “虚函数”(基类声明虚函数,子类重写);C 语言中,用基类结构体中的函数指针模拟 “虚函数接口”。

    • 基类Animal中的void (*speak)(const void* self)是 “通用方法接口”,所有子类(Dog、Cat)都需实现对应的具体函数(dog_speakcat_speak)。

    • 创建子类对象时(如create_dog函数),将基类的speak函数指针绑定到子类的具体实现(dog->base.speak = dog_speak),这一步相当于 “子类重写虚函数”。

  1. 用 “基类指针” 实现 “动态绑定”

  • 多态的关键是 “运行时根据实际对象类型,调用对应实现”(动态绑定);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_speakcat_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)

子类对象给基类函数指针赋值

动态绑定

基类指针指向子类的基类成员

这种方式的局限性:需要手动管理 “继承”(结构体嵌套)和 “虚函数绑定”(函数指针赋值),没有编译器自动支持;但通过统一接口封装不同实现,能让代码更灵活、可扩展。


网站公告

今日签到

点亮在社区的每一天
去签到