C语言面对对象设计模式汇编
面向对象发展到今天,已经出现了许许多多优秀的实践、方法和技术。很多的技术都能够有效的提高软件质量。IBM上的《面向对象软件开发和过程》系列文章对面对对象设计从如下层面进行了详细的介绍:代码是核心、 案例实战(上)、 案例实战(下)、 重用、优化代码的组织、 针对契约设计、 业务建模。
虽然文章中的案例实战或代码示例都是以Java、C++等面对对象语言为主,但是辅以一定的方法和工具,C语言一样能做出优秀的面对对象设计,因为软件设计的基本理念原则是相通的。C语言面对对象设计的主要难点在于,对于抽象、继承、泛型(模板)等面对对象设计手段,在语法层次上缺少直接的支持。
本文试图对如下四个主要的面对对象设计手段提供C语言的解决方案和示例。
- 抽象数据类型
- 对象继承(又叫实现继承)
- 接口继承
- 函数模板
抽象数据类型
抽象数据类型:方法A
要点:
a. 头文件只提供类型声明和接口声明
b. 类型定义和接口实现在.c中完成
c. 接口支持参数类型检查
1 2 3 4 5 6 7 8 9 10 11 | /* stach.h */ #ifndef STACK_H #define STACK_H typedef struct stack *stack; extern stack new ( void ); extern void free (stack *stk); extern int empty(stack stk); extern void push(stack stk, void *x); extern void *pop(stack stk); |
抽象数据类型:方法B
要点:
a. 头文件只提供接口声明
b. 定义一个 void * 指针类型全局变量,封装接口需要的所有和对象相关的信息
c. 类型定义和接口实现在.c中完成
d. 接口不支持参数类型检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | /* set.h */ #ifndef H #define H //Set, Object 本质是对象size的指针,用于new一个新对象 extern const void * Set; extern const void * Object; extern void * new ( const void * type, ...); extern void delete ( void * item); extern void * add ( void * set, const void * element); extern void * find ( const void * set, const void * element); extern void * drop ( void * set, const void * element); extern int contains ( const void * set, const void * element); extern unsigned count ( const void * set); #endif /* main.c */ int main () { void * s = new (Set); void * a = add(s, new (Object)); void * b = add(s, new (Object)); void * c = new (Object); if (contains(s, a) && contains(s, b)) puts ( "ok" ); if (contains(s, c)) puts ( "contains?" ); if (differ(a, add(s, a))) puts ( "differ?" ); if (contains(s, drop(s, a))) puts ( "drop?" ); delete (drop(s, b)); delete (drop(s, c)); return 0; } |
对象继承
对象继承:方法A
要点:
a. 纯虚基类以聚合(关联)方式继承,类型为const void *以便于信息隐藏
b. 非纯虚基类以组合方式继承,类型为const struct superclass_name
c. 所有基类必须作为派生类第一个成员
d. 基类在派生类中以命名为'_'进行信息隐藏
e. 纯虚基类在各个派生类中实例化
f. 对外部不暴露具体数据类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | extern const void * Circle; /* new(Circle, x, y, rad) */ extern const void * Point; /* new(Point, x, y); */ void * new ( const void * class , ...); void delete ( void * item); void draw ( const void * self); void move ( void * point, int dx, int dy); /* .c */ struct Class { size_t size; void * (* ctor) ( void * self, va_list * app); void * (* dtor) ( void * self); void (* draw) ( const void * self); }; struct Point { const void * class ; int x, y; }; struct Circle { const struct Point _; int rad; }; void move ( void * _self, int dx, int dy) { struct Point * self = _self; self -> x += dx, self -> y += dy; } void draw ( const void * self) { const struct Class * const * cp = self; assert (self && * cp && (* cp) -> draw); (* cp) -> draw(self); } |
对象继承:方法B
要点:
a. 基类作为派生类的一个字段,但不必作为派生类的第一个字段
b. 通过 container_of 方法找到派生类的基类
c. 对外不暴露派生类类型,但暴露基类类型
d. 支持多重继承
e. 多用于业务逻辑/流程在派生类模块中实现的场景,基类为派生类提供公共服务
1 2 3 4 5 6 7 | struct ep_device { struct usb_endpoint_descriptor *desc; struct usb_device *udev; struct device dev; }; #define to_ep_device(_dev) \ container_of(_dev, struct ep_device, dev) |
对象继承:方法C
要点:
a. 派生类作为基类的一个字段,此字段以void *类型定义
b. 在派生类模块中对基类此字段进行初始化
c. 对外不暴露派生类类型,但暴露基类类型
d. 多用于业务逻辑/流程在基类模块中实现的场景,派生类为基类提供个性化服务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | struct super_block { /* omitted */ void *s_fs_info; /* Filesystem private info */ /* omitted */ }; int autofs_fill_super( struct super_block *s, void *data, int silent) { struct autofs_sb_info *sbi; /* omitted */ sbi = kzalloc( sizeof (*sbi), GFP_KERNEL); if (!sbi) goto fail_unlock; s->s_fs_info = sbi; /* omitted */ } |
接口继承
接口继承:虚函数表实现
要点:
a. 派生接口对象通过字段inherits继承基接口对象。
b. 派生接口对象通过ata_finalize_port_ops初始化。
c. C99中,若数据结构体中的某个字段被重复初始化,最后一次初始化有效。根据这个特点,实现接口成员的覆盖功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | struct ata_port_operations { /* omitted */ void (*sff_dev_select)( struct ata_port *ap, unsigned int device); /* omitted */ /* * ->inherits must be the last field and all the preceding * fields must be pointers. */ const struct ata_port_operations *inherits; }; const struct ata_port_operations ata_base_port_ops = { .prereset = ata_std_prereset, .postreset = ata_std_postreset, .error_handler = ata_std_error_handler, }; const struct ata_port_operations sata_port_ops = { .inherits = &ata_base_port_ops, .qc_defer = ata_std_qc_defer, .hardreset = sata_std_hardreset, }; const struct ata_port_operations sata_pmp_port_ops = { .inherits = &sata_port_ops, .pmp_prereset = ata_std_prereset, .pmp_hardreset = sata_std_hardreset, .pmp_postreset = ata_std_postreset, .error_handler = sata_pmp_error_handler, }; static void ata_finalize_port_ops( struct ata_port_operations *ops) { static DEFINE_SPINLOCK(lock); const struct ata_port_operations *cur; void **begin = ( void **)ops; void **end = ( void **)&ops->inherits; void **pp; if (!ops || !ops->inherits) return ; spin_lock(&lock); for (cur = ops->inherits; cur; cur = cur->inherits) { void **inherit = ( void **)cur; for (pp = begin; pp < end; pp++, inherit++) if (!*pp) *pp = *inherit; } for (pp = begin; pp < end; pp++) if (IS_ERR(*pp)) *pp = NULL; ops->inherits = NULL; spin_unlock(&lock); } |
接口继承:宏实现
要点:
a. 基类接口对象用宏实现。
b. 派生接口对象通过直接内嵌基类接口对象宏来实现继承。
c. C99中,若数据结构体中的某个字段被重复初始化,最后一次初始化有效。根据这个特点,实现接口成员的覆盖功能。
d. 只适用于两层继承结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #define BCM_XXX_OPS .dwBoardId = 0xffff, \ .trunk_id_translate = NULL, \ .trunk_add_port = bcm_XXX_trunk_add_port, \ ... \ .trunk_del_port = bcm_XXX_trunk_del_port BCM_OPERATIONS bcm_XXX_table[]= { {BCM_XXX_OPS}, /*default*/ {BCM_XXX_OPS, .dwBoardId = _BT_3G_USRUD, .pre_config = bcm_XXX_pre_config, .packet_pro_config = bcm_XXX_packet_pro_config}, {BCM_XXX_OPS, .dwBoardId = _BT_3G_HMPUE, .pre_config = bcm_XXX_pre_config, .packet_pro_config = bcm_XXX_packet_pro_config}, {_BT_3G_NULL,0}, }; |
函数模板
函数模板: 参数多态实现
要点:
a. 通用操作,如:构造函数、解析函数、比较函数、复制函数等
b. 函数模板参数以 void * 定义
c. 把相关模板函数转化为纯虚基类的成员函数
d. 模板函数基于纯虚基类上实现
e. 每个派生类继承纯虚基类,纯虚基类以关联方式继承,类型为const void *以便于信息隐藏
f. 所有基类必须作为派生类第一个成员,所以不支持多重继承方式
g. 派生类具体实现自己特定接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | /* new.h */ void * new ( const void * class , ...); void delete ( void * item); void * clone ( const void * self); int differ ( const void * self, const void * b); size_t sizeOf ( const void * self); /* new.r */ struct Class { size_t size; void * (* ctor) ( void * self, va_list * app); void * (* dtor) ( void * self); void * (* clone) ( const void * self); int (* differ) ( const void * self, const void * b); }; /* new.c */ void * new ( const void * _class, ...) { const struct Class * class = _class; void * p = calloc (1, class -> size); assert (p); * ( const struct Class **) p = class ; if ( class -> ctor) { va_list ap; va_start (ap, _class); p = class -> ctor(p, & ap); va_end (ap); } return p; } /* string.h */ extern const void * String; /* string.c */ struct String { const void * class ; /* must be first */ char * text; }; static void * String_ctor ( void * _self, va_list * app) { struct String * self = _self; const char * text = va_arg (* app, const char *); self -> text = malloc ( strlen (text) + 1); assert (self -> text); strcpy (self -> text, text); return self; } static const struct Class _String = { sizeof ( struct String), String_ctor, String_dtor, String_clone, String_differ }; const void * String = & _String; /* app.c */ int main () { void * a = new (String, "a" ); //... delete (a); return 0; } |
函数模板: 宏实现
要点:
a. 函数定义成宏表达式,因为宏表达式不携带类型。当然这损失了代码可维护性。
b. 函数参数类型通过C关键词typeof获取
c. 变量读写尽量利用内存操作,因为内存操作可泛化具体数据类型,通过union和sizeof获取参数首址和长度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | #define WRITE_ONCE(x, val) \ ({ \ union { typeof(x) __val; char __c[1]; } __u = \ { .__val = (__force typeof(x)) (val) }; \ __write_once_size(&(x), __u.__c, sizeof (x)); \ __u.__val; \ }) static __always_inline void __write_once_size( volatile void *p, void *res, int size) { switch (size) { case 1: *( volatile __u8 *)p = *(__u8 *)res; break ; case 2: *( volatile __u16 *)p = *(__u16 *)res; break ; case 4: *( volatile __u32 *)p = *(__u32 *)res; break ; case 8: *( volatile __u64 *)p = *(__u64 *)res; break ; default : barrier(); __builtin_memcpy(( void *)p, ( const void *)res, size); barrier(); } } |
函数模板: 函数指针实现
要点:
a. 构造调用服务对象,来封装函数及其参数信息,不同的函数通过服务id来识别
b. 通过可变参数实现不同服务的参数提取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | #include <stdio.h> #include <stdarg.h> struct call_svc { long id; long func; long arg_num; }; static int a( void ) { printf ( "%s(void)\n" , __FUNCTION__); return 0; } static int b( long arg1) { printf ( "%s(%ld)\n" , __FUNCTION__, arg1); return 0; } static int c( long arg1, long arg2, char * str) { printf ( "%s(%ld, %ld, %s)\n" , __FUNCTION__, arg1, arg2, str); return 0; } static struct call_svc svc[] = { {0, ( long )&a, 0}, {1, ( long )&b, 1}, {2, ( long )&c, 3}, }; static int call( long id, ...) { int svc_num = sizeof (svc)/ sizeof (svc[0]); int i ,ret; va_list argptr; for (i=0; i<svc_num; i++) { if (id == svc[i].id) break ; } if (i >= svc_num) { printf ( "unsupported svc id: %ld\n" , id); return -1; } va_start (argptr, svc[i].arg_num); switch (svc[i].arg_num) { case 0: ret = (( int (*)())svc[i].func)(); break ; case 1: { long v1 = va_arg ( argptr, long ); ret = (( int (*)( long ))svc[i].func)(v1); } break ; case 3: { long v1 = va_arg ( argptr, long ); long v2 = va_arg ( argptr, long ); char *v3 = va_arg ( argptr, char *); ret = (( int (*)( long , long , char *))svc[i].func)(v1, v2, v3); } break ; default : printf ( "unsupported svc param number. id: %ld, arg_num: %ld.\n" , id, svc[i].arg_num); break ; } va_end (argptr); return ret; } int main( void ) { call(0); call(1, 11); call(2, 11, 22, "hello world" ); return 0; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?