📌 阅读时长:25分钟 | 关键词:C++、函数重载、默认参数、内联函数、回调函数、递归调用、函数指针
引言
函数是 C++ 代码复用的基本单元。但仅仅会定义和调用函数远远不够------真正的生产力来自函数的高级用法:怎么让同一个函数适应不同类型?怎么把函数当作参数传来传去?怎么优雅地处理重复操作?这篇文章一次讲透。
一、参数传递的三种方式
在进入函数高级特性之前,先夯实参数传递的基础------这直接影响程序的正确性和效率。
cpp
// 1. 值传递:函数内修改不影响实参
void byValue(int a, int b) {
int temp = a; a = b; b = temp;
}
// 2. 指针传递:通过地址修改实参
void byPointer(int *a, int *b) {
int temp = *a; *a = *b; *b = temp;
}
// 3. 引用传递:通过别名修改实参,语法最简洁
void byReference(int &a, int &b) {
int temp = a; a = b; b = temp;
}
int main() {
int x = 10, y = 20;
byValue(x, y); // x=10, y=20 未变
byPointer(&x, &y); // x=20, y=10 已交换
byReference(x, y); // x=10, y=20 又换回来
}
| 传参方式 | 能否修改实参 | 效率 | 推荐场景 |
|---|---|---|---|
| 值传递 | ❌ | 低(需拷贝) | 简单类型、不需修改 |
| 指针传递 | ✅ | 中 | 动态内存、可能为 nullptr |
| 引用传递 | ✅ | 高(无拷贝) | 需要修改、语法简洁 |
常量引用 const T& |
❌ | 高 | 大对象只读传递(最佳实践) |
cpp
// 常量引用:大对象只读传递的最佳选择
void printInfo(const std::string &name) {
std::cout << "Name: " << name << std::endl;
// name[0] = 'X'; // ❌ 错误,const 保护
}
二、函数重载:同名不同参
C++ 允许同一个函数名拥有多个版本,只要参数列表不同(类型、数量或顺序):
cpp
int sum(int a, int b) { // 两个 int
return a + b;
}
int sum(int a, int b, int c) { // 三个 int
return a + b + c;
}
double sum(double a, double b) { // 两个 double
return a + b;
}
int main() {
std::cout << sum(2, 3) << std::endl; // 5(自动匹配第一个)
std::cout << sum(2, 3, 4) << std::endl; // 9(匹配第二个)
std::cout << sum(2.5, 3.5) << std::endl; // 6.0(匹配第三个)
}
⚠️ 重载只看参数列表 ,不看返回值类型。
int f()和double f()不是重载,是编译错误。
三、默认参数:让函数调用更简洁
从右向左给参数设默认值,调用时可省略:
cpp
// 计算矩形面积,width 默认为 1
int area(int length, int width = 1) {
return length * width;
}
// 打印消息,times 默认为 1
void print(const std::string &msg, int times = 1) {
for (int i = 0; i < times; ++i)
std::cout << msg << std::endl;
}
int main() {
std::cout << area(5) << std::endl; // 5*1 = 5
std::cout << area(5, 3) << std::endl; // 5*3 = 15
print("Hello"); // 打印 1 次
print("World", 3); // 打印 3 次
}
| 规则 | 说明 |
|---|---|
| 从右向左 | 如果 a 有默认值,它右边的 b、c 也必须有 |
| 声明时指定 | 默认值在函数声明中写一次即可 |
| 不重复指定 | 声明和定义不能同时写默认值 |
| 调用时省略 | 只能从右端开始省略参数 |
四、内联函数:牺牲体积换速度
inline 建议编译器将函数体直接嵌入调用处,省去函数调用的压栈、跳转开销:
cpp
inline int max(int a, int b) {
return a > b ? a : b;
}
inline double square(double x) {
return x * x;
}
int main() {
int m = max(10, 20); // 可能被替换成 20 > 10 ? 20 : 10
double s = square(3.5); // 可能被替换成 3.5 * 3.5
}
| 适合内联 | 不适合内联 |
|---|---|
| 短小函数(<10行) | 包含循环 |
| 频繁调用 | 包含递归 |
| getter/setter | 函数体过大 |
| 简单计算 | switch/goto 多分支 |
💡
inline只是建议,编译器可能忽略。通常声明和定义放同一个头文件里。
五、函数指针:把函数当参数传递
函数指针存储函数的入口地址,可以把函数像数据一样传递:
cpp
#include <iostream>
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
// 函数指针作为参数
int operate(int a, int b, int (*op)(int, int)) {
return op(a, b);
}
int main() {
int (*funcPtr)(int, int); // 声明函数指针
funcPtr = add; // 指向 add
std::cout << funcPtr(5, 3) << std::endl; // 8
std::cout << operate(10, 5, add) << std::endl; // 15
std::cout << operate(10, 5, subtract) << std::endl; // 5
}
函数指针的声明格式:返回值类型 (*指针名)(参数类型列表)
六、回调函数:让函数"通知"你
回调就是把函数指针传给另一个函数,让它在合适的时机反过来调用你:
cpp
#include <vector>
#include <algorithm>
bool ascending(int a, int b) { return a < b; }
bool descending(int a, int b) { return a > b; }
void sortNumbers(std::vector<int> &nums, bool (*cmp)(int, int)) {
std::sort(nums.begin(), nums.end(), cmp); // cmp 就是回调
}
int main() {
std::vector<int> v = {3, 1, 4, 1, 5};
sortNumbers(v, ascending); // 升序:1 1 3 4 5
sortNumbers(v, descending); // 降序:5 4 3 1 1
}
回调的典型应用场景:
| 场景 | 示例 |
|---|---|
| 自定义排序规则 | std::sort 的比较函数 |
| 事件处理 | GUI 按钮点击回调 |
| 异步通知 | 网络请求完成后的回调 |
| 过滤条件 | std::copy_if 的谓词函数 |
七、递归:函数调用自己
递归 = 终止条件 + 递推关系。就像两面镜子互相对照:
cpp
// 阶乘:n! = n × (n-1)!
int factorial(int n) {
if (n == 0) return 1; // 终止条件
return n * factorial(n - 1); // 递推关系
}
// 斐波那契:f(n) = f(n-1) + f(n-2)
int fibonacci(int n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
std::cout << factorial(5) << std::endl; // 120
std::cout << fibonacci(10) << std::endl; // 55
}
递归 vs 迭代
| 递归 | 迭代 | |
|---|---|---|
| 优点 | 代码简洁、思路清晰 | 效率高、内存占用小 |
| 缺点 | 深度过大→栈溢出,重复计算 | 某些问题实现复杂 |
| 选择 | 树、图遍历、分治算法 | 简单循环、追求性能 |
⚠️ 务必要有终止条件,否则无限递归导致程序崩溃!尾递归可以被编译器优化,但不依赖于此。
八、指针函数:返回指针的函数
函数可以返回指针,常用于返回动态分配的内存或查找结果:
cpp
int* createArray(int size) {
int *arr = new int[size]; // 堆上分配
for (int i = 0; i < size; ++i)
arr[i] = i * 2;
return arr; // 返回堆内存地址(安全)
}
// ❌ 危险!不要返回局部变量的地址
int* badFunction() {
int local = 10;
return &local; // local 在函数返回后已销毁!
}
int main() {
int *arr = createArray(5);
for (int i = 0; i < 5; ++i)
std::cout << arr[i] << " "; // 0 2 4 6 8
delete[] arr; // 必须释放
arr = nullptr;
}
小结
| 序号 | 知识点 | 一句话总结 |
|---|---|---|
| 1 | 参数传递 | 值传递安全但慢,引用传递高效简洁,const引用最常用 |
| 2 | 函数重载 | 同名函数通过不同参数列表区分 |
| 3 | 默认参数 | 从右向左设默认值,让调用更简洁 |
| 4 | 内联函数 | 把函数体嵌入调用处,换取速度 |
| 5 | 函数指针 | 存储函数入口地址,让函数像数据一样传递 |
| 6 | 回调函数 | 把函数指针作参数,实现"通知"模式 |
| 7 | 递归 | 函数调用自己,必须有终止条件 |
| 8 | 指针函数 | 返回指针(通常是堆内存),注意不要返回局部变量地址 |
下一篇文章,我们将深入 C++ 内存管理的核心------new/delete、栈与堆、全局/局部/静态变量,这是 C++ 开发中最容易踩坑的地方。
本文是「C++ 从基础到项目实战」系列的第 3 篇。关注我,不错过后续更新。