6、函数基础:函数定义与声明、参数传递、返回值、函数重载

函数这东西,说白了就是C++里的「积木块」。你写程序不可能把所有代码都塞在main里,那会乱成一锅粥。我刚开始学的时候,就喜欢把所有逻辑堆在一起,结果改一个bug要翻半天——后来被前辈骂了一顿,才老老实实学函数。

今天咱们就把函数这块彻底聊透。从定义声明,到参数怎么传,再到重载那些弯弯绕绕,我一个一个给你拆开讲。

6.1 函数定义与声明

先看一个最简单的函数长什么样:

// 函数定义
int add(int a, int b) {
    return a + b;
}

这玩意儿分三部分:返回类型(int)、函数名(add)、参数列表(int a, int b)。花括号里是函数体,干活的。

那声明又是啥?声明就是告诉编译器:「嘿,有个函数叫add,它长这样,但具体实现你等会儿再看。」

// 函数声明(也叫函数原型)
int add(int a, int b);

// 主函数
int main() {
    int result = add(3, 5);
    return 0;
}

// 函数定义放在后面
int add(int a, int b) {
    return a + b;
}

为什么要分开?因为C++编译是自上而下的。如果你把add的定义放在main后面,编译器看到add(3,5)时还不认识它,就会报错。声明就是提前打个招呼。

我的习惯:头文件里放声明,源文件里放定义。这样别人想看接口,直接翻头文件就行,不用去翻实现细节。

6.2 参数传递:值传递 vs 引用传递

这是新手最容易踩坑的地方。我当年就吃过亏,写了个交换函数,结果发现外面变量纹丝不动——因为传的是值,不是引用。

6.2.1 值传递

值传递就是把实参的副本传给函数。函数里怎么改,都不影响外面的变量。

void swap(int x, int y) {
    int temp = x;
    x = y;
    y = temp;
    // 这里x和y确实交换了,但外面a和b没变
}

int main() {
    int a = 10, b = 20;
    swap(a, b);
    // a还是10,b还是20
    return 0;
}

为什么会这样?因为函数内部操作的是a和b的「复印件」,原件完好无损。你想想看,你复印了一份合同,在复印件上乱涂乱画,原件会变吗?不会。

6.2.2 引用传递

引用传递就不一样了。它传的是变量的「别名」,函数里操作的就是外面的变量本身。

void swap(int &x, int &y) {
    int temp = x;
    x = y;
    y = temp;
    // 这次是真的交换了
}

int main() {
    int a = 10, b = 20;
    swap(a, b);
    // a变成20,b变成10
    return 0;
}

注意那个&符号,它就是引用的标志。加了它,x就是a的别名,改x就是改a。

核心区别:
  • 值传递:安全,但拷贝大对象时性能差
  • 引用传递:高效,但函数内部可能意外修改外部变量
我曾经踩过的坑:有一次写一个处理大数组的函数,用了值传递,结果每次调用都要拷贝整个数组,程序慢得像蜗牛。后来改成const引用,速度直接起飞。记住:大对象尽量用const引用传参。

6.3 返回值

函数干完活,总得给个结果吧?返回值就是干这个的。

int max(int a, int b) {
    return (a > b) ? a : b;
}

这里return后面跟的就是要返回的值。类型必须和函数声明里的返回类型一致。

但也有特殊情况——void函数,它不返回任何东西:

void printMessage(const std::string &msg) {
    std::cout << msg << std::endl;
    // 没有return语句,或者可以写 return;
}

嗯,这里要注意:void函数里也可以写return,但后面不能跟值,作用只是提前结束函数。

还有一点,返回引用也是可以的:

int& getElement(int arr[], int index) {
    return arr[index];
}

int main() {
    int data[] = {1, 2, 3};
    getElement(data, 1) = 100;  // 直接修改数组元素
    // data变成 {1, 100, 3}
    return 0;
}

但千万注意:不要返回局部变量的引用。局部变量在函数结束时就销毁了,返回它的引用会得到悬空引用,程序随时可能崩溃。

我的建议:除非你很清楚自己在做什么,否则优先返回值,而不是返回引用。安全第一。

6.4 函数重载

函数重载,说白了就是「同名不同人」。同一个函数名,参数不同,干不同的事。

int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

int add(int a, int b, int c) {
    return a + b + c;
}

这三个函数都叫add,但参数个数或类型不同。编译器会根据你调用时传的参数,自动匹配最合适的那个。

重载的规则其实很简单:

  • 参数个数不同
  • 参数类型不同
  • 参数顺序不同

但注意:返回值类型不能作为重载的依据。下面这样是不行的:

int getValue();
double getValue();  // 编译错误!

为什么?因为调用时如果写getValue(),编译器不知道你要int还是double,它没法区分。

重载的底层原理:编译器会把函数名和参数类型组合成一个「修饰名」。比如add(int, int)可能变成add_int_int,add(double, double)变成add_double_double。这样链接器就能区分它们了。

我在项目中经常用重载来提供「便捷接口」。比如一个打印函数,可以传int、double、string,都叫print,但内部实现不同。调用者不用记一堆名字,直接print(10)、print(3.14)、print("hello")就行,多清爽。

注意歧义:如果重载的函数参数类型太接近,比如int和long,或者float和double,调用时可能产生歧义。编译器会报错,让你自己选。这时候加个强制类型转换就能解决。

知识体系总览

下面这张图把函数基础的核心知识点串起来了,你可以对照着复习:

函数基础 定义与声明 函数定义(实现) 函数声明(原型) 参数传递 值传递(副本) 引用传递(别名) 返回值 返回值(拷贝) 返回引用(小心) 函数重载 参数个数不同 参数类型不同 参数顺序不同 返回值类型不能作为重载依据

函数这块内容其实不难,但细节很多。你写代码的时候多想想:这个参数该传值还是传引用?这个函数要不要重载几个版本?想清楚了,代码质量自然就上去了。

我个人觉得,函数是C++里最基础也最重要的抽象手段。把功能拆成一个个小函数,每个函数只干一件事,代码会变得特别好维护。你试试看,写一个月之后,你会爱上这种感觉的。


公众号:蓝海资料掘金营,微信deep3321