• C++继承与派生


    2017-06-25 23:00:59

    c++中的继承和派生是面向对象编程中的一个重要内容,通过继承可以实现代码的复用,同时继承也是实现多态性的基础。

    一、c++继承的基本形式

    class 派生类名:继承方式 基类名,继承方式 基类名

    {};

    继承方式主要有三种,public ,private ,protected。

    缺省条件下是private继承,三种中public继承用的最多,不同的继承方式决定了子类中从基类继承过来的成员的访问属性。

    public继承:

    基类的public,protected成员在子类中访问属性不变,子类新增的成员函数可以直接访问,对于基类的private成员依然是基类的私有,子类无法直接进行访问。

    private继承:

    基类的public,protected成员转变为子类的private成员,子类新增的成员函数可以进行访问,对于基类的private成员依然是基类的私有,子类无法直接进行访问。

    protected继承:

    基类的public,protected成员转变为子类的protected成员,子类新增的成员函数可以进行访问,对于基类的private成员依然是基类的私有,子类无法直接进行访问。

    private继承和protected继承的区别是private继承的子类如果继续被继承,那么这些从其分类继承得到的数据将不会被其子类继承,而protected则是可以的。

    二、c++继承的注意事项

    (1)父类的构造函数和析构函数是不会被继承的,需要重写派生类的构造函数和析构函数。

    派生类的成员数据中有来自父类的成员数据,因此在写派生类的构造函数的时候需要调用其父类的构造函数。

    如果派生类的成员中有成员对象,那么也需要用成员对象名来进行初始化。

    这两种都是用初始化表来进行初始化工作的。

    (2)派生类构造函数、析构函数的调用顺序

    • 构造函数中首先先调用各个直接基类的构造函数,之后再调用成员对象的构造函数,最后才是新增成员的初始化。
    • 对于多继承有多个基类,那么其构造函数的调用顺序是按被继承时的声明顺序,从左到右一次调用,与初始化表的顺序无关
    • 对于成员对象的初始化也是一样,与他们的声明顺序有关,和构造函数中的初始化表的顺序无关。
    • 如果没有进行显示的调用,那么会调用其默认的构造函数
    • 子类的拷贝构造函数也要为各个直接基类的拷贝构造函数传递参数
    //若 B 类是 C 类的基类,则 C 类的自定义拷贝构造函数可定义为:
    C:: C (  C  &c1  )  : B ( c1 )  {   函数体   }
    • 在派生类的析构函数中不会显示调用基类的析构函数,系统会自动隐式调用,调用顺序和构造函数的调用顺序正好相反。先构造的后析构。
    class A
    {
        int x1,y1;
    public:
        //构造函数
        A(int a=0,int b=0)
        {
            x1=a;
            y1=b;
            cout<<"调用A的构造函数了
    ";
        }
    
        //拷贝构造函数
        A(A& a)
        {
            x1=a.x1;
            y1=a.y1;
            cout<<"调用A的拷贝构造函数了"<<endl;
        }
    
        //展示内容
        void show()
        {
            cout<<x1<<" "<<y1<<endl;
        }
    
        //析构函数,析构函数无参数且无返回值
        ~A()
        {
            cout<<"调用A的析构函数了"<<endl;
        }
    };
    
    class B
    {
        int x2,y2;
    public:
        //构造函数
        B(int a=0,int b=0)
        {
            x2=a;
            y2=b;
            cout<<"调用B的构造函数了
    ";
        }
    
        //拷贝构造函数
        B(B& b)
        {
            x2=b.x2;
            y2=b.y2;
            cout<<"调用B的拷贝构造函数了"<<endl;
        }
    
        //展示内容
        void show()
        {
            cout<<x2<<" "<<y2<<endl;
        }
    
        //析构函数,析构函数无参数且无返回值
        ~B()
        {
            cout<<"调用B的析构函数了"<<endl;
        }
    };
    
    class C:public A,public B
    {
        int x3,y3;
    public:
        //形参写成int x3,int y3,也就是和成员函数重名的话,内部的数据会出错,所以,不要重名,改成a,b即可
        C(int x1,int y1,int x2,int y2,int a,int b):A(x1,y1),B(x2,y2)
        {
            x3=a;
            y3=b;
            cout<<"调用C的构造函数了"<<endl;
        }
    
        C(C& c):A(c),B(c)
        {
            x3=c.x3;
            y3=c.y3;
            cout<<"调用C的拷贝构造函数了"<<endl;
        }
    
        void show()
        {
            A::show();
            B::show();
            cout<<x3<<" "<<y3<<endl;
        }
    
        ~C()
        {
            cout<<"调用C的析构函数了"<<endl;
        }
    };
    
    
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        C c1(1,2,3,4,5,6);
        c1.show();
        C c2(c1);
        c2.show();
        return 0;
    }

    (3)赋值兼容规则

    规定了基类和其子类之间的赋值规则。

    对于公有派生,可以将派生类对象赋值给其基类,反之则是不被允许的。

    具体体现为:

    • 基类对象=派生类对象
    • &基类对象的别名=派生类对象
    • *基类对象的指针=&派生类对象的地址

    对于这种赋值,基类只能访问子类中从基类继承过来的那部分成员。

    (4)继承过程中的二义性问题

    继承过程中可能会出现二义性的问题。主要有两种形式的二义性。

    一是在多继承中,两个父类中有同名的变量,这时如果是公有继承,那么子类中就会出现两个同名变量。解决方法有两种,一是用域作用符进行限定,二是使用同名隐藏。

    二是在多继承中,两个父类的父类是同一个类,这时就需要使用虚基类的手段来进行解决。虚基类可以保证在间接继承的时候只保留一份共同基类的成员数据。

    虚基类的定义:

    class 派生类:virtual 继承方式 基类名

    {};

    • 由虚基类直接或者间接派生出来的子类都必须列出对虚基类的构造函数,若未显示列出则调用默认构造函数
    • 最终建立对象的类称为最终派生类,只在最终派生类中调用基类的构造函数,而在基类的直接派生类中不掉用基类的构造函数,这样就避免了基类成员的重复继承问题
    class B
    {
        int b;
    public:
        B(int x)
        {
            b=x;
            cout<<"调用了B的构造函数"<<endl;
        }
    
        ~B()
        {
            cout<<"调用了B的析构函数"<<endl;
        }
    };
    
    class B1:virtual public B
    {
        int b1;
    public:
        B1(int x,int y):B(x)
        {
            b1=y;
            cout<<"调用B1的构造函数了"<<endl;
        }
    
        ~B1()
        {
            cout<<"调用B1的析构函数了"<<endl;
        }
    };
    
    class B2:virtual public B
    {
        int b2;
    public:
        B2(int x,int y):B(x)
        {
            b2=y;
            cout<<"调用B2的构造函数了"<<endl;
        }
    
        ~B2()
        {
            cout<<"调用B2的析构函数了"<<endl;
        }
    };
    
    
    class C:public B1,public B2
    {
        int c;
    public:
        C(int a,int b,int c,int d):B1(a,b),B2(a,c),B(a)
        {
            c=d;
            cout<<"调用C的构造函数了"<<endl;
        }
    
        ~C()
        {
            cout<<"调用C的析构函数了"<<endl;
        }
    };
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        C c1(1,2,3,4);
        return 0;
    }

     

  • 相关阅读:
    挺好用的SQLSERVER数据库自动备份工具SQLBackupAndFTP(功能全面)
    SQLSERVER中的鬼影索引
    SQLSERVER NULL和空字符串的区别 使用NULL是否节省空间
    SQLSERVER中NULL位图的作用
    SQLSERVER到底能识别多少个逻辑CPU?
    不正常关机引起的数据库置疑
    如何在大型的并且有表分区的数据库中进行DBCC CHECKDB操作
    索引视图是否物理存储在数据库中以及使用索引视图的一些见解
    Oracle非重要文件恢复,redo、暂时文件、索引文件、password文件
    最大匹配、最小顶点覆盖、最大独立集、最小路径覆盖(转)
  • 原文地址:https://www.cnblogs.com/hyserendipity/p/7078423.html
Copyright © 2020-2023  润新知