# 继承 Inheritance

看什么视频,还是文档来得快

一些说法:

  • 派生类 / 子类 / 子类 --("is-a" relationship)-> 基类 / 父类 / 超类。

  • 子类继承父类,父类泛化子类。

  • 派生类继承基类之后还可以添加属性与方法,不可删除基类属性与方法。子类包含父类中大部分成员。

  • 基类的 ctor, dtor, cp ctor, friend 无法被继承。

    • C++11 使用 using A::A; 继承类 A 所有 (cp) ctor,除了派生类中 显式定义过的 同参的 构造函数。
    • 若基类的 ctor 未被显式调用,基类的默认构造函数会被调用。
  • C++11 引入 final 标识符,可以使类不能被继承。

    • 例如: class B final{}; class D : public B {}; 报错。

语法:

Class 派生类名 : (访问修饰符1) 基类1, (访问修饰符2) 基类2 ... { 派生类体 };

实例:

#include <iostream>
using namespace std;
class A
{  
public:
    int d;
    A(int d = 233)
    {
        this -> d = d;
        cout << "ctor_A" << endl;
    }
};
class B : public A
{
public:
    using A::A;
};
class C : public A
{
public:
    int e{123};
    C(int y) : e{y} // 默认先调用 A ()
    {
        cout << "ctor_C" << endl;
    }
};
int main()
{
    B b; //ctor_A
    cout << B{466}.d << endl; //ctor_A 466
    cout << b.d << endl; // 233
    C c{233};//ctor_A ctor_C
    return 0;
}

# 访问属性与派生方式

访问属性:

权限publicprotectedprivate
类内 / 友元truetruetrue
派生类truetruefalse
外部truefalsefalse

继承:

继承方式基类成员派生类成员函数外部
公有继承在派生类访问属性不变不能访问基类的私有成员可以访问派生类公有成员
私有继承派生类中都变成私有不能访问基类的私有成员不可访问派生类继承的成员
保护继承公有变成保护,私有不变不能访问基类的私有成员不可访问派生类继承的成员

# 构造链与析构链

构造函数调用从父亲到儿子,析构函数调用从儿子到父亲。

实例:

#include <iostream>
using namespace std;
class A
{  
public:
    A(){ cout << "ctor_A" << endl; }
    ~A(){ cout << "dtor_A" << endl; }
};
class B : public A
{
public:
    B(){ cout << "ctor_B" << endl; }
    ~B(){ cout << "dtor_B" << endl; }
};
class C : public B
{
public:
    C() { cout << "ctor_C" << endl; }
    ~C() { cout << "dtor_C" << endl; }
};
int main()
{
    C c;
    return 0; // 结束自动销毁 c
}
/*
ctor_A
ctor_B
ctor_C
dtor_C
dtor_B
dtor_A
*/

# 继承中同名相关问题

# 名字隐藏

派生类的函数会隐藏基类的同名函数。理解为作用域小的变量优先。

使用 using ParentName::ParentFunction 引入同名函数重载。

class P {
public:
    void f() {}
};
class C :public P {
public:
    void f(int x) {}
    using P::f; // 不加这句会在下面报错
};
int main() {
    C c;
    c.f(); // 就是这里 qwq
    // 下面两种不用 using 的写法
    c.P::f();
    static_cast<P>(c).f();
    return 0;
}

# 重定义函数

派生类中定义与基类 同名同参的函数,直接替代。

若要访问基类函数,前加 ParentName:: 范围限定符。

#include <iostream>
using namespace std;
class P {
public:
    void f() { cout << "P" << endl;}
};
class C :public P {
public:
    void f() { cout << "C" << endl;}
};
int main() {
    C c;
    c.f(); // C
    c.P::f(); // P
    return 0;
}

# 多态 Polymorphism

肽是 α-氨基酸以肽键连接在一起而形成的化合物,是蛋白质水解的中间产物。由三个或三个以上氨基酸分子组成的肽叫多肽。

不同类型的对象 对同一消息的 不同响应。

  • 重载多态
  • 子类型多态(重定义函数)

实现多态:通过联编 Binding :确定具有多态性的语句调用哪个函数的过程。

  • 静态联编:编译时确定。
    • 通过 派生类对象 / 基类对象指针 访问同名函数。(访问指针类型
  • 动态联编:运行时确定。(C++ 中多态主要是这个)
    • 通过 基类对象的指针或引用 访问同名虚函数。(访问所指类型

实例:

有对象 ABC,若使用 print () 输出信息,需要写三个重载函数。

# 虚函数

virtal 关键字,添加至正常函数之前变成虚函数,传参变成传递指针或引用。

运行时检查 指针所指的对象所引用对象的类型(取决于对象真正的类而不是指针的类型) 决定调用哪个同名虚函数。用途可以使用父类指针访问子类对象成员。

可由继承传递,即基类添加 virtal 后派生类同名函数也自动变成虚函数。

当前类找不到虚函数时会沿继承链一直向父类寻找。

#include <iostream>
using namespace std;
class A
{  
public:
    virtual string toString() { return "A"; } // 不加 virtual 则下方全是 A
};
class B : public A
{
public:
    string toString() { return "B"; } // 写错函数名的话(即找不到虚函数),下方会输出 A
};
class C : public B
{
public:
    string toString() { return "C"; }
};
void print(A *o) // 等价于使用引用的写法
{
    cout << o -> toString() << endl;
}
int main()
{
    A a; B b; C c;
    print(&a); // A
    print(&b); // B
    print(&c); // C
    return 0;
}

# override 和 final

C++11 override 关键字,表示该函数为基类函数的复写,当函数名或参数与基类不同时会报错。

例如类 B 中: string toString() override {...}

override 只能在类内使用。

final 关键字 例如: string toString() {...} final; 阻止继续复写。( final 还可用于阻止继承)

# 抽象类与纯虚函数

沿继承链移动,基类越来越抽象,派生类越来越具体。

类太抽象以至于无法实例化(即无法创建对象)叫抽象类。

例如:在某个基类中声明一个函数(抽象函数 / 纯虚函数),只有在派生类中将其实现 才可以创建派生对象。

暂时不知道有多大用处,实例鸽了

# 动态类型转换

回收:Runoob_C++ 强制转换运算符

dynamic_cast 运算符:沿继承层级向上向下或侧向转换到类的指针和引用。

  • 这里假定 Shape 类 是 Circle 类 的基类,比如现在有个 Shape *p = &s; ,其实它是个 Circle 。

  • 转指针 Circle *c = dynamic_cast<Circle*>(p); 失败返回 nullptr。

  • 转引用 Circle &c = dynamic_cast<Circle&>(s); 失败抛出异常。

向上转换可采用隐式转换,向下转换必须显式执行。派生类可截断,基类不能加长。

内存模型:

<img src = "https://s3.ax1x.com/2021/02/04/y3SxJ0.png" width = "500px">

# typeid 查询所属类

#include <iostream>
#include <typeinfo>
using namespace std;
class A{};
int main()
{
    A a;
    auto& t1 = typeid(a); // 自动推断要加上 & amp;
    cout << t1.name() << endl; // 1A
    return 0;
}
更新于