9.2.3 指针变量作为函数参数
函数的参数已经讲解过了,可以是整型、实型、字符型等数据,还可以是指针类型,那么指针类型参数的作用是将一个变量的地址传送到一个函数中。看看如下范例:
这个范例并不复杂,读者可以自己跟踪调试看一下,这里以指针变量作为实参,传递到swap函数(swap函数用于交换两个变量的值)中去,实参是两个指针变量p1和p2,swap函数中对应的两个形参是pdest1和pdest2,而在swap函数中,实际上就是把a的值赋给中间变量temp,把b的值赋给a,然后再把中间变量temp的值赋给b,从而实现a和b两个变量值的交换。
那么刚进入swap函数时,内存示意图应该如图9.10所示,读者可以比较一下,看是否跟自己所想的一样。
图9.10 指针变量作为实参传递给函数swap(刚进入swap函数)内存布局
看着图9.10,回忆一下,前面曾经说过,函数参数的传递是一一对应的,即单向按值传递。那么,这种用指针变量作为实参和形参的情况,通过程序运行结果,发现可以在被调用的swap函数中改变变量a和b的值,也就是在这里看起来不再是单向按值传递了(真是这样吗?后面还会说),a和b值被改变值后,在主调用函数main中,可以使用被改变后的a和b值了。
但是,不知是否有人会像下面这样写代码——想通过指针的调换来交换a和b的值。这种写法是不对的,毫无效果,并不能交换a、b的值:
虽然上面这段指针操作的代码并没有达到交换a和b值的目的,但是对于这种指针赋值(调换)操作,要求必须彻底掌握、透彻理解。有必要仔仔细细分析上面这段指针操作相关代码:
(1)进入swap函数并定义了ptemp指针后是这样的情形,如图9.11所示。
图9.11 进入swap函数并定义了ptemp指针后内存布局
(2)进入swap函数,并执行了第一行代码“ptemp=pdest1;”,这个赋值表示pdest1指向哪ptemp就指向哪,所以内存布局如图9.12所示。
图9.12 进入swap函数并执行了第一行代码“ptemp=pdest1;”后内存布局
(3)执行“pdest1=pdest2;”,这个赋值表示pdest2指向哪pdest1就指向哪,如图9.13所示。
(4)执行“pdest2=ptemp;”,这个赋值表示ptemp指向哪pdest2就指向哪,如图9.14所示。
可以看到,根据图9.12~图9.14,这三步指针赋值的动作其实任何有用的事情都没做(只是指针的指向发生了改变),当函数执行完毕后,pdest1、pdest2、ptemp这几个临时变量都变得没意义了。而外面调用swap函数的实参p1、p2压根儿就没有受到过任何影响。
前面讲过,C语言中,实参变量和形参变量之间的数据传递是单向的值传递,实质上,指针变量作为函数参数也要遵循这个原则,调用函数swap()不能改变实参指针变量的值,也就是说p1还是指向a,p2还是指向b,但可以改变实参指针变量所指向的变量的值,也就是说,在swap()函数中,通过“temp=*pdest1;”“*pdest1=*pdest2;”“*pdest2=temp;”这种纯粹的变量赋值语句(而不是指针变量赋值语句),还是可以改变变量a和b中的值的,因为a变量和b变量就是实参指针变量p1和p2所指向的变量。
图9.13 进入swap函数执行了“pdest1=pdest2;”后内存布局
所以能够看到,通过指针变量做参数,可以间接地在函数中改变指针变量所指向的变量的值,从而达到在被调用函数内改变外界变量值的效果,如果不用指针变量作为参数,就比较难做到这一点。
在进一步地理解了指针变量的工作细节后,看一个指针变量的错误用法。看看如下范例:
这两行代码在老版本的Visual Studio编译器上能够编译通过,但执行起来后会报异常,而在新版本的Visual Studio编译器上,编译就会报错。究其原因,是因为在“*p=5;”这句代码中,*p是指针变量p所指向的变量,但指针变量p到底指向了谁呢?根本就不确定,所以*p可能会造成某段内存被无意修改,从而使系统崩溃。所以进行如下代码修改,就没问题了:
图9.14 进入swap函数执行了“pdest2=ptemp;”后内存布局
本节中虽然讲解的话题是指针变量作为函数参数,但这节的主要目的还是帮助读者梳理和巩固指针变量的各种用法,希望读者看到一段使用了指针变量的程序后能看懂,熟悉指针的概念,熟练运用和指针相关的“&”(取地址运算符)以及“*”(指针运算符)。笔者希望将自身对指针的理解方法传达给读者,这也是笔者花费大量心血和篇幅去绘制这些图解的初衷。