原来引用还有这么多不为人知的秘密呢?_引用变量

你好,我是安然无虞。

文章目录

学习网站

推荐给老铁们两款学习网站:
面试利器&算法学习:​牛客网​ 风趣幽默的学人工智能:人工智能学习 首个付费专栏:《C++入门核心技术》

写在前面

前面我们有讲解函数重载相关的知识,因为比较重要,所以讲的比较详细,下面还有一个很重要的知识点——引用,虽然很重要但是不难哦,不信你看看就知道了。

引用概念

引用不是新定义一个变量,而是给已存在变量取一个别名编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

比如:我们知道,在水浒传中有一个好汉名叫李逵,他在家呢叫“铁牛”,江湖人称“黑旋风”。

有点小可爱哦。

原来引用还有这么多不为人知的秘密呢?_算法_02


基本结构:

类型& 引用变量名 = 引用实体;

注意哦,引用类型必须是和引用实体是同种类型的。

看下面一段代码:

#include<iostream>
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共用同一块空间。

原来引用还有这么多不为人知的秘密呢?_引用变量_03

调试代码验证一下上面的说法,看四个变量的地址:

原来引用还有这么多不为人知的秘密呢?_c++_04


很明显地址一样,所以说,引用不是新定义一个变量,而是给已存在的变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

引用特性

  • 引用在定义时必须初始化;
  • 一个变量可以有多个引用;
  • 引用一旦引用一个实体,不能再引用其他实体。

对上述三个特性一一说明:

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;//编译错误
}

常引用

开始之前呢,先说一下取别名的原则:

对原引用变量,读写权限要么不变,要么缩小,但是不能放大。

原来引用还有这么多不为人知的秘密呢?_引用变量_05

void TestConstRef()
{
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语言的时候知道不同类型的值相互赋值时会发生隐式类型的转换,比如:

double d = 3.14;
int f = d;

原来引用还有这么多不为人知的秘密呢?_c++_06


很明显,发生隐式类型转换时中间会产生一个临时变量,而临时变量具有常性。

知道这一点上之后,就好解释这段代码了:

double d = 3.14;
//int& rd = d;编译出错,double->int 类型不同
const int& rd = d;//编译通过,想想为什么?

原来引用还有这么多不为人知的秘密呢?_c语言_07


上面有说到rd不是d的别名,而是临时变量的别名,下面我们调试验证:

原来引用还有这么多不为人知的秘密呢?_c++_08

引用使用场景

· 做参数

之前我们写交换函数是这样写的:

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;
}

使用引用传参的好处:

  1. 无需解引用,简单多了;
  2. 使用引用传参更直观,更好理解。

· 做返回值

我们知道,传值返回时会有一个拷贝,而传引用返回就没有这个拷贝,返回的直接就是变量的别名,这样可以减少拷贝,提高效率。
思考下面一段代码:

int& Count()
{
static int n = 0;//注意静态变量的特点:只会初始化一次
n++;
return n;
}

int main()
{
int& ret = Count();
return 0;
}

原来引用还有这么多不为人知的秘密呢?_初始化_09


传引用返回的是返回值的别名,传值返回的是那个临时变量

所以这里返回的是n的别名,为了证明这个说法,只需要验证ret 和 n的地址一样,意味着ret 就是 n 的别名。

好,下面进行调试验证:

原来引用还有这么多不为人知的秘密呢?_c语言_10


故而证明上述结论:

传引用返回的是返回值的别名,传值返回的是那个临时变量

好的,现在将代码改动一点:

int Count()
{
static int n = 0;
n++;
return n;
}

int main()
{
int ret = Count();
return 0;
}

原来引用还有这么多不为人知的秘密呢?_引用变量_11


我们知道,传值返回的是那个临时变量,那么如何证明产生临时变量了呢?很简单,不信你看:

int& ret = Count();//报错

其实不难,不过需要你细品。
再看下面一段代码,问输出结果是什么?为什么?

int& Add(int a, int b)
{
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 是局部变量,出了作用域就被销毁了,所以不能使用引用传参,会导致野指针。

原来引用还有这么多不为人知的秘密呢?_c++_12

注意:函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。

传值、传引用效率比较

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

引用和指针的区别

在语法概念上引用就是一个别名,没有独立的空间,和其引用的实体共用同一块空间。

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;

我们来看一下引用和指针的汇编代码对比:

原来引用还有这么多不为人知的秘密呢?_引用变量_13


看到了吗,一模模一样!

总结引用和指针的不同点:

  1. 引用在定义时必须初始化,指针没有要求;
  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型的实体;
  3. 没有NULL引用,但有NULL指针;
  4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台占4个字节,64位平台占8个字节);
  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小;
  6. 有多级指针,但是没有多级引用;
  7. 访问实体方式不同,指针需要显示解引用,引用是编译器自己处理;
  8. 引用比指针使用起来相对更加安全。

上面这8点,不要死记硬背,要理解性记忆哦!

大厂面试真题

1、关于引用以下说法错误的是( )。(阿里)
A.引用必须初始化,指针不必
B.引用初始化以后不能被改变,指针可以改变所指的对象
C.不存在指向空值的引用,但是存在指向空值的指针
D.一个引用可以看作是某个变量的一个“别名”
E.引用传值,指针传地址
F.函数参数可以声明为引用或指针类型
解析:引用表面好像是传值,其本质也是传地址,只是这个工作有编译器来做,所以E错。

2、引用”与指针的区别是什么( )
A.指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作
B.引用通过某个引用变量指向一个对象后,对它所指向的变量间接操作。程序中使用引用,程序的可读性差;而指针本身就是目标变量的别名,对指针的操作就是对目标变量的操作
C.指针比引用更节省存储空间
D.以上都不正确

解析:指针是间接操作对象,引用时对象的别名,对别名的操作就是对真实对象的直接操作;指针需要开辟空间,引用不需要开辟空间。

3、关于引用与指针的区别,下面叙述错误的是( )
A.引用必须被初始化,指针不必
B.指针初始化以后不能被改变,引用可以改变所指的对象
C.删除空指针是无害的,不能删除引用
D.不存在指向空值的引用,但是存在指向空值的指针

解析:空指针没有任何指向,删除无害,引用是别名,删除引用就删除真实对象。

原来引用还有这么多不为人知的秘密呢?_c语言_14

遇见安然遇见你,不负代码不负卿。