没有合适的资源?快使用搜索试试~ 我知道了~
首页C++面试精华:引用与指针详解及面试技巧
C++面试精华:引用与指针详解及面试技巧
需积分: 13 0 下载量 179 浏览量
更新于2024-07-18
收藏 2.67MB PDF 举报
C++面试题集锦涵盖了技术类和研发类岗位面试中常见的问题,旨在帮助求职者提升面试准备,减少走弯路,更快找到理想工作。以下是其中两个关键知识点的详细解析: 1. 引用与指针的区别: - 引用是变量的别名,不需要单独分配内存空间,定义时必须初始化且一旦绑定就不能改变。而指针是一个独立的变量,需要内存存储,可以不初始化,指向的地址可变,允许多级指针,但无多级引用。 - 操作上,引用是直接访问目标变量,sizeof引用返回的是变量大小,如`int`;指针是间接访问,sizeof指针则返回指针本身的大小。 - 在参数传递方面,引用传递的是变量的地址,实质上是按地址传参,而指针传递的是指针的值,即使指针改变,也不会影响原实参。 2. 指针与引用在汇编层面的理解: - 从汇编代码看,引用实际上是通过指针实现的。例如,当你定义`int &b = x;`时,汇编指令会将`x`的地址复制到`b`中,这与指针类似。这表明引用虽然语法上看起来像是“直接”引用,但在底层机制上,其实是在操作内存地址。 - 对于参数传递,指针参数是值传递,相当于创建了一个新的地址副本;而引用参数是按地址传递,实参变量的值直接传递给了形参,这意味着形参的改变会影响实参。 理解这些区别有助于面试者展示对C++基础概念的深刻理解和掌握,特别是在面试官考察编程思维和细节处理能力时。同时,了解这些差异也有助于编写更高效、安全的代码,尤其是在处理大量数据或者函数内部修改变量时。
资源详情
资源推荐
37. 组合与继承优缺点?
一:继承
继承是 Is a 的关系,比如说 Student 继承 Person,则说明 Student is a Person。继承的优
点是子类可以重写父类的方法来方便地实现对父类的扩展。
继承的缺点有以下几点:
①:父类的内部细节对子类是可见的。
②:子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继
承的方法的行为。
③:如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出
相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想。
二:组合
组合也就是设计类的时候把要组合的类的对象加入到该类中作为自己的成员变量。
组合的优点:
①:当前对象只能通过所包含的那个对象去调用其方法,所以所包含的对象的内部细节
对当前对象时不可见的。
②:当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中代码不需要修
改当前对象类的代码。
③:当前对象可以在运行时动态的绑定所包含的对象。可以通过 set 方法给所包含对象
赋值。
组合的缺点:①:容易产生过多的对象。②:为了能组合多个对象,必须仔细对接口进
行定义。
38. 左值右值
1) 在 C++11 中所有的值必属于左值、右值两者之一,右值又可以细分为纯右值、将
亡值。在 C++11 中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名
字的就是右值(将亡值或纯右值)。举个例子,int a = b+c, a 就是左值,其有变量名为
a,通过&a 可以获取该变量的地址;表达式 b+c、函数 int func()的返回值是右值,在其
被赋值给某一变量前,我们不能通过变量名找到它,&(b+c)这样的操作则不会通过编
译。
2) C++11 对 C++98 中的右值进行了扩充。在 C++11 中右值又分为纯右值(prvalue,Pure
Rvalue)和将亡值(xvalue,eXpiring Value)。其中纯右值的概念等同于我们在 C++
98 标准中右值的概念,指的是临时变量和不跟对象关联的字面量值;将亡值则是 C++1
1 新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),
比如返回右值引用 T&&的函数返回值、std::move 的返回值,或者转换为 T&&的类型转
换函数的返回值。将亡值可以理解为通过“盗取”其他变量内存空间的方式获取到的值。
在确保其他变量不再被使用、或即将被销毁时,通过“盗取”的方式可以避免内存空间的
释放和分配,能够延长变量值的生命期。
3) 左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,
事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。右值引
用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即
进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只
是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)
变量的别名。左值引用通常也不能绑定到右值,但常量左值引用是个“万能”的引用类型。
它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值
在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始
化。
4) 右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要 std::
move()将左值强制转换为右值。
39. 移动构造函数
1) 我们用对象 a 初始化对象 b,后对象 a 我们就不在使用了,但是对象 a 的空间还在呀(在
析构之前),既然拷贝构造函数,实际上就是把 a 对象的内容复制一份到 b 中,那么为
什么我们不能直接使用 a 的空间呢?这样就避免了新的空间的分配,大大降低了构造的
成本。这就是移动构造函数设计的初衷;
2) 拷贝构造函数中,对于指针,我们一定要采用深层复制,而 移动构造函数中,对于指针,
我们采用浅层复制。浅层复制之所以危险,是因为两个指针共同指向一片内存空间,若
第一个指针将其释放,另一个指针的指向就不合法了。所以我们只要避免第一个指针释
放空间就可以了。避免的方法就是将第一个指针(比如 a->value)置为 NULL,这样在
调用析构函数的时候,由于有判断是否为 NULL 的语句,所以析构 a 的时候并不会回
收 a->value 指向的空间;
3) 移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是
移动构造函数的初值是一个右值引用。意味着,移动构造函数的参数是一个右值或者将
亡值的引用。也就是说,只用用一个右值,或者将亡值初始化另一个对象的时候,才会
调用移动构造函数。而那个 move 语句,就是将一个左值变成一个将亡值。
40. C 语言的编译链接过程?
源代码-->预处理-->编译-->优化-->汇编-->链接-->可执行文件
1) 预处理
读取 c 源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。包括宏
定义替换、条件编译指令、头文件包含指令、特殊符号。 预编译程序所完成的基本
上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译
指令、没有特殊符号的输出文件。.i 预处理后的 c 文件,.ii 预处理后的 C++文件。
2) 编译阶段
编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合
语法规则之后,将其翻译成等价的中间代码表示或汇编代码。.s 文件
3) 汇编过程
汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统
处理的每一个 C 语言源程序,都将最终经过这一处理而得到相应的目标文件。目
标文件中所存放的也就是与源程序等效的目标的机器语言代码。.o 目标文件
4) 链接阶段
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引
用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件
成为一个能够诶操作系统装入执行的统一整体。
41. vector 与 list 的区别与应用?怎么找某 vector 或者 list 的倒数第二
个元素
1) vector 数据结构
vector 和数组类似,拥有一段连续的内存空间,并且起始地址不变。因此能高效的进
行随机存取,时间复杂度为 o(1);但因为内存空间是连续的,所以在进行插入和删除
操作时,会造成内存块的拷贝,时间复杂度为 o(n)。另外,当数组中内存空间不够
时,会重新申请一块内存空间并进行内存拷贝。连续存储结构:vector 是可以实现动
态增长的对象数组,支持对数组高效率的访问和在数组尾端的删除和插入操作,在
中间和头部删除和插入相对不易,需要挪动大量的数据。它与数组最大的区别就是
vector 不需程序员自己去考虑容量问题,库里面本身已经实现了容量的动态增长,而
数组需要程序员手动写入扩容函数进形扩容。
2) list 数据结构
list 是由双向链表实现的,因此内存空间是不连续的。只能通过指针访问数据,所以
list 的随机存取非常没有效率,时间复杂度为 o(n);但由于链表的特点,能高效地进行
插入和删除。非连续存储结构:list 是一个双链表结构,支持对链表的双向遍历。每
个节点包括三个信息:元素本身,指向前一个元素的节点(prev)和指向下一个元素
的节点(next)。因此 list 可以高效率的对数据元素任意位置进行访问和插入删除等
操作。由于涉及对额外指针的维护,所以开销比较大。
区别:
vector 的随机访问效率高,但在插入和删除时(不包括尾部)需要挪动数据,不易
操作。list 的访问要遍历整个链表,它的随机访问效率低。但对数据的插入和删除
操作等都比较方便,改变指针的指向即可。list 是单向的,vector 是双向的。vector
中的迭代器在使用后就失效了,而 list 的迭代器在使用之后还可以继续使用。
3)
int mySize = vec.size();vec.at(mySize -2);
list 不提供随机访问,所以不能用下标直接访问到某个位置的元素,要访问 list 里的
元素只能遍历,不过你要是只需要访问 list 的最后 N 个元素的话,可以用反向迭代
器来遍历:
42. STL vector 的实现,删除其中的元素,迭代器如何变化?为什么是
两倍扩容?释放空间?
size()函数返回的是已用空间大小,capacity()返回的是总空间大小,capacity()-size()则是剩
余的可用空间大小。当 size()和 capacity()相等,说明 vector 目前的空间已被用完,如果再
添加新元素,则会引起 vector 空间的动态增长。
由于动态增长会引起重新分配内存空间、拷贝原空间、释放原空间,这些过程会降低程序
效率。因此,可以使用 reserve(n)预先分配一块较大的指定大小的内存空间,这样当指定大
小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。只有当
n>capacity()时,调用 reserve(n)才会改变 vector 容量。
resize()成员函数只改变元素的数目,不改变 vector 的容量。
1. 空的 vector 对象,size()和 capacity()都为 0
2. 当空间大小不足时,新分配的空间大小为原空间大小的 2 倍。
3. 使用 reserve()预先分配一块内存后,在空间未满的情况下,不会引起重新分配,从而提升
了效率。
4. 当 reserve()分配的空间比原空间小时,是不会引起重新分配的。
5. resize()函数只改变容器的元素数目,未改变容器大小。
6. 用 reserve(size_type)只是扩大 capacity 值,这些内存空间可能还是“野”的,如果此时使用
“[ ]”来访问,则可能会越界。而 resize(size_type new_size)会真正使容器具有 new_size 个对
象。
1. 不同的编译器,vector 有不同的扩容大小。在 vs 下是 1.5 倍,在 GCC 下是 2 倍;
2. 空间和时间的权衡。简单来说, 空间分配的多,平摊时间复杂度低,但浪费空间也多。
3. 使用 k=2 增长因子的问题在于,每次扩展的新尺寸必然刚好大于之前分配的总和,也就
是说,之前分配的内存空间不可能被使用。这样对内存不友好。最好把增长因子设为(1,2)
1. 对比可以发现采用采用成倍方式扩容,可以保证常数的时间复杂度,而增加指
定大小的容量只能达到 O(n)的时间复杂度,因此,使用成倍的方式扩容。
如何释放空间:
由于 vector 的内存占用空间只增不减,比如你首先分配了 10,000 个字节,然后 erase 掉后
面 9,999 个,留下一个有效元素,但是内存占用仍为 10,000 个。所有内存空间是在 vector
析构时候才能被系统回收。empty()用来检测容器是否为空的,clear()可以清空所有元素。
但是即使 clear(),vector 所占用的内存空间依然如故,无法保证内存的回收。
如果需要空间动态缩小,可以考虑使用 deque。如果 vector,可以用 swap()来帮助你释放内
存。
vector(Vec).swap(Vec);
将 Vec 的内存空洞清除;
vector().swap(Vec);
清空 Vec 的内存;
43. 容器内部删除一个元素
1) 顺序容器
erase 迭代器不仅使所指向被删除的迭代器失效,而且使被删元素之后的所有迭代器
失效(list 除外),所以不能使用 erase(it++)的方式,但是 erase 的返回值是下一个有效迭
代器;
It = c.erase(it);
2) 关联容器
erase 迭代器只是被删除元素的迭代器失效,但是返回值是 void,所以要采用 erase(it++)
的方式删除迭代器;
c.erase(it++)
44. STL 迭代器如何实现
1. 迭代器是一种抽象的设计理念,通过迭代器可以在不了解容器内部原理的情况下遍
历容器,除此之外,STL 中迭代器一个最重要的作用就是作为容器与 STL 算法的粘
合剂。
2. 迭代器的作用就是提供一个遍历容器内部所有元素的接口,因此迭代器内部必须保
存一个与容器相关联的指针,然后重载各种运算操作来遍历,其中最重要的是*运算
符与->运算符,以及++、--等可能需要重载的运算符重载。这和 C++中的智能指针很
像,智能指针也是将一个指针封装,然后通过引用计数或是其他方法完成自动释放
内存的功能。
3. 最常用的迭代器的相应型别有五种:value type、difference type、pointer、reference、
iterator catagoly;
45. set 与 hash_set 的区别
1. set 底层是以 RB-Tree 实现,hash_set 底层是以 hash_table 实现的;
2. RB-Tree 有自动排序功能,而 hash_table 不具有自动排序功能;
3. set 和 hash_set 元素的键值就是实值;
4. hash_table 有一些无法处理的型别;
46. hashmap 与 map 的区别
1. 底层实现不同;
2. map 具有自动排序的功能,hash_map 不具有自动排序的功能;
3. hashtable 有一些无法处理的型别;
47. map、set 是怎么实现的,红黑树是怎么能够同时实现这两种容器?
为什么使用红黑树?
1) 他们的底层都是以红黑树的结构实现,因此插入删除等操作都在 O(logn)时间内完成,
因此可以完成高效的插入删除;
2) 在这里我们定义了一个模版参数,如果它是 key 那么它就是 set,如果它是 map,那
么它就是 map;底层是红黑树,实现 map 的红黑树的节点数据类型是 key+value,而
实现 set 的节点数据类型是 value
3) 因为 map 和 set 要求是自动排序的,红黑树能够实现这一功能,而且时间复杂度比
较低。
48. 如何在共享内存上使用 stl 标准库?
1) 想像一下把 STL 容器,例如 map, vector, list 等等,放入共享内存中,IPC 一旦有了
这些强大的通用数据结构做辅助,无疑进程间通信的能力一下子强大了很多。我们
没必要再为共享内存设计其他额外的数据结构,另外,STL 的高度可扩展性将为 IPC
所驱使。STL 容器被良好的封装,默认情况下有它们自己的内存管理方案。当一个
元素被插入到一个 STL 列表(list)中时,列表容器自动为其分配内存,保存数据。考
虑到要将 STL 容器放到共享内存中,而容器却自己在堆上分配内存。一个最笨拙的
剩余116页未读,继续阅读
jackkang01
- 粉丝: 100
- 资源: 2
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- 前端面试必问:真实项目经验大揭秘
- 永磁同步电机二阶自抗扰神经网络控制技术与实践
- 基于HAL库的LoRa通讯与SHT30温湿度测量项目
- avaWeb-mast推荐系统开发实战指南
- 慧鱼SolidWorks零件模型库:设计与创新的强大工具
- MATLAB实现稀疏傅里叶变换(SFFT)代码及测试
- ChatGPT联网模式亮相,体验智能压缩技术.zip
- 掌握进程保护的HOOK API技术
- 基于.Net的日用品网站开发:设计、实现与分析
- MyBatis-Spring 1.3.2版本下载指南
- 开源全能媒体播放器:小戴媒体播放器2 5.1-3
- 华为eNSP参考文档:DHCP与VRP操作指南
- SpringMyBatis实现疫苗接种预约系统
- VHDL实现倒车雷达系统源码免费提供
- 掌握软件测评师考试要点:历年真题解析
- 轻松下载微信视频号内容的新工具介绍
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功