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语言天生是不能完整的实现面向对象的各种特性的,没必要强求面向对象