数组
数组是一种类似于标准库类型vector的数据结构,但是在性能和灵活性上又与vector有所不同。与vector
相似的地方是,数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问。
与vector不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。因为数组的大小固定,因此
对某些特殊的应用来说程序的运行时性能较好,但是相应地损失了一些灵活性。
Tip:如果不清楚元素的确切个数,请使用vector
定义和初始化内置数组
数组是一种复合类型。数组的声明形如a[d],其中a是数组的名字,d是数组的维度。维度说明了数组中
元素的个数,因此必须大于0.数组中元素的个数也属于数组类型的一部分,编译的时候维度应该是已知的。
也就是说,维度必须是一个常量表达式:
unsigned cnt = 42; //不是常量表达式
constexpr unsigned sz = 42; //常量表达式
int arr[10]; //含有10个整数的数组
int *parr[sz]; //含有42个整型指针的数组
string bad[cnt]; //错误:cnt不是常量表达式
string strs[get_size()]; //当get_size是constexpr时正确;否则错误
默认情况下。数组的元素被默认初始化
WARNNING: 和内置类型的变量一样,如果在函数内部定义了某种内置类型的数组,那么默认
初始化会令数组含有未定义的值
定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值列表推断类型。另外
和vector一样,数组的元素应为对象,因此不存在引用的数组。
不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值:
int a[] = {0,1,2}; //含有3个整数的数组
int a2[] = a; //错误:不允许使用一个数组初始化另一个数组
a2 = a; //错误:不能把一个数组直接赋值给另外一个数组
WARNNING:一些编译器支持数组的赋值,这就是所谓的编译器扩展(compiler extension).但一般
来说,最好避免使用非标准特性,因为含有非标准特性的程序很可能在其他编译器上无法正常工作。
理解复杂的数组声明
和vector一样,数组能存放大多数类型的对象。例如,可以定义一个存放指针的数组。又因为
数组本身就是对象,所以允许定义数组的指针及数组的引用。在这几种情况中,定义存放指针的数组
比较简单和直接,但是定义数组的指针或数组的引用就稍微复杂一点了:
int *ptrs[10]; //ptrs是含有10个整型指针的数组
int &refs[10] = /* ? */; //错误:不存在引用的数组
int (*Parray)[10] = &arr; //Parray指向一个含有10个整数的数组
int (&array)[10] = arr; //arrRef引用一个含有10个整数的数组
int *(&arr)[10] = ptrs; //array是数组的引用,该数组含有10个指针
在一些情况下数组的操作实际上是指针的操作,这一结论有很多隐含的意思,其中一层意思是当使用
数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组:
int ia[] = {0,1,2,3,4,5,6,7,8,9}; //ia是一个含有10个整数的数组
auto ia2(ia); //ia2是一个整型指针,指向ia的第一个元素
ia2 = 42; //错误:ia2是一个指针,不能使用int值给指针赋值
尽管ia是由10个整数构成的数组,但当使用ia作为初始值时,编译器实际运行的初始化过程类似于下面
的形式:
auto ia2(&ia[0]); //显然ia2的类型是int*
必须指出的是,当使用decltype关键字时上述转换不会发生,decltype(ia)返回的类型是由10个整数构成
的数组:
//ia3是一个含有10个整数的数组
decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 = p; //错误:不能使用整型指针给数组赋值
ia3[4] = i; //正确:把i的值赋给ia3的一个元素。