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