4.1 数组初识
数组(array)是一种数据格式,能够存储多个相同类型的值。想象你有一排整齐排列的盒子,每个盒子都有一个编号,并且这些盒子都用来存放同一种类型的东西,比如都是放玩具汽车。在编程里,数组就类似这样一排盒子,它是一组相同类型的数据元素的集合,这些元素在内存中是连续存储的。每个元素都可以通过一个唯一的编号来访问,这个编号就叫做索引(也叫数组下标)。
要创建数组,可以使用声明语句。数组的声明应该正确的指出三点,数据的类型、数组名称以及数组中的元素个数。其中数组中的元素个数必须是正整数常数1。例如:
数据类型 数组名[数组大小];
例如,声明一个可以存放 5 个整数的数组:
int numbers[5];
这里,int
是数组元素的类型,numbers
是数组的名称,5
是数组的大小,表示这个数组可以存放 5 个整数。事实上,我们可以将数组中的每个元素看作是一个简单变量。
4.1.1 初始化数组
C++ 初始化数组有几条规则,他们限制了初始化的时刻,决定了数组元素数目与初始化器中值的数目不相同时将发生的情况。
只有在定义数组时才能使用初始化,此后就不能再初始化了,也不能将一个数组赋给另外一个数组(但可以使用索引分别给数组元素赋值):
// 允许
int arr1[3] = {0, 1, 2};
// 允许
int arr2[3];
// 允许
int number = 2;
int arr3[3];
arr3[3] = number;
// 不允许
int arr4[3];
arr4[3] = {0, 1, 2}
// 不允许
arr1 = arr2;
有几种常见的初始化数组的方式:
全部初始化:在声明数组时,为所有元素提供初始值。
int numbers[5] = {1, 2, 3, 4, 5};
部分初始化:只提供部分元素的初始值,剩下的元素会自动初始化为 0。
int numbers[5] = {1, 2};
numbers[0]
为 1,numbers[1]
为 2,而 numbers[2]
、numbers[3]
、numbers[4]
都为 0。
省略数组大小:如果在初始化时提供了所有元素的值,可以省略数组大小,编译器会根据初始化的值的数量来确定数组的大小。
int numbers[] = {1, 2, 3, 4, 5};
numbers
的大小会被编译器自动确定为 5。
4.1.2 访问数组元素
程序代码4-1(酒店房间预订程序)中说明了数组的一些属性,包括初始化数组、声明数组以及给数组元素赋值。
三元运算符(? :)
之前提到三元运算符,也称为条件运算符,是 C++ 中唯一的一个三目运算符,它提供了一种简洁的条件判断和赋值方式。三元运算符的基本语法结构如下:
condition ? expression1 : expression2;
expression1:当 condition 的结果为 true 时,整个三元运算符表达式的结果就是 expression1 的值。
expression2:当 condition 的结果为 false 时,整个三元运算符表达式的结果就是 expression2 的值。
简而言之,condition
为真吗?如果为真返回 expression1
否则就返回 expression2
。
#include <iostream>
using namespace std;
int main() {
// 定义布尔数组来表示 3 个房间的使用状态,初始都为空闲
bool roomStatus[3] = {false, false, false};
// 显示房间初始状态
cout << "房间 1:" << (roomStatus[0] ? "已使用" : "空闲") << endl;
cout << "房间 2:" << (roomStatus[1] ? "已使用" : "空闲") << endl;
cout << "房间 3:" << (roomStatus[2] ? "已使用" : "空闲") << endl;
// 模拟预订房间 2
roomStatus[1] = true;
// 显示房间更新后的状态
cout << "\n更新后:" << endl;
cout << "房间 1:" << (roomStatus[0] ? "已使用" : "空闲") << endl;
cout << "房间 2:" << (roomStatus[1] ? "已使用" : "空闲") << endl;
cout << "房间 3:" << (roomStatus[2] ? "已使用" : "空闲") << endl;
return 0;
}
下面是程序的运行结果:
房间 1:空闲
房间 2:空闲
房间 3:空闲
更新后:
房间 1:空闲
房间 2:已使用
房间 3:空闲
图 4-1-1 数组下标
数组的索引是从 0 开始的。也就是说,对于一个大小为 n 的数组,合法的索引范围是从 0 到 n - 1。例如程序代码4-1(酒店房间预订程序)中我们使用 roomStatus[1] = true;
语句将房间 2 由空闲变为已使用状态。你可以像使用普通变量一样使用数组元素,比如进行赋值、计算等操作。
务必注意
目前大多数市面上的 C++ 编译器并不会主动检查使用的下标是否合法有效。
如果将一个值赋一个不存在的元素,例如:array[666]
,编译器并不会报错,但程序运行后,可能会导致程序异常终止。所以请务必注意确保使用有效的下标值。
4.1.3 多维数组
除了一维数组,还有多维数组,最常见的是二维数组。二维数组可以想象成一个表格,有行和列。声明二维数组的格式如下:
数据类型 数组名[行数][列数];
例如,我们声明一个 5 行 4 列的整数二维数组,并使其初始化:
//声明一个二维数组
int matrix[5][4];
//对二维数组进行初始化
int matrix[5][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
{13, 14, 15, 16}
{17, 18, 19, 20}
};
访问二维数组的元素需要指定行索引和列索引,例如 matrix[1][2]
表示第二行第三列的元素,值为 7。
4.1.4 数组的替代品
C++ 标准模板库(STL)中有一种数组的替代品—— vector,它比复合类型数组更复杂也更灵活,好虽好但也请不要着急,我们还在第 16 章向你详细介绍 STL 和 模板类 vector。
依靠编译器计算元素个数
在某些情况下,若依靠编译器来自动确定数组元素的个数,可能会产生不符合预期的结果。这是因为编译器的计算逻辑是基于它所看到的数据来进行的,当代码存在一些特殊情况或者开发者对数组的操作和设想与编译器的理解不一致时,就会导致计算出的元素个数和开发者原本设想的不同。
例如,当你将一个数组传递给函数时,数组名会退化为指针,此时如果在函数内部使用某些方式尝试让编译器计算数组元素个数,就会得到错误的结果,因为指针本身并不包含数组大小的信息。
但这种方法对于将字符数组初始化为一个字符串来说比较安全,当使用字符串常量来初始化字符数组时,编译器会正确处理字符串末尾的空字符 '\0'。
在这里我介绍一种计算数组元素个数的方法。
int array[] = {0, 1, 2, 3, 4, 5};
int num_elements = sizeof array / sizeof(array);
sizeof
它可以返回一个变量或者类型所占用的字节数。sizeof array
会返回整个数组 array
所占用的字节数,sizeof(short)
会返回 short
类型所占用的字节数。将整个数组的字节数除以单个元素的字节数,就可以得到数组中元素的个数。
如果在编写代码时,数组元素个数可能会经常变动,使用这种方式可以避免手动修改元素个数的代码,提高代码的可维护性,这就是很有必要的;
-
正整数常数:正整数常数指的是大于 0 的整数,常数是具有固定值的量(如 const 常量),其值不会发生变化。也就是说,在指定数组元素数目时我们必须将其指定为大于 0 且不可以是变量(不过,一些编译器(如 GCC)支持可变长度数组(VLA)作为扩展特性,但这并非 C++ 标准的一部分)。本章节后续会介绍如何使用
new
操作符来规避这种限制。 ↩