C++中的参数传递有三种方式:传递变量名,传递指针,传递引用。即值传递,指针传递和引用传递。
1.将变量名作为形参和实参
这种情况下传给形参的是变量的值,传递是单向的,就是说如果在执行函数期间形参的值发生变化,并不会传回给实参,这就是所谓的值传递。因为在调用函数期间,形参和实参并不是同一个存储单元。简单示例如下:
07 |
int _tmain( int argc, _TCHAR* argv[]) |
15 |
cout<< "i=" <<i<< ",j=" <<j<<endl; |
19 |
cout<< "i=" <<i<< ",j=" <<j<<endl; |
27 |
void swap( int a, int b){ |
2.传递变量指针
形参是指针变量,实参是一个变量的地址,调用函数时,形参(指针变量)指向实参变量单元。这种方式还是“值传递”,只不过实参的值是变量的地址而已,在函数中改变的不是实参的值(即改变不到地址,这种改变也是影响不到实参的),而是实参地址所指向的变量的值。示例:
05 |
int _tmain( int argc, _TCHAR* argv[]) |
13 |
cout<< "i=" <<i<< ",j=" <<j<<endl; |
17 |
cout<< "i=" <<i<< ",j=" <<j<<endl; |
25 |
void swap( int *p1, int *p2){ |
3.引用传递
引用形参是把实参的地址传递给形参(引用变量),从而是形参的地址取实参的地址,从而达到形参与实参共享同一单元的方式,这就是地址传值方式。
方式2中传递指针变量需要额外开辟一块内存单元,内容为地址。而方式3不是一个独立变量,不需要独占内存单元。而且调用使用引用形参的函数时,实参不必用函数的地址,而是直接使用变量名。系统向形参传递的是实参的地址而不是实参的值。
这三种方式有什么区别呢?
1.从功能上。按值传递在传递的时候,实参被复制了一份,然后在函数体内使用,函数体内修改参数变量时修改的是实参的一份值拷贝,而实参本身是没有改变的(例如上面的示例1),所以如果想在调用的函数中修改实参的值,使用值传递是不能达到目的的,这时只能使用引用或指针传递。
2.从传递效率上来说。这里所说传递效率,是说调用被调函数的代码将实参传递到被调函数体内的过程,正如上面代码中,这个过程就是函数main()中的a,b传递到函数swap()中的过程。这个效率不能一概而论。对于内建的int,char,,short,long,float等4字节或以下的数据类型而言,实际上传递时也只需要传递1-4个字节,而使用指针传递时在32位CPU中传递的是32位的指针,4个字节,都是一条指令,这种情况下值传递和指针传递的效率是一样的,而传递double,long long等8字节的数据时,在32位CPU中,其传值效率比传递指针要慢,因为8个字节需要2次取完。而在64位的CPU上,传值和传址的效率是一样的。再说引用传递,这个要看编译器具体实现,引用传递最显然的实现方式是使用指针,这种情况下与指针的效率是一样的,而有些情况下编译器是可以优化的,采用直接寻址的方式,这种情况下,效率比传值调用和传址调用都要快,与上面说的采用全局变量方式传递的效率相当。再说自定义的数据类型,class,struct定义的数据类型。这些数据类型在进行传值调用时生成临时对象会执行构造函数,而且当临时对象销毁时会执行析构函数,如果构造函数和析构函数执 行的任务比较多,或者传递的对象尺寸比较大占用的内存空间也比较大,那么传值调用的消耗就比较大。这种情况下,采用传址调用和采用传引用调用的效率大多数下相当,正如上面所说, 某些情况下引用传递可能被优化,总体效率稍高于传址调用。
3.从执行效率上讲。这里所说的执行效率,是指在被调用的函数体内执行时的效率。因为传值调用时,当值被传到函数体内,临时对象生成以后,所有的执行任务都是通过直接寻址的方式执行的,而指针和大多数情况下的引用则是以间接寻址的方式执行的,所以实际的执行效率会比传值调用要低。如果函数体内对参数传过来的变量进行操作比较频繁,执行总次数又多的情况下,传址调用和大多数情况下的引用参数传递会造成比较明显的执行效率损失。
综合2、3两种情况,具体的执行效率要结合实际情况,通过比较传递过程中的资源消耗和执行函数体消耗之和来选择哪种情况比较合适。而就引用和指针传递的效率上来讲,引用传递的效率始终不低于指针,所以从这个层面来讲,在C++中使用参数传递时应该优先考虑使用引用传递而非指针。
4.从类型安全上来讲。值传递和引用传递在传递过程中都会进行强类型检查,而指针的检查较弱,特别是当类型被声明为void*时它几乎不检查,只要是指针编译器就认为是合法的。所以导致程序的健壮性下降。如果没有必要,就使用值传递和引用传递,最好不用指针传递,更好地利用编译器的类型检查,使得我们有更少的出错机会,以增加代码的健壮性。
5.从参数检查上讲。一个健壮的函数,总会对传递来的参数进行参数检查,保证输入数据的合法性,以防止对数据的破坏并且更好地控制程序按期望的方向运行,在这种情况下使用值传递比使用指针传递要安全得多,因为你不可能传一个不存在的值给值参数或引用参数,而使用指针就可能,很可能传来的是一个非法的地址(没有初始化,或者指向已经delete掉的对象的指针等)。所以使用值传递和引用传递会使你的代码更健壮,具体是使用引用还是使用,最简单的一个原则就是看传递的是不是内建的数据类型,对内建的数据类型优先使用值传递,而对于自定义的数据类型,特别是传递较大的对象,那么请使用引用传递。
6.从灵活性上来说。无疑,指针是最灵活的,因为指针除了可以像值传递和引用传递那样传递一个特定类型的对象外,还可以传递空指针,不传递任何对象。这算是指针的优点了。
在参数传递过程中配合使用较多的const关键字,可以保证参数不被修改。就像我找人帮我数钱,我肯定不会希望我的钱变少了,所以使用const修饰变量,使其指向一个const对象。需要注意下参数顺序。当同个函数名有不同参数时,如果有相同的参数尽量要把参数放在同一位置上,以方便其他函数调用。