值传递,引用传递,指针传递
一、值传递、引用传递及指针传递
1 | 值传递 | 不能改变实参变量的值 |
形参是实参的拷贝,改变形参的值并不会影响外部实参的值。从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出。 |
2 | 引用传递 | 可以改变实参变量的值 |
形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作。 在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。 被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。 |
3 | 指针传递 | 可以改变实参变量的值 |
指针传递参数本质上是值传递的方式,它所传递的是一个地址值。 值传递过程中,被调函数的形式参数作为被调函数的局部变量在栈中开辟了空间,以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。 值传递的特点是被调函数对形式参数的任何操作都是作为局部变量进行,不会影响主调函数的实参变量的值。(这里是在说实参指针本身的地址值不会变) |
二、引用
在平时练习中,常常会用到 引用作为函数返回值类型、引用参数为常引用 等等情况,引用是C++重要的知识点,也是健壮代码的必要因素,所以还是有必要整理一下资料。
1、变量的引用
int a; int& b = a;
对一个变量的引用,相当于对原变量起了一个别名。
- 引用声明时必须进行初始化;
- 引用声明完毕后,则目标变量有两个名称,分别是原变量和引用别名,且当声明后该引用不能改变成其他变量的别名;
- 引用本身不是一种数据结构,因此不在栈中开辟存储空间。
- 不能进行数组的引用,因为数组是一系列元素的集合,因此不能建立一个数组的引用。
2、引用作为传递参数
1 void swap(int& a,int& b) 2 { 3 int temp = a; 4 a = b; 5 b = temp; 6 }
- 引用传递与值传递的区别:如上述程序,能够实现两实参的值交换,而若传递方式为值传递时,形参作为局部变量在函数调用后被销毁,实参的值不能实现交换。
- 引用传递与指针传递的区别:引用作为传递参数,与指针传递一样,能够改变实参的内容,但两者的运作机制不同。对于指针传递,形参作为局部变量在内存开辟了存储空间,存放的是原实参的值,相当于原实参的拷贝,因此两者的存储地址是不同的。对于引用传递,则存放的是原实参的地址,因此可以通过间接寻址对原实参的值进行操作。如果传递的是对象,还需要调用拷贝构造函数,因此当参数传递的数值较大时,用引用的效率较高。
3、引用作为函数返回值类型
引用作为函数返回值类型,格式为:
类型标识符& 函数名(形参列表)
{
函数体
}
引用作为返回值的最大好处在于,在内存中不产生返回值的副本。
对于引用作为返回值类型的性质,如下程序:
1 int& func(int& a) //系统不生成返回值的副本,引用传递 2 { 3 a+=10; 4 return a; 5 } 6 int& func2(int a) //系统不生成返回值的副本,编译显示返回了局部变量的引用 7 { 8 a+=10; 9 return a; 10 } 11 int func3(int a) //系统生成了返回值的副本 12 { 13 a+=10; 14 return a; 15 } 16 int main(int argc, char *argv[]) { 17 18 int a = 0; 19 int& b = func(a); //b是a的引用 20 cout << b <<','<< a<<endl; //b=10,a=10 21 a = func3(a); //a=20 22 cout << b <<','<< a<<endl; //b=20,a=20 23 a = func2(a); //a=30,可能会产生编译错误,左值原实参变量是int类型,而右值是int&类型,且返回值是局部变量 24 cout << b <<','<< a<<endl; //b=30,a=30 25 26 return 0; 27 }
因此,引用作为函数返回值类型,需要遵守以下规则:
- 不能返回局部变量的引用,主要原因是当函数调用后局部变量会被销毁,因此返回的引用就成了“无所指”的状态,程序会进入未知状态;
- 不能返回函数内部new分配的内存的引用,虽然不存在1中局部变量被销毁的问题,但是由于返回的是一个临时变量,会出现这个引用指向的内存无法释放的问题,造成内存泄漏;
- 对于返回类成员的引用,最好是常引用const。如const int& getSize();
- 引用与一些操作符的重载:流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << \"hello\" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
- 在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。
4、引用和多态
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
【例】:
class A;
class B:public A{ ... ... }
B b;
A &Ref = b;//用派生类对象初始化基类对象的引用
Ref 只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。
参考资料:
http://www.cnblogs.com/yanlingyin/
http://www.cnblogs.com/gw811/archive/2012/10/20/2732687.html