数组

数组时一种类似于标准库类型 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_ 执行自增运算后是向又移动的。对于高维情况也是如此。

posted @ 2017-11-27 19:46  geloutingyu  阅读(229)  评论(0编辑  收藏  举报