10. 函数(一):函数定义与声明、函数参数传递、return语句、函数原型

函数这东西,说白了就是C语言的积木块。你把一段逻辑封装起来,给它起个名字,以后想用的时候直接喊它一声就行。我刚开始学的时候,总觉得写函数麻烦,所有代码都往main里塞。后来项目一复杂,自己都看不懂自己写的啥了……嗯,从那以后,我养成了“功能模块化”的习惯。

10.1 函数的定义与声明

先说说定义。函数定义就是告诉编译器:“嘿,我这有个功能模块,它长这样,你记好了。” 格式其实挺固定的:

返回值类型 函数名(参数列表) {
    // 函数体
    // 各种操作
    return 返回值;
}

举个例子,写一个计算两个整数和的函数:

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

这里 int 是返回值类型,add 是函数名,int a, int b 是参数列表。函数体里做了加法,最后用 return 把结果送回去。

那声明又是什么?声明就是提前打个招呼:“注意了,后面我会用到这个函数,它长这样,别到时候不认识。” 声明通常放在文件开头或者头文件里:

int add(int a, int b);  // 这就是函数原型

我个人习惯,不管函数定义在main前面还是后面,都会先写声明。为什么?因为这样代码结构清晰,别人一看就知道这个文件里有哪些“积木块”。

核心区别:定义是“造积木”,声明是“告诉别人有这块积木”。定义只能有一次,声明可以有多次。

10.2 函数原型

函数原型其实就是函数的“身份证”。它包含了函数名、返回值类型、参数类型和个数。编译器看到原型,就知道怎么调用这个函数了。

原型长这样:

返回值类型 函数名(参数类型1, 参数类型2, ...);

注意,原型里可以只写参数类型,不写参数名。比如:

int add(int, int);  // 合法,编译器只关心类型

但我建议你写上参数名。为什么?因为可读性更好。你想想看,int add(int, int)int add(int a, int b),后者一看就知道是加两个数。

注意:如果函数定义在调用之后,而且没有提前声明,编译器会报错或者给出警告。我曾经在项目里吃过这个亏——函数定义写在文件末尾,调用写在前面,结果编译报了一堆“隐式声明”的错。从那以后,我写任何函数都先写原型。

10.3 函数参数传递

参数传递,说白了就是“怎么把数据交给函数”。C语言里只有一种传递方式:值传递。嗯,你没看错,只有这一种。

值传递的意思是:调用函数时,把实参的值复制一份给形参。函数内部操作的是副本,不影响原来的变量。

#include <stdio.h>

void swap(int x, int y) {
    int temp = x;
    x = y;
    y = temp;
    printf("函数内部:x=%d, y=%d\n", x, y);
}

int main() {
    int a = 5, b = 10;
    printf("调用前:a=%d, b=%d\n", a, b);
    swap(a, b);
    printf("调用后:a=%d, b=%d\n", a, b);
    return 0;
}

运行结果:

调用前:a=5, b=10
函数内部:x=10, y=5
调用后:a=5, b=10

看到了吗?函数内部确实交换了,但main里的a和b纹丝不动。这就是值传递——你给函数的是复印件,原件还在自己手里。

小技巧:如果想在函数内部修改外部变量,怎么办?用指针。把变量的地址传进去,函数通过地址操作原数据。这个后面会详细讲,先记住这个思路。

10.4 return语句

return 的作用有两个:一是返回结果,二是结束函数执行。格式很简单:

return 表达式;

或者:

return;

如果函数返回值类型是 void,那就用第二种,表示“我不返回任何东西,只是结束”。

举个例子:

int max(int a, int b) {
    if (a > b) {
        return a;  // 如果a大,直接返回a,函数结束
    }
    return b;      // 否则返回b
}

这里有两个 return,但每次只会执行一个。函数执行到第一个 return 就结束了,后面的代码不会执行。

避坑指南:我曾经写过这样的代码:

int func() {
    int x = 10;
    // 忘记写return了
}

结果函数返回了一个不确定的值。因为函数没有 return,它会返回栈上残留的随机数。这种bug特别难找,因为有时候“运气好”返回的值刚好是对的,有时候又不对。所以,非void函数一定要保证所有路径都有 return

10.5 知识体系总览

下面这张图,把本章的核心知识点串起来了。你可以把它当作一个“函数入门地图”:

函数(一) 函数定义 vs 函数声明 定义:造积木 包含完整函数体 声明:打招呼 只有函数原型 参数传递:值传递 传副本,不改原件 想改原件?用指针! return语句 返回结果 + 结束函数

10.6 综合示例

最后,来个完整的例子,把今天讲的东西串起来:

#include <stdio.h>

// 函数原型声明
int add(int a, int b);
void print_result(int x, int y, int sum);

int main() {
    int num1 = 15, num2 = 25;
    int result = add(num1, num2);  // 调用add函数
    print_result(num1, num2, result);
    return 0;
}

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

// 函数定义:打印结果
void print_result(int x, int y, int sum) {
    printf("%d + %d = %d\n", x, y, sum);
}

运行结果:

15 + 25 = 40

这个例子虽然简单,但包含了函数声明、定义、参数传递、return语句所有要素。你想想看,如果以后要写一个计算器程序,是不是可以把加减乘除分别写成函数?然后main里只需要调用就行了。代码瞬间变得清爽。

我的建议:刚开始学函数,别怕多写。哪怕只有两行逻辑,也值得封装成一个函数。养成这个习惯后,你会发现调试和阅读代码都轻松很多。


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