我们在前面讲指针重要性的时候讲过:“指针能使被调函数返回一个以上的结果”。本小节给大家写一个经典的程序,就是通过一个函数修改主函数中好几个变量的值。这个程序很经典,把这个程序弄清楚了,指针就算是入门了。在写这个程序之前先来作一个铺垫:
# include <stdio.h>
void Swap(int a, int b); //函数声明
int main(void)
{
int i = 3, j = 5;
Swap(i, j);
printf("i = %d, j = %d\n", i, j);
return 0;
}
void Swap(int a, int b)
{
int buf;
buf = a;
a = b;
b = buf;
return;
}
大家想一下,执行这个程序是否能互换 i 和 j 的值?不能!i 还是3,j 还是5。
因为实参和形参之间的传递是单向的,只能由实参向形参传递。被调函数调用完之后系统为其分配的内存单元都会被释放。所以虽然将 i 和 j 的值传给了 a 和 b,但是交换的仅仅是内存单元 a 和 b 中的数据,对 i 和 j 没有任何影响。
“为什么不用 return 语句?”因为 return 语句只能返回一个值,并不能返回两个值。“将 printf 放在被调函数中不就行了吗?”我们的目的是互换内存单元 i 和内存单元 j 中的数据。而 printf 的功能仅仅是将结果输出,并不能改变数据处理的本质,互换的还是单元 a 和单元 b 中的数据。
以上传递方式叫作拷贝传递,即将内存 1 中的值拷贝到内存 2 中。拷贝传递的结果是:不管如何改变内存 2 中的值,对内存 1 中的值都没有任何影响,因为它们两个是不同的内存空间。
所以要想直接对内存单元进行操控,用指针最直接,指针的功能很强大。
# include <stdio.h>
void Swap(int *p, int *q); //函数声明
int main(void)
{
int i = 3, j = 5;
Swap(&i, &j);
printf("i = %d, j = %d\n", i, j);
return 0;
}
void Swap(int *p, int *q)
{
int buf;
buf = *p;
*p = *q;
*q = buf;
return;
}
输出结果是:i = 5, j = 3
此时实参向形参传递的不是变量 i 和 j 的数据,而是变量 i 和 j 的地址。其实传递指针也是拷贝传递,只不过它拷贝的不是内存单元中的内容,而是内存单元的地址,这就是天壤之别了。拷贝地址就可以直接对地址所指向的内存单元进行操作,即此时被调函数就可以直接对变量 i 和 j 进行操作了。有人会说:“被调函数用完就释放了,不就把 i 和 j 都释放了吗?”不是的,当函数调用完之后,释放的是 p 和 q,不是 i 和 j。p 和 q 中存放的是 i 和 j 的地址。所以 p 和 q 被释放之后并不会影响 i 和 j 中的值。前面讲过,修改指针变量的值不会影响所指向变量中的数据。只不过它们之间的指向关系没有了而已。
此外需要注意的是,形参中变量名分别为 p 和 q,变量类型都是 int* 型。所以实参 i 和 j 的地址 &i 和 &j 是分别传递给 p 和 q,而不是传递给 *p 和 *q。
函数参数传指针和传数据的区别
综上所述,如果希望在另外一个函数中修改本函数中变量的值,那么在调用函数时只能传递该变量的地址。如果这个变量是普通变量,那么传递它的地址就可以直接操作该变量的内存空间。
那么,是不是要定义一个指针变量指向它然后传递这个指针变量呢?不用多此一举。比如有一个“int i;”,如果想传递i的地址那就直接传递 &i 就行了,不用专门定义一个指针变量指向它,然后再传递这个指针变量。
如果要传递的变量本身就是一个指针变量怎么办?如果要操作该指针变量所指向的内存空间是不是要传递该指针变量的地址呢?
指针变量本身就是地址,本身就是指向那个内存空间的,所以直接把它传过去就行了。除非你要改变那个指针变量里面存放的地址,即你要改变指针变量的指向,那么你就必须要传递指针变量的地址。
此外,传指针和传数据相比还有一个好处就是节约内存。我们知道,传数据拷贝的是内存单元的数据,如果数据很多的话拷贝过来都要为它们分配内存。而传指针的话只需要传递 4 字节的地址就行了。而且传数据非常消耗效率,为形参分配内存需要时间,拷贝需要时间,最后结束了返回还是需要时间。前面说过,return 时系统会先自动创建一个临时变量来存放返回的值。所以传数据时很消耗效率,而传指针就是为了提高效率。
事实上,在实际编程中我们都是传递指针!往往只有满足下面这两个条件的时候我们才会直接传递数据而不是传递指针,而且这两个条件缺一不可:
此时不是不能用指针,当然也可以用指针,只是没有必要。
以后在使用函数的时候,只要函数的参数不满足上面这两个条件,那么全部都用指针。此外需要注意的是,数组名本身就是地址,所以如果传递数组的话直接传递数组名就行了。接收的形参可以定义成数组也可以定义为同类型的指针,这点后面再讲。