引用(reference)
1 &不是取地址,只是引用的标示符号。
2 引用必须被初始化,因为引用是被引用对象的别名,那就要先要有被引用对象,比如一个人有一个绰号,那么引用相当于这个绰号,那么必须有一个绰号对应的人这个对象。一旦定义这个别名就不可以再作为别的变量名了。
3 引用本身是有地址的,一般是一个4字节的内存来保存,被引用对象的地址,但是由于编译器的优化,每次碰到引用就会用被引用对象的地址替换,也就是语法上不可以取到引用本身的地址,对引用的操作就是直接对被引用对象的操作,所以操作符&,sizeof,++都不同于指针。
引用被编译器优化以后,引用就是被引用对象自己,无需任何拷贝,无需再开辟一个内存存一份拷贝了,共用那一块内存,函数传值方式中,分为传值和传引用,传值,分为普通的传值,还有传指针,传指针传的值是指针指向的地址,需要开辟一块内存来放这个值,指针指向的地址。
在之前的一篇博客中详细分析过了,引用和指针的底层实现相同,引用的底层实现通过指针实现的,具体用法是通过编译器的限制从而导致的不同。引用也是占内存的,只是不允许取他本身的地址值。从而用法和效果就是,相当于对被引用对象的直接操作。详细可以看前面那篇博客。
Int a=10;
Int &b=a;
此时取b的地址就是相当于取a的地址,&b相当于&a
Sizeof(b)相当于sizeof(a)
b++相当于a++,会得到10
同理由于不可获取引用自身地址,所以一个指向引用的指针也是非法的
4 引用与数组
int a[3]={1,2,3};
int& arry[3]={1,2,3};//错误,(int&)arry[3]={1,2,3};
//这里arry是一个数组,而数组的元素是引用
//c++中不允许声明一个引用数组,也就是由引用组成的数组
//因为数组是由很多数组成的集合,数组不会以传值的方式取数组中的元素
//从汇编代码可知,取数组中的元素都是根据首地址的偏移也就是通过指针来实现的。
//虽然,但是引用本身是有地址,而c++中由于编译器的原因,无法对引用本身取地址
int (&arry)[3]=a;//正确,不可以声明一个引用数组,可以声明一个数组的引用。
//不可以由引用作为元素组成数组,但是可以对一个数组进行引用。
int (&arry)[3]={1,2,3}; //错误,因为编译器会为了{1,2,3}可以寻址,生成一个临时变量,这个时候是对临时变量temp的引用,通过引用修改的也是temp临时变量的值,不会对实际的{1,2,3}做修改,所以这个时候的引用似乎没有什么作用了,所以引用只能读到值,但是不可以访问修改,所以应该加上const变成一个只读的。编译器才不会报错。非const引用不可以对临时变量,局部变量进行引用。无法取这些变量的地址。
编译器对上面代码的转换如下:
int temp[3]={1,2,3};
int (&arry)[3]=temp;
加了const的正确实现:
const int (&arry)[3]={1,2,3};//正确
引用类型:
当函数的参数是一个数组类型的引用时,数组的长度也是参数的类型的一部分,在传递参数时,编译器会检查实参的数组长度与函数定义时的形参中数组的指定长度是否匹配。
多维数组中的第一维与参数类型无关,在传参时只检查多维数组除了第一维以外的其他维的长度与函数定义时的形参类型中指定的长度是否匹配。
实例运行验证:
const引用:
当引用用不同类型的对象初始化,用文字常量等不可寻址的值初始化时,必须为const引用。
不同类型:
double pi=3.14;
int &a=pi;//错误,类型不同,编译器要产生临时变量,而非const引用不可以对临时变量产生引用
编译器转化如下:
int temp=(int)pi;
int &a=pi;
正确的格式:
const int &a=pi;//正确,只读
double &a=pi+1;//错误,无法直接得到pi+1的值,也需要生成临时变量
编译器转化为:
double temp=pi+1;
double &a=temp;
正确的格式:
const double &a=pi+1;//正确
不同类型中牵涉到指针时:
int a=10;
int *p=&a;
int *&b=&a;//错误,类型不同,一个指针型一个不是指针,会产生临时变量
转换:
int *temp=&a;
int *&b=temp;
正确:
const int *&b=&a;
int *&b=p;//也是正确的,类型相同
const int x=1024;
int &m=x;//错误,非const类型引用了const类型
const int &m=x;
int *&m=&x;//错误1,类型不同,一个指针一个非指针,2非const引用const
const int *&m=&x;//错误,类型不同,一个指针一个非指针
const int *const &m=&x;
不可取址的值:
int b=1024;
int &a=b;//正确,正常的用法,存在可以取址的变量b
int &a=1024; //错误,为了取址需要生成一个临时变量
编译器转化为:
int temp=1024;
int &a=temp;
正确的:
const int &a=1024;
const在类中使用:
1 引用类型的成员变量的初始化,不可以直接在构造函数中初始化,因为引用一旦初始化,就不可以再更改,所以不可以用=赋值号来初始化,必须使用初始化列表中来初始化,也就是放在:后,并且构造函数的形参必须是引用类型。
2 在含有引用类型的成员变量的类,不能含有缺省的构造函数,因为引用在类构造时必须进行初始化。缺省构造函数又叫默认构造函数,没有任何参数,不做任何操作。如果类中没有构造函数时编译器会自动生成一个默认构造函数。当声明对象时,会调用一个构造函数。
3 如果两个类要对第三个类的数据进行共享处理,考虑把第三个类作为这两个类的引用类型的成员变量。
引用的作用:
1 使函数的参数具有返回值的能力,通过更改形参达到同时更改实参的效果。
比如实现两个数的交换的函数,一般就是通过指针或者引用实现的。
void swap(int *a,int *b)
调用:swap(&m,&n)
void swap(int &a,int &b)
调用:swap(m,n)
2 给函数传递大型对象。使用引用为函数传递大型参数,不会产生对象的副本,也就是参数传递时,参数无需复制。比如类的对象。
由于重载运算符不能对指针进行操作,所以对于重载运算符的情况传递引用。
引用与返回值:
1
在这种情况下不要错误的认为返回的就是c变量,实际是test函数在调用完以后,test函数的栈空间会被释放掉,会生成一个临时变量temp来保存返回值,这个temp是c变量的一个副本,一个拷贝。
2 返回值赋给引用
函数返回值是一个临时变量的值,这个时候pn会变成一个零时变量的引用,而临时变量在函数调用完成后内存会被释放,导致,pn变成了一个没有指向的引用了,导致编译出错。
3 返回值是引用,再将返回值赋给变量
正确的前提是,变量是全局变量
4 函数返回值为引用,并把返回值赋给引用
5 在+-*/四则运算符不能返回引用。
引用与多态:
引用是除指针外的另外一个可以产生多态效果的手段。一个基类的引用可以指向她的派生类实例。
class A;//基类A
class B:public A{....}//派生类B
B b;//实例化一个类B对象b
A &ref=b;//用派生类对象初始化一个基类引用
ref只能访问派生类对象中从基类继承下来的成员,使基类引用指向派生类。如果A类中定义了虚函数,并且在派生类B中重写了这个虚函数,就可以通过ref产生多态效果。
多态就是比如开门,开窗户,开电视等,这个开,代表多态,也就是可以调用不同派生类中的虚函数。