环境:
编译器:CLion2021.3;操作系统:macOS Ventura 13.0.1
文章目录
地表最强C++系列传送门:
「地表最强」C++核心编程(一)内存分区模型
「地表最强」C++核心编程(二)引用
「地表最强」C++核心编程(三)函数提高——暂未更新
「地表最强」C++核心编程(四)类和对象——暂未更新
「地表最强」C++核心编程(五)文件操作——暂未更新
一、引用的基本语法
可以将引用简单理解为给一个变量起的别名,他和原变量共享同一块空间,基本语法如下:
数据类型 &别名 = 原名;
//举例
int a = 10;
int &b = a;//此处便是定义了一个引用
cout << a << endl;//10
cout << b << endl;//10
b = 20;
cout << a << endl;//20
cout << b << endl;//20
通过举例可以看出,对引用的操作会直接改变原变量的值。留意一下这一点,本文第三点将会进一步举例说明。
二、引用的注意事项
2.1 引用必须初始化
int a = 10;
//int &b;//err,因为未初始化
int &b = a;//ok,进行了初始化
2.2 引用初始化后不可更改
这里强调一下,前边说过,引用可以简单理解为别名。这里的不可更改的意思是指:假设定义了一个变量a,用引用b作为a的别名即int &b = a;
那么b将不可以再作为其他变量的别名,但是其内容是可以修改的,不要将二者混淆。
int a = 10;
int &b = a;//定义了一个引用并初始化,此时b是a的别名
int c = 20;
/*
有人认为下边的一行是在更改引用,实际上这个想法是错误的。
要时刻记住,引用就是别名,因此此行代码实际上就相当于a = c;这只是一个赋值操作,显然是没有问题的。
*/
b = c;//ok,这只是个赋值操作。
/*
那什么操作是在更改引用呢?我查看了一些文章都没有这种错误举例,这里便举两种可能的错误:
int &b = c;//有的文章把这个作为修改引用的反例,但我觉得不妥,这个错误应该是重复定义引用b。
&b = c;//这个操作不知道会不会有人写,这是编译器报的错误是不可赋值。
*/
三、引用做函数参数
以交换变量值函数为例。在了解引用之前,我们常用值传递和地址传递:
//值传递
void mySwap01(int a,int b){
int temp = a;
a = b;
b = temp;
}
//地址传递
void mySwap02(int* pa,int* pb){
int temp = *a;
*a = *b;
*b = temp;
}
之所以要用地址传递就是因为值传递的时候,其实是函数又自己给形参开辟了新的空间,实际上交换的也只是形参的值,实参并没有发生改变。但我们使用此来函数往往需要实参发生改变。而地址传递虽然也给自己的形参开辟了新的空间,但这个形参是实参的地址,因此函数可以通过这个地址直接操作实参,从而实现实参的值的交换。
如果文字介绍看不清楚,可以移步「地表最强」C语言(六)函数查看6.3的图解,说不定会对你有帮助。
有了引用之后,我们不需要地址传递也可以实现交换实参的值,只需要将引用作为参数传递也可以完成实参的交换,对于不喜欢使用指针的兄弟们很友好:
void mySwap03(int& a,int& b){
int temp = a;
a = b;
b = temp;
}
int main(){
int a = 10;
int b = 20;
//调用引用传参的函数的时候,参数直接写变量名。把引用想简单一些,就是别名。
mySwap03(a,b);
cout << "a = " << a <<endl;//20
cout << "b = " << b <<endl;//10 成功交换实参
return 0;
}
四、引用做函数的返回值
4.1 不要返回局部变量的引用
int& test01() {//引用作为返回值
int a = 10;//局部变量,函数调用结束后就会释放
return a;//返回了局部变量的引用
}
int main(){
int &ref = test01();
cout << "ref= " << ref << endl;//err。部分编译器下可以正常运行,但这是编译器的功劳,这种操作本身是非法的,因为局部变量已经被释放了
return 0;
}
4.2 返回值是引用的函数调用可以作为左值
int& test02() {
static int a = 10;//静态变量存放在全局区,程序结束后才释放
return a;//返回的是静态变量
}
int main(){
int &ref = test02();//ref是a的别名
cout << "ref= " << ref << endl;//注意与4.1的区别
//4.1中返回的局部变量在打印时已经被释放,而此处返回的是静态变量,函数调用后其空间不会被释放,因此可以合法访问
test02() = 1000;//test02()的返回值是引用,可以作为左值。这里其实相当于操作a
cout << "ref= " << ref << endl;//1000
return 0;
}
五、引用的本质
首先来介绍两个概念:
指针常量int* const p
:该指针指向的内容可以改变,而指针的指向不可以改变;
常量指针const int* p
:该指针指向的内容不可以改变,而指针的指向可以改变。
引用的本质就是指针常量,这也是为什么引用一旦初始化后,就不可以修改指向,但是内容却可以更改。
void func(int &ref) {//更改指向的内容
ref = 100;
}
int main(){
int a = 10;
int &ref = a;//自动转换为 int* const ref = &a; 可以看出引用是指针常量,不能修改指向,因此引用不可更改
ref = 20;//发现ref是引用,自动转换为*ref = 20; 更改的是内容而非指向
cout << "a= " << a << endl;//20
cout << "ref= " << ref << endl;//20
func(a);//更改了内容
cout << "a= " << a << endl;//100
cout << "ref= " << ref << endl;//100
return 0;
}
六、常量引用
通过本文第三节我们知道了引用作文函数参数传递时,可以修改实参。但有时我们并不希望实参被修改,此时就需要常量引用来修饰形参,防止误操作。
也就是说:
1.引用一经初始化,其指向不可更改,但内容可以修改;
2.常量引用不但指向不可更改,内容同样不可更改。
void showValue(const int &val) {//形参是常量引用
// val = 1000;//err,常量引用不可修改其内容,防止了误操作
cout << "val= " << val << endl;
}
引用和常量引用的另一个区别是:不可以用字面值常量给引用赋值,但是可以用字面值常量给常量引用赋值:
int main(){
int a = 10;
// int& ref = 10;//err,引用必须引一块合法的内存空间(栈或堆),不能直接引字面常量
const int& ref = 10;//ok,自动转换为 int temp = 10; const int& ref = temp;
// ref = 20;//err,此时相当于const int* const ref,指向和内容都不能修改
return 0;
}