C++ Primer Plus 重读精讲 _ 函数进阶:重载、默认参数、函数指针与
全文连载前置回顾前15篇完整知识链路1-6篇开发环境、基础数据类型、运算符全集7-10篇分支结构、三大循环、数组、字符数组与字符串11-12篇指针基础与进阶、三类const指针、指针数组与内存地址13-14篇基础输入输出、结构体与自定义数据类型15篇函数基础、参数传递机制、模块化编程上一篇我们掌握了函数的基础语法、声明与定义、参数传递的三种方式、返回值形式。但C函数的能力远不止于此。真实工业开发中还需要同名函数处理不同类型、省略常用参数、动态选择处理逻辑、把函数作为参数传递给另一个函数等高级能力。前言函数进阶的四大核心能力是函数重载、默认参数、函数指针、递归。它们让函数的使用更加灵活、更加智能。函数重载Function Overloading同一个函数名根据参数类型/数量自动匹配合适的实现。比如一个print函数可以传int、double、字符串、结构体编译器自动选择对应版本。默认参数Default Arguments给某些参数指定默认值调用时可以省略——最常用的参数不需要每次都写。函数指针Function Pointer把函数的地址存储在指针变量中可以作为参数传递、动态选择调用。这是实现回调函数、策略模式的基础。递归Recursion函数调用自己来解决可以分解为更小同类问题的场景。常用于树结构遍历、分层数据处理、分治算法。本篇逐一讲解这四项能力每一项都配合工业场景说明实际应用价值。一、函数重载同名函数处理不同类型函数重载允许在同一个作用域内定义多个同名函数但它们的参数列表必须不同参数的类型、数量或顺序不同。编译器根据调用时的实参自动匹配。1. 重载的基本用法#includeiostream#includecstringusingnamespacestd;// 重载版本1打印整数voidprint(intvalue){cout[整数] valueendl;}// 重载版本2打印浮点数voidprint(doublevalue){cout[浮点数] valueendl;}// 重载版本3打印字符串voidprint(constchar*str){cout[字符串] strendl;}structDevice{intid;charname[30];doubletemp;};// 重载版本4打印设备结构体voidprint(constDevicedev)// 是引用后续详解这里可先当作安全指针{cout[设备] IDdev.id 名称dev.name 温度dev.temp度endl;}intmain(){print(1001);// 自动匹配版本1print(82.5);// 自动匹配版本2print(加热炉A);// 自动匹配版本3Device d{1001,加热炉A,82.5};print(d);// 自动匹配版本4return0;}重载规则重载必须通过参数区分不能通过返回值区分。下面这种写法是错误的intgetValue(){return10;}doublegetValue(){return10.5;}// 错误仅返回值不同不能重载2. 重载在工业代码中的应用设备数据处理是典型场景——同一个处理函数名根据数据类型自动选择不同实现#includeiostream#includecstringusingnamespacestd;structSensorData{intsensorId;doublevalue;charunit[10];};structDeviceAlert{intdeviceId;charmessage[100];intlevel;// 1警告 2严重 3紧急};voidprocessData(intrawValue)// 处理原始整数采集{cout[整型采集] 值rawValue (存入寄存器)endl;}voidprocessData(doublecalibratedValue)// 处理校准后的浮点数{cout[浮点数据] 值calibratedValue (写入数据库)endl;}voidprocessData(constSensorDatadata)// 处理完整传感器数据包{cout[传感器 data.sensorId] data.valuedata.unitendl;}voidprocessData(constDeviceAlertalert)// 处理报警信息{cout[报警] 设备alert.deviceId 级别alert.level 消息alert.messageendl;}intmain(){intraw4095;doublecal82.5;SensorData s{101,82.5,C};DeviceAlert a{1001,温度超阈值,2};processData(raw);processData(cal);processData(s);processData(a);return0;}设计价值对外提供统一的processData接口调用者不需要记住多个函数名。内部实现可以自由优化而不影响调用方。二、默认参数简化常用调用1. 基本用法在函数声明中给某些参数指定默认值。调用时如果省略这些参数就使用默认值。#includeiostreamusingnamespacestd;// 声明中指定默认参数默认温度阈值80度默认压力上限1.2MPavoidcheckDevice(intdeviceId,doubletemperature,doubletempLimit80.0,doublepressureLimit1.2){cout设备deviceId: ;if(temperaturetempLimit)cout温度temperature度 超过阈值tempLimit度!;elsecout温度temperature度 正常;coutendl;}intmain(){checkDevice(1001,75.0);// 用默认的温度阈值checkDevice(1002,85.5);// 用默认阈值checkDevice(1003,125.0,120.0);// 反应釜自定义高温阈值checkDevice(1004,28.5,45.0);// 冷却设备低温阈值return0;}关键规则默认参数必须从右向左连续设置。// 正确从右开始连续voidfunc(inta,intb10,intc20);// 错误跳过了b却给c设默认值voidfunc(inta,intb10,intc);// 编译错误工业最佳实践默认参数写在声明中头文件不要在定义中重复写部分编译器允许但不统一。三、函数指针把函数当作数据函数本质上是一段可执行代码它在内存中也有地址。函数指针就是存储这个地址的变量。1. 函数指针的声明语法// 普通函数接收两个int返回intintadd(inta,intb){returnab;}intmultiply(inta,intb){returna*b;}// 函数指针类型指向接收两个int返回int的函数typedefint(*MathFunc)(int,int);语法记忆返回值类型 (*指针变量名)(参数类型列表)。括号不能省略否则就变成返回指针的函数了。2. 通过函数指针调用#includeiostreamusingnamespacestd;intadd(inta,intb){returnab;}intmultiply(inta,intb){returna*b;}intsubtract(inta,intb){returna-b;}typedefint(*MathFunc)(int,int);intmain(){MathFunc op;// op是一个函数指针变量// 让op指向add函数opadd;cout10 5 op(10,5)endl;// 通过指针调用// 让op指向multiply函数opmultiply;cout10 * 5 op(10,5)endl;// 让op指向subtractopsubtract;cout10 - 5 op(10,5)endl;return0;}3. 函数指针作为参数策略模式这是工业代码中最重要的应用把处理逻辑作为参数传入实现策略选择。#includeiostream#includecstringusingnamespacestd;structDevice{intid;charname[30];doubletemp;doublepress;};// 不同的处理策略voidstrategyNormal(Device*d){coutd-name: 正常运行继续监控endl;}voidstrategyWarn(Device*d){coutd-name: 注意参数接近阈值加强监控endl;}voidstrategyShutdown(Device*d){coutd-name: 紧急超过安全阈值立即停机endl;}typedefvoid(*Strategy)(Device*);// 根据温度选择策略并执行voidprocessDevice(Device*d,doubletempLimit,doublepressLimit){Strategy strat;if(d-temptempLimit||d-presspressLimit)stratstrategyShutdown;elseif(d-temptempLimit*0.9||d-presspressLimit*0.9)stratstrategyWarn;elsestratstrategyNormal;strat(d);// 调用选中的策略}intmain(){Device devices[]{{1001,加热炉A,78.5,0.75},{1002,加热炉B,88.5,0.85},{1003,反应釜C,128.5,1.35},{1004,冷却塔D,28.5,0.42}};inttotalsizeof(devices)/sizeof(Device);cout 设备巡检温度阈值80度压力1.2MPaendl;for(inti0;itotal;i){cout设备devices[i].id(devices[i].name) Tdevices[i].temp度 Pdevices[i].pressureMPa: ;processDevice(devices[i],80.0,1.2);}return0;}四、递归函数调用自己递归的核心思想把一个大问题分解成结构相似的小问题直到遇到基础情形Base Case为止。1. 递归的基本结构每个递归函数必须包含基础情形Base Case递归终止条件直接返回结果递归调用Recursive Call把问题缩小后调用自身结果合并把递归得到的子问题结果组合成当前问题的答案// 经典例子计算阶乘// n! n * (n-1)!基础情形0! 1, 1! 1intfactorial(intn){if(n1)return1;// 基础情形returnn*factorial(n-1);// 递归调用 结果合并}2. 工业应用分层设备组递归巡检真实工厂中设备按组管理工厂包含多个车间车间包含多条生产线生产线包含多台设备。这种嵌套结构天然适合递归处理。#includeiostream#includecstringusingnamespacestd;// 简化的设备组结构每个组可以包含若干子组和若干设备structGroup{charname[30];// 组名称如一号车间、生产线Aintlevel;// 层级1工厂 2车间 3生产线 4设备intchildCount;// 子组数量intchildren[10];// 子组索引简化用数组doubletemperature;// 仅叶子节点设备有温度intdeviceId;// 仅叶子节点设备有ID};// 递归巡检从指定组开始遍历其下所有子节点voidinspectGroup(Group groups[],intgroupIndex,intdepth){// 打印缩进表示层级for(inti0;idepth;i)cout ;Groupggroups[groupIndex];// 基础情形是设备叶子节点if(g.level4){cout[设备g.deviceId] g.name 温度g.temperature度;if(g.temperature80.0)cout [超温!];coutendl;return;}// 递归情形是组节点先打印组名再递归处理所有子组cout[组g.level] g.nameendl;for(inti0;ig.childCount;i){inspectGroup(groups,g.children[i],depth1);// 递归调用}}intmain(){// 构建简化设备树// 工厂(0) - 车间A(1) - 生产线1(3) - 设备101(5)、设备102(6)// - 生产线2(4) - 设备103(7)、设备104(8)// 车间B(2) - 生产线3(9) - 设备201(10)、设备202(11)Group g[]{{总工厂,1,2,{1,2},0,0},{车间A,2,2,{3,4},0,0},{车间B,2,1,{9},0,0},{生产线1,3,2,{5,6},0,0},{生产线2,3,2,{7,8},0,0},{加热炉101,4,0,{},78.5,101},{冷却塔102,4,0,{},28.5,102},{反应釜103,4,0,{},125.5,103},{阀门104,4,0,{},45.5,104},{生产线3,3,2,{10,11},0,0},{加热炉201,4,0,{},85.5,201},{冷却塔202,4,0,{},30.2,202}};cout 全厂设备巡检报告 endl;inspectGroup(g,0,0);// 从根节点索引0开始遍历return0;}核心要点每个节点的处理方式相同打印信息然后对每个子节点递归处理基础情形保证递归一定能终止叶子设备不再继续递归递归深度受限于调用栈大小通常几千层内安全五、独家C#语法对照对比维度CC#工业开发差异函数重载同名函数不同参数同名方法不同参数概念一致C#同样支持重载默认参数void f(int x 10)从右向左设置void F(int x 10)同样规则两者几乎相同函数指针typedef int (*Func)(int, int)委托Delegate / Func, / ActionC#委托更安全、更强大内置事件机制匿名函数后续学习lambda表达式(x, y) x ylambdaC# lambda语法更简洁递归函数直接调用自己方法直接调用自己两者一致都受栈深度限制重载决策编译器根据实参类型匹配编译器根据实参类型匹配两者重载规则几乎相同六、重读专属进阶函数六大易错点坑1重载歧义— 两个重载版本都能匹配编译器不知道选哪个voidf(intx){}voidf(doublex){}f(10L);// long可以转int也可以转double歧义坑2默认参数与重载冲突— 默认参数造成与重载版本重叠坑3递归没有基础情形— 缺少终止条件导致栈溢出崩溃坑4递归深度过大— 嵌套几千层也会栈溢出可改用迭代或动态规划坑5函数指针类型不匹配— 函数签名不同不能互相赋值坑6返回值被忽略— 函数返回重要状态但调用方不检查应显式处理或用void七、原书课后习题解析习题用递归计算斐波那契数列的第N项#includeiostreamusingnamespacestd;// 递归版本F(0)0, F(1)1, F(n)F(n-1)F(n-2)intfibonacci(intn){if(n0)return0;if(n1)return1;returnfibonacci(n-1)fibonacci(n-2);}// 非递归版本效率更高工业代码推荐intfibonacciIter(intn){if(n0)return0;if(n1)return1;inta0,b1,c;for(inti2;in;i){cab;ab;bc;}returnb;}intmain(){coutF(0)~F(10): ;for(inti0;i10;i)coutfibonacci(i) ;coutendl;coutF(20) fibonacciIter(20)endl;return0;}核心考点递归基础结构基础情形 递归调用 合并。注意纯递归斐波那契效率极低指数级工业中应用迭代或动态规划。这说明了一个重要原则递归表达思路简洁但实际工程中要评估性能必要时转为循环实现。本篇总结函数重载允许同名函数处理不同类型参数编译器根据实参自动匹配默认参数从右向左连续设置简化常用场景的调用函数指针把函数地址存起来可以动态选择处理逻辑实现策略模式/回调递归把大问题分解为同类小问题必须有基础情形保证终止工业中递归常用于树结构、分层数据、分治场景但要注意栈深度和性能重载 默认参数 函数指针 递归四项组合使用能写出非常灵活的代码下篇预告下一篇第十七篇名称空间与代码组织。学习namespace关键字解决多人协作开发中的命名冲突、using声明与编译指令、名称空间嵌套与匿名空间、配合模块化项目结构掌握大型项目代码组织的核心手段。