你好,我是安然无虞。
文章目录
- 学习网站
- 写在前面
- 引用概念
- 引用特性
- 常引用
- 引用使用场景
- · 做参数
- · 做返回值
- 传值、传引用效率比较
- 引用和指针的区别
- 大厂面试真题
学习网站
推荐给老铁们两款学习网站:
面试利器&算法学习:牛客网 风趣幽默的学人工智能:人工智能学习 首个付费专栏:《C++入门核心技术》
写在前面
前面我们有讲解函数重载相关的知识,因为比较重要,所以讲的比较详细,下面还有一个很重要的知识点——引用,虽然很重要但是不难哦,不信你看看就知道了。
引用概念
引用不是新定义一个变量,而是给已存在变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:我们知道,在水浒传中有一个好汉名叫李逵,他在家呢叫“铁牛”,江湖人称“黑旋风”。
有点小可爱哦。
基本结构:
类型& 引用变量名 = 引用实体;
注意哦,引用类型必须是和引用实体是同种类型的。
看下面一段代码:
using std::cout;
using std::endl;
int main()
{
int a = 10;
int& b = a;//b是a的引用(别名)
int& c = a;//c是a的引用(别名)
int& d = b;//d是b的引用(别名)
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
cout << "d = " << d << endl;
return 0;
}
前面引用的概念已经说了,引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块空间。
这样的话也就是说,变量a、b、c、d共用同一块空间。
调试代码验证一下上面的说法,看四个变量的地址:
很明显地址一样,所以说,引用不是新定义一个变量,而是给已存在的变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
引用特性
- 引用在定义时必须初始化;
- 一个变量可以有多个引用;
- 引用一旦引用一个实体,不能再引用其他实体。
对上述三个特性一一说明:
1.引用在定义时必须初始化
void Test1(){
int a = 10;
int& ra; //这条语句编译时会出错
int& rb = a; //引用在定义时必须初始化
}
2.一个变量可以有多个引用
void Test2(){
int a = 10;
int& ra = a;
int& rb = a;
}
3.引用一旦引用一个实体,再不能引用其他实体(注意哦,这点跟指针不同)
void Test3(){
int a = 10;
int b = 20;
int& ra = a;
int& ra = b;//编译错误
}
常引用
开始之前呢,先说一下取别名的原则:
对原引用变量,读写权限要么不变,要么缩小,但是不能放大。
{
const int a = 10;//常变量,只读不可写
//int& ra = a;编译报错,a->ra 权限放大(a只读,ra可读可写)
const int& ra = a;//a->ra 权限不变
//int& b = 10;编译报错,常量10->a 权限放大
const int& b = 10;//10->b 权限不变
double d = 3.14;
//int& rd = d;编译出错,double->int 类型不同
const int& rd = d;//编译通过,想想为什么?
}
下面这段代码:
double d = 3.14;//int& rd = d;编译出错,double->int 类型不同
const int& rd = d;//编译通过,想想为什么?
为什么加上const就可以了呢?
不着急,先说说之前学习过的知识。我们在学习C语言的时候知道不同类型的值相互赋值时会发生隐式类型的转换,比如:
int f = d;
很明显,发生隐式类型转换时中间会产生一个临时变量,而临时变量具有常性。
知道这一点上之后,就好解释这段代码了:
//int& rd = d;编译出错,double->int 类型不同
const int& rd = d;//编译通过,想想为什么?
上面有说到rd不是d的别名,而是临时变量的别名,下面我们调试验证:
引用使用场景
· 做参数
之前我们写交换函数是这样写的:
void Swap(int* left, int* right){
int temp = *left;
*left = *right;
*right = temp;
}
现在我们学习引用后,大可不必像之前那样:
void swap(int& left, int& right){
int temp = left;
left = right;
right = temp;
}
使用引用传参的好处:
· 做返回值
我们知道,传值返回时会有一个拷贝,而传引用返回就没有这个拷贝,返回的直接就是变量的别名,这样可以减少拷贝,提高效率。
思考下面一段代码:
{
static int n = 0;//注意静态变量的特点:只会初始化一次
n++;
return n;
}
int main()
{
int& ret = Count();
return 0;
}
传引用返回的是返回值的别名,传值返回的是那个临时变量。
所以这里返回的是n的别名,为了证明这个说法,只需要验证ret 和 n的地址一样,意味着ret 就是 n 的别名。
好,下面进行调试验证:
故而证明上述结论:
传引用返回的是返回值的别名,传值返回的是那个临时变量。
好的,现在将代码改动一点:
{
static int n = 0;
n++;
return n;
}
int main()
{
int ret = Count();
return 0;
}
我们知道,传值返回的是那个临时变量,那么如何证明产生临时变量了呢?很简单,不信你看:
其实不难,不过需要你细品。
再看下面一段代码,问输出结果是什么?为什么?
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;
return 0;
}
其实这里的 Add 函数是错误的,因为 c 是局部变量,出了作用域就被销毁了,所以不能使用引用传参,会导致野指针。
注意:函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
引用和指针的区别
在语法概念上引用就是一个别名,没有独立的空间,和其引用的实体共用同一块空间。
int a = 10;//语法角度而言,ra是a的别名,没有额外开空间
int& ra = a;
//语法角度而言,pa存储a的地址,pa开了4/8个字节的空间
int* pa = &a;
不过在底层实现上其实是有空间的,因为引用是按照指针方式来实现的。看汇编代码即可证明:
int a = 10;int& ra = a;
ra = 20;
int* pa = &a;
*pa = 20;
我们来看一下引用和指针的汇编代码对比:
看到了吗,一模模一样!
总结引用和指针的不同点:
上面这8点,不要死记硬背,要理解性记忆哦!
大厂面试真题
1、关于引用以下说法错误的是( )。(阿里)
A.引用必须初始化,指针不必
B.引用初始化以后不能被改变,指针可以改变所指的对象
C.不存在指向空值的引用,但是存在指向空值的指针
D.一个引用可以看作是某个变量的一个“别名”
E.引用传值,指针传地址
F.函数参数可以声明为引用或指针类型
解析:引用表面好像是传值,其本质也是传地址,只是这个工作有编译器来做,所以E错。
2、引用”与指针的区别是什么( )
A.指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作
B.引用通过某个引用变量指向一个对象后,对它所指向的变量间接操作。程序中使用引用,程序的可读性差;而指针本身就是目标变量的别名,对指针的操作就是对目标变量的操作
C.指针比引用更节省存储空间
D.以上都不正确
解析:指针是间接操作对象,引用时对象的别名,对别名的操作就是对真实对象的直接操作;指针需要开辟空间,引用不需要开辟空间。
3、关于引用与指针的区别,下面叙述错误的是( )
A.引用必须被初始化,指针不必
B.指针初始化以后不能被改变,引用可以改变所指的对象
C.删除空指针是无害的,不能删除引用
D.不存在指向空值的引用,但是存在指向空值的指针
解析:空指针没有任何指向,删除无害,引用是别名,删除引用就删除真实对象。
遇见安然遇见你,不负代码不负卿。