1、堆和栈的区别
堆空间的内存是动态分配的,个别寄存对象,并且须要手动开释内存 malloc()/free()或 new/delete。
栈空间的内存是由零碎主动调配,个别寄存局部变量,比方对象的地址等值,不须要程序员对这块内存进行治理,比方,函数中的局部变量的作用范畴(生命周期)就是在调完这个函数之后就完结了。
2、free/delete 与 malloc/free 区别
- new 分配内存依照数据类型进行调配,malloc 分配内存依照指定的大小调配;
- new 不仅调配一段内存,而且会调用构造函数,malloc 不会。(面试官强调)
- new 调配的内存要用 delete 销毁,malloc 要用 free 来销毁;delete 销毁的时候会调用对象的析构函数,而 free 则不会。(面试官强调)
- new 返回的是指定对象的指针,而 malloc 返回的是 void*,因而 malloc 的返回值个别都须要进行类型转化。
- new 是一个操作符能够重载,malloc 是一个库函数。
- malloc 调配的内存不够的时候,能够用 realloc 扩容。扩容的原理?new 没用这样操作。
- new 如果调配失败了会抛出 bad_malloc 的异样,而 malloc 失败了会返回 NULL。
- 申请数组时:new[]一次调配所有内存,屡次调用构造函数,搭配应用 delete[],delete[]屡次调用析构函数,销毁数组中的每个对象。而 malloc 则只能 sizeof(int) * n。
3、指针常量与常量指针
指针常量——指针类型的常量(int *const p)。实质上一个常量,指针用来阐明常量的类型,示意该常量是一个指针类型的常量。在指针常量中,指针本身的值是一个常量,不可扭转,始终指向同一个地址。在定义的同时必须初始化。
常量指针——指向“常量”的指针(const int p,int const p)。常量指针实质上是一个指针,常量示意指针指向的内容,阐明该指针指向一个“常量”。在常量指针中,指针指向的内容是不可扭转的,指针看起来如同指向了一个常量。
须要 C /C++ Linux 高级服务器架构师学习材料后盾加群 812855908(包含 C /C++,Linux,golang 技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg 等)
4、指针与援用的区别
- 指针有本人的一块空间,而援用只是一个别名;
- 应用 sizeof 看一个指针的大小是 4,而援用则是被援用对象的大小;
- 指针能够被初始化为 NULL,而援用必须被初始化且必须是一个已有对象 的援用;
- 作为参数传递时,指针须要被解援用才能够对对象进行操作,而间接对引 用的批改都会扭转援用所指向的对象;
- 能够有 const 指针,然而没有 const 援用;
- 指针在应用中能够指向其它对象,然而援用只能是一个对象的援用,不能被扭转;(面试官强调)
- 指针能够有多级指针(**p),而援用至于一级;
- 指针和援用应用 ++ 运算符的意义不一样;
- 如果返回动态内存调配的对象或者内存,必须应用指针,援用可能引起内存泄露。
5、struct 与 union 数据内存对齐,内存对齐的作用
构造体 struct 内存对齐的 3 大规定:
- 对于构造体的各个成员,第一个成员的偏移量是 0,排列在前面的成员其以后偏移量必须是以后成员类型的整数倍;
- 构造体内所有数据成员各自内存对齐后,构造体自身还要进行一次内存对齐,保障整个构造体占用内存大小是构造体内最大数据成员的最小整数倍;
- 如程序中有 #pragma pack(n)预编译指令,则所有成员对齐以 n 字节为准(即偏移量是 n 的整数倍),不再思考以后类型以及最大构造体内类型。
struct CAT_s
{
int ld;
char Color;
unsigned short Age;
char *Name;
void(*Jump)(void);
}Garfield;
依照下面的 3 大规定间接来进行剖析:
- 应用 32 位编译,int 占 4,char 占 1,unsigned short 占 2,char* 占 4,函数指针占 4 个,因为是 32 位编译是 4 字节对齐,所以该构造体占 16 个字节。(阐明:按几字节对齐,是依据构造体的最长类型决定的,这里是 int 是最长的字节,所以按 4 字节对齐);
- 应用 64 位编译,int 占 4,char 占 1,unsigned short 占 2,char* 占 8,函数指针占 8 个,因为是 64 位编译是 8 字节对齐,(阐明:按几字节对齐,是依据构造体的最长类型决定的,这里是函数指针是最长的字节,所以按 8 字节对齐)所以该构造体占 24 个字节。
联合体 union 内存对齐的 2 大规定:
- 找到占用字节最多的成员;
- union 的字节数必须是占用字节最多的成员的字节的倍数,而且须要可能包容其余的成员.
//x64
typedef union {
long i;
int k[5];
char c;
}D
要计算 union 的大小, 首先要找到占用字节最多的成员, 本例中是 long, 占用 8 个字节,int k[5]中都是 int 类型, 依然是占用 4 个字节的,而后 union 的字节数必须是占用字节最多的成员的字节的倍数, 而且须要可能包容其余的成员, 为了要包容 k(20 个字节), 就必须要保障是 8 的倍数的同时还要大于 20 个字节, 所以是 24 个字节。
内存对齐作用:
- 平台起因(移植起因):不是所有的硬件平台都能拜访任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异样。
- 性能起因:数据结构 (尤其是栈) 应该尽可能地在天然边界上对齐。起因在于,为了拜访未对齐的内存,处理器须要作两次内存拜访;而对齐的内存拜访仅须要一次拜访。
6、TCP 与 UDP 协定区别,具体的底层协定,属于哪一层,TCP 握手协定的实现
TCP 与 UDP 属于传输层
7、说出本人罕用的容器,并给出罕用的一些规范库用法
vector stack queue set
8、map 容器(面试官强调)
C++ map 用法参考链接
9、map 与 hush_map 区别
hash_map 和 map 的区别参考链接
10、给你一亿个数据如何统计 IP 地址呈现的次数
因为电脑内存不够,所以不能一次将数据全副读取,只能履行分而治之的办法,能力满足空间的需要。能够创立一个 hash 表,将数据按肯定的 Key value 分类,能够应用了将 IP 转化成长整形,而后取其后三位转化成字符串作为 Key value,这样就能将所有 IP 按后三位分类。
11、vector 与 list 的区别,以及各种操作的复杂度
1. 概念
1)vector:
间断存储的容器,动静数组,在堆上调配空间。
底层实现:数组。
两倍容量增长:vector 减少(插入)新元素时,如果未超过过后的容量,则还有残余空间,那么间接增加到最初(插入指定地位),而后调整迭代器。如果没有残余空间了,则会重新配置原有元素个数的两倍空间,而后将原空间元素通过复制的形式初始化新空间,再向新空间减少元素,最初析构并开释原空间,之前的迭代器会生效。
性能:
拜访:工夫复杂度 O(1)
插入:在最初插入(空间够):很快。工夫复杂度 O(1)。
在最初插入(空间不够):须要内存申请和开释,以及对之前数据进行拷贝。工夫复杂度 O(n)。
在两头插入(空间够):内存拷贝
在两头插入(空间不够):须要内存申请和开释,以及对之前数据进行拷贝。
删除:在最初删除:很快。
在两头删除:内存拷贝。
实用场景:常常随机拜访,且不常常对非尾节点进行插入删除。
2)list:
动静链表,在堆上调配空间,每插入一个元数都会调配空间,每删除一个元素都会开释空间。
底层:双向链表。
性能:
拜访:随机拜访性能很差,只能快速访问头尾节点。
插入:很快,个别是常数开销。
删除:很快,个别是常数开销。
实用场景:常常插入删除大量数据。
2. 区别
- vector 底层实现是数组;list 是双向 链表。
- vector 反对随机拜访,list 不反对。
- vector 是程序内存,list 不是。
- vector 在两头节点进行插入删除会导致内存拷贝,list 不会。
- vector 一次性调配好内存,不够时才进行 2 倍扩容;list 每次插入新节点都会进行内存申请。
- vector 随机拜访性能好,插入删除性能差;list 随机拜访性能差,插入删除性能好。
3. 利用
vector 领有一段间断的内存空间,因而反对随机拜访,如果须要高效的随即拜访,而不在乎插入和删除的效率,应用 vector。
list 领有一段不间断的内存空间,如果须要高效的插入和删除,而不关怀随机拜访,则应应用 list。
12、排序,以及排序代码实现逻辑,以及复杂度。
13、笼罩与重载的区别。
重载:两个函数名雷同,然而参数列表不同(个数,类型),返回值类型没有要求,在同一作用域中。
重载的实现是:编译器依据函数不同的参数表,对同名函数的名称做润饰,而后这些同名函数就成了不同的函数(至多对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer; 和 function func(p:string):integer;。那么编译器做过润饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就曾经确定了,是动态的(记住:是动态)。也就是说,它们的地址在编译期就绑定了(早绑定),因而,重载和多态无关!
笼罩:子类继承了父类,父类中的函数是虚函数,在子类中从新定义了这个虚函数
真正和多态相干的是“笼罩”。当子类从新定义了父类的虚函数后,父类指针依据赋给它的不同的子类指针,动静(记住:是动静!)的调用属于子类的该函数,这样的函数调用在编译期间是无奈确定的(调用的子类的虚函数的地址无奈给出)。因而,这样的函数地址是在运行期绑定的(晚邦定)。论断就是:重载只是一种语言个性,与多态无关,与面向对象也无关!援用一句 Bruce Eckel 的话:“不要犯傻,如果它不是晚邦定,它就不是多态。”
14、过程与线程的区别。
1. 基本概念:
过程是对运行时程序的封装,是零碎进行资源调度和调配的的根本单位,实现了操作系统的并发;
线程是过程的子工作,是 CPU 调度和分派的根本单位,用于保障程序的实时性,实现过程外部的并发;线程是操作系统可辨认的最小执行和调度单位。每个线程都单独占用一个虚构处理器:单独的寄存器组,指令计数器和处理器状态。每个线程实现不同的工作,然而共享同一地址空间(也就是同样的动态内存,映射文件,指标代码等等),关上的文件队列和其余内核资源。
2. 区别:
- 一个线程只能属于一个过程,而一个过程能够有多个线程,但至多有一个线程。线程依赖于过程而存在。
- 过程在执行过程中领有独立的内存单元,而多个线程共享过程的内存。(资源分配给过程,同一过程的所有线程共享该过程的所有资源。同一过程中的多个线程共享代码段(代码和常量),数据段(全局变量和动态变量),扩大段(堆存储)。然而每个线程领有本人的栈段,栈段又叫运行时段,用来寄存所有局部变量和长期变量。)
- 过程是资源分配的最小单位,线程是 CPU 调度的最小单位;
- 零碎开销:因为在创立或吊销过程时,零碎都要为之调配或回收资源,如内存空间、I/ O 设施等。因而,操作系统所付出的开销将显著地大于在创立或吊销线程时的开销。相似地,在进行过程切换时,波及到整个以后过程 CPU 环境的保留以及新被调度运行的过程的 CPU 环境的设置。而线程切换只须保留和设置大量寄存器的内容,并不波及存储器治理方面的操作。可见,过程切换的开销也远大于线程切换的开销。
- 通信:因为同一过程中的多个线程具备雷同的地址空间,以致它们之间的同步和通信的实现,也变得比拟容易。过程间通信 IPC,线程间能够间接读写过程数据段(如全局变量)来进行通信——须要进程同步和互斥伎俩的辅助,以保证数据的一致性。在有的零碎中,线程的切换、同步和通信都毋庸操作系统内核的干涉
- 过程编程调试简略可靠性高,然而创立销毁开销大;线程正相反,开销小,切换速度快,然而编程调试绝对简单。
- 过程间不会相互影响;线程一个线程挂掉将导致整个过程挂掉
- 过程适应于多核、多机散布;线程实用于多核
15、过程间的通信形式。
过程间通信次要包含管道、零碎 IPC(包含音讯队列、信号量、信号、共享内存等)、以及套接字 socket(面试官强调)。
16、读写锁、自旋锁与死锁。
读写锁理论是一种非凡的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读拜访,写者则须要对共享资源进行写操作。这种锁相对于自旋锁而言,能进步并发性,因为在多处理器零碎中,它容许同时有多个读者来访问共享资源,最大可能的读者数为理论的逻辑 CPU 数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与 CPU 数相干),但不能同时既有读者又有写者。
有了原子操作,就能够了制作管制临界区的锁机制了。自旋锁就是其中的一个代表。自旋锁机制能够用门和锁的例子来比喻。过程执行到某个临界区,相当于要进入一栋房子,这是过程会查看屋内是否有人(过程),如果屋内没有人,则间接拿起钥匙进入并把门锁上(进入临界区);如果屋内有人(过程),则在门口期待(忙期待)屋内的过程进去再进去。能够看出,自旋锁最多只能被一个过程持有,如果有新的过程心愿获取自旋锁,它将会始终忙期待直到前一个持有自旋锁的过程开释锁。
在多道程序零碎中,因为多个过程的并发执行,改善了系统资源的利用率并进步了零碎 的解决能力。然而,多个过程的并发执行也带来了新的问题——死锁。所谓死锁是指多个过程因竞争资源而造成的一种僵局(相互期待),若无外力作用,这些过程都将无奈向前推动。