火龙信奥
  • 首页
  • 课程
  • 题库
  • 打卡
    • 代码对战
    • 快速对战
  • 题单
  • 团队
  • 荣誉墙
  • 商城
  • 登录 / 注册

C++ 程序设计课讲义:流程控制、数组与字符串

作者: 作者的头像   huolong , 时间:2026-08-15 23:15:34 , 所有人可见, 阅读  3

# C++ 程序设计课讲义:流程控制、数组与字符串

**课时:3 小时**  
**适用对象:C++ 入门到基础算法阶段学生**

## 一、课程安排

| 时间 | 内容 | 目标 |
| --- | --- | --- |
| 0:00 - 0:30 | 复习:顺序、分支、循环结构 | 能正确读写基本程序 |
| 0:30 - 1:00 | 嵌套循环 | 能用双层循环处理表格、枚举问题 |
| 1:00 - 1:20 | 调试程序 | 能定位常见编译错误、运行错误和逻辑错误 |
| 1:20 - 2:00 | 一维数组 | 会定义、读入、遍历、统计数组 |
| 2:00 - 2:30 | 桶技巧 | 能用“值作下标”统计频次、判重 |
| 2:30 - 3:00 | 一维字符数组 | 理解 C 风格字符串及常用函数 |

---

# 第一部分:顺序、分支、循环结构复习

## 问题引入

输入一个整数 `n`,输出:

- 如果 `n` 是正数,输出 `positive`
- 如果 `n` 是负数,输出 `negative`
- 如果 `n` 是 `0`,输出 `zero`
- 最后计算 `1 + 2 + ... + n`,当 `n > 0` 时输出和

这道题需要依次完成输入、判断、计算和输出,分别对应顺序、分支和循环结构。

## 知识点讲解

### 1. 顺序结构

程序从上到下依次执行。

```cpp
#include <iostream>
using namespace std;

int main() {
    int a, b;
    cin >> a >> b;

    int sum = a + b;
    cout << sum << endl;

    return 0;
}

2. 分支结构

if

if (条件) {
    条件成立时执行的语句;
}

if...else

if (条件) {
    条件成立时执行;
} else {
    条件不成立时执行;
}

多分支

if (n > 0) {
    cout << "positive" << endl;
} else if (n < 0) {
    cout << "negative" << endl;
} else {
    cout << "zero" << endl;
}

3. 循环结构

for 循环

适合循环次数明确的情况。

for (int i = 1; i <= n; i++) {
    cout << i << " ";
}

while 循环

适合“不知道具体循环多少次,但知道结束条件”的情况。

int x;
while (cin >> x) {
    cout << x << endl;
}

如何应用

计算 1 到 n 的和:

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;

    if (n <= 0) {
        cout << "n must be positive" << endl;
    } else {
        int sum = 0;
        for (int i = 1; i <= n; i++) {
            sum += i;
        }
        cout << sum << endl;
    }

    return 0;
}

学习难点

  • 理解条件表达式结果只有“成立”或“不成立”。
  • 判断区间时要注意边界,例如 n >= 0 与 n > 0 不同。
  • 理解循环变量 i 的变化过程。
  • 明确循环体 {} 中的语句会重复执行。

易错点

if (x = 5)

这里是赋值,不是比较。应写为:

if (x == 5)

循环条件写错:

for (int i = 1; i < n; i++)

这只能遍历 1 到 n - 1。若要遍历到 n,应写:

for (int i = 1; i <= n; i++)

存在的问题

  1. 输入 n = 0 时,求和循环是否执行?
  2. 输入负数时,是否应该计算 1 到 n 的和?
  3. if 条件中能否写多个条件?

问题提问

  1. if (a > b) 中,a == b 时会进入哪个分支?
  2. for (int i = 0; i < 5; i++) 一共执行几次?
  3. for 循环中的 i++ 是在循环体之前还是之后执行?
  4. 如何输出 1 到 100 中所有的偶数?

第二部分:嵌套循环

问题引入

输出一个 n × n 的乘法表。例如 n = 3:

1 2 3
2 4 6
3 6 9

一行中有多个数,且有多行,因此需要“循环里再套循环”。

知识点讲解

嵌套循环是一个循环体中包含另一个循环。

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++) {
        cout << i * j << " ";
    }
    cout << endl;
}

其中:

  • 外层循环变量 i 控制行数。
  • 内层循环变量 j 控制每一行输出的列数。
  • 每次外层循环执行一次,内层循环会完整执行一遍。

如何应用

1. 输出矩形

输入 n 和 m,输出 n 行 m 列的星号。

#include <iostream>
using namespace std;

int main() {
    int n, m;
    cin >> n >> m;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            cout << "*";
        }
        cout << endl;
    }

    return 0;
}

2. 输出直角三角形

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            cout << "*";
        }
        cout << endl;
    }

    return 0;
}

3. 枚举两个数

求 1 到 n 中所有满足 i < j 的数对。

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;

    for (int i = 1; i <= n; i++) {
        for (int j = i + 1; j <= n; j++) {
            cout << i << " " << j << endl;
        }
    }

    return 0;
}

学习难点

  • 外层循环每执行一次,内层循环都要从头开始。
  • 分清行、列和循环变量之间的对应关系。
  • 根据图形规律确定内层循环上界,例如 j <= i。

易错点

忘记换行:

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= n; j++) {
        cout << "*";
    }
}

应在外层循环中补上:

cout << endl;

内外层变量重复定义:

for (int i = 1; i <= n; i++) {
    for (int i = 1; i <= n; i++) {
    }
}

应使用不同变量,例如 i 和 j。

存在的问题

  1. 双层循环的时间复杂度通常是多少?
  2. 当 n = 1000 时,n × n 次操作是否仍然可接受?
  3. 如何打印倒三角形?

问题提问

  1. 外层循环执行 5 次,内层循环每次执行 3 次,总共执行多少次?
  2. 如何输出 n 行,每行恰好 n - i + 1 个星号?
  3. 怎样用嵌套循环找出 1 到 n 中所有两两不同的数对?

第三部分:调试程序

问题引入

下面程序希望计算 1 到 n 的和,但结果不正确:

#include <iostream>
using namespace std;

int main() {
    int n, sum;
    cin >> n;

    for (int i = 1; i <= n; i++) {
        sum += i;
    }

    cout << sum << endl;
    return 0;
}

输入 5,理论答案是 15,但程序可能输出随机数。

知识点讲解

程序错误通常分为三类。

1. 编译错误

程序无法编译,通常由语法错误造成。

cout << "Hello" << endl

缺少分号,应改为:

cout << "Hello" << endl;

2. 运行错误

程序可以编译,但运行时崩溃或异常退出。

int a[5];
cout << a[10] << endl;

访问超出数组范围的位置,行为不确定。

3. 逻辑错误

程序能运行,但结果不符合题意。

int sum = 0;
for (int i = 1; i < n; i++) {
    sum += i;
}

这里漏加了 n,应改为 i <= n。

如何应用

修复前面的求和程序:

#include <iostream>
using namespace std;

int main() {
    int n;
    int sum = 0;
    cin >> n;

    for (int i = 1; i <= n; i++) {
        sum += i;
    }

    cout << sum << endl;
    return 0;
}

调试步骤:

  1. 阅读题意,明确输入、输出和边界条件。
  2. 用小数据手动模拟,例如 n = 1、n = 2、n = 5。
  3. 在关键位置输出变量值。
  4. 检查变量是否初始化。
  5. 检查循环边界和数组下标。

输出中间变量:

for (int i = 1; i <= n; i++) {
    sum += i;
    cout << "i = " << i << ", sum = " << sum << endl;
}

比赛提交前应删除调试输出。

学习难点

  • 区分“程序不能运行”和“程序运行但答案错误”。
  • 能够手动模拟循环中的变量变化。
  • 理解未初始化变量的值不可预测。

易错点

  • 忘记初始化累加变量,例如 sum、ans、cnt。
  • 数组访问越界。
  • 循环少执行一次或多执行一次。
  • 调试输出没有删除,导致格式错误。
  • 输入多个测试数据时,变量没有重新初始化。

存在的问题

  1. 为什么 int sum; 不一定自动等于 0?
  2. 为什么数组下标越界有时没有立即报错?
  3. 为什么程序本地正确,提交后却错误?

问题提问

  1. for (int i = 0; i <= n; i++) 在数组长度为 n 时可能有什么风险?
  2. 如何设计最小测试数据来检查循环边界?
  3. 当答案比正确值小一项时,应优先检查程序的什么位置?

第四部分:一维数组

问题引入

输入 n 个整数,求它们的最大值、最小值和总和。

如果没有数组,只能定义很多变量:

int a1, a2, a3, a4, a5;

当数据有 100 个、1000 个时,这种写法不可行。数组可以用一个名字保存一组同类型数据。

知识点讲解

1. 数组定义

int a[100];

表示定义一个能存放 100 个整数的数组。

数组下标从 0 开始:

a[0] = 10;
a[1] = 20;

若数组长度为 n,合法下标为:

0, 1, 2, ..., n - 1

2. 数组读入

for (int i = 0; i < n; i++) {
    cin >> a[i];
}

3. 数组遍历

for (int i = 0; i < n; i++) {
    cout << a[i] << " ";
}

4. 数组性质

  • 同一个数组中的元素类型相同。
  • 数组元素连续存储。
  • 下标必须是合法范围。
  • 数组长度在定义时确定。
  • a[i] 表示第 i + 1 个元素。

如何应用

求最大值、最小值和总和

#include <iostream>
using namespace std;

int main() {
    int n;
    int a[1005];
    cin >> n;

    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }

    int sum = 0;
    int maximum = a[0];
    int minimum = a[0];

    for (int i = 0; i < n; i++) {
        sum += a[i];

        if (a[i] > maximum) {
            maximum = a[i];
        }

        if (a[i] < minimum) {
            minimum = a[i];
        }
    }

    cout << "sum = " << sum << endl;
    cout << "max = " << maximum << endl;
    cout << "min = " << minimum << endl;

    return 0;
}

逆序输出数组

for (int i = n - 1; i >= 0; i--) {
    cout << a[i] << " ";
}

统计偶数个数

int count = 0;

for (int i = 0; i < n; i++) {
    if (a[i] % 2 == 0) {
        count++;
    }
}

学习难点

  • 数组下标从 0 开始,而不是 1。
  • a[n] 不是第 n 个元素,而是第 n + 1 个位置,通常会越界。
  • 明确数组容量和实际使用元素个数不同。
  • 初始化最大值、最小值时,通常使用第一个元素。

易错点

错误写法:

int a[100];
for (int i = 1; i <= n; i++) {
    cin >> a[i];
}

若 n = 100,会访问 a[100],数组越界。

推荐写法:

for (int i = 0; i < n; i++) {
    cin >> a[i];
}

访问空数组第一个元素:

int maximum = a[0];

当 n = 0 时,这种写法不合法。应先确认题目是否保证 n >= 1。

存在的问题

  1. 数组 int a[10] 的最大合法下标是什么?
  2. 如何交换数组中两个元素?
  3. 如何原地翻转一个数组?

问题提问

  1. 输入 n 个数后,如何输出其中最后一个数?
  2. 如何统计数组中负数的个数?
  3. 如何求数组中第二大的不同元素?
  4. 如何判断一个数组是否已经升序排列?

第五部分:一维数组进阶:桶技巧

问题引入

输入 n 个范围在 0 到 100 的整数,统计每个数字出现多少次。

例如:

输入:2 1 2 3 2 1
输出:
1 出现 2 次
2 出现 3 次
3 出现 1 次

如果每个数字都和其他数字比较,可能需要使用双层循环。若数据范围较小,可以把“数值”直接当作数组下标。

知识点讲解

桶技巧也称计数数组、频次数组。

int bucket[101] = {};

数组下标代表某个数值:

  • bucket[0] 表示数字 0 出现次数。
  • bucket[1] 表示数字 1 出现次数。
  • bucket[100] 表示数字 100 出现次数。

读到一个数字 x 时:

bucket[x]++;

这表示数字 x 的出现次数加一。

如何应用

1. 统计每个数出现次数

#include <iostream>
using namespace std;

int main() {
    int n;
    int bucket[101] = {};

    cin >> n;

    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;
        bucket[x]++;
    }

    for (int x = 0; x <= 100; x++) {
        if (bucket[x] > 0) {
            cout << x << " appears " << bucket[x] << " times" << endl;
        }
    }

    return 0;
}

2. 判断是否有重复数字

#include <iostream>
using namespace std;

int main() {
    int n;
    int bucket[1001] = {};
    bool hasDuplicate = false;

    cin >> n;

    for (int i = 0; i < n; i++) {
        int x;
        cin >> x;

        bucket[x]++;

        if (bucket[x] >= 2) {
            hasDuplicate = true;
        }
    }

    if (hasDuplicate) {
        cout << "Yes" << endl;
    } else {
        cout << "No" << endl;
    }

    return 0;
}

3. 找出只出现一次的数

for (int x = 0; x <= 100; x++) {
    if (bucket[x] == 1) {
        cout << x << " ";
    }
}

4. 利用桶排序

当数值范围较小时,按下标从小到大扫描桶,就能得到有序序列。

for (int x = 0; x <= 100; x++) {
    for (int k = 1; k <= bucket[x]; k++) {
        cout << x << " ";
    }
}

学习难点

  • 理解“数组下标表示值,数组元素表示次数”。
  • 正确确定桶数组范围。
  • 判断输入值是否可能为负数。
  • 区分原数组 a[i] 和计数数组 bucket[x] 的含义。

易错点

桶数组开得过小:

int bucket[100];

如果输入可能等于 100,访问 bucket[100] 会越界。应定义:

int bucket[101] = {};

没有初始化:

int bucket[101];

应初始化为 0:

int bucket[101] = {};

输入存在负数时直接使用:

bucket[x]++;

若 x < 0,数组下标非法。可通过偏移处理:

int bucket[2001] = {};
bucket[x + 1000]++;

适用于 -1000 到 1000 的数据。

存在的问题

  1. 若数据范围达到 10^9,还能直接开桶吗?
  2. 若输入包含负数,如何设计桶下标?
  3. 桶技巧和双层循环统计频次相比,哪个更快?

问题提问

  1. 输入 20 个 0 到 9 的数字,如何输出出现次数最多的数字?
  2. 如果有多个数字出现次数最多,如何输出最小的那个?
  3. 如何用桶技巧判断两个数组是否包含相同的元素及次数?
  4. 为什么桶排序只适合数值范围相对较小的数据?

第六部分:一维字符数组与 C 风格字符串

问题引入

输入一个英文单词,统计其中有多少个元音字母。

例如:

输入:algorithm
输出:3

单词本质上是一串字符,可以用一维字符数组保存。

知识点讲解

1. 字符数组定义

char s[105];

char 用于保存单个字符:

char ch = 'A';

字符使用单引号:

char ch = 'a';

字符串使用双引号:

char s[] = "hello";

2. 字符串结束标志 '\0'

C 风格字符串以 '\0' 结尾。

char s[] = "hello";

实际存储为:

h e l l o \0

因此 "hello" 有 5 个可见字符,但至少需要长度为 6 的字符数组。

char s[6] = "hello";

3. 输入字符数组

读取一个不含空格的单词:

cin >> s;

读取一整行文字:

cin.getline(s, 105);

4. 遍历字符数组

for (int i = 0; s[i] != '\0'; i++) {
    cout << s[i] << endl;
}

5. 常见字符串函数

使用字符串函数时需要:

#include <cstring>

strlen

获取字符串长度,不包括结尾的 '\0'。

int len = strlen(s);

strcmp

比较两个字符串。

if (strcmp(a, b) == 0) {
    cout << "same" << endl;
}

strcpy

复制字符串。

strcpy(a, b);

strcat

拼接字符串。

strcat(a, b);

如何应用

1. 统计元音字母

#include <iostream>
#include <cstring>
using namespace std;

int main() {
    char s[105];
    cin >> s;

    int count = 0;

    for (int i = 0; s[i] != '\0'; i++) {
        if (s[i] == 'a' || s[i] == 'e' || s[i] == 'i' ||
            s[i] == 'o' || s[i] == 'u') {
            count++;
        }
    }

    cout << count << endl;
    return 0;
}

2. 使用 strlen 遍历字符串

#include <iostream>
#include <cstring>
using namespace std;

int main() {
    char s[105];
    cin >> s;

    int len = strlen(s);

    for (int i = 0; i < len; i++) {
        cout << s[i] << " ";
    }

    return 0;
}

3. 判断回文字符串

回文字符串正读和倒读相同,例如 level、abcba。

#include <iostream>
#include <cstring>
using namespace std;

int main() {
    char s[105];
    cin >> s;

    int len = strlen(s);
    bool isPalindrome = true;

    for (int i = 0; i < len / 2; i++) {
        if (s[i] != s[len - 1 - i]) {
            isPalindrome = false;
            break;
        }
    }

    if (isPalindrome) {
        cout << "Yes" << endl;
    } else {
        cout << "No" << endl;
    }

    return 0;
}

字符数组与 string 的区别

对比项 字符数组 char s[105] string s
本质 固定大小的字符数组 C++ 标准库字符串类型
结尾 需要 '\0' 标记结束 自动管理结尾
长度 strlen(s) s.length() 或 s.size()
比较 strcmp(a, b) a == b
拼接 strcat(a, b) a + b 或 a += b
输入空格 cin.getline() getline(cin, s)
数组容量 必须预先确定 可自动扩展

基础算法阶段应理解字符数组的底层结构;实际 C++ 编程中,很多题目使用 string 会更安全方便。

学习难点

  • 区分字符 'a' 和字符串 "a"。
  • 理解 '\0' 不是普通可见字符,而是字符串结束标志。
  • 理解字符数组容量必须为“字符个数加一”。
  • 掌握 cin >> s 和 cin.getline() 的区别。
  • 理解 strcmp 返回 0 才代表两个字符串相等。

易错点

字符与字符串混用:

char ch = "a";

应改为:

char ch = 'a';

字符数组空间不足:

char s[5] = "hello";

应改为:

char s[6] = "hello";

不能直接用 == 比较两个字符数组:

if (a == b) {
}

应使用:

if (strcmp(a, b) == 0) {
}

cin >> s 无法读取空格后的内容:

char s[105];
cin >> s;

若输入为 hello world,只能读到 hello。读取整行应使用:

cin.getline(s, 105);

存在的问题

  1. 为什么字符数组必须预留一个位置给 '\0'?
  2. strlen(s) 和 sizeof(s) 有什么区别?
  3. 为什么 strcmp(a, b) == 0 表示两个字符串相等?
  4. 使用 cin >> s 后立刻使用 cin.getline(),为什么可能读到空行?

问题提问

  1. 如何统计字符串中数字字符的个数?
  2. 如何将字符串中的小写字母转换为大写字母?
  3. 如何判断两个单词是否由相同字母组成?
  4. 如何统计一个句子中单词的数量?
  5. 如何删除字符串中所有的空格?

课后练习

基础题

  1. 输入 n,输出 1 到 n 中所有 3 的倍数。
  2. 输入 n,输出一个边长为 n 的实心正方形。
  3. 输入 n 个整数,输出最大值和最小值。
  4. 输入 n 个 0 到 100 的整数,输出出现次数最多的数。
  5. 输入一个单词,统计其中大写字母、小写字母和数字的数量。

提高题

  1. 输入 n 个整数,判断是否存在重复元素。
  2. 输入 n 个 0 到 1000 的整数,按从小到大顺序输出。
  3. 输入一个字符串,判断它是否为回文串。
  4. 输入两个字符串,判断它们是否互为字母重排词。
  5. 输入一行文本,统计每个英文字母出现的次数,忽略大小写。 ```

—— 本文来自火龙信奥(义乌睿码科技):义乌青少年信息学奥赛与编程教育平台,专注 CSP-J/S、NOIP、GESP 竞赛培训,线上线下融合教学,助力编程升学。网址:hlcoding.com

关于火龙

  • 关于我们
  • 学员获奖
  • 预约试听
  • ACM课程
  • CSP课程
  • 学习指南

帮助中心

  • 用户协议
  • 打字练习
  • 在线画图
  • DevC++下载
  • CSP报名
  • GESP官网

推荐课程

  • C++零基础入门(可试看)
  • C++进阶提升
  • GESP考级辅导
  • GESP打卡
  • CSP-J/S打卡

公众号

火龙信奥公众号二维码

地址:义乌市北门街188号新天地商厦二楼2F 邮箱:wdlok305@126.com

© 2017-2026 义乌市睿码科技有限公司版权所有 浙ICP备2021013995号

火龙信奥
请输入登录信息


请完成安全验证
验证码底图 滑块
向右拖动滑块完成验证
请输入用户名 / 绑定的手机号码



请输入注册信息(手机号验证码注册)





验证码5分钟有效,60秒内不可重复获取,每日最多3次

微信登录

微信登录二维码

正在生成二维码...

账号已过期,请续期。
去续期

绑定手机号

📱

为了更好地保护您的账号安全,享受完整的平台服务

请您尽快绑定手机号码