数组
一、定义和初始化内置数组
数组是一种复合类型。数组的声明形如a[d],其中a是数组名,d是数组的难度。难度说明了数组中元素的个数,因此必须大于0。数组中元素的个数也属于数组类型的一部分,编译的时候维度应该已知的。也就是说,维度必须是一个常量表达式:
unsigned cnt = 42; //不是常量表达式
constexpr unsigned sz =42; //常量表达式
int arr[10]; //含有10个整数的数组
int *parr[sz]; //含有42个整数的数组
string bar[cnt]; //错误:cnt不是常量表达式
string strs[get_size()]; //当get_size是constexpr时正确,否则错误
定义数组的时候必指定数组的类型,不允许用auto关键字由初始值的列表推断类型。
二、显示初始化数组元素
1.对数组的元素进行列表初始化,允许忽略数组的维度。
2.在声明数组时没有指明维度,编译器会根据初始值的数量计算并推测出来。
3.指明了维度,那么初始值的总量不应该超过指定的大小。
4.如果维度比提供的初始值数量大,则用提供的初始值初始化靠前的元素,剩下的元素被初始化成默认值:
const unsigned sz =3;
int ial[sz] = {0,1,2}; //含有3个元素的数组,元素值分别是0,1,2
int a2[] = {0,1,2}; //维度是3的数组
int a3[5] = {0,1,2}; //等价于a3[] ={0,1,2,0,0}
string a4[3] = {"hi","bye"}; //等价于a4[] ={"hi","bye",""}
int a5[2] = {0,1,2}; //错误:越界初始值过多
三、字符数组的特殊性
字符数组有一种额外的初始化形式,我们可以用字符串字面值对此类数组初始化。当使用这种方法时,一定要注意字符串字面值的结尾还有一个空字符,这个空字符也会拷贝到字符数组中去:
char a1[] ={'c','+','+'}; //列表初始化,没有空字符
char a2[] ={'c','+','+','\0'}; //列表初始化,含有显示的空字符
char a3[] ="C++"; //自动添加表示字符串结束的空字符
const char a4[6] ="abcefg"; //错误:没有多余的空间存放空字符,至少7个
四、不允许拷贝和赋值
不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值:
int a[] = {0,1,2}; //含有3个整数的数组
int a2[] = a; //错误:不允许使用一个数组初始化另一个数组
a2 = a; //错误:不能把一个数组直接赋值给另一个数组
五、复杂的数组声明
数组也能存放大多数类型的对象,例如,可以定义一个存放指针的数组。又因为数组本身就是对象,所以允许定义数组的指针及数组的引用。
int *ptrs[10]; //ptrs是含有10个整形指针的数组
int &refs[10] = /*?*/; //错误:不存在引用的数组
int (*Parray)[10] = &arr; //Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; //arrRef引用一个含有10个整数的数组
六、指针和数组
使用取地址符(&)来获取指向某个对象的指针,取地址符可以用于任何对象。数组的元素的也是对象,对数组使用下标运算符得到该数组指定位置的元素。因此像其他对象一样,对数组的元素使用取地址符就能得到指向该元素的指针:
string nums[] = {"one","two","three"}; //数组的元素是string对象
string *p = &nums[0]; //p指向nums第一个元素
在很多用到数组名字的地方,编译器会自动地将其替换为一个指向数组首元素的指针:
string *p2 = nums; //等价于p2 = &nums[0]
当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组:
int ia[] = {0,1,2,3,4,5,6}; //ai是一个含有7个整数的数组
auto ia2(ia); //ia2是一个整型指针,指向ia的第一个元素(0)
ia2 = ia ; //错误:ia2是一个指针,不能用int值给指针赋值
尽管ia是由10个整数构成的数组,但当使用ia作为初始值时,编译器实际执行但初始化过程类似于下面的形式:
auto ia2(&ia[0]);
当使用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的一个元素
七、指针也是迭代器
vector和string的迭代器支持的运算,数组的指针也全部支持:
//使用递增运算符将指向数组元素的指针向前移动到下一个位置上
int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *p = arr; //p指向arr的第一个元素
++p; //p指向arr[1]
八、标准库函数begin和end
数组也可以使用begin和end函数,不过数组毕竟不是类类型,因此这两个函数不是成员函数。正确的使用形式是将数组作为它们的参:
//使用递增运算符将指向数组元素的指针向前移动到下一个位置上
#include<iterator> //begin和end函数定义在iterator头文件中
int ia[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(ia); //指向ia首元素的指针
int *last = end(ia); //指向ia尾元素的下一位置的指针
使用bengin和end可以很容易写出一个循环并处理数组中的元素:
//找到arr数组中的一个负数
int arr[] = {0,1,2,3,4,5,6,7,8,9};
int *pbeg = begin(arr); //指向ia首元素的指针
int *pend = end(arr); //指向arr尾元素的下一位置的指针
//寻找第一个负值元素,如果已经检查完全部元素则结束循环
while(pbed!= pend && *pbeg >= 0)
++pbeg;
九、指针运算
指向数组元素的指针可以执行迭代器运算:
1.给一个指针加上(减去)某整数值,结果仍是指针。新指针指向的元素与原来的指针相比前进了(后退了)该整数值个位置:
constexpr size_t sz = 5;
int arr[sz] = {1,2,3,4,5};
int *ip = arr; //等价于int *ip = &arr[0]
int *ip2 = ip + 4; //ip2指向arr的尾元素arr[4]
ip加上4所得的结果仍是一个指针,该指针所指的元素与ip原来所指的元素相比前进了4个位置。
给指针加上一个整数,得到的新指针仍需指向同一个数组的其他元素,或者指向同一个数组的尾元素的下一个位置:
//正确:arr转换成指向它首元素的指针;p指向arr尾元素的下一位置
int arr[sz] = {1,2,3,4,5};
int *p = arr +sz; //不要用解引用
int *p2 = arr + 10; //错误:arr只有5个元素,p2的值未定义
和迭代器一样,两个指针相减的结果是它们之间的距离。参与运算的两个指针必须指向同一个数组当中的元素:
auto n = end(arr) - begin(arr);
只要两个指针指向同一个数组的元素,或者指向该数组的尾元素的下一位置,就能利用关系运算符对其进行比较:
//可以按照下面的方法遍历数组中的元素
int arr[sz] = {1,2,3,4,5};
int *b = arr ,*e = arr+sz;
while(b<e){
++b
}
如果两个指针分别指向不相关的对象,则不能比较它们
int i = 0,sz = 42;
int *p = &i, *e = &sz;
//未定义的:p和e无关,因此比较毫无意义。
while(p<e)
10、解引用和指针运算交互
指针加上一个整数所得的结果还是一个指针。假设结果指针指向了一个元素,则允许解引用该结果指针:
int ia[] = {0,2,4,6,8}; //含有5个整数的数组
int last = *(ia+4); //正确:把last初始化成8,也就是ia[4]的值
11、下标和指针
使用数组的名字其实用的是一个指向数组首元素的指针。一个典型的例子是当数组使用下标运算符时,编译器会自动执行上述转换操作:
int ia[] = {0,2,4,6,8}; //含有5个整数的数组
ia[0]是一个使用了数组名字的表达式,对数组执行下标运算其实是对指向数组元素的指针指向下标运算:
int ia[] = {0,2,4,6,8}; //含有5个整数的数组
int i = ia[2]; //ia转换成指向数组首元素的指针
//ia[2]得到(ia+2)所指的元素
int *p = ia; //p指向ia的首元素
i = *(p+2); //等价于i=ia[2]
只要指针指向的数组中的元素(或者数组中尾元素的下一个位置),都可以执行下标运算:
int *p =&ia[2]; //p指向索引为2的元素
int j = p[1]; //p[1]等价于*(p+1),就是ia[3]表示的那个元素
int k =p[-2]; //p[-2]是ia[0]表示的那个元素
总结
数组指针->数组名就是指向数组首元素的指针。