34-使用指针在函数间通信
9.7.3 使用指针在函数间通信
我们才刚刚接触指针,指针的世界丰富多彩。本节着重介绍如何使用指针解决函数间的通信问题。请看程序清单9.15,该程序在 interchange() 函数中使用了指针参数。稍后我们将对该程序做详细分析。
程序清单9.15 swap3.c 程序
/* swap3.c -- 使用指针解决交换函数的问题 */
#include <stdio.h>
void interchange(int * u, int * v);
int main(void)
{
int x = 5, y = 10;
printf("Originally x = %d and y = %d.\n", x, y);
interchange(&x, &y); // 把地址发送给函数
printf("Now x = %d and y = %d.\n", x, y);
return 0;
}
void interchange(int * u, int * v)
{
int temp;
temp = *u; // temp获得 u 所指向对象的值
*u = *v;
*v = temp;
}
该程序是否能正常运行?下面是程序的输出:
Originally x = 5 and y = 10.
Now x = 10 and y = 5.
没问题,一切正常。接下来,我们分析程序清单9.15的运行情况。首先看函数调用:
interchange(&x, &y);
该函数传递的不是 x 和 y 的值,而是它们的地址。这意味着出现在 interchange() 原型和定义中的形式参数 u 和 v 将把地址作为它们的值。因此,应把它们声明为指针。由于 x 和 y 是整数,所以 u 和 v 是指向整数的指针,其声明如下:
void interchange (int * u, int * v)
接下来,在函数体中声明了一个交换值时必需的临时变量:
int temp;
通过下面的语句把 x 的值存储在 temp 中:
temp = *u;
记住, u 的值是 &x ,所以 u 指向 x 。这意味着用* u 即可表示 x 的值,这正是我们需要的。不要写成这样:
temp = u; /* 不要这样做 */
因为这条语句赋给 temp 的是 x 的地址( u 的值就是 x 的地址),而不是 x 的值。函数要交换的是 x 和 y 的值,而不是它们的地址。
与此类似,把 y 的值赋给 x ,要使用下面的语句:
*u = *v;
这条语句相当于:
x = y;
我们总结一下该程序示例做了什么。我们需要一个函数交换 x 和 y 的值。把 x 和 y 的地址传递给函数,我们让 interchange() 访问这两个变量。使用指针和*运算符,该函数可以访问存储在这些位置的值并改变它们。
可以省略ANSI C风格的函数原型中的形参名,如下所示:
void interchange(int *, int *);
一般而言,可以把变量相关的两类信息传递给函数。如果这种形式的函数调用,那么传递的是 x 的值:
function1(x);
如果下面形式的函数调用,那么传递的是 x 的地址:
function2(&x);
第1种形式要求函数定义中的形式参数必须是一个与 x 的类型相同的变量:
int function1(int num)
第2种形式要求函数定义中的形式参数必须是一个指向正确类型的指针:
int function2(int * ptr)
如果要计算或处理值,那么使用第1种形式的函数调用;如果要在被调函数中改变主调函数的变量,则使用第2种形式的函数调用。我们用过的 scanf() 函数就是这样。当程序要把一个值读入变量时(如本例中的 num ),调用的是 scanf("%d", &num) 。 scanf() 读取一个值,然后把该值存储到指定的地址上。
对本例而言,指针让 interchange() 函数通过自己的局部变量改变 main() 中变量的值。
熟悉Pascal和Modula-2的读者应该看出第1种形式和Pascal的值参数相同,第2种形式和Pascal的变量参数类似。C++程序员可能认为,既然C和C++都使用指针变量,那么C应该也有引用变量。让他们失望了,C没有引用变量。对BASIC程序员而言,可能很难理解整个程序。如果觉得本节的内容晦涩难懂,请多做一些相关的编程练习,你会发现指针非常简单实用(见图9.6)。

变量:名称、地址和值
通过前面的讨论发现,变量的名称、地址和变量的值之间关系密切。我们来进一步分析。
编写程序时,可以认为变量有两个属性:名称和值(还有其他性质,如类型,暂不讨论)。计算机编译和加载程序后,认为变量也有两个属性:地址和值。地址就是变量在计算机内部的名称。
在许多语言中,地址都归计算机管,对程序员隐藏。然而在C中,可以通过 & 运算符访问地址,通过运算符获得地址上的值。例如, &barn 表示变量 barn 的地址,使用变量名即可获得变量的数值。例如, printf("%d\n", barn) 打印 barn 的值,使用运算符即可获得存储在地址上的值。如果 pbarn = &barn; ,那么* pbarn 表示的是存储在 &barn 地址上的值。
简而言之,普通变量把值作为基本量,把地址作为通过 & 运算符获得的派生量,而指针变量把地址作为基本量,把值作为通过*运算符获得的派生量。
虽然打印地址可以满足读者好奇心,但是这并不是 & 运算符的主要用途。更重要的是使用 & 、*和指针可以操纵地址和地址上的内容,如 swap3.c 程序(程序清单9.15)所示。
小结:函数
形式:
典型的ANSI C函数的定义形式为:
返回类型 名称(形参声明列表)
函数体
形参声明列表是用逗号分隔的一系列变量声明。除形参变量外,函数的其他变量均在函数体的花括号之内声明。
示例:
int diff(int x, int y) // ANSI C
{ // 函数体开始
int z; // 声明局部变量
z = x - y;
return z; // 返回一个值
} // 函数体结束
传递值:
实参用于把值从主调函数传递给被调函数。如果变量 a 和 b 的值分别是 5 和 2 ,那么调用:
c = diff(a,b);
把5和2分别传递给变量 x 和 y 。5和2称为实际参数(简称实参), diff ()函数定义中的变量 x 和 y 称为形式参数(简称形参)。使用关键字 return 把被调函数中的一个值传回主调函数。本例中, c 接受 z 的值3。被调函数一般不会改变主调函数中的变量,如果要改变,应使用指针作为参数。如果希望把更多的值传回主调函数,必须这么做。
函数的返回类型:
函数的返回类型指的是函数返回值的类型。如果返回值的类型与声明的返回类型不匹配,返回值将被转换成函数声明的返回类型。
函数签名:
函数的返回类型和形参列表构成了函数签名。因此,函数签名指定了传入函数的值的类型和函数返回值的类型。
示例:
double duff(double, int); // 函数原型
int main(void)
{
double q, x;
int n;
...
q = duff(x,n); //函数调用
...
}
double duff(double u, int k) //函数定义
{
double tor;
...
return tor; //返回double类型的值
}