共计 9459 个字符,预计需要花费 24 分钟才能阅读完成。
title: C++ 常见面试题之根本语言
categories:
- [C++]
tags: - [面试题]
date: 2021/05/11
作者:hackett
微信公众号:加班猿
一、根本语言
1、基本知识
1 说下 C ++ 和 C 的区别
C++ 是面向对象的语言(有重载、继承和多态三种个性,减少了类型平安性能,比方强制类型转换,反对范式编程,比方模板类、函数模板等)
C 是面向过程的结构化编程语言
2 说一下 C ++ 中 static 关键字的作用
-
动态全局变量:
- 全局数据区分配内存,程序运行期间始终存在
- 未初始化 主动初始化为 0
- 申明它的整个文件可见,文件之外不可见
-
动态局部变量:
- 该对象的申明处时 被首次初始化 ,即 当前的函数调用不再进行初始化
- 作用域仍为 部分作用域,当定义它的函数或者语句块完结的时候,作用域完结
-
动态函数:
- 只在申明的文件中可见,不可被其余文件调用
- 其它文件中能够定义雷同名字的函数,不会发生冲突
-
静态数据成员:
- 静态数据成语在程序中只有一个拷贝,由该类型所有对象共享拜访,只调配一次内存
- 存储在全局数据区,静态数据成员定义时要调配空间,所以不能在类申明中定义
-
动态成员函数:
- 它为 类的全副服务 而不是为某一个类的具体对象服务
- 它 不具备 this 指针 , 无法访问属于类对象的非静态数据成员,也无法访问非动态成员函数
3 说一说 C ++ 中四种 cast 转换
-
const_cast
用于将 const 变量转为非 const
-
static_cast
用于各种隐式转换,比方非 const 转 const,void* 转指针等, static_cast 能用于多态向上转化,如果向下转能胜利然而不平安,后果未知;
-
dynamic_cast
用于动静类型转换。只能用于含有虚函数的类,用于类档次间的向上和向下转化。只能转指针或援用。向下转化时,如果是非法的对于指针返回 NULL,对于援用抛异样。要深刻理解外部转换的原理。
向上转换:指的是子类向基类的转换
向下转换:指的是基类向子类的转换
它通过判断在执行到该语句的时候变量的运行时类型和要转换的类型是否雷同来判断是否可能进行向下转换。
-
reinterpret_cast
简直什么都能够转,比方将 int 转指针,可能会出问题,尽量少用;
4 说一下 C /C++ 中指针和援用的区别
1. 指针有本人的一块空间,而援用只是一个别名;
2. 应用 sizeof 看一个指针的大小是 4,而援用则是被援用对象的大小;
3. 指针能够被初始化为 NULL,而援用必须被初始化且必须是一个已有对象 的援用;
4. 作为参数传递时,指针须要被解援用才能够对对象进行操作,而间接对援用的批改都会扭转援用所指向的对象;
5. 能够有 const 指针,然而没有 const 援用;
6. 指针在应用中能够指向其它对象,然而援用只能是一个对象的援用,不能被扭转;
7. 指针能够有多级指针(**p),而援用只有一级;
8. 指针和援用应用 ++ 运算符的意义不一样;
9. 如果返回动态内存调配的对象或者内存,必须应用指针,援用可能引起内存泄露。
5 说一下指针和数组的次要区别
指针和数组的次要区别如下:
指针 | 数组 |
---|---|
保留数据的地址 | 保留数据 |
间接拜访数据,首先取得指针的内容,而后将其作为地址,从该地址中提取数据 | 间接拜访数据, |
通常用于动静的数据结构 | 通常用于固定数目且数据类型雷同的元素 |
通过 Malloc 分配内存,free 开释内存 | 隐式的调配和删除 |
通常指向匿名数据,操作匿名函数 | 本身即为数据名 |
6 说一下野指针是什么?
野指针就是指向一个已删除的对象或者未申请拜访受限内存区域的指针。
7 说一下 C ++ 中的智能指针
share_ptr:申请堆内存初始化为 1,应用时 +1,开释时 -1,为 0 时堆内存开释
unique_ptr:指向的堆内存空间的援用计数,都只能为 1,放弃所指空间,堆内存空间开释回收
weak_ptr:须要搭配 share_ptr 应用,指针被开释所指堆内存的援用计数不会 -1
8 说一下智能指针有没有内存泄露的状况
当两个对象互相应用一个 shared_ptr 成员变量指向对方,会造成循环援用,使援用计数生效,从而导致内存透露
9 说一下智能指针内存泄露如何解决
为了解决循环援用导致的内存透露,引入了 weak_ptr 弱指针,weak_ptr 的构造函数不会批改援用计数的值,从而不会对对象的内存进行治理,其相似一个一般指针,但不指向援用计数的共享内存,然而其能够检测到所治理的对象是否曾经被开释,从而防止非法拜访。
10 说一下为什么析构函数必须是虚函数?为什么 C ++ 默认的析构函数不是虚函数
1、将可能会被继承的父类的析构函数设置为虚函数,能够保障当咱们 new 一个子类,而后应用基类指针指向该子类对象,开释基类指针时能够开释掉子类的空间,避免内存透露。
2、C++ 默认的析构函数不是虚函数是因为虚函数须要额定的 虚函数表和虚表指针 ,占用额定的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会节约内存。因而 C ++ 默认的析构函数不是虚函数,而是 只有当须要当作父类时,设置为虚函数。
11 说一下函数指针
定义:函数指针是指向函数的指针变量。
用处:调用函数和做函数的参数,比方回调函数。
12 说一下 fork 函数
fork()会创立一个新的过程,它简直与调用 fork()的过程截然不同
13 说一下 C ++ 析构函数的作用
1、析构函数与构造函数对应,当对象完结其生命周期,如对象所在的函数已调用结束时,零碎会主动执行析构函数
2、应用的过程中动静的申请了内存,那么最好显示结构析构函数在销毁类之前,开释掉申请的内存空间,防止内存透露
14 说一下动态函数和虚函数的区别
动态函数在编译的时候就曾经确定运行机会,虚函数在运行的时候动静绑定。虚函数因为用了虚函数表机制,调用的时候会减少一次内存开销
15 写个函数在 main 函数执行前先运行
__attribute((constructor)) void before()
{
printf(
“before main\n”);
}
16 C++ 怎么定义常量?常量 寄存在内存中的那个地位?
1、常量在 C ++ 里的定义就是一个 top-level const 加上对象类型,常量定义必须初始化。
2、对于部分对象,常量寄存在栈区,对于全局对象,常量寄存在全局 / 动态存储区。对于字面值常量,常量寄存在常量存储区。
17 const 润饰成员函数的目标?
const 润饰的成员函数表明函数调用不会对对象做出任何更改
18 如果同时定义了两个函数,一个带 const,一个不带,会有问题吗?
不会,这相当于函数的重载。
19 请你来说一说 C ++ 中的隐式类型转换?
首先,对于内置类型,低精度的变量给高精度变量赋值会产生隐式类型转换,其次,对于只存在单个参数的构造函数的对象结构来说,函数调用能够间接应用该参数传入,编译器会主动调用其构造函数生成长期对象
20 请你来说一说 C ++ 函数栈空间的最大值?
默认是 1M,不过能够调整
21 请你来说一说 extern“C”?
C++ 调用 C 函数须要 extern C,因为 C 语言没有函数重载。
22 请你说说你理解的 RTTI?
运行时类型查看,在 C ++ 层面次要体现在 dynamic_cast 和 typeid,VS 中虚函数表的 - 1 地位寄存了指向 type_info 的指针。对于存在虚函数的类型,typeid 和 dynamic_cast 都会去查问 type_info
23 请你说说虚函数表具体是怎么实现运行时多态的?
子类若重写父类虚函数,虚函数表中,该函数的地址会被替换,对于存在虚函数的类的对象,在 VS 中,对象的对象模型的头部寄存指向虚函数表的指针,通过该机制实现多态。
24 请你说说 C 语言是怎么进行函数调用的?
每一个函数调用都会调配函数栈,在栈内进行函数执行过程。调用前,先把返回地址压栈,而后把以后函数的 esp 指针压栈。
25 请你说说 C 语言参数压栈程序?
从右到左
26 请你说说 C ++ 如何解决返回值?
生成一个长期变量,把它的援用作为函数参数传入函数内
27 请你答复一下 C ++ 中拷贝赋值函数的形参是否进行值传递?
不能。如果是这种状况下,调用拷贝构造函数的时候,首先要将实参传递给形参,这个传递的时候又要调用拷贝构造函数。。如此循环,无奈实现拷贝,栈也会满。
28 请你答复一下 malloc 与 new 区别?
1、new 分配内存依照数据类型进行调配,malloc 分配内存依照指定的大小调配;
2、new 返回的是指定对象的指针,而 malloc 返回的是 void*,因而 malloc 的返回值个别都须要进行类型转化。
3、new 不仅调配一段内存,而且会调用构造函数,malloc 不会。
4、new 调配的内存要用 delete 销毁,malloc 要用 free 来销毁;delete 销毁的时候会调用对象的析构函数,而 free 则不会。
5、new 是一个操作符能够重载,malloc 是一个库函数。
6、malloc 调配的内存不够的时候,能够用 realloc 扩容。扩容的原理?new 没用这样操作。
7、new 如果调配失败了会抛出 bad_malloc 的异样,而 malloc 失败了会返回 NULL。
8、申请数组时:new[]一次调配所有内存,屡次调用构造函数,搭配应用 delete[],delete[]屡次调用析构函数,销毁数组中的每个对象。而 malloc 则只能 sizeof(int) * n。
29 请你说一说 select?
select 在应用前,先将须要监控的描述符对应的 bit 地位 1,而后将其传给 select, 当有任何一个事件产生时,select 将会返回所有的描述符,须要在应用程序本人遍历去查看哪个描述符上有事件产生,效率很低,并且其一直在内核态和用户态进行描述符的拷贝,开销很大
30 请你说说 fork,wait,exec 函数?
父过程产生子过程应用 fork 拷贝进去一个父过程的正本,此时只拷贝了父过程的页表,两个过程都读同一块内存,当有过程写的时候应用写实拷贝机制分配内存,exec 函数能够加载一个 elf 文件去替换父过程,从此父过程和子过程就能够运行不同的程序了。fork 从父过程返回子过程的 pid,从子过程返回 0. 调用了 wait 的父过程将会产生阻塞,直到有子过程状态扭转, 执行胜利返回 0,谬误返回 -1。exec 执行胜利则子过程从新的程序开始运行,无返回值,执行失败返回 -1
2、容器和算法
1 请你来说一下 map 和 set 有什么区别,别离又是怎么实现的?
map 和 set 都是 C ++ 的关联容器,其底层实现都是红黑树(RB-Tree)
map 和 set 区别在于:
(1)map 中的元素是 key-value(关键字—值)对:关键字起到索引的作用,值则示意与索引相关联的数据;Set 与之绝对就是关键字的简略汇合,set 中每个元素只蕴含一个关键字。
(2)set 的迭代器是 const 的,不容许批改元素的值;map 容许批改 value,但不容许批改 key。其起因是因为 map 和 set 是依据关键字排序来保障其有序性的,如果容许批改 key 的话,那么首先须要删除该键,而后调节均衡,再插入批改后的键值,调节均衡,如此一来,严重破坏了 map 和 set 的构造,导致 iterator 生效,不晓得应该指向扭转前的地位,还是指向扭转后的地位。所以 STL 中将 set 的迭代器设置成 const,不容许批改迭代器的值;而 map 的迭代器则不容许批改 key 值,容许批改 value 值。
(3)map 反对下标操作,set 不反对下标操作 。map 能够用 key 做下标,map 的下标运算符[] 将关键码作为下标去执行查找,如果关键码不存在,则插入一个具备该关键码和 mapped_type 类型默认值的元素至 map 中,因而下标运算符 [] 在 map 利用中须要慎用,const_map 不能用,只心愿确定某一个要害值是否存在而不心愿插入元素时也不应该应用,mapped_type 类型没有默认值也不应该应用。如果 find 能解决须要,尽可能用 find。
2 请你来说一说 STL 迭代器删除元素?
1. 对于序列容器 vector,deque 来说,应用 erase(itertor)后,后边的每个元素的迭代器都会生效,然而后边每个元素都会往前挪动一个地位,然而 erase 会返回下一个无效的迭代器;
2. 对于关联容器 map set 来说,应用了 erase(iterator)后,以后元素的迭代器生效,然而其构造是红黑树,删除以后元素的,不会影响到下一个元素的迭代器,所以在调用 erase 之前,记录下一个元素的迭代器即可。
3. 对于 list 来说,它应用了不间断调配的内存,并且它的 erase 办法也会返回下一个无效的 iterator,因而下面两种正确的办法都能够应用
3 请你说一说 STL 中 map 数据寄存模式?
红黑树。unordered map 底层构造是哈希表
4 请你说说 STL 中 map 与 unordered_map?
1、Map 映射,map 的所有元素都是 pair,同时领有实值(value)和键值(key)。pair 的第一元素被视为键值,第二元素被视为实值。所有元素都会依据元素的键值主动被排序。不容许键值反复。
底层实现:红黑树
实用场景:有序键值对不反复映射
2、Multimap
多重映射。multimap 的所有元素都是 pair,同时领有实值(value)和键值(key)。pair 的第一元素被视为键值,第二元素被视为实值。所有元素都会依据元素的键值主动被排序。容许键值重 复。
底层实现:红黑树
实用场景:有序键值对可反复映射
5 请你说一说 vector 和 list 的区别,利用,越具体越好?
1、概念:
1)Vector
间断存储的容器,动静数组,在堆上调配空间
底层实现:数组
两倍容量增长:
vector 减少(插入)新元素时,如果未超过过后的容量,则还有残余空间,那么间接增加到最初(插入指定地位),而后调整迭代器。
如果没有残余空间了,则会重新配置原有元素个数的两倍空间,而后将原空间元素通过复制的形式初始化新空间,再向新空间减少元素,最初析构并开释原空间,之前的迭代器会生效。
性能:
拜访:O(1)
插入:在最初插入(空间够):很快
在最初插入(空间不够):须要内存申请和开释,以及对之前数据进行拷贝。
在两头插入(空间够):内存拷贝
在两头插入(空间不够):须要内存申请和开释,以及对之前数据进行拷贝。
删除:在最初删除:很快
在两头删除:内存拷贝
实用场景:常常随机拜访,且不常常对非尾节点进行插入删除。
2、List
动静链表,在堆上调配空间,每插入一个元数都会调配空间,每删除一个元素都会开释空间。
底层:双向链表
性能:
拜访:随机拜访性能很差,只能快速访问头尾节点。
插入:很快,个别是常数开销
删除:很快,个别是常数开销
实用场景:常常插入删除大量数据
2、区别:
1)vector 底层实现是 数组 ;list 是 双向链表。
2)vector 反对随机拜访,list 不反对。
3)vector 是程序内存,list 不是。
4)vector 在两头节点进行插入删除会导致内存拷贝,list 不会。
5)vector 一次性调配好内存,不够时才进行 2 倍扩容;list 每次插入新节点都会进行内存申请。
6)vector 随机拜访性能好,插入删除性能差;list 随机拜访性能差,插入删除性能好。
3、利用
vector 领有一段 间断的内存空间,因而反对随机拜访,如果须要高效的随即拜访,而不在乎插入和删除的效率,应用 vector。
list 领有一段 不间断的内存空间,如果须要高效的插入和删除,而不关怀随机拜访,则应应用 list。
6 请你来说一下 STL 中迭代器的作用,有指针为何还要迭代器?
Iterator(迭代器)提供一种办法程序拜访一个聚合对象中各个元素, 而又不需裸露该对象的外部示意
Iterator 类的拜访形式就是把不同汇合类的拜访逻辑形象进去,使得不必裸露汇合外部的构造而达到循环遍历汇合的成果
7 请你说一说 epoll 原理?
调用程序:
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
首先 创立一个 epoll 对象 ,而后应用 epoll_ctl 对这个对象进行操作, 把须要监控的形容增加进去 ,这些形容如将会以 epoll_event 构造体的模式组成一颗 红黑树 ,接着 阻塞在 epoll_wait,进入大循环,当某个 fd 上有事件产生时,内核将会把其对应的构造体放入到一个链表中,返回有事件产生的链表
8 n 个整数的无序数组,找到每个元素前面比它大的第一个数,要求工夫复杂度为 O(N)?
vector<int> findMax(vector<int>num)
{if(num.size()==0)
return num;
vector<int>res(num.size());
int i=0;
stack<int>s;
while(i<num.size())
{if(s.empty()||num[s.top()]>=num[i])
{s.push(i++);
}
else
{res[s.top()]=num[i];
s.pop();}
}
while(!s.empty())
{res[s.top()]=INT_MAX;
s.pop();}
for(int i=0; i<res.size(); i++)
cout<<res[i]<<endl;
return res;
}
9 请你答复一下 STL 里 resize 和 reserve 的区别?
resize():扭转以后容器内含有元素的数量(size())
reserve():只是为元素预留出空间而已
3、类和数据抽象
1 请你来说一下 C ++ 中类成员的拜访权限?
public:共有的
protected:受爱护的
private:公有的
类内三者都能够拜访,类外只能拜访共有 public 的
2 请你来说一下 C ++ 中 struct 和 class 的区别?
C++ 中,能够用 struct 和 class定义类 ,都能够 继承
struct 的默认继承权限和默认拜访权限是 public
class 的默认继承权限和默认拜访权限是 private
class 还能够定义模板类形参,比方 template <class T, int i>
3 请你答复一下 C ++ 类内能够定义援用数据成员吗?
能够,必须通过成员函数初始化列表初始化。
4 请你答复一下什么是右值援用,跟左值又有什么区别?
右值援用是 C ++11 中引入的新个性 , 它实现了转移语义和准确传递。它的次要目标有两个方面:
- 打消两个对象交互时不必要的对象拷贝,节俭运算存储资源,提高效率。
- 可能更简洁明确地定义泛型函数。
左值和右值的概念:
左值:能对表达式取地址、或具名对象 / 变量。个别指表达式完结后仍然存在的长久对象。
右值:不能对表达式取地址,或匿名对象。个别指表达式完结就不再存在的长期对象。
右值援用和左值援用的区别:
- 左值能够寻址,而右值不能够。
- 左值能够被赋值,右值不能够被赋值,能够用来给左值赋值。
- 左值可变, 右值不可变(仅对根底类型实用,用户自定义类型右值援用能够通过成员函数扭转)。
4、编译与底层
1 请你来说一下一个 C ++ 源文件从文本到可执行文件经验的过程?
对于 C ++ 源文件,从文本到可执行文件个别须要四个过程:
预处理阶段:对源代码文件中文件蕴含关系(头文件)、预编译语句(宏定义)进行剖析和替换,生成预编译文件。
编译阶段:将通过预处理后的预编译文件转换成特定汇编代码,生成汇编文件
汇编阶段:将编译阶段生成的汇编文件 转化成机器码,生成可重定位指标文件
链接阶段:将多个指标文件及所须要的库连接成最终的 可执行指标文件
2 请你答复一下 malloc 的原理,另外 brk 零碎调用和 mmap 零碎调用的作用别离是什么?
Malloc 函数用于动静分配内存。
malloc 其采纳内存池的形式,先申请大块内存作为堆区,而后将堆辨别为多个内存块,以块作为内存治理的根本单位。当用户申请内存时,间接从堆区调配一块适合的闲暇块。
Malloc 采纳隐式链表构造将堆辨别成间断的、大小不一的块,蕴含已调配块和未调配块;
malloc 采纳显示链表构造来治理所有的闲暇块,即应用一个双向链表将闲暇块连接起来,每一个闲暇块记录了一个间断的、未调配的地址
Malloc 在申请内存时 < 128K — 应用零碎函数 brk 在 堆区 中调配
Malloc 在申请内存时 > 128K — 应用零碎函数 mmap 在 映射区 调配
3 请你说一说 C ++ 的内存治理是怎么的?
在 C ++ 中,虚拟内存分为 代码段、数据段、BSS 段、堆区、文件映射区 以及栈区六局部。
代码段:包含只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。
数据段:存储程序中 已初始化的全局变量和动态变量
bss 段:存储 未初始化的全局变量和动态变量(部分 + 全局),以及所有被初始化为 0 的全局变量和动态变量。
堆区:调用 new/malloc 函数时在堆区动静分配内存,同时须要调用 delete/free 来手动开释申请的内存。
映射区:存储动态链接库以及调用 mmap 函数进行的文件映射
栈:应用栈空间存储函数的返回地址、参数、局部变量、返回值
4 请你答复一下如何判断内存透露?
内存透露通常是因为调用了 malloc/new 等内存申请的操作,然而短少了对应的 free/delete
1、内存透露查看工具 Valgrind,mtrace
2、写代码时增加内存申请和开释的统计性能,统计以后申请和开释的内存是否统一,来判断内存是否泄露
5 请你来说一下什么时候会产生段谬误?
段谬误通常产生在 拜访非法内存地址 的时候,具体来说分为以下几种状况:
1、应用 野指针
2、试图 批改字符串常量 的内容
6 请你来说一下 reactor 模型组成?
reactor 模型要求主线程只负责监听文件形容上是否有事件产生,有的话就立刻将该事件告诉工作线程,除此之外,主线程不做任何其余实质性的工作,读写数据、承受新的连贯以及解决客户申请均在工作线程中实现。
7 请本人设计一下如何采纳单线程的形式解决高并发?
在单线程模型中,能够采纳 I/ O 复用 来进步单线程解决多个申请的能力,而后再 采纳事件驱动模型 ,基于 异步回调 来处理事件来
5、C++11
1 请问 C ++11 有哪些新个性?
C++11 最罕用的新个性如下:
auto 关键字:编译器能够依据初始值主动推导出类型。然而不能用于函数传参以及数组类型的推导
nullptr 关键字:nullptr 是一种非凡类型的字面值,它能够被转换成任意其它的指针类型;而 NULL 个别被宏定义为 0,在遇到重载时可能会呈现问题。
智能指针:C++11 新增了 std::shared_ptr、std::weak_ptr 等类型的智能指针,用于解决内存治理的问题。
初始化列表:应用初始化列表来对类进行初始化
右值援用 :基于右值援用能够 实现挪动语义和完满转发,打消两个对象交互时不必要的对象拷贝,节俭运算存储资源,提高效率
atomic 原子操作用于多线程资源互斥操作
新增 STL 容器 array 以及 tuple
利用 Lambda 表达式,能够不便的定义和创立匿名函数
2 请你具体介绍一下 C ++11 中的可变参数模板、右值援用和 lambda 这几个新个性
可变参数模板:
C++11 的可变参数模板,对参数进行了高度泛化,能够示意任意数目、任意类型的参数,其语法为:在 class 或 typename 前面带上省略号”。
右值援用:
C++ 中,左值通常指能够取地址,有名字的值就是左值,而不能取地址,没有名字的就是右值。
利用 Lambda 表达式,能够不便的定义和创立匿名函数
如果你感觉文章还不错,记得 ”点赞关注“
关注我的微信公众号【加班猿】能够获取更多内容