C++启蒙课程:第7章  函数——C++的编程模块

我们将掌握编程的“模块化构建术”——函数!它能帮我们把复杂的任务分解成一个个清晰、易于管理的小模块。

🏭

函数工厂:定义与原型

学习“任务分队”`void`和`return`,以及“数据快递员”按值传递。

🛡️

数组特工队:指针与const

掌握“数据阵列的指挥官”如何传递地址,并用`const`封条保护数据。

🌀

高级魔法:递归与指针

探索“无限嵌套的镜像”`递归`和“地址传递的信使”`函数指针`。

课时一:函数工厂:定义与原型

(覆盖知识点 7.1.1 - 7.2.2)

7.1.1 “任务分队” (定义)

`void` 分队只执行任务(如打印)。有返回值 (如 `double`) 的分队会返回一个“结果包裹”。

控制台 (void):
结果包裹 (return):
// void 分队 (无返回值) void cheers() { cout << "Go Team!"; } // double 分队 (返回一个 double) double cube(double x) { return x * x * x; }

7.1.2 “ID卡” (原型)

C++中,你必须在 `main` 函数“之前”提交函数的“ID卡”(原型),否则编译器不认识它。

// ID 卡 / 原型 double cube(double x); int main() { double val = cube(2.0); // OK! return 0; } // 函数定义 (可以放在 main 后面) double cube(double x) { ... }

7.2 “数据快递员” (按值传递)

C++默认“按值传递”。快递员把你的数据“复印”一份,函数在“副本”上操作,你的“原件”安然无恙。

💡 互动体验:

main (原件):
val = 10
no_change (副本):
x = ?

📜 代码支撑:

void no_change(int x) // x 是一个副本 { // 2. 修改副本 x = 99; // x 变为 99 } int main() { int val = 10; // 1. 发送 val (10) 的副本 no_change(val); // val 仍然是 10!原件未变 cout << val; // 输出 10 return 0; }

课时二:数组特工队:指针与const保护

(覆盖知识点 7.3.1 - 7.4)

7.3.1 “数据阵列的指挥官”

传递数组时,传递的是“地址”(指挥官)。函数拿到地址后,可以直接修改**原始数组**。

main (原始数组):
10
20
30
// arr[] 实际上是 int* arr void modify_arr(int arr[], int n) { // 修改的是原始数组! arr[0] = 99; } int main() { int data[] = {10, 20, 30}; modify_arr(data, 3); // data[0] 现在是 99 }

7.3.5 “const 只读封条”

如果不希望函数修改数组,请贴上 `const` 封条。`const` 保护原始数据不被意外更改。

🛡️
// const 保证函数不会修改 arr double average(const double arr[], int n) { // arr[0] = 1.0; // 错误! // 编译器会阻止你修改 ... }

7.3.3 数组区间

一种更灵活的方式是传递两个指针:一个指向“起始”,一个指向“结尾之后”。

int data[] = {10, 20, 30, 40, 50}; // 指向 data[1] (20) int* begin = data + 1; // 指向 data[4] (50) 之后 int* end = data + 4; // 函数可以处理从 20 到 40 的区间 // void process(begin, end);

7.4 二维数组

传递二维数组时,C++要求你必须指定“列数”(除了第一维之外的所有维度)。

// 3 是行数 (可以省略) // 4 是列数 (必须指定!) void show2D(int arr[][4], int rows) { for (int r = 0; r < rows; r++) { for (int c = 0; c < 4; c++) { cout << arr[r][c]; } } }

课时三:灵活的特派员:字符串与结构

(覆盖知识点 7.5.1 - 7.8)

7.5.1 C-风格字符串与 👻

传递 C-字符串 (`char*`) 时,函数不知道它的长度。它只能一直读,直到撞见“空值幽灵” `\0`。

int count_char(const char* str) { int count = 0; // 循环直到 *str 指向 '\0' while (*str != '\0') { count++; str++; // 指针移到下一个 } return count; }

7.6 “结构包裹”

结构 `struct` 像基本类型一样,可以被“按值传递”(复制)和“返回”。

struct Boss { string name; int age; }; // 函数返回一个结构 "包裹" Boss create_boss(string n, int a) { Boss new_boss; new_boss.name = n; new_boss.age = a; return new_boss; // 返回副本 } // 也可以按值传递 (Boss b) // 或按地址传递 (const Boss* pb)

课时四:高级魔法:递归与函数指针

(覆盖知识点 7.9.1 - 7.10.4)

7.9 “无限嵌套的镜像” (递归)

递归是函数调用“自己”。它必须有一个“基线条件”来停止,否则将无限嵌套!

void countdown(int n) { if (n <= 0) { // 基线条件: 停止! cout << "Blastoff!\n"; return; } cout << n << "...\n"; countdown(n - 1); // 递归调用 }

7.10 “地址传递的信使”

函数指针是一个“信使”,你告诉它要去哪个函数(地址),它就能帮你调用那个函数。

1. 指派信使 (pf) 去:
int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; } // typedef 简化声明 typedef int (*PFUNC)(int, int); int main() { PFUNC pf; // 声明信使 pf = add; // 指派去 add pf(10, 5); // 调用 (结果 15) pf = mul; // 指派去 mul pf(10, 5); // 调用 (结果 50) }

编程实践与作业 (7.13)

是时候检验你作为“模块化构建师”的实力了!

练习 1:温度转换 (7.13 练习 5)

任务:

编写一个有返回值的函数 `ctof`,接受一个 `double` 类型的摄氏温度值,返回一个 `double` 类型的华氏温度值。

点击查看参考答案
#include <iostream> using namespace std; // 7.1.2 函数原型 double ctof(double c_temp); int main() { double c = 25.0; double f = ctof(c); cout << c << "C = " << f << "F" << endl; return 0; } // 7.1.1 函数定义 double ctof(double c_temp) { return 1.8 * c_temp + 32.0; }
练习 2:const 保护的数组平均值 (7.13 练习 5 简化)

任务:

编写一个函数 `average`,计算 `double` 数组的平均值。函数必须使用 `const` 保护数组不被修改。

点击查看参考答案
#include <iostream> // 7.3.2 const 保护数组 double average(const double arr[], int n); int main() { double data[] = {10.0, 20.0, 30.0, 40.0}; int size = 4; std::cout << "平均值: " << average(data, size) << std::endl; return 0; } double average(const double arr[], int n) { double sum = 0.0; for (int i = 0; i < n; i++) { sum += arr[i]; } return sum / n; }
练习 3:C-字符串字符计数 (7.13 练习 8 简化)

任务:

编写一个函数 `count_char`,接受一个 C-风格字符串 (const char *) 和一个 `char`,统计该字符在字符串中出现的次数。

点击查看参考答案
#include <iostream> using namespace std; int count_char(const char * str, char ch); int main() { const char * message = "banana"; cout << "'a' 出现了 " << count_char(message, 'a') << " 次。" << endl; return 0; } int count_char(const char * str, char ch) { int count = 0; // 7.5.1 循环直到遇到空值字符 '\0' while (*str != '\0') { if (*str == ch) { count++; } str++; // 指针递增 } return count; }
练习 4:函数指针 (7.13 练习 11)

任务:

编写一个函数 `judge()`,它接受另一个函数的地址作为参数。该被调用函数接受 const char* 参数并返回 `int` 值 (例如,计算字符串长度)。

点击查看参考答案
#include <iostream> using namespace std; // 7.10.4 使用 typedef 简化 typedef int (*PC_FUNC)(const char *); int judge(PC_FUNC p, const char * str); int str_length(const char * str); // 辅助函数 int main() { const char * input_str = "C++ Expert"; PC_FUNC p_len = str_length; // 7.10.2 初始化指针 // 7.10.3 传递函数指针 int result = judge(p_len, input_str); cout << "字符串长度: " << result << endl; return 0; } // judge 函数定义 int judge(PC_FUNC p, const char * str) { // 7.10.3 使用指针调用函数 return p(str); } // 辅助函数定义 int str_length(const char * str) { int len = 0; while (*str++) len++; return len; }

本章知识点总结与复习

核心概念 解释/功能 关键用法/示例
函数定义/原型原型描述接口,C++中必不可少。int func(int x);
按值传递传递参数的副本,保护原始数据。func(val);
函数与数组传递数组名即传递**指针/地址**。void func(int arr[], int n);
`const`参数保护原始数据不被函数修改。void func(const int * arr);
C-字符串传递 `char *` 地址,通过 `\0` 识别结尾。int count(const char * str);
函数与结构可按值传递、返回。大结构应传递地址。Boss func(Boss b);
递归函数调用自身,必须有终止条件。void f(int n) { f(n-1); }
函数指针存储函数地址的变量,可作为参数传递。double (*pf)(int);