C++中类的继承

继承概述

为什么需要继承

//网页类
class IndexPage{
public:
    //网页头部
    void Header(){
        cout << "网页头部!" << endl;
    }
    //网页左侧菜单
    void LeftNavigation(){
        cout << "左侧导航菜单!" << endl;
    }
    //网页主体部分
    void MainBody(){
        cout << "首页网页主题内容!" << endl;
    }
    //网页底部
    void Footer(){
        cout << "网页底部!" << endl;
    }
private:
    string mTitle; //网页标题
};
#if 0
//如果不使用继承,那么定义新闻页类,需要重新写一遍已经有的代码
class NewsPage{
public:
//网页头部
    void Header(){
        cout << "网页头部!" << endl;
    }
    //网页左侧菜单
    void LeftNavigation(){
        cout << "左侧导航菜单!" << endl;
    }
    //网页主体部分
    void MainBody(){
        cout << "新闻网页主体内容!" << endl;
    }
    //网页底部
    void Footer(){
        cout << "网页底部!" << endl;
    }
private:
    string mTitle; //网页标题
};
void test(){
    NewsPage* newspage = new NewsPage;
    newspage->Header();
    newspage->MainBody();
    newspage->LeftNavigation();
    newspage->Footer();
}
#else
//使用继承,可以复用已有的代码,新闻业除了主体部分不一样,其他都是一样的
class NewsPage : public IndexPage{
public:
    //网页主体部分
    void MainBody(){
        cout << "新闻网页主主体内容!" << endl;
    }
};
void test(){
    NewsPage* newspage = new NewsPage;
    newspage->Header();
    newspage->MainBody();
    newspage->LeftNavigation();
    newspage->Footer();
}
#endif
int main(){
    test();
    return EXIT_SUCCESS;
}

继承基本概念

        c++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的 数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。
        一个 B 类继承于 A 类,或称从类 A 派生类 B 。这样的话,类 A 成为基类(父类), 类 B 成为派生类(子类)。
派生类中的成员,包含两大部分:
        一类是从基类继承过来的,一类是自己增加的成员。
        从基类继承过过来的表现其共性,而新增的成员体现了其个性。

派生类定义

派生类定义格式:
        Class 派生类名 : 继承方式 基类名 {
                //派生类新增的数据成员和成员函数
        }
三种继承方式:
        public : 公有继承
        private : 私有继承
        protected : 保护继承
从继承源上分:
        单继承:指每个派生类只直接继承了一个基类的特征
`·      多继承:指多个基类派生出一个派生类的继承关系, 多继承的派生类直接继承了不 止一个基类的特征

派生类访问控制

        派生类继承基类,派生类拥有基类中全部成员变量和成员方法(除了构造和析构之 外的成员方法),但是在派生类中,继承的成员并不一定能直接访问,不同的继承 方式会导致不同的访问权限。
派生类的访问权限规则如下:
//基类
class A{
public:
    int mA;
protected:
    int mB;
private:
    int mC;
};
//1. 公有(public)继承
class B : public A{
public:
    void PrintB(){
        cout << mA << endl; //可访问基类 public 属性
        cout << mB << endl; //可访问基类 protected 属性
        //cout << mC << endl; //不可访问基类 private 属性
    } 
};
class SubB : public B{
    void PrintSubB(){
        cout << mA << endl; //可访问基类 public 属性
        cout << mB << endl; //可访问基类 protected 属性
        //cout << mC << endl; //不可访问基类 private 属性
    }
};
void test01(){
    B b;
    cout << b.mA << endl; //可访问基类 public 属性
    //cout << b.mB << endl; //不可访问基类 protected 属性
    //cout << b.mC << endl; //不可访问基类 private 属性
}
//2. 私有(private)继承
class C : private A{
public:
    void PrintC(){
        cout << mA << endl; //可访问基类 public 属性
        cout << mB << endl; //可访问基类 protected 属性
        //cout << mC << endl; //不可访问基类 private 属性
    }
};
class SubC : public C{
    void PrintSubC(){
        //cout << mA << endl; //不可访问基类 public 属性
        //cout << mB << endl; //不可访问基类 protected 属性
        //cout << mC << endl; //不可访问基类 private 属性
    }
};
void test02(){
    C c;
    //cout << c.mA << endl; //不可访问基类 public 属性
    //cout << c.mB << endl; //不可访问基类 protected 属性
    //cout << c.mC << endl; //不可访问基类 private 属性
}
//3. 保护(protected)继承
class D : protected A{
public:
    void PrintD(){
        cout << mA << endl; //可访问基类 public 属性
        cout << mB << endl; //可访问基类 protected 属性
        //cout << mC << endl; //不可访问基类 private 属性
    }
};
class SubD : public D{
    void PrintD(){
        cout << mA << endl; //可访问基类 public 属性
        cout << mB << endl; //可访问基类 protected 属性
        //cout << mC << endl; //不可访问基类 private 属性
    }
};
void test03(){
    D d;
    //cout << d.mA << endl; //不可访问基类 public 属性
    //cout << d.mB << endl; //不可访问基类 protected 属性
    //cout << d.mC << endl; //不可访问基类 private 属性
}

继承中的构造和析构

继承中的对象模型

C++ 编译器的内部可以理解为结构体,子类是由父类成员叠加子类新成员而成:
class Aclass{
public:
    int mA;
    int mB;
};
class Bclass : public Aclass{
public:
    int mC;
};
class Cclass : public Bclass{
public:
    int mD;
};
void test(){
    cout << "A size:" << sizeof(Aclass) << endl;
    cout << "B size:" << sizeof(Bclass) << endl;
    cout << "C size:" << sizeof(Cclass) << endl;
}

对象构造和析构的调用原则

继承中的构造和析构
        子类对象在创建时会首先调用父类的构造函数
        父类构造函数执行完毕后,才会调用子类的构造函数
        当父类构造函数有参数时,需要在子类初始化列表( 参数列表 ) 中显示调用父类构造 函数
        析构函数调用顺序和构造函数相反
class A{
public:
    A(){
        cout << "A 类构造函数!" << endl;
    }
    ~A(){
        cout << "A 类析构函数!" << endl;
    }
};
class B : public A{
public:
    B(){
        cout << "B 类构造函数!" << endl;
    }
    ~B(){
        cout << "B 类析构函数!" << endl;
    }
};
class C : public B{
public:
    C(){
        cout << "C 类构造函数!" << endl;
    }
    ~C(){
        cout << "C 类析构函数!" << endl;
    }
};
void test(){
    C c;
}
继承与组合混搭的构造和析构 
class D{
public:
    D(){
        cout << "D 类构造函数!" << endl;
    }
    ~D(){
        cout << "D 类析构函数!" << endl;
    }
};
class A{
public:
    A(){
        cout << "A 类构造函数!" << endl;
    }
    ~A(){
        cout << "A 类析构函数!" << endl;
    }
};
class B : public A{
public:
    B(){
        cout << "B 类构造函数!" << endl;
    }
    ~B(){
        cout << "B 类析构函数!" << endl;
    }
};
class C : public B{
public:
    C(){
        cout << "C 类构造函数!" << endl;
    }
    ~C(){
        cout << "C 类析构函数!" << endl;
    }
public:
    D c;
};
void test(){
    C c;
}

继承中同名成员的处理方法

        当子类成员和父类成员同名时,子类依然从父类继承同名成员
        如果子类有成员和父类同名,子类访问其成员默认访问子类的成员( 本作用域,就近原则)
在子类通过作用域 :: 进行同名成员区分 ( 在派生类中使用基类的同名成员,显示使用类名限定符)
class Base{
public:
    Base():mParam(0){}
    void Print(){ cout << mParam << endl; }
public:
    int mParam;
};
class Derived : public Base{
public:
    Derived():mParam(10){}
    void Print(){
        //在派生类中使用和基类的同名成员,显示使用类名限定符
        cout << Base::mParam << endl;
        cout << mParam << endl;
    }
    //返回基类重名成员
    int& getBaseParam(){ return Base::mParam; }
public:
    int mParam;
};
int main(){
    Derived derived;
    //派生类和基类成员属性重名,子类访问成员默认是子类成员
    cout << derived.mParam << endl; //10
    derived.Print();
    //类外如何获得基类重名成员属性
    derived.getBaseParam() = 100;
    cout << "Base:mParam:" << derived.getBaseParam() << endl;
    return EXIT_SUCCESS;
}
注意 : 如果重新定义了基类中的重载函数,将会发生什么?
class Base{
public:
    void func1(){
        cout << "Base::void func1()" << endl;
    };
    void func1(int param){
        cout << "Base::void func1(int param)" << endl;
    }
    void myfunc(){
        cout << "Base::void myfunc()" << endl;
    }
};
class Derived1 : public Base{
public:
    void myfunc(){
        cout << "Derived1::void myfunc()" << endl;
    }
};
class Derived2 : public Base{
public:
    //改变成员函数的参数列表
    void func1(int param1, int param2){
        cout << "Derived2::void func1(int param1,int param2)" << endl;
    };
};
class Derived3 : public Base{
public:
    //改变成员函数的返回值
    int func1(int param){
        cout << "Derived3::int func1(int param)" << endl;
        return 0;
    }
};
int main(){
    Derived1 derived1;
    derived1.func1();
    derived1.func1(20);
    derived1.myfunc();
    cout << "-------------" << endl;
    Derived2 derived2;
    //derived2.func1(); //func1 被隐藏
    //derived2.func1(20); //func2 被隐藏
    derived2.func1(10,20); //重载 func1 之后,基类的函数被隐藏
    derived2.myfunc();
    cout << "-------------" << endl;
    Derived3 derived3;
    //derived3.func1(); 没有重新定义的重载版本被隐藏
    derived3.func1(20);
    derived3.myfunc();
    return EXIT_SUCCESS;
}
        Derive1 重定义了 Base 类的 myfunc 函数, derive1 可访问 func1 及其重载版本的函数。
        Derive2 通过改变函数参数列表的方式重新定义了基类的 func1 函数,则从基类 中继承来的其他重载版本被隐藏,不可访问 Derive3 通过改变函数返回类型的方式重新定义了基类的 func1 函数,则从基类 继承来的没有重新定义的重载版本的函数将被隐藏。
        任何时候重新定义基类中的一个重载函数,在新类中所有的其他版本将被自动隐藏    

非自动继承的函数

        不是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对 象的创建和析构操作,构造和析构函数只知道对它们的特定层次的对象做什么,也 就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。
        另外 operator= 也不能被继承,因为它完成类似构造函数的行为。也就是说尽管我 们知道如何由= 右边的对象如何初始化 = 左边的对象的所有成员,但是这个并不意 味着对其派生类依然有效。在继承的过程中,如果没有创建这些函数,编译器会自动生成它们。

继承中的静态成员特性

静态成员函数和非静态成员函数的共同点 :
        1. 他们都可以被继承到派生类中。
        2. 如果重新定义一个静态成员函数,所有在基类中的其他重载函数会被隐藏。
        3. 如果我们改变基类中一个函数的特征,所有使用该函数名的基类版本都会被隐藏
class Base{
public:
    static int getNum(){ return sNum; }
    static int getNum(int param){
    return sNum + param;
}
public:
    static int sNum;
};
int Base::sNum = 10;
class Derived : public Base{
public:
    static int sNum; //基类静态成员属性将被隐藏
#if 0
    //重定义一个函数,基类中重载的函数被隐藏
    static int getNum(int param1, int param2){
        return sNum + param1 + param2;
    }
#else
    //改变基类函数的某个特征,返回值或者参数个数,将会隐藏基类重载的函数
    static void getNum(int param1, int param2){
        cout << sNum + param1 + param2 << endl;
    }
#endif
};
int Derived::sNum = 20;

多继承

多继承概念

        我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由 于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义
class Base1{
public:
    void func1(){ cout << "Base1::func1" << endl; }
};
class Base2{
public:
    void func1(){ cout << "Base2::func1" << endl; }
    void func2(){ cout << "Base2::func2" << endl; }
};
//派生类继承 Base1、Base2
class Derived : public Base1, public Base2{};
int main(){
    Derived derived;
    //func1 是从 Base1 继承来的还是从 Base2 继承来的?
    //derived.func1(); 
    derived.func2();
    //解决歧义:显示指定调用那个基类的 func1
    derived.Base1::func1(); 
    derived.Base2::func1();
    return EXIT_SUCCESS;
}
        多继承会带来一些二义性的问题, 如果两个基类中有同名的函数或者变量,那么 通过派生类对象去访问这个函数或变量时就不能明确到底调用从基类 1 继承的版 本还是从基类 2 继承的版本? 解决方法就是显示指定调用那个基类的版本。
    

菱形继承和虚继承

        两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为 菱形继承,或者钻石型继承。
这种继承所带来的问题:
        1. 羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用 函数或者数据时,就会产生二义性。
        2. 草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我 们只需要一份就可以。
class BigBase{
public:
    BigBase(){ mParam = 0; }
    void func(){ cout << "BigBase::func" << endl; }
public:
    int mParam;
};
class Base1 : public BigBase{};
class Base2 : public BigBase{};
class Derived : public Base1, public Base2{};
int main(){
    Derived derived;
    //1. 对“func”的访问不明确
    //derived.func();
    //cout << derived.mParam << endl;
    cout << "derived.Base1::mParam:" << derived.Base1::mParam << endl;
    cout << "derived.Base2::mParam:" << derived.Base2::mParam << endl;
    //2. 重复继承
    cout << "Derived size:" << sizeof(Derived) << endl; //8
    return EXIT_SUCCESS;
}
        上述问题如何解决?对于调用二义性,那么可通过指定调用那个基类的方式来解决, 那么重复继承怎么解决?
        对于这种菱形继承所带来的两个问题,c++ 为我们提供了一种方式,采用虚基类。
那么我们采用虚基类方式将代码修改如下:
class BigBase{
public:
    BigBase(){ mParam = 0; }
    void func(){ cout << "BigBase::func" << endl; }
public:
    int mParam;
};
class Base1 : virtual public BigBase{};
class Base2 : virtual public BigBase{};
class Derived : public Base1, public Base2{};
int main(){
    Derived derived;
    //二义性问题解决
    derived.func();
    cout << derived.mParam << endl;
    //输出结果:12
    cout << "Derived size:" << sizeof(Derived) << endl;
    return EXIT_SUCCESS;
}
        以上程序 Base1 Base2 采用虚继承方式继承 BigBase, 那么 BigBase 被称为虚基类。
        通过虚继承解决了菱形继承所带来的二义性问题。
        但是虚基类是如何解决二义性的呢?并且 derived 大小为 12 字节,这是怎么回事?

虚继承实现原理

class BigBase{
public:
    BigBase(){ mParam = 0; }
    void func(){ cout << "BigBase::func" << endl; }
public: int mParam;
};
#if 0 //虚继承
    class Base1 : virtual public BigBase{};
    class Base2 : virtual public BigBase{};
#else //普通继承
    class Base1 : public BigBase{};
    class Base2 : public BigBase{};
#endif
class Derived : public Base1, public Base2{};

        通过内存图,我们发现普通继承和虚继承的对象内存图是不一样的。我们也可以猜 测到编译器肯定对我们编写的程序做了一些手脚。
        BigBase 菱形最顶层的类,内存布局图没有发生改变。
        Base1 和 Base2 通过虚继承的方式派生自 BigBase, 这两个对象的布局图中可以看 出编译器为我们的对象中增加了一个 vbptr (virtual base pointer),vbptr 指向了一张 表,这张表保存了当前的虚指针相对于虚基类的首地址的偏移量。
        Derived 派生于 Base1 Base2, 继承了两个基类的 vbptr 指针,并调整了 vbptr 与 虚基类的首地址的偏移量。
        由此可知编译器帮我们做了一些幕后工作,使得这种菱形问题在继承时候能只继承 一份数据,并且也解决了二义性的问题。现在模型就变成了 Base1 Base2 Derived 三个类对象共享了一份 BigBase 数据。
        当使用虚继承时,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对 象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的)。即使 共享虚基类,但是必须要有一个类来完成基类的初始化(因为所有的对象都必须被 初始化,哪怕是默认的),同时还不能够重复进行初始化,那到底谁应该负责完成初始化呢?C++标准中选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),但是虚基类的初始化是由最后的子类完成,其他的初始化语句都不会调用。
class BigBase{
public:
    BigBase(int x){mParam = x;}
    void func(){cout << "BigBase::func" << endl;}
public:
    int mParam;
};
class Base1 : virtual public BigBase{
public:
    Base1() :BigBase(10){} //不调用 BigBase 构造
};
class Base2 : virtual public BigBase{
public:
    Base2() :BigBase(10){} //不调用 BigBase 构造
};
class Derived : public Base1, public Base2{
public:
    Derived() :BigBase(10){} //调用 BigBase 构造
};
//每一次继承子类中都必须书写初始化语句
int main(){
    Derived derived;
    return EXIT_SUCCESS;
}
注意:
        虚继承只能解决具备公共祖先的多继承所带来的二义性问题,不能解决没有公共 祖先的多继承的. 工程开发中真正意义上的多继承是几乎不被使用,因为多重继承带来的代码复杂性 远多于其带来的便利,多重继承对代码维护性上的影响是灾难性的,在设计方法上, 任何多继承都可以用单继承代替。
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值