结构化程序设计鼓励将重复使用的或者较为独立的代码作为函数。函数是非常实用的程序组件:操作系统提供的系统功能调用体现为API函数,C标准函数库中提供的各种功能体现为库函数,用户定制的功能由自定义函数实现。
要想正确地使用函数就要了解如何向函数传递参数和接收函数的处理结果。在介绍传参之前,先介绍几个术语:
形式参数:也称形参,是指在定义函数时使用的参数,在函数体中使用参数名起到占位的作用,故名形式参数。当编译函数代码时,将为形参申请内存空间,等待调用者传入参数值来填入。
实际参数:也称实参,是指在调用函数时使用的实际值。
传值
当形参是简单变量(整型、实型、字符型)时,传参过程为单向值传递,即将实参的值拷贝到形参的内存单元中。这样,在函数执行过程中,函数对形参的使用与其内部变量一样,利用传入的实参值进行运算,通过return语句返回函数的运算结果。
请看如下示例:
#include <stdio.h>
// 用户自定义函数,计算两数之和
// x和y是形参
int mysum1(int x, int y)
{
int sum = x + y;
x++; // 修改x的值
y--; // 修改y的值
printf("mysum: x is %d; y is %d\n", x, y);
return sum; // 返回两数的和
}
int main(void)
{
int x = 1, y = 2;
int s = mysum1(x, y); // x和y是实参
printf("main: %d + %d = %d\n", x, y, s); // 打印结果
return 0; // 程序正常返回
}
上例中,当main调用函数mysum1时,变量x、y作为实参传值给mysum1中的形参x、y。虽然同名,但这是位于不同函数空间中的两组变量。函数mysum1执行时会修改其中的变量x、y的值,但是这个修改不会影响到main中的变量x、y,因此程序的执行结果为:
可以看出,函数mysum1中对变量x和y的修改仅对其内部变量x、y有效;当函数执行结束返回时,其内部变量被销毁,而main函数中的变量x和y的值并没有受到影响。
传地址
对于复杂变量,调用者将变量的地址传给函数。这样做主要有两个原因:一方面,对于数组之类的变量进行值传递,需要大量的内存操作实现,效率低下;另一方面,有时候需要在函数中计算多个值,而使用return语句仅能返回一个值,因此要将返回值作为参数进行传递。
请看如下示例:
#include <stdio.h>
// 用户自定义函数,计算两数之和
// x、y、sum是形参:x和y是简单整数类型;sum是整型指针,即sum是整数变量的地址
void mysum2(int x, int y, int *sum)
{
*sum = x + y; // *是间接运算符,其含义是以sum的值作为地址访问内存单元
}
int main(void)
{
int x = 1, y = 2;
int s = 0;
mysum2(x, y, &s); // x、y、&s是实参:&是间接运算符,其含义是取变量s的地址
printf("main: x = %d; y = %d; sum = %d\n", x, y, s); // 打印结果
return 0; // 程序正常返回
}
上例中,函数mysum2没有显式地返回值,而是通过第三个参数传递整型变量的地址,函数将计算结果直接写入变量所在的内存单元。这样,函数的调用者就可以直接访问变量获得运算结果。上面的代码运行结果为:
可以看出,函数mysum2计算的结果存入了变量s所在的地址单元中,调用函数后s的值从0变为3。虽然函数mysum2没有显式地返回值,但是通过将结果写入sum所指向的内存单元的方式,隐式地返回了结果。
利用指针传地址的方式非常有用,例如要实现向链表头添加元素的函数,使用二级指针会非常方便。这又是一个有趣的话题,后面会再写一篇短文来讨论它。