C语言的类型分为函数类型、对象类型和不完全类型三大类。对象类型又分为标量类型和非标量类型。指针类型属于标量类型,因此也可以做逻辑与、或、非运算的操作数和if、for、while的控制表达式,NULL指针表示假,非NULL指针表示真。不完全类型是暂时没有完全定义好的类型,编译器不知道这种类型占几个字节的存储空间,例如:
struct s;
union u;
char str[];
具有不完全类型的变量可以通过多次声明组合成一个完全类型,比如数组str声明两次:
char str[];
char str[10];
当编译器碰到第一个声明时,认为str是一个不完全类型,碰到第二个声明时str就组合成完全类型了,如果编译器处理到程序文件的末尾仍然无法把str组合成一个完全类型,就会报错。读者可能会想,这个语法有什么用呢?为何不在第一次声明时就把str声明成完全类型?有些情况下这么做有一定的理由,比如第一个声明是写在头文件里的,第二个声明写在.c文件里,这样如果要改数组长度,只改.c文件就行了,头文件可以不用改。
如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:
struct B; //对结构体B进行不完整声明
//结构体A中包含指向结构体B的指针
struct A { struct B *partner; //other members; };
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B { struct A *partner; //other members; };
当结构体中,使用“本结构体类型的指针”(注意并非“指向本结构体的指针”)作为成员,这个指针指向的数据类型是这种结构,但不一定是自己所在的这个结构。
struct list
{
......
list *next;
};
当代码出现struct list的时候,编译器就认为已经声明了list这种类型,注意只是声明。然后在结构体里面就可以使用list类型的指针或者引用了。但是,如果使用list定义变量,如list nest; 那么计算一下sizeof(结点),会发现这个值是无穷的,因为结点里面包含结点,一直循环,根本停不下来。但是用指针的话就不一样,因为指针的字节数是确定的,然后给他赋值为下一结点的地址就可以访问下一个结点了。而且它还没有定义完,所以在此之前只能使用引用或者指针。
用这个结构声明出一个变量之后,里面包含的那个指针只是个指针而已,在被你赋值之前它并没有指向自己,之后你给它赋谁的值,它就指向谁。
另一个疑惑可能是想这个结构还没定义完,怎么就能在内部用?可以这样理解:对编译器来说,反正所有的指针占用的空间都是一样的,你只要告诉编译器这是个指针就行了。那么既然本质上没区别,那么编译器也就没必要深究这个指针类型到底是咋回事。事实上也有很多代码干脆就把这个指针定义成void*的,就是为了糊弄一些爱较真的编译器。
这个结构体中的指针并不是指向本身,而是另外一个结构体,比如链表,这样做才能使得该节点可以迅速链接到下一节点。要区分什么是结构体的定义,什么是结构体的实例。
指针也是一种数据类型啊,结构里面包含一个类型为指针的成员,至于这个指针是指向另外一个啥啥,对这个结构体来说不重要。