面试简友广场想法

千万不要错过的后端【纯干货】面试知识点整理 I

2021-06-08  本文已影响0人  阿兵云原生

C++面试题

img

语言相关基础题

对象复用的了解,零拷贝的了解

对象复用

指得是设计模式,对象可以采用不同的设计模式达到复用的目的,最常见的就是继承和组合模式了。

零拷贝:

零拷贝主要的任务就是避免CPU将数据从一块存储拷贝到另外一块存储,主要就是利用各种零拷贝技术,避免让CPU做大量的数据拷贝任务,减少不必要的拷贝,或者让别的组件来做这一类简单的数据传输任务,让CPU解脱出来专注于别的任务。这样就可以让系统资源的利用更加有效。

零拷贝技术常见linux中,例如用户空间到内核空间的拷贝,这个是没有必要的,我们可以采用零拷贝技术,这个技术就是通过mmap直接将内核空间的数据通过映射的方法映射到用户空间上,即物理上共用这段数据

介绍C++所有的构造函数

默认构造函数、一般构造函数、拷贝构造函数

为什么要内存对齐?

image

成员初始化列表的概念,为什么用成员初始化列表会快一些

类型一 : 内置数据类型,复合类型(指针,引用)

类型二 : 用户定义类型(类类型)

对于类型一,在成员初始化列表和构造函数体内进行,在性能和结果上都是一样的

对于类型二,结果上相同,但是性能上存在很大的差别

因为类类型的数据成员对象在进入函数体是已经构造完成,也就是说在成员初始化列表处进行构造对象的工作,这是调用一个构造函数,

在进入函数体之后,进行的是 对已经构造好的类对象的赋值,又调用个拷贝赋值操作符才能完成(如果并未提供,则使用编译器提供的默认按成员赋值行为)

简单的来说:

对于用户定义类型:

所以对于用户定义类型,使用列表初始化可以减少一次默认构造函数调用过程

c/c++ 程序调试方法

image

自己封装宏函数,进行打印出错位置的文件,行号,函数

通过gcc -DDEBUG_EN 打开调试信息输出

#ifdefine DEBUG_EN
#define DEBUG(fmt, args...) \
do { \
printf("DEBUG:%s-%d-%s "fmt, __FILE__, __LINE__, __FUNCTION__, ##args);\
}while(0)
 
#define ERROR(fmt, args...) \
do { \
printf("ERROR:%s-%d-%s "fmt, __FILE__, __LINE__, __FUNCTION__, ##args);\
}while(0)
#else
#define DEBUG(fmt, args) do{}while(0)
#define ERROR(fmt, args) do{}while(0)
#endif

当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存状态记录下来,保存在一个文件中,这种行为就叫做Core Dump.

MAP 文件是程序的全局符号、源文件和代码行号信息的唯一的文本表示方法,是整个程序工程信息的静态文本,通常由linker生成。

通过运行程序,打断点、单步、查看变量的值等方式在运行时定位bug。

file <文件名> 加载被调试的可执行程序文件
b <行号>/<函数名称> 在第几行或者某个函数第一行代码前设置断点
r 运行
s 单步执行一行代码
n 执行一行代码,执行函数调用(如果有)
c 继续运行程序至下一个断点或者结束
p<变量名称> 查看变量值
q 退出

引用是否能实现动态绑定,为什么引用可以实现

因为对象的类型是确定的,在编译期就确定了,指针或引用是在运行期根据他们绑定的具体对象确定。

在什么情况下系统会调用拷贝构造函数:(三种情况)

(1)用类的一个对象去初始化另一个对象时

(2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用

(3)当函数的返回值是类的对象或引用时

左值和右值

左值:可以对表达式取地址,有名字的的值就是左值,一般指表达式结束后依然存在的持久对象

右值:不能对表达式取地址,没有名字的值,就是右值,一般指表达式结束后不再存在的临时对象

右值引用可以实现转移语义和完美转发的新特性

c++的访问限定符

image

在类的内部,不受限定符号的约束,可以随意访问,在类的外部,只能访问类中public的成员

struct 和 class 的区别

struct 和class 都可以定义类,struct连的成员权限都是public的

map

底层实现:红黑树

key 和 value , key 是不可以重复的,所有元素的值都会自动排序,key不允许重复

key 和 value , key是可以重复的,所有元素的值都会自动排序,key不允许重复

vector

连续存储的容器,内存分配在堆上面,动态数组

底层实现:数组

两倍容量增长:vector一次性分配好内存, 在增加新元素的时候,如果没有超过当前的容量,那么直接添加,然后调整迭代器,如果超过了当前的容量, 则vector会重新配置原数组的内存的2倍空间,将原空间元素内存拷贝到新空间,释放掉原空间,且此时迭代器会失效

性能:

插入末尾:空间不够,则需要申请内存,和释放原空间,对数据进行拷贝

空间够,则直接插入,速度很快

插入中间:空间不够,则需要申请内存,和释放原空间,对数据进行拷贝

空间够,内存拷贝

list

image

动态链表,内存分配在堆上,每增加一个数据,则会开辟一个数据的空间,删除一个数据,则会释放掉一个数据的空间

底层实现:双向链表

set

集合,所有元素都会根据元素的值进行排序,且不允许重复

底层实现:红黑树(一种平衡二叉树)

适用场景:有序不重复集合

迭代器

迭代器是类模版,表现的像指针。封装了指针的一些行为,重载了指针的++/--/->/*等操作符号,相当于一种智能指针。可以根据不同的数据结构,来实现 ++ 和 -- 操作

迭代器时如何删除元素的

vector<int> val = { 1,2,3,4,5,6 };  
vector<int>::iterator iter;  
for (iter = val.begin(); iter != val.end(); )  
{  
     if (3 == *iter)  
          iter = val.erase(iter);     //返回下一个有效的迭代器,无需+1  
     else  
          ++iter;  
}  
set<int> valset = { 1,2,3,4,5,6 };  
set<int>::iterator iter;  
for (iter = valset.begin(); iter != valset.end(); )  
{  
     if (3 == *iter)  
          valset.erase(iter++);  
     else  
          ++iter;  
}  

epoll原理

image

resize 和 reverse

resize 是改变容器内含有元素的数量,它会创建元素,且会将值默认为0,如果resize后需要追加数据,则是在尾部追加

reverse 是改变容器的最大容量,它不会创建元素

编译与底层 c++源文件到可执行文件经历的过程

预处理阶段:将源代码文件中头文件,宏定义进行分析和替换,生成预编译文件

编译阶段:将预编译文件转换成特定的汇编代码,生成汇编文件

汇编阶段:将编译阶段的汇编文件转换成机器码,生成可重定位目标文件

链接阶段:将多个目标文件及所需的库链接成最终的可执行文件

编译过程及内存管理

""和<>的区别

"" :

<>:

malloc原理

image

向内存申请一块连续可用的空间,并返回指向这块空间的指针

calloc

向内存申请一块连续可用的空间,并返回指向这块空间的指针

void* calloc(size_t num, size_t size);

realloc

向内存申请一块续可用的空间,并返回指向这块空间的指针

void* realloc(void* ptr, size_t size);

情况1:原有空间之后有足够大的空间

情况2:原有空间之后没有足够大的空间

当是情况1的时候,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。

当是情况2的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

free

用来释放动态开辟的内存

void free(void* ptr);

欢迎点赞,关注,收藏

朋友们,你的支持和鼓励,是我坚持分享,提高质量的动力

image

好了,本次就到这里,**下一次 后端纯干货面试题整理 I I **,

技术是开放的,我们的心态,更应是开放的。拥抱变化,向阳而生,努力向前行。

我是小魔童哪吒,欢迎点赞关注收藏,下次见~

上一篇下一篇

猜你喜欢

热点阅读