C Primer Plus之结构和其他数据形式
声明和初始化结构指针
声明结构化指针,例如:
struct guy * him;
初始化结构指针(如果barney是一个guy类型的结构),例如:
him = &barney;
注意:和数组不同,一个结构的名字不是该结构的地址,必须使用&运算符(类似普通变量)。
问题:如何使用指针来访问成员?
1° 使用一个新运算符:->
him->income is barney.income if him == &barney
2° 如果him = &barney,那么*him = barney,因为&和*是一对互逆的运算符。
barney.income == (*him).income
总之,如果him是指向名为barney的guy类型结构的指针,则下列表达式是等价的:
barney.income == (*him).income == him->income //假设him == &barney
C允许把一个结构赋值给一个结构,但不能对数组这样做,也就是说,如果n_data和o_data是同一类型的结构,那么:
o_data = n_data; // 把一个结构赋值给一个结构
这就使o_data的每个成员都被赋成n_data相应成员的值,即使有一个成员是数组也照样完成赋值。也可以把一个结构初始化为一个同样类型的结构:
struct names right_field = {"Ruthie", "George"}; struct names captain = right_field; // 把一个结构初始化为一个结构
结论:通常,程序员为了追求效率而使用结构指针作为参数;当需要保护数据、防止意外改变数据时对指针使用const限定词。传递结构值是处理小型结构最常用的方法。
C99具有一个称为伸缩型数组成员的新特性。利用这一新特性可以声明最后一个成员是一个具有特殊属性的数组的结构。该数组成员的特殊属性之一是它不存在,至少不立即存在。第二个特殊属性是您可以编写适当的代码使用这个伸缩型数组成员,就像它确实存在并且拥有您需要的任何数目的元素一样。
声明一个伸缩型数组成员的规则:
- 伸缩型数组成员必须是最后一个数组成员
- 结构中必须至少有一个其他成员
- 伸缩型数组就像普通数组一样被声明,除了它的方括号内是空的
例如:
struct flex { int count; double average; double scores[]; // 伸缩型数组成员 };
C99的意图并不是让您声明struct flex类型的变量;而是希望您声明一个指向struct flex类型的指针,然后使用malloc()来分配足够的空间,以存放struct flex结构的常规内容和伸缩型数组成员需要的任何额外空间。例如,假设想要用scores表示含有5个double型数值的数组,那么:
struct flex * pf; pf = malloc(sizeof(struct flex) + 5 * sizeof(double)); // 请求一个结构和一个数组的空间
例如:
#include <stdio.h> #include <stdlib.h> struct flex { int count; double average; double scores[]; // 伸缩型数组成员 }; void showFlex(const struct flex * p); int main(void) { struct flex * pf1, * pf2; int n = 5; int i; int tot = 0; pf1 = malloc(sizeof(struct flex) + n * sizeof(double)); // 没有指派类型,通用指针 pf1->count = n; for(i = 0; i < n; i++) { pf1->scores[i] = 20.0 - i; tot += pf1->scores[i]; } pf1->average = tot / n; showFlex(pf1); n = 9; tot = 0; pf2 = malloc(sizeof(struct flex) + n * sizeof(double)); // 同上 pf2->count = n; for(i = 0; i < n; i++) { pf2->scores[i] = 20.0 - i / 2.0; tot += pf2->scores[i]; } pf2->average = tot / n; showFlex(pf2); free(pf2); free(pf1); return 0; } void showFlex(const struct flex * p) { int i; printf("Scores: "); for(i = 0; i < p->count; i++) printf("%g ", p->scores[i]); printf("\nAverage: %g\n", p->average); }
联合简介
联合(union)是一个能在同一个存储空间里(但不同时)存储不同类型数据的数据类型。一个典型的应用是一种表,设计它是用来以某种既没有规律、事先也未知的顺序保存混合类型数据。使用联合类型的数组,可以创建相同大小单元的数组,每个单元都能存储多种类型的数据。
联合是以与结构同样的方式建立的,也是需要有一个联合模板和一个联合变量。例如:
union hold { int digit; double bigfl; char letter; };
注意:具有类似声明的结构可以含有一个int型数值和一个double型数值以及一个char型数值,而这个联合可以含有一个int型数值或一个double型数值或一个char型数值。
下面是定义3个hold类型联合变量的例子:
union hold fit; // hold类型的联合变量 union hold save[10]; // 10个联合变量的数组 union hold * pu; // 指向hold类型变量的指针
可以初始化一个联合。因为联合只存储一个值,所以初始化的规则与结构的初始化不同。具体地,有3种选择:
- 把一个联合初始化为同样类型的一个联合
- 初始化联合第一个元素
- 按照C99标准,可以使用一个指定的初始化项目
union hold valA; valA.letter = 'R'; union hold valB = valA; // 把一个联合初始化为一个联合 union hold valC = {88}; // 初始化联合的digit成员 union hold valD = {.bigfl = 118.2}; // 指定初始化项目
注意:对于联合这种数据类型,在同一个时间只能存储一个值。即使有足够的空间,也不能同时保存一个char类型和一个int类型的值。由您负责记住当前保存在联合中的数据的类型。
可以与指向联合的指针一起来使用->运算符
pu = &fit; x = pu->digit; // 相当于 x = fit.digit
使用联合的适当场合:
- 使用一个成员来将值保存到一个联合中,该结构所存储的信息有赖于其中的一个成员。
- 在某些结构中,该结构所存储的信息有赖于其中的一个成员。
枚举类型
可以使用枚举类型声明代表整数常量的符号名称。通过使用关键字enum,可以创建一个新“类型”并指定它可以具有的值(实际上,enum常量是int类型的,因此在使用int类型 的任何地方都可以使用它)。枚举类型的目的是提高程序的可读性。
enum spectrum{red, orange, yellow, green, blue, violent}; enum spectrum color;
虽然枚举常量都是int类型的,但枚举常量较宽松地限定为任一种整数类型,只要改整数类型能保存这些枚举常量。
问:blue和red到底是什么?
答:从技术上讲,它们是int类型的常量。
red是一个代表整数0的命名常量,同样,其他标识符也是代表1到5的命名常量。在使用整数常量的任何地方都能使用枚举常量。
默认时,枚举列表中的常量被指定为整数值0、1、2等等。也可以选择常量具有的整数值,只须在声明中包含期望的值。如果只对一个常量赋值,而没有对后面的常量赋值,那么这些后面的常量会被赋予后续的值。
因为枚举类型是一个整数类型,所以enum变量能像整数变量那样被用在表达式中。
共享的名字空间
C使用术语名字空间(namespace)来表示识别一个名字的程序部分。作用域是这个概念的一部分;名字相同但具有不同作用域的两个变量不会冲突;而名字相同并在相同作用域中的两个变量就会冲突。名字、空间是分类别的。在一个特定作用域内的结构标记、联合标记以及枚举标记都共享同一个名字空间,并且这个名字空间与普通变量使用的名字空间是不同的,这意味着,可以在同一个作用域内对一个变量和一个标记使用同一个名字,而不会产错误;但是不能在同一作用域内使用名字相同的两个标记或名字相同的两个变量。例如:
struct rect{double x; double y}; int rect; // 在C中不会引起冲突
缺点:用两种不同的方式使用同一标识符会造成混乱;而且,C++不允许在同一个作用域内对一个变量和一个标记使用同一个名字,因为它把标记和变量名放在同一个名字空间中。
typedef简介
typedef工具是一种高级数据特性,它使您能够为某一类型创建自己的名字。在这个方面,它和#define相似,但是它们具有3个不同之处:
- 与#define不同,typedef给出的符号名称仅限于对类型,而不是对值
- typedef的解释由编译器,而不是预处理器
- 虽然它的范围有限,但在其受限范围内,typedef比#define更灵活
假设要对1字节的数值使用术语BYTE,您只须像定义一个char变量那样定义BYTE,然后在这个定义前面加上关键字typedef,如:
typedef unsigned char BYTE;
该定义的作用域取决于typedef语句所在的位置,如果定义是在一个函数内部,它的作用域就是局部的,限定在那个函数里。如果定义是在函数外部,它将具有全局作用域。
通常,这些定义使用大写字母,以提醒用户这个类型名称实际上是一个符号缩写,不过,您也可以使用小写字母。
typedef有助于增加可移植性。例如:
sizeof运算符返回类型size_t类型,表示函数time()的返回值类型time_t类型。C标准规定sizeof和time()应返回整数类型,但它留给具体的实现来决定到底是哪种整数类型。
time_t time(time_t *);
使用#define可以实现typedef的一部分功能。如下:
#define BYTE unsigned char
但也有#define实现不了的功能。如下:
typedef char * STRING;
如果没有关键字typedef,该例将STRING识别为一个char指针。有了这个关键字,是STRING成为char指针的标识符。
但是假设这样做:
#define STRING char *
那么:
STRING name, sign;
将会被翻译为下面的形式:
char * name, sign; // 只有name是一个指针
也可以对结构使用typedef:
typedef struct complex { float real; float imag; } COMPLEX;
这样就可以用类型COMPLEX代替struct complex来表示复数。使用typedef来命名一个结构类型时,可以省去结构的标记:
tyoedef struct {double x; double y;} rect;
可以像下面这样使用typedef定义的类型名:
rect r1 = {3.0, 6.0}; rect r2; r2 = r1;
↓相当于
struct {double x; double y;} r1 = {3.0, 6.0}; struct {double x; double y;} r2; r2 = r1;
使用typedef的原因:
- 为经常出现的类型创建一个方便的、可识别的名称
- 经常被用于复杂的类型