本章目标:函数的声明定义、参数传递、返回值、重载、 以及现代 C++ 的灵魂——lambda 表达式。
int add(int a, int b) { // 返回类型 名字(参数)
return a + b; // return 返回结果
}
int main() {
int s = add(3, 5); // 调用:s == 8
}
要点:
void say_hi() { std::println("hi"); }
C++ 要求"先看到再用"。函数可以: 1) 先定义再调用(简单,教程一直这么干) 2) 先声明后定义(大项目必须):
int add(int a, int b); // 声明:告诉编译器"有这么个函数"
int main() { return add(1, 2); } // 此时可以用
int add(int a, int b) { return a + b; } // 定义在文件后面
"声明"= 只写签名不写身体,类似"合同"; "定义"= 签名 + 实现,类似"履约"。
1) 传值(拷贝):函数拿到的是副本,改它不影响外面
void f(int x) { x = 999; } // 外面不受影响
2) 传引用(&):函数拿到的是"本人",改它影响外面
void f(int& x) { x = 999; } // 外面变了!
3) 传 const 引用:既要避免拷贝(性能),又不允许修改
void print_big(const std::string& s) { ... }
经验法则(重要,背下来):
为什么大对象不要传值?因为每次调用都完整复制一份, string 几 MB 就复制几 MB,又慢又费内存。
void greet(std::string name, std::string greeting = "你好") {
std::println("{}, {}", greeting, name);
}
greet("小明"); // 你好, 小明
greet("小明", "早上好"); // 早上好, 小明
规则:默认参数只能从右往左连续给(greeting 有默认值, 它左边的 name 可以有默认值,但"跳过一个"不行)。
int max(int a, int b) { return a > b ? a : b; }
double max(double a, double b) { return a > b ? a : b; }
int max(int a, int b, int c) { return max(max(a,b), c); }
编译器根据"参数个数和类型"自动挑合适的版本。 这是 C++ 多态的第一种形态(编译期多态)。
注意:只改返回类型不算重载,编译报错。
lambda 就是"没有名字的临时函数",写在哪用在哪:
auto f = [](int x) { return x * 2; }; // 定义
int r = f(21); // 调用,r == 42
语法拆解:
[捕获] (参数) -> 返回类型 { 函数体 }
^^^^^^ ^^^^^^ ^^^^^^^^ ^^^^^^^^
捕获列表 参数表 返回类型 函数体
返回类型可以省略(编译器推断):
auto f = [](int x) { return x * 2; };
默认情况下,lambda 不能使用外面的变量。要使用必须"捕获":
int offset = 10;
auto f = [offset](int x) { return x + offset; }; // 按值捕获
auto g = [&offset](int x) { offset += 1; return x; }; // 按引用捕获
auto all1 = [=](...){...}; // 全部按值捕获(旧风格,慎用)
auto all2 = [&](...){...}; // 全部按引用捕获
auto mix = [=, &x]{...}; // 除了 x 按引用,其余按值
auto init = [y = offset*2](int x){ return x + y; }; // C++14 初始化捕获
人话解释"按值 vs 按引用":
注意:按引用捕获的变量必须活得比 lambda 久! lambda 存下来以后再用,而引用指向的变量已经销毁 → 悬空引用 → 崩溃。 这是新手最容易踩的内存 bug。
auto add = [](auto a, auto b) { return a + b; };
add(1, 2); // 3
add(1.5, 2); // 3.5
add(std::string("a"), "b"); // "ab"
参数写 auto 就能接受任意类型(模板的偷懒写法)。 注意:所有调用必须类型一致,add(1, 2.5) 会编译报错 (因为 a 被推断成 int 了)。
1) 传给 STL 算法(第十四章细讲):
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });
2) 作为回调:比如事件处理、线程任务 3) 就地写小逻辑,避免到处散落的具名小函数
int& bad() { int x = 5; return x; } // x 销毁了!别这么写
返回局部变量本身(按值)则是安全的,会自动拷贝。
练习题
再改按引用捕获,验证变了。
用范围 for 打印所有元素。