初识数组
如果想要表示一系列变量的时候,数组可以帮你完成这个任务。在了解数组之前,先来看看这个例子:
你可以试一下这个例子。这段程序编译运行的结果,是将 a
到 j
这是个字母一行一个地输出出来。那么这是怎么做到的呢?你可以注意到第 5 行貌似写出了从 a
到 j
这十个字母,应该也能猜到这就是这段程序工作的核心原理。
没错,这一行就声明并定义了一个数组。数组类型(Array type)是一种特殊的变量类型,它实际上由有限个相同类型的变量“合在一起”组成;一个数组类型的变量被称为数组(Array)。比如上述程序的第 5 行所声明并定义的数组 a
,这个数组由 10 个 char
类型的变量“合在一起”组成。另外,你可以通过 a[0]
a[1]
a[2]
……的方式去分别依次获取这 10 个 char
类型的变量——这就是第 7 行的工作原理。
数组
T a[n]
实际上指:名字a
指代了n
块连续的存储空间,这些存储空间分别是T
类型的变量。
首先来看数组的声明语句的写法:
类型说明符 数组名[数组大小] 初始化器;
你会发现这个写法和普通变量非常相似,只不过多了一个 [数组大小]
。这段声明语句的含义是什么呢?它声明并定义了一个数组,这个数组包含 数组大小
这么多个 类型说明符
类型的变量;并用 初始化器
提供的值进行初始化。听上去很复杂,看看下面的例子:
int a[5]{1, 2, 3, 4, 5};
这里声明了一个名字叫 a
的数组。它是由 5
个 int
型的变量“合在一起”形成的一个“大”变量。除此之外,这个声明语句提供了形如 {1, 2, 3, 4, 5}
的初始化器——意思也很直接,就是说把这 5
个 int
型的变量分别初始化为 1
2
3
4
5
。
这里我不必太多介绍初始化器了。因为它和原先普通变量的初始化器相比不过是改成了用逗号分隔的列表而已。这些列表中的值会逐一地初始化数组对应位置的值。
数组本身作为一个“大”变量并没有什么太多存在感,实际上我们更想要组成这个数组的那些“小”变量。这些“小”变量被称为数组的元素(Element)。我们可以非常简单地通过数组名 + 方括号 []
的形式来访问这些元素。比如:
a[0]; // 访问数组的第一个元素,它是一个 int 类型的变量,刚刚被初始化为 1
a[1]; // 访问数组的第二个元素,它刚刚被初始化为 2
a[2];
a[3];
a[4]; // 这是数组的最后一个元素
你已经注意到了:使用 []
访问元素的时候,是从 0
开始的,也就是说对于长度为 5 的数组,最多只能用到 a[4]
,而 a[5]
则是错误的。这和我们平常“第一”“第二”的说法有一点点不同,请慢慢习惯。
注意事项
数组用起来很有用,但也很麻烦;因为数组有着太多太多的细节问题,以至于我们这里只能简单提及,更详细的说明将会在下一章中才能展开。不过尽管如此,请仔细阅读接下来的文字,因为它们至关重要!
- 在声明并定义数组时,要求数组的大小必须是常量,而且是正数。注意要求是常量而不仅是只读变量。
- 数组在 C++ 属于“二等公民”,因此它并不能和普通变量“平起平坐”。比如:数组不能被整体赋值,
a = {1, 2, 3}
是语法错误。若想实现对数组的整体赋值,只能对数组的每一个元素进行赋值。 - 通过中括号
[]
访问数组时,若访问的元素不合法(比如长度为5
的数组a
,去访问元素a[10]
),则**不会给出编译错误,甚至运行时也不会给出错误!**因此请在处理数组时谨慎设定长度。 - 数组的初始化器可以为空,比如
int a[5]{};
。这个时候,会将数组中的每一个元素初始化为零。但是空的初始化器不代表可以不写初始化器;不写初始化器的话不会进行初始化,也就是不知道每一个元素的值是多少。
危险
数组不能整体赋值,数组不能整体赋值,数组不能整体赋值! 请将这句话牢牢记在心里。
简单应用
我在这里通过一道例题来简单介绍数组的一种应用方式:用于统计。
用户首先输入一个正整数 n,最后输入 n 个介于 之间(含)的整数。要求程序输出 之间每个数被输入的次数。这是一道典型的可以运用数组解决的问题。你可以先停下来想一想怎样去写这个程序。如果没有思路的话,请继续来看:
#include <iostream>
using namespace std;
int main() {
int n{0}, x{0}, i{1};
int cnt[21]{}; // 初始化器内留空,表明全部初始化为 0
cin >> n;
while (i <= n) {
cin >> x;
cnt[x] = cnt[x] + 1;
i = i + 1;
}
i = 0;
while (i <= 20) {
if (cnt[i] > 0) {
cout << "Number " << i << " was entered " << cnt[i] << " time(s)." << endl;
}
i = i + 1;
}
}
我可以用一个数组 int cnt[21]{};
来存储每个数被输入的次数——比如,这个数组里的元素 cnt[i]
就代表整数 i
被输入的次数。(为什么数组大小设置为 21
?)请点击右侧按钮查看完整代码,它编译运行效果是这样的:
¶10↵
¶1 1 2 4 5 20 19 19 8 7↵
Number 1 was entered 2 time(s).
Number 2 was entered 1 time(s).
Number 4 was entered 1 time(s).
Number 5 was entered 1 time(s).
Number 7 was entered 1 time(s).
Number 8 was entered 1 time(s).
Number 19 was entered 2 time(s).
Number 20 was entered 1 time(s).
看上去不错。我们来理解一下这段代码。首先来看前半段:
#include <iostream>
using namespace std;
int main() {
int n{0}, x{0}, i{1};
int cnt[21]{}; // 初始化器内留空,表明全部初始化为 0
cin >> n;
while (i <= n) {
cin >> x;
cnt[x] = cnt[x] + 1;
i = i + 1;
}
i = 0;
while (i <= 20) {
if (cnt[i] > 0) {
cout << "Number " << i << " was entered " << cnt[i] << " time(s)." << endl;
}
i = i + 1;
}
}
这里就像我刚才所说的声明并定义了一个数组叫 cnt
,它的长度为 21
,也就是里面包含了 21 个小的 int
变量。注意这里是一个空初始化器,意思是数组 cnt
内部的元素值全为零。这是我们想要的,因为在输入之前,所有数出现的次数必然是 0。然后输入 n
,随后令变量 i
从 1
递增到 n
,用于输入这 n
个要统计的数。在循环内部,首先输入要统计的数,随后就是最关键的一行: cnt[x] = cnt[x] + 1;
。这一行的含义是,对输入的 x
对应的次数存放变量 cnt[x]
增加 1
。因为目前又输入了一个 x
,相比之前 x
的个数就会增加 1 个。于是通过这种方式就可以记录所有数出现的次数。
下半部分相对就很简单了,只不过是一个输出罢了。
#include <iostream>
using namespace std;
int main() {
int n{0}, x{0}, i{1};
int cnt[21]{}; // 初始化器内留空,表明全部初始化为 0
cin >> n;
while (i <= n) {
cin >> x;
cnt[x] = cnt[x] + 1;
i = i + 1;
}
i = 0;
while (i <= 20) {
if (cnt[i] > 0) {
cout << "Number " << i << " was entered " << cnt[i] << " time(s)." << endl;
}
i = i + 1;
}
}
首先令 i
从 0
循环到 20
。这是为了输出数 i
出现的次数。在循环内部,我们设置只有当出现次数大于零——也就是出现过,而不是没出现——的数输出,因此套了一层 if
语句,只有满足条件的时候才会输出。输出语句看上去很长,实际上也很好理解,这里不再赘述。最后让 i
递增,就实现了所有数的次数统计。