调整课次结构:前9节重新排序,新建第06课一维数组,后3节不变

This commit is contained in:
chengzi
2026-07-03 15:27:11 +08:00
parent 30f803784a
commit b973fb7825
29 changed files with 1301 additions and 13 deletions

View File

@@ -0,0 +1,151 @@
# 第3课自定义函数教学拓展
> 等级说明:本课用于拓展代码分解能力,不属于 GESP 一级、二级必考内容。
## 教学目标
- 掌握函数的定义、调用与声明
- 理解形参与实参、返回值、void函数
- 理解函数重载(同名不同参)
- 理解局部变量与全局变量的作用域
## 核心考点:函数的参数、返回值与调用过程
### 考什么
补全函数定义区分________与________并根据函数调用过程判断输出。
### 必须理解
定义函数时括号里的变量叫________调用时传入的值叫________。执行到 `return`函数会________________。普通值传递中形参得到的是实参的________。
### 核心写法
```cpp
int maxValue(int a, int b) {
if (a > b) return ________;
return ________;
}
int ans = maxValue(________, ________);
```
### 常见错误
- 返回类型不是 `void`却忘记________________
- 修改普通形参是否一定改变外部实参________________
- 局部变量可以在哪个范围使用________________
### 判断是否掌握
- [ ] 我能区分形参和实参。
- [ ] 我能说清函数调用与返回的执行顺序。
- [ ] 我能独立编写有参数、有返回值的函数。
---
## 课堂编程题
### 课堂1三个数的最大公约数【适中】
**题目描述:** 编写函数 `gcd(a,b)`,使用辗转相除法求两个正整数的最大公约数。在 `main` 前写函数声明、`main` 后写函数定义,连续调用两次求三个数的最大公约数。
**输入格式:** 输入三个正整数。
**输出格式:** 三个数的最大公约数。
**数据范围:** `1≤a,b,c≤10^9`
**输入样例:**
```text
12 18 30
```
**输出样例:**
```text
6
```
**解题思路:** 先求 `gcd(a,b)`,再把结果与 `c` 求最大公约数,体会函数声明、定义和多次调用。
**易错提醒:** 函数声明必须与定义的返回类型、函数名和参数列表一致。
---
### 课堂2统计区间内的素数【适中】
**题目描述:** 编写有参数、有返回值的函数 `isPrime(n)`。输入 `L``R`,输出闭区间 `[L,R]` 中素数的个数。
**输入格式:** 输入区间的左端点和右端点。
**输出格式:** 素数个数。
**数据范围:** `1≤L≤R≤10000`
**输入样例:**
```text
10 20
```
**输出样例:**
```text
4
```
**解题思路:** 每个实参传入函数后复制给形参 `n`;找到因数时提前返回 `false`,全部检查后返回 `true`
**易错提醒:** 1 不是素数;普通值传递不会修改主函数中的实参。
---
### 课堂3统一格式输出【适中】
**题目描述:** 重载函数 `printLine`:一个版本接收一个整数并输出由该整数重复 5 次组成的一行;另一个版本接收一个字符和次数,输出指定长度的一行。两个函数都没有返回值。
**输入格式:** 依次输入一个整数、一个字符和字符的重复次数。
**输出格式:** 第一行输出 5 个 `x`;第二行输出 `n` 个字符 `c`
**数据范围:** `0≤x≤9``1≤n≤50`
**输入样例:**
```text
7 * 4
```
**输出样例:**
```text
77777
****
```
**解题思路:** 两个函数同名但参数列表不同,构成函数重载;只负责输出,因此返回类型为 `void`
**易错提醒:** 重载依据参数数量或类型区分,不能只靠返回类型区分。
---
### 课堂4全班累计加分【适中】
**题目描述:** 全局变量 `total` 表示全班累计加分。编写 `addScore(x)` 修改它;函数内部再定义同名局部变量 `total`,演示局部变量优先。每次调用后输出全局累计值。
**输入格式:** 第一行输入加分次数;第二行输入每次的加分值。
**输出格式:** 输出每次加分后的全局累计值。
**数据范围:** `1≤n≤20`,每次加分 `110`
**输入样例:**
```text
3
2 5 1
```
**输出样例:**
```text
2 7 8
```
**解题思路:**`::total` 指明全局变量;局部变量仅在函数的大括号内有效。
**易错提醒:** 局部变量会遮住同名全局变量;实际编程应尽量避免故意重名,本题只用于理解作用域。
---
## 课后作业
1.
2.

View File

@@ -0,0 +1,171 @@
# 第3课自定义函数教学拓展
> 等级说明:自定义函数属于 GESP 四级知识。本课程保留这一课,是为了训练代码分解和复用能力,不作为一级、二级考试必考内容。
## 教学目标
- 掌握函数的定义、调用与声明
- 理解形参与实参、返回值、void函数
- 理解函数重载(同名不同参)
- 理解局部变量与全局变量的作用域
## 核心考点:函数的参数、返回值与调用过程
### 考什么
常见题型包括补全函数定义、判断形参与实参、根据调用过程求输出,以及把一段重复逻辑封装成函数。重点是看懂数据怎样进入函数、函数怎样处理、结果怎样返回主程序。
### 必须理解
定义函数时括号里的变量叫形参,是函数内部接收数据的“临时名字”;调用函数时传入的值叫实参。普通值传递会把实参复制给形参,函数内部修改形参通常不会改变外面的变量。执行到 `return` 时,函数立即结束并把结果交回调用位置;`void` 函数不返回结果。局部变量只在所属的大括号内有效,不同函数中的同名局部变量互不影响。
### 核心写法
```cpp
int maxValue(int a, int b) { // a、b 是形参
if (a > b) return a;
return b;
}
int answer = maxValue(x, y); // x、y 是实参
```
调用时先计算实参,再进入函数执行;遇到 `return` 后回到原调用位置,把返回值赋给 `answer`
### 常见错误
- 声明的返回类型是 `int`,却忘记写 `return`
- 把函数定义写进 `main` 内部C++ 不允许在函数内部再定义普通函数。
- 以为修改普通形参就会修改实参,混淆“值的复制”和“原变量”。
- 在变量作用域之外使用局部变量。
### 判断是否掌握
能画出“实参传入→形参接收→函数执行→return 返回→主程序继续”的过程,并能独立编写一个有参数、有返回值的判断或计算函数。
---
## 题目一:判断素数
### 核心代码(一句话)
> 判断素数的核心:**从 2 试到 √n只要有一个能整除就不是素数。**
### 自然语言思路
素数就是"除了1和自己不能被任何数整除"的数。
比如判断 17 是不是素数:
- 试 217 ÷ 2 = 8 余 1不行
- 试 317 ÷ 3 = 5 余 2不行
- 试 417 ÷ 4 = 4 余 1不行
- 试到 √17 ≈ 4.1,所以只需试到 4
- 全都不行 → 17 是素数!
为什么只试到 **√n**?因为如果 n 有一个大于 √n 的因数,那一定会有一个小于 √n 的因数跟它配对。比如 36 = 6 × 6一个因数大于 6另一个必然小于 6。所以试到 √n 就够了。
**为什么要写成函数?** 判断素数的代码以后经常用到,封装成 `isPrime(n)`,以后需要时一行调用,不用重复写。
### 伪代码
```
函数 isPrime(n):
如果 n < 2: 返回 false // 1 不是素数
对于 i 从 2 到 sqrt(n):
如果 n % i == 0: // 能被整除
返回 false // 不是素数,直接返回
返回 true // 全都试过了,是素数
主程序:
输入 m
如果 isPrime(m): 输出 "YES"
否则: 输出 "NO"
```
### 真实代码
```cpp
#include <iostream>
#include <cmath>
using namespace std;
// 函数定义:判断素数
bool isPrime(int n) {
if (n < 2) return false; // 1 和负数不是素数
for (int i = 2; i <= sqrt(n); i++) {
if (n % i == 0) return false; // 找到因数,不是素数
}
return true; // 全部试完,是素数
}
int main() {
int m;
cin >> m;
if (isPrime(m)) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
return 0;
}
```
### 注意点
1. **1 不是素数**:必须在函数开头处理 `n < 2` 的情况,这是 GESP 高频易错点。
2. **为什么 `i <= sqrt(n)` 而不是 `i < n`**:前者效率高很多。判断 100 是否是素数,试到 10 就够了,不用试到 99。
3. **`sqrt` 返回 double**`i <= sqrt(n)` 是 int 和 double 比较C++ 会自动转换,没问题。但如果担心精度,可以写成 `i * i <= n`
4. **函数名要有意义**`isPrime` 一看就知道是"判断素数",比 `f``check` 好得多。
5. **局部变量 i**i 只在 isPrime 函数里有效main 里访问不到它 —— 这就是**局部变量**的作用域。
---
## 题目二:(待橙子补充)
> 建议覆盖函数重载、void函数等
---
### 核心代码(一句话)
>
### 自然语言思路
### 伪代码
### 真实代码
```cpp
```
### 注意点
---
## 题目三:(待橙子补充)
> 建议覆盖:局部变量与全局变量对比
---
### 核心代码(一句话)
>
### 自然语言思路
### 伪代码
### 真实代码
```cpp
```
### 注意点
---
## 本课打油诗
> 函数写好存身旁,起名调用响当当。
> 参数传进返结果,少写重复心不慌。
---
## 课后作业
(待补充)
1.
2.

View File

@@ -0,0 +1,687 @@
# 第2课编程题教师版
## 本课知识点覆盖表
| 核心知识点 | GESP等级 | 对应课堂题 |
|---|---|---|
| 函数声明、定义与调用 | 二级必考 | 课堂1 |
| 形参与实参、值传递、返回值、提前 `return` | 二级必考 | 课堂2 |
| `void` 函数与函数重载 | 二级 | 课堂3 |
| 局部变量、全局变量与作用域 | 二级必考 | 课堂4 |
> 难度均为本课内部相对难度,参考程序采用 C++17。
## 一、课堂练习4题
### 课堂1三个数的最大公约数【适中】
**题目描述:** 编写函数 `gcd(a,b)`,使用辗转相除法求两个正整数的最大公约数。在 `main` 前写函数声明、`main` 后写函数定义,连续调用两次求三个数的最大公约数。
**输入格式:** 输入三个正整数。
**输出格式:** 三个数的最大公约数。
**数据范围:** `1≤a,b,c≤10^9`
**输入样例:**
```text
12 18 30
```
**输出样例:**
```text
6
```
**解题思路:** 先求 `gcd(a,b)`,再把结果与 `c` 求最大公约数,体会函数声明、定义和多次调用。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int gcd(int a, int b);
int main() {
int a, b, c;
cin >> a >> b >> c;
cout << gcd(gcd(a, b), c);
return 0;
}
int gcd(int a, int b) {
while (b != 0) {
int r = a % b;
a = b;
b = r;
}
return a;
}
```
**易错提醒:** 函数声明必须与定义的返回类型、函数名和参数列表一致。
### 课堂2统计区间内的素数【适中】
**题目描述:** 编写有参数、有返回值的函数 `isPrime(n)`。输入 `L``R`,输出闭区间 `[L,R]` 中素数的个数。
**输入格式:** 输入区间的左端点和右端点。
**输出格式:** 素数个数。
**数据范围:** `1≤L≤R≤10000`
**输入样例:**
```text
10 20
```
**输出样例:**
```text
4
```
**解题思路:** 每个实参传入函数后复制给形参 `n`;找到因数时提前返回 `false`,全部检查后返回 `true`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
int main() {
int L, R, count = 0;
cin >> L >> R;
for (int x = L; x <= R; x++) {
if (isPrime(x)) count++;
}
cout << count;
return 0;
}
```
**易错提醒:** 1 不是素数;普通值传递不会修改主函数中的实参。
### 课堂3统一格式输出【适中】
**题目描述:** 重载函数 `printLine`:一个版本接收一个整数并输出由该整数重复 5 次组成的一行;另一个版本接收一个字符和次数,输出指定长度的一行。两个函数都没有返回值。
**输入格式:** 依次输入一个整数、一个字符和字符的重复次数。
**输出格式:** 第一行输出 5 个 `x`;第二行输出 `n` 个字符 `c`
**数据范围:** `0≤x≤9``1≤n≤50`
**输入样例:**
```text
7 * 4
```
**输出样例:**
```text
77777
****
```
**解题思路:** 两个函数同名但参数列表不同,构成函数重载;只负责输出,因此返回类型为 `void`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
void printLine(int x) {
for (int i = 0; i < 5; i++) cout << x;
cout << "\n";
}
void printLine(char c, int n) {
for (int i = 0; i < n; i++) cout << c;
cout << "\n";
}
int main() {
int x, n;
char c;
cin >> x >> c >> n;
printLine(x);
printLine(c, n);
return 0;
}
```
**易错提醒:** 重载依据参数数量或类型区分,不能只靠返回类型区分。
### 课堂4全班累计加分【适中】
**题目描述:** 全局变量 `total` 表示全班累计加分。编写 `addScore(x)` 修改它;函数内部再定义同名局部变量 `total`,演示局部变量优先。每次调用后输出全局累计值。
**输入格式:** 第一行输入加分次数;第二行输入每次的加分值。
**输出格式:** 输出每次加分后的全局累计值。
**数据范围:** `1≤n≤20`,每次加分 `110`
**输入样例:**
```text
3
2 5 1
```
**输出样例:**
```text
2 7 8
```
**解题思路:**`::total` 指明全局变量;局部变量仅在函数的大括号内有效。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int total = 0;
void addScore(int x) {
int total = x;
::total += total;
}
int main() {
int n, x;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> x;
addScore(x);
if (i) cout << " ";
cout << total;
}
return 0;
}
```
**易错提醒:** 局部变量会遮住同名全局变量;实际编程应尽量避免故意重名,本题只用于理解作用域。
## 二、课后作业3题
### 课后1求长方形面积【简单】
**题目描述:** 编写函数 `area(a,b)` 返回长方形面积。
**输入格式:** 输入两个正整数。
**输出格式:** 长方形面积。
**数据范围:** `1≤a,b≤10000`
**输入样例:**
```text
4 6
```
**输出样例:**
```text
24
```
**解题思路:** 函数接收两个形参,返回它们的乘积。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int area(int a, int b) {
return a * b;
}
int main() {
int a, b;
cin >> a >> b;
cout << area(a, b);
return 0;
}
```
**易错提醒:** 有返回值的函数不能漏写 `return`
### 课后2判断偶数【简单】
**题目描述:** 编写布尔函数 `isEven(n)`,偶数返回 `true`,奇数返回 `false`。主函数据此输出 `YES``NO`
**输入格式:** 输入一个整数。
**输出格式:** `YES``NO`
**数据范围:** `-10^9≤n≤10^9`
**输入样例:**
```text
18
```
**输出样例:**
```text
YES
```
**解题思路:** 判断 `n%2==0` 并直接返回结果。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
bool isEven(int n) {
return n % 2 == 0;
}
int main() {
int n;
cin >> n;
cout << (isEven(n) ? "YES" : "NO");
return 0;
}
```
**易错提醒:** 判断相等使用 `==`,不是赋值符号 `=`
### 课后3打印分隔线【简单】
**题目描述:** 编写无返回值函数 `printLine(n)`,输出 `n` 个减号。
**输入格式:** 输入一个整数。
**输出格式:** 一行 `n``-`
**数据范围:** `1≤n≤100`
**输入样例:**
```text
5
```
**输出样例:**
```text
-----
```
**解题思路:** 输出操作封装进 `void` 函数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
void printLine(int n) {
for (int i = 0; i < n; i++) cout << "-";
}
int main() {
int n;
cin >> n;
printLine(n);
return 0;
}
```
**易错提醒:** `void` 函数不能把调用结果赋给变量。
## 三、扩展练习7题
### 扩展1绝对值函数【基础综合】
**题目描述:** 不使用标准库 `abs`,编写函数 `myAbs(n)` 返回整数的绝对值。
**输入格式:** 输入一个整数。
**输出格式:** `n` 的绝对值。
**数据范围:** `-10^9≤n≤10^9`
**输入样例:**
```text
-23
```
**输出样例:**
```text
23
```
**解题思路:** 负数返回 `-n`,否则返回 `n`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int myAbs(int n) {
if (n < 0) return -n;
return n;
}
int main() {
int n;
cin >> n;
cout << myAbs(n);
return 0;
}
```
**易错提醒:** 每条可能的执行路线都应有返回值。
### 扩展2数字各位和【基础综合】
**题目描述:** 编写函数 `digitSum(n)`,返回正整数各位数字之和。
**输入格式:** 输入一个正整数。
**输出格式:** 各位数字之和。
**数据范围:** `1≤n≤10^9`
**输入样例:**
```text
5026
```
**输出样例:**
```text
13
```
**解题思路:** 在函数内循环取个位并累加。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int digitSum(int n) {
int sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
return sum;
}
int main() {
int n;
cin >> n;
cout << digitSum(n);
return 0;
}
```
**易错提醒:** 函数内修改形参 `n` 不会改变主函数中的实参。
### 扩展3最小公倍数【中等】
**题目描述:** 编写 `gcd(a,b)``lcm(a,b)` 两个函数,输出两数的最小公倍数。
**输入格式:** 输入两个正整数。
**输出格式:** 最小公倍数。
**数据范围:** `1≤a,b≤10^6`,答案不超过 `10^9`
**输入样例:**
```text
12 18
```
**输出样例:**
```text
36
```
**解题思路:** 先求最大公约数,再利用 `lcm=a/gcd*b`,体现函数之间相互调用。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int gcd(int a, int b) {
while (b != 0) {
int r = a % b;
a = b;
b = r;
}
return a;
}
long long lcm(int a, int b) {
return 1LL * a / gcd(a, b) * b;
}
int main() {
int a, b;
cin >> a >> b;
cout << lcm(a, b);
return 0;
}
```
**易错提醒:** 先除后乘并使用 `long long`,可降低中间结果溢出的风险。
### 扩展4重载求面积【中等】
**题目描述:** 重载函数 `area`:接收一个整数时返回正方形面积;接收两个整数时返回长方形面积。输入类型编号及边长后输出面积。
**输入格式:** 先输入图形类型;类型为 1 时再输入一个边长,类型为 2 时再输入长和宽。
**输出格式:** 图形面积。
**数据范围:** 边长为 `110000`
**输入样例:**
```text
2 4 7
```
**输出样例:**
```text
28
```
**解题思路:** 用不同参数个数区分两个同名函数。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int area(int side) {
return side * side;
}
int area(int length, int width) {
return length * width;
}
int main() {
int type, a, b;
cin >> type >> a;
if (type == 1) cout << area(a);
else {
cin >> b;
cout << area(a, b);
}
return 0;
}
```
**易错提醒:** 两个重载版本不能拥有完全相同的参数列表。
### 扩展5回文数函数【中等】
**题目描述:** 编写函数 `isPalindrome(n)` 判断正整数是否为回文数,输出 `YES``NO`
**输入格式:** 输入一个正整数。
**输出格式:** `YES``NO`
**数据范围:** `1≤n≤10^9`
**输入样例:**
```text
12321
```
**输出样例:**
```text
YES
```
**解题思路:** 在函数中备份原数并构造反转数,最后比较。
**参考代码:**
```cpp
#include <iostream>
using namespace std;
bool isPalindrome(int n) {
int original = n;
int reversed = 0;
while (n > 0) {
reversed = reversed * 10 + n % 10;
n /= 10;
}
return reversed == original;
}
int main() {
int n;
cin >> n;
cout << (isPalindrome(n) ? "YES" : "NO");
return 0;
}
```
**易错提醒:** 必须备份原数;形参被拆完后会变成 0。
### 扩展6连续素数对【略难】
**题目描述:** 两个相差 2 的素数称为孪生素数。编写 `isPrime`,输出 `[2,n]` 中所有孪生素数对,每对占一行;若没有,输出 `NONE`
**输入格式:** 输入一个整数。
**输出格式:** 所有满足条件的 `p p+2`
**数据范围:** `2≤n≤100000`
**输入样例:**
```text
15
```
**输出样例:**
```text
3 5
5 7
11 13
```
**解题思路:** 枚举较小的数 `p`,同时调用两次 `isPrime`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
int main() {
int n;
bool found = false;
cin >> n;
for (int p = 2; p + 2 <= n; p++) {
if (isPrime(p) && isPrime(p + 2)) {
cout << p << " " << p + 2 << "\n";
found = true;
}
}
if (!found) cout << "NONE";
return 0;
}
```
**易错提醒:** 枚举上界必须保证 `p+2≤n`;无解情况不能漏掉。
### 扩展7函数流水线【略难】
**题目描述:** 编写三个函数:`digitSum(n)` 求各位和,`isPrime(n)` 判断素数,`countGood(L,R)` 统计区间中“各位和为素数”的整数个数。
**输入格式:** 输入区间的左端点和右端点。
**输出格式:** 满足条件的整数个数。
**数据范围:** `1≤L≤R≤100000`
**输入样例:**
```text
10 15
```
**输出样例:**
```text
3
```
**解题思路:** 1015 的各位和依次为 16其中 2、3、5 是素数。让 `countGood` 调用 `digitSum`,再把结果交给 `isPrime`
**参考代码:**
```cpp
#include <iostream>
using namespace std;
int digitSum(int n) {
int sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
return sum;
}
bool isPrime(int n) {
if (n < 2) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}
int countGood(int L, int R) {
int count = 0;
for (int x = L; x <= R; x++) {
if (isPrime(digitSum(x))) count++;
}
return count;
}
int main() {
int L, R;
cin >> L >> R;
cout << countGood(L, R);
return 0;
}
```
**易错提醒:** 分清每个函数的单一职责;不要在多个位置重复编写同一段逻辑。