本文共 3553 字,大约阅读时间需要 11 分钟。
本文是C语言封装设计的第三篇文章,前两篇请见和。
本文介绍C语言中如何封装数据结构,让调用者可以引用这个数据结构,但无法获知这个数据结构的内部构成,也无法读写这个数据结构的成员变量。
解决这个问题,常见的手法是提供一个new_xxx 函数,返回一个数据结构的指针。 比如,数据结构为 dlist_t, 那么在 dlist.h和dlist.c中定义如下:// dlist.h#ifndef DLIST_H#define DLIST_Htypedef struct dlist_t dlist_t;dlist_t* new_dlist();int dlist_init(dlist_t* plist, uint64_t capacity);int dlist_destroy(dlist_t* plist);int dlist_append(dlist_t* plist, void* pdata);// .....#endifdlist.c 中的源码为:
// dlist.c#include "dlist.h"#include这种方法,能够让调用者使用dlist_t这个数据结构,但无法获知这个数据结构的内部构成,也无法读写这个数据结构的成员变量。 但这种方法的不足在于,这个数据结构不能在栈上分配,并且这个对象只能从堆中分配,无法从调用者的内存池中分配。#include typedef struct dlist_node_t { struct dlist_node_t* pprev; struct dlist_node_t* pnext; void* pdata;} dlist_node_t;struct dlist_t { dlist_node_t* phead; dlist_node_t* ptail; uint64_t capacity; // ....};dlist_t* new_dlist() { dlist_t* plist = malloc(sizeof(dlist_t)); if (!plist) return NULL; memset(plist, 0, sizeof(dlist_t)); return pplist;}int dlist_init(dlist_t* plist, uint64_t capacity) { // ...}int dlist_destroy(dlist_t* plist) { // ...}int dlist_append(dlist_t* plist, void* pdata) { // ...}// ....
因此,我们对上面这种方法进行改进,彻底解决这些不足。先探讨一下解决的思路:
要想能够在栈上声明这个数据结构变量,那么这个数据结构必须对调用者完全可见,并且在源码编译过程中就能确定这个对象的尺寸大小。但违背了我们的需求:向调用者隐藏数据结构的内部实现。因此,要完美解决这些问题,我们需要提供两种数据结构,一种对调用者可见,但没有数据结构的内部实现;另一种对调用者不可见,这是真正的数据结构,有完整的内部实现细节。这两种数据结构的大小完全相同,因此可以相互转化。
具体的做法是:对调用者可见的数据结构,是个char数组,数组的大小为真正数据结构的大小。提供对应的宏,封装这些细节。
我们用这个思路,改造一下上面的代码:dlist.h 中的源码为:#ifndef DLIST_H#define DLIST_H#include#include typedef struct dlist_t dlist_t;// 在栈上声明一个dlist_t结构,varptr_name 为指向这个结构的指针#define DLIST_VAR(varptr_name) DLIST_VAR2(varptr_name, __LINE__)// 通过malloc 申请一个 dlist_t,arptr_name 为指向这个结构的指针#define DLIST_NEW(varptr_name) dlist_t* varptr_name = (dlist_t*)malloc(DLIST_SIZE);// 在其它struct 中,声明一个dlist_t结构的成员变量#define DLIST_FIELD_DEF(varname) char varname[DLIST_SIZE];// 引用结构中的dlist_t成员,返回dlist_t*的指针#define DLIST_FIELD(full_varname) ((dlist_t*)(full_varname))// for internal use!#define DLIST_SIZE 24#define DLIST_VAR2(varptr_name, n) \ char varptr_name ## n [DLIST_SIZE]; \ dlist_t* varptr_name = (dlist_t*)varptr_name ## n;// public 接口函数int dlist_init(dlist_t* plist, uint64_t capacity);int dlist_destroy(dlist_t* plist);int dlist_append(dlist_t* plist, void* pdata);// .....#endif
dlist.c 中的源码为:
// dlist.c#include "dlist.h"#include我们写一个测试程序,测试一下各种使用场景:#include #include typedef struct dlist_node_t { struct dlist_node_t* pprev; struct dlist_node_t* pnext; void* pdata;} dlist_node_t;struct dlist_t { dlist_node_t* phead; dlist_node_t* ptail; uint64_t capacity; // ....};int dlist_init(dlist_t* plist, uint64_t capacity) { //printf("sizeof(dlist_t) = %d\n", sizeof(dlist_t)); assert(DLIST_SIZE == sizeof(dlist_t)); memset(plist, 0, sizeof(dlist_t)); plist->capacity = capacity; return 0;}int dlist_destroy(dlist_t* plist) { return 0;}int dlist_append(dlist_t* plist, void* pdata) { return 0;}
// test_dlist.c#include "dlist.h"typedef struct queue_t { int len; int cap; DLIST_FIELD_DEF(list)} queue_t;int main() { DLIST_VAR(plist) dlist_init(plist, 1024); dlist_destroy(plist); DLIST_NEW(plist2) dlist_init(plist2, 1024); free(plist2); queue_t q; dlist_init(DLIST_FIELD(q.list), 1024); return 0;}通过这种方法,我们就可以实现数据结构的完美封装,调用者可以使用这个数据结构,但不能读写内部成员,并且这个对象可以在栈、堆或用户自己的内存池中分配。 我的微信号 "实力程序员",欢迎大家关注我。
转载地址:http://gvlsi.baihongyu.com/