函数的参数传递(parameter passing)有三种类型:实参值传递(argument value passing)、指针传递(pointer passing)和引用传递(reference passing)。其中实参值传递和指针传递本质上是相同的,传递的是变量的。引用传递的本质是传递地址,故称为地址传递。(这些是我自己归纳的,不是很准确,关键在于理解)
一、实参值传递
所谓实参值传递,就是变量名作为函数的实参(argument)和形参(parameter)。
当函数调用的时候,实参的值被复制,然后赋给形参。也就是说,形参是实参的一个副本(copy)。
这个复制的过程是由形参所属的数据类型的复制构造函数(copy constructor)完成的,在函数运行结束后,形参所属的数据类型的析构函数负责释放该形参。
形参作为一个局部变量,它的作用域在函数内部,一旦跳出了函数,形参的值不被保存,这样在函数体内就不能改变实参的值。当函数返回时,形参的值不会被复制到对应的实参中去。
整个传递过程是单向的。在调用函数时,形参和实参处于不同的存储单元。
这种传递方式非常简单,不再举例。
分析这种传递方式会发现它有几个缺点:
(1)函数内无法改变实参的值。因为传递的是值,值是赋给形参的,编译的时候形参其实是作为一个临时变量,它被初始化为实参的值。即形参和实参是两个变量,它们之间基本没有关系。对形参做任何修改不会影响到实参。
(2)增加程序的运行开销。假如实参是一个很大的对象(你可以把它想象成一个很大的数组,虽然数组没有这种传递方式,传递的过程就是复制数组的每一个元素),那么将其复制到形参需要花费大量的时间,占用较多的内存,而在函数调用结束后,析构函数又需要很长的时间来释放空间。
(3)有些对象无法被复制。例如某些类类型。
二、指针传递
指针传递,顾名思义,指针变量作为函数的参数。如下代码所示:
void reset(int *ip) //指向整型的指针变量做函数参数 { *ip=0; //改变了指针变量指向的对象的值 ip=0; //改变了指针变量的值 } int main() { int i=42; //定义一个整型变量 int *p=&i; //定义一个指针变量p,指向整型变量i cout<<i<<" "<<*p<<endl; //输出变量和指针变量指向的变量的值,结果为:42 42 reset(p); //调用reset函数 cout<<i<<" "<<*p<<endl; //输出变量和指针变量指向的变量的值,结果为:0 0 return 0; }
分析上面的代码,在reset函数中,我们使用*ip=0将指针变量指向的对象的值改为0,我们使用ip=0将指针变量的值改为0。第一条语句改变了变量i的值,第二条语句改变的是局部变量ip,所以最后i的值仍为0。
这个过程中,形参是指针变量,实参是变量的地址,函数被调用时,实参的地址被复制,然后赋值给形参。形参指向实参变量单元,一旦形参发生变化,实参也随之变化。这种方式叫虚实结合,其本质仍然是值传递。虽然形参是指针变量,实参是变量的地址,但是我们看到,当我们使用ip=0修改指针变量的值以后,实参的值并没有改变。也就是说,我们只是通过形参来访问实参,修改实参的值,而修改形参的地址,不会对实参产生任何影响。
好了,现在我们将指针传递与实参值传递进行对比:
相同点:都是值传递,传递的过程都是复制的过程。
不同点:实参值传递复制的是实参的值,指针传递复制的是实参的地址;实参传递方式在函数内无法修改实参值,指针传递在函数内可以通过地址访问到实参,进而修改实参值。
总结:指针传递相比于实参传递,更加灵活,而且它没有实参传递的两个缺点(当需要传递一个大的对象时,我们可以只传递它的首地址),所以这种传递方式被广泛使用。最常见的就是数组作为参数。
由于指针传递比较灵活,我们可以对其进行限定,限定方式主要是添加const关键字。例如我们想防止实参被修改,那么我们可以用一个常变量指针作为参数,void reset(const int *ip),这样就只能获得实参值,而无法修改实参。
三、引用传递
引用传递,就是以变量的引用作为函数的参数。例如下面的代码:
void reset(int &ip) //整型变量的引用做函数参数 { ip=0; //改变了指针变量的值 } int main() { int i=42; //定义一个整型变量 reset(i); //调用reset函数 cout<<i<<endl; //输出结果为0 return 0; }
上述代码中,reset函数中,形参为整型变量的应用。在main函数中调用reset函数时,实参是一个整型变量。当在reset函数中修改形参时,实参随之变化。在这个过程中,传递的是地址,形参作为一个引用变量,直接指向了实参的地址,而不是复制。
引用传递和指针传递类似,但是引用传递不是复制地址,而是直接指向地址。所以对形参的任何修改都会影响到实参,这使得它使用起来很容易出错。在实际使用的过程中,我们经常使用常引用变量作为形参。如void reset(const int &ip),这样我们就不能修改形参的值,从而导致实参的值的改变。
如果我们是用非常量引用作为形参,使用起来有很多限制:(1)实参类型必须与形参完全一样,不能有任何类型转换;(2)实参不能是常量;(3)实参必须是左值。
总结:
本文介绍了函数参数的几种形式,先将使用方法归纳如下:
实参值传递:形参是变量,实参是变量 void reset(int); reset(i);
指针传递: 形参是指针,实参是变量地址 void reset(int *); reset(&i);
引用传递: 形参是引用,实参是变量 void reset(int &); reset(i);
The End