C语言的不完整类型和前置声明(转)
声明与定义(Declaration and Definition)
开始这篇文章之前,我们先弄懂变量的declaration和definition的区别,即变量的声明和定义的区别。
一般情况下,我们这样简单的分辨声明与定义的区别:建立存储空间的声明称之为“定义”,而把不需要建立存储空间的称之为“声明”。
其实更为准确地描述的话,变量的声明可以分为两种情况:
(1)一种是需要建立存储空间的。例如:int a;在声明的时候就已经建立了存储空间。这种声明是定义性声明(defining declaration)。即我们平时所说的“定义”;
(2)另一种是不需要建立存储空间的,只是告诉编译器某变量已经在别处定义过了。例如:extern int a;其中,变量a是在别处定义的。这种声明是引用性声明(referning declaration)。即我们平时所说的“声明”。
所以,从广义的角度来说,声明中包含着定义,但是并非所有的声明都是定义。即,定义性声明既是定义又是声明,而引用性声明只是声明。例如,int a;它既是定义又是声明,而extern int a;就只是声明而不是定义。再来看具体的例子:
- int a; // 定义性声明,分配存储空间,初值不确定
- int b = ; // 定义性声明,分配存储空间,赋初值
- extern int c; // 引用性声明,不分配存储空间,只是告诉编译器变量c在别处分配过了
- int a; // 定义性声明,分配存储空间,初值不确定
C语言类型(C Types)
C语言将类型分为三类(C99 6.2.5):
Types are partitioned intoobject types(types that fully describe objects),function types(types that describe functions), andincomplete types(types that describe objects but lack information needed to determine their sizes).
(1)对象类型(object types):对象的大小(size)、内存布局(layout)和对齐方式(alignment requirement)都很清楚的对象。
(2)不完整类型(incomplete types):与对象类型相反,包括那些类型信息不完整的对象类型(incompletely-defined object type)以及空类型(void)。
(3)函数类型(function types):这个很好理解,描述函数的类型 -- 描述函数的返回值和参数情况。
这里我们详细了解下不完整类型。先看哪些情况下一个类型是不完整类型:
再看标准的说明:
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类型(it is an incomplete type that cannot be completed)
sizeof操作符不可以用于不完整类型,也不可以定义不完整类型的数组
为什么要有不完整类型
或者说不完整类型有哪些作用,C里为什么要有不完整类型?
可以这么说,C的不完整类型是提供给C实现封装抽象的唯一工具(这样说,不知道是不是有些武断,欢迎批评指正哈)。
举个例子,我们可以在list.c中定义
- struct __list {
- struct __list *prev;
- struct __list *next;
- viud *data;
- };
在list.h中这样:
- typedef struct __list *list_t;
这样的话,链表结构的具体定义对用户来说就是透明的了,不能直接的访问结构成员,只能提供相应的接口来供访问,这样做的好处显而易见,可以防止用户随意破坏模块内部的抽象数据类型。
示例:
libevent中:
- //event2/event.h
- struct event_base
- #ifdef EVENT_IN_DOXYGEN_
- {/*Empty body so that doxygen will generate documentation here.*/}
- #endif
- ;
- //event-internel.h
- struct event_base {
- /** Function pointers and other data to describe this event_base's
- * backend. */
- const struct eventop *evsel;
- /** Pointer to backend-specific data. */
- void *evbase;
- ...
- }
外部使用libevent库时,只需要inclued <event2/enent.h>,这样可以得到不透明的event_base实例并进行使用,并且完全不会对event_base的内部变量有任何改动。要对event_base进行操作的话只能使用libevent的函数,而内部函数使用event_base时则会include <event-internel.h>,从而能够获取到event_base的实际结构。
不完整类型的缺点
(1)使用不完整类型的话,我们也就只能使用指向该不完整类型的指针了,因为指针类型是平台相关的,即在特定的平台上指针变量的大小是已知的。
(2)在不完整类型还没有完整之前,sizeof操作符是获取不了该类型的大小的。
(3)头文件中我们也是不可以使用inline函数的,因为类型是不完整的,在inline函数中如果访问成员的话,编译器会报错。
前置声明(forward declaration)
维基百科上的定义是:
In computer programming, a forward declaration is a declaration of an identifier (denoting an entity such as a type, a variable, or a function) for which the programmer has not yet given a complete definition. It is required for a compiler to know the type of an identifier (size for memory allocation, type for type checking, such as signature of functions), but not a particular value it holds (in case of variables) or definition (in the case of functions), and is useful for one-pass compilers. Forward declaration is used in languages that require declaration before use; it is necessary for mutual recursion in such languages, as it is impossible to define these functions without a forward reference in one definition. It is also useful to allow flexible code organization, for example if one wishes to place the main body at the top, and called functions below it.
我们可以从上面定义中提取出如下信息:
(1)前置声明是针对类型,变量或者函数而言的
(2)前置声明是个不完整的类型
(3)前置声明会加快程序的编译时间
其实上面的typedef struct __list *list_t;就是建立在前置声明基础上的。
前置声明有哪些作用
(1)前置声明可以有效的避免头文件循环包含的问题,看下面的例子
- // circle.h
- #include "point.h"
- struct circle {
- struct coordinate center;
- };
- // circle.h
- // point.h
- #include "circle.h"
- struct coordinate {
- struct circle cir;
- };
- // point.h
- #include "circle.h"
- int main(int argc, const char *argv[])
- {
- struct circle cir;
- return ;
- }
如果编译这个程序,你会发现因为头文件循环包含而发生编译错误,即使修改头文件如下也还是不行:
- #ifndef __CIRCLE_H__
- #define __CIRCLE_H__
- // circle.h
- #include "point.h"
- struct circle {
- struct coordinate center;
- };
- #endif
- #ifndef __CIRCLE_H__
- #ifndef __POINT_H__
- #define __POINT_H__
- // point.h
- #include "circle.h"
- struct coordinate {
- struct circle cir;
- };
- #endif
- #ifndef __POINT_H__
这个时候就可以使用前置声明轻松的解决这个问题,但是必须要使用指向不完整类型的指针了。
- #ifndef __CIRCLE_H__
- #define __CIRCLE_H__
- // circle.h
- //#include "point.h"
- struct coordinate;
- struct circle {
- struct coordinate *center;
- };
- #endif
- #ifndef __CIRCLE_H__
- #ifndef __POINT_H__
- #define __POINT_H__
- // point.h
- //#include "circle.h"
- struct circle;
- struct coordinate {
- struct circle *cir;
- };
- #endif
- #ifndef __POINT_H__
可以发现我们连头文件都不用包含的,这就可以缩短编译的时间了。
因为前置声明是个不完整类型,所有不完整类型的优缺点和注意事项完全适用于前置声明。
参考链接:
http://blog.csdn.net/xiaoyusmile/article/details/5420252
http://blog.csdn.net/tonywearme/article/details/8136530
http://blog.csdn.net/candcplusplus/article/details/38498707
http://blog.sina.com.cn/s/blog_6f70a9530101en3a.html
http://everything.explained.today/Opaque_data_type/
http://verplant.org/oo_programming_in_c.shtml
http://www.tamabc.com/article/459136.html
http://blog.aaronballman.com/2011/07/opaque-data-pointers/
http://jatinganhotra.com/blog/2012/11/25/forward-class-declaration-in-c-plus-plus/
http://accu.org/index.php/journals/1593
http://spin.atomicobject.com/2014/05/19/c-undefined-behaviors/
https://akrzemi1.wordpress.com/2013/12/11/type-erasure-part-iii/
http://ericniebler.com/2014/11/13/tiny-metaprogramming-library/
http://hubicka.blogspot.com/2014/09/devirtualization-in-c-part-6-enforcing.html
http://jguegant.github.io/blogs/tech/sfinae-introduction.html
《Opaque Type Oriented Programming in C》
《不透明类型》http://os.is-programmer.com/posts/40846.html?utm_source=tuicool&utm_medium=referral
《不透明结构体使用》
《关于不透明指针》
《C及虚拟内存总结》
转自:http://blog.csdn.net/astrotycoon/article/details/41286413
C语言的不完整类型和前置声明(转)的更多相关文章
- C++_前置声明
为什么要有前置声明? eg: -定义一个类 class A,这个类里面使用了类B的对象b,然后定义了一个类B,里面也包含了一个类A的对象a,就成了这样: //a.h #include "b. ...
- C++前置声明
[1]一般的前置函数声明 见过最多的前置函数声明,基本格式代码如下: #include <iostream> using namespace std; void fun(char ch, ...
- Go语言规格说明书 之 类型(Types)
go version go1.11 windows/amd64 本文为阅读Go语言中文官网的规则说明书(https://golang.google.cn/ref/spec)而做的笔记,完整的介绍Go语 ...
- C++类型前置声明
前言 本文总结了c++中前置声明的写法及注意事项,列举了哪些情况可以用前置声明来降低编译依赖. 前置声明的概念 前置声明:(forward declaration), 跟普通的声明一样,就是个声明, ...
- GO语言总结(2)——基本类型
上篇博文总结了Go语言的基础知识——GO语言总结(1)——基本知识 ,本篇博文介绍Go语言的基本类型. 一.整型 go语言有13种整形,其中有2种只是名字不同,实质是一样的,所以,实质上go语言有1 ...
- C语言基础(6)-char类型
1. char常量.变量 使用单引号‘’引起来的就是char的常量 ‘a’是一个char类型的常量 “a”是一个字符串类型的常量 1是一个int型的常量 ‘1’是一个char型的常量 char a; ...
- C语言小结之结构类型
C语言小结之结构类型 @刁钻的游戏 (1)枚举型类型enum COLOR {BLACK,RED,BLUE};//声明一种新的数据类型,其值分别为0,1,2但是用BLACK/RED/BLUE代表也可以这 ...
- 【C语言学习】存储类型
C语言中的存储类型主要有四种:auto.static.extern.register ★auto存储类型 默认的存储类型.在C语言中,假设忽略了变量的存储类型,那么编译器就会自己主动默认为auto型 ...
- C语言学习之枚举类型
前言 枚举(enum)类型是计算机编程语言中的一种数据类型.枚举类型:在实际问题中,有些变量的取值被限定在一个有限的范围内.例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等.如果把 ...
随机推荐
- 【Kruskal+贪心思想】BZOJ3624-[Apio2008]免费道路
国庆万岁!!!!! [题目大意] 给定一张无向图,有两种边的类型为0和1.求一个最小生成树使得边0有k条. [思路] 跑两次Kruskal. 第一次的时候优先选择边1,然后判断有哪些边0还不能连通,那 ...
- hdu 1561 树形dp+分组背包
题意:就是给定n个点,每个地点有value[i]的宝物,而且有的宝物必须是另一个宝物取了才能取,问取m个点可以获得的最多宝物价值. 一个子节点就可以返回m个状态,每个状态表示容量为j(j<=m) ...
- 树形DP--codevs 1380 没有上司的舞会
codevs 1380 没有上司的舞会 变式题目:给定一棵树每个点有一个点权,求一个独立集使得点权和最大,树上的独立集指的是选取树上的点,使尽量多的点不直接相连 时间限制: 1 s 空间限制: 1 ...
- MVC 3.0错误 HTTP 404您正在查找的资源(或者它的一个依赖项)可能已被移除,或其名称已更改,或暂时不可用。请检查以下 URL 并确保其拼写正确。
MVC3.0框架开发项目: 有时在程序运行的时候会出现“HTTP 404.您正在查找的资源(或者它的一个依赖项)可能已被移除,或其名称已更改,或暂时不可用.请检查以下 URL 并确保其拼写正确.”的错 ...
- 给WebAPI的REST接口添加测试页面(二)
在上篇文章中,我对Swagger-UI的基本功能进行了一些介绍,今天在这里介绍一下如何在WebAPI中集成Swagger-UI.这里以一个简单的CRUD的REST服务为例. /// <summa ...
- oracle定时任务(dbms_job)
author:skate time:2007-09-12 http://publish.it168.com/2006/0311/20060311017002.shtml 今天总结下Oracle的任务队 ...
- 字符集UTFMB4
http://my.oschina.net/leejun2005/blog/232732#OSC_h3_4 http://ju.outofmemory.cn/entry/211473
- 学习mfc书籍
Visual C++ and MFC Programming http://www.math.hcmuns.edu.vn/~tatuana/C%20For%20Win/MFC/Tai%20Lieu%2 ...
- 在安卓上,微信公众号无法分享到QQ的解决办法之一
今天做一个微信公众号分享功能,参考微信sdk,代码几乎没有任何问题,但就是分享到QQ失败,以下是我QQ分享部分的代码: wx.onMenuShareQQ({ title: '快来和我一起玩转大脑', ...
- NYOJ 618 追击
追击 时间限制:1000 ms | 内存限制:65535 KB 难度:2 描写叙述 因为洛丹伦南部的兽人暴动,不得不使人类联盟採取最后的手段进行镇压.国王泰瑞纳斯派出了两位最棒的圣骑士以遏制兽人的 ...