/* *
* 一. 基类的定义
*
* 派生类类可以继承基类的成员, 在遇到与类型相关的操作时, 则需要对其进行重新定义, 也就是覆盖.
*
* 在C++中, 基类必须将其两种成员函数区分开来.
*
* 普通函数, 派生类直接继承而不改变的函数.
*
* 虚函数, 希望其派生类进行覆盖的函数, 当使用指针或引用调用虚函数时, 该调用将被动态绑定,
*
* 可能执行基类的版本, 也可能执行某个派生类的版本.
*
* 1. 基类通过在成员函数前添加virtual使得该函数执行动态绑定.
*
* 2. 虚函数可以是任何除了构造函数外的非静态函数.
*
* 3. virtual只能添加在类内函数声明前, 而不能在类外函数定义前.
*
* 4. 若基类把一个函数声明成虚函数, 则该函数在派生类中隐式的也是虚函数.
*
* 5. 成员函数如果没被声明为虚函数, 则其解析发生在编译时, 而非运行时.
*
* 二. 定义派生类
*
* 如果一个派生类是公有的:
*
* 1. 则基类的公有成员也是派生类接口的组成部分
*
* 2. 可以将其对象绑定到基类的引用或指针上
*
* 大多数类都只继承自一个类, 这种形式的继承被称作"单继承".
*
* 对于虚函数
*
* 1. 派生类经常覆盖它继承的虚函数(也可以不覆盖).
*
* 2. 若派生类没有覆盖基类中的某个虚函数, 则该虚函数的行为与普通函数类似,
*
* 会被派生类直接继承.
*
* 3. 派生类可以在其覆盖的函数前使用virtual关键字, 但不是必须. C++11允许
*
* 我们在覆盖的函数后加override来显式注明该成员函数是用来覆盖基类中对应虚函数的.
*
* 一个派生类对象包含多个组成部分, 一个含有派生类自己定义的(非静态)成员的子对象, 以及
*
* 一个与派生类继承的基类对应的子对象, 如果有多个基类, 这样的子对象也有多个.
*
* 因为在派生类对象中含有基类对应的组成部分, 所以我们可以把派生类对象当作基类来使用,
*
* 而且我们也能将基类的指针或引用绑定到派生类对象中的基类部分上, 这种转换称为派生类到
*
* 基类的转换, 编译器会隐式的执行官派生类到基类的转换.
*
*
* 每个类负责定义各自的接口, 要想与类的对象交互必须使用该类的接口, 即使这个对象是派生类的
*
* 基类部分也是如此.
*
* 三. 类型转换与继承
*
* 当我们使用存在继承关系的类型时, 必须将一个变量或其他表达式的静态类型与该表达式表示对象的
*
* 动态类型区分开来.
*
* 静态类型, 在编译时总是已知的, 它是变量声明时的类型或表达式生成的类型.
*
* 动态类型, 变量或表达式表示的内存中的对象的类型, 直到运行时才可知.
*
* 如果表达式既不是引用也不是指针, 则它的动态类型永远与静态类型一致.
*
* 基类的引用或指针的静态类型可能与它的动态类型不一致.
*
* 不存在从基类到派生类的转换, 若允许, 则可能访问到基类中不存在的成员.
*
* */
class Base
{
private:
/* *
* private下的成员不可以被派生类访问
* */
int pr;
public:
/* *
* 基类通常需要定义一个虚析构函数, 这样在对由基类指针指向的动态分配的派生类对象执行
*
* delete操作时, 就会调用对应的派生类的析构函数.
* */
virtual ~Base() = default;
protected:
/* *
* protected下的成员可以被派生类访问, 而无法被外部访问
* */
int data;
/* *
* 无论派生多少个派生类, 该静态成员都只存在一份.
* */
static int static_data;
};
/* *
* 派生类必须通过类派生列表来明确指出他是从哪个基类派生而来的.
*
* 类派生列表的形式是一个':'后紧跟由逗号分割的访问说明符和基类名
*
* 被用作基类的类, 必须已经定义. 派生类不能派生自身.
*
* */
class Derived : public Base
{
/* *
* 派生类无法直接初始化从基类继承而来的成员, 必须使用基类的构造函数
*
* 来进行初始化.
*
* 初始化过程中, 会首先初始化基类部分, 然后按声明的顺序依次初始化派生类的成员.
* */
Derived() : Base() {}
};
/* *
* 派生类的声明和普通类的声明一样, 不需要加类派生列表.
*
* */
class Derived;
/* *
* Base是Derived的直接基类, 是Derived1的间接基类.
*
* 直接基类出现在派生列表中, 而间接基类由派生类通过其直接
*
* 基类继承而来.
*
* 在类名后加final关键字, 可以防止继承发生.
* */
class Derived1 final : public Derived
{
};
int main()
{
/* *
* 通常要想把引用或指针绑定到一个对象上, 则引用或指针的类型应与对象的类型一致, 或者
*
* 对象的类型含有一个可接受的const类型转换规则.
*
* 存在继承关系的类是一个例外, 我们可以将基类的指针或引用绑定到派生类的对象上.
*
* 允许将基类的指针或引用绑定到派生类的对象上, 表示当使用基类指针时, 我们并不清楚
*
* 该指针或引用所绑定对象的真实类型. 该对象可能是基类的对象, 也可能是派生类的对象.
*
* 智能指针也支持派生类向基类的转换.
*
* */
Derived derived;
Base *basePtr = &derived;
Base &baseRef = derived;
/* *
* Derived *derivedPtr = basePtr;
*
* 即使基类指针绑定的是一个派生类对象, 也不能执行基类向派生类的转换,
*
* 因为编译器只能通过检查指针或引用的静态类型来判断该转换是否合法.
*
* 若基类中含有一个虚函数, 我们可以用dynamic_cast在运行时进行转换.
*
* 或者我们确认该转换是安全的, 可以用static_cast进行强制转换.
*
* */
Dervied *derivedPtr = dynamic_cast<Dervied *>(basePtr);
derviedPtr = static_cast<Dervied *>(basePtr);
/* *
* 对象之间不存在类型转换, 如果用一个派生类对象赋值, 移动或初始化给一个基类
*
* 对象, 那么基类对象只会拷贝派生类中的基类部分, 其余的部分就
*
* 被切掉了.
*
* */
Base base = dervied;
return 0;
}