目录ios
1.概述c++
2.继承git
2.0 基类github
2.1 派生类编程
2.3 虚基类ide
2.4 继承方式函数
3.3 static type和dynamic type区别
struct Base { int a, b, c; }; // 每一个 Derived 类型对象包含 Base 为子对象 struct Derived : Base { int b; }; // 每一个 Derived2 类型对象包含 Derived 与 Base 为子对象 struct Derived2 : Derived { int c; };
基类必须已经定义,才能派生。
基类的成员函数定义
虚析构函数
虽然析构函数是不继承的,若基类声明器其析构函数为 virtual
,则派生的析构函数始终覆写它。
这使得能够经过指向基类的指针 delete 动态分配的多态类型对象。
class Base { public: virtual ~Base() { /* 释放 Base 的资源 */ } }; class Derived : public Base { ~Derived() { /* 释放 Derived 的资源 */ } }; int main() { Base* b = new Derived; delete b; // 进行到 Base::~Base() 的虚函数调用 // 由于它为虚,故它调用 Derived::~Derived() , // 能释放派生类的资源,而后遵循一般析构顺序 // 调用 Base::~Base() }
【重要!】任何基类的析构函数必须为公开且虚public virtual,或protected受保护且非虚 。
若类为多态(声明或继承至少一个虚函数),且其析构函数非虚,会致使资源泄漏。由于派生类的资源未释放。
struct
声明的类默认为 public
,对以类关键 class
声明的类为 private
。容许空的基类子对象大小为零。
为保证同一类型的不一样对象地址始终有别,要求任何对象或成员子对象的大小至少为 1 ,即便该类型是空类类型(即无非静态数据成员的 class 或 struct )。
然而,基类子对象不受这种制约,并且能够彻底从对象布局中被优化掉:
#include <cassert> struct Base {}; // 空类 struct Derived1 : Base { int i; }; int main() { // 任何空类类型的对象大小至少为 1 assert(sizeof(Base) > 0); // 应用空基优化 assert(sizeof(Derived1) == sizeof(int)); }
若空基类之一亦为首个非静态数据成员的类型或其类型的基类,则禁用空基优化,由于要求同类型二个基类子对象在最终派生类的对象表示中拥有不一样地址。
这种状况的典例是 std::reverse_iterator 的朴素实现(派生自空基类 std::iterator ),它保有底层迭代器(亦派生自 std::iterator )为其首个非静态数据成员。
#include <cassert> struct Base {}; // 空类 struct Derived1 : Base { int i; }; struct Derived2 : Base { Base c; // Base ,占用 1 字节,后随为 i 的填充 int i; }; struct Derived3 : Base { Derived1 c; // 从 Base 派生,占用 sizeof(int) 字节 int i; }; int main() { // 不该用空基优化 // 基类占用 1 字节,Base 成员占用 1 字节,后随2个填充字节以知足 int 对齐要求 assert(sizeof(Derived2) == 2*sizeof(int)); // 不该用空基类优化, // 基类占用至少 1 字节加填充以知足首个成员的对齐要求(其对齐要求同 int ) assert(sizeof(Derived3) == 3*sizeof(int)); }
C++11:对于标准布局类型 (StandardLayoutType) 要求有空基类优化,以维持指向标准布局对象的指针,用 reinterpret_cast 转换后,还指向其首成员,这是标准布局类型“无拥有非静态数据成员的基类,且无与其首个非静态数据成员同类型的基类”的缘由。
【注意】对于每一个指定为 virtual
的相异基类,最终派生类对象仅含有该类型的一个基类子对象,即便该类在继承层级中出现屡次(只要它每次都以 virtual
继承)。
下面的AA对象只有2个B基类子对象,XY共有一个虚B基类,Z有一个非虚B基类。
struct B { int n; }; class X : public virtual B {};//相异基类 class Y : virtual public B {};//相异基类 class Z : public B {}; // 每一个 AA 类型对象拥有一个 X ,一个 Y ,一个 Z 和二个 B : // 其一是 Z 的基类,另外一者为 X 与 Y 所共享 struct AA : X, Y, Z { void f() { X::n = 1; // 修改虚 B 基类子对象的成员 Y::n = 2; // 修改同一虚 B 基类子对象的成员 Z::n = 3; // 修改非虚 B 基类子对象的成员 std::cout << X::n << Y::n << Z::n << '\n'; // 打印 223 } };
【例子: iostream 】
继承层级有虚基类的例子之一是标准库的 iostream 的继承层级:
std::istream 与 std::ostream 从 std::ios 使用虚继承派生。
std::iostream 继承 std::istream 和 std::ostream ,
故每一个 std::iostream 实例含一个 std::ostream 子对象、一个 std::istream 子对象和仅一个 std::ios 子对象(继而有一个 std::ios_base )。
(这里不理解)
全部 虚基类子对象 在 任何 非虚基类子对象 前 初始化,故 只有 最终派生类于其成员初始化器列表调用虚基类的构造函数:
struct B { int n; B(int x) : n(x) {} }; struct X : virtual B { X() : B(1) {} }; struct Y : virtual B { Y() : B(2) {} }; struct AA : X, Y{ AA() : B(3),X(),Y() {} //逗号分隔基类列表。前面能够有访问说明。 }; // AA 的默认构造函数调用 X 和 Y 的默认构造函数 // 但这些构造函数不调用 B 的构造函数,由于 B 是虚基类 AA a; // a.n == 3 // X 的默认构造函数调用 B 的构造函数 X x; // x.n == 1
涉及虚继承时,类成员的非限定名称查找有特殊规则(有时被引用为支配规则),见 unqualified_lookup#成员函数定义。
公开继承 public
公开继承模拟面向对象编程的子类型关系:派生类对象是( IS-A )基类子对象。期待派生类对象的引用和指针,可为使用期待到其任何基类的引用和指针的代码所用(见 LSP ),或者为了 DbC ,派生类应该维护其公开基类的类不变量,不该强化任何其所覆写的成员函数的前置条件,或弱化任何其后置条件。
受保护继承 protected
受保护继承可用于“受控制的多态”:在派生类的成员中,还有在全部进一步派生类的成员中,派生类是( IS-A )基类:到派生类的引用和指针可用于期待到基类的引用和指针处。
私有继承 private
私有继承经常使用于基于策略的设计,由于策略常是空基类,而使用基类能够启用静多态并活用空基类优化.
私有继承亦可用于实现合成关系(基类子对象是派生类对象的实现细节)。成员使用提供更好的封装,并且一般受到偏好,除非派生类要求访问基类的受保护成员(包含构造函数)、须要覆写基类的虚成员、须要基类构造先于或析构后于某其余基类子对象,须要共享虚基类或须要控制虚基类的构造。实现合成的成员使用亦不可应用于从参数包多重继承的状况,或在编译时经过模板元编程肯定基类身份的状况。
同受保护继承,私有继承亦可用于受控制的多态:在派生类的成员内(但不在进一步派生类内),派生类是( IS-A )基类。
template<typename Transport> class service : Transport // 从 Transport 策略私有继承 { public: void transmit() { this->send(...); // 发送传输所提供的任何内容 } }; // TCP 传输策略 class tcp { public: void send(...); }; // UDP 传输策略 class udp { public: void send(...); }; service<tcp> service(host, port); service.transmit(...); // 发送完毕 TCP
类成员非限定及限定名称查找的规则详细列于名称查找。
派生类内部必须对全部重定义的虚函数进行声明,能够在函数前加上virtual关键字,也能够不加。
只有虚函数才能被覆盖。签名要匹配。
为了方便编译器找出错误。C++经过override和final显式说明派生类的虚函数,这两个说明符在语句的最后。
在成员函数声明或定义中, override
能够显式地指出该函数为虚函数,并覆写来自基类的虚函数。
struct A { virtual void foo(); void bar(); }; struct B : A { void foo() const override; // 错误: B::foo 不覆写 A::foo // (签名不匹配) void foo() override; // OK : B::foo 覆写 A::foo void bar() override; // 错误: A::bar 非虚 };
在虚函数声明或定义中使用时, final
确保函数为虚,且不可被派生类覆写。
final
亦可用于联合体定义,此状况下它无效(除了 std::is_final 上的结果),由于不能派生联合体。final 是在用于成员函数声明或类头部时有特殊含义的标识符。其余语境中它非保留并且可用于命名对象或函数。
struct Base { virtual void foo(); }; struct A : Base { void foo() final; // A::foo 被覆写且是最终覆写 void bar() final; // 错误:非虚函数不能被覆写或是 final }; struct B final : A // struct B 为 final { void foo() override; // 错误: foo 不能被覆写,由于它在 A 中是 final }; struct C : B // 错误: B 为 final { };
使用基类的引用或指针,调用一个虚函数,虚函数运行时,形参的版本由实参对象的类型决定。
C++ OOP的关键:基类和派生类之间的类型转换。
只有在指针和引用之间才有类型转换。在对象之间没有类型转换。
若使用到基类的指针或引用处理派生类,则对被覆写虚函数的调用,将会调用定义于派生类的行为。
若使用有限定名称查找(做用域解决运算符 ::
),调用做用域内部的非虚函数。
#include <iostream> struct Base { virtual void f() { std::cout << "base\n"; } }; struct Derived : Base { void f() override { // 'override' 可选 std::cout << "derived\n"; } }; int main() { Base b; Derived d; // 经过引用调用虚函数 Base& br = b; // br 的类型是 Base& Base& dr = d; // dr 的类型也是 Base& br.f(); // 打印 "base" dr.f(); // 打印 "derived" // 经过指针调用虚函数 Base* bp = &b; // bp 的类型是 Base* Base* dp = &d; // dp 的类型也是 Base* bp->f(); // 打印 "base" dp->f(); // 打印 "derived" // 非虚函数调用 br.Base::f(); // 打印 "base" dr.Base::f(); // 打印 "base" }
若某成员函数 vf
在类 Base
中声明为 virtual
,且某个直接或间接从 Base
派生的类 Derived
拥有下列几点与之相同的成员函数声明
则类 Derived
中的此函数亦为虚(不管是否于其声明使用关键词 virtual
)并覆写 Base::vf (不管是否于其声明使用词 override
)。
要覆写的 Base::vf
不须要可见(可声明为 private ,或用私有继承继承)。
class B { virtual void do_f(); // 私有成员 public: void f() { do_f(); } // 公开继承 }; struct D : public B { void do_f() override; // 覆写 B::do_f }; int main() { D d; B* bp = &d; bp->f(); // 内部调用 D::do_f(); }
对于每一个虚函数,存在最终覆写者,它在虚函数调用进行时执行。基类 Base
虚成员函数 vf
是最终覆写者,除非派生类声明或继承(经过多重继承)另外一覆写 vf
的函数。
struct A { virtual void f(); }; // A::f 为 virtual struct B : A { void f(); }; // B::f 覆写 A::f in B struct C : virtual B { void f(); }; // C::f 覆写 A::f in C struct D : virtual B {}; // D 不引入覆写者, B::f 在 D 中为最终 struct E : C, D { // E 不引入覆写者, C::f 在 E 中为最终 using A::f; // 非函数声明,仅令 A::f 能为查找所见 }; int main() { E e; e.f(); // 虚调用调用 C::f , e 中的最终覆写者 e.E::f(); // 非虚调用调用 A::f ,它在 E 中可见 }
虚函数只能有一个最终覆写者:
struct A { virtual void f(); }; struct VB1 : virtual A { void f(); // 覆写 A::f }; struct VB2 : virtual A { void f(); // 覆写 A::f }; // struct Error : VB1, VB2 { // // 错误: A::f 在 Error 中拥有二个最终覆写者 // }; struct Okay : VB1, VB2 { void f(); // OK :这是 A::f 的最终覆写者 }; struct VB1a : virtual A {}; // 不声明覆写者 struct Da : VB1a, VB2 { // Da 中, A::f 的最终覆写者是 VB2::f };
拥有同名和相异参数列表的函数不覆写同名的基类函数,但隐藏它:在非限定名称查找检验派生类的做用域时,查找找到该声明,且不检验基类。
struct B { virtual void f(); }; struct D : B { void f(int); // D::f 隐藏 B::f (错误的参数列表) }; struct D2 : D { void f(); // D2::f 覆写 B::f (它不可见是没关系的) }; int main() { B b; B& b_as_b = b; D d; B& d_as_b = d; D& d_as_d = d; D2 d2; B& d2_as_b = d2; D& d2_as_d = d2; b_as_b.f(); // 调用 B::f() d_as_b.f(); // 调用 B::f() d2_as_b.f(); // 调用 D2::f() d_as_d.f(); // 错误: D 中的查找只找到 f(int) d2_as_d.f(); // 错误: D 中的查找只找到 f(int) }
非成员函数和静态成员函数不能为虚。
函数模板不能为虚 virtual
。这只应用于自身是模板的函数——类模板的常规成员函数能声明为虚。
在编译时替换虚函数的默认实参。
若函数 Derived::f
覆写 Base::f
,则其返回类型必须相同或为协变。若知足全部下列要求,则二个类型为协变:
Base::f()
的返回类型中被引用/指向的类,必须是 Derived::f()
的返回类型中被引用/指向的类的无歧义且可直接或间接访问的基类。Derived::f()
的返回类型必须有相对于 Base::f()
的返回类型的相等或较少的 cv 限定。Derived::f
的返回类型中的类必须是 Derived
自身,或必须是于 Derived::f
声明点的完整类型。
进行虚函数调用时,最终覆写者的返回类型被隐式转换成本该调用的被覆写函数的返回类型:
class B {}; struct Base { virtual void vf1(); virtual void vf2(); virtual void vf3(); virtual B* vf4(); virtual B* vf5(); }; class D : private B { friend struct Derived; // Derived 中, B 是 D 的可访问基类 }; class A; // 前置声明类是不完整类型 struct Derived : public Base { void vf1(); // 虚,覆写 Base::vf1() void vf2(int); // 非虚,隐藏 Base::vf2() // char vf3(); // 错误:覆写 Base::vf3 ,但有相异而非协变返回类型 D* vf4(); // 覆写 Base::vf4() 并用有协变返回类型 // A* vf5(); // 错误: A 是不完整类型 }; int main() { Derived d; Base& br = d; Derived& dr = d; br.vf1(); // 调用 Derived::vf1() br.vf2(); // 调用 Base::vf2() // dr.vf2(); // 错误: vf2(int) 隐藏 vf2() B* p = br.vf4(); // 调用 Derived::vf4() 并转换结果为 B* D* q = dr.vf4(); // 调用 Derived::vf4() 并不转换结果为 B* }
当直接或间接从构造函数或从析构函数调用虚函数(包含在类的非静态成员函数的构造或析构期间,例如在初始化器列表中),且应用调用的对象是正在构造或析构中的对象,则所调用的函数是构造函数或析构函数的类中的最终覆写者,而非进一步派生类中的覆写者。 换言之,在构造和析构期间,进一步派生类不存在。
构建有多分支的复杂类时,在属于一个分支的构造函数内,多态被限制到该类与其基类:若它得到到其子层级外的基类子对象的指针,且试图进行虚函数调用(例如经过显式成员访问),则行为未定义:
struct V { virtual void f(); virtual void g(); }; struct A : virtual V { virtual void f(); // A::f 是 V::f 在 A 中的最终覆写者 }; struct B : virtual V { virtual void g(); // B::g 是 V::g 在 B 中的最终覆写者 B(V*, A*); }; struct D : A, B { virtual void f(); // D::f 是 V::f 在 D 中的最终覆写者 virtual void g(); // D::g 是 V::g 在 D 中的最终覆写者 // 注意: A 初始化先于 B D() : B((A*)this, this) { } }; // B 的构造函数,从 D 的构造函数调用 B::B(V* v, A* a) { f(); // 对 V::f 的虚调用(尽管 D 拥有最终覆写者, D 也不存在) g(); // 对 B::g 的虚调用,在 B 中是最终覆写者 v->g(); // v 的类型 V 是 B 的基类,虚调用如前调用 B::g a->f(); // a 的类型 A 不是 B 的基类,它属于层级中的不一样分支。 // 尝试经过不一样分支的虚调用致使未定义行为, // 即便此状况下 A 已彻底构造 // (它在 B 前构造,由于它在 D 的基类列表中出现先于 B ) // 实践中,对 A::f 的虚调用会试图使用 B 的虚成员函数表, // 由于它在 B 的构造中活跃 }
定义不能被实例化,但能用做基类的抽象类型。
纯虚 (pure virtual) 函数是声明器拥有下列语法的虚函数:
此处序列 = 0
被称做 pure-specifier ,且当即出现于 declarator 后或于可选的 virt-specifier ( override 或 final )后。
pure-specifier 不能出现于成员函数定义中。
struct Base { virtual int g(); virtual ~Base() {} }; struct A : Base{ // OK :声明三个成员虚函数,其二为纯 virtual int f() = 0, g() override = 0, h(); // OK :析构函数亦能为纯 ~A() = 0; // 错误:纯指定符在函数定义上 virtual int b()=0 {} };
abstract class 是定义或继承了至少一个最终覆写为 pure virtual 的函数的类。
抽象类用于表示通用概念(例如 Shape 、 Animal ),它可用做具体类(例如 Circle 、 Dog )的基类。
不能建立抽象类的实例。抽象类型不能用做参数类型、函数返回类型或显式转换的类型。能够声明到抽象类的指针或引用。
struct Abstract { virtual void f() = 0; // 纯虚 }; // "Abstract" 为抽象 struct Concrete : Abstract { void f() override {} // 非纯虚 virtual void g(); // 非纯虚 }; // "Concrete" 为非抽象 struct Abstract2 : Concrete { void g() override = 0; // 纯虚覆写 }; // "Abstract2" 为抽象 int main() { // Abstract a; // 错误:抽象类 Concrete b; // OK Abstract& a = b; // OK :到抽象基类的引用 a.f(); // 到 Concrete::f() 的虚派发 // Abstract2 a2; // 错误:抽象类( g() 的最终覆写为纯) }
能够提供纯虚函数的定义(并且若析构函数为纯虚则必须提供):导出类的成员函数能够自由地用有限定函数 id 调用虚基类的纯虚函数。此定义必须在类体外(函数声明的语法不容许纯虚指定符 = 0
和函数体一块儿出现)。
从抽象类的构造函数或析构函数进行纯虚函数的虚调用是未定义行为(不管纯虚函数是否拥有定义)。
struct Abstract { virtual void f() = 0; // 纯虚 virtual void g() {} // 非纯虚 ~Abstract() { g(); // OK :调用 Abstract::g() // f(); // 未定义行为! Abstract::f(); // OK :非虚调用 } }; // 纯虚函数的定义 void Abstract::f() { std::cout << "A::f()\n"; } struct Concrete : Abstract { void f() override { Abstract::f(); // OK :调用纯虚函数 } void g() override {} ~Concrete() { g(); // OK :调用 Concrete::g() f(); // OK :调用 Concrete::f() } };