1.1 创建引用变量
引用是已定义的变量的别名(另一个名称)。
int a;
int &b = a; // 将b作为a变量的别名
C和C++使用&符号来指示变量的地址。C++给&符号赋予了另一个含义,将其用来声明引用。其中,&不是取址运算符,而是类型标识符的一部分(就像int*指的是指向int的指针一样,int&指的是指向int的引用)。
上述代码中的引用声明允许将a和b互换——它们指向相同的值和内存单元,二者可以交替使用。
1.2 引用的注意事项
1)引用声明中的&符号是类型标识符的一部分,不是取址操作。
#include<iostream>
int main(){
using namespace std;
int a = 10;
int &b = a; // 将b作为a变量的别名
cout << "a = " << a << endl;
cout << "b = " << b << endl;
b++;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "address(a) = " << &a << endl;
cout << "address(b) = " << &b << endl;
return 0;
}
输出:
a = 10
b = 10
a = 11
b = 11
address(a) = 0x62fe14
address(b) = 0x62fe14
2)必须在声明引用变量时进行初始化,且引用初始化之后无法改变。
一旦引用在初始化时与某个变量关联起来,就将一直效忠于它,无法更改。
#include<iostream>
int main(){
using namespace std;
int a = 10;
int &b = a; // 将b作为a变量的别名
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "address(a) = " << &a << endl;
cout << "address(b) = " << &b << endl;
int c = 20;
b = c; // 试图将b改为c的引用
cout << "c = " << c << endl;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "address(c) = " << &c << endl;
cout << "address(a) = " << &a << endl;
cout << "address(b) = " << &b << endl;
return 0;
}
输出:
a = 10
b = 10
address(a) = 0x62fe14
address(b) = 0x62fe14
c = 20
a = 20
b = 20
address(c) = 0x62fe10
address(a) = 0x62fe14
address(b) = 0x62fe14
最初,b引用的是变量a,但随后程序试图将b改为c的引用。乍一看,这种意图似乎成功了,因为b的值由10变为了20.但是a的值也变为了20,并且a与b的地址相同但与c的地址不同。由于b是a的别名,因此上述 b = c 的赋值语句等效于下面的语句:
a = c; // 将变量c的值赋给变量a
1.3 数组的引用
#include<iostream>
int main(){
using namespace std;
int arr[] = {1, 2, 3, 4, 5};
/*方法1*/
// step1.定义数组类型
typedef int(MY_ARR)[5];
// step2.建议引用
MY_ARR &arref1 = arr;
/*方法2*/
// 直接定义引用
int (&arref2)[5] = arr;
/*方法3*/
// step1.定义数组引用类型
typedef int(&MY_ARRREF)[5];
MY_ARRREF arref3 = arr;
for(int i=0; i<5; i++){
cout << arref1[i] << '\t';
}
cout << endl;
for(int i=0; i<5; i++){
cout << arref2[i] << '\t';
}
cout << endl;
for(int i=0; i<5; i++){
cout << arref3[i] << '\t';
}
cout << endl;
return 0;
}
输出:
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
02.引用的本质
引用的本质在 c++ 内部实现是一个常量指针.
c++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见。
int &b = a;
实际上是下述代码的伪装表示:
int* const pr = &a;
其中,引用b扮演的角色与表达式*pr相同。
正因为引用本质是一个常量指针,所以创建时必须进行初始化。
//发现是引用,转换为 int* const ref = &a;
void testFunc(int& ref){
ref = 100; // ref 是引用,转换为*ref = 100
}
int main(){
int a = 10;
int& aRef = a; //自动转换为 int* const aRef = &a;这也能说明引用为什么必须初始化
aRef = 20; //内部发现 aRef 是引用,自动帮我们转换为: *aRef = 20;
cout << "a:" << a << endl;
cout << "aRef:" << aRef << endl;
testFunc(a);
return 0;
}
03.指针的指针与指针的引用
摘自:https://www.cnblogs.com/li-peng/p/4116349.html
char *p1 = "hello";
char* &p2 = p1; // 将p2作为指针p1的别名,即指针的引用
3.1 为什么需要使用它们
当我们把一个指针做为参数传一个方法时,其实是把指针的复本传递给了方法,也可以说传递指针是指针的值传递。如果我们在方法内部修改指针会出现问题,在方法里做修改只是修改的指针的copy而不是指针本身,原来的指针还保留着原来的值。
我们用下边的代码说明一下问题:
#include<iostream>
int m_value = 1;
void func(int *p){
p = &m_value;
}
int main(){
using namespace std;
int n = 2;
int *pn = &n;
cout << *pn << endl;
func(pn);
cout << *pn <<endl;
return 0;
}
输出:
2
2
3.2 使用指针的指针
展示一下使用""指针的指针"(二级指针)做为参数:
#include<iostream>
int m_value = 1;
void func(int **p){
*p = &m_value;
// 也可以根据你的需求分配内存
*p = new int;
**p = 5;
}
int main(){
using namespace std;
int n = 2;
int *pn = &n;
cout << *pn << endl;
func(&pn);
cout << *pn <<endl;
return 0;
}
输出:
2
5
我们看一下 func(int **p)
这个方法
-
p
: 是一个指针的指针,在这里我们不会去对它做修改,否则会丢失这个指针指向的指针地址; -
*p
: 是被指向的指针,是一个地址。如果我们修改它,修改的是被指向的指针的内容。换句话说,我们修改的是main()方法里指针pn; -
**p
: 两次解引用是指向main()方法里的pn,即指针pn*指向内当中的具体内容。
3.3 指针的引用
再看一下指针的引用代码:
#include<iostream>
int m_value = 1;
void func(int *&p){
p = &m_value;
// 也可以根据你的需求分配内存
p = new int;
*p = 5;
}
int main(){
using namespace std;
int n = 2;
int *pn = &n;
cout << *pn << endl;
func(pn);
cout << *pn <<endl;
return 0;
}
输出:
2
5
看一下func(int *&p)方法
-
p
: 是指针的引用,main()方法里的 指针pn; -
*p
:是main()方法里的pn指向的内容。
04.引用的使用场景
引用变量的主要用途是作为函数的形参,通过将引用变量用作参数,函数将使用原始数据,而不是其副本。
现在通过一个常见案例——交换两个变量的值进行演示。
交换函数必须能够修改调用程序中变量的值。这意味着按值传递将不管用,因为函数将交换原始变量副本的内容,而不是变量本身的内容。但传递引用时,函数将可以使用原始数据。另一种方法是,传递指针访问原始数据。
#include<iostream>
void swapr(int & a, int & b); // 传递引用进行变量值交换
void swapp(int * p, int * q); // 传递指针进行变量值交换
void swapv(int a, int b); // 传递副本进行变量值交换(不可行)
int main(){
using namespace std;
int a = 111;
int b = 999;
cout << "a = " << a;
cout << " b = " << b << endl;
cout << "Using references to swap contents:\n";
swapr(a, b);
cout << "a = " << a;
cout << " b = " << b << endl;
cout << "Using pointers to swap contents:\n";
swapp(&a, &b);
cout << "a = " << a;
cout << " b = " << b << endl;
cout << "Trying to use passing by value:\n";
swapv(a, b);
cout << "a = " << a;
cout << " b = " << b << endl;
return 0;
}
void swapr(int & a, int & b){
int tmp;
tmp = a;
a = b;
b = tmp;
}
void swapp(int * a, int * b){
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
}
void swapv(int a, int b){
int tmp;
tmp = a;
a = b;
b = tmp;
}
输出:
a = 111 b = 999
Using references to swap contents:
a = 999 b = 111
Using pointers to swap contents:
a = 111 b = 999
Trying to use passing by value:
a = 111 b = 999
05.常量引用
5.1 概述
#include<iostream>
double cube_a(double x); // 接受double类型的参数
double cube_b(double &rx); // 接受double引用
int main(){
using namespace std;
double a = 3;
cout << cube_a(a);
cout << " = cube of " << a << endl;
cout << cube_b(a);
cout << " = cube of " << a << endl;
return 0;
}
double cube_a(double x){
x *= x * x;
return x;
}
double cube_b(double &rx){
rx *= rx * rx;
return rx;
}
输出:
27 = cube of 3
27 = cube of 27
cube_b
修改了main()
中的变量a
的值,而cube_a
没有。这是因为cube_b
使用了引用参数,因此修改rx
实际上就是修改a
。
如果程序员想让函数仅传递变量信息,而不对变量本身进行修改,同时又想使用引用,则应该使用常量引用
:
double cube_b(const double &rx); // const 修饰的引用,不能修改
这样做,当编译器发现代码修改了rx
的值时,将报错。
5.2 临时变量&左值右值
1)左值右值
按值传递的函数,如上述的cube_a
,可使用多种类型的实参,如下的调用都是合法的:
double a = 3;
double z;
z = cube_a(a + 2.0);
z = cube_a(8.0);
但是,若将上面的参数传递给接受引用参数的函数,如上述的cube_b
,将会报错。
因为
double cube_b(double &rx);
中rx
是某个变量的别名,则实参必须是该变量。而表达式a+2.0
并不是变量,8.0
也不是变量而是字面量
。
如图所示,错误提示信息为非常量引用的初始值必须为左值
,接下来介绍左值
与右值
的概念。
左值
:左值参数是指可被引用的数据对象(可通过地址进行访问的数据对象),例如,变量、数组元素、结构成员、引用和解除引用的指针都属于左值;右值
:包括字面常量(用引号括起的字符串除外,它们由其地址表示)和包含多项的表达式。
在C语言中,左值最初是指可出现在赋值语句左边的实体,但这是引入关键字 const 之前的情况。现在,常规变量和const变量都可视为左值,因为可通过地址访问它们。
但是常规变量属于可修改的左值,而const变量属于不可修改的左值。
2)临时变量
但是,如果将函数cube_b(double &rx)
改为如下实现,上述不合法的参数传递将变为合法的:
double cube_b(const double &rx){ // 接收常量引用
return rx * rx * rx; // 常量引用不允许修改rx的值,否则报错,所以将rx *= rx * rx改为rx * rx * rx
}
#include<iostream>
double cube_b(const double &rx); // 接受double引用
int main(){
using namespace std;
double a = 3;
cout << cube_b(a + 2.0);
cout << " = cube of " << a + 2.0 << endl;
cout << cube_b(8.0);
cout << " = cube of " << 8.0 << endl;
return 0;
}
double cube_b(const double &rx){
return rx * rx * rx;
}
输出:
125 = cube of 5
512 = cube of 8
这是因为C++为a+2.0
和8.0
各自生成了一个临时变量。仅当参数为常量引用时,C++才会这样做。
当函数接收的是常量引用时,C++创建临时变量的2种情形:
- 实参的类型正确,但不是左值;
- 实参的类型不正确,但可以转换为正确的类型。
#include<iostream>
double cube_b(const double &rx); // 接受double常引用
int main(){
using namespace std;
double side = 3.0;
double *pd = &side;
double &rd = side;
int edge = 5;
double lens[4] = {2.0, 5.0, 10.0, 12.0};
double c1 = cube_b(side); // rx is side
double c2 = cube_b(lens[2]); // rx is lens[2]
double c3 = cube_b(rd); // rx is rd is side
double c4 = cube_b(*pd); // rx is *pd is side
double c5 = cube_b(edge); // rx is temporary variable
double c6 = cube_b(7.0); // rx is temporary variable
double c7 = cube_b(side + 10.0); // rx is temporary variable
cout << "c1 = " << c1 << endl;
cout << "c2 = " << c2 << endl;
cout << "c3 = " << c3 << endl;
cout << "c4 = " << c4 << endl;
cout << "c5 = " << c5 << endl;
cout << "c6 = " << c6 << endl;
cout << "c7 = " << c7 << endl;
return 0;
}
double cube_b(const double &rx){
return rx * rx * rx;
}
输出:
c1 = 27
c2 = 1000
c3 = 27
c4 = 27
c5 = 125
c6 = 343
c7 = 2197
- 参数
side
、lens[2]
、rd
和*pd
都是有名称的且类型为double的数据对象,因此可以为其创建引用,而不需要临时变量。 -
edge
虽然是变量,但是类型却不正确,double引用不能指向int,此时编译器将生成一个临时匿名变量。 - 参数
7.0
和side+10.0
的类型都正确,但没有名称(即二者均为右值),此时编译器都将生成一个临时匿名变量,并让rx
指向它。这些临时变量只在函数调用期间存在,函数调用结束将被销毁。
也就是说,cube_b(side + 10.0)
等价于double temp = 200; const double &rx = temp;
。
如果函数调用的参数不是左值或与相应的const引用参数的类型不匹配,则C++将创建类型正确的临时匿名变量,将函数调用的参数的值传递给该临时变量,并让参数来引用该变量。
5.3 尽可能使用const
将引用参数声明为常量引用的理由:
-
使用const可以避免无意中修改数据的编程错误;
-
使用const使得函数能够处理const和非const实参,否则将只能接收非const数据;
这是因为C++相对于C语言而言,类型匹配更加严格,对一个赋值语句,要求左右类型必须一致。而C语言则会进行自动类型转换。
-
使用const引用使函数能够正确生成并使用临时变量。
因此,应尽可能将引用形参声明为常量引用。