上一篇我们学了 C++ 的基础语法,所有代码都写在 main 函数里。但真实的程序不可能把所有逻辑塞在一个函数中,我们需要把代码拆分成多个函数,让程序结构更清晰、代码可以复用。
这篇文章除了讲函数的基本用法,还会讲 C++ 中一个非常重要的机制——引用(reference)。学完之后你就能看懂 C++ 代码中函数参数里那些 & 符号了。
函数
函数就是一段可以重复调用的代码块,给它一个名字,需要的时候调用就行。
定义与调用
一个函数由返回类型、函数名、参数列表和函数体组成:
return 语句用来返回函数的结果,并且立即结束函数的执行。void 类型的函数没有返回值,不需要写 return(当然你也可以写一个空的 return; 来提前退出函数)。
在 C++ 中,函数必须在调用之前定义。所以在刷算法题时,最简单的做法就是把辅助函数写在 main 函数上面。
值传递
C++ 的函数参数默认是值传递,也就是说传进去的是值的副本,在函数内修改参数不会影响外面的变量:
a 的值传给了 x,但 x 只是 a 的副本,修改 x 不会影响 a。
那如果我确实想在函数里修改外面的变量呢?这就需要用到引用了。
引用传递(& 符号)
在参数类型后面加一个 &,就变成了引用传递。引用就是给变量起了个别名,函数内操作的就是原变量本身,而不是副本:
对比一下:
- 值传递
void f(int x)——x是副本,改了白改 - 引用传递
void f(int& x)——x是原变量的别名,改了就是真改
值传递 vs 引用传递:什么时候用哪个
简单记:基本类型传值(副本),容器类型传引用(避免复制)。
基本类型(int、double、bool、char)本身就很小,复制一个 int 几乎没有开销,直接传值就好。除非你确实需要在函数里修改原变量,才用引用。
容器类型(vector、string、map 等)可能包含大量数据,如果传值的话每次调用都要把整个容器复制一遍,非常浪费。所以容器类型一般都传引用。
如果函数只是读取容器、不需要修改它,就加上 const 表示"只读引用":
// 传值:每次调用都复制整个 vector,太浪费了
int sum(vector<int> nums) { ... }
// 引用传递:不复制,但函数内可以修改原容器
int sum(vector<int>& nums) { ... }
// const 引用:不复制,也不允许修改,最安全
int sum(const vector<int>& nums) { ... }在刷算法题时,最常用的写法就是 const vector<int>& 和 vector<int>&,根据需不需要修改来选择。
递归函数传容器的注意事项
这是一个非常容易踩的坑:递归函数中传容器千万别用值传递,否则会超时(TLE)甚至超内存(MLE)。
为什么?因为值传递意味着每次递归调用都会把整个容器复制一份。如果容器有 n 个元素,递归深度是 d,那就会产生 d 份完整拷贝,时间和空间都是灾难。
上面的回溯算法中,path 用引用传递,所有递归层共享同一个 path,通过 push_back 和 pop_back 来维护状态。如果把 vector<int>& path 改成 vector<int> path(值传递),每层递归都会复制一份 path,不仅浪费内存,而且逻辑上回溯也不对了。
简单记住:递归函数中,容器类型的参数一律用引用 &。
函数重载
C++ 允许多个函数同名,只要参数列表不同(参数个数不同,或参数类型不同),编译器会根据调用时传入的参数自动选择正确的版本。这就是函数重载:
编译器会根据你传入的参数类型和个数,自动匹配最合适的函数版本。函数重载让接口更简洁——同样是"相加"这个操作,你不需要给它们起不同的名字。
小结
这篇讲了两个核心内容:
函数让你把代码拆成可复用的小块,避免重复。引用让你在函数中直接操作原变量,避免不必要的复制。
关键点回顾:
- 值传递是默认行为,传进去的是副本
- 加
&变成引用传递,操作的是原变量 - 基本类型传值,容器类型传引用
const引用表示只读,既不复制也不修改- 递归函数中容器参数一律用引用
& - 函数重载:同名函数,参数不同,编译器自动选择
下一篇我们来学 C++ 中常用的数据结构,包括 vector、string、pair 和结构体。