← 目录 / C++ 编程语法
函数是将特定功能的代码打包封装、起名复用的机制。好的函数让程序结构清晰,有效避免重复代码。本章还将介绍递归这一强大技术。
8.1
函数是对实现某一功能的代码进行模块化封装。定义格式如下:
| 组成部分 | 说明 | 是否必须 |
|---|---|---|
| 返回值类型 | 函数执行完毕后返回的数据类型,如 int、double、string 等 | ✅ 必需(无返回值写 void) |
| 函数名 | 调用函数时使用的名字,命名规则同变量名 | ✅ 必需 |
| 参数列表 | 调用时传入的数据,需指定类型和名称,多个参数用逗号分隔 | 可选(无参数时留空) |
| 函数体 | 花括号 {} 内要执行的具体代码 | ✅ 必需 |
| return 语句 | 返回计算结果并结束函数;返回值类型为 void 时可省略 | 视返回类型而定 |
| 1 | // 示例1:计算两数之和(有返回值) |
| 2 | int Add(int a, int b) // 返回类型 int,两个 int 参数 |
| 3 | { |
| 4 | int result = a + b; |
| 5 | return result; // 返回计算结果 |
| 6 | } |
| 7 | |
| 8 | // 示例2:打印问候语(无返回值) |
| 9 | void Greet(string name) // 返回类型 void,表示不返回值 |
| 10 | { |
| 11 | cout << "Hello, " << name << "!" << endl; |
| 12 | // 无需 return |
| 13 | } |
Add、GetMax、PrintResult)。多个参数即使类型相同也不能省略类型声明,必须写 int a, int b,不能写 int a, b。8.2
调用函数就是使用函数的功能来执行代码或获取返回值。在函数名后跟圆括号,括号内传入实参,多个参数用逗号分隔。
| 1 | int Add(int a, int b) // a、b 是形参(定义处) |
| 2 | { return a + b; } |
| 3 | |
| 4 | int main() |
| 5 | { |
| 6 | int x = 3, y = 5; |
| 7 | int sum = Add(x, y); // x、y 是实参(变量) |
| 8 | Add(10, 20); // 10、20 也是实参(字面量) |
| 9 | return 0; |
| 10 | } |
8.3
C++ 要求函数必须先定义(或声明)后调用。如果函数定义在 main() 之后,需要在前面写函数声明(也叫函数原型),提前告诉编译器这个函数的存在。
| 概念 | 格式 | 作用 |
|---|---|---|
| 函数声明(原型) | 返回类型 函数名(参数列表); | 告诉编译器函数"长什么样",没有具体实现代码 |
| 函数定义(实现) | 返回类型 函数名(参数列表) { 函数体 } | 给出函数的完整实现代码 |
| 1 | #include <iostream> |
| 2 | using namespace std; |
| 3 | |
| 4 | int Add(int a, int b); // ← 函数声明(只写函数头,不写函数体) |
| 5 | // 参数名可省略:int Add(int, int); |
| 6 | |
| 7 | int main() |
| 8 | { |
| 9 | cout << Add(3, 5) << endl; // 正常调用 |
| 10 | return 0; |
| 11 | } |
| 12 | |
| 13 | int Add(int a, int b) // ← 函数定义(在 main 之后也没问题) |
| 14 | { |
| 15 | return a + b; |
| 16 | } |
8.4
C++ 中函数参数共有三种传递方式:值传递、引用传递和指针传递。三者的核心区别在于:函数内对参数的操作,是否能影响到调用方的原始变量。
int xf(a)int &xf(a)(同值传递,自动绑定)* 修改原变量。int *xf(&a)(需取地址)最常用的默认方式。调用时将实参的值复制一份传给形参,两者互不干扰。适合传递小型数据(int、double、char 等)且不需要修改原值的场景。
| 1 | void TryDouble(int x) // x 是副本 |
| 2 | { |
| 3 | x = x * 2; // 只改了副本 |
| 4 | cout << "函数内 x = " << x; // 10 |
| 5 | } |
| 6 | |
| 7 | int main() |
| 8 | { |
| 9 | int a = 5; |
| 10 | TryDouble(a); // 传入 a 的副本 |
| 11 | cout << "函数外 a = " << a; // 仍是 5,原变量未变 |
| 12 | return 0; |
| 13 | } |
int 这样的小型类型开销极小;但若传递大型结构体或 string,拷贝成本较高,此时应改用引用传递。在参数类型后加 &,函数接收的是原变量的别名——形参和实参指向同一块内存,对形参的任何操作都直接作用于原变量。调用时写法和值传递完全相同,& 只写在函数定义处。
| 1 | void RealDouble(int &x) // & 表示引用,x 就是原变量的别名 |
| 2 | { |
| 3 | x = x * 2; // 直接修改原变量 |
| 4 | cout << "函数内 x = " << x; // 10 |
| 5 | } |
| 6 | |
| 7 | // 经典用途:交换两个变量 |
| 8 | void Swap(int &a, int &b) |
| 9 | { |
| 10 | int tmp = a; |
| 11 | a = b; b = tmp; |
| 12 | } |
| 13 | |
| 14 | int main() |
| 15 | { |
| 16 | int a = 5; |
| 17 | RealDouble(a); // 调用写法与值传递相同 |
| 18 | cout << "函数外 a = " << a; // 10,原变量已修改 |
| 19 | |
| 20 | int x = 3, y = 7; |
| 21 | Swap(x, y); |
| 22 | cout << x << " " << y; // 7 3 |
| 23 | return 0; |
| 24 | } |
const int &x。编译器会阻止你在函数内修改 x,既安全又高效。将原变量的内存地址传入函数,形参是一个指针变量(int *x)。函数内需要用解引用运算符 *x 才能访问或修改原变量的值。调用时须用取地址运算符 &a 传入地址。
指针传递与引用传递效果相同(都能修改原变量),但写法更繁琐。在 C++ 中优先使用引用传递;指针传递主要出现在与 C 语言兼容、或需要传递数组的场景。
| 1 | void PtrDouble(int *x) // x 是指向 int 的指针 |
| 2 | { |
| 3 | *x = *x * 2; // 解引用:通过地址修改原变量 |
| 4 | cout << "函数内 *x = " << *x; // 10 |
| 5 | } |
| 6 | |
| 7 | // 经典用途:指针版 Swap |
| 8 | void PtrSwap(int *a, int *b) |
| 9 | { |
| 10 | int tmp = *a; |
| 11 | *a = *b; *b = tmp; |
| 12 | } |
| 13 | |
| 14 | int main() |
| 15 | { |
| 16 | int a = 5; |
| 17 | PtrDouble(&a); // 传地址:&a |
| 18 | cout << "函数外 a = " << a; // 10,原变量已修改 |
| 19 | |
| 20 | int x = 3, y = 7; |
| 21 | PtrSwap(&x, &y); // 注意:传 &x 不是 x |
| 22 | cout << x << " " << y; // 7 3 |
| 23 | return 0; |
| 24 | } |
nullptr(空指针)。因此指针比引用更灵活,但也更危险——解引用空指针会导致程序崩溃。| 对比项 | 值传递 | 引用传递 | 指针传递 |
|---|---|---|---|
| 形参写法 | int x |
int &x |
int *x |
| 调用写法 | f(a) |
f(a) |
f(&a) |
| 函数内访问 | x(操作副本) |
x(操作原变量) |
*x(解引用操作原变量) |
| 能否修改原变量 | ❌ 不能 | ✅ 能 | ✅ 能 |
| 是否产生拷贝 | ✅ 产生(开销大) | ❌ 不产生 | ❌ 不产生(仅传地址) |
| 可以为空 | — | ❌ 引用必须绑定有效变量 | ✅ 可以是 nullptr |
| 推荐使用场景 | 读取小型数据、不需修改原值 | 需修改原值,或传递大对象(加 const) | 与 C 语言兼容、传递数组、动态内存 |
const 引用(最优雅)8.5
我们知道函数可以调用其他函数——那函数能不能调用自己?答案是可以,这就是递归。
以计算 4!(4的阶乘)为例:4! = 4 × 3!,而 3! = 3 × 2!,2! = 2 × 1!,1! = 1。问题的规模在每一步都缩小了一级,直到答案已知为止。这种"把大问题分解成同结构的小问题"的思路,正是递归的本质。
一个正确的递归函数必须具备两个要素,缺一不可:
| 1 | // 计算 n!(n 的阶乘) |
| 2 | int Factorial(int n) |
| 3 | { |
| 4 | if (n == 1) return 1; // ① 边界:1! = 1 |
| 5 | return n * Factorial(n - 1); // ② 推进:n! = n × (n-1)! |
| 6 | } |
| 7 | |
| 8 | // 计算 1+2+...+n |
| 9 | int Sum(int n) |
| 10 | { |
| 11 | if (n == 1) return 1; // ① 边界 |
| 12 | return n + Sum(n - 1); // ② 推进:Sum(n) = n + Sum(n-1) |
| 13 | } |
| 14 | |
| 15 | cout << Factorial(5); // 120 |
| 16 | cout << Sum(10); // 55 |