🌈个人主页:godspeed_lucip
🔥 系列专栏:《C++程序设计》阅读笔记
本文对应的PDF源文件请关注微信公众号程序员刘同学
,回复C++程序设计
获取下载链接。
1 引用
1.1 概念
引用作为目标的别名而使用,对引用的改动实际就是对目标的改动。
例如:rInt
就是someInt
的引用
int someInt;
int &rInt = someInt;
//int &rInt、int& rInt、int & rInt都是等价的
引用不是值,不占存储空间,声明引用时,目标的存储状态不会改变。
引用只有声明,没有定义(定义必然设计到分配具体空间)
引用在声明时必须被初始化,则会产生编译错误。例如:int &rInt
是错误的
虽然引用运算符与地址操作符使用相同的符号(&
),但它们不是一样的
&
除了引用之外,其他任何时候使用都表示取地址操作符。例如int i=1; cout<<&i;
是输出整形变量i
的地址。
1.2 和引用相关的操作
引用的地址是被引用目标的地址
引用被建立后,实际上就不能让引用指向新的引用目标,因为对引用的操作就是对其引用目标的操作。请看下面的例子:
#include<iostream>
using namespace std;
int main(){
int origin = 1;
int & r_origin = origin; //建立对origin变量的引用
cout<<&origin<<endl;
cout<<&r_origin<<endl; //输出两者的地址
cout<<origin<<endl;
cout<<r_origin<<endl; //输出两者的值
int other = 2; //新建立一个int变量
r_origin = other; //试图让引用指向新的引用目标
cout<<origin<<endl;
cout<<r_origin<<endl;
cout<<other<<endl;//输出三者的值
exit(0);
}
结果:
从上面这个例子也可以看出引用和指针的差别:
引用不能改变指向,但是指针是可以改变指向的(指针常量除外)
1.2.1 什么能被引用
若一个引用对象被声明为Type &r = value
,则要求value
是Type
类型,或者value
可以被隐式转换为Type
类型。
有待商榷,我的C++03版本的dev中,下面的代码是错误的:
int main(){ int a = 1; double &b = a; return 0; }
如果引用类型Type
的初始值不是一个左值,那么将建立一个Type
类型的目标并用初始值初始化,那个目标的地址变成引用的值。
事实上我感觉这条有待商榷。我的C++版本是C++03。下列代码都是错误的:
void test02() { double& r_dou_1 = 1; double& r_dou_2 = 1.0; }
只有这样才是正确的:
void test02() { double sour = 1.0; double& r_dou_2 = sour; }
- 指针也是一种数据类型,也可以被引用。例如:
void test03(){
int b=1;
int* a = &b;
int* &point_r = a;
cout<<*point_r<<endl;
}
结果:
TIP:
int* a = &1;是错误的,只能像int b=1; int* a = &b;
才可以。因为
1
是一个常量右值,它没有一个明确的内存地址
- 不可以对
void
进行引用。例如
void& a=3; //error
void
只是在语法上相当于一个类型,本质上不是类型,没有任何一个变量或对象的其类型为void
。
数组不能被引用
一方面,数组是某个数据类型元素的集合,每个元素皆为引用,意味着每个元素必须初始化为其他内存实体;并且数组的大小必须在编译时就确定,但是引用是在运行时才进行绑定的,因此引用的数组是错误的;
另一方面,数组名只是表示该元素集合空间的起始地址,若对其引用,那就是数组的别名,与指向数组的指针没有什么区别
其实好像也是可以引用的,例如:
int main(){ int arr[] = {1, 2, 3, 4, 5}; int (&ref)[5] = arr; // 引用数组 cout<<ref<<endl; return 0; }
其结果就是:
- 不可以对引用再次引用,也不可以用指针指向引用
其实很好理解,引用本质上不是对象,不占用内存空间。被引用和指向的目标都必须是一个对象。
- 引用不可以使用类型来初始化
例如:
int &ra = int; //error
理解:引用是变量或者对象的引用,而不是类型的引用
- 空引用不能存在,如:
int &r = NULL
是错误的
1.3 用引用传递函数参数
因为引用就是变量的别名,传递引用就是在传递引用对象本身。
传递引用给函数与传递指针的效果一样,传递的是原来的变量或对象(事实上,在底层还是通过传递地址的方式实现的),而不是在函数作用域内建立变量或对象的副本。
例子:使用引用交换变量值
void swap(int &x,int &y){ int temp = x; x = y; y = temp; } void run_swap(){ int a=0; int b=4; cout<<a<<endl; cout<<b<<endl; swap(a,b); cout<<a<<endl; cout<<b<<endl; } int main(){ run_swap(); }
结果:
注意:下面的两个重载函数会报错:
1.4 用引用返回值
请看以下例子:
对于第一种情况:
fn1
以值的形式返回。在返回全局变量temp
时,编译器会在fn1
函数的栈空间中,创建临时变量存储temp
的值。之后再将该临时变量赋值给a
。
对于第二种情况:
fn1
以值的形式返回。与第一种情况相同,编译器同样会创建临时变量。但是接受该变量的是一个引用,于是引用b
的引用变量就变成了该临时变量。但是,由于临时变量是存储在fn1
的栈空间中的,但是当fn1
返回后,其数据就会被销毁,所以引用b就会指向一个无意义的数据。因此会出现warning
。正确做法是这样的:
int x = fn1(5.0);
int &b = x;
对于第三种情况:
fn2
以引用的形式返回,因此不会创建数据副本,而是直接把c
赋值为全局变量temp
的值。
对于第四种情况:
以引用的形式返回,同时以引用的形式接受。这时候,引用d
的目标对象就是temp
。
返回局部变量的引用是错误的(类似于返回局部变量的指针)。因为这可能导致对已释放内存的引用或失效的对象的引用,进而引发未定义行为。
1.5 const限定引用
简单来说,加上const
之后,引用就只能被读,不能被修改
例如下面的代码:
其结果为:(代码较简单,不详细解释)
1.6 返回堆中变量的引用
小tip:
正确代码:
int *p = new int(20); int* &ref = p;
错误代码:
int* &ref = new int(20);
解释:原因是
new int(20)
返回的是一个右值(rvalue),而非左值(lvalue)。在C++中,非const引用无法绑定到右值,因为右值通常是临时的,不可修改。
考虑到new
不一定能成功申请到内存,所以可能返回NULL
,但是引用不可以是NULL
。所以最好增加一个检查
释放内存:
在上述的例子中,
ref
是指针p
的引用,因此可以使用delete ref
或delete p
释放。当然,假如代码是这样的:
int *p = new int(20); int &ref = *p;
那么,使用
delete &ref
(注意这里的&
是取地址操作)或者delete p
释放。
2 总结
C++,犹如编程的交响乐, 在代码的海洋中奏响和谐的旋律。
它是创造者的笔,雕刻着无尽可能,
是思想的翅膀,让梦想飞翔的天空。
无拘无束,灵活多变。
C++,是程序员心中的宝藏,永不凋零的花朵。
渴望挑战C++的学习路径和掌握进阶技术?不妨点击下方链接,一同探讨更多C++的奇迹吧。我们推出了引领趋势的💻C++专栏:《C++程序设计》阅读笔记,旨在深度探索C++的实际应用和创新。🌐🔍