我们可以把引用理解成给一个变量起的别名。
也可以这样理解:
(对照下图理解)
比如,一个变量是一个盒子,盒子里面装的是这个变量的数据。
变量名就是这个盒子本身的名字。比如a。
那引用就是贴在这个盒子上的一个标签贴纸。
贴纸上写的是这个变量的别的名字。比如b,c。
我们可以通过a改变盒子里放的数字,也可以通过b,c改变盒子里放的数字。
注意:

那既然是贴纸就有下面几个属性:
(其实代码也不能实现)。//引用的语法
类型 &引用名 = 变量;
void reference_test(){
int a = 100;
int &b = a;//b引用a,b就是a的别名
cout << "a=" << a << endl;//100
cout << "b=" << b << endl;//100
cout << "&a" << &a << endl;//&a0x61fde4
cout << "&b" << &b << endl;//&a0x61fde4
cout << "-----------" << endl;
b++;//对引用做改变,变量本身也会改变
cout << "a=" << a << endl;//101
cout << "b=" << b << endl;//101
cout << "&a" << &a << endl;//&a0x61fde4
cout << "&b" << &b << endl;//&a0x61fde4
int &r;//这样直接报错,因为引用r没有初始化
int c = 200;
b = c;//这个是将c的值赋值给b,相当于赋值给a,不是修改绑定的目标,这个不报错
char& rc = c;//引用和变量类型不一致,报错
}
使用const关键字修饰的因为即为
常引用,不能通过常引用修改引用的目标
这里和const和指针的关系类似。
const 类型 &引用名 = 变量;
类型 const &引用名 = 变量;//这两种写法是相同的的
注意
是不是const引用只与const和&的相对位置有关系。这个和指向常量的指针类似。
普通引用:也叫左值引用,只能引用左值
const引用:也叫万能引用,既能引用左值,也能引用右值。
左值lvalue:可以放在操作符左边,可以被修改,比如普通变量
右值rvalue:只能放在操作符右边,不能被修改,比如产量10、被const修饰的变量
int func(){
int num = 100;
cout << "&num = " << &num << endl;
return num;//编译器会生成一个临时变量,保存这个函数返回的结果
//临时变量 = num
//注意:临时变量都是右值,只能用const引用来接
}
void lrVal_test(){
//int res = 临时变量(右值);
//临时变量的声明周期是语句级别的,当int res = func();运行结束以后,临时变量就被释放了
int res = func();
//int& res_unconst = func();//报错,普通引用只能引用左值
const int& res_const = func();//正确,const引用可以引用右值
cout << res_const << endl;//100
cout << "&res_const = " << &res_const << endl;
}
引用型函数参数的特点:
void swap1(int& x, int& y){
/*
int temp = b;
b = a;
a = temp;*/
//使用^异或运算符,效率最高
x = x ^ y;//011(x) ^ 101(y) = 110(x)
y = x ^ y;//110(x) ^ 101(y) = 011(y=>3)
x = x ^ y;//110(x) ^ 011(y) = 101(x=>5)
}
void swap2(int* x, int* y){
*x = *x ^ *y;
*y = *x ^ *y;
*x = *x ^ *y;
}
void refArg_test2(){
int a = 3;
int b = 5;
cout << "a = " << a << ",b = " << b <<endl;
swap1(a, b);
cout << "a = " << a << ",b = " << b <<endl;
swap2(&a, &b);
cout << "a = " << a << ",b = " << b <<endl;
}
刚才说了,传递引用型参数开销非常小,所以我们在以后写代码的过程中,只要定义函数要传递参数,都可以定义成引用型函数参数了。
但是有两个问题
问题1:如果传入的这个变量本身被const修饰怎们办?问题2:如果不想这个变量在函数中被修改怎么办?对问题1:
如果定义函数时候,形参是普通引用,而要传入的变量(实参)被const修饰,这样是会报错的。
为什么呢?
被const修饰的变量是右值,右值只能被右值引用所引用。
所以,出现这种情况,我们要给引用形参前面加上const。
让这个引用形参,变成右值引用形参(6.2 常引用、左值和右值)
右值引用无论左值还是右值都可以接受,所以,如果传入普通变量也是没有问题的。
问题1解决。
对问题2:
我们知道,形参是引用和形参是指针效果一样,都可以在函数内改变变量(实参)值。
那如果我们在业务逻辑上不想改变变量(实参)的值怎们办?
给引用形参前面加上const就行啦。
表示:在这个函数中,这个变量是不可以被修改的,如果修改,则编译不会通过。
总结:
通过问题1和问题2我们发现,大部分情况下,我们给引用形参前面加上const都是没有任何问题的。只有当我们需要修改传入的变量的时候,去掉const就可以了。
代码示例:
struct Student{
char name[100];
int age;
};
void print(const Student& s){
//形参类型为const引用,函数内部就无法通过引用去修饰这个变量
//为了提高程序运行效率,则以后就将所有的参数传递都用引用传递
//为了防止有的不想被改变的变量被修改,则我们用const修饰引用形参
//const引用可以接受左值也可以接受右值
cout << "姓名:" << s.name << ",年龄:" << s.age << endl;
}
void refArg_test1(){
Student s = {"辛巴达", 99};
print(s);
}
函数返回值声明为引用类型:
函数返回的结果就是return后面数据的别名,避免了函数返回数据副本的开销。
函数中返回引用,一定要保证在函数返回以后,该引用的目标依然有效
struct A{
int data;
int& func(){
return data;
}
};
void refReturn_test(){
A a = {100};
cout << a.data << endl;
//返回值是个引用,而且是个左值引用,所以可以直接给这个引用赋值
//给这个引用赋值就相当于给data赋值
a.func() = 200;
cout << a.data << endl;//a.data = 200
}
如果对指针不熟悉的可以看这个:指针知识点总结
在底层实现上,引用是用指针实现的。
但是,但是,下面的话很重要:
在c++中,我们认为:
引用不是实体的类型,就是,不像整型、指针类型、char型那样。
在c++中我们认为他没有实体,没有引用给分配地址。
| 引用 | 指针 | ||
|---|---|---|---|
| 1 | 引用必须初始化 | 指针可以不初始化 | |
| 2 | 不能定义引用的指针 | 可以定义指针的指针 | |
| 3 | 不能定义引用的引用 | 可以定义指针的引用 | |
| 4 | 不能定义引用数组(就是数组内元素都是引用) | 可以定义指针数组 | |
| 5 | 可以定义函数引用 | 可以定义函数指针 |
出现上面这些区别的主要原因都是:
在c++中,我们认为引用不是一个实体的类型,编译器是没有给引用分配地址的。
那上面的结论就能理解了:
没有地址,肯定就没有引用的指针(引用的指针就是引用的地址)。
没有地址,就没有引用的引用。
没有地址,就没有引用数组,因为数组要求内部元素地址连续。
其实这样还不是很好理解,这时候又要使用我们的变量盒子与贴画了。


下面分别用代码来展示一下上面5天引用和指针的区别与联系
1.引用必须初始化,指针可以不初始化
void test(){
int a = 10;
int *p;//不会报错
int &r;//会报错
int &r = a;//不会报错
}
2.不能定义引用的指针,可以定义指针的指针
void test(){
int a = 10;
int *p = &a;//不会报错,定义指针变量p
int **pp = &p;//不会报错,定义指针p的指针pp
int &r = a;//不会报错,定义引用r
int& *pr = &r;//会报错,试图定义引用的指针
//int&表示这个指针的类型是int类型的引用
//*pr表示这是一个指针。从指针定义的语法来说看似合理,实则报错
}
3.不能定义引用的引用,可以定义指针的引用
void test(){
int a = 10;
int *p = &a;//定义指针
int* &rp = p;//不会报错,定义指针的引用
int &r = a;
int& &rr = r;//报错,不能定义应用的引用,因为应用本身就不是一个实体
}
4.不能定义引用数组,可以定义指针数组
void test(){
int a = 1, b = 2, c = 3;
int *pr[3] = {&a, &b, &c};//不报错,定义指针数组(数组里都是指针)
int &rr[3] = {a, b, c};//报错,试图定义引用数组(数组里都是引用)
//注意,但是我们可以引用一个数组变量
int b[3] = {1, 2, 3};//这是一个int型数组,数组名为b
int (&br)[3] = b;//不报错,相当于给数组b起了个别名,叫br
}
5.可以定义函数引用,可以定义函数指针
//随便定义一个函数名为func
void func(int a, int b){
cout << "func:" << a << ";" << b << endl;
}
//定音函数指针(函数指针就是指向函数的指针)
void (*funcPtr)(int,int) = func;
funcPtr(10, 20);//输出func:10;20
void (&funcRef)(int, int) = func;
funcRef(10,20);//输出func:10;20