原文地址:https://www.cnblogs.com/jycboy/p/5184638.html
一、变量的引用:
引用仅是变量的别名,而不是实实在在地定义了一个变量,因此引用本身并不占用内存,而是和目标变量共同指向目标变量的内存地址.
表达式中的取地址符&不再是取变量的地址,而是用来表示该变量是引用类型的变量。
定义一个引用时,必须对其初始化。
#include <stdio.h>
#include <iostream>
using namespace std;
//c++中的引用实例
void fun(int &x){
printf("形式参数的值为:%d\n",x);
printf("形式参数的地址为:%d\n",&x);
x+=10;
}
void fun2(int *num){
printf("形式参数的值为:%d\n",*num);
printf("形式参数的地址值为:%d\n",num); //2686724
printf("形式参数的地址为:%d\n",&num); //2686704 这个和
*num =(*num)+10;
}
int main(void){
int num = 4,a=3;
printf("实参num的值为:%d\n",num);
printf("实参num的地址为:%d\n",&num);//都是一样的
fun(num);
cout<<"num的值为:"<<num<<endl; //14 num的值改啦
fun2(&a);
printf("实参a的值为:%d\n",a);
printf("实参a的地址为:%d\n",&a);//
return 0;
}
1.和是一样的,
而后边用指针作为函数参数,把地址传过去,,而(在指针的地址上存的值),2686696这个地址指向的值是3,也就是num的值。
,和不一样,而引用的地址是和变量的值一样的。
二、引用的应用
引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。
#include<iostream.h>
////此处函数的形参p1, p2都是引用
void swap(int &p1,int &p2){
int p=p1;
p1=p2;
p2=p;
}
为在程序中调用该函数,则相应的主调函数的调用点处,直接以变量作为实参进行调用即可,而不需要实参
变量有任何的特殊要求。如:对应上面定义的swap函数,相应的主调函数可写为:
void main(){
int a,b;
cin>>a>>b;//输入a,b两个变量的值
swap(a,b);//直接以a和b作为实参调用swap函数
cout<<"a="<<a<<",b="<<b<<endl;
}
上述程序运行时,如果输入数据10 20并回车后,则输出结果为a=20,b=10。
而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(2)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
就如同上边的例子一样。
常引用声明方式:const 类型标识符 &引用名 = 目标变量名;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。
#include <stdio.h>
#include <iostream>
using namespace std;
void test_const();
int main(void){
test_const();
return 0;
}
void test_const(){
int a=1;
int &b=a;
b=2;
cout<<"a="<<a<<endl;//2
int c=1;
const int &d=c;
// d=2;//编译错误 error: assignment of read_only reference 'd'
c=2;//正确
cout<<"c="<<c<<endl;//
}
这不光是让代码更健壮,也有其它方面的需求。
string foo();
void bar(string &s);
那么下面的表达式将是非法的:
bar(foo());
bar("hello world");
原因在于foo( )和"hello world"串都会产生一个临时对象,因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
要以引用返回函数值,则函数定义时要按以下格式:
类型标识符 &函数名 (形参列表及类型说明)
{ 函数体 }
说明:
(1)以引用返回函数值,定义函数时需要在函数名前加&
以下程序中定义了一个普通的函数fn1(它用返回值的方法返回函数值),另外一个函数fn2,它以引用的方法返回函数值。
#include <iostream>
using namespace std;
float temp;//定义全局变量temp
float fn1(float r);//声明函数fn1
float &fn2(float r);//声明函数fn2 r
float fn1(float r){//定义函数fn1,它以返回值的方法返回函数值
temp=(float)(r*r*3.14);
return temp;
}
float &fn2(float r){//定义函数fn2,它以引用方式返回函数值
temp=(float)(r*r*3.14);
return temp;
}
int main(){
float e=10.0;
float a=fn1(10.0);//第1种情况,系统生成要返回值的副本(即临时变量)
/****************************************
error: invalid initialization of non-const reference of type 'float&' from an rvalue of type 'float'
****************************************/
// float &b=fn1(10.0); //第2种情况,可能会出错(不同 C++系统有不同规定)
//不能从被调函数中返回一个临时变量或局部变量的引用
float c=fn2(10.0);//第3种情况,系统不生成返回值的副本
//可以从被调函数中返回一个全局变量的引用
float &d=fn2(10.0); //第4种情况,系统不生成返回值的副本
e=d;
cout<<"a="<<a<<",c="<<c<<",d="<<d<<",e="<<e<<endl;
//a=314,c=314,d=314
return 0;
}
引用作为返回值,必须遵守以下规则:
(1)这条可以参照Effective C++[1]的Item 31。,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。如上面中的第2种情况出现编译错误。
(2)这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。
(3)这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)引用与一些操作符的重载:流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
#include<iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main(){
put(0)=10;//以put(0)函数值作为左值,等价于vals[0]=10;
put(9)=20;//以put(9)函数值作为左值,等价于vals[9]=20;
cout<<vals[0]<<endl;//10
cout<<vals[9]<<endl;//20
}
int &put(int n){
if(n>=0 && n<=9)
return vals[n];
else{
cout<<"subscript error";
return error;
}
}
(5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。
【例7】:
class A;
class B:public A{ ... ... }
B b;
A &Ref = b;//用派生类对象初始化基类对象的引用
Ref 只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。
(1)在引用的使用中,单纯给某个变量取个别名是毫无意义的,
(2),
(3),;
(4)、