学到函数这一章,我终于感受到C++比C"进化"了多少。
C语言的函数很朴素:定义、调用、传参、返回。而C++在这个基础上,加了一大堆好用的特性——函数重载、默认参数、内联函数、引用传参…每一个都能让代码更简洁、更优雅。
这篇就聊聊从C转C++时,函数这块让我眼前一亮的那些新特性。
一、老规矩:C里就有的东西
先快速过一遍,C语言里的函数特性,C++基本都保留了:
函数定义和声明
参数传递(值传递、地址传递)
返回值
递归
函数指针
作用域和生存期
全局变量、局部变量、静态变量
这些和C基本一样,有C基础的话不用重新学。
过渡感悟:刚开始学觉得C++函数不就是多了几个语法糖嘛,后来才发现——这些"糖"加在一起,编程的思维方式都跟着变了。
二、C++新增的函数特性

  1. 函数重载
    这绝对是我最喜欢的特性之一!
    同一个函数名,可以有多个版本,只要参数列表不一样就行:
    // 两个int相加
    int add(int a, int b) {
    return a + b;
    }
    // 三个int相加
    int add(int a, int b, int c) {
    return a + b + c;
    }
    // 两个double相加
    double add(double a, double b) {
    return a + b;
    }
    调用的时候,编译器会根据你传的参数自动选对应的版本:
    add(1, 2); // 调用第一个
    add(1, 2, 3); // 调用第二个
    add(1.5, 2.5); // 调用第三个
    C语言里怎么办? 得给每个函数起不同的名字:add_int、add_double、add_int3…又丑又难记。 重载的规则:
    函数名必须相同
    参数列表必须不同(个数不同、类型不同、顺序不同)
    返回值不同不算重载!
    int func(int a);
    void func(int a); // 错误!返回值不同不算重载
  2. 默认参数
    函数参数可以有默认值,调用的时候不传就用默认的:
    // 声明默认参数
    void print(int a, int b = 10, int c = 20) {
    cout << a << " " << b << " " << c << endl;
    }

print(1); // 输出:1 10 20
print(1, 2); // 输出:1 2 20
print(1, 2, 3); // 输出:1 2 3
默认参数的规则:
默认参数必须从右往左依次定义,不能中间跳一个
默认参数在声明里写,定义里别重复写(如果声明和定义分开的话)
默认参数的值必须是编译期能确定的(常量、全局变量、函数调用等)
// 错误!中间的b没有默认参数,c不能有
void func(int a, int b, int c = 10); // 对
void func(int a, int b = 10, int c); // 错!
使用心得:默认参数用好了能少写好多重载函数。比如一个打印函数,默认打印到屏幕,也可以指定打印到文件。
3. 内联函数
C语言里我们用宏定义来代替小函数,减少函数调用开销:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
但宏有好多坑:没有类型检查、运算符优先级问题、副作用问题…
C++有了更好的选择——内联函数:
inline int max(int a, int b) {
return a > b ? a : b;
}
内联函数在编译的时候会把函数体直接展开到调用的地方,没有函数调用的开销,同时又有函数的类型检查和语法检查。
注意:inline 只是给编译器的一个建议,不是强制的。编译器觉得函数太复杂或者太大,就会忽略inline,当成普通函数处理。
还有,内联函数的定义要放在头文件里,因为编译器需要看到完整的定义才能展开。
4. 引用传参
这又是一个超级实用的特性!
C语言里,如果想在函数里修改实参的值,只能传指针:
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
swap(&x, &y); // 调用的时候还要取地址
C++里有了引用,传参更直观了:
void swap(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
swap(x, y); // 直接传变量,不用取地址
引用就像是变量的"别名",操作引用就是操作原变量。传引用和传指针底层其实差不多,但写起来更优雅,也不容易出空指针的问题。
什么时候用引用传参?
需要修改实参的值 → 用引用
不需要修改,但对象很大(比如结构体、类对象)→ 用const引用,避免拷贝开销
小的基本类型(int、double)→ 直接值传递就行,没必要用引用
引用 vs 指针:
引用必须初始化,不能为"空",更安全
引用一旦绑定就不能改指向,指针可以
引用用起来更直观,像普通变量一样
指针更灵活,但也更容易出错
三、其他值得注意的变化

  1. 函数必须有返回类型
    C语言里,函数如果不写返回类型,默认是int:
    func() { // C里默认返回int
    return 10;
    }
    C++里不行,所有函数都必须明确写返回类型,没有默认int这一说。
  2. 空参数就是空参数
    C语言里,void func() 其实是"参数个数不确定"的意思,不是"没有参数"。想要没有参数得写 void func(void)。
    C++里,void func() 就是"没有参数"的意思,和 void func(void) 一样。
  3. const 修饰函数(成员函数)
    这个是类的成员函数才有的特性,后面讲类的时候会细说。简单说就是在函数后面加const,表示这个函数不会修改成员变量:
    class Person {
    string name;
    public:
    string get_name() const { // const成员函数,不能修改name
    return name;
    }
    };
    四、从C到C++的函数编程思维转变
  4. 从"起不同名字"到"重载"
    C里处理不同类型的相似逻辑,得给函数起不同的名字。C++用重载,同一个名字搞定,调用方也不用记那么多函数名。
  5. 从"指针传参"到"引用传参"
    C里想修改实参、想避免大对象拷贝,都得用指针。C++里可以用引用,更直观、更安全。
  6. 从"宏"到"内联函数"
    C里用宏代替小函数,坑多。C++用内联函数,既有宏的效率,又有函数的安全。
  7. 从"一套代码一套类型"到"泛型编程"
    函数模板让一套逻辑能适配多种类型,代码复用率大大提高。
    五、我的踩坑记录
    坑1:函数重载和默认参数冲突
    void func(int a);
    void func(int a, int b = 10);
    func(5); // 调用哪个?编译器懵了,报错!
    既有重载又有默认参数的时候,小心出现二义性。
    坑2:引用绑定到临时对象
    int& func() {
    int a = 10;
    return a; // 错误!返回局部变量的引用,函数结束a就销毁了
    }
    返回局部变量的引用或指针都是危险行为,和C里返回局部变量指针是一样的坑。
    坑3:const引用传参传了右值
    这个不是坑,是特性。const引用可以绑定到临时对象(右值):
    void print(const int& x) {
    cout << x << endl;
    }
    print(10); // 可以,10是临时对象,const引用能绑定
    普通的非const引用不行:
    void print(int& x) { … }
    print(10); // 错误!不能把临时对象绑到非const引用
    函数这一章学完,我最大的感受是:C++真的很"宠"程序员——为了让你写代码更舒服、更安全、更高效,加了好多贴心的特性。
    重载让你不用起一堆相似的函数名,默认参数让你少写好多重复代码,引用让传参更直观,内联函数解决了宏的安全问题…每一个特性都直击C语言的痛点。
    但这些特性也不是银弹——用不好反而会让代码更复杂。比如函数重载太多,调用哪个全靠猜;默认参数用得太随意,接口变得不清晰。
    还是那句话:工具是死的,人是活的。了解每个特性的优缺点,在合适的场景用合适的特性,才是正道。

更多推荐