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

实力程序员 发表于 2021/06/07 09:13:32 2021/06/07
【摘要】 本文介绍C语言中如何封装数据结构,让调用者可以引用这个数据结构,但无法获知这个数据结构的内部构成,也无法读写这个数据结构的成员变量。

本文是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;
}


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


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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区),文章链接,文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件至:cloudbbs@huaweicloud.com进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容。
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。