阅读时长25分钟 | 关键词C、函数重载、默认参数、内联函数、回调函数、递归调用、函数指针引言函数是 C 代码复用的基本单元。但仅仅会定义和调用函数远远不够——真正的生产力来自函数的高级用法怎么让同一个函数适应不同类型怎么把函数当作参数传来传去怎么优雅地处理重复操作这篇文章一次讲透。一、参数传递的三种方式在进入函数高级特性之前先夯实参数传递的基础——这直接影响程序的正确性和效率。// 1. 值传递函数内修改不影响实参voidbyValue(inta,intb){inttempa;ab;btemp;}// 2. 指针传递通过地址修改实参voidbyPointer(int*a,int*b){inttemp*a;*a*b;*btemp;}// 3. 引用传递通过别名修改实参语法最简洁voidbyReference(inta,intb){inttempa;ab;btemp;}intmain(){intx10,y20;byValue(x,y);// x10, y20 未变byPointer(x,y);// x20, y10 已交换byReference(x,y);// x10, y20 又换回来}传参方式能否修改实参效率推荐场景值传递❌低需拷贝简单类型、不需修改指针传递✅中动态内存、可能为 nullptr引用传递✅高无拷贝需要修改、语法简洁常量引用const T❌高大对象只读传递最佳实践// 常量引用大对象只读传递的最佳选择voidprintInfo(conststd::stringname){std::coutName: namestd::endl;// name[0] X; // ❌ 错误const 保护}二、函数重载同名不同参C 允许同一个函数名拥有多个版本只要参数列表不同类型、数量或顺序intsum(inta,intb){// 两个 intreturnab;}intsum(inta,intb,intc){// 三个 intreturnabc;}doublesum(doublea,doubleb){// 两个 doublereturnab;}intmain(){std::coutsum(2,3)std::endl;// 5自动匹配第一个std::coutsum(2,3,4)std::endl;// 9匹配第二个std::coutsum(2.5,3.5)std::endl;// 6.0匹配第三个}⚠️ 重载只看参数列表不看返回值类型。int f()和double f()不是重载是编译错误。三、默认参数让函数调用更简洁从右向左给参数设默认值调用时可省略// 计算矩形面积width 默认为 1intarea(intlength,intwidth1){returnlength*width;}// 打印消息times 默认为 1voidprint(conststd::stringmsg,inttimes1){for(inti0;itimes;i)std::coutmsgstd::endl;}intmain(){std::coutarea(5)std::endl;// 5*1 5std::coutarea(5,3)std::endl;// 5*3 15print(Hello);// 打印 1 次print(World,3);// 打印 3 次}规则说明从右向左如果 a 有默认值它右边的 b、c 也必须有声明时指定默认值在函数声明中写一次即可不重复指定声明和定义不能同时写默认值调用时省略只能从右端开始省略参数四、内联函数牺牲体积换速度inline建议编译器将函数体直接嵌入调用处省去函数调用的压栈、跳转开销inlineintmax(inta,intb){returnab?a:b;}inlinedoublesquare(doublex){returnx*x;}intmain(){intmmax(10,20);// 可能被替换成 20 10 ? 20 : 10doublessquare(3.5);// 可能被替换成 3.5 * 3.5}适合内联不适合内联短小函数10行包含循环频繁调用包含递归getter/setter函数体过大简单计算switch/goto 多分支inline只是建议编译器可能忽略。通常声明和定义放同一个头文件里。五、函数指针把函数当参数传递函数指针存储函数的入口地址可以把函数像数据一样传递#includeiostreamintadd(inta,intb){returnab;}intsubtract(inta,intb){returna-b;}// 函数指针作为参数intoperate(inta,intb,int(*op)(int,int)){returnop(a,b);}intmain(){int(*funcPtr)(int,int);// 声明函数指针funcPtradd;// 指向 addstd::coutfuncPtr(5,3)std::endl;// 8std::coutoperate(10,5,add)std::endl;// 15std::coutoperate(10,5,subtract)std::endl;// 5}函数指针的声明格式返回值类型 (*指针名)(参数类型列表)六、回调函数让函数通知你回调就是把函数指针传给另一个函数让它在合适的时机反过来调用你#includevector#includealgorithmboolascending(inta,intb){returnab;}booldescending(inta,intb){returnab;}voidsortNumbers(std::vectorintnums,bool(*cmp)(int,int)){std::sort(nums.begin(),nums.end(),cmp);// cmp 就是回调}intmain(){std::vectorintv{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(n0)return1;// 终止条件returnn*factorial(n-1);// 递推关系}// 斐波那契f(n) f(n-1) f(n-2)intfibonacci(intn){if(n1)returnn;returnfibonacci(n-1)fibonacci(n-2);}intmain(){std::coutfactorial(5)std::endl;// 120std::coutfibonacci(10)std::endl;// 55}递归 vs 迭代递归迭代优点代码简洁、思路清晰效率高、内存占用小缺点深度过大→栈溢出重复计算某些问题实现复杂选择树、图遍历、分治算法简单循环、追求性能⚠️ 务必要有终止条件否则无限递归导致程序崩溃尾递归可以被编译器优化但不依赖于此。八、指针函数返回指针的函数函数可以返回指针常用于返回动态分配的内存或查找结果int*createArray(intsize){int*arrnewint[size];// 堆上分配for(inti0;isize;i)arr[i]i*2;returnarr;// 返回堆内存地址安全}// ❌ 危险不要返回局部变量的地址int*badFunction(){intlocal10;returnlocal;// local 在函数返回后已销毁}intmain(){int*arrcreateArray(5);for(inti0;i5;i)std::coutarr[i] ;// 0 2 4 6 8delete[]arr;// 必须释放arrnullptr;}小结序号知识点一句话总结1参数传递值传递安全但慢引用传递高效简洁const引用最常用2函数重载同名函数通过不同参数列表区分3默认参数从右向左设默认值让调用更简洁4内联函数把函数体嵌入调用处换取速度5函数指针存储函数入口地址让函数像数据一样传递6回调函数把函数指针作参数实现通知模式7递归函数调用自己必须有终止条件8指针函数返回指针通常是堆内存注意不要返回局部变量地址下一篇文章我们将深入C 内存管理的核心——new/delete、栈与堆、全局/局部/静态变量这是 C 开发中最容易踩坑的地方。本文是「C 从基础到项目实战」系列的第 3 篇。关注我不错过后续更新。
【C++ 从基础到项目实战】C++(三):函数进阶——重载、回调、递归与默认参数
阅读时长25分钟 | 关键词C、函数重载、默认参数、内联函数、回调函数、递归调用、函数指针引言函数是 C 代码复用的基本单元。但仅仅会定义和调用函数远远不够——真正的生产力来自函数的高级用法怎么让同一个函数适应不同类型怎么把函数当作参数传来传去怎么优雅地处理重复操作这篇文章一次讲透。一、参数传递的三种方式在进入函数高级特性之前先夯实参数传递的基础——这直接影响程序的正确性和效率。// 1. 值传递函数内修改不影响实参voidbyValue(inta,intb){inttempa;ab;btemp;}// 2. 指针传递通过地址修改实参voidbyPointer(int*a,int*b){inttemp*a;*a*b;*btemp;}// 3. 引用传递通过别名修改实参语法最简洁voidbyReference(inta,intb){inttempa;ab;btemp;}intmain(){intx10,y20;byValue(x,y);// x10, y20 未变byPointer(x,y);// x20, y10 已交换byReference(x,y);// x10, y20 又换回来}传参方式能否修改实参效率推荐场景值传递❌低需拷贝简单类型、不需修改指针传递✅中动态内存、可能为 nullptr引用传递✅高无拷贝需要修改、语法简洁常量引用const T❌高大对象只读传递最佳实践// 常量引用大对象只读传递的最佳选择voidprintInfo(conststd::stringname){std::coutName: namestd::endl;// name[0] X; // ❌ 错误const 保护}二、函数重载同名不同参C 允许同一个函数名拥有多个版本只要参数列表不同类型、数量或顺序intsum(inta,intb){// 两个 intreturnab;}intsum(inta,intb,intc){// 三个 intreturnabc;}doublesum(doublea,doubleb){// 两个 doublereturnab;}intmain(){std::coutsum(2,3)std::endl;// 5自动匹配第一个std::coutsum(2,3,4)std::endl;// 9匹配第二个std::coutsum(2.5,3.5)std::endl;// 6.0匹配第三个}⚠️ 重载只看参数列表不看返回值类型。int f()和double f()不是重载是编译错误。三、默认参数让函数调用更简洁从右向左给参数设默认值调用时可省略// 计算矩形面积width 默认为 1intarea(intlength,intwidth1){returnlength*width;}// 打印消息times 默认为 1voidprint(conststd::stringmsg,inttimes1){for(inti0;itimes;i)std::coutmsgstd::endl;}intmain(){std::coutarea(5)std::endl;// 5*1 5std::coutarea(5,3)std::endl;// 5*3 15print(Hello);// 打印 1 次print(World,3);// 打印 3 次}规则说明从右向左如果 a 有默认值它右边的 b、c 也必须有声明时指定默认值在函数声明中写一次即可不重复指定声明和定义不能同时写默认值调用时省略只能从右端开始省略参数四、内联函数牺牲体积换速度inline建议编译器将函数体直接嵌入调用处省去函数调用的压栈、跳转开销inlineintmax(inta,intb){returnab?a:b;}inlinedoublesquare(doublex){returnx*x;}intmain(){intmmax(10,20);// 可能被替换成 20 10 ? 20 : 10doublessquare(3.5);// 可能被替换成 3.5 * 3.5}适合内联不适合内联短小函数10行包含循环频繁调用包含递归getter/setter函数体过大简单计算switch/goto 多分支inline只是建议编译器可能忽略。通常声明和定义放同一个头文件里。五、函数指针把函数当参数传递函数指针存储函数的入口地址可以把函数像数据一样传递#includeiostreamintadd(inta,intb){returnab;}intsubtract(inta,intb){returna-b;}// 函数指针作为参数intoperate(inta,intb,int(*op)(int,int)){returnop(a,b);}intmain(){int(*funcPtr)(int,int);// 声明函数指针funcPtradd;// 指向 addstd::coutfuncPtr(5,3)std::endl;// 8std::coutoperate(10,5,add)std::endl;// 15std::coutoperate(10,5,subtract)std::endl;// 5}函数指针的声明格式返回值类型 (*指针名)(参数类型列表)六、回调函数让函数通知你回调就是把函数指针传给另一个函数让它在合适的时机反过来调用你#includevector#includealgorithmboolascending(inta,intb){returnab;}booldescending(inta,intb){returnab;}voidsortNumbers(std::vectorintnums,bool(*cmp)(int,int)){std::sort(nums.begin(),nums.end(),cmp);// cmp 就是回调}intmain(){std::vectorintv{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(n0)return1;// 终止条件returnn*factorial(n-1);// 递推关系}// 斐波那契f(n) f(n-1) f(n-2)intfibonacci(intn){if(n1)returnn;returnfibonacci(n-1)fibonacci(n-2);}intmain(){std::coutfactorial(5)std::endl;// 120std::coutfibonacci(10)std::endl;// 55}递归 vs 迭代递归迭代优点代码简洁、思路清晰效率高、内存占用小缺点深度过大→栈溢出重复计算某些问题实现复杂选择树、图遍历、分治算法简单循环、追求性能⚠️ 务必要有终止条件否则无限递归导致程序崩溃尾递归可以被编译器优化但不依赖于此。八、指针函数返回指针的函数函数可以返回指针常用于返回动态分配的内存或查找结果int*createArray(intsize){int*arrnewint[size];// 堆上分配for(inti0;isize;i)arr[i]i*2;returnarr;// 返回堆内存地址安全}// ❌ 危险不要返回局部变量的地址int*badFunction(){intlocal10;returnlocal;// local 在函数返回后已销毁}intmain(){int*arrcreateArray(5);for(inti0;i5;i)std::coutarr[i] ;// 0 2 4 6 8delete[]arr;// 必须释放arrnullptr;}小结序号知识点一句话总结1参数传递值传递安全但慢引用传递高效简洁const引用最常用2函数重载同名函数通过不同参数列表区分3默认参数从右向左设默认值让调用更简洁4内联函数把函数体嵌入调用处换取速度5函数指针存储函数入口地址让函数像数据一样传递6回调函数把函数指针作参数实现通知模式7递归函数调用自己必须有终止条件8指针函数返回指针通常是堆内存注意不要返回局部变量地址下一篇文章我们将深入C 内存管理的核心——new/delete、栈与堆、全局/局部/静态变量这是 C 开发中最容易踩坑的地方。本文是「C 从基础到项目实战」系列的第 3 篇。关注我不错过后续更新。