c++教程(十一:Arrays)

————————————————————————

该系列教程为翻译c++官方教程,点击参考英文原版,水平有限,翻译不通之处敬请谅解!

————————————————————————

数组

数组就是一系列相同类型元素的集合,并且放置在连续的内存空间,它们可以通过添加索引标识符来单独引用。
例如,有五个int类型的值可以声明为一个数组而无需声明5个不同的变量来存储(数组里面每个都有其自己的标识符)。相反,使用一个数组,五个int值存储在连续的内存位置,所有的五个值都可以使用类似适当的的标识符来访问。
例如包含5个int类型的书存在foo这个数组里面表示如下:

每个块代表数组中的一个元素,这样的话它们都是int类型的。元素的索引位置为0-4,0是第一个元素,4是最后一个元素。C++中访问第一个元素都是从索引0开始的,不管数组长度有多大。
和常规的元素不一样的是,数组元素在使用的时候必须事先声明:

1type name [elements]; 2

这里,type就是规定数组的类型(比如int型,float等等,)固定的数组名字name以及数组的元素,包含在[]内。元素的长度就是数组的长度。
因此,上述foo数组就是拥有5个元素,也就是长度为5的数组,那么可以定义为:

1int foo [5]; 2

注意:元素的大小范围要使用中括号[],代表元素的个数,同时里面的数一定是一个常量表达式,因为数组在编译的时候,默认为是大小一定的静态存储区域。

初始化数组

默认情况下,规则的局部范围数组(例如,那些在一个函数声明)可以不初始化。这意味着它的元素没有被设置为任何特定的值,在该数组被声明的位置,它的内容是不确定的。
但是一个数组在声明的时候可以被初始化为有具体的元素,初始化的遏制用大括号{}括起来,比如:

1int foo [5] = { 16, 2, 77, 40, 12071 }; 2

那么这个数组实际表示就如下:
大括号{}里面的元素的个数不应该比声明的数组的个数大,例如,如果声明了一个大小为5的数组(也就是中括号里面的数值),大括号里面元素就是对应这5个数值的,每个元素一个值。如果{}里面声明的元素不够,那么剩下的元素就是默认值(对于一般基本类型的值,它们都会默认为0),例如:

1int bar [5] = { 10, 20, 30 }; 2

那么这个数组就会想下面这个样子:
如果我们什么都不初始化,像下面这样:

1int baz [5] = { }; 2

这就创建了一个大小为5的数组,它们中的元素都为0:
这里写图片描述
当初始化一个数组的元素值时,C++允许方括号[ ]中的值为空。在这种情况下,编译器将自动辨别包含在大括号{ }里的元素,从而来确定数组的大小:

1int foo [] = { 16, 2, 77, 40, 12071 }; 2

在这里例子里面声明了5个int型的元素,那么数组foo的大小为5。
最后,随着C++的演化,普遍采用了对数组的初始化。从而声明和初始化之间的等号不再需要。下面这两个语句都是等价的:

1int foo[] = { 10, 20, 30 }; 2int foo[] { 10, 20, 30 }; 3

静态数组以及那些直接在命名空间(外部任何函数)中声明的,总是被初始化。如果没有显式指定初始化,所有元素都是默认初始化(零,基本类型)。

访问数组的值

数组里面元素的访问就像访问同一种类型的一般变量一样,形式如下:

1name[index] 2

像上面的例子中foo数组里面有5个元素,并且都是int型的,那么访问其中的每一个元素就可以按照下面的索引访问:
例如我想访问储存在foo数组里面元素为75的这个数可以表示如下:
foo [2] = 75;
下面这样子就是将这个数组的第三个元素赋值给变量x
X = foo[2];
因此,表达式foo[2]本身也是一个int类型的数。
注意的是,foo数组里面第三个元素是foo[2],因为数组的下标访问是从foo[0]开始的,第二个数是foo[1],以此类推。同样最后一个元素是foo[4],如果写成了foo[5],那么将访问第六个元素,这实际上已经超出了foo得大小范围了。

在C++中,在语法上如果超过索引的有效范围的数组也是正确的。这是创建的问题,访问了范围之外的元素不会导致编译的错误,但会导致运行时错误。在后面的章节中,当指针引入时就知道这个原因了。

在这一点上,重要的是要能够清楚地区分与[]有关的数组的用法。他们执行两个不同的任务:一个是在定义时指定数组的大小;第二个是指定索引访问的具体数组元素。不要将这两种可能的括号与数组混淆起来。

1int foo[5]; // declaration of a new array 2foo[2] = 75; // access to an element of the array. 3

一个重要的不同是声明需要声明数组的类型,而访问不需要。
一些数组的操作:

1foo[0] = a; 2foo[a] = 75; 3b = foo [a+2]; 4foo[foo[a]] = foo[2] + 5; 5

举个例子:

1// arrays example 2#include <iostream> 3using namespace std; 4 5int foo [] = {16, 2, 77, 40, 12071}; 6int n, result=0; 7 8int main () 9{ 10 for ( n=0 ; n<5 ; ++n ) 11 { 12 result += foo[n]; 13 } 14 cout << result; 15 return 0; 16} 17

多维数组

多维数组可以理解为是数组的数组,例如一个二维数组可以理解为两个维度的表格元素一样,他们的类型都相同。

Jimmy代表的是一个int类型的3行5列的二维数组,在c++中的表示方法为:

1Int jimmy [3][5]; 2

例如想访问第二行第四列的元素,可以写成:

1Jimmy[1][3] 2

(需要记住的是数组的访问索引都是从0开始)
多维数组并不只是限于二维,可以是任意需要的维度。需要注意的是内存的空间是否足够。比如:

1Char century [100][365][24][60][60]; 2

声明一个多维char型数组,这个数组总的大小超过3百万个char型数,所以声明这个数组需要3GB大小的内存。
最后,多维数组只是一个抽象的编程表示方式,因为它可以用一个简单的一维数组来表示,只是通过乘以它的维度大小:

1int jimmy [3][5]; // is equivalent to 2int jimmy [15]; // (3 * 5 = 15) 3

唯一的不同就是,对于多维数组编译器会自动记住每一个虚维度的深度。下面的两个代码产生完全相同的结果,但是使用一个二维数组,另一个使用简单的数组:
上面两个代码都不会显示内容到屏幕上,只是将数组存在被称为jimmy的数组中:
请注意,代码使用定义的宽度和高度,而不是直接使用它们的数值。这给了代码一个更好的可读性,并允许在一个地方很容易地修改代码。

作为参数的数组

这里我们需要讲数组作为函数的参数。在c++中,要将整个内存块当做参数传递给函数是不可能的,那么在传递的时候是传递的地址。实际上就可以实现相同的功能,并且在操作上速度更快也更方便。

为了接受一个数组作为函数的参数,那么声明的时候需要将参数声明为一个空的数组的格式,省略数组的大小,例如:

1void procedure (int arg[]) 2

函数接受一个整数类型的arg数组,为了使得函数使用数组,可以声明一个数组:

1int myarray [40]; 2

然后可以调用:

1procedure (myarray); 2

这里是一个完整的例子:

1// arrays as parameters 2#include <iostream> 3using namespace std; 4 5void printarray (int arg[], int length) { 6 for (int n=0; n<length; ++n) 7 cout << arg[n] << ' '; 8 cout << '\n'; 9} 10 11int main () 12{ 13 int firstarray[] = {5, 10, 15}; 14 int secondarray[] = {2, 4, 6, 8, 10}; 15 printarray (firstarray,3); 16 printarray (secondarray,5); 17} 18

在上面的代码中,第一个参数(int arg [])接受任何元素的数组int类型,无论其长度为多少。为了解决其长度问题,我们包含了第二个参数,它告诉函数的每个数组的长度,我们通过它作为数组的一个长度参数。这样可以在循环的过程中打印出数组,不能超出数组的大小范围。
在数组声明的时候也可以包含多维数组的大小。下面是一个三维数组的参数声明:

1base_type[][depth][depth] 2

例如一个有多维数组的函数声明:

1void procedure (int myarray[][3][4]) 2

注意,第一个括号[ ]是空的,而下面的括号为各自的维度指定大小。这是必要的,以便编译器能够确定每一个额外的维度的深度。

总之,通过一个数组作为参数总是需要丢失一个维度。原因是,由于历史的原因,数组不能直接复制,因此真正传递的是一个指针。这是新手程序员常见的错误。明确的理解就是一个指针,详细解释将到下一章解释。

数组库

上面解释的数组是一个直接实现程序语言方法,继承了C语言。他们是的功能很好,但出于限制,它很容易地迁移到指针上,从而在优化上有一些麻烦。

为了克服数组在语言上的限制,c++提供了一个标准的数组类型的容器,所有的定义都包含在头文件< array >中。

容器是一个库功能,属于本教程的范围,因此不会在这里详细说明。简单的说,他们以类似的方式运作,内置的阵列,除了允许他们被复制(实际上这是很昂贵的操作,复制整个块的内存,因此需要小心使用)和变成指针,除非明确告诉要这样做(通过其成员数据)。

在这里下看一看一个使用构建数组以及使用数组的容器库来实现的相同功能的例子:
可以看到,这两种数组使用相同的语法来访问其元素:MyArray [i]。除这些之外,主要的区别就在数组的声明上,并且包含了库数组的一个附加的头文件array。还有就是如何方便地访问库数组的大小。

代码交流 2021