写点什么

C 语言数据结构的封装方法

发布于: 2021 年 06 月 04 日

本文是 C 语言封装设计的第三篇文章,前两篇请见《C 语言面向对象的封装方式》《 C语言面向对象的封装方式(示例)》


本文介绍 C 语言中如何封装数据结构,让调用者可以引用这个数据结构,但无法获知这个数据结构的内部构成,也无法读写这个数据结构的成员变量。


解决这个问题,常见的手法是提供一个 new_xxx 函数,返回一个数据结构的指针。

比如,数据结构为 dlist_t, 那么在 dlist.h 和 dlist.c 中定义如下:

// dlist.h
#ifndef DLIST_H#define DLIST_H
typedef 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);// .....
#endif
复制代码


dlist​.c 中的源码为:

// dlist.c#include "dlist.h"
#include <stdlib.h>#include <string.h>
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) { // ...}
// ....
复制代码


这种方法,能够让调用者使用 dlist_t 这个数据结构,但无法获知这个数据结构的内部构成,也无法读写这个数据结构的成员变量。


但这种方法的不足在于,这个数据结构不能在栈上分配,并且这个对象只能从堆中分配,无法从调用者的内存池中分配。


因此,我们对上面这种方法进行改进,彻底解决这些不足。先探讨一下解决的思路:

要想能够在栈上声明这个数据结构变量,那么这个数据结构必须对调用者完全可见,并且在源码编译过程中就能确定这个对象的尺寸大小。但违背了我们的需求:向调用者隐藏数据结构的内部实现。


因此,要完美解决这些问题,我们需要提供两种数据结构,一种对调用者可见,但没有数据结构的内部实现;另一种对调用者不可见,这是真正的数据结构,有完整的内部实现细节。这两种数据结构的大小完全相同,因此可以相互转化。


具体的做法是:对调用者可见的数据结构,是个 char 数组,数组的大小为真正数据结构的大小。提供对应的宏,封装这些细节。


我们用这个思路,改造一下上面的代码:


dlist​.h 中的源码为:

#ifndef DLIST_H#define DLIST_H
#include <stdint.h>#include <stdlib.h>
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 <string.h>#include <assert.h>#include <stdio.h>
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;}
复制代码


通过这种方法,我们就可以实现数据结构的完美封装,调用者可以使用这个数据结构,但不能读写内部成员,并且这个对象可以在栈、堆或用户自己的内存池中分配。


我的微信号 "实力程序员",欢迎大家关注我。

发布于: 2021 年 06 月 04 日阅读数: 6
用户头像

实力程序员,用实力说话! 2021.05.24 加入

超过20年一线产品研发和技术管理的实力程序员

评论

发布
暂无评论
C 语言数据结构的封装方法