1.面向对象的三大特性(核心都是为了代码重用)
- 封装:把客观事物抽象成类,并且将属性和操作属性的方法放在一起。
- 继承:是指可以让某个类型的对象获得另一个类型的对象的属性的方法。它支持按级分类的概念。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。 通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”。继承的过程,就是从一般到特殊的过程。要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。继承概念的实现方式有二类:实现继承与接口继承。实现继承是指直接使用基类的属性和方法而无需额外编码的能力;接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力。
- 多态:所谓多态就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。
实现方法?编译器怎么支持?
2.类的访问权限
- public
- protected:可以被类中的函数,子类的函数,友元函数访问,但不可以由该类的对象访问
- private:可以被类中的函数,友元函数访问,但不可以由子类或者类的对象直接访问
class 的默认访问级别是private,struct的默认访问级别是public
3.类的构造函数、析构函数、赋值函数、拷贝构造函数、移动构造函数
- 构造函数:一种特殊的类成员函数,无返回值,函数名与类 同名。它提供了对成员变量进行初始化的方法(函数体内赋值或者使用初始化列表),使得在声明对象时能自动的初始化对象。
- 析构函数:一种特殊的类成员函数,无返回值,无参数,不能重载,用来在系统释放对象前做一些清理工作。
- 拷贝构造函数:特殊的构造函数,用基于同一类的一个对象构造和初始化另一个对象。(适合如下场景:一个对象以值传递的方式传入函数体、一个对象以值传递的方式从函数中返回,一个对象需要通过另一个对象进行初始化)
- 赋值函数:重载了赋值运算的类成员函数
- 移动构造函数:特殊的构造函数。考虑如下场景,当用对象a初始化对象b后,a不再使用了。如果b直接使用a的空间,就避免了新的空间的分配,大大降低了构造的成本。具体的讲,移动构造函数首先接管参数的内存地址空间,然后将其内部所有指针设置为nullptr,并且在原地址上进行新对象的构造,最后调用原对象的析构函数。(因为移动构造函数是对传递的参数进行浅拷贝,也就是说如果参数为指针变量,进行拷贝之后将会有两个指针指向同一地址空间,这个时候如果前一个指针对象进行了析构,则后一个指针将会变成野指针,从而引发错误)
注意!
- 如果成员是const、引用,或者属于某种未提供默认构造函数的类类型,则必须通过构造函数的初始化列表为这些成员提供初始值。
- 拷贝构造函数首先是构造函数,体现了初始化作用。而赋值函数体现在赋值,即被赋值的对象肯定是已经存在了的。
- 默认的拷贝构造函数(编译器帮你合成的)使用浅拷贝(逐bit复制)
- 移动构造函数的参数是右值引用,而构造函数的参数是左值引用。
#includeusing namespace std; class demo{ public: demo():num(new int(0)){ cout<<"construct!"< cout<<"copy construct!"< d.num = NULL; cout<<"move construct!"< cout<<"class destruct!"< return demo(); } int main(){ demo a = get_demo(); return 0; }
4.深拷贝与浅拷贝的区别
区别在于是否真正获取一个对象的实体。假如被复制对象的某个指针指向了另一个对象,浅拷贝只是增加了一个指针指向另一个对象,而深拷贝不仅增加了一个指针,还会申请一块内存,存放另一个对象的复制体。
5.空类有哪些函数?空类的大小?
编译器合成的默认构造函数,默认析构函数,默认复制构造函数,默认赋值函数。空类的大小视机器而定,一般编译器会为之插入一个char型数据,故大小为1。当空类是另一个类的成员时,为了补齐,编译器会对应填充几个字节进去。
6.内存分区:全局区、堆区、栈区、常量区、代码区
- 栈区:栈区由编译器自动分配释放;存放参数,局部变量,函数调用的返回地址;函数运行结束后自动释放空间,即调整esp和ebp;遵守先进后出原则;由高地址向低地址延伸。
- 堆区:调用malloc函数申请的内存空间,不用时需要调用free函数(无论是程序员调用还是编译器调用)释放,否则会造成内存泄漏;与栈区相反,由低地址向高地址延伸。
- 全局区(静态区):存放静态变量和全局变量
- 常量区:存放字符串,数字,const 修饰的变量等常量;常量区的内容不可更改(设置了只读权限)
- 代码区:存放程序执行代码;字符串常量和 define 定义的常量也有可能存放在代码区。
7.new/delete与malloc/free的区别
相同点,都是从堆上申请一块空间;不同点在于:
- malloc 和 free 是函数,而 new 和 delete 是操作符(编译器可以识别)
- malloc 和 free 申请的空间不会被初始化,而 new 和 delete 会将其初始化再交给调用者
- malloc 申请空间时,需要明确给出申请空间的大小,而 new 只需要给出空间的使用类型
- malloc 的返回值是 v o i d ∗ void * void∗,在使用时需要强转,而 new 返回的是对应类型的指针。
- malloc 申请空间失败时,返回NULL,因此使用时需要判是否为空,而 new 申请失败时会抛出异常
void* malloc(size_t size)
void free(void* p)
8.new干了什么?
- 调用 operator new(用户可以重载) 分配内存
- 调用构造函数生成类对象
- 返回对应的指针
operator new最终还是调用malloc,并且有多种重载形式:
- void* operator(size_t size) throw (bad_alloc); 分配 size 字节的空间,成功则返回指向该空间首字节的指针,失败则抛出异常。用户可重载
- void* operator(size_t size, nothrow_t& nothrow_constant) throw(); 失败返回NULL指针,不抛出异常。用户可重载
- void* operator(size_t size, void* ptr) throw();(placement new) 不分配内存,直接在ptr指向的空间构造对象,之后返回实参指针ptr。用户不可重载
placement new 适合在那些需要高频率申请内存然后释放掉的场景,事先申请一块儿大的空间,然后在上面构造对象,析构对象,避免调用底层的malloc。
- 传入一个void类型的指针,所以即可以在堆上构造对象,也可以在栈上构造对象
- 指针指向的空间在调用发生之前已经申请好了
- 构造的对象不再使用时,需要显示的调用析构函数,以便其它对象的构造。
最后当这块空间不再使用时,调用delete或者其它方式释放掉。
9.内存泄露和内存溢出(OOM)
内存泄漏是指应用程序未能正确释放已经不再使用的内存,导致这块内存在程序运行期间不受应用程序控制,且不能被操作系统回收加以重新利用。多次泄漏的情况下会导致内存溢出。
内存溢出是指应用系统中存在无法回收的内存 或者 使用的内存过多,导致程序运行用到的内存大于操作系统能提供的最大内存,操作系统就会提示内存溢出。
造成内存泄漏的常见情况:
- 指针重新赋值
- 错误的内存释放,甚至没有释放(函数申请了临时空间,没有释放就返回)
- 返回值的不正确处理
检查内存泄漏的方法:
- 检查代码,看new和delete,malloc和free是否匹配
- 检查是否构造和析构的对象数目是否匹配
- 检查类中申请的空间是否完全释放,尤其是在继承的关系下。
- 检查函数返回前是否释放申请的临时空间



