C++面试常见问题总结

1、new和malloc的区别
1)分配方式:new根据数据类型分配内存,会调用构造函数,malloc根据数据大小分配内存;
2)类型:new是操作符,可重载,而malloc是库函数;
3)返回类型:new返回指定对象的指针,malloc返回的是void*,所以一般需要进行内存转换;
4)销毁方式:new分配的内存用delete销毁,delee会调用析构函数;malloc要用free销毁;
5)分配失败时:new会抛出bad_alloc的异常,可采用nothrow的方法禁止抛出异常而返回NULL,而malloc分配失败会返回NULL;

2、虚拟地址到物理地址的映射
内存管理单元MMU通过APR(active page register)寄存器将虚拟地址转换为物理地址,内核通过向与执行进程相对应的、供用户进程使用的APR设定适当的值,保证各用户拥有独立的虚拟空间,APR共有8组,由0到7,进程的虚拟地址空间以页或者段为单位进行管理,一组APR对应一页,每一页最多可以分配128个块(8kb);虚拟地址的高位3比特决定了对应的页,APR的11-0位决定了物理地址基地址的块嫡长子,加上虚拟地址的12-6比特得到物理内存的块地址,再加上作为块内偏移值的虚拟地址的5-0位,就得到了最后的地址。

3、STL内存分配之配置器
第一级配置器:allocate()直接使用malloc(),deallocate()直接使用free()
第一级配置器以malloc(),free(),realloc()等c函数执行实际的内存配置、释放、重配置操作
第二级配置器:主要有内存池和自由链表构成,维护16个自由链表,负责16种小型区块的次配置能力,内存池(memory pool)以malloc()配置而得,如果内存不足,转调用第一级配置器,如果需求区块大于128byte,就转调用第一级配置器

二级空间配置器使用内存池+自由链表的形式避免了小块内存带来的碎片化,提高了分配的效率和利用率。先判断要开辟的空间是否大于128byte,如果大于则认为是大块内存,直接调用一级配空间配置器分配,否则通过内存池分配,假设要分配8个字节大小的空间,它就去内存池中分配多个8字节大小的内存块,将多余的挂在自由链表上,下一次在需要8个字节时就去自由链表上取就可以了,如果回收这8个字节的话,直接将它挂在自由链表上就可以了;为了便于管理,二级空间配置器在分配的时候都是以8的倍数对齐,尽管这样做有内碎片的问题,但是便于管理。分配的内存用完后用deallocate函数将对应的内存块归还给不同的地方;

小块内存:频繁分配释放小块内存容易在堆中造成外碎片,开辟空间时分配器需要去寻找空闲块给用户,碎片太多会导致性能低下,为了解决这个问题,提出了内存池的概念,基本思想是一次向heap申请一块很大的内存(内存池),如果需要申请小块内存的话直接在内存池中申请

3、TCP中的time_wait状态
time_wait状态是四次挥手中服务器向客户端发送FIN终止连接后进入的状态,当处于time_wait状态时,我们无法创建新的连接,因为端口被占用

存在的原因:1)可靠的终止TCP连接,若处于time_wait的客户端发送给服务器确认报文段丢失的话,服务器将在此重新发送FIN报文段,那么客户端必须处于一个可接受的状态就是time_wait而不是close状态
2)保证迟来的TCP报文段有足够的时间被识别并丢弃,linux中一个TCP端口不能打开两次或两次以上,当客户端处于time_wait状态时我们将无法使用此端口建立新连接,如果不存在time_wait状态,新连接可能会收到旧连接的数据

哪一方会有time_wait状态
一般是客户端,而且会占用端口,有时产生在服务器端,因为服务器主动断开连接或者发生异常

4、extern关键字详解
extern可以置于函数或者变量前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义,也可用来进行链接指定
与C一起联用时,则告诉编译器在编译这个函数名时按照C的规则去翻译相应的函数名而不是C++

.c文件中声明了一个全局变量,这个变量如果要被引用,就放在.h中并用extern来声明

函数声明中带有extern表示这个函数可能在别的源文件里定义

5、static关键字详解
1)修饰局部变量。使得被修饰的变量成为静态变量,存储在静态区,存储在静态区的数据生命周期与程序相同,在main函数之前初始化,在程序退出时销毁(无论是局部静态还是全局静态)
2)修饰全局变量。全局变量本来就在静态区,因此不改变存储位置,但是static限制了其链接属性,被static修饰的全局变量只能包含该定义的文件访问
3)修饰函数。使得函数只能在包含该函数定义的文件中被调用
4)在C++中,对于静态成员变量和静态成员函数,所有的对象都只维持同一个实例,因此,采用static可以实现不同对象之间数据共享

6、volatile
volatile声明的类型变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其他线程等,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问,int volatile a;当要求使用volatile声明的变量的值时,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据,而且读取的数据立刻被保存

1)中断服务程序中修改的供其他程序检测的变量需要加volatile;
2)多任务环境下各任务间共享的标志应该加volatile;
3)存储器映射的硬件寄存器通常也要加volatile说明,因为每次对它的读写都可能有不同意义

多线程下的volatile
当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值

7、C++多态性
一个接口,多种方法;程序在运行时才决定调用的函数
C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖或重写,而重载则是允许有多个同名的函数,这些函数的参数列表不同,允许参数个数不同或者参数类型不同或者都不同
封装可以使得代码模块化,继承可以扩展已存在的代码,都是为了代码重用,而多态则是为了接口重用,不论传递过来的是哪个类的对象,函数都能够通过同一接口调用到适应各自对象的实现方法
最常见的用法是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。

虚函数的作用:
允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数

虚函数的使用方法:
1)在基类中用virtual声明成员函数为虚函数,在类外定义虚函数时,不必再加virtual;
2)在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并且根据派生类的需要重新定义函数体
3)定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象
4)通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数

你可能感兴趣的:(面试,C++,面试)