1.数组名和指针的区别?
明确一点数组名不是指针,可以通过sizeof来验证。其本质是指代数组这样一个数据结构。
数组名在表现很像const指针指针,不可以被改变(被赋值,++,--),可以用*来取对象,作为指针参数,可以+整数
做为形参的数组名失去了其数据结构的本质内涵,其本质是一个指针。
2.引用类型和指针的区别
从反汇编的角度看,引用的本质是一个由编译器管理的指针。其表现出来的语法特征和其所引用的对象一致。
作为函数参数时,和指针一样,真正传递的是指针,而非对象的拷贝,较少了堆栈空间的使用,而且可以在函数体内可以对所指的对象进行修改。
引用不可以被修改,以失去了指针的灵活性为代价,提高了程序安全性。
3.const总结
const的作用是希望通过对指定const属性,保证变量不被错误修改,提高代码的安全性。
修饰变量:变成了常量,不可以被修改,其他操作不受影响。编译器在编译时将用到该变量的地方替换成对应的值。与#define的替换相比,const保留了变量的类型属性,可以做类型检验。
修饰指针:作用在指针本身,则该指针不能被修改(const指针),所以必须初始化;作用在其指向的对象上,则不能通过该指针修改其指向的对象;绑定常量的引用必须是指向常量的引用,指向常量的引用绑定的未必是常量。
修饰引用:不能够通过该引用修改其绑定的对象。绑定常量的引用必须是指向常量的引用,指向常量的引用绑定的未必是常量。
修饰函数:修饰隐式的this指针,将this指针设置为指向常量的指针,所以不能在函数体内修改数据成员。
4.new/delete和malloc/free的区别
new/delete最终调用malloc/free来申请和释放内存。
c++封装了new和delete完成为对象分配内存,调用构造函数,调用析构函数,释放内存工作。
使用上比malloc和free更加方便,不用指定大小;引入异常bad_alloc。
new和delete通过调用operator new和operator delete来分配和释放内存, 而这两个函数可以重载,为程序员自己实现内存管理提供了方便。
5 struct和class的区别
在功能上基本没有什么区别,class能做到事情struct基本能做。struct可以有成员函数,可以继承,可以有虚函数(实现多态)。
区别在于默认的访问控制:
6.const和define的区别
处理阶段:const在编译运行阶段使用,define在编译预处理阶段展开
存储方式:const需要分配内存来存储常量,而define不用分配内存
安全性:编译器会对const做类型检查更加安全。
作用域:默认情况下const的作用范围是文件内部,多个文件共享需要使用extend ,#define的作用范围是全局,多个文件定义define同名会报错。
7.const和static的区别
内存区域:
8.const和static在类中使用的注意事项
static成员不属于任何对象,而是被他们共享。
静态数据成员应该在类的外部定义。
因为没有this指针,static成员函数不能用const修饰,也不能访问非静态成员。
9.c++内存管理
10.内联函数
将函数指定为内联函数(inline),调用位置上内联展开,避免函数调用的开销(堆栈维护,调整指令)。应用在规模小,流程直接,频繁调用的函数。
将代码放在符号表里,在调用点上展开。
内联说明只是向编译器发出一个请求,编译器可以忽略这个请求。编译器优化时,也可能将没有inline说明的函数展开成内联函数。
11.多态和虚函数
C++通过虚函数实现动态绑定,也就是多态。
动态绑定是根据对象本身,而不是指针或引用的类型来确定具体调用的函数。具体实现是在对象中维护一个虚函数表指针,同个虚函数表中存储了对象调用的函数的指针。
虚函数通过虚函数指针和虚函数表来实现动态绑定。
编译器编译时,为每个有虚函数的类生成一个虚函数表(一个一维数组),用来存储该类的虚函数地址。
编译器为每个对象提供一个指向虚表的指针。(在32位机器的内存上看,是其前4个字节,这也是为什么有虚函数的类型的sizeof多4个字节的原因)。
指向虚表的指针的初始化在构造函数中进行。
虚函数的位置,按照先后顺序排列。
子类复制一份父类的虚函数表,替换重写的虚函数地址,新加的虚函数依此放在表尾。
12.C++中的重载和重写的区别:
重载:名称相同参数不同。对操作相识的函数,用相同的名字,方便程序员的理解,减轻起名字,记名字的负担。
重写:同名同参数,虚函数,子类将父类覆盖掉(在虚函数表中覆盖掉),即使通过强制类型转化也不能访问(对指针和引用而言)。
重定义:同名(参数可同可不同),基类中的函数被隐藏,子类对象无法直接访问。
13.面向对象的三个特征
继承,多态,封装。
继承:本质是代码复制。
多态:多态和虚函数
封装:将数据和和对操作封装到放在一起。
14.析构函数一般写成虚函数的原因
delete父类指针时,可以调用对像真正的析构函数。否则可能会出错,如子类中有指针类型,并且申请了内存,这时就会造成内存泄漏。
15.构造函数为什么一般不定义为虚函数
析构函数通过虚表来调用的,而指向虚表的指针是在构造函数中初始化的,这是个矛盾。
16.构造函数或者析构函数中调用虚函数会怎样
结果上来看和调用普通函数无异。即基类调用基类的虚函数,派生类调用派生类的虚函数。
17.纯虚函数
18.引用是否能实现动态绑定,为什么引用可以实现
引用和指针的静态类型和动态类型可以不一样。
静态类型:变量声明时的类型或表达式生成的类型。编译时已经知道。
动态类型:变量或表达式表示的内存的对象的类型。
19.深拷贝和浅拷贝的区别(举例说明深拷贝的安全性)
在有指针成员的情况下,浅拷贝只是将指针指向已存在的内存。即两个对象的指针成员指向的是同一内存区域。
深拷贝的做法是申请一个内存复制一份,并将新对象指针指向备份区。
安全性:浅拷贝如果修改了指针指向的内容,将对两个对象都有影响。
20.对象复用的了解,零拷贝的了解
对象池:对象池通过对象复用的方式来避免重复创建对象,它会事先创建一定数量的对象放到池中,当用户需要创建对象的时候,直接从对象池中获取即可,用完对象之后再放回到对象池中,以便复用。
适用性:类的实例可重用。类的实例化过程开销较大。类的实例化的频率较高。
零拷贝:emplace_back
21.什么情况下会调用拷贝构造函数
(1)用类的一个对象去初始化另一个对象时
(2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用
(3)当函数的返回值是类的对象或引用时
22.结构体内存对齐方式和为什么要进行内存对齐?
23.内存泄露的定义,如何检测与避免?
申请的堆没有被释放,该内存不能在被利用。
24.手写实现智能指针类
25.调试程序的方法
26.C++的四种强制转换(C++不是类型安全的)
static_cast , dynamic_cast , const_cast , reinterpret_cast
static_cast:可以实现C++中内置基本数据类型之间的相互转换。
const_cast: 操作不能在不同的种类间转换。相反,它仅仅把一个它作用的表达式转换成常量。它可以使一个本来不是const类型的数据转换成const类型的,或者把const属性去掉。
reinterpret_cast:有着和C风格的强制转换同样的能力。它可以转化任何内置的数据类型为其他任何的数据类型,也可以转化任何指针类型为其他的类型。它甚至可以转化内置的数据类型为指针,无须考虑类型安全或者常量的情形。不到万不得已绝对不用。
dynamic_cast:1)运行时处理,类型检查;2)不能用于内置的基本数据类型的强制转换;3)转换如果成功的话返回的是指向类的指针或引用,转换失败的话则会返回NULL(指针类型,如果是应用类型,抛出bad_cast异常);4)使用dynamic_cast进行转换的,基类中一定要有虚函数,否则编译不通过;5)
27.遇到coredump要怎么调试
28.调试程序的方法
29.模板的用法与适用场景
模板是C++泛型编程的基础。
使用:template //关键字+<模板参数列表>
非类型模板参数:
应用场景:除了参数类型不一样外,其他的内容全部一样(函数体),用模板,而不是每一个类型都写一个函数。
特化:模板的一个独立的定义,其中一个或多个参数被指定为特定的类型。通用模板不能适应所有情况。(特殊情况特殊处理)
30.成员初始化列表的概念,为什么用成员初始化列表会快一些
概念:以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式,对成员进行初始化。
效率:构造函数之前执行默认初始化。而成员初始化列表,可以立即初始化,所以更快些。
成员是const对象和引用必须初始化。
31.虚继承
为了避免一个类多次集成一个基类而包含多份基类的拷贝,使用虚继承机制。(基于多重继承)
32.函数调用过程(堆栈图)