C语言的不完整类型和前置声明

声明与定义(Declaration and Definition)
html

开始这篇文章以前,咱们先弄懂变量的declaration和definition的区别,即变量的声明定义的区别。数组

通常状况下,咱们这样简单的分辨声明定义的区别:创建存储空间的声明称之为“定义”,而把不须要创建存储空间的称之为“声明”。electron

其实更为准确地描述的话,变量的声明能够分为两种状况:ide

(1)一种是须要创建存储空间的。例如:int a;在声明的时候就已经创建了存储空间。这种声明是定义性声明(defining declaration)。即咱们平时所说的“定义”;函数

(2)另外一种是不须要创建存储空间的,只是告诉编译器某变量已经在别处定义过了。例如:extern int a;其中,变量a是在别处定义的。这种声明是引用性声明(referning declaration)。即咱们平时所说的“声明”。工具

因此,从广义的角度来讲,声明中包含着定义,可是并不是全部的声明都是定义。即,定义性声明既是定义又是声明,而引用性声明只是声明。例如,int a;它既是定义又是声明,而extern int a;就只是声明而不是定义。再来看具体的例子:布局

  1. int a;        // 定义性声明,分配存储空间,初值不肯定  flex

  2. int b = 0;    // 定义性声明,分配存储空间,赋初值  ui

  3. extern int c; // 引用性声明,不分配存储空间,只是告诉编译器变量c在别处分配过了    spa

 

C语言类型(C Types)

C语言将类型分为三类(C99 6.2.5):

Types are partitioned into object types(types that fully describe objects), function types(types that  describe  functions),  and incomplete  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):这个很好理解,描述函数的类型 -- 描述函数的返回值和参数状况。

这里咱们详细了解下不完整类型。先看哪些状况下一个类型是不完整类型:

    (1)具体的成员还没定义的结构体(共用体)

    (2)没有指定维度的数组

    (3)void类型(it is an incomplete type that cannot be completed) 

sizeof操做符不能够用于不完整类型,也不能够定义不完整类型的数组

为何要有不完整类型

或者说不完整类型有哪些做用,C里为何要有不完整类型?

能够这么说,C的不完整类型是提供给C实现封装抽象的惟一工具(这样说,不知道是否是有些武断,欢迎批评指正哈)。

举个例子,咱们能够在list.c中定义

  1. struct __list {  

  2.     struct __list *prev;  

  3.     struct __list *next;  

  4.     viud      *data;  

  5. };  

在list.h中这样:

  1. typedef struct __list *list_t;  

这样的话,链表结构的具体定义对用户来讲就是透明的了,不能直接的访问结构成员,只能提供相应的接口来供访问,这样作的好处显而易见,能够防止用户随意破坏模块内部的抽象数据类型。

不完整类型的缺点

(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)前置声明能够有效的避免头文件循环包含的问题,看下面的例子

  1. // circle.h  

  2. #include "point.h"  

  3.   

  4. struct circle {  

  5.     struct coordinate center;  

  6. };   

  1. // point.h  

  2. #include "circle.h"  

  3.   

  4. struct coordinate {  

  5.     struct circle cir;  

  6. };  

  1. #include "circle.h"  

  2.   

  3. int main(int argc, const char *argv[])  

  4. {  

  5.     struct circle cir;  

  6.     return 0;  

  7. }  

若是编译这个程序,你会发现由于头文件循环包含而发生编译错误,即便修改头文件以下也仍是不行:

  1. #ifndef __CIRCLE_H__  

  2. #define __CIRCLE_H__  

  3. // circle.h  

  4. #include "point.h"  

  5.   

  6. struct circle {  

  7.     struct coordinate center;  

  8. };  

  9. #endif  

  1. #ifndef __POINT_H__  

  2. #define __POINT_H__  

  3. // point.h  

  4. #include "circle.h"  

  5.   

  6. struct coordinate {  

  7.     struct circle cir;  

  8. };  

  9. #endif  

这个时候就能够使用前置声明轻松的解决这个问题,可是必需要使用指向不完整类型的指针了。

  1. #ifndef __CIRCLE_H__  

  2. #define __CIRCLE_H__  

  3. // circle.h  

  4. //#include "point.h"  

  5.   

  6. struct coordinate;  

  7. struct circle {  

  8.     struct coordinate *center;  

  9. };  

  10. #endif  

  1. #ifndef __POINT_H__  

  2. #define __POINT_H__  

  3. // point.h  

  4. //#include "circle.h"  

  5.   

  6. struct circle;  

  7. struct coordinate {  

  8.     struct circle *cir;  

  9. };  

  10. #endif  

能够发现咱们连头文件都不用包含的,这就能够缩短编译的时间了。

由于前置声明是个不完整类型,全部不完整类型的优缺点和注意事项彻底适用于前置声明。

 

参考连接:

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://www.embedded.com/electronics-blogs/programming-pointers/4024893/Incomplete-types-as-abstractions

http://www.tuicool.com/articles/RbYVnq

相关文章
相关标签/搜索