3.使用C语言实现伪面向对象的思想(OOP)

核心思路:

  • 对象 (Object):struct 来表示。结构体包含对象的数据成员(属性)。
  • 方法 (Method): 用普通的 C 函数来表示。这些函数通常接受一个指向对象结构体的指针作为第一个参数(约定俗成地命名为 selfthis),用来操作对象的数据。
  • 封装 (Encapsulation): 通过信息隐藏技术(如 Opaque Pointers 或在 .c 文件中定义结构体)来限制对对象内部数据的直接访问。
  • 继承 (Inheritance): 通过在子结构体中包含父结构体(通常作为第一个成员)来模拟。
  • 多态 (Polymorphism): 通过在结构体中使用函数指针(通常组织成一个“虚函数表” vtable)来模拟。

1. 模拟封装 (Encapsulation)

封装是将数据和操作数据的方法捆绑在一起,并隐藏对象的内部实现细节。

  • 数据: 使用 struct 定义对象的状态。
  • 方法: 创建操作该 struct 指针的函数。
  • 隐藏:
    • 方法一 (推荐 - Opaque Pointer): 在公共头文件 (.h) 中只提供结构体的前向声明或 typedef 一个指针类型,但不给出完整的结构体定义。这样用户只能通过你提供的函数来操作对象,无法直接访问其成员。
    • 方法二 (简单):.c 文件中定义完整的 struct,在 .h 文件中只提供函数声明。这种方式下,同一模块内的其他代码理论上仍可访问,但模块外的代码不行。

示例:一个简单的计数器对象

// counter.h (公共接口 - 使用 Opaque Pointer)
#ifndef COUNTER_H
#define COUNTER_H

// 1. 前向声明结构体,但不定义它,创建了一个不完整类型
typedef struct Counter Counter;

// 2. 或者直接 typedef 一个指针类型 (更常用)
// typedef struct Counter* CounterHandle;
// 下面的函数参数就要用 CounterHandle

// 3. 提供“构造函数”和“析构函数” (模拟)
Counter* Counter_create(int initial_value);
void Counter_destroy(Counter* self);

// 4. 提供公共方法
void Counter_increment(Counter* self);
int Counter_get_value(const Counter* self); // 如果方法不修改对象,用 const

#endif // COUNTER_H

// ---

// counter.c (私有实现)
#include "counter.h"
#include <stdlib.h> // for malloc, free
#include <stdio.h>  // for printf in methods if needed

// 5. 在 .c 文件中给出完整的结构体定义
struct Counter {
    int value;
    // 可以有其他私有成员
};

// 6. 实现构造函数
Counter* Counter_create(int initial_value) {
    Counter* self = (Counter*)malloc(sizeof(Counter));
    if (self == NULL) {
        perror("Failed to allocate Counter");
        return NULL;
    }
    self->value = initial_value;
    printf("Counter created with value %d\n", self->value);
    return self;
}

// 7. 实现析构函数
void Counter_destroy(Counter* self) {
    if (self) {
        printf("Destroying Counter with value %d\n", self->value);
        free(self);
    }
}

// 8. 实现方法
void Counter_increment(Counter* self) {
    if (!self) return; // 总是指针检查
    self->value++;
    printf("Counter incremented to %d\n", self->value);
}

int Counter_get_value(const Counter* self) {
    if (!self) return -1; // 或者其他错误码
    return self->value;
}

// ---

// main.c (使用 Counter 对象)
// #include "counter.h"
//
// int main() {
//     Counter* c1 = Counter_create(10);
//     Counter* c2 = Counter_create(0);
//
//     if (c1) {
//         Counter_increment(c1); // 输出: Counter incremented to 11
//         int val = Counter_get_value(c1); // val = 11
//         // c1->value = 99; // 错误!无法访问,因为 Counter 定义不完整
//     }
//
//     if (c2) {
//         Counter_increment(c2); // 输出: Counter incremented to 1
//     }
//
//     Counter_destroy(c1); // 输出: Destroying Counter with value 11
//     Counter_destroy(c2); // 输出: Destroying Counter with value 1
//
//     return 0;
// }

2. 模拟继承 (Inheritance)

C 语言没有原生的继承。模拟继承通常有两种方式:

  • 结构体嵌套 (Embedding for “is-a” relationship):

    • 将“父类”结构体作为“子类”结构体的第一个成员
    • 这样,“子类”对象的指针可以安全地转换为“父类”对象的指针(因为内存布局开头相同)。
    • 可以调用“父类”的方法,只需将子类指针强制转换为父类指针即可。
  • 组合 (Composition for “has-a” relationship):

    • 在一个结构体中包含指向其他结构体的指针。
    • 通过调用包含对象的方法来实现功能。
    • 这通常比模拟继承更灵活、更简单,也是 C 中更常用的方式。

示例:结构体嵌套模拟继承 (Shape -> Circle)

// shape.h
#ifndef SHAPE_H
#define SHAPE_H

typedef struct Shape Shape; // Opaque pointer

struct Shape { // 在 .c 中定义
    float x, y;
};

Shape* Shape_create(float x, float y);
void Shape_destroy(Shape* self);
void Shape_move(Shape* self, float dx, float dy);
void Shape_print_pos(const Shape* self);

#endif // SHAPE_H

// ---

// circle.h
#ifndef CIRCLE_H
#define CIRCLE_H
#include "shape.h" // 包含父类头文件

typedef struct Circle Circle; // Opaque pointer

// 公共方法
Circle* Circle_create(float x, float y, float radius);
void Circle_destroy(Circle* self);
float Circle_get_radius(const Circle* self);
void Circle_draw(const Circle* self); // Circle 特有的方法

// 可以复用 Shape 的方法,需要提供一个获取 Shape 指针的函数或直接转换
Shape* Circle_as_Shape(Circle* self);

#endif // CIRCLE_H

// ---

// circle.c
#include "circle.h"
#include "shape.h" // Shape 的实现也需要,或者只用其接口
#include <stdlib.h>
#include <stdio.h>

// 子类结构体定义
struct Circle {
    struct Shape base; // 父类结构体作为第一个成员!
    float radius;      // 子类特有成员
};

// 返回基类指针的辅助函数
Shape* Circle_as_Shape(Circle* self) {
    return (Shape*)self;
}

Circle* Circle_create(float x, float y, float radius) {
    Circle* self = (Circle*)malloc(sizeof(Circle));
    if (!self) return NULL;
    // 初始化父类部分 - 可以直接访问,因为在同一个 .c 文件中
    // 或者调用 Shape_create (但 Shape_create 可能不知道 Circle 的大小)
    // 更好的方式是提供 Shape_init(Shape* self, ...) 函数
    self->base.x = x;
    self->base.y = y;
    // 初始化子类部分
    self->radius = radius;
    printf("Circle created at (%.1f, %.1f) with radius %.1f\n", x, y, radius);
    return self;
}

void Circle_destroy(Circle* self) {
    if (self) {
        printf("Destroying Circle\n");
        // 如果父类有需要清理的资源,应该先调用父类的清理函数
        // Shape_cleanup(&self->base); // 假设有这样一个函数
        free(self);
    }
}

float Circle_get_radius(const Circle* self) {
    return self ? self->radius : -1.0f;
}

void Circle_draw(const Circle* self) {
    if (!self) return;
    printf("Drawing Circle at (%.1f, %.1f), radius %.1f\n",
           self->base.x, self->base.y, self->radius);
}

// ---

// main.c usage
// #include "circle.h"
// int main() {
//     Circle* c = Circle_create(1, 2, 5.0);
//     if (c) {
//         Circle_draw(c);
//         // 调用继承自 Shape 的方法 (通过类型转换)
//         Shape_move((Shape*)c, 10, 10); // 将 Circle* 转为 Shape*
//         Shape_print_pos((Shape*)c);   // 输出新的位置
//         // 或者使用辅助函数
//         // Shape_move(Circle_as_Shape(c), 10, 10);
//         // Shape_print_pos(Circle_as_Shape(c));
//         Circle_destroy(c);
//     }
//     return 0;
// }

注意: 这种继承模拟比较脆弱,主要依赖于内存布局。没有 C++ 中继承的很多特性(如访问控制、构造函数链式调用等)。


3. 模拟多态 (Polymorphism)

多态允许我们以统一的方式处理不同类型的对象。在 C 中,主要通过函数指针实现。

  • 在“父类”结构体中定义一个包含函数指针的结构体(通常称为 vtablevptr,即虚函数表指针)。
  • 每个“子类”都提供自己实现的函数,并将这些函数的地址填充到自己的 vtable 中。
  • 创建子类对象时,将其 vtable 指针指向正确的 vtable。
  • 通过调用 vtable 中的函数指针来执行操作,这样就会调用到对应子类的实现。

示例:使用 vtable 实现 Shape 的多态 draw()area()

// shape_poly.h
#ifndef SHAPE_POLY_H
#define SHAPE_POLY_H

// 前向声明
typedef struct Shape Shape;
typedef struct ShapeVTable ShapeVTable;

// 虚函数表结构体
struct ShapeVTable {
    void (*draw)(const Shape* self); // 函数指针 draw
    float (*area)(const Shape* self); // 函数指针 area
    void (*destroy)(Shape* self);    // 函数指针 destroy
};

// 父类结构体,包含 vtable 指针
struct Shape {
    const ShapeVTable* vtable; // 指向虚函数表的指针 (通常 const)
    float x, y;              // 父类数据成员
};

// 辅助函数,通过 vtable 调用方法 (通常 inline)
static inline void Shape_draw(const Shape* self) {
    if (self && self->vtable && self->vtable->draw) {
        self->vtable->draw(self);
    }
}
static inline float Shape_area(const Shape* self) {
    if (self && self->vtable && self->vtable->area) {
        return self->vtable->area(self);
    }
    return 0.0f; // Or error
}
static inline void Shape_destroy(Shape* self) {
    if (self && self->vtable && self->vtable->destroy) {
        self->vtable->destroy(self);
    }
}

// 父类初始化函数 (可选,但推荐)
void Shape_init(Shape* self, const ShapeVTable* vtable, float x, float y);

#endif // SHAPE_POLY_H

// ---

// circle_poly.h
#ifndef CIRCLE_POLY_H
#define CIRCLE_POLY_H
#include "shape_poly.h" // 包含父类

typedef struct Circle {
    Shape base; // 父类作为第一个成员
    float radius; // 子类成员
} Circle;

// 子类构造函数
Circle* Circle_create(float x, float y, float radius);

#endif // CIRCLE_POLY_H

// ---

// circle_poly.c
#include "circle_poly.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h> // for M_PI (might need -lm link flag)

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

// --- Circle 类方法的具体实现 ---
static void Circle_draw_impl(const Shape* self) {
    const Circle* circle = (const Circle*)self; // 类型转换回来
    printf("Drawing Circle (poly) at (%.1f, %.1f) with radius %.1f\n",
           circle->base.x, circle->base.y, circle->radius);
}

static float Circle_area_impl(const Shape* self) {
    const Circle* circle = (const Circle*)self;
    return (float)M_PI * circle->radius * circle->radius;
}

static void Circle_destroy_impl(Shape* self) {
    Circle* circle = (Circle*)self;
    printf("Destroying Circle (poly) with radius %.1f\n", circle->radius);
    // 如果 Circle 内部有动态分配的资源,在这里释放
    free(circle); // 最后释放对象本身
}

// --- Circle 类的虚函数表 (通常是 static const) ---
static const ShapeVTable g_circle_vtable = {
    .draw = Circle_draw_impl,
    .area = Circle_area_impl,
    .destroy = Circle_destroy_impl
};

// --- Circle 类的构造函数 ---
Circle* Circle_create(float x, float y, float radius) {
    Circle* self = (Circle*)malloc(sizeof(Circle));
    if (!self) return NULL;

    // 调用父类初始化,并传入 Circle 的 vtable
    Shape_init((Shape*)self, &g_circle_vtable, x, y);

    // 初始化子类成员
    self->radius = radius;
    printf("Circle (poly) created.\n");
    return self;
}

// --- Shape 的初始化函数实现 (可以放在 shape_poly.c) ---
void Shape_init(Shape* self, const ShapeVTable* vtable, float x, float y) {
    if (!self) return;
    self->vtable = vtable;
    self->x = x;
    self->y = y;
}

// ---

// main.c (使用多态)
// #include "shape_poly.h"
// #include "circle_poly.h"
// // #include "rectangle_poly.h" // 假设也定义了 Rectangle 类
//
// int main() {
//     Shape* shapes[2]; // 创建一个父类指针数组
//
//     // 创建不同类型的对象,但都赋值给父类指针
//     shapes[0] = (Shape*)Circle_create(1, 1, 5.0);
//     // shapes[1] = (Shape*)Rectangle_create(2, 2, 10.0, 5.0); // 假设有 Rectangle
//
//     // 统一调用方法,会自动调用到子类的实现
//     for (int i = 0; i < 1; ++i) { // 只用了 Circle
//         if (shapes[i]) {
//             Shape_draw(shapes[i]); // 调用 Circle_draw_impl
//             float area = Shape_area(shapes[i]); // 调用 Circle_area_impl
//             printf("  Area = %.2f\n", area);
//         }
//     }
//
//     // 统一销毁对象
//     for (int i = 0; i < 1; ++i) {
//         Shape_destroy(shapes[i]); // 调用 Circle_destroy_impl
//     }
//
//     return 0;
// }

总结与局限性

  • 优点: 可以在 C 中实现更好的代码组织、封装和一定程度的多态,提高代码的可维护性和复用性。
  • 局限性:
    • 手动管理: 构造、析构、内存管理都需要手动完成,容易出错。
    • 语法繁琐: 需要编写更多的模板代码(如 vtable 定义、类型转换)。
    • 类型安全: 类型转换 ((Shape*)c) 依赖程序员保证正确性,编译器无法检查。
    • 无 RTTI: 没有运行时的类型信息。
    • 继承限制: 模拟的继承不如原生 OOP 语言强大和灵活。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值