C++
析构函数为什么最好是虚函数
- 虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
虚析构函数使用
class Shape
{
public:
Shape(); // 构造函数不能是虚函数
virtual double calcArea();
virtual ~Shape(); // 虚析构函数
};
class Circle : public Shape // 圆形类
{
public:
virtual double calcArea();
...
};
int main()
{
Shape * shape1 = new Circle(4.0);
shape1->calcArea();
delete shape1; // 因为Shape有虚析构函数,所以delete释放内存时,先调用子类析构函数,再调用基类析构函数,防止内存泄漏。
shape1 = NULL;
return 0;
}
子类和父类执行构造函数和析构函数的顺序
- 构造函数: 父类的先执行,子类的后执行
析构函数: 父类的后执行,子类的先执行
附加
默认构造函数(无参构造函数),有参构造函数,拷贝构造函数,级别依次递增。当我们提供了后面的,前面的编译器就不会提供了。
类的内存分布
new[]/delete[]与new/delete之间的差别,以及具体实现
指针和引用的区别
引用必须被初始化,指针不必。
引用初始化以后不能被改变,指针可以改变所指的对象。
不存在指向空值的引用,但是存在指向空值的指针。
多态的意义
不同的对象调用同一个函数,表现出不同的状态,称为多态。
一是增加程序的灵活性
不同继承自父类的子类,对同一函数可以有不同的表现
二是增加程序的可扩展性
新增子类,只需修改需要重写的函数
new/delete和malloc/free的区别
#define的底层实现
const int p和int const p的区别
对于const和指针结合的变量,要从右往左看
int* const p 是常量指针,指的是p本身是个常量,p的值不能边,但p指向的内容可以改变,这是个顶层cosnt
const int *p 是指针常量,指的是指向常量的指针,p的值能改变,p指向的内容不能改变,这个个底层const
c++的编译过程
- 预处理、编译、汇编、链接
python为什么效率低
- 动态语言:运行时可以改变程序结构
- 解释运行:先转换为字节码再解释字节码
- 一切都是对象:需要维护引用计数
- 垃圾回收:自动垃圾回收中断当前程序
定义了两个vector,vector里面存的是对象,是否可以直接使用memcpy去复制vector
- 不能 不是POD类型 ,memcpy只能进行浅拷贝
构造函数和析构函数中调用虚函数
构造函数不能定义为虚函数,析构函数一般需要定义为虚函数
调试的时候打断点的原理
虚函数的实现原理
vector,list区别和相关内容
emplace_back和push_back有什么区别
C++姿势点: push_back和emplace_back
C++编译过程
预处理->编译->汇编->链接
预处理
预处理主要是处理各种宏展开;删除注释;保留编译器用到的编译器指令等。
添加行号和文件标识符,为编译器产生调试信息提供便利;
编译
编译是在预处理文件基础上经过一系列词法分析、语法分析及优化后生成汇编代码。
汇编
汇编是将汇编代码转化为机器可以执行的指令。
链接
将汇编生成的目标文件链接生成可执行文件
动态链接和静态链接
库:成熟的可复用代码
静态库(.a,.lib)动态库(.so,.dll)
静态库
在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
特点:
静态库对函数库的链接是放在编译时期完成的。
程序在运行时与函数库再无瓜葛,移植方便。
浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件。
动态库
目的:为解决静态库浪费空间;对程序的更新、部署和发布会带来麻烦。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
特点:
动态库把对一些库函数的链接载入推迟到程序运行的时期。
可以实现进程之间的资源共享。(因此动态库也称为共享库)
将一些程序升级变得简单。
甚至可以真正做到链接载入完全由程序员在程序代码中控制(显示调用)。
字节对齐
内存分配方式
静态存储区域
- 内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
栈
- 在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。效率很高,但是分配的内存容量有限。
堆
- 亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。
栈内存溢出
对每个程序来说,栈能使用的内存是有限的,一般是 1M~8M,这在编译时就已经决定了,程序运行期间不能再改变。过多的递归就会导致栈溢出
栈溢出的危害
缓冲区溢出攻击,原因程序没有对作为缓冲区的数组进行越界检查
重写和重载
- 重写是子类继承父类,可以对父类中声明为虚函数的函数进行重新定义,屏蔽父类的定义。
- 重载是指c++可以声明函数名相同,函数签名不同的函数,可以通过传入形参的不同来调用不同的函数
虚表指针是存在类里面还是对象里面
- 对象
接口和抽象类
- 接口是一个概念。它在C++中用抽象类来实现
接口类应该是只提供方法声明,而自身不提供方法定义的抽象类。接口类自身不能实例化,接口类的方法定义/实现只能由接口类的子类来完成。
而对于C++,其接口类一般具有以下特征:
最好不要有成员变量,但可以有静态常量(static const或enum)
要有纯虚接口方法
要有虚析构函数,并提供默认实现
不要声明构造函数
参数为什么要从右往做压榨
进一步发现,Pascal语言不支持可变长参数,而C语言支持这种特色,正是这个原因使得C语言函数参数入栈顺序为从右至左。具体原因为:C方式参数入栈顺序(从右至左)的好处就是可以动态变化参数个数。通过栈堆分析可知,自左向右的入栈方式,最前面的参数被压在栈底。除非知道参数个数,否则是无法通过栈指针的相对位移求得最左边的参数。这样就变成了左边参数的个数不确定,正好和动态参数个数的方向相反。
因此,C语言函数参数采用自右向左的入栈顺序,主要原因是为了支持可变长参数形式。换句话说,如果不支持这个特色,C语言完全和Pascal一样,采用自左向右的参数入栈方式。
lambda表达式,引用捕获
- lambda表达式表示一个可调用的代码单元,类似于一个匿名的内联函数
值捕获
C++的GC机制
- csapp
- C++垃圾回收机制
- 而C++ 0x则提供了基于引用计数算法的智能指针进行内存管理
array,vector,数组
- 共同点
- (1.)都和数组相似,都可以使用标准数组的表示方法来访问每个元素(array和vector都对下标运算符[ ]进行了重载)
- (2.)三者的存储都是连续的,可以进行随机访问
- 不同点
- (0.)数组是不安全的,array和vector是比较安全的(有效的避免越界等问题)
- (1.)array对象和数组存储在相同的内存区域(栈)中,vector对象存储在自由存储区(堆)
- (2.)array可以将一个对象赋值给另一个array对象,但是数组不行
- (3.)vector属于变长的容器,即可以根据数据的插入和删除重新构造容器容量;但是array和数组属于定长容器
- (4.)vector和array提供了更好的数据访问机制,即可以使用front()和back()以及at()(at()可以避免a[-1]访问越界的问题)访问方式,使得访问更加安全。而数组只能通过下标访问,在写程序中很容易出现越界的错误
- (5.)vector和array提供了更好的遍历机制,即有正向迭代器和反向迭代器
- (6.)vector和array提供了size()和Empty(),而数组只能通过sizeof()/strlen()以及遍历计数来获取大小和是否为空
- (7.)vector和array提供了两个容器对象的内容交换,即swap()的机制,而数组对于交换只能通过遍历的方式逐个交换元素
- (8 .)array提供了初始化所有成员的方法fill()
- (9.)由于vector的动态内存变化的机制,在插入和删除时,需要考虑迭代的是否有效问题
- (10.)vector和array在声明变量后,在声明周期完成后,会自动地释放其所占用的内存。对于数组如果用new[ ]/malloc申请的空间,必须用对应的delete[ ]和free来释放内存