引用: 给已存在的变量取别名,不用再另开辟空间
{
int a = 10;
int& ra = a; // &引用标记 ; 引用实体必须存在
}
const int& ra = a; // 值不能被修改
特性:
- 一个变量可以有多个别名
- 引用类型必须和引用实体同一类型
- 引用定义必须初始化
- 一旦引用一个实体,不能再引用其他实体
引用与指针在底层处理方式完全相同,引用就按指定指针方式处理;
int& ra = a; ==> int* const ra = &a;
const int& ra = a;
const int& ra = a ==> const int * const ra = &a;
引用和指针的不同点:
1. 引用在定义时必须初始化,指针没有要求;
2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同一类型实体;
3. 没有NULL引用,但有NULL指针;
4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4字节);
5. 引用自加即引用的实体增加1,指针自加即指针后偏移一个类型大小;
6. 有多级指针,但没有多级引用;
7. 访问实体方式不同,指针需要显示解引用,引用编译器自己处理;
8. 引用比指针使用起来相对安全。
引用为什么要初始化?
一般变量 | 声明:声明变量类型和名字; 定义:根据类型分配内存地址空间; 初始化:将初始值拷贝到变量的内存地址空间。 |
引用变量 | 将引用绑定到初始化对象(要求引用类型时必须有初始值对象且必须为左值) |
当形参为内置类型时,大都使用传值方式;当形参为结构或类类型时,多使用引用。
引用作为函数的参数:
临时变量、引用参数和const
#include <iostream>
using namespace std;
// double refcube(double& ra) // 一般引用传参
double refcube(const double& ra)
{
return ra*ra*ra;
}
int main()
{
double side = 3.0;
double* pd = &side;
double& rd = side;
long edge = 5L;
double lens[4] = { 2.0, 5.0, 10.0, 12.0 };
double c1 = refcube(side);
double c2 = refcube(lens[2]);
double c3 = refcube(rd);
double c4 = refcube(*pd);
double c5 = refcube(edge);
double c6 = refcube(7.0);
double c7 = refcube(side + 10.0);
return 0;
}
在通常传引用中,若传入实参为非左值或者与引用参数类型不匹配或者表达式时就会出错,
如上代码中c5,c6,c7的函数调用就会出错。
解决方法是,将引用参数声明为const类型。
当引用参数为const类型时:
- 参数side、lens[2]、rd、*pd都是有名称的double类型数据对象,所以可以为其创建引用,而不需要创建临时变量;
- 而edge由于类型不匹配,编译器会生成临时变量;
- 参数7.0和side+1.0的类型匹配但是没有名称,编译器会生成一个临时匿名变量。
因为参数声明为const类型,则该函数的目的只是使用传递的值,而不是修改他们,因此实际上实参不匹配, 则其行为类似于按值传递,为确保原始数据不被修改,使用临时变量存储值。这些临时变量只存在于函数调 用期间,此后编译器可以随意删除。
创建临时变量不会造成不利影响,反而会使函数在可处理的参数种类方面更通用。
生成临时变量的两种情况?
- 实参类型正确,但不是左值;
- 实参类型不正确,但可以转换为正确的类型。
补充: 左值参数:可以被引用的数据对象,例如:变量,数组元素,结构成员,引用和解引用的指针 右值参数:字面常量(用引号括起来的字符串不是,它是由其地址表示),包含多项的表达式
将引用参数声明为常量数据的引用理由:
- 使用 const 可以避免无意中修改数据的编程错误;
- 使用 const 使函数能处理 const 和 非const 实参,否则只能接受非const 类型参数;
- 使用 const 引用使函数能正确生成并使用临时变量。
补充:在C++11中 增加了右值引用,这种引用可以指向右值,是使用&&声明的,而&声明的引用也就被称为左值引用。
若引用作为函数返回值类型
在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
所以返回变量生命周期一定比函数的生命周期长。
不能返回局部变量的引用 | 主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。 |
不能返回函数内部new分配的内存的引用 | 虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。 例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。 |
可以返回类成员的引用 | 主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。 如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。 |
流操作符重载返回值声明为“引用”的作用 | 流操作符<<和>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。 可选的其它方案包括: > 返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。 > 对于返回一个流指针则不能连续使用<<操作符。 > 返回一个流对象引用是唯一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性。赋值操作符'='这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的唯一返回值选择。 |
+-*/ 四则运算符不能返回引用 | 主要原因是这四个操作符没有side effect(副作用),因此,它们必须构造一个对象作为返回值. 可选的方案包括: 返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。 根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。 |
值返回 | 返回引用 |
与按值传递参数类似:计算return 后的表达式,将结果返回给调用函数,调用函数接收后将返回值复制到临时位置,调用程序再使用这个值。 | 返回引用则直接将返回值复制给接收的变量 |
返回引用虽然高效,但应避免返回函数结束时就销毁的变量(如:被调用函数中的局部变量)。
避免方法:返回一个作为参数传递给函数的引用。