
全文连载前置回顾(前15篇完整知识链路)
- 1-6篇:开发环境、基础数据类型、运算符全集
- 7-10篇:分支结构、三大循环、数组、字符数组与字符串
- 11-12篇:指针基础与进阶、三类const指针、指针数组与内存地址
- 13-14篇:基础输入输出、结构体与自定义数据类型
- 15篇:函数基础、参数传递机制、模块化编程
上一篇我们掌握了函数的基础语法、声明与定义、参数传递的三种方式、返回值形式。但C++函数的能力远不止于此。真实工业开发中还需要:同名函数处理不同类型、省略常用参数、动态选择处理逻辑、把函数作为参数传递给另一个函数等高级能力。
前言
函数进阶的四大核心能力是函数重载、默认参数、函数指针、递归。它们让函数的使用更加灵活、更加智能。
函数重载(Function Overloading):同一个函数名,根据参数类型/数量自动匹配合适的实现。比如一个print函数,可以传int、double、字符串、结构体,编译器自动选择对应版本。
默认参数(Default Arguments):给某些参数指定默认值,调用时可以省略------最常用的参数不需要每次都写。
函数指针(Function Pointer):把函数的地址存储在指针变量中,可以作为参数传递、动态选择调用。这是实现回调函数、策略模式的基础。
递归(Recursion):函数调用自己来解决可以分解为更小同类问题的场景。常用于树结构遍历、分层数据处理、分治算法。
本篇逐一讲解这四项能力,每一项都配合工业场景说明实际应用价值。
一、函数重载:同名函数处理不同类型
函数重载允许在同一个作用域内定义多个同名函数,但它们的参数列表必须不同(参数的类型、数量或顺序不同)。编译器根据调用时的实参自动匹配。
1. 重载的基本用法
cpp
#include <iostream>
#include <cstring>
using namespace std;
// 重载版本1:打印整数
void print(int value)
{
cout << "[整数] " << value << endl;
}
// 重载版本2:打印浮点数
void print(double value)
{
cout << "[浮点数] " << value << endl;
}
// 重载版本3:打印字符串
void print(const char* str)
{
cout << "[字符串] " << str << endl;
}
struct Device
{
int id;
char name[30];
double temp;
};
// 重载版本4:打印设备结构体
void print(const Device& dev) // &是引用,后续详解,这里可先当作安全指针
{
cout << "[设备] ID=" << dev.id << " 名称=" << dev.name
<< " 温度=" << dev.temp << "度" << endl;
}
int main()
{
print(1001); // 自动匹配版本1
print(82.5); // 自动匹配版本2
print("加热炉A"); // 自动匹配版本3
Device d = {1001, "加热炉A", 82.5};
print(d); // 自动匹配版本4
return 0;
}
重载规则 :重载必须通过参数区分,不能通过返回值区分。下面这种写法是错误的:
cpp
int getValue() { return 10; }
double getValue() { return 10.5; } // 错误!仅返回值不同不能重载
2. 重载在工业代码中的应用
设备数据处理是典型场景------同一个处理函数名,根据数据类型自动选择不同实现:
cpp
#include <iostream>
#include <cstring>
using namespace std;
struct SensorData
{
int sensorId;
double value;
char unit[10];
};
struct DeviceAlert
{
int deviceId;
char message[100];
int level; // 1=警告 2=严重 3=紧急
};
void processData(int rawValue) // 处理原始整数采集
{
cout << "[整型采集] 值=" << rawValue << " (存入寄存器)" << endl;
}
void processData(double calibratedValue) // 处理校准后的浮点数
{
cout << "[浮点数据] 值=" << calibratedValue << " (写入数据库)" << endl;
}
void processData(const SensorData& data) // 处理完整传感器数据包
{
cout << "[传感器 " << data.sensorId << "] "
<< data.value << data.unit << endl;
}
void processData(const DeviceAlert& alert) // 处理报警信息
{
cout << "[报警] 设备" << alert.deviceId
<< " 级别=" << alert.level
<< " 消息=" << alert.message << endl;
}
int main()
{
int raw = 4095;
double cal = 82.5;
SensorData s = {101, 82.5, "C"};
DeviceAlert a = {1001, "温度超阈值", 2};
processData(raw);
processData(cal);
processData(s);
processData(a);
return 0;
}
设计价值 :对外提供统一的processData接口,调用者不需要记住多个函数名。内部实现可以自由优化而不影响调用方。
二、默认参数:简化常用调用
1. 基本用法
在函数声明中给某些参数指定默认值。调用时如果省略这些参数,就使用默认值。
cpp
#include <iostream>
using namespace std;
// 声明中指定默认参数:默认温度阈值80度,默认压力上限1.2MPa
void checkDevice(int deviceId, double temperature,
double tempLimit = 80.0, double pressureLimit = 1.2)
{
cout << "设备" << deviceId << ": ";
if (temperature > tempLimit)
cout << "温度" << temperature << "度 超过阈值" << tempLimit << "度!";
else
cout << "温度" << temperature << "度 正常";
cout << endl;
}
int main()
{
checkDevice(1001, 75.0); // 用默认的温度阈值
checkDevice(1002, 85.5); // 用默认阈值
checkDevice(1003, 125.0, 120.0); // 反应釜:自定义高温阈值
checkDevice(1004, 28.5, 45.0); // 冷却设备:低温阈值
return 0;
}
关键规则:默认参数必须从右向左连续设置。
cpp
// 正确:从右开始连续
void func(int a, int b = 10, int c = 20);
// 错误:跳过了b却给c设默认值
void func(int a, int b = 10, int c); // 编译错误!
工业最佳实践 :默认参数写在声明中(头文件),不要在定义中重复写(部分编译器允许,但不统一)。
三、函数指针:把函数当作数据
函数本质上是一段可执行代码,它在内存中也有地址。函数指针就是存储这个地址的变量。
1. 函数指针的声明语法
cpp
// 普通函数:接收两个int,返回int
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
// 函数指针类型:指向"接收两个int返回int"的函数
typedef int (*MathFunc)(int, int);
语法记忆:返回值类型 (*指针变量名)(参数类型列表)。括号不能省略,否则就变成返回指针的函数了。
2. 通过函数指针调用
cpp
#include <iostream>
using namespace std;
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int subtract(int a, int b) { return a - b; }
typedef int (*MathFunc)(int, int);
int main()
{
MathFunc op; // op是一个函数指针变量
// 让op指向add函数
op = add;
cout << "10 + 5 = " << op(10, 5) << endl; // 通过指针调用
// 让op指向multiply函数
op = multiply;
cout << "10 * 5 = " << op(10, 5) << endl;
// 让op指向subtract
op = subtract;
cout << "10 - 5 = " << op(10, 5) << endl;
return 0;
}
3. 函数指针作为参数(策略模式)
这是工业代码中最重要的应用:把处理逻辑作为参数传入,实现策略选择。
cpp
#include <iostream>
#include <cstring>
using namespace std;
struct Device { int id; char name[30]; double temp; double press; };
// 不同的处理策略
void strategyNormal(Device* d)
{
cout << d->name << ": 正常运行,继续监控" << endl;
}
void strategyWarn(Device* d)
{
cout << d->name << ": 注意,参数接近阈值,加强监控" << endl;
}
void strategyShutdown(Device* d)
{
cout << d->name << ": 紧急!超过安全阈值,立即停机" << endl;
}
typedef void (*Strategy)(Device*);
// 根据温度选择策略并执行
void processDevice(Device* d, double tempLimit, double pressLimit)
{
Strategy strat;
if (d->temp > tempLimit || d->press > pressLimit)
strat = strategyShutdown;
else if (d->temp > tempLimit * 0.9 || d->press > pressLimit * 0.9)
strat = strategyWarn;
else
strat = strategyNormal;
strat(d); // 调用选中的策略
}
int main()
{
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}
};
int total = sizeof(devices)/sizeof(Device);
cout << "=== 设备巡检(温度阈值80度,压力1.2MPa)===" << endl;
for (int i = 0; i < total; i++)
{
cout << "设备" << devices[i].id << "(" << devices[i].name
<< ") T=" << devices[i].temp << "度 P=" << devices[i].pressure << "MPa: ";
processDevice(&devices[i], 80.0, 1.2);
}
return 0;
}
四、递归:函数调用自己
递归的核心思想:把一个大问题分解成结构相似的小问题,直到遇到基础情形(Base Case)为止。
1. 递归的基本结构
每个递归函数必须包含:
- 基础情形(Base Case):递归终止条件,直接返回结果
- 递归调用(Recursive Call):把问题缩小后调用自身
- 结果合并:把递归得到的子问题结果组合成当前问题的答案
cpp
// 经典例子:计算阶乘
// n! = n * (n-1)!,基础情形:0! = 1, 1! = 1
int factorial(int n)
{
if (n <= 1) return 1; // 基础情形
return n * factorial(n - 1); // 递归调用 + 结果合并
}
2. 工业应用:分层设备组递归巡检
真实工厂中设备按组管理:工厂包含多个车间,车间包含多条生产线,生产线包含多台设备。这种嵌套结构天然适合递归处理。
cpp
#include <iostream>
#include <cstring>
using namespace std;
// 简化的设备组结构:每个组可以包含若干子组和若干设备
struct Group
{
char name[30]; // 组名称:如"一号车间"、"生产线A"
int level; // 层级:1=工厂 2=车间 3=生产线 4=设备
int childCount; // 子组数量
int children[10]; // 子组索引(简化用数组)
double temperature; // 仅叶子节点(设备)有温度
int deviceId; // 仅叶子节点(设备)有ID
};
// 递归巡检:从指定组开始,遍历其下所有子节点
void inspectGroup(Group groups[], int groupIndex, int depth)
{
// 打印缩进(表示层级)
for (int i = 0; i < depth; i++) cout << " ";
Group& g = groups[groupIndex];
// 基础情形:是设备(叶子节点)
if (g.level == 4)
{
cout << "[设备" << g.deviceId << "] " << g.name
<< " 温度=" << g.temperature << "度";
if (g.temperature > 80.0) cout << " [超温!]";
cout << endl;
return;
}
// 递归情形:是组节点,先打印组名,再递归处理所有子组
cout << "[组" << g.level << "] " << g.name << endl;
for (int i = 0; i < g.childCount; i++)
{
inspectGroup(groups, g.children[i], depth + 1); // 递归调用
}
}
int main()
{
// 构建简化设备树:
// 工厂(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)开始遍历
return 0;
}
核心要点:
- 每个节点的处理方式相同:打印信息,然后对每个子节点递归处理
- 基础情形保证递归一定能终止(叶子设备不再继续递归)
- 递归深度受限于调用栈大小(通常几千层内安全)
五、独家C#语法对照
| 对比维度 | C++ | C# | 工业开发差异 |
|---|---|---|---|
| 函数重载 | 同名函数不同参数 | 同名方法不同参数 | 概念一致,C#同样支持重载 |
| 默认参数 | void f(int x = 10),从右向左设置 |
void F(int x = 10),同样规则 |
两者几乎相同 |
| 函数指针 | typedef int (*Func)(int, int) |
委托Delegate / Func<,> / Action | C#委托更安全、更强大,内置事件机制 |
| 匿名函数 | 后续学习lambda表达式 | (x, y) => x + y lambda |
C# lambda语法更简洁 |
| 递归 | 函数直接调用自己 | 方法直接调用自己 | 两者一致,都受栈深度限制 |
| 重载决策 | 编译器根据实参类型匹配 | 编译器根据实参类型匹配 | 两者重载规则几乎相同 |
六、重读专属:进阶函数六大易错点
-
坑1:重载歧义 --- 两个重载版本都能匹配,编译器不知道选哪个
cppvoid f(int x) {} void f(double x) {} f(10L); // long可以转int也可以转double,歧义! -
坑2:默认参数与重载冲突 --- 默认参数造成与重载版本重叠
-
坑3:递归没有基础情形 --- 缺少终止条件导致栈溢出崩溃
-
坑4:递归深度过大 --- 嵌套几千层也会栈溢出(可改用迭代或动态规划)
-
坑5:函数指针类型不匹配 --- 函数签名不同不能互相赋值
-
坑6:返回值被忽略 --- 函数返回重要状态但调用方不检查(应显式处理或用void)
七、原书课后习题解析
习题:用递归计算斐波那契数列的第N项
cpp
#include <iostream>
using namespace std;
// 递归版本:F(0)=0, F(1)=1, F(n)=F(n-1)+F(n-2)
int fibonacci(int n)
{
if (n <= 0) return 0;
if (n == 1) return 1;
return fibonacci(n - 1) + fibonacci(n - 2);
}
// 非递归版本(效率更高,工业代码推荐)
int fibonacciIter(int n)
{
if (n <= 0) return 0;
if (n == 1) return 1;
int a = 0, b = 1, c;
for (int i = 2; i <= n; i++)
{
c = a + b;
a = b;
b = c;
}
return b;
}
int main()
{
cout << "F(0)~F(10): ";
for (int i = 0; i <= 10; i++)
cout << fibonacci(i) << " ";
cout << endl;
cout << "F(20) = " << fibonacciIter(20) << endl;
return 0;
}
核心考点:递归基础结构(基础情形 + 递归调用 + 合并)。注意纯递归斐波那契效率极低(指数级),工业中应用迭代或动态规划。这说明了一个重要原则:递归表达思路简洁,但实际工程中要评估性能,必要时转为循环实现。
本篇总结
- 函数重载允许同名函数处理不同类型参数,编译器根据实参自动匹配
- 默认参数从右向左连续设置,简化常用场景的调用
- 函数指针把函数地址存起来,可以动态选择处理逻辑,实现策略模式/回调
- 递归把大问题分解为同类小问题,必须有基础情形保证终止
- 工业中递归常用于树结构、分层数据、分治场景,但要注意栈深度和性能
- 重载 + 默认参数 + 函数指针 + 递归,四项组合使用能写出非常灵活的代码
下篇预告
下一篇第十七篇:名称空间与代码组织。学习namespace关键字解决多人协作开发中的命名冲突、using声明与编译指令、名称空间嵌套与匿名空间、配合模块化项目结构,掌握大型项目代码组织的核心手段。