B.4.3 派生数据类型
派生数据类型是根据一个或多个基本数据类型构建的数据类型。派生数据类型包括数组、结构、联合和指针(它包含对象)。还可将返回特定类型值的函数或方法看作派生的数据类型。除了函数和方法以外,其他所有派生数据类型都在下面的各段中进行了总结。在“函数”和“类”两节中会分别涉及到作为派生数据类型的函数和方法。
1.数组
(1)一维数组
可以定义数组使其包含任何一种数据类型或者任何一种派生数据类型。包含函数的数组是不允许的(尽管允许包含函数指针的数组)。
数组声明的基本形式如下:
type name[n]={initExpression, initExpression,..};
数组名中的表达式n确定元素个数,通过提供一列指定的初始值可以省略表达式n。在这种情况下,数组的大小是由列出的初始值个数决定的,在使用指定的初始方法时,是基于被引用元素的最大索引来决定。
如果定义的是全局数组,那么初始值必须是常量表达式。当初始值的个数少于数组中元素的个数时,可将初始值列在初始化列表中,但如果情况相反,则不能采用这种方法。如果指定了较少的值,就会只初始化这么多数组元素—其余的元素将设置为0。
数组初始化的特殊情况出现在字符数组中,即字符数组可以使用常量字符串来初始化。例如
char today[]=“Monday”;
将today声明为一个字符数组。这个数组分别用字符‘M’、‘O’、‘n’、‘d’、‘a’、‘y’和‘\0’来初始化。
如果你显式地指定字符数组的维数,并且没有为结尾的空字符保留空间,编译器就不会在数组末尾放置空字符:
char today[6]=“Monday”;
这条语句将today声明为一个包含6个字符的数组,并将其元素分别设为字符‘M’、‘o’、\&‘n’、‘d’、‘a’和‘y’。
通过将数组成员括在一对方括号中,可以用任何顺序初始化特定的数组元素。例如
int x=1233;
int a[]={[9]=x+1,[2]=3,[1]=2,[0]=1};
定义了一个包含10个元素的数组a(基于数组中的最大索引),并将最后一个元素初始化为值x+1(1234),前3个元素分别初始化为1、2和3。
(2)长度可变数组
在函数、方法或程序块内,可以使用包含变量的表达式来确定数组的维数。这种情况下,数组的大小将在运行时计算。例如,函数
int makeVals(int n)
{
int valArray[n];
……
}
定义了一个名为valArray的自动数组,它包含n个元素,而n将在运行时计算,并且可以在函数调用中改变。长度可变的数组不能被初始化。
(3)多维数组
声明多维数组的一般格式如下:
type name[d1][d2]……[dn]=initializationList;
数组名定义为包含特定类型的d1 x d2 x……x dn个元素。例如
int three_d[5][2][20];
定义了一个3维数组three_d,该数组包含200个整数。
通过将每一维的指定下标括在该维的方括号中,可以引用多维数组中的特定元素。例如,语句
three_d[4][0][15]=100;
将100存储到数组three_d的指定元素。
多维数组初始化的方式可以和一维数组相同。可以使用嵌套的花括号来控制数组中元素的赋值。
下面将matrix声明为一个包含4行3列的二维数组:
int matrix[4][3]=
{{1,2,3},
{4,5,6},
{7,8,9}};
该声明将matrix的第1行元素分别设为1、2和3;第2行元素分别设为4、5和6;第3行分别设为7、8和9。第4行元素设为0,因为没有为那一行指定值。声明
int matrix[4][3]=
{1,2,3,4,5,6,7,8,9};
将matrix初始化为相同的值,因为多维数组的元素是按维的顺序来初始化的—即,从最左边的维到最右边的维。
声明
int matrix[4][3]=
{{1},
{4},
{7}};
将第1行的第1个元素设为1,第2行的第1个元素设为4,第3行的第1个元素设为7。其余所有的元素按默认值设为0。
最后,声明
int matrix[4][3]={[0][0]=1,[1][1]=5,[2][2]=9};
将指出的数组元素初始化为特定的值。
2.结构
一般格式:
struct name
{
memberDeclaration
memberDeclaration
……
}variableList;
结构name被定义为包含由memberDeclaration指定的成员。每一个这样的声明都包括位于一个或多个成员名列表之后的类型说明。
定义结构的同时可以声明变量,通过在结束分号之前列出它们或者使用以下格式随后声明它们即可:
struct name variableList;
如果在定义结构时省略name,就不能使用这种格式。在这种情况下,该结构类型的所有变量必须使用定义来声明。
声明结构变量的格式与声明数组变量的格式类似。结构的成员可以通过将初始值列表括在一对花括号内的方式来声明。如果定义的是全局结构,那么列表中的值必须是常量表达式。
声明
struct point
{
float x;
float y;
}start={100.0,200.0};
定义了一个名为point的结构和一个名为start的struct point变量,该变量具有指定的初始值。可以使用表示法
.member=value
在初始化列表中以任何顺序为初始化指定特定成员,如
struct point end={.y=500,.x=200};
中一样。
声明
struct entry
{
char*word;
char*def;
}dictionary[1000]={
{“a”,“first letter of the alphabet”},
{“aardvark”,“a burrowing African mammal”},
{“aback”,“to startle”}
};
声明了包含1000个entry结构的dictionary,且它的前3个元素被初始化为特定的字符串指针。使用指定的初始化方法,还可将上述定义写成以下形式:
struct entry
{
char*word;
char*def;
}dictionary[1000]={
[0].word=“a”,[0].def=“first letter of the alphabet”,
[1].word=“aardvark”,[1].def=“a burrowing African mammal”,
[2].word=“aback”,[2].def=“to startle”
};
或等价地声明为:
struct entry
{
char*word;
char*def;
}dictionary[1000]={
{{.word=“a”,.def=“first letter of the alphabet”},
{.word=“aardvark”,.def=“a burrowing African mammal”},
{.word=“aback”,.def=“to startle”}
};
可将自动结构变量初始化为相同类型的另一个结构,如下:
struct date tomorrow=today;
这条语句声明了date结构变量tomorrow,并将(以前声明的)date结构变量today的内容指派给该变量。
具有格式
type fieldName:n
的memberDeclaration在结构内定义了n位宽的field,其中n是一个整数。在某些计算机上,字段是可以从左到右组装的,而在其他计算机上则是从左到右组装的。如果省略fieldName,位的特定成员将会保留下来,但不能引用它。如果省略fieldName并且n是0,那么随后的字段将会对与下一个存储unit的边界对齐,而unit是实现定义的。字段的类型可以是int、signed int或unsigned int。不管字段被看作带符号或无符号,它都是实现定义的。地址运算符(&)不能应用于字段,并且不能定义字段的数组。
3.联合
一般格式:
union name
{
memberDeclaration
memberDeclaration
……
}variableList;
这定义了一个名为name的联合,它的成员通过每一个memberDeclaration来指定。联合的每一个成员共享重叠的存储空间,编译器确保了保留足够大的空间以包含联合中的最大成员。
变量可以在定义联合时声明,或者随后使用以下表示法来声明
union name variableList;
来声明,前提是在定义联合时提供了联合命名。
确保从联合内检索的值与最后一个存储在联合内的值一致,这个工作是由程序员负责完成的。联合的第一个成员可以通过将其初始值(在全局联合变量的情况中,该初始值必须是常量表达式)放在一对花圆括号内来初始化:
union shared
{
long long int l;
long int w[2];
}swap={0xffffffff};
用指定成员名的方式取而代之,可以初始化不同的变量,如同在
union shared swap2={.w[0]=0x0,.w[1]=0xffffffff};
中一样。
这条语句声明了联合变量swap并将它的第l个成员设为十六进制值ffffffff。
自动联合变量还可以初始化为相同类型的联合,如
union shared swap2=swap;
中一样。
4.指针
声明指针变量的基本格式如下:
type*name;
标识符name声明为“指向type的指针”类型,该类型可以是基本数据类型也可以是派生数据类型。例如
int*ip;
将ip声明为指向int的指针,而声明
struct entry*ep;
将ep声明为一个指向entry结构的指针。如果将Fraction定义为一个类,则声明
Fraction*myFract;
将myFract声明为Fraction类型的对象,或者更明确地讲,当创建指派给该变量的对象实例之后,myFract将用于保存指向对象数据结构的指针。
指向数组中元素的指针声明为指向数组中包含的元素类型。例如,上述ip的声明也可以用来声明指向整型数组的指针。
同样允许更高级的指针声明格式。例如,声明
char*tp[100];
将tp声明为一个包含100个字符指针的数组,而声明
struct entry(*fnPtr)(int);
将fnPtr声明为一个指向函数的指针,该函数返回一个entry结构并接受单个int参数。
通过将指针与一个值为0的常量表达式进行比较,可以测试它是否为空。实现可以选择使用非0值来内部表示一个空指针。然而,这样内部表示的空指针和值为0的常量之间的比较必须是相等的。
指针转换成整数和整数转换成指针所采用的方式依赖于计算机,容纳指针所需的整数大小也是一样。
类型“指向void的指针”是通用指针类型。这门语言保证任何类型的指针都能被赋值给一个void指针,并且可在不更改其值的情况下转换为原来类型。
类型id是通用对象指针。任何类的任何对象都可以指派为一个id变量,反之亦然。
与这两种特殊情况不同,不允许指定不同的指针类型,如果尝试这样做,编译器通常会产生一条警告消息。