概念与创建引用
变量名,本身是一段内存的引用,即别名(alias)。此处引入的引用,是为己有变量起一个别名。
声明如下:
int main()
{
int a;
int &b = a;
}
规则
引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故而类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址。
声明的时候必须初始化,一经声明,不可变更。
-
可对引用,再次引用。多次引用的结果,是某一变量具有多个别名。
int main() { int a,b; int &r = a; int &r = b; //错误,不可更改原有的引用关系 float &rr = b; //错误,引用类型不匹配 cout<<&a<<&r<<endl; //变量与引用具有相同的地址。 int &ra = r; //可对引用更次引用,表示 a 变量有两个别名,分别是 r 和 ra }
应用
引用用于函数参数
以实现两个数据的交换为例:
void swapv(int a, int b); //传值无法实现数据的交换
void swapp(int *a, int *b); //传指针实现数据交换
void swapr(int &a, int &b); //传引用也可以实现数据交换,避免用指针来解决
作为函数参数:
void swapv(int a, int b)
{
int temp;
temp = a;
a = b;
b = temp;
}
void swapp(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void swapr(int &a, int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
引用用于结构
引用非常适合用于结构和类,确实,引入引用主要也是为了用于这些类型的,而不是基本的内置类型
假设有如下结构定义:
struct people
{
string name;
int age;
float height;
};
则可以这样编写函数原型,将指向结构的引用作为函数参数:
void set_age(people &p);
如果不希望修改函数传入的结构,也可以使用const(在引用补充部分会说):
void display(const people &p);
函数返回引用:
people & add_age(people &p1,const people &p2);
p3=add_age(p1,p2);
这里可以理解为:如果add_age返回的是一个结构体,而不是指向结构的引用,将把整个结构复制到一个临时位置,再将这个拷贝复制给p3,但当返回值为引用时,会直接将p1(p2)复制到p3,效率更高。
返回引用时需要注意的问题:应避免返回函数终止时不再存在的内存单元引用,类似于下面的代码:
const people &clone(people &p1)
{
people p3;
p3=p1;
return p3;
}
//函数返回了一个临时变量的引用,函数退出后,它将不再存在
引用用于类对象
以拼接字符串为例:
//函数申明
string version1(const string &s1, const string &s2);
const string &version2(string &s1, const string &s2);
//函数实现
string version1(const string &s1, const string &s2)
{
string temp;//定义临时变量
temp = s2 + s1 + s2;
return temp;
}
const string &version2(string &s1, const string &s2)
{
s1 = s2 + s1 + s2; //修改了S1的值
return s1;
}
引用补充
引用的本质是指针,C++对裸露的内存地址(指针)作了一次包装。又取得的指针的优良特
性。所以再对引用取地址,建立引用的指针没有意义。
-
可以定义指针的引用,但不能定义引用的引用。
int a; int* p = &a; int*& rp = p; // ok int& r = a; int&& rr = r; // error
-
可以定义指针的指针(二级指针),但不能定义引用的指针。
int a; int* p = &a; int** pp = &p; // ok int& r = a; int&* pr = &r; // error
-
可以定义指针数组,但不能定义引用数组,可以定义数组引用。
int a, b, c; int* parr[] = {&a, &b, &c}; // ok int& rarr[] = {a, b, c}; // error int arr[] = {1, 2, 3}; int (&rarr)[3] = arr; // ok 的
-
常引用
-
const 对象的引用必须是 const 的,将普通引用绑定到 const 对象是不合法的。 这 个原因比较简单。既然对象是 const 的,表示不能被修改,引用当然也不能修改,必须使 用 const 引用。
const int a=10; int &ra=a;//error const int &rra=a;//ok
-
const 引用可使用相关类型的对象(常量,非同类型的变量或表达式)初始化。这个是 const 引用与普通引用最大的区别。
double refcube(const double &ra) { return ra*ra*ra; } //考虑如下代码: double side = 3.0; double *pd = &side; double &rd = side; long edge = 5L; double lens[4] = {3.0, 4.0, 5.0, 6.0}; double c1 = refcube(side); //ra is side; double c2 = refcube(lens[2]); //ra is lens[2]; double c3 = refcube(rd); //ra is rd is side; double c4 = refcube(*pd); //ra is *pd is side; double c5 = refcube(edge); //ra is tamporary variable; double c6 = refcube(7.0); //ra is tamporary variable; double c7 = refcube(side + 10.0); //ra is tamporary variable; 说明:如果函数调用的参数出现以下两种情况: * 为常量或者表达式(如c6和c7) * 参数与const引用的参数类型不匹配 那么C++将创建正确的临时变量,将函数调用的参数(实参)的值传递给该临时变量,并让参数(形参)来引用该变量。
-
应尽可能使用const
- 使用 const 可以避免无意修改数据的编程错误。
- 使用 const 可以处理 const 和非 const 实参。否则将只能接受非 const 数据。
- 使用 const 引用,可使函数能够正确的生成并使用临时变量(如果实参与引用参
数不匹配,就会生成临时变量)。
-
何时使用引用
使用引用参数的原因主要有两个
能够修改调用函数中的数据对象
-
通过传递引用而不是整个数据对象,提高程序的运行速度
使用引用的一些指导原则如下表:
内置数据类型 | 数组 | 结构 | 类对象 | |
---|---|---|---|---|
对于使用传递的值而不做修改的函数 | 值传递 | const指针 | const指针或const引用 | const引用 |
对于修改调用函数中数据对象的函数 | 指针 | 指针 | 指针或引用 | 引用 |
关于左值,右值,左值引用,右值引用,引用类型的重载
-
左值,右值
在C++11中所有的值必属于左值、右值两者之一,右值又可以细分为纯右值、将亡值。在C++11中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。举个例子,int a = b+c, a 就是左值,其有变量名为a,通过&a可以获取该变量的地址;表达式b+c、函数int func()的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编译。1.
-
左值引用,右值引用
左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。
右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。
左值引用通常也不能绑定到右值,但常量左值引用(const)是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。
int &a = 2; # 左值引用绑定到右值,编译失败
int b = 2; # 非常量左值
const int &c = b; # 常量左值引用绑定到非常量左值,编译通过
const int d = 2; # 常量左值
const int &e = c; # 常量左值引用绑定到常量左值,编译通过
const int &b =2; # 常量左值引用绑定到右值,编程通过
右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值,例如:
int a;
int &&r1 = c; # 编译失败
int &&r2 = std::move(a); # 编译通过
下表列出了在C++11中各种引用类型可以引用的值的类型。
3.引用类型的重载
由于类设计和STL经常使用引用参数,因此知道不同引用类型的重载很有用,请看下面三个原型:
void sink(double &s1);
void sink(const double &s2);
void sink(double && s3);
说明:
左值引用参数r1与左值匹配
常量左值引用参数与左值,常量左值,右值匹配
右值引用参数与右值匹配
举例说明:
double x=3.333;
const double y=22.0;
sink(x);//匹配void sink(double &s1);
sink(y);//匹配void sink(const double &s2);
sink(x+y);//匹配void sink(double && s3);
注:当没有定义void sink(double && s3)时,sink(x+y)将匹配void sink(const double &s2);