C++继承总结

来源:互联网 发布:淘宝链接前加了s.click 编辑:程序博客网 时间:2024/05/21 12:42

继承:继承是面向对象复用的重要手段,通过继承定义一个类,继承是关系类型之间的建模,
共享共有的东西,实现各自本质不同的东西。

在继承关系当中,派生类继承父类的成员由此达到复用手段。

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

protected/private继承是一个实现继承,是has-a的原则,基类的部分成员函数并未
完全的成为子类接口的一部分。

通俗的来讲:is-a表示了一种是的关系;例如 白马是马,香蕉是水果的这种关系。
has-a 表示了有的关系,例如:午餐有香蕉,但是香蕉不是午餐,而是午餐的一种

继承与转换–赋值兼容规则–public继承

  1. 子类对象可以赋值给父类对象(切割/切片)
  2. 父类对象不可以赋值给子类对象
  3. 父类的指针和引用可以指向子类对象
  4. 子类的指针和引用不可以指向父类对象(可以通过强制类型转换)

继承体系中的作用域

  1. 在继承体系当中基类和派生类都有自己独立的作用域。
  2. 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问,
    (在子类当中,可以使用基类::基类成员 访问),隐藏重定义。
  3. 在实际中在继承体系当中最好不要定义同名成员。

例如:

#pragma onceclass Person{public:      Person(const char* name = "", int num = 0)      :_name(name)      , _num(num)      {}protected:      string _name;      int _num; //身份证号};class Student :public Person{public:      Student(const char* name, int id, int sum)            :Person(name, id)            , _num(sum)      {}      void Print()      {            cout << "身份证号:" << Person::_num << endl;            cout << "学号:" << _num << endl;      }protected:      int _num;};void Testper(){      Student s("zahngsan", 120, 11);      s.Print();}

单继承&多继承
1. 单继承–一个子类只有一个直接父类时称这个继承关系为单继承
2. 多继承– 一个子类有两个或以上直接父类时称这个继承关系为多重继承

菱形继承是典型的多继承
菱形继承:两个子类同时继承一个父类,而又有子类同时继承这两个子类

如图所示:
菱形继承在内存中的布局:

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>using namespace std;class A{public:    int _a;};class B: public A{public:    int _b;};class C: public A{public:    int _c;};class D:public B,public C{public:    int _d;};int main(){    D dd;    dd.B::_a = 1;    dd.B::_b = 2;    dd.C::_a = 3;    dd.C::_c = 4;    dd._d = 5;    cout << sizeof(dd) << endl;    system("pause");    return 0;}

思考这里的sizeof(dd)是多少?
这里写图片描述

它在内存中的布局:

这里写图片描述
D的对象中有两份A成员
菱形继承存在二义性和数据冗余的问题
虚继承可以解决———菱形继承存在二义性和数据冗余的问题

#define _CRT_SECURE_NO_WARNINGS 1#include<iostream>using namespace std;class A{public:    int _a;};class B: virtual public A{public:    int _b;};class C: virtual public A{public:    int _c;};class D:public B,public C{public:    int _d;};int main(){    D dd;    dd.B::_a = 1;    dd.B::_b = 2;    dd.C::_a = 3;    dd.C::_c = 4;    dd._d = 5;    cout << sizeof(dd) << endl;    system("pause");    return 0;}

思考这里的sizeof(dd)是多少?
相信很多人都认为这里的值为16,大家可能都会想虚继承后B中的A和C中的A会指向同一块空间
运行后的结果如图所示:
这里写图片描述
很多人可能都会有疑问sizeof的值不仅不变小怎么还增大了
请看它在内存中的布局
这里写图片描述

将间接存放A的偏移量的地址输入可以看到偏移量:

这里写图片描述

虚继承体系看起来好复杂,在实际应用中我们通常不会定义如此复杂的继承体系,⼀般不到万不得已都不要
定义菱形结构的虚继承体系结构,因为虚继承体系解决数据冗余问题也带来了性能上的损耗。

问题:上边的虚基表里为什么不直接存储偏移量,而在偏移量上边留了四个字节的位置?

验证:设置一个菱形继承,且为虚继承,每个子类既重写了父类的虚函数,还拥有自己的虚函数。

#pragma onceclass A{public:    virtual void Fun1()    {        cout << "A::Fun1" << endl;    }    virtual void Fun2()    {        cout << "A::Fun2" << endl;    }    int _a;};class B :virtual public A{public:    virtual void Fun1()    {        cout << "B::Fun1" << endl;    }    virtual void Fun3()    {        cout << "B::Fun3" << endl;    }    int _b;};class C :virtual public A{public:    virtual void Fun1()    {        cout << "C::Fun1" << endl;    }    virtual void Fun4()    {        cout << "C::Fun4" << endl;    }    int _c;};class D:public B, public C{public:    virtual void Fun1()    {        cout << "D::Fun1" << endl;    }    virtual void Fun5()    {        cout << "D::Fun5" << endl;    }    int _d;}; void TestABCD(){    D d;    d.B::_a = 1;    d.B::_b = 2;    d.C::_a = 3;    d.C::_c = 4;    d._d = 5;    cout << sizeof(d) << endl;}

这里写图片描述

所以那里预留出的四个字节是给虚函数表留的。

虚函数&多态
虚函数–类的成员函数前⾯加virtual关键字,则这个成员函数称为虚函数。
虚函数重写–当在⼦类的定义了⼀个与⽗类完全相同的虚函数时,则称⼦类的这个
函数重写(也称覆盖)了⽗类的这个虚函数。

例子:

#pragma onceclass Person{public:    virtual void BuyTickets()    {        cout << "买票-全价" << endl;    }protected:    string _name;};class Student :public Person{public:    virtual void BuyTickets()    {        cout << "买票--半价" << endl;    }protected:    int _num;};void Fun(Person& p){    p.BuyTickets();}void Test(){    Person p;    Student s;    Fun(p);    Fun(s);}

构成多态的两个条件:
1. 虚函数的重写
2. 父类的指针或者引用

编译阶段 –检查是否构成多态
不构成–静态连编 按对象类型去调用
构成– 动态连编 跟对象的类型无关 跟指向对象有关 –在汇编的时候找到虚表

总结:
1. 派生类重写基类的虚函数实现多态,要求函数名,参数列表、返回值完全相同。(协变除外)
2. 基类定义了虚函数,在派生类中该函数始终保持虚函数的特性
3. 只有类的成员函数才能成为虚函数
4. 静态成员函数不能定义为虚函数
5. 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数不能加virtual
6. 构造函数不能定义为虚函数,虽然可以将operator=定义为虚函数,但是最好不要这样定义容易引起混淆。
7. 不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,
可能会发生未定义行为。
8. 最好把基类的析构函数声明为虚函数

#pragma onceclass AA{public:     ~AA()    {        cout << "~AA()" << endl;    }};class BB :public AA{public:     ~BB()    {        cout << "~BB()" << endl;    }};void test(){    AA *p = new BB;    delete p;}

这里析构函数只会调用AA的显然是不对的。
解决方法:将父类的析构函数定义为虚函数就可以了。
这里写图片描述

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

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

继承与静态成员:
基类定义了static成员,则整个继承体系只有这样一个成员,无论派生出多少个子类,
都只有一个static成员。

1 1