c++中引用面试点7连问以及引用真的不分配内存吗

cpp加油站
• 阅读 2178

本篇文章从面试官的口吻连问7个引用有关的问题,并且从汇编的层面上对引用进行深入分析,让你充分理解引用的概念和原理。

首先还是看一下思维导图:

c++中引用面试点7连问以及引用真的不分配内存吗

1. 引用的背景和概念

说到引用,首先要说一下'&'标识符,其实c语言中这个符号只是用来取地址的,并没有引用的概念,直到c++对这个标识符的作用进行了扩充,才有了引用这个概念。

所谓引用,其实就是给变量取了一个别名,一个简单的例子如下:

int main()
{
    int a = 2;
    int &b = a;
    return 0;
}

对于这段c++代码而言,其实b就是a的别名,对变量b进行操作其实就是对a进行操作。

2. 引用本身具有哪些特点

对于引用,有如下特点:

  • &标识符这里是引用,不是取地址符;
  • 声明引用的时候就必须对其进行初始化,因为引用声明以后你没有办法再对它进行修改,语法上就不支持;
  • 声明一个引用并没有新增加一个变量,只是被引用的变量多了一个别名而已,此时对引用求地址其实就是对被引用的变量求地址;
  • 引用也会分配存储空间,用于保存被引用变量的地址,这一点在第7点中会进行说明;
  • 基于以上原因,引用不可作为数组的元素。

3. 引用作为函数参数有什么特点

引用也可以作为函数参数,而且我们经常会用到,比如如下代码:

#include <stdio.h>

void add(int &p_a)
{
    p_a = p_a+1;
}

int main()
{
    int a = 2;
    printf("a=%d\n", a);
    add(a);
    printf("a=%d\n", a);

    return 0;
}

这段代码在调用add函数的时候,对参数p_a操作其实就是直接对变量a操作,所以在函数调用完成以后,变量a的值也被改变了,基于这一点,当需要在函数内部修改传递进来的变量的值并传出去,也就是一个变量既作为入参,也作为出参,此种情况下,可以使用引用。

4. 什么时候需要使用常引用

当既要使用引用提高程序的效率,又不能在函数内部修改实参的值时,可使用常引用。

大家可能会想,不想修改实参的值,直接使用const传递参数就可以了,何必要使用引用呢,其实就是避免了临时对象的拷贝,这一点对于基础内置类型而言,可能不能提高效率,但是对于一些比较复杂的自定义类型,它所占用的内存较大的情况下,使用引用肯定要比拷贝临时对象效率要高的多。

5. 引用作为函数返回值有什么好处以及需要遵循什么规则

引用作为函数返回值的好处:在内存中不会产生被返回值的临时副本。

引用作为函数返回值需遵循的规则:

  • 不能返回局部变量的引用,因为局部变量在函数返回的同时也会被释放掉;
  • 不能返回函数内部动态分配的变量的引用,因为引用只是作为一个临时变量的出现,并未赋予一个实际的变量,该引用所指向的空间无法被释放;
  • 可以返回类成员的引用,但最好是const类型,防止成员被修改;
  • 为了保证连续使用流操作符(<< >>)重载返回值时,操作的是同一个对象,流操作符重载返回值应该声明为引用;
  • +-*/这四则运算符重载不能返回引用。

6. 引用和多态的关系

引用是c++中另外一种实现多态的手段,与指针一样,也是基类的引用可指向派生类的实例。

7. 引用和指针的区别

之前都说引用和指针最大的区别是引用不会分配存储空间,而数组需要,但其实不是这样的,我们用一段代码进行说明一下:

//test.cpp
int main()
{
    int a = 2;
    int &b = a;
    int *c = &a;

    return 0;
}

对于这段代码,我们使用g++ -g test.cpp编译以后用gdb进行一下调试,看看它对应的汇编指令是怎样的:

(gdb) b main  #在main函数入口处打断点
Breakpoint 1 at 0x400560: file test.cpp, line 3.
(gdb) set disassemble-next-line on  #打开汇编指令开关,后续代码每执行一步,就会打印出来对应执行的汇编指令
(gdb) r
Starting program: /root/a.out 

Breakpoint 1, main () at test.cpp:3
3        int a = 2;
=> 0x0000000000400560 <main()+4>:    c7 45 ec 02 00 00 00    movl   $0x2,-0x14(%rbp) #把2这个值赋给寄存器rbp偏移20个位置的地方,也就是把a这个变量的值赋给它的地址
(gdb) n
4        int &b = a;
=> 0x0000000000400567 <main()+11>:    48 8d 45 ec    lea    -0x14(%rbp),%rax #rbp寄存器偏移20个位置的地址存入rax寄存器,其实就是取变量a的地址
   0x000000000040056b <main()+15>:    48 89 45 f8    mov    %rax,-0x8(%rbp) #rax寄存器的值赋给rbp寄存器偏移8个位置的地方,其实就是把变量a的地址存入rbp寄存器偏移8个位置的地方
(gdb) 
5        int *c = &a;
=> 0x000000000040056f <main()+19>:    48 8d 45 ec    lea    -0x14(%rbp),%rax#rbp寄存器偏移20个位置的地址存入rax寄存器,这里也是在取变量a的地址
   0x0000000000400573 <main()+23>:    48 89 45 f0    mov    %rax,-0x10(%rbp) #把变量a的地址存入rbp寄存器偏移16个位置的地方

看到了吗,注释清楚的写明了每行汇编指令的意思,通过这个注释,我们可以看出来其实引用和指针的汇编指令是一样的,并且引用也是分配了8个字节用来存放被引用变量的地址的,所以从汇编的层面看,引用和指针其实是一样的。

通过以上代码和汇编指令,对引用和数组的区别总结如下:

  • 从c++的层面看,引用是变量的别名,对引用进行操作其实就是对变量本身操作,而指针是通过它所保存的地址来对变量进行间接的操作;
  • 引用和指针一样,都会申请一段内存用来存放变量的地址,我们可以认为引用是匿名指针;
  • 指针本身的值可以修改,也就是说指针可以指向不同的变量,而引用在声明时初始化以后不能再指向别的变量,从这个角度而言,引用可以认为是常量指针。

好了,本篇文章就为大家介绍到这里,觉得内容对你有用的话,记得顺手点个赞哦~

点赞
收藏
评论区
推荐文章
blmius blmius
1年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
1年前
java 复制Map对象(深拷贝与浅拷贝)
java复制Map对象(深拷贝与浅拷贝)CreationTime2018年6月4日10点00分Author:Marydon1.深拷贝与浅拷贝  浅拷贝:只复制对象的引用,两个引用仍然指向同一个对象
Stella981 Stella981
1年前
PhoneGap设置Icon
参考:http://cordova.apache.org/docs/en/latest/config\_ref/images.html通过config.xml中的<icon标签来设置Icon<iconsrc"res/ios/icon.png"platform"ios"width"57"height"57"densi
Wesley13 Wesley13
1年前
Java四种引用类型
引用与对象每种编程语言都有自己操作内存中元素的方式,例如在C和C里是通过指针,而在Java中则是通过“引用”。在Java中一切都被视为了对象,但是我们操作的标识符实际上是对象的一个引用(reference)。//创建一个引用,引用可以独立存在,并不一定需要与一个对象关联Strings;
Stella981 Stella981
1年前
KVM调整cpu和内存
一.修改kvm虚拟机的配置1、virsheditcentos7找到“memory”和“vcpu”标签,将<namecentos7</name<uuid2220a6d1a36a4fbb8523e078b3dfe795</uuid
Easter79 Easter79
1年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
1年前
MySQL查询按照指定规则排序
1.按照指定(单个)字段排序selectfromtable_nameorderiddesc;2.按照指定(多个)字段排序selectfromtable_nameorderiddesc,statusdesc;3.按照指定字段和规则排序selec
Wesley13 Wesley13
1年前
C++:指针和引用
引用的概念及用法 所谓的引用并不是说重新定义的一个新的变量,而是给一个已经定义好了的变量起的一个别名。 下面看看引用到底是如何使用的:voidtest1(){  inta1;  int&ba;//引用变量b是a的别名  std::cout<<"a:address"<<&a<<std::
Wesley13 Wesley13
1年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
helloworld_34035044 helloworld_34035044
7个月前
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为