数组
数组时一种类似于标准库类型 vector 的数据结构,但是再性能和灵活的权衡上又与 vector 有所不同。与 vector 相似的地方是,数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问。与 vector 不同的地方是,数组的大小确定后不能变,不能随意向数组中添加元素。因为数组的大小固定,因此对某些特殊的应用来说程序运行时的性能较好,但是相应的也损失了一些灵活性。
定义和初始化内置数组
1 unsigned cnt = 42;//不是常量表达式 2 3 constexpr unsigned sz = 42;//常量表达式 4 5 int a[10];//含有10整数的数组 6 7 int *b[sz];//含有42个整型指针的数组 8 9 string c[cnt];//错误,cnt不是常量表达式 10 11 string d[get_size()];//当constexpr时正确,否则错误
默认情况下,数组的元素被默认初始化。定义在函数体外时默认值为0,在函数体内时默认值为随机值(未定义的值)。
定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。另外和 vector 一样,数组的元素应该为对象,因此不存在引用的数组。
显示初始化元素:
1 const unsigned sz = 3; 2 3 int a[sz] = {0, 1, 2};//含有3个元素的数组,元素值分别为0, 1, 2 4 5 int b[] = {0, 1, 2};//含有3个元素的数组,元素值分别为0, 1, 2 6 7 int c[5] = {0, 1, 2};//等价于 c[5] = {0, 1, 2, 0, 0} 8 9 string d[3] = {"hello", "world"};//等价于d[3] = {"hello", "world", ""} 10 11 int e[2] = {0, 1, 2};//错误,初始值过多
可以发现,当初始值的数目多于数组大小时,会编译错误,当初始值的数目小于数组大小时,初始值会赋给靠前的元素,剩下的元素被初始化为默认值(一般为0)。
字符数组:
字符数组有一种额外的初始化形式,可以用字符串字面值对此类数组初始化。当使用这种方式时,一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其他字符一样拷贝到字符数组中去。
1 char a[] = {'c', '+', '+'};//列表初始化,没有空字符 2 3 char b[] = {'c', '+', '+', '\0'};//列表初始化,含有显示的空字符 4 5 char c[] = {"c++"};//用字符串字面值拷贝初始化,会自动添加表示字符串结束的空字符 6 7 char d[3] = {"c++"};//错误,没有空间存储空字符
不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值,这一点也是区别于 vector 的
1 int a[] = {1, 2, 3}; 2 3 int b[] = a;//错误,不能用一个数组初始化另一个数组 4 5 b = a;//错误,不能把一个数组直接赋值给另一个数组
和 vector 一样,数组能存放大多数类型的对象。如:可以定义一个存放指针的数组。又因为数组本身就是对象,所以允许定义数组的指针及数组的引用。
1 int x[10]; 2 3 int *a[10];//a是含有10个整型指针的数组 4 5 int &b[10] = /*?*/;//错误,不存在引用的数组(引用不是对象) 6 7 int (*c)[10] = &x;//c指向一个含有10个整型数的数组 8 9 int (&d)[10] = x;//d绑定一个含有10个整型的数组 10 11 int *(&e)[10] = a;//e是数组的引用,该数组含有10个指针
理解复杂的声明语句可以由变量名开始由内往外,从右往左阅读声明语句
数组和指针:
在c++语言中,指针和数组联系非常紧密。使用数组的时候编译器一般会将数组转化为指针。
通常情况下,使用取地址符来获取指向某个对象的指针,取地址符可用于任何对象,对数组使用下标运算符得到该数组指定位置的元素。因此像其他对象一样,对数组的元素使用取地址符能得到该元素的指针:
1 string nums[] = {"one", "two", "three"}; 2 3 string *p = &nums[0];//p指向nums数组的第一个元素 4 5 string *q = nums;//等价于q = nums[0];
在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。
1 int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 2 3 auto b(a);//b是一个整型指针,指向a的第一个元素 4 5 b = 2014;//错误,不能给指针赋一个int类型的值
尽管 a 是由 10 个整型数构成的数组,但当使用 a 作为初始值时,编译器实际执行的初始化过程类似于
auto b(&a[0]);//显然 b 的类型是 int*
另外还需要注意的是,当使用 decltype 关键字时上述转换不会发生,decltype(a) 返回的类型是由 10 个整型数组成的数组:
1 int cnt = 1024, *p = &cnt; 2 3 int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 4 5 decltype(a) b = {1, 2, 3, 4, 5, 6};//b是一个包含 10 个整型数的数组 6 7 cout << b[0] << endl;//输出 b 数组的第一个元素1 8 9 b = p;//错误,不能用整型指针给数组赋值
指针也是迭代器
1 int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; 2 3 int *s = a, *e = &a[10];//s指向a的第一个元素,e为a的尾后指针 4 5 for(int *indx = a; indx != e; indx++){//用指针遍历数组 6 7 cout << (*indx) << endl; 8 9 } 10 11 int *x = begin(a), *y = end(a);//x指向a的第一个元素,y为a的尾后指针 12 13 //begin返回a的首元素指针,end返回a的尾后指针,这两个元素定义在iterator头文件中 14 15 while(x != y){ 16 17 cout << (*x) << endl; 18 19 x++; 20 21 }
下标和指针
1 int a = [1, 2, 3, 4, 5]; 2 3 int *p(a);//此时a和p具有相同的地址,所以 a[1, 2, 3, 4] 和 p[1, 2, 3, 4] 地址也相同 4 5 cout << p[2] << endl;//p[2]等价于a[2],输出3 6 7 p[2] += 1;//给&p[2]位置的对象加一,所以此时 a[2] 的值为 4 8 9 int cnt = *(p + 2);//等价于cnt = a[2] 10 11 p += 2; 12 13 int k = p[-2];//等价于k = a[0]
使用数组初始化 vector 对象
1 int a[] = {1, 2, 3, 4, 5}; 2 3 vector<int> v(begin(a), end(a));//整个数组 a 的元素作为 v 的初始值 4 5 vector<int> vt(a + 1, a + 3);//将数组元素 a[1],a[2] 作为 vt 的初始值
多维数组:
在 c++ 中多维数组就是数组的数组。
多维数组的初始化:
1 int a[3][2] = { 2 3 {1, 2},//内层在嵌套花括号对应a的三个数组元素 4 5 {3, 4}, 6 7 {5, 6} 8 9 }; 10 11 int a[3][2] = { 12 13 {1, 2},//也可以给部分元素嵌花括号 14 15 3, 4, 16 17 5, 6 18 19 }; 20 21 int a[3][2] = { 22 23 1, 2,//也可以不嵌花括号 24 25 3, 4, 26 27 5, 6 28 29 }; 30 31 int a[3][2] = { 32 33 1, 2, 3//没加花括号时初始化是从前往后依次进行的,不足的自动初始化为0 34 35 }; 36 37 int a[3][2] = { 38 39 {1},//也可以只初始化某些行的部分元素,剩下的元素自动初始化0 40 41 {3}, 42 43 {5, 6} 44 45 }; 46 47 int a[3][2] = { 48 49 1, 2//值初始化了第一行,其余的自动初始化为0 50 51 };
多维数组的下标引用:
如果表达式含有的下标运算符数量和数组的维度一样多,该表达式的结果将是给定类型的元素;反之,如果表达式含有的下标运算符数量比数组的维度少,则表达式的结果将是给定索引处的一个内层数组:
1 int a[3][2] = { 2 3 {1, 2},//内层在嵌套花括号对应a的三个数组元素 4 5 {3, 4}, 6 7 {5, 6} 8 }; 9 10 int (&row)[2] = a[2];//将row绑定到 a 的第三个 2 元素数组上 11 12 cout << row[0] << " " << row[1] << endl;//输出5 6
使用范围 for 循环处理多维数组
1 int a[3][2] = {0, 1, 2, 3, 4, 5}; 2 3 for(auto &row : a){//如果row不加引用声明,数组会自动转化为指针,即row会是 int* 类型,显然是错误的 4 5 for(auto &col : row){//如果col不加引用声明,col会是 int 类型,原数组的元素不会被修改 6 7 col += 1; 8 9 } 10 11 }
还有需要注意的是:
对于多维数组,如果解引用次数少于数组维数的话得到的是指针,而且将数组映射到空间的话,同一位置对应的内存地址是相同的,与解引用次数无关。如上图所示:a[3][3][3],可以当作一个 3*3*3 的正方体,a 即(0,0,0)位置,在得到其中元素对象之前,解引用0次,1次,2次得到的地址是相同的。对于更高维的数组也同样如此。不过这些地址本身值是一样的,但在其他方面还是有所不同的:
解引用次数不同的指针的移动方向是不同的,如上图中的二维数组所示,没有解引用得到的指针 q 执行自增运算后是向下移动的,而解引用一次得到的指针 q_ 执行自增运算后是向又移动的。对于高维情况也是如此。