Cpp_继承&对象模型

来源:互联网 发布:tensorflow的windows版 编辑:程序博客网 时间:2024/05/17 05:06

1. 继承

  • 定义

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。

  • 格式

这里写图片描述

  • 继承关系&访问限定符

这里写图片描述

代码如下:

#include <iostream>using namespace std;class Base{public:    Base()    {        cout << "B()" << endl;    }    ~Base()    {        cout << "~B()" << endl;    }    void ShowBase()    {        cout << "_pri = " << _pri << endl;        cout << "_pro = " << _pro << endl;        cout << "_pub = " << _pub << endl;    }private:    int _pri;protected:    int _pro;public:    int _pub;};class Derived :public Base{public:    Derived()    {        cout << "D()" << endl;    }    ~Derived()    {        cout << "~D()" << endl;    }    void ShowDerived()    {        cout << "_d_pri = " << _d_pri << endl;        cout << "_d_pro = " << _d_pro << endl;        cout << "_d_pub = " << _d_pub << endl;    }private:    int _d_pri;protected:    int _d_pro;public:    int _d_pub;};
  • 小结

①基类的private成员在派生类中是不能被访问的,如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的。

②public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也都是一个父类对象。

③protected/private继承是一个实现继承,基类的部分成员并非完全成为子类接口的一部分,是 has-a 的关系原则,所以非特殊情况下不会使用这两种继承关系,在绝大多数的场景下使用的都是公有继承。私有继承以为这is-implemented-in-terms-of(是根据……实现的)。通常比组合(composition)更低级,但当一个派生类需要访问基类保护成员或需要重定义基类的虚函数时它就是合理的。

④不管是哪种继承方式,在派生类内部都可以访问基类的公有成员和保护成员,基类的私有成员存在但是在子类中不可见(不能访问)。

⑤使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。

⑥在实际运用中一般使用都是public继承,极少场景下才会使用protetced/private继承。

  • 派生类的默认成员函数

在继承关系里面,在派生类中如果没有显示定义这六个成员函数,编译系统则会默认合成这六个默认成员函数。

这里写图片描述

【构造函数调用顺序】

这里写图片描述

【析构函数调用过程】

这里写图片描述

  • 同名隐藏(重定义)

①一个在基类,一个在派生类;
②只要名字一样。

  • 赋值兼容规则——>public继承

①派生类对象可直接赋值给基类对象;
②基类指针或引用可直接指向派生类的对象。

这里写图片描述

  • 友元与继承

友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。

  • 继承与静态成员

基类定义了static成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。

  • 继承模型

【单继承】——> 一个子类只有一个直接父类

这里写图片描述

【多继承】——> 一个子类有两个或以上直接父类

这里写图片描述

【菱形继承】——> (钻石继承)

这里写图片描述

#include <iostream>using namespace std;class B{public:    int _b;};class C1 :public B{public:    int _c1;};class C2 :public B{public:    int _c2;};class D :public C1, public C2{public:    int _d;};void test(){    D d;    d.C1::_b = 0;    d._c1 = 1;    d.C2::_b = 0;    d._c2 = 3;    d._d = 4;}

由上图和代码分析,在D创建的d对象中,存储两份_b成员,因此,可以知道,菱形继承具有二义性和数据冗余的问题。若要解决这个问题,提出虚拟继承。

  • 虚拟继承

这里写图片描述

①虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。
②虚继承体系看起来好复杂,在实际应用我们通常不会定义如此复杂的继承体系。一般不到万不得已都不要定义菱形结构的虚继承体系结构,因为使用虚继承解决数据冗余问题也带来了性能上的损耗。

2.对象模型

  • 多态 ——> 多种形态

这里写图片描述

【静态多态】
又称【静态绑定】或者【早绑定】。是指编译器在编译期间完成的,编译器根据函数实参的类型(可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该数,否则出现编译错误。

给出一段代码:

int Add(int left, int right){    return left + right;}float Add(float left, float right){    return left + right;}int main(){    cout << Add(1, 2) << endl;    cout << Add(1.23f, 3.21f) << endl;    return 0;}

分析如下:
这里写图片描述

【动态多态】
又称【动态绑定】,是指在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。

动态绑定的条件:
①必须是虚函数;(派生类中必须重写基类的虚函数)
②通过积累类型的引用或指针调用虚函数。

**注意:**virtual关键字只能出现在函数声明之时。

  • 继承体系同名成员函数的关系

【重写】(覆盖)
①一定是虚函数
②函数一个在基类,一个在派生类
③函数原型——>函数名相同/返回值相同/参数相同
特例:【协变】——>返回值类型不同

【重载】
①函数在同一作用域
②函数原型——>返回值可不同/函数名相同/参数列表不同(类型、个数、次序)

【重定义】(隐藏)
①函数一个在基类,一个在派生类
②函数名相同
注意:在基类和派生类中,只要不构成重写,就是重定义。

  • 纯虚函数

在成员函数的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。

代码如下:

class Person{    virtual void Display() = 0; // 纯虚函数protected:    string _name; // 姓名};class Student : public Person{};
  • 小结

①派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值完全相同。(协变除外)

②基类中定义了虚函数,在派生类中该函数始终保持虚函数的特性。

③只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数。

④如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。

⑤构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但最好不要这么做,使用时容易混淆。

⑥不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。

⑦最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)。

⑧虚表是所有类对象实例共用的。