-
面向对象的三大特性
-
封装
-
定义:将数据和对该数据进行合法操作的函数封装在一起作为一个类的定义,即用类进行数据抽象。
-
继承
-
定义:用类派生从一个类继承另一个类,派生类继承基类的成员。
-
访问控制与继承
访问方式 |
private |
protected |
public |
同一个类和友元类 |
可以 |
可以 |
可以 |
派生类 |
不可以 |
可以 |
可以 |
外部类 |
不可以 |
不可以 |
可以 |
继承方式 | private继承 | protected继承 | public继承 |
基类的private成员 | 不可见 | 不可见 | 不可见 |
基类的protected成员 | 变为private成员 | 仍为protected成员 | 仍为protected成员 |
基类的public成员 | 变为private成员 | 变为protected成员 | 仍为public成员 |
-
构造函数与析构函数的调用:
- 构造派生类对象:a.调用派生类的构造函数;b.在派生类构造函数初始化列表处调用基类的构造函数;c.完成基类构造函数;d.返回并完成派生类构造函数剩下部分
- 析构派生类对象:a.调用派生类的析构函数;b.执行派生类析构函数的函数体; c.调用基类的析构函数;d.执行基类析构函数函数体后返回
-
派生类的默认成员函数
-
在继承关系里,派生类中如果没有显示定义这六个成员函数,编译系统则会默认合成六个成员函数,即构造函数、拷贝构造函数、析构函数、赋值操作符重载、取地址操作符重载、const修饰的取地址操作符重载。
-
转换与继承
- 派生类对象可以赋值或初始化基类对象。(public)
- 基类对象不可以赋值给派生类对象。(public)
- 基类对象的指针和引用可以指向派生类对象。(public)
- 派生类对象的指针和引用不能指向基类对象,但是可以通过强制转化完成。(public)
-
继承与静态成员
- 基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。(所有类对象共享一个static类成员,static类对象必须要在类外进行初始化。)
- static成员遵循常规访问控制与继承
-
继承与友元关系
- 友元关系不能继承。基类的友元对派生类的成员没有特殊的访问权限。
-
继承情况下的类作用域
- 在继承体系中基类和派生类都有独立的作用域。当子类和父类中有同名成员,子类成员将屏蔽父类成员的直接访问。
-
多态
-
定义:编译或运行时决定使用基类中定义的函数还是使用派生类中定义的函数。“一个接口,多种方法”
-
静态多态与动态多态
-
区别:在什么时候将函数实现和函数调用关联起来,是在编译时期还是运行时期。
-
静态多态是指在编译期间就可以确定函数的调用地址,并生产代码,这就是静态的,也就是说地址是早早绑定的,静态多态也往往被叫做静态联编。
-
静态多态往往通过函数重载来实现,调用速度快,效率高但缺乏灵活性。
-
动态多态则是指函数调用的地址不能在编译器期间确定,必须需要在运行时才确定,这就属于晚绑定,动态多态也往往被叫做动态联编。
-
动态多态通过虚函数来实现,虚函数允许派生类重新定义成员函数,而派生类重新定义基类的做法称为覆盖或称为重写。
-
重载(overload)与覆盖(重写override)
-
重载要求函数名相同,但是参数列表必须不同,返回值可以相同也可以不同。
-
覆盖要求函数名、参数列表、返回值必须相同。
-
在类中重载是同一个类中不同成员函数之间的关系。
-
在类中覆盖则是子类和基类之间不同成员函数之间的关系。
-
重载函数的调用是根据参数列表来决定调用哪一个函数。
-
覆盖函数的调用是根据对象类型的不同决定调用哪一个。
-
在类中对成员函数重载是不能够实现多态。
-
在子类中对基类虚函数的覆盖可以实现多态。
-
虚函数
-
定义:虚函数是在基类中使用关键字virtual声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
-
纯虚函数
-
定义:纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。纯虚函数在声明虚函数时被“初始化”为0的函数。在成员函数的形参后面写上=0,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象
-
虚函数表
-
定义:C++ 中的虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的,简称为V-Table。虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可。同一个类的所有对象都使用同一个虚表。
-
虚表指针
-
定义:为了指定对象的虚表,对象内部包含一个虚表的指针,来指向自己所使用的虚表。为了让每个包含虚表的类的对象都拥有一个虚表指针,编译器在类中添加了一个指针,*__vptr,用来指向虚表。这样,当类的对象在创建时便拥有了这个指针,且这个指针的值会自动被设置为指向类的虚表。
-
虚函数与构造函数(构造函数为什么一般不定义为虚函数)
-
若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数来构造函数了
-
虚函数可以调用在派生类中的函数,如果构造函数定义为虚函数,那么构造函数可能操作没有被初始化的成员,导致出错。(虚函数只需要”部分“信息就可以自动调用,特别地,这种机制允许我们在只知道接口,不知道具体对象的类型的情况下调用函数。而实例化一个对象需要完整的信息,特别地,必须知道实例化对象的确切类型。)
-
如果构造函数使用虚机制,他将只产生通过它自己的V-Table的调用,而不是最后派生的VTABLE。
-
虚函数与析构函数(析构函数为什么一般定义为虚函数)
-
对象已经被构造,它的*__vptr已经被初始化了,所以可以发生虚函数调用。
-
析构函数不设为虚函数,可能只调用基类的析构函数,而没有调用派生类的析构函数,容易造成内存泄露。
-
纯虚析构函数
-
虚析构函数是为了让通过基类指针或引用可以正确释放派生类对象。有时候如果想让基类成为一个抽象类,也就是不能被实例化,可以为类引入一个纯虚函数。但如果手上没有任何pure virtual函数时,该怎么办?由于抽象类总是会被作为基类用于派生的,而基类就该有一个虚的析构函数,并且由纯虚函数可以导致抽象类。所以常常把基类的析构函数声明为纯虚析构函数。又由于所有对象析构时,最后都会调用其基类的析构函数,所以基类的析构函数必须有定义。纯虚析构函数也不例外。
本系列文章目的为个人准备面试的简单总结,文中多有不足,敬请批评指正!
参考:
https://blog.csdn.net/m0_38121874/article/details/76147303
http://www.runoob.com/cplusplus/cpp-inheritance.html
https://blog.csdn.net/chenkaixin_1024/article/details/59489817
https://blog.csdn.net/xy913741894/article/details/52939323
https://blog.csdn.net/eagle_1036077338/article/details/53186171
https://blog.csdn.net/tennysonsky/article/details/49252679