2295|5

227

帖子

0

TA的资源

一粒金砂(高级)

楼主
 

你知道C语言的不完全类型吗? [复制链接]

什么是不完全类型?

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;          

};

从此之后,内部的细节都被隐藏了,封装成库之后别人再也不清楚内部的数据结构了,只能严格按照接口的要求进行调用,自然无法修改你的内部数据了。并且,以后修改内部实现也更方便了,甚至外部的接口都不需要做更改。

从用户的角度,知道的细节越少越好,即减少了记忆的成本,也避免了一些不必要的麻烦。

最新回复

某个软件大师(确实的大师)把这种表达归类成一种类似面向对象封装的形式,其实是没错的。   详情 回复 发表于 2021-5-25 21:57
点赞 关注
个人签名

欢迎关注“麦克泰技术”

 
 

回复
举报

6593

帖子

0

TA的资源

五彩晶圆(高级)

沙发
 

楼主的小故事讲明白了

 
 
 

回复

7452

帖子

18

TA的资源

五彩晶圆(高级)

板凳
 

写的很好!谢谢分享

个人签名

默认摸鱼,再摸鱼。2022、9、28

 
 
 

回复

227

帖子

0

TA的资源

一粒金砂(高级)

4
 
Jacktang 发表于 2021-5-21 21:35 楼主的小故事讲明白了

哈哈感谢,看来我有讲故事的潜力了

个人签名

欢迎关注“麦克泰技术”

 
 
 

回复

227

帖子

0

TA的资源

一粒金砂(高级)

5
 
freebsder 发表于 2021-5-21 22:38 写的很好!谢谢分享

感谢支持哈~

个人签名

欢迎关注“麦克泰技术”

 
 
 

回复

7452

帖子

18

TA的资源

五彩晶圆(高级)

6
 

某个软件大师(确实的大师)把这种表达归类成一种类似面向对象封装的形式,其实是没错的。

个人签名

默认摸鱼,再摸鱼。2022、9、28

 
 
 

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

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表