一、什么是引用?引用是干什么的?
所谓引用,就是给变量起一个别名,这个别名就相当于这个变量的另一个名字,也代表这个变量。这也就是说,他们是同一个变量,也就共用同一块空间,并不会开辟一个新的空间。
二、引用 的使用
数据类型 &别名 = 原名
int a=10;
int &b=a; //代表b是a的引用.也就是b是a变量的另一个别名
cout<<"a="<<a<<endl; //结果是a=10
cout<<"b="<<b<<endl; //结果是b=10
相当于下图这种形式
b=100;
cout<<"a="<<a<<endl; //打印出来结果是100
cout<<"b="<<b<<endl; //打印出来结果也是100
当我们修改引用的赋值时,变量的值也会更改,这说明它们共用同一块空间,代表同一个变量。
三、引用的注意事项
1.引用必须初始化
2.引用一旦初始化后,不能更改
3.一个变量可以有多个引用(相当于给一个变量起了多个别名)
int a=10;
int b=20;
//int &c; 这种写法是错误的,声明引用的时候没有初始化这个引用
int &c=a; //这种写法是正确的,它代表,c是a的引用
那为什么必须要给引用初始化呢?
我们之前说过,创建引用的时候并不会开辟一块内存空间,也就是它并不存在自己单独的实体,它依附于变量的内存空间,若我们不给它初始化,那它相当于一块虚无,并无意义。所以,c++并不会允许我们这么去写,它会提醒你需要初始设定值。
int a=10;
int b=20;
int &c=a;
c=b; //并不会把从a的引用变成b的引用,而是将b变量的值赋给a变量
cout<<"a="<<a<<endl; //a=20
cout<<"c="<<c<<endl; //c=20;
cout<<"b="<<b<<endl; //b=20;
执行结果:
如果c的值变为20,我们并不能看出c的引用是否改变,但是a的值也被改变了,说明c还是a变量的引用,如果还是觉得不清晰,我们在这个操作之后再来改变a的值,看c是否改变,就能看出c是谁的引用。
int a = 10;
int b = 20;
int& c = a;
c = b; //并不会把从a的引用变成b的引用,而是将b变量的值赋给a变量
cout << "a=" << a << endl; //a=20
cout << "c=" << c << endl; //c=20;
cout << "b=" << b << endl; //b=20;
cout << "更改a和b的值后,引用c的值" << endl;
a = 50;
b = 100;
cout << "c=" << c << endl;
我们可以看到c的值与a的值相同,而与b的值无关,这说明,引用c并没有被更改,也就是第二条,初始化后不可更改,而c=b;这种操作则会被理解为赋值操作
四、引用的本质
其实上述注意事项中,引用在初始化之后不可更改是因为引用的本质在c++内部实现是一个指针常量.
1.什么是指针常量?
顾名思义,就是指针类型的常量,主语是常量,修饰词是指针类型,也就是说指针类型的这个值是不可更改的,指针类型的值就是指向的地址,也就是指针的指向不可更改,原来指向a,不能将它改为指向b。
使用
数据类型 * const 变量名
// 例如int *const test,这个就是指针常量
这也就解释了为什么引用初始化后不可更改,本质是指针常量,指向就不能改变,在c++内部,如果发现是引用,就会转换为指针常量。
2.什么是常量指针
常量指针,也就是常量类型的指针,相当于常量的指针,代表指针指向的是一个常量,这就说明,指针解引用之后的值是不可更改的,要与指针常量进行区别。
使用
const 数据类型 *变量名
const int *test;
两个作用相加就是常量指针常量,不仅指向不能被修改,解引用之后的值也不能被修改。
使用
const int * const test;
五、引用做函数参数
1.作用:函数传参时,可以利用引用的技术让形参和实参代表的是同一块内存
2.优点:可以简化指针修改实参
//1. 值传递
void Swap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
//2. 地址传递
void Swap02(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
//3. 引用传递
void Swap03(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
int main() {
int a = 10;
int b = 20;
Swap01(a, b);
cout << "a:" << a << " b:" << b << endl;
Swap02(&a, &b);
cout << "a:" << a << " b:" << b << endl;
Swap03(a, b);
cout << "a:" << a << " b:" << b << endl;
结果:
可以看到,地址传递将a,b的值进行了交换,此时实参a,b的值应该是a=20,b=10,然后进行引用传递之后,a=10,b=20,值又换回来了,可以说明,引用传递和地址传递一样,都会改变实参的值,而值传递则不会改变实参的值,而且引用传递不需要解引用,使用起来比较方便。
六、引用做函数返回值
使用
数据类型 & 函数名(){return 变量名 }
注意事项:
1.要在定义函数的时候,在函数名前加&
2.不能返回局部变量的引用
3.如果函数做左值,必须返回引用
对于第一点,因为返回值是引用,所以函数类型应该也要有&
对于第二点,如果返回局部变量的引用,可以看到打印出来的结果是错误的
//返回局部变量引用
int& test01() {
int a = 10; //局部变量
return a;
}
int main() {
//不能返回局部变量的引用
int& ref = test01();
cout << "ref = " << ref << endl;
cout << "ref = " << ref << endl;
return 0;
}
结果
为什么会出现这种情况呢?
我们知道函数调用之后就会结束进程,而引用并没有实体空间,而ref变量已经被销毁了,再打印引用就会出现是随机值的情况(有的编译器第一次调用结果是对的,第二次调用是随机值)
正确使用方法(返回静态变量的引用)
//返回静态变量引用
int& test02() {
static int a = 20;
return a;
}
int main(){
int& ref2 = test02();
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
return 0;
}
第三点,一般都是函数做右值,也就是将函数的返回值赋值给变量,那函数做左值就是函数的返回值会被赋值,普通函数是不可实现的
但是引用作为返回值,就可以这么使用
#include <iostream>
using namespace std;
int &test02() {
static int a = 10;
return a;
}
int main() {
int& ref2 = test02();
test02() = 1000;
cout << "ref2 = " << ref2 << endl;
cout << "ref2 = " << ref2 << endl;
return 0;
}
结果
七、引用和指针的区别
1.引用必须被初始化,指针可以选择不初始化
2.引用指向不能被修改,指针指向可以被修改
3.访问实体的方式不同,引用是编译器处理,指针需要自己解引用
4.引用不可以为空,指针可以为空
5.引用比指针更安全(指针野指针,空指针,悬挂指针)
6.sizeof引用得到的是所指向变量(对象)的大小,sizeof指针得到的是指针的大小
7.引用作为函数参数传递时,传递的是实参本身,指针作为函数参数传递时,传递的是指针变量的值
8.引用比指针使用起来更简洁
9.引用不能有多级,指针有多级指针