结构、联合和枚举

结构、联合和枚举

l  所谓的抽象数据类型,应该只在源文件中看到细节才完美;

l  对于大工程而言,最好将函数原型和结构声明放在单独的头文件中;

l  在结构中,比较安全的定义方式是使用指针而不是数组;

l  不能使用==!=来比较结构是因为可能会遇到结构中没有使用的洞hole的随机内容而导致失败;

l  当结构作为函数参数传递的时候,通常会把整个结构都推进栈,需要多少空间就使用多少空间,正是为了避免这个代价,程序员经常使用指针而不是结构

l  为了广泛的可移植性,在读写文件的时候,最好使用二进制b选项;

l  正确地对齐是种编译器高效访问的策略;

l  将结构中的域按照从大到小的顺序排列可以最大限度地降低填充的影响;

l  结构为一级对象,而结构为二级对象,不能直接对结构进行赋值等;

l  在原来的ANSI C中,只有联合中的第一个成员可以被初始化,C99引入了指定初始式,可以用来初始化任意成员

l  联合无法跟踪到底是哪一个域在使用;

l  枚举的优点为:自动赋值;调试器在检验枚举变量时,可以显示符号值;它们服从数据块作用域规则。一个缺点是程序员不能控制这些非致命的警告,有些程序员则反感于无法控制枚举变量的大小;

l  位域冒号:指定二进制大小的方法只适用于结构和联合的成员;

位域是可以移植的;

声明和初始化

声明和初始化

l  char至少有8位,short intint至少有16位,long int至少有32位,在C99中,long long至少有64位;

l  C语言中,唯一能够让你以二进制位的方式指定大小的地方就是结构中的位域;

l  尽管一个全局变量或函数可以在多个编译单元中有多处声明declaration,但是定义definiton却最多只能允许出现一次

l  extern只对数据声明有意义,因为函数默认就有该属性;

l  typedef用来定义新的类型名称,而不是定义新的变量或函数;

l  对于typedefdefine而言,最好使用typedef,因为它可以处理指针类型,而define有时不可以,就算可以,也要加很多的括号;

l  定义结构中包含结构本身指针的较好风格:

n  clip_image002

l  C语言有4种作用域:函数、文件、块和原型;

l  C语言有四种命名空间:

n  行标label:即goto的目的地;

n  标签tag:结构、联合和枚举的名称;

n  结构/联合成员;

n  普通标识符:函数、变量、类型定义名称和枚举常量;

结构和联合

结构和联合

结构基础知识

       聚合数据类型(aggregate data type)能够同时存储超过一个的单独数据。C提供了两种类型的聚合数据类型:数组和结构

       数组:是相同类型的元素的集合,它的每个元素时通过下标引用或指针间接访问来选择的;

       结构:也是一些值的集合,这些值成为它的成员member,但一个结构的各个成员可能具有不同的类型。

结构声明

       结构声明为:

       struct tag {member-list} variable-list;

       红色部分为可选部分,可选部分不能全部省略,至少要出现两个

 

struct {

       int a;

       char b;

       float c;

}x;

 

struct {

       int a;

       char b;

       float c;

}y[10],*z;

如上面所示,即使两个结构的成员列表完全相同,但是编译器仍然会把它们当做完全不同的两种类型来对待。

结构成员的直接访问

       直接访问通过点操作符.访问。点操作符结构两个操作数,左操作数就是结构变量的名字,右操作数就是需要访问的成员的名字。

结构成员的间接访问

对于结构

struct data {

       int a;

       char b;

       float c;

};

那么struct data *x,我们应该使用(*x).a来访问a,但是这样写太繁琐。C语言提供了一种更方便的操作符,即箭头操作符。使用x->a即可访问a

结构的自引用

       在一个结构内部包含一个类型为该结构本身的成员是非法的clip_image001,比如:

struct data {

       int a;

       char b;

       float c;

       struct data x;

};

其中的struct data会无限递归。我们可以通过包含一个该结构的指针来解决

struct data {

       int a;

       char b;

       float c;

       struct data *x;

};

正确的原因是,编译器在结构的长度确定之前就已经知道了指针的长度,所以这种类型的自引用是合法的。

如果你觉得一个结构内部包含一个指向该结构本身的指针有些奇怪,请记住它事实上所指向的是同一种类型的不同结构,比如更高级的数据结构,链表和树,都是采用这种技巧实现的。

警惕陷阱

typedef struct {

       int a;

       char b;

       float c;

       DATA *x;

}DATA;

上面这个结构是失败的,因为类型名DATA直到声明的末尾才定义,所以在结构声明的内部它尚未定义。解决方法为定义一个结构标签:

typedef struct DATA_TAG {

       int a;

       char b;

       float c;

       struct DATA_TAG *x;

}DATA;

不完整的声明

       当两个结构互相引用时,肯定存在一些依赖的结构。此时应该首先声明哪一个呢?我们可以使用不完整声明(incomplete declaration)来解决这个问题,它声明一个作为结构标签的标识符。然后,我们可以把这个标签用在不需要知道这个结构的长度的声明中,如声明指向这个结构的指针。接下来的声明把这个标签与成员列表联系在一起。例如:

struct B;

 

struct A {

       struct B *partner;

       int a;

       float b;

}

 

struct B {

       struct A *partner;

       int a;

       float b;

}

访问结构成员

       比如对结构:

struct data {

       int a;

       char b;

       float c;

       struct data *x;

};

我们定义一个struct data m

pa = &m

pi = &(m.a);

我们可以知道papi的值其实是相同的,因为pa是整个结构的地址,而pia的地址,而a是结构的第一个元素,所以值相同。但是两个是不等同的,因为pa是指向结构的指针,而pi是指向int的指针。可以通过强制类型转换pi = (int *)pa来令两者相等,但是有很大危险性。

作为函数参数的结构

       当把结构作为参数产地给函数时,最好使用结构的指针,而不是结构本身。比如:

struct data {

       int a;

       char b;

       float c;

       struct data *x;

}DATA;

 

l  void test(DATA x);   这种方法是不提倡的;复制DATA,如果DATA结构十分庞大,会占用很大的空间;

l  void test(DATA *x);   这种方法是提倡的clip_image002只是传递一个指针。

l  void test(register DATA const*x);  这种方法是大力提倡的clip_image002[1]clip_image002[2]clip_image002[3],这种模式可以提高速度,并且可以防止指针被修改。

 

什么时候应该向函数传递一个结构而不是指针呢?很少有这种情况。只有当一个结构特别的小(长度和指针相同或更小)时,结构传递方案的效率才不会输给指针传递方案。但是对于绝大多数的结构,传递指针显然效率更高。如果你希望修改结构的任何成员,也应该使用指针传递方案。

位段

       位段的声明和结构类似,但它的成员时一个或多个位的字段,这些不同长度的字段实际上存储于一个或多个整型变量中

       位段的声明和任何普通的结构成员声明相同,但有两个例外:

l  首先,位段成员必须声明为intsigned intunsigned int类型;

l  其次,在成员们的后面是一个冒号和一个整数,这个整数指定该位段所占用的位的数目。

 

Mark:用signedunsigned整数显式地声明位段是个好主意,因为int在不同的机器上解释可能不同,指明了signedunsigned类型有利于移植。

       使用位段的好理由是,位段能够把长度为奇数的数据包装在一起,节省存储空间;使用位段还可以很方便地访问一个整型值的部分内容。

联合

       联合的所有成员引用的是内存中的相同位置。当你想在不同的时刻把不同的东西存储于同一个位置时,就可以使用联合。

clip_image004

       上述联合在我的机器上共占用8个字节。

l  如果成员f被使用,这个字就作为浮点型访问;

l  如果成员i被使用,这个字就作为整型值访问;

l  如果成员d被使用,这个字就作为双精度浮点型访问;

如果联合的各个成员具有不同的长度,联合的长度就是它最长成员的长度。比如,上述的联合fid的联合长度就应该是double类型做占用的字节数。这样的话,联合就足以容纳它最大的成员,但是如果这些成员的长度相差悬殊,当存储长度较短的成员时,浪费的空间是相当可观的。在这种情况下,更好的方法是在联合中存储指向不同成员的指针而不是直接存储成员本身,这样就解决了内存浪费的问题,当它需要使用哪个成员时,就分配正确数量的内存来存储它。

联合的初始化

       联合可以被初始化,但这个初始值必须是第一个成员的类型(如果被赋予其他值,有可能会被强制类型转换),而且它必须位于一对花括号里面。例如:

clip_image006

总结

l  不同的结构声明即使它们的成员列表相同也被认为是不同的类型;

l  结构不能包含类型也是这个结构的成员,但是它的成员可以是一个指向这个结构的指针,这个技巧长用于链式数据结构中;

l  为了声明两个结构,每个结构都包含一个指向对方的指针的成员,我们需要使用不完整的声明来定义一个结构标签名;

l  结构可以作为参数传递给函数,也可以作为返回值从函数返回。但是,向函数传递一个指向结构的指针往往效率更高。在结构指针参数的声明中可以加上const关键字防止函数修改指针所指向的结构。

l  向函数传递结构参数是低效的,最好传递指针。