基于日常开发、算法以及面试的需求,对C++进行粉碎性学习,会根据学习的深入重复更新。
1. OOP的基本特征
封装,继承,多态
继承:更多的是描述类之间的关系,一种对象 / 类(派生类)可以继承另外一种对象 / 类 (基类)的属性和方法。进而提升代码的复用,减少代码和数据冗余!
多态性:在面向对象的编程中,多态是指程序能够通过引用或指针的动态类型获取类特定行为的能力(继承、虚函数);函数重载(参数不一致),同一个函数名但不同表达。
2. OOP的核心特性
数据抽象、继承、动态绑定
动态绑定:函数的运行版本由实参类型决定,即在运行时选择函数版本;但是!只有通过引用或者指针调用虚函数时,才会出触发动态绑定,因此只有在这种情况下,对象的动态类型才能和静态类型不同。
3. 基类和派生类
3.1 这两个概念,通常和面向对象编程(OOP)中的继承一起出现,通过继承联系在一起的类构成一种层次关系,通常在层次关系的根部有一个基类,其它直接或间接从基类继承得到的称为派生类。
在c++11中,如果我们不希望某一个类A成为基类,可以在类名后跟一个关键字final
class A final {/* */}
3.2 派生类的成员将隐藏同名的基类成员
4. 虚函数
在使用基类的引用或指针调用一个虚成员函数时会执行动态绑定,因此直到运行时才知道调用了哪个版本的虚函数,所以所有虚函数必须有定义!
等价概念如下:
派生类需要在内部对继承自基类的虚函数进行新的声明和定义(覆盖 overide),当使用指针或者引用调用虚函数时,将被动态绑定!
问题1:构造函数可以时虚函数吗?不可以
知识点1:虚函数表指针赋值时间(在构造对象,执行函数体时)
程序在编译期间,编译器会在构造函数中增加为虚函数表指针*__vptr赋值的代码(编译器行为),当程序在运行时,需要创建对象时!,执行对象的构造函数,执行函数体内的赋值函数!至此*__vptr 赋值成功。
知识点2:虚函数表何时创建
虚函数表是在编译期间创建吗,编译期间编译器就为每个类确定好了对应的虚函数表以及里面的内容。而在程序运行时,编译器执行对象所属类的构造函数并完成虚函数表指针的赋值。
现在回到问题1,如果构造函数是虚函数,那么在执行构造函数之前需要找到“虚函数表”,想要找到虚函数表就需要虚函数表指针赋值,但是根据知识点1可知,虚函数表指针赋值是在执行构造函数过程中,这里形成了死锁。
这里设计到虚函数表和虚函数指针的概念:C++随心记(二)_云从天上来的博客-CSDN博客
问题2: 析构函数可以时虚函数吗?可以,见7
5. 虚函数 final 和 override 关键字
override是在c++11引入的新特征,用于智能判断虚函数的重新定义是否正确;final则是防止其它类覆盖。这里引用primer中的代码
struct B {
virtual void f1(int) const;
virtual void f2();
void f3();
void f5() const final; // 不允许后续的其它类覆盖
}
struct A : B {
void f1(int) override; // 正确,与基类中的f1匹配
void f2(int) override; // 错误,参数不一致
void f3() override; // 只有虚函数可以覆盖,而f3不是
void f4() override; // 没有f4()
}
6. 纯虚函数
纯虚函数无需定义,在类内部 = 0表示即可,这种表达只能出现在类内;当然我们也可以提供具体定义,必须在类的外部!
class B : public A {
public:
int pure_vir_func() const = 0;
}
7. 虚析构函数
常见的问题是:为什么析构函数是虚函数。原因在于delete某一个动态分配的对象的指针时,此时可能出现指针的静态类型和被删除对象的动态类型不符的情况,编译器需要清楚执行的是继承类的析构函数。
基类 A = new 继承类();
8. 抽象基类 : 含有(或者未经覆盖直接继承)纯虚函数的类是抽象基类。
抽象基类负责定义接口,后续的其它类继承该类并覆盖;
抽象基类不能直接创建一个抽象基类的对象!!!!
9. struct和class的区别,唯2的区别
区别1:默认访问说明符,在struct中默认访问权限是public,而class默认是private
区别2:默认派生运算符,struct默认public继承;class默认private继承
class Base {/* */}
struct A : Base {/* */} // 默认public继承
class B : Base {/* */} // 默认private继承
9. 函数调用的解析过程(名字查找优先类型查找)
假设调用a -> func( ),其中A a;首先确定a的静态类型,即A,在A类中查找名为func的函数,如果找到继续判断参数类型和返回类型;如果没有找到,则继续向基类寻找。
需要注意的是,找到对应函数并且通过类型检查(调用合法),如果func是虚函数并且是通过引用和指针调用,则编译器产生的代码将在运行时确定虚函数的版本,即动态绑定;否则将会是一个常规函数的调用。