引用数组里的某一格,靠下标:数组名[下标]。这个写法既能读也能写(是 3.4 说的"左值")。它最大的坑是——越界不报错:写了 a[5] 而数组只有 5 格,编译器一声不吭,程序照跑,只是悄悄踩坏了别人的数据。
7.1.1 把柜子订好了,这一节学"怎么开柜门"。a[i] 里的 i 可以是常量,也可以是变量或表达式——配上 5.2 的 for,就得到"遍历"这个超级常用的套路。另外,a[i] 能当左值直接赋值(a[0] = 5;),是因为它指向内存里一个真实存在的格子(3.4 左值定义为"有地址"的盒子,数组格子天然满足)。
一、引用的写法
数组名[下标]
int a[5] = {10, 20, 30, 40, 50};
a[0] // 10 —— 第一格
a[2] // 30 —— 第三格(不是第二格!)
a[4] // 50 —— 最后一格
a[1] = 99; // 也能赋值:改的是第二格
下标与格子的对应关系(务必记牢):
| 你想要 | 写什么 | 说明 |
|---|---|---|
| 第 1 格 | a[0] |
下标 0 |
| 第 i 格 | a[i-1] |
下标比序号小 1 |
| 最后一格 | a[4] |
长度为 5 时 |
| 不存在的格 | a[5] |
⚠️ 越界! |
二、下标可以是"会变的"
[] 里不限于写死的数字,任何算出整数的表达式都行:
int a[5] = {10, 20, 30, 40, 50};
int i = 3;
cout << a[i] << endl; // 40 —— 变量当下标
cout << a[i - 1] << endl; // 30 —— 表达式当下标
cout << a[2 * 1] << endl; // 30 —— 随便算
正因为下标能变,才能配 for 一次走遍整排柜子——这就是遍历:
for (int i = 0; i < 5; i++) {
cout << a[i] << " "; // i 依次取 0 1 2 3 4
}
为什么是 i < 5 而不是 i <= 5?因为 i <= 5 会让 i 取到 5,而 a[5] 越界。这个 < 与 <= 的一字之差,是数组相关 bug 的头号来源。
三、最大的坑:越界不报错
看这段代码,你觉得会发生什么?
int a[3] = {1, 2, 3};
int b = 100;
a[3] = 999; // ⚠️ a 只有 3 格,下标 3 已经越界
cout << b << endl; // 打印什么?
答案:很可能打印不出 100。原因在于数组相邻存放(7.1 的内存图):a 后面的内存是谁,编译器不管你。而 a[3] 位置的那块内存,可能正好就是变量 b 的位置。于是 a[3] = 999 悄悄把 b 改成了 999。
为什么编译器不拦?因为 a[3] 在编译器眼里只是"从头往后挪 3 格",语法完全合法——它没有义务去数你订了几格。这就是 C/C++ 有名的"信任程序员"哲学:速度快、灵活,代价是自己看住下标。
安全尾巴:后面的章节会介绍更安全的容器(
vector、string),它们会替你把关下标。但理解数组越界,是理解"内存到底是什么"的必经之路。
四、动手 Demo:三个常用套路
#include <iostream>
using namespace std;
int main() {
int a[6] = {12, 45, 7, 33, 88, 20};
int n = sizeof(a) / sizeof(a[0]); // 数格子(7.1.1)
cout << "正向:";
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
cout << "倒序:";
for (int i = n - 1; i >= 0; i--) cout << a[i] << " "; // 从最后一格往回走
cout << endl;
int mx = a[0]; // 找最大:从第一格起步
for (int i = 1; i < n; i++)
if (a[i] > mx) mx = a[i]; // 4.2.1 的单分支 if
cout << "最大 = " << mx << endl;
int pos = 0; // 找目标所在的下标
for (int i = 1; i < n; i++)
if (a[i] == 88) pos = i;
cout << "88 的下标 = " << pos << endl;
return 0;
}
看到什么算成功:
正向:12 45 7 33 88 20
倒序:20 88 33 7 45 12
最大 = 88
88 的下标 = 4
三个套路值得记下来:① 正向遍历从 0 到 n-1;② 倒序遍历从 n-1 到 0(下标是 i >= 0,别写成 i > 0,否则会漏掉第一格);③ "找最大/找目标"都把第一格当作初始值,从第二格开始比。
30 秒实验:把倒序循环的条件 i >= 0 改成 i > 0,重跑——最小的那个数(第一格 12)不见了。这就是"边界差一"(off-by-one)的典型现场,数组里天天遇到。
五、常见误区
- "越界会报错,报错我就知道错了"——不会报错。编译能过、运行可能也不崩,只是默默改坏别人的内存。越界是本课最"阴"的坑。
- "
a[1]是第一个元素"——是第二个。第一个是a[0]。写循环时记住"数量减一"。 - "遍历写
i <= n也行"——那就访问了a[n],越界。应该是i < n。 - "
a[i]只能读、不能改"——能改。a[i] = 值;完全合法(它是左值,3.4)。数组的"不能整体赋值"(a = b不行)和"单格能赋值"是两回事。 - "下标能给负数吗"——语法上
a[-1]编译能过,但那是越界访问a前面的内存,属于未定义行为,后果不可预测。
自测三问(点开对答案)
1. `int a[5]` 中 `a[5]` 会怎样?——越界。编译器不报错,可能悄悄改坏相邻内存,属于未定义行为。 2. 长度为 n 的数组,遍历时的循环条件?——`for (int i = 0; i < n; i++)`,注意是 `<` 不是 `<=`。 3. 为什么 `a[i] = 5;` 合法,而 `a = b;` 不合法?——前者改的是**某一格**(有地址的左值);后者想整体替换**整排柜子的首地址**,而数组名不是可改的变量(7.5.1 细说)。- ☐ 7.1.3 一维数组的初始化(下一节)
相关笔记
- 一维数组的声明 —— 格子订好了,才知道下标范围
- for循环语句的一般形式 —— 遍历数组的搭档
- 判断左值与右值 ——
a[i]是左值 - 🔷 C++基础 · 索引 —— 全课路线图