函数与模块化思维
一、课上练习
编程练习
二、知识总结
✨ 函数的核心思想
函数的本质是教计算机做一件事,也就是把一组指令进行捆绑,并且给这组指令起一个名字。函数可以有输入和输出,增加了函数的普适性。
函数思维是一种模块化的思维方式,帮助我们把一个功能独立出来,增加了代码的复用性,并且大大简化了代码编写。
✨ 函数的创建
函数的创建包括函数声明和函数定义。
函数声明
函数的声明只需要写明函数的返回值类型、函数名和参数即可,要写在调用和定义之前,一般把函数声明写在主程序之前。如果在声明函数的时候将函数的定义补全,则无需再单独写函数的定义。
函数的声明必须包括:
- 函数的返回值类型
- 函数的名称(可以是空)
- 函数的参数(可以没有)
// 函数的声明
返回值类型 函数名(参数1类型 参数1,参数2类型 参数2, ……);int my_max(int a, int b);函数定义
函数定义需要写明函数的具体执行内容。如果在函数声明中写明了函数的具体执行内容,则不需要再写函数的定义。
函数的定义包括:
- 函数的声明,即函数的返回值类型、函数的名称和函数的参数
- 函数的主体内容
- 返回语句(void函数可以没有返回语句)
1// 函数的声明
2返回值类型 函数名(参数1类型 参数1,参数2类型 参数2, ……);
3
4
5// 函数的定义
6返回值类型 函数名(参数1类型 参数1,参数2类型 参数2, ……) {
7 函数具体执行的代码
8
9 return 返回值
10}1// 函数声明
2int my_max(int a, int b);
3
4// 函数定义
5int my_max(int a, int b) {
6 if (a > b) {
7 return a;
8 }
9 return b;
10}✨ 函数的注意事项
1. 函数返回值类型
函数的返回值类型可以是:
- 基础数据类型:整数型、浮点型、字符型、布尔型
- 复杂的数据类型:string等复杂数据
- 空(void):即过程类函数,不需要返回值
2. void函数
函数可以没有返回值,没有返回值的函数请用void表示。
3. 函数名
函数的命名规则与变量相同。一般情况下函数不能重名,除非是完成同一功能且参数不同(参数数量不同、参数类型不同)时才可以重名,此时这些函数被称为对某个函数名称的重载。
4. 返回语句
函数使用return语句进行返回,一旦运行return语句后,return语句后面的代码将不再执行。
void函数写return语句时,直接写return;即可,同时void函数可以不写return语句,如果不写return语句编译器会自动补全一句return;。
✨ 函数调用
调用函数时可直接使用函数名加括号的形式进行调用,对于有参数的函数,需要在括号内填写上实际的参数。
1#include <bits/stdc++.h>
2using namespace std;
3
4// 函数声明
5int my_max(int a, int b);
6
7int main() {
8 int num1, num2;
9 cin >> num1 >> num2;
10 int max_number = my_max(num1, num2);
11 cout << max_number << endl;
12}
13
14// 函数定义
15int my_max(int a, int b) {
16 if (a > b) {
17 return a;
18 }
19 return b;
20}✨ 函数重载
如果几个函数的函数名称相同,但是参数不同(参数数量不同、参数类型不同),那么这些函数被称为对某个函数名称的重载。
以下两个函数都是返回两个数中较小的那个数,但是第一个函数是用来比较int型数据的,而第二个函数则是用来比较double型数据的。
1#include <iostream>
2//#include <bits/stdc++.h>
3using namespace std;
4
5double min(int num1, int num2);
6double min(double num1, double num2);
7
8int main() {
9 cout << min(2, 4) << endl;
10 cout << 3 / min(2, 4) << endl;
11 cout << min(0.2, 0.8) << endl;
12// cout << min(0.2, 2) << endl;
13 return 0;
14}
15
16double min(int num1, int num2) {
17 if (num1 < num2) {
18 return num1;
19 }
20 return double(num2);
21}
22
23double min(double num1, double num2) {
24 if (num1 < num2) {
25 return num1;
26 }
27 return num2;
28}✨ 函数调用的执行示例
函数调用的完整流程可以用下图表示:
下面以"纯粹合数"问题为例,演示函数是如何被调用和返回的。
问题:判断一个数的每一位数字是否都是合数(4、6、8、9),如果是则称为纯粹合数。
1// 判断一个数字是否是合数位(4、6、8、9)
2bool isCompositeDigit(int d) {
3 return d == 4 || d == 6 || d == 8 || d == 9;
4}
5
6// 判断一个数是否是纯粹合数
7bool isPureComposite(int n) {
8 while (n > 0) {
9 int d = n % 10;
10 if (!isCompositeDigit(d)) {
11 return false;
12 }
13 n = n / 10;
14 }
15 return true;
16}执行过程(n = 468):
| 步骤 | 函数 | n | d | isCompositeDigit(d) | 动作 |
|---|---|---|---|---|---|
| 1 | isPureComposite | 468 | 8 | 调用 → true | 继续 |
| 2 | isPureComposite | 46 | 6 | 调用 → true | 继续 |
| 3 | isPureComposite | 4 | 4 | 调用 → true | 继续 |
| 4 | isPureComposite | 0 | - | - | 循环结束,return true |
执行过程(n = 462):
| 步骤 | 函数 | n | d | isCompositeDigit(d) | 动作 |
|---|---|---|---|---|---|
| 1 | isPureComposite | 462 | 2 | 调用 → false | return false(提前结束) |
关键理解: 函数调用就像"跳转"——主程序跳到函数中执行,函数执行完后跳回主程序继续。每次调用函数时,参数会被赋予新的值,函数内部的变量不会影响外部。
✨ 思考过程:何时应该使用函数
信号1:同一段代码出现了多次。 如果你发现自己在复制粘贴代码,说明应该把这段代码提取成一个函数。
信号2:逻辑可以独立。 例如"判断是否为素数"、"求最大公约数"、"交换两个数"等操作,逻辑完整且独立,适合封装成函数。
信号3:代码太长难以阅读。 当 main 函数超过 50 行时,考虑将其中的逻辑拆分成多个函数,每个函数完成一个明确的任务。
设计函数的步骤:
- 明确功能:这个函数要做什么?用一句话描述。
- 确定输入:需要哪些参数?类型是什么?
- 确定输出:返回值是什么类型?void 还是有返回值?
- 实现逻辑:编写函数体。
- 测试:用几组数据验证函数是否正确。
✨ 函数的常见错误
- 函数声明和定义的参数不一致:声明时写了
int mymax(int a, int b);,定义时写成int mymax(int a, double b) {...},参数类型不同会导致编译器认为这是两个不同的函数。 - 忘记写 return 语句:非 void 函数如果没有 return 语句,编译器可能只给出警告而不报错,但返回值是未定义的,会导致难以排查的 bug。
- 在函数外使用函数内的变量:函数内定义的变量是局部变量,只在函数内部有效。函数执行结束后,局部变量就销毁了。如果需要把结果传出来,应该通过 return 返回。
- 函数调用时参数顺序写反:例如
strcpy(src, dst)写成了strcpy(dst, src)(虽然这个例子中 strcpy 的参数顺序本身就是 dst 在前),在自定义函数中更容易犯这种错误。 - 递归调用忘记终止条件:虽然本节不涉及递归,但如果函数直接或间接调用自身而没有终止条件,程序会无限循环直到栈溢出崩溃。
三、课后练习
基础知识练习
- 函数与模块化思维 - 选择题## 编程练习