【C++ 从基础到项目实战】C++(三):函数进阶——重载、回调、递归与默认参数

📌 阅读时长: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 篇。关注我,不错过后续更新。

相关推荐
爱喝水的鱼丶1 小时前
SAP-ABAP:SAP 简单报表输出开发系列(共6篇)第二篇:SAP 报表数据筛选优化:选择屏幕自定义与查询效率提升
开发语言·数据库·学习·性能优化·sap·abap
HappyAcmen1 小时前
8.角色 Prompt 模板
开发语言·python·prompt
西梅汁1 小时前
C++ 线程间通信(二)
c++
minji...1 小时前
Linux 高级IO(七)多进程、多线程的Reactor反应堆模式扩展、OTOL
linux·运维·c++·多路转接·epoll·reactor反应堆模型
晚风吹红霞1 小时前
C++ list 容器完全指南:从入门到手撕双向链表
c++·链表·list
handler011 小时前
【Linux 网络】:poll/epoll 底层机制与 Reactor 并发模型
linux·运维·服务器·网络·c++·多路转接·多路复用
oort1231 小时前
VLStream 全开源决策式 AI 视频平台 技术视角完整说明
大数据·开发语言·人工智能·经验分享·python·开源·音视频
Cloud_Shy6181 小时前
解读《Effective Python 3rd Edition》:从练气到老魔(第二章 Item 10 - 12)
c语言·开发语言·网络·人工智能·windows·python·编辑器
cpp_25011 小时前
P10109 [GESP202312 六级] 工作沟通
数据结构·c++·算法·题解·洛谷·gesp六级