4. 函数的传参

这节课我们来学习函数传参,同时用变量交换的示例来说明传值传址的用法。

先来做一道练习题

写一个程序,创建两个整数变量 x 和 y;,然后交换两个变量的值后打印结果。如下程序请填充代码:

#include <stdio.h>

int main(int argc, char * argv[]) {
    int x = 100, y = 200;

    // 此处交换两个变量的值。
    printf("x: %d, y: %d\n", x, y);  // 计划结果打印:x: 200, y: 100

    return 0;
}

问题说明:

现实世界中,如果你左手托着一个大西瓜,右手托着一个大哈密瓜,你想将左右手的两个水果交换一下,如果不允许抛起或放地上,怎么办呢?聪明的你请路人帮忙拿一下你手里的西瓜,然后你的左手就空出来了。你右手的哈密瓜倒到左手,右手接过路人手里的西瓜,此时你完成了左右手水果的交换。

对于 C 语言中交换两个变量的值一定要借助于第三个变量来临时保存一个某个变量的值,然后才能进行交换。

我们改写上述程序,使用 临时变量 temp 完成交换如下:

// filename: myswap.c
#include <stdio.h>

int main(int argc, char * argv[]) {
    int x = 100, y = 200;

    // 此处交换两个变量的值。
    int temp = x;
    x = y;
    y = temp;

    printf("x: %d, y: %d\n", x, y);  // 计划结果打印:x: 200, y: 100

    return 0;
}

运行结果如下:

weimingze@mzstudio:~$ gcc -o myswap myswap.c
weimingze@mzstudio:~$ ./myswap
x: 200, y: 100

可见我们实现了 xy 两个变量的交换。

为完成上述功能。我们在程序声明了一个临时变量 temp。这个变量在 声明后就会一直存在于程序中,它会占用空间,也可能对后续的变量命名产生冲突,我们再改进一下这个程序,我们将 临时变量 temp 放入一个复合语句中。因为复合语句有自己的作用域。在复合语句的作用域中声明的变量是复合语句内部的局部变量,只能在此作用域内有效,并优先访问最近的作用域内的变量。当复合语句执行结束后,此作用域中会释放,其中的所有声明的变量也会释放。基于这个原理。我们改写如下:

// filename: myswap.c
#include <stdio.h>

int main(int argc, char * argv[]) {
    int x = 100, y = 200;

    // 此处交换两个变量的值。
    {
        int temp = x;
        x = y;
        y = temp;
    }

    printf("x: %d, y: %d\n", x, y);  // 计划结果打印:x: 200, y: 100

    // 注意此时变量 temp 已经不存在了,不能再使用 temp 变量
    return 0;
}

程序的运行结果相同。

下面我们将上述程序中的交换部分的代码

    {
        int temp = x;
        x = y;
        y = temp;
    }

修改成为一个函数 myswap 程序改写如下:

// filename: myswap.c
#include <stdio.h>

void myswap(int x, int y)
{
    int temp = x;
    x = y;
    y = temp;
}

int main(int argc, char * argv[]) {
    int x = 100, y = 200;

    // 此处交换两个变量的值。
    myswap(x, y);

    printf("x: %d, y: %d\n", x, y);  // 计划结果打印:x: 200, y: 100

    return 0;
}

运行结果如下:

weimingze@mzstudio:~$ gcc -o myswap myswap.c
weimingze@mzstudio:~$ ./myswap
x: 100, y: 200

可见上述程序中并没有实现 main 函数内两个变量 xy 的交换。为什么呢?

原因是函数 myswap 函数内的两个变量 xy 并不是 main 函数内的 xy,函数调用会有自己的运行空间。当 main 主函数调用 myswap 时,实参 xy的值会赋值给 myswap 函数的形参变量 xy,而这两个形参变量是 myswap 内的局部变量。在函数内是交换了这两个形参变量的值,但当函数退出后,这两个变量也就销毁了。而主函数中的 xy 不会改变。

结论:

函数实参传递给形参的实质是赋值,我们也把这种传递方式称为传值

那我们要如何实现在 myswap 函数内实现对 main 主函数的 xy 进行交换呢?答案就是将 xy 的地址传递给 myswap 函数,myswap 函数的形式参数用指针指向 main 函数内的 xy。这样我们就可以在 myswap 函数内修改 main 函数内的 xy 的值了。我们把这种传递方式称之为传址

再次改写上述示例

// filename: myswap.c
#include <stdio.h>

void myswap(int *px, int *py)  // 形参用指针保存实参地址
{
    int temp = *px;
    *px = *py;
    *py = temp;
}

int main(int argc, char * argv[]) {
    int x = 100, y = 200;

    // 此处交换两个变量的值。
    myswap(&x, &y);  // 传址方式调用

    printf("x: %d, y: %d\n", x, y);  // 计划结果打印:x: 200, y: 100

    return 0;
}

运行结果如下:

weimingze@mzstudio:~$ gcc -o myswap myswap.c
weimingze@mzstudio:~$ ./myswap
x: 200, y: 100

可见,使用 传址 的方式传递参数,函数 void myswap(int *px, int *py) 内的形参 pxpy 指向了 main 函数内的 xy,只用 *px*py 指针解引用后就可以操作 xy了。

传值传址 总结:

  1. 传值传址实质都是传值,只是传址时传递的是内存地址的值。
  2. 传址方式传参时,被调用函数可以通过形参的指针来修改上层函数内变量的值。

练习:

写一个函数 void mydiv(int x, int y, int *quotient, int *remainder); 实现 x ➗ y 的结果,一次性的获取商和余数,参数 quotient 用于保存商,remainder 用于保存余数。

函数的调用示例如下:

int main(int argc, char * argv[]) {

    int shang = 0;  // 用于保存除法的商
    int yushu = 0;  // 用于保存除法的余数

    mydiv(14, 3, &shang, &yushu);

    printf("14 除以 3 等于 %d,余 %d\n", shang, yushu);

    return 0;
}

运行结果如下:

14 除以 3 等于 4,余 2