7. 函数进阶:默认参数、内联函数、函数指针、lambda表达式、递归函数
函数这东西,写几行代码就得打交道。但很多人写了几年,还在用最基础的那一套。说实话,C++ 的函数远不止「传参-返回」这么简单。今天我把几个进阶玩法掰开揉碎讲清楚,都是我实际项目中踩过坑、尝过甜头的东西。
7.1 默认参数:少写代码的偷懒技巧
默认参数,说白了就是给函数参数一个「保底值」。调用时你不传,它就自动用这个值。
void ShowLog(const std::string& msg, int level = 1) {
std::cout << "[Level " << level << "] " << msg << std::endl;
}
// 调用
ShowLog("启动成功"); // level 默认为 1
ShowLog("严重错误", 5); // 手动指定 level 为 5
我个人习惯把最常用的参数放在后面,这样默认值才有意义。你想想看,如果第一个参数就有默认值,后面全是必填,那调用时反而更别扭。
void f(int a = 1, int b),编译器会直接报错。
我在项目中遇到过一个问题:头文件里声明了默认参数,但实现文件里又写了一遍。结果两个地方不一致,编译不报错,运行结果却莫名其妙。后来我定了个规矩——默认参数只写在声明里,实现里绝不重复。
7.2 内联函数:用空间换时间
函数调用是有开销的——压栈、跳转、返回。如果一个函数体很小,比如就两三行,那调用开销可能比函数本身还大。这时候,inline 就派上用场了。
inline int Max(int a, int b) {
return a > b ? a : b;
}
加了 inline,编译器会把函数体直接「粘贴」到调用处,省去调用开销。但注意,这只是个建议,不是命令。编译器觉得不合适,照样忽略你的 inline。
还有一点:inline 函数通常定义在头文件里。因为编译器需要在每个调用点看到完整的函数体,才能做替换。
7.3 函数指针:把函数当变量用
函数指针,就是指向函数的指针。你可以把它赋值、传参、甚至放进数组里。听起来有点绕,但用起来很灵活。
int Add(int a, int b) { return a + b; }
int Sub(int a, int b) { return a - b; }
int main() {
// 声明一个函数指针
int (*op)(int, int);
op = Add;
std::cout << op(10, 5) << std::endl; // 输出 15
op = Sub;
std::cout << op(10, 5) << std::endl; // 输出 5
}
函数指针的声明语法有点丑,我建议用 using 或 typedef 给它起个别名:
using OpFunc = int(*)(int, int);
OpFunc op = Add;
我在项目中用函数指针做过一个简单的命令分发器。用户输入命令字符串,程序查表找到对应的处理函数,直接调用。比写一长串 if-else 清爽多了。
7.4 Lambda 表达式:匿名函数的优雅写法
Lambda 是 C++11 引入的,说白了就是「临时用一下的函数」。你不需要给它起名字,写出来直接用。
auto add = [](int a, int b) -> int {
return a + b;
};
std::cout << add(3, 4) << std::endl; // 输出 7
Lambda 的核心语法:[捕获列表](参数列表) -> 返回类型 { 函数体 }。捕获列表用来「抓取」外部变量:
int factor = 2;
auto multiply = [factor](int x) { return x * factor; };
std::cout << multiply(5) << std::endl; // 输出 10
[=]— 按值捕获所有外部变量[&]— 按引用捕获所有外部变量[x, &y]— 混合捕获,x 按值,y 按引用
我建议优先用按值捕获。按引用捕获容易出问题——如果 lambda 执行时,引用的变量已经销毁了,程序直接崩溃。我曾经在回调函数里吃过这个亏,排查了半天才发现是引用悬空了。
Lambda 在 STL 算法里特别好用:
std::vector<int> nums = {3, 1, 4, 1, 5, 9};
std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a > b; // 降序排列
});
7.5 递归函数:自己调用自己
递归,就是函数调用自身。解决那些可以拆分成「相同子问题」的场景特别合适,比如遍历树、计算阶乘。
int Factorial(int n) {
if (n <= 1) return 1;
return n * Factorial(n - 1);
}
写递归要记住两件事:
- 终止条件 — 没有终止条件就是死循环,最终栈溢出崩溃
- 递推关系 — 当前结果怎么由子问题得到
递归的经典例子:斐波那契数列。但注意,直接递归效率极低,因为大量重复计算。用记忆化(缓存中间结果)或者直接改成循环,才是正经做法。
// 带记忆化的斐波那契
int Fib(int n, std::vector<int>& memo) {
if (n <= 1) return n;
if (memo[n] != -1) return memo[n];
memo[n] = Fib(n-1, memo) + Fib(n-2, memo);
return memo[n];
}
知识体系总览
下面这张图把本章的核心知识点串起来了。你可以看到,默认参数和内联函数偏向「编译期优化」,函数指针和 lambda 偏向「运行时灵活性」,递归则是另一种思维模式。
这五个知识点,每个单独看都不难。但组合起来,你就能写出更灵活、更高效的代码。比如用 lambda 做回调、用函数指针做策略模式、用递归处理树形结构——这些都是实际项目里的高频场景。
公众号:蓝海资料掘金营,微信deep3321