查看: 1652|回复: 0

[原创] 你知道C语言的不完全类型吗?

[复制链接]
  • TA的每日心情
    开心
    2023-6-12 14:34
  • 签到天数: 165 天

    连续签到: 1 天

    [LV.7]常住居民III

    发表于 2021-5-21 17:04:54 | 显示全部楼层 |阅读模式
    分享到:

    什么是不完全类型?

    C语言类型分为3类:函数、对象(如char、int、数组、结构体、指针)和不完全类型。

    不完全类型是指除了函数之外,大小不能被确定的类型。比如,声明了一个数组,但不给出数组的长度;声明了一个结构类型,但不给出结构体的定义,只告诉编译器这是一个结构体。在最终你还是必须得给出完整的定义,否则编译器在编译单元中都找不到不完全类型的完整定义信息的话就会报错。

    在C 99标准中对不完全类型是这么描述的:
    The void type comprises an empty set of values; it is an incomplete type that cannot be completed. (C99 6.2.5/19)
    An array type of unknown size is an incomplete type. It is completed, for an identifier of that type, by specifying the size in a later declaration (with internal or external linkage). A structure or union type of unknown content (as described in 6.7.2.3) is an incomplete type. It is completed, for all declarations of that type, by declaring the same structure or union tag with its defining content later in the same scope.(C99 6.2.5/22)
    总结来说,不完全类型就是:
    1、没有给出长度的数组
    2、没有给出具体成员的结构体或联合体
    3、void类型

    举个例子:
    在某个*.c文件中:
    int str[]; //不完全类型数组str定义
    int str[10]; //定义str数组完整的类型信息

    再举一个例子:
    在头*.h文件中声明结构:typedef struct __list *list_t;,最终在*.c文件中定义:
    struct __list {
        struct __list *prev;
        struct __list *next;
        viud   *data;
    };
    注意:不完全类型不包含具体的类型信息,所以在为完整定义前不能通过sizeof来获知大小,并且不完全类型定义不适合局部变量。
    不完整类型的用途
    1、提高代码灵活性。在*.h头文件中声明的数组,不清楚具体使用场景应该需要多大,在*.c中使用数组前再完整定义,就可以很方便的更改数组的大小,也不用再去修改头文件。
    2、两个结构体需要相互指向,唯一能够实现的方式就是不完全结构,例如:
    struct a { struct b *pb; };
    struct b { struct a *pa; };
    3、实现抽象模型的封装,降低程序模块之间的耦合,防止用户直接访问结构成员,破坏内部抽象数据类型。这样可以强制用户通过接口规则访问,隐藏内部实现细节,降低沟通成本。

    下面有一个小故事。
    项目中需要用到环形缓存,于是小伙伴将这个任务交给了你。然后你实现了ring_buffer.c,并在ring_buffer.h头文件中定义了实现功能用的数据结构和接口:
    typedef struct _ring_buffer_type
    {
        uint8_t *phead;               
        uint8_t *ptail;                 
        uint8_t *pread;               
        uint8_t *pwrite;               
        size_t   size;               
        volatile size_t counts;         
    }rcb_t;
    /* 构建并初始化一个环形缓存 */
    err_t  ring_buffer_init(uint8_t *pbuffer, size_t size);
    /* 向缓存中写数据 */
    err_t  ring_buffer_write(rcb_t *const p_rcb, uint8_t *pdata, size_t len);
    /* 从缓存中读数据 */
    err_t  ring_buffer_read(rcb_t *const p_rcb, uint8_t *pdata, size_t len);
    /* 检查缓存已使用的字节数 */
    err_t  ring_buffer_check(rcb_t *const p_rcb, size_t *len);
    经过测试,功能实现很好,任务顺利完成。为了屏蔽功能实现细节你将模块封装成了库,信心十足的交给了小伙伴使用。但是你的伙伴却投来了鄙视的目光,说你的实现的功能有问题,于是你们一起检查他的代码,你发现他写了如下代码。
    ring_buffer_write(&buf_rcb, pdata, 10);
    buf_rcb. pwrite += 10;
    buf_rcb.counts += 10;
    于是你不解的质问小伙伴,为什么要动内部的数据,但小伙伴却说,往里面写入了数据,应该要修改指针啊。你认为的事,小伙伴想的却不一样。
    然后为了不让别人动你内部的数据,于是你在头文件ring_buffer.h中把结构定义改成了:
    typedef struct _ring_buffer_type rcb_t;
    并将结构的定义放在了ring_buffer.c中:
    struct _ring_buffer_type
    {
        uint8_t *phead;              
        uint8_t *ptail;                 
        uint8_t *pread;                 
        uint8_t *pwrite;               
        size_t   size;                  
        volatile size_t counts;         
    };
    从此之后,内部的细节都被隐藏了,封装成库之后别人再也不清楚内部的数据结构了,只能严格按照接口的要求进行调用,自然无法修改你的内部数据了。并且,以后修改内部实现也更方便了,甚至外部的接口都不需要做更改。
    从用户的角度,知道的细节越少越好,即减少了记忆的成本,也避免了一些不必要的麻烦。

    回复

    使用道具 举报

    您需要登录后才可以回帖 注册/登录

    本版积分规则

    关闭

    站长推荐上一条 /3 下一条



    手机版|小黑屋|与非网

    GMT+8, 2025-1-15 06:51 , Processed in 0.111693 second(s), 15 queries , MemCache On.

    ICP经营许可证 苏B2-20140176  苏ICP备14012660号-2   苏州灵动帧格网络科技有限公司 版权所有.

    苏公网安备 32059002001037号

    Powered by Discuz! X3.4

    Copyright © 2001-2024, Tencent Cloud.