适用对象: 已完成第 3 课(循环)的同学 使用方式: 自学讲义。本课延续第 2、3 课的厚度——知识点 3 个大模块 + 8 题练习 + 加餐,做完大约需要 1.5 小时 学完本课,你应该能:
先别急着学新东西,写一个小程序把前三课串起来——读入一个整数,判断奇偶,再用循环把 1 加到它:
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
if (n % 2 == 0) {
cout << n << " 是偶数" << endl;
} else {
cout << n << " 是奇数" << endl;
}
int sum = 0;
for (int i = 1; i <= n; i++) sum += i;
cout << "1 加到 " << n << " 是 " << sum << endl;
return 0;
}int
类型(L01)、if/else(L02)、for
循环(L03)——这三课的知识在这一个程序里全用到了。今天要加入第四块拼图:数组。
Python 里你用 list 装一串数据:
Python 你这样写:
a = [0, 0, 0, 0, 0] # 5 个 0
b = [1, 2, 3, 4, 5] # 直接给初始值
b[2] = 99 # 改第 3 个(下标从 0 开始)
print(b[0], b[2])C++ 改成这样:
#include <bits/stdc++.h>
using namespace std;
int main() {
int a[5] = {0}; // 5 个 0(花括号里写一个 0,其余自动补 0)
int b[5] = {1, 2, 3, 4, 5};
b[2] = 99;
cout << b[0] << " " << b[2] << endl;
return 0;
}输出:1 99
只有这几个不同:
[大小]":int a[5]——5
是这个数组一共有多少个盒子b[0]
是第一个、b[4] 是最后一个,和 Python 完全一样{ }(不是方括号):{1, 2, 3, 4, 5}a[i]
既能读(cout << a[i])也能写(a[i] = 99;),用法和普通变量一样,只是多了个下标⚠️ 本课最大的新规矩:C++
数组的大小必须是提前写死的常数,声明完就不能再变大。 不像
Python 的 list 能 .append() 无限加长——C++
也有能动态增长的容器(叫
vector),以后学,现在先学"大小固定"的数组,够用了。
那不知道具体有多少个数据怎么办?
常见做法是先开一个"足够大"的数组,再用一个变量 n
记录"实际用了多少格":
#include <bits/stdc++.h>
using namespace std;
int main() {
int a[1005]; // 先开一个够大的数组(1000 多个格子)
int n;
cin >> n; // 实际要用几个,读进来存在 n 里
for (int i = 0; i < n; i++) cin >> a[i]; // 只用前 n 个格子
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}📝 C++ 数组没有 Python 的 len(a)。
数组本身不知道自己"装了几个有效数据",必须自己另开一个变量
n 来记账——这是和 Python list 最大的使用习惯差异。
新建 check1.cpp:声明一个
int a[5] = {10, 20, 30, 40, 50};,用 for 循环把 5
个数依次输出(空格分隔)。
几乎所有数组题目都是这三步:
// ① 读入:把数据装进数组
for (int i = 0; i < n; i++) cin >> a[i];
// ② 处理:遍历数组做点什么
for (int i = 0; i < n; i++) { /* ... */ }
// ③ 输出:把结果打印出来
cout << 结果 << endl;
第 3
课你已经用循环写过累加、计数、打擂台三种套路,只是当时数组是"半成品"(照抄了
int a[1005]
却不知道为什么这样写)。现在把那三个套路重新过一遍,这次完全讲清楚:
① 累加(数组求和):
#include <bits/stdc++.h>
using namespace std;
int main() {
int a[5] = {3, 1, 4, 1, 5};
int sum = 0;
for (int i = 0; i < 5; i++) sum += a[i];
cout << sum << endl; // 14
return 0;
}② 计数(数组里数满足条件的个数):
#include <bits/stdc++.h>
using namespace std;
int main() {
int a[5] = {3, 1, 4, 1, 5};
int cnt = 0;
for (int i = 0; i < 5; i++) {
if (a[i] % 2 == 1) cnt++; // 数奇数
}
cout << cnt << endl; // 4
return 0;
}③ 打擂台(数组里找最大值):
#include <bits/stdc++.h>
using namespace std;
int main() {
int a[5] = {3, 1, 4, 1, 5};
int maxV = a[0]; // 第一个先当擂台冠军
for (int i = 1; i < 5; i++) {
if (a[i] > maxV) maxV = a[i];
}
cout << maxV << endl; // 5
return 0;
}📝 这三个套路 + "读入-处理-输出"三部曲,组合起来就能解决今天练习里几乎所有的题。
错误 1:数组越界(第 3 课提过,现在自己写数组更要小心)
int a[5] = {1, 2, 3, 4, 5};
cout << a[5] << endl; // ← a 只有下标 0~4,a[5] 是数组外面!
a[5]
访问的是数组外面的内存,编译器大概率不报错,但会输出一个乱七八糟的垃圾值,运气不好还会让程序直接崩溃。
📝 数组开了几个,有效下标就是 0 到
大小-1,循环条件永远用 i < n,不要手滑写成
i <= n。
错误 2:数组没初始化就用(结果是垃圾值)
int a[5]; // 没写 = {0},也没读入任何数据
cout << a[2] << endl; // 这里可能输出任何数字,每次运行都可能不一样
局部数组(写在 main
函数里的数组)不会自动清零——想要全部是 0,要么写
int a[5] = {0};,要么老老实实用 cin
把每个位置都读进去。
📝 拿到"结果一会儿对一会儿不对"的程序,先检查数组是不是有格子从来没被赋值就被读取了。
和第 3 课一样,每道题的帮助分成三层,一层一层点开:
| 层 | 里面是什么 | 什么时候点开 |
|---|---|---|
| 提示 1:往哪个方向想 | 只告诉你用哪个套路 + 一个反问,不给代码 | 自己想满 5 分钟还没头绪 |
| 提示 2:关键的那一步 | 最关键的一两行 + 这题的坑 | 看了提示 1 又想了 5 分钟还写不动 |
| 参考答案 | 完整代码 | 提示 2 也试过了,确实写不出来 |
⚠️ 直接翻到参考答案,等于这道题没做。 你会觉得"我看懂了",然后下次遇到同类题还是写不出来——看懂和写得出来,中间隔着很远的距离。
📝 每点开一层,在纸上记一笔:我是卡在哪儿才看的。 一学期下来,这张纸比任何讲义都准确地告诉你:你到底缺什么。
📝 看完参考答案之后,把它关掉,从空文件重写一遍。 这一步不做,前面全白费。
⚠️
这一课的八道题里,有六道的坑都在"下标从哪儿开始、到哪儿结束"。
每写完一道,先别急着跑,用手指在纸上点一遍:第一次循环 i
是几、最后一次 i 是几、这两个下标在数组里真的存在吗。
⭐ 基础题 1:声明与输出(文件名
t1.cpp)
声明 int a[6] = {2, 4, 6, 8, 10, 12};,用 for
循环输出全部元素(空格分隔)。
输出样例:
2 4 6 8 10 12
数组配循环,就是第 2 节那个"三部曲"里的输出这一步。
先回答一个问题再动手:这六个盒子的编号是从几到几?
(不是 1 到 6。)
for (int i = 0; i < 6; i++) cout << a[i] << " ";
⚠️ 是 i < 6,不是
i <= 6。 六个盒子的编号是
0 1 2 3 4 5——写 <= 6 就会去摸第七个盒子
a[6],那是数组外面,不是你的地盘。
📝 "开了几个就写 i < 几"
——这条从今天起要变成肌肉记忆。
#include <bits/stdc++.h>
using namespace std;
int main() {
int a[6] = {2, 4, 6, 8, 10, 12};
for (int i = 0; i < 6; i++) cout << a[i] << " ";
cout << endl;
return 0;
}⭐ 基础题 2:读入 n 个数原样输出(文件名
t2.cpp)
输入一个整数 n,接着输入 n 个整数,原样输出(空格分隔)。
输入样例:
4
7 3 9 2
输出样例:
7 3 9 2
第 2 节的"三部曲":读入 → (这题不用处理)→ 输出,两个循环。
动手前先答一个问题:n
是运行的时候才知道的,那数组该开多大?
按题目允许的最大值开死,比如
int a[1005];。开大一点不花钱,n
只负责管循环跑几次:
int a[1005];
for (int i = 0; i < n; i++) cin >> a[i];
⚠️ 别写 int a[n];。
有的编译器能过,有的过不了,考试的评测机上不一定认——这个写法以后再说。现在一律先开死大小。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int a[1005];
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = 0; i < n; i++) cout << a[i] << " ";
cout << endl;
return 0;
}⭐⭐ 实战题 3:逆序输出(文件名
t3.cpp)
输入 n 个整数,从后往前输出(不建新数组,直接倒着遍历)。
输入样例:
5
1 2 3 4 5
输出样例:
5 4 3 2 1
题目已经说了:不建新数组。
那么读入的循环一个字都不用改,只改输出的那个循环——改的也只有一件事:从哪儿走到哪儿、往哪边走。
for (int i = n - 1; i >= 0; i--) cout << a[i] << " ";
⚠️ 两头都容易差一个:
n - 1(最后一个的下标),不是
n——a[n] 在数组外面i >= 0,不是
i > 0——写成 > 0 就把第一个数
a[0] 漏掉了📝 拿 n = 1 试一下:只输出一个数才对。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int a[1005];
for (int i = 0; i < n; i++) cin >> a[i];
for (int i = n - 1; i >= 0; i--) cout << a[i] << " ";
cout << endl;
return 0;
}下标从 n-1(最后一个)倒着数到
0(第一个),不用另开数组存倒序结果。
⭐⭐ 实战题 4:统计区间内的个数(文件名
t4.cpp)
输入 n 个整数和两个数
l、r,统计数组中有多少个数落在
[l, r] 区间内(含两端)。
输入样例:
6
2 5 8 5 12 5
3 8
输出样例:
4
计数套路(第 3 课的三套路之一):一个计数器
cnt,符合条件就 cnt++。
和第 3 课唯一的差别,是判断条件从"是不是偶数"换成了"在不在区间里"。
⚠️ 注意读入顺序:先读 n 个数,最后才读 l 和
r——照题目给的顺序来。
if (a[i] >= l && a[i] <= r) cnt++;
⚠️ 一个区间要写两个条件,中间用 &&
连起来(第 2 课学的)。不能写成
l <= a[i] <= r ——那在 C++
里是合法的,但算出来的结果不是你想要的。
⚠️ 题目说"含两端",所以是 >= 和
<=,不是 > 和 <。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int a[1005];
for (int i = 0; i < n; i++) cin >> a[i];
int l, r;
cin >> l >> r;
int cnt = 0;
for (int i = 0; i < n; i++) {
if (a[i] >= l && a[i] <= r) cnt++;
}
cout << cnt << endl;
return 0;
}第 2 课学的 x > 0 && x < 10
区间判断,在这道题里配合数组用上了。
⭐⭐ 实战题 5:求和与平均分(文件名
t5.cpp)
输入 n 个整数(学生成绩),输出总分和平均分(平均分保留 1 位小数)。
输入样例:
4
80 90 70 100
输出样例:
340 85.0
累加套路,读入的时候顺手加,一个循环就够了。
⚠️ 真正的麻烦在平均分:第 2 课那个整数除法的坑,在这题上又来了。
先自己想一分钟:340 / 4 你写出来会是多少?如果换成
sum = 341、n = 4 呢?
double avg = (double)sum / n;
⚠️ 除号左边必须先变成小数,否则
int / int 先按整数算完(小数部分直接砍掉),再赋值给
double 已经晚了。
📝 double avg = sum / n; 是救不回来的。
判断症状很简单:平均分永远是 .0 结尾。
(double)sum 叫强制类型转换,先照抄;输出用
printf("%.1f", avg)
保留一位小数,也先照抄,格式化输出以后正式学。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int a[1005];
int sum = 0;
for (int i = 0; i < n; i++) {
cin >> a[i];
sum += a[i];
}
double avg = (double)sum / n; // 除法前转成 double,不然又踩第 2 课的整数除法坑
printf("%d %.1f\n", sum, avg);
return 0;
}(double)sum 是"类型转换"——把 int 强行当成
double
来算,这样除出来才有小数。printf("%.1f", ...) 是保留 1
位小数的写法,先直接照抄,格式化输出以后会正式学。
⭐⭐⭐ 冲刺题 6:山峰数组(文件名
t6.cpp)
输入 n
个整数,判断这个数组是不是"山峰形状"——先严格递增一段,到达最高点后再严格递减,最高点前后都至少要有一个数(不能一直增或一直减)。是山峰输出
是山峰,不是输出 不是山峰。
输入样例1: 输入样例2:
5 4
1 3 5 2 1 1 2 3 4
输出样例1: 输出样例2:
是山峰 不是山峰
分两步:先找到最高点,再检查它两边。
⚠️ 但这里有个关键的差别,想清楚了这题就通了一半:这道题要的不是最高的那个"数值",而是最高点在"第几格"。
为什么?因为你接下来要检查的是"它左边那一段"和"它右边那一段"——知道数值没用,得知道位置。
打擂台的时候,擂主记的是下标不是数值:
int peak = 0;
for (int i = 1; i < n; i++) {
if (a[i] > a[peak]) peak = i; // 比的是 a[i] 和 a[peak],记的是 i
}
然后三件事都要检查:
peak
不能是第一个也不能是最后一个(peak == 0 || peak == n-1
→ 不是山峰)。题目要求"最高点前后都至少有一个数"1 到
peak:每个都要比前一个大(严格,相等也不行
→ <= 就判假)peak+1 到 n-1:每个都要比前一个小📝 用一个 bool ok = true; 记结果,任何一条不满足就
ok = false,别一发现不对就急着输出。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int a[1005];
for (int i = 0; i < n; i++) cin >> a[i];
int peak = 0; // 打擂台找最高点的下标
for (int i = 1; i < n; i++) {
if (a[i] > a[peak]) peak = i;
}
bool ok = true;
if (peak == 0 || peak == n - 1) ok = false; // 最高点不能是第一个或最后一个
for (int i = 1; i <= peak; i++) {
if (a[i] <= a[i - 1]) ok = false; // 前半段要严格递增
}
for (int i = peak + 1; i < n; i++) {
if (a[i] >= a[i - 1]) ok = false; // 后半段要严格递减
}
cout << (ok ? "是山峰" : "不是山峰") << endl;
return 0;
}先打擂台找到最高点的下标(不只是数值,因为要检查它前后的位置),再分别检查前半段递增、后半段递减。
⭐⭐⭐ 冲刺题 7:综合大题——成绩统计(文件名
t7.cpp)
输入 n
个学生的成绩,依次输出:最高分、90 分及以上的人数、平均分(保留
1 位小数)。这题会用满本课和前三课的所有知识点。
输入样例:
5
78 92 85 90 60
输出样例:
92 2 81.0
三个套路一次全用上:累加(总分)、打擂台(最高分)、计数(90 分以上几个人)。
⚠️ 别急着写代码。先在纸上列出你需要哪几个变量、每个的初值是几。 列不出来就说明还没想清楚,这时候敲键盘只会越写越乱。
(提示:一共四个变量,其中三个是"从头攒到尾"的。)
三件事可以分成三个循环写(好懂),也可以合成一个循环(读入的时候顺手全干完)。两种都对,先写好懂的那种。
⚠️ 打擂台的初值必须是 a[0],不能是 0。
这就是第 3 次课改错 3-2 那道题——maxV = 0
在正数数据上永远看不出问题,一遇到全负数就露馅。
⚠️ 所以如果你要合成一个循环,得先把数据读进来才有
a[0] 可用;或者干脆分开写:先读入,再
int maxV = a[0];,再从 i = 1 开始比。
平均分照旧 (double)sum / n。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int a[1005];
int sum = 0;
for (int i = 0; i < n; i++) {
cin >> a[i];
sum += a[i]; // 累加
}
int maxV = a[0];
for (int i = 1; i < n; i++) {
if (a[i] > maxV) maxV = a[i]; // 打擂台
}
int cnt = 0;
for (int i = 0; i < n; i++) {
if (a[i] >= 90) cnt++; // 计数
}
double avg = (double)sum / n;
printf("%d %d %.1f\n", maxV, cnt, avg);
return 0;
}int / double(L01)、比较运算(L02)、for
循环(L03)、数组(本课)——一次全用上。这就是"综合"的意思:不是学了新东西就忘了旧的,而是新旧拼在一起解决更大的问题。
⭐⭐⭐ 冲刺题 8:找众数(文件名
t8.cpp,选做)
输入 n 个整数,值的范围是
0~100,输出出现次数最多的那个数(保证答案唯一)。
输入样例:
6
3 1 3 5 3 2
输出样例:
3
如果你的想法是"拿每个数去和其他所有数比一遍"——能做出来,但太笨了,而且写起来要两层循环套着,容易乱。
换个方向想:题目特意告诉你值的范围是
0~100,这句话是白给的吗?
⚠️ 想象你开一个数组,让"下标就是那个数字本身",格子里放"它出现了几次"。 读到一个 3,就去 3 号格子里加一笔。
那么读完之后,答案在哪儿?
int cnt[101] = {0}; // 下标 0~100,正好盖住题目给的取值范围
...
cnt[x]++; // 读到 x,就在 x 号格子上加一笔
统计就这一句,不用任何比较。最后在 cnt
上打擂台,找的是最大值所在的下标(和第 6
题一样,要位置不要数值):
int ans = 0;
for (int v = 0; v <= 100; v++) {
if (cnt[v] > cnt[ans]) ans = v;
}
⚠️ = {0} 不能省。 写在
main 里的数组不会自动清零(第 3 节「错误
2」讲过),不清零就
cnt[x]++,等于在一堆随机数上加一笔,结果每次运行都可能不一样。
⚠️ 数组要开到 101 不是 100——下标
0 到 100 一共 101 个。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int cnt[101] = {0};
for (int i = 0; i < n; i++) {
int x;
cin >> x;
cnt[x]++;
}
int ans = 0;
for (int v = 0; v <= 100; v++) {
if (cnt[v] > cnt[ans]) ans = v;
}
cout << ans << endl;
return 0;
}这题用了"数组当计数器"的技巧——下标本身就是数值,cnt[x]
直接就是"x 出现了几次",比一个个比较快得多。这个技巧以后会经常用到。
第 1 课学的 string 其实内部就是"字符排成一排",也能用
[] 按下标取出单个字符:
#include <bits/stdc++.h>
using namespace std;
int main() {
string s = "hello";
cout << s[0] << " " << s[4] << endl; // h o
return 0;
}s[0] 是第一个字符 h,s[4]
是第五个字符
o——和数组的下标规则完全一样。字符串更完整的用法(拼接、查找、二维数组、vector
动态数组)以后学,今天只需要知道"字符串和数组用的是同一套下标语法"。
| Python | C++ | 备注 |
|---|---|---|
a = [0, 0, 0, 0, 0] |
int a[5] = {0}; |
大小是提前写死的常数 |
a = [1, 2, 3, 4, 5] |
int a[5] = {1, 2, 3, 4, 5}; |
花括号初始化 |
a[i] |
a[i] |
下标都从 0 开始,读写都行 |
len(a) |
没有! | 要自己另开一个变量 n 记录个数 |
a.append(x) |
没有! | 数组大小不能动态增长,vector 以后学 |
s[i](字符串取字符) |
s[i] |
string 也能按下标取字符 |
数组上的三种循环套路:
sum += a[i]; // ① 累加
if (cond) cnt++; // ② 计数
if (a[i] > maxV) maxV = a[i]; // ③ 打擂台两个数组专属坑:
| 症状 | 原因 |
|---|---|
| 结果是垃圾值或程序崩溃 | 数组下标越界(i < n 写成了
i <= n) |
| 结果时对时错 | 数组某些位置从没被赋值就被读取了 |
本课新符号: int a[大小]、数组下标
[]、(double)
类型转换、printf("%.1f", ...)
append?现在怎么绕过这个限制?