C不能完全支持面向对象特性。但是可以通过结构体、函数指针、宏定义实现类似于C++的面向对象特性。

封装:通过头文件只暴露结构体和方法的声明来实现封装。

C中的封装只是在头文件声明中通过是否暴露接口的方式实现。这种方式不能和C++的类一样实现成员访问控制(public/protect/private),对于结构体的成员变量而言,依然可以访问哪些未在头文件声明的内容。尽管类的成员变量没法访问控制,但是类的成员方法可以通过是否在头文件暴露来实现访问控制。

  // object.h - 公共头文件
  #ifndef OBJECT_H
  #define OBJECT_H

  // 不完整类型声明 - 外部无法知道结构体成员,但实际上可以访问
  typedef struct Object Object;

  // 公共接口,类的pulic方法
  Object* object_create(int val);
  void object_destroy(Object* obj);
  int object_get(const Object* obj);

  #endif
  // object.c - 实现文件
  #include <stdio.h>
  #include <stdlib.h>
  #include "object.h"

  // 完整结构体定义 - 只在实现文件中可见,没有public/protected/private的访问控制
  struct Object {
      int member;
      //...    
  };

  Object* object_create(int val) {
      //....
  }

  void object_destroy(Object* obj) {
      //....
  }

  int object_get(const Object* obj) {
      //....
  }

  //私有接口,类的private方法
  static int object_private(const Object* obj) {
      //....
  }

继承:通过将父类的结构体作为子类的结构体的成员实现类继承。

C的继承通过结构体嵌套的方式实现,结构体的成员默认是公有的(public)而类的成员默认是私有的(private)。

基类

  // shape.h
  #ifndef SHAPE_H
  #define SHAPE_H

  typedef struct Shape Shape;

  // 基类公共接口
  Shape* shape_create(int x, int y);
  void shape_destroy(Shape* shape);
  void shape_move(Shape* shape, int dx, int dy);
  void shape_draw(const Shape* shape);

  #endif
  // shape.c
  #include <stdio.h>
  #include <stdlib.h>
  #include "shape.h"

  struct Shape {
      int x, y;  // 位置坐标
      void (*draw)(const Shape* self);  // 虚函数
  };

  static void shape_draw_impl(const Shape* self) {
      printf("Drawing Shape at (%d, %d)\n", self->x, self->y);
  }

  Shape* shape_create(int x, int y) {
      Shape* shape = malloc(sizeof(Shape));
      if (shape) {
          shape->x = x;
          shape->y = y;
          shape->draw = shape_draw_impl;
      }
      return shape;
  }

  void shape_destroy(Shape* shape) {
      free(shape);
  }

  void shape_move(Shape* shape, int dx, int dy) {
      shape->x += dx;
      shape->y += dy;
  }

  void shape_draw(const Shape* shape) {
      if (shape && shape->draw) {
          shape->draw(shape);
      }
  }

派生类

  // circle.h
  #ifndef CIRCLE_H
  #define CIRCLE_H

  #include "shape.h"

  typedef struct Circle Circle;

  // 派生类公共接口
  Circle* circle_create(int x, int y, int radius);
  void circle_destroy(Circle* circle);
  int circle_get_radius(const Circle* circle);

  #endif
  // circle.c
  #include <stdio.h>
  #include <stdlib.h>
  #include "circle.h"

  // 派生类结构体 - 基类必须作为第一个成员
  struct Circle {
      Shape base;    // 继承Shape(必须放在第一个!)
      int radius;    // 派生类特有成员
  };

  // 重写的draw方法
  static void circle_draw_impl(const Shape* self) {
      const Circle* circle = (const Circle*)self;  // 安全转换
      printf("Drawing Circle at (%d, %d) with radius %d\n", 
            circle->base.x, circle->base.y, circle->radius);
  }

  Circle* circle_create(int x, int y, int radius) {
      Circle* circle = malloc(sizeof(Circle));
      if (circle) {
          // 初始化基类部分
          circle->base.x = x;
          circle->base.y = y;
          circle->base.draw = circle_draw_impl;  // 重写方法
          
          // 初始化派生类部分
          circle->radius = radius;
      }
      return circle;
  }

  void circle_destroy(Circle* circle) {
      free(circle);
  }

  int circle_get_radius(const Circle* circle) {
      return circle->radius;
  }

多态:包括重写(Override)和重载(Overloading)。重写可以通过结构体的函数指针或者新建一个函数指针表实现,重载特性通过宏和_Generic实现类似重载。

重写有几种实现方式:一种是在类定义的结构体中使用函数指针,在之前的继承已经展示,另一种是使用虚函数表(类似C++的底层实现机制)的数据结构,在c的结构体中添加一个指针vptr指向虚函数表。

  // vtable.h
  #ifndef VTABLE_H
  #define VTABLE_H

  // 虚函数表定义
  typedef struct {
      void (*draw)(void* self);
      void (*area)(void* self);
      void (*destroy)(void* self);
  } VTable;

  // 基类
  typedef struct {
      const VTable* vtable;  // 虚表指针
      int x, y;
  } Shape;

  // 多态接口函数
  static inline void shape_draw(Shape* shape) {
      shape->vtable->draw(shape);
  }

  static inline void shape_area(Shape* shape) {
      shape->vtable->area(shape);
  }

  static inline void shape_destroy(Shape* shape) {
      shape->vtable->destroy(shape);
  }

  #endif
  // circle_vtable.c
  #include <stdio.h>
  #include <stdlib.h>
  #include "vtable.h"

  typedef struct {
      Shape base;
      int radius;
  } Circle;

  // 圆形方法实现
  static void circle_draw(void* self) {
      Circle* circle = (Circle*)self;
      printf("Drawing Circle at (%d,%d) radius %d\n", 
            circle->base.x, circle->base.y, circle->radius);
  }

  static void circle_area(void* self) {
      Circle* circle = (Circle*)self;
      float area = 3.14159 * circle->radius * circle->radius;
      printf("Circle area: %.2f\n", area);
  }

  static void circle_destroy(void* self) {
      free(self);
  }

  // 圆形虚函数表(静态常量)
  static const VTable circle_vtable = {
      .draw = circle_draw,
      .area = circle_area,
      .destroy = circle_destroy
  };

  Circle* circle_create(int x, int y, int radius) {
      Circle* circle = malloc(sizeof(Circle));
      circle->base.vtable = &circle_vtable;
      circle->base.x = x;
      circle->base.y = y;
      circle->radius = radius;
      return circle;
  }

C的重载需要依赖于宏定义进行,这种方法是麻烦(阅读困难)、复杂(调试困难)且不安全(编译时检查)的,需要谨慎的使用。
一般的,c中单纯的将实现统一功能但是参数不同的函数使用命名区分,不做重载!(linux、sqlite等大型工程中的实践)

  #include <stdio.h>

  // 不同类型的add函数
  int add_int(int a, int b) {
      return a + b;
  }

  double add_double(double a, double b) {
      return a + b;
  }

  const char* add_string(const char* a, const char* b) {
      static char result[256];
      snprintf(result, sizeof(result), "%s%s", a, b);
      return result;
  }

  // 使用_Generic实现重载
  #define add(a, b) _Generic((a), \
      int: add_int, \
      double: add_double, \
      const char*: add_string, \
      char*: add_string \
  )(a, b)

  // 测试重载
  void test_overloading() {
      printf("add(10, 20) = %d\n", add(10, 20));
      printf("add(3.14, 2.71) = %.2f\n", add(3.14, 2.71));
      printf("add(\"Hello, \", \"World!\") = %s\n", add("Hello, ", "World!"));
  }

C语言天生是不能完整的实现面向对象的各种特性的,没必要强求面向对象