精华内容
下载资源
问答
  • C++继承关系

    千次阅读 2016-11-08 12:50:15
    C++继承关系 一、继承概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员保持原有特性的基础上进行扩展,增加功能。这样产生新的,称派生。继承呈现了面向对象...

                            C++中的继承关系

    一、继承概念

    继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。

    简单的说,如果用继承关系来描述手机的发展的话,就如下图所示的关系:


        上图描述的是某手机的ABC三个版本,后一个版本总是在保持原有版本功能的情况下增加了自己新的功能。

    下面我们来说说继承在C++中的作用:

    通过继承联系在一起的类构成一种层次关系,通常在层次关系的根部有一个基类,其他类则直接或间接地从基类继承而来,这些继承得到的类成为派生类,基类负责定义在层次关系中所有类共同拥有的成员,而每个派生类定义各自特有的成员。注意,基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此。

    二、继承的定义格式

    class  DeriveClassName : acess-lable  BaseClassName

            派生类类名    继承权限    基类(父类)名

    继承权限:指三种继承关系,public(公有继承)、protected(保护继承)、    private(私有继承)。

    三、继承关系

    在继承中,根据继承权限的不同,基类成员的访问权限会在派生类中发生适当的改变,下面我们对三种不同访问权限对基类成员在派生类中的访问权限的影响。

    继承权限为public时:

    #include<iostream>

    using namespace std;

    class Base

    {

    public:

    Base()

    {

    cout << "Base()" << endl;

    }

    virtual ~Base()

    {

    cout << "~Base()" << endl;

    }

    void showBase()

    {

    cout << "_pub=" << _pub << endl;

    }

    public:

    int _pub;

    protected:

    int _pro;

    private:

    int _pri;

    };

    class Derive :public Base

    {

    public:

    Derive()

    {

    cout << "Derive()" << endl;

    }

    void showDerive()

    {

    cout << "_D_pub=" << _D_pub << endl;

    }

    virtual ~Derive()

    {

    cout << "~Derive()" << endl;

    }

    void FunTest1()

    {

    Derive d1;

    d1._pro = 10;   //可以访问

    d1._pub = 11;   //可以访问

    d1._pri = 12      //不能访问

    }

    public:

    int _D_pub;

    protected:

    int _D_pro;

    private:

    int _D_pri;

    };

    void FunTest2()

    {

    Derive d1;

    d1._pro = 10;   //不能访问

    d1._pub = 11;   //可以访问

    d1._pri = 12;   //不能访问

    }

    分析

      这了给出了一个基类Base类和一个派生类Dierived类,我们来简单分析一下这两个类


    这个函数是上面派生类中的一个测试函数,我们可以看到,在派生类中,基类的公有成员和保护成员可以访问,但私有成员是不能被访问的。

     

    FunTest2函数是类外的一个测试函数,可以看到,在类外通过创建的派生类对象可以对基类中的公有成员进行访问,但保护成员和私有成员都不能被访问。

    继承权限为protected时:

    #include<iostream>

    using namespace std;

    class Base

    {

    public:

    Base()

    {

    cout << "Base()" << endl;

    }

    virtual ~Base()

    {

    cout << "~Base()" << endl;

    }

    void showBase()

    {

    cout << "_pub=" << _pub << endl;

    }

    public:

    int _pub;

    protected:

    int _pro;

    private:

    int _pri;

    };

    class Derive :protected Base

    {

    public:

    Derive()

    {

    cout << "Derive()" << endl;

    }

    void showDerive()

    {

    cout << "_D_pub=" << _D_pub << endl;

    }

    virtual ~Derive()

    {

    cout << "~Derive()" << endl;

    }

    void FunTest1()

    {

    Derive d1;

    d1._pro = 10;   //可以访问

    d1._pub = 11;   //可以访问

    d1._pri = 12;     //不能访问

    }

    public:

    int _D_pub;

    protected:

    int _D_pro;

    private:

    int _D_pri;

    };

    class D :public Derive

    {

    public:

    D()

    {}

    virtual ~D()

    {}

    void FunTest3()

    {

    D d;

    d._pro = 10;   //可以访问

    d._pub = 11;   //可以访问

    d._pri = 12;     //不能访问

    }

    private:

    int data;

    };

    分析:

        

        从截图中我们可以看见,派生类中的FunTest1函数中创建的对象可以对基类中的公有和保护成员变量进行访问,但不能对私有成员变量进行访问。

     

    FunTest3是派生类D中的一个函数,D是Deriver的一个派生类,可以看出,在D中,对象对基类Base中的公有、保护类型成员可以访问,而私有成员不能访问。

     

    在类外的测试函数FunTest2中,Base基类中的所有成员都不能被D类的对象进行访问。

    由此可以看出,Base基类中的成员变量在Derive继承下来后都把公有访问权限改成保护类型。

    继承权限为private时:

    #include<iostream>

    using namespace std;

    class Base

    {

    public:

    Base()

    {

    cout << "Base()" << endl;

    }

    virtual ~Base()

    {

    cout << "~Base()" << endl;

    }

    void showBase()

    {

    cout << "_pub=" << _pub << endl;

    }

    public:

    int _pub;

    protected:

    int _pro;

    private:

    int _pri;

    };

    class Derive :private Base

    {

    public:

    Derive()

    {

    cout << "Derive()" << endl;

    }

    void showDerive()

    {

    cout << "_D_pub=" << _D_pub << endl;

    }

    virtual ~Derive()

    {

    cout << "~Derive()" << endl;

    }

    public:

    int _D_pub;

    protected:

    int _D_pro;

    private:

    int _D_pri;

    };

    class D :public Derive

    {

    public:

    D()

    {}

    virtual ~D()

    {}

    void FunTest3()

    {

    D d;

    d._pro = 10;   //不能访问

    d._pub = 11;   //不能访问

    d._pri = 12;     //不能访问

    }

    private:

    int data;

    };

    void FunTest2()

    {

    Derive d1;

    d1._pro = 10;   //不能访问

    d1._pub = 11;   //不能访问

    d1._pri = 12;   //不能访问

    }

    分析:

     

    FunTest3是派生类D中的一个函数,D是Deriver的一个派生类,可以看出,在D中,对象对基类Base中的公有、保护和私有类型也都不可以访问。由此可以看出,Base基类中的成员变量在Derive继承下来后都把访问权限改成了私有;类型。

    总结:

    通过对三种继承权限不同情况的分析,我们可以得出下列结论:


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

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

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

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

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

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

    四、派生类的默认成员函数

    类的六个默认的成员函数,他们分别是构造函数、拷贝构造函数、析构函数、赋值操作符重载、取地址操作符重载、const修饰的取地址操作符重载。

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

     

    继承关系中构造函数的调用顺序

    首先我们先来看一段代码,看看编译器的运行结果:

    #include<iostream>

    using namespace std;

    class Base

    {

    public:

    Base()

    {

    cout << "Base()" << endl;

    }

    ~Base()

    {

    cout << "~Base()" << endl;

    }

    void showBase()

    {

    cout << "_pub=" << _pub << endl;

    }

    public:

    int _pub;

    protected:

    int _pro;

    private:

    int _pri;

    };

    class Derive :public Base

    {

    public:

    Derive()

    {

    cout << "Derive()" << endl;

    }

    void showDerive()

    {

    cout << "_D_pub=" << _D_pub << endl;

    }

    ~Derive()

    {

    cout << "~Derive()" << endl;

    }

     

    public:

    int _D_pub;

    protected:

    int _D_pro;

    private:

    int _D_pri;

    };

    void FunTest()

    {

    Derive d;

    d._pub = 10;

    }

    int main()

    {

    FunTest();

    system("pause");

    return 0;

    }

    运行结果:

     

    从运行结果看,是先调用基类的构造函数,然后调用派生类的构造函数,再调用派生类的析构函数,最后调用基类的析构函数。可实际上编译器是不是按照这个顺序呢?我们来看看此程序的汇编代码一查究竟。

     

     

     

     

        从截图中可以看出来,在地址为37D0处调用了Derive的构造函数,而在地址在3815处才调用了Base类的构造函数,具体的大家也可以把程序复制到自己的编译器上调试一步一步跟一下。

    所以说编译器实际上是先调用派生类的构造函数的,但编译器是在什么时候调用的呢?其实编译器是先进入派生类的构造函,然后在初始化列表处调用了基类的构造函数,然后再回来执行自己的函数体。构造函数说清楚了,析构函数的调用顺序似乎还是很模糊,编译器为什么会先调用派生类的析构函数呢?这个编译器是根据生命周期的长短来调用的,派生类中构造函数生命周期比基类的生命周期短,所以就先调用派生类的析构函数。

    【说明】

    1、基类没有缺省构造函数,派生类必须要在初始化列表中显式给出基类名和参数列表。

    2、基类没有定义构造函数,则派生类也可以不用定义,全部使用缺省构造函数。

    3、基类定义了带有形参表构造函数,派生类就一定定义构造函数。

    五、继承体系中的作用域

    1. 在继承体系中基类和派生类是两个不同作用域。

    2. 子类和父类中有同名成员,子类成员将屏蔽父类对成员的直接访问。(在子类成员函数中,可以使用 基类::基类成员 访问)--隐藏--重定义

          验证:

       class Base

    {

    public:

    int _Data;

    };

    class Derive :public Base

    {

    public:

    int _Data;

    };

    void FunTest()

    {

    Derive d;

    d._Data = 10;

    }

    int main()

    {

    FunTest();

    system("pause");

    return 0;

    }

         

        从截图中看出,只改变了派生类中成员的值,这就是由于基类和派生类中使用了同名的成员变量,而派生类中的成员变量对基类中的同名成员变量形成了覆盖/重定义。

    3. 注意在实际中在继承体系里面最好不要定义同名的成员。

    六、继承与转换--赋值兼容规则--public继承

    1. 子类对象可以赋值给父类对象(切割/切片)。

    2. 父类对象不能赋值给子类对象。

          验证1,2

         #include<iostream>

    using namespace std;

    class Base

    {

    public:

    int _pub;

    protected:

    int _pro;

    private:

    int _pri;

    };

    class Derive :public Base

    {

    public:

    int _D_pub;

    protected:

    int _D_pro;

    private:

    int _D_pri;

    };

    void FunTest()

    {

    Base b;

    Derive d;

    b = d;    //能赋值

    d = b;    //不能赋值

    }

    int main()

    {

    FunTest();

    system("pause");

    return 0;

    }

     

        我们可以看到,编译器会在第二次赋值报错,这说明子类对象可以赋值给父类对象,而父类对象不能赋值给子类对象。其实这里面的原因很简单,我们前面说过,public继承是保持is-a的原则,每个子类对象也是一个父类对象,但由于子类对象添加了新的功能,所以,一个父类对象并不是一个子类对象,所以父类对象不能给子类对象赋值。

    3. 父类的指针/引用可以指向子类对象

    4. 子类的指针/引用不能指向父类对象(可以通过强制类型转换完成)

    下面我们用一段代码来证明:

       验证3,4

    在这里我首先提示一下,34的验证尽量不要放在同一段代码中,我看的一些文章中把这四个放在一块证明,不小心的话会遇到一些不必要的麻烦,因为前两个的赋值如果没屏蔽的话会对后面两个造成影响。

    #include<iostream>

    using namespace std;

    class Base

    {

    public:

    int _pub;

    protected:

    int _pro;

    private:

    int _pri;

    };

    class Derive :public Base

    {

    public:

    int _D_pub;

    protected:

    int _D_pro;

    private:

    int _D_pri;

    };

    void FunTest()

    {

    Base b;

    Derive d;

    Base* pb1 = &b;

    pb1 = &d;

    Derive* d1 = &d;

    d1 = &b;     

    d1 = (Derive*)&b;

    }

    int main()

    {

    FunTest();

    system("pause");

    return 0;

    }

     

    我们可以看到,父类的指针可以指向子类的对象,而子类的对象不可以指向父类的对象,编译器会报错,但最后一句中,我们可以通过强制类型转换的方式实现,但建议大家尽量别使用,因为那种方式可能造成程序崩溃,而这种错误是很难找出来的。

    七、友元于继承

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

       验证:

      #include<iostream>

    using namespace std;

    class Base

    {

    friend void show(Base &b,Derive d);

    public:

    int _pub;

    protected:

    int _pro;

    private:

    int _pri;

    };

    class Derive :public Base

    {

    public:

    int _D_pub;

    protected:

    int _D_pro;

    private:

    int _D_pri;

    };

    void show(Base b,Derive d)

    {

    cout << b._pub << endl;

    cout << b._pro << endl;   //无法访问protected成员,因为友元关系未继承

    cout << d._D_pub << endl;

    }

    void FunTest1()

    {

    Base b;

    Derive d;

    show(b, d);

    }

    int main()

    {

    FunTest1();

    system("pause");

    return 0;

    }

     

         正如截图中显示的那样,对于基类的保护类成员是无法访问的,原因就是友元关系未继承。但是下面这种情况就要注意了,输出运算符的重载,这个例子并不能说明友元被继承了。这个是因为参数发生类型转换,派生类对象赋值给基类对象。

    #include<iostream>

    using namespace std;

    class Base

    {

    friend ostream& operator<<(ostream& _cout,Base& b)

    {

    _cout << b._pri << endl;

    return _cout;

    }

    public:

    int _pub;

    protected:

    int _pro;

    private:

    int _pri;

    };

    class Derive :public Base

    {

    public:

    int _D_pub;

    protected:

    int _D_pro;

    private:

    int _D_pri;

    };

    void FunTest1()

    {

    Base b;

    Derive d;

    cout << b << endl;

    cout << d << endl;

    }

    int main()

    {

    FunTest1();

    system("pause");

    return 0;

    }

    八、继承与静态成员

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

    九、单继承&多继承&菱形继承

      

        单继承:一个子类只有一个直接父类时称为单继承

         

     

         多继承:一个子类有两个或者两个以上父类的继承关系称为多继承

     


     

    菱形继承:

     


    菱形继承的对象模型:

     

      从对象模型中我们可以发现,菱形继承具有二义性和数据冗余的问题,图中显示Base被存了两份,那么要解决这个问题,C++中引入了我们下面要讨论的一种继承方式——虚继承。

    十、虚继承

        1. 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余&浪费空间的问题。

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

    #include<iostream>

    using namespace std;

    class Base

    {

    public:

    int data1;

    };

    class Derive :virtual public Base

    {

    public:

    int data2;

    };

    class E :virtual public Base

    {

    public:

    int data4;

    };

    class F :public Derive,public E

    {

    public:

    int data5;

    };

     

    void FunTest1()

    {

    cout << sizeof(Derive) << endl;

    }

    int main()

    {

    FunTest1();

    system("pause");

    return 0;

    }

     

    上述代码计算的是Derive类的大小,结果是12,我们不禁就会想,大小=自己的成员变量大小+基类成员变量大小,为什么会多出来个四呢?其实是多存放了一个指针。而这个指针是干什么用的呢?我们通过代码验证一下:

    #include<iostream>

    using namespace std;

    class Base

    {

    public:

    int data1;

    };

    class Derive :virtual public  Base

    {

    public:

    int data2;

    };

    class E :virtual public  Base

    {

    public:

    int data3;

    };

    class F :public  Derive,public E

    {

    public:

    int data4;

    };

    void FunTest()

    {

     

    F f;

    f.data1 = 1;

    f.data2 = 2;

    f.data3 = 3;

    f.data4 = 4;

    }

    int main()

    {

     

    FunTest();

    system("pause");

    return 0;

    }

     

    上图为我们取到的对象f的地址,从内存可以看出第一个存了一个指针,下面才是赋值,那我们再看一下这个指针指向了哪:

     

     

     

    我们看到里面有个0,有个20,这正好是到Base类的偏移量,所以,这个表中存的是此类到基类的偏移量,而不用把Base类存两遍。解决了冗余问题。

     

       从内存中f各个变量的存储我们不难看出虚拟继承的模型图,如下图所示:

     

     

    展开全文
  • 继承继承 多重继承 菱形继承继承与虚继承继承与虚继承 多重继承与虚继承 菱形继承与虚继承 虚基类表指针 虚基类表 不同继承方式成员对面模型 内存分布情况 赋值顺序

    概要


    本文讲述在VS2012环境下,采用代码和图结合的方法,分析C++代码中不同继承方式的对象模型,以及从汇编角度分析虚拟继承编译器生成的虚基类表里的内容,不涉及虚函数。


    继承分类:

    1.单继承

    一个子类只有一个直接父类

    // 单继承  工人类 继承 人类
    class Person
    {
    	//...
    };
    class Worker : public Person
    {
    	//...
    };


    2.多继承

    一个子类有两个以上直接父类

    // 多继承 农民工类分别继承 工人类 和 农民类
    class Worker
    {
    	// ...
    };
    class Farmer
    {
    	// ...
    };
    class MigrantWorker : public Worker, public Farmer
    {
    	// ..
    };
    




    3.多重继承

    继承关系大于两层

    // 多继承 工人类继承 Person类 农民工类继承Worker类
    class Person
    {
    	// ...
    };
    class Worker : Person
    {
    	// ...
    };
    class MigrantWorker : public Worker
    {
    	// ..
    };




    4.菱形继承

    两个子类继承同一个父类,而又有子类同时继承这两个子类。

    // 菱形继承也叫钻石继承 
    // 工人类继承人类, 农民继承人类, 农民工类继承工人类和农民类
    class Person
    {
    	//...
    };
    class Worker : public Person
    {
    	//...
    };
    class Framer : public Person
    {
    	//...
    };
    class MigrantWorker : public Worker,public Framer
    {
    	//...
    };





    派生类内存分布情况:
    1.单继承


    2.多继承


    我们改变Worker类 和 Farmer类的继承顺序再观察:


    3.多重继承


    4.菱形继承


    在MigrantWorker的对象中有两份Person的成员,导致菱形继承存在二义性和数据冗余的问题,而虚拟继承可以很好地解决菱形继承的二义性和数据冗余问题。


    虚拟继承:


    虚拟继承与单继承:

    // 工人类虚继承 Person类
    class Person
    {
    public:
    	int dataPerson;
    };
    class Worker : virtual public Person
    {
    public:
    	int dataWorker;
    };
    
    int main()
    {
    	Worker w;
    	w.dataPerson = 1;
    	w.dataWorker = 2;
    	return 0;
    }
    


    内存分布图情况:


    我们可以看到在对象w的成员数据之前多了4个字节,下面我们来分析一下这四个字节是什么.

    先打开看一看里面存的是什么:


    里面存储了0和8这又是什么呢?

    首先进入派生类的构造函数:


    进入构造函数内:


    然后继续执行下去直到ret返回后:


    然后继续执行:


    然后下一步,mov dword ptr w[ecx],1 即在派生类对象首地址往后偏移ECX(8)个字节然后存入1,而派生类则是利用ebp-XX 的方式栈帧赋值。至此我们已经搞清楚“表”里的08存储的是什么,即:基类对象成员在派生类对象中与派生类对象首地址之间的偏移量。00到底是什么请继续看下面的分析。


    虚拟继承与多继承:

    (1)先继承的类为虚基类时

    内存分布图:


    赋值情况:


    (2)后继承的类为虚基类时

    内存分布图:


    赋值情况:


    (3)继承的两个类都为虚基类时

    内存分布:


    赋值情况:


    虚拟继承与多重继承:

    (1)最上层类是虚基类时:

    内存分布图:


    对比多继承中表的值我们可以发现,表中前四个字节到底是什么:派生类对象首地址与派生类中存储的的表的指针之间的偏移量。


    在这里表的指针存储在对象的前四个字节,相对派生类对象地址偏移量为0,所以表中前四个字节为0.

    而在多重继承中的前两种情况中,表指针存储在派生类对象向后偏移4个字节出,也就是表的指针往前偏移4个字节,即-4,在内存中-4存储的是补码也就是我们说看到的fc ff ff  ff。

    至此我们已经弄明白了表中到底存储的是什么,也要给这个表(这个地址)一个名字,它叫虚基类表指针。在这里不深究该表的来源,暂且理解为编译器的功劳。

    赋值情况:


    经过前面的观察和对比我们发现,赋值情况只有虚基类才会取偏移地址赋值,虚基类表中存放的内容也分析清楚,对象内存分布也分析清楚,此后不再赘述,只放上成员内存分布图。

    (2)中间层是虚基类时:

    内存分布图:


    菱形继承

    内存分布:


    分布顺序分析:


    进Worker构造函数:

    4

    Framer构造函数:


    赋值情况:



    虚继承虽然解决的菱形继承里子类对象包含对分父类对象的数据冗余浪费空间的问题,单因为要给对象分配内存去存储虚表,也带来了性能上的损耗。


    _________________________________________________________END______________________________________________________________________


    展开全文
  • java继承关系

    千次阅读 2013-10-14 17:24:52
     一个子类可以有多个直接父类(JAVA不支持多继承),不直接支持多继承是因为多个父类可能有相同的成员,会产生不确定性。 (3)JAVA支持多层继承(多重继承):C继承B,B继承A。 (4)当本的成员与局部...
                                       继承
    (1)单继承:
                  一个子类只能直接有一个父类
    (2)多继承:
            一个子类可以有多个直接父类(JAVA中不支持多继承),不直接支持多继承是因为多个父类中可能有相同的成员,会产生不确定性。
    (3)JAVA中支持多层继承(多重继承):C继承B,B继承A。
    (4)当本类的成员与局部变量同名时用this区分。{this代表父类}
    (5)当子类和父类中的成员变量同名时用super区分父类。{super代表父类}
    (6)this和super的区别:
                this:代表一个本类对象的引用。
                super:代表一个父类空间。
    (7)当子类和父类中出现成员函数一模一样(返回值,参数列表,函数名都相同)的情况,会运行子类的函数,这种现象称为覆盖。
    (8)子类方法覆盖父类方法时,子类权限必须要大于等于父类权限。
    (9)什么时候使用覆盖?
                当对一个类进行子类的扩展时,子类需要保留父的声明,但是要定义该功能特有内容时就使用覆盖方法完成。
    (10)子类的初始化过程:
                子类中所有的构造函数都会默认访问父类中的空参数的构造函数。
    (11)子类在使用构造函数时必须访问父类中的空参数构造函数,如果父类中没有定义空参数的构造函数,那么子类必须使用super()语句明确要调用父类中的哪个构造函数。且super()语句必须定义在子类构造函数的第一行。
    (12)子类构造函数中如果使用this调用了本类构造函数时,那么super就没有了,因为super和this都只能定义在第一行。所以只能有一个,但是可以保证的是,子类中肯定会有其他的构造函数访问父类的构造函数。

    (13)final:
            *final是一个修饰符,可以修饰类,方法,变量。
            *final修饰的类不可以被继承。
            *final修饰的方法不可以被覆盖。
            *final修饰的变量时一个常量,只能赋值一次。
            *














    展开全文
  • Python中类的多继承

    千次阅读 2019-05-21 18:02:49
    文章目录Python中类的多继承继承Python多继承实现多继承的缺点Mixin*思路1**思路2**思路3**思路4*Mixin Python中类的多继承 Python2.2之前是没有共同的祖先的,之后,引入object,它是所有的共同祖先...

    Python中类的多继承

    Python2.2之前类是没有共同的祖先的,之后,引入object类,它是所有类的共同祖先类object。Python2中为了兼容,分为古典类(旧式类)和新式类。Python3中全部都是新式类。新式类都是继承自object的,新式类可以使用super。

    #一下代码在Python2.x中运行
    
    #古典类(旧式类)
    class A:pass
    
    #新式类
    class B(object):pass
    
    print(dir(A))
    print(dir(B))
    print(A.__bases__)
    print(B.__bases__)
    
    #古典类
    a = A()
    print(a.__class__)
    print(type(a)) #<type 'instance'>
    
    #新式类
    b = B()
    print(b.__class__)
    print(type(b))
    

    多继承

    OCP原则:多用“继承”、少修改
    继承的用途:在子类上实现对基类的增强、实现多态
    多态: 在面向对象中,父类、子类通过继承联系在一起,如果可以通过一套方法,就可以实现不同表现,就是多态。一个类继承自多个类就是多继承,它将具有多个类的特征。

    • 多继承弊端
    1. 多继承很好的模拟了世界,因为事物很少是单一继承,但是舍弃简单,必然引入复杂性,带来了冲突。
    2. 多继承的实现会导致代码设计的复杂度增加,所以有些高级编程语言舍弃了类的多继承。
    3. C++支持多继承;Java舍弃了多继承。
      • Java中,一个类可以实现多个接口,一个接口也可以继承多个接口。Java的接口很纯粹,只是方法的声明,继承者 必须实现这些方法,就具有了这些能力,就能干什么。
    4. 多继承可能会带来二义性
    5. 实现多继承的语言,要解决二义性,深度优先或者广度优先。

    Python多继承实现

    class ClassName(基类列表):
        类体
    

    class3_001

    左图是多继承(菱形继承),右图是单一继承。
    多继承带来路径选择问题,究竟继承哪个父类的特征呢?
    Python使用MRO(method resolution order方法解析顺序)解决基类搜索顺序问题。

    • 历史原因,MRO有三个搜索算法:

      1. 经典算法:按照定义从左到右,深度优先策略。2.2版本之前 左图的MRO是MyClass,D,B,A,C,A
      2. 新式类算法:是经典算法的升级,深度优先,重复的只保留最后一个。2.2版本 左图的MRO是MyClass,D,B,C,A,object
      3. C3算法:在类被创建出来的时候,就计算出一个MRO有序列表。2.3之后,Python3唯一支持的算法 左图中的MRO是MyClass,D,B,C,A,object的列表
        • C3算法解决多继承的二义性
    • 经典算法有很大的问题,如果C中有覆盖A的方法,就不会访问到了,因为先访问A(深度优先)。

    • 新式类算法,依然采用了深度优先,解决了重复问题,但是同经典算法一样,没有解决继承的单调性。

    • C3算法,解决了继承的单调性,它阻止创建之前版本产生二义性的代码。求得的MRO本质是为了线性化,且确定 了顺序。

      1. 单调性:假设有A、B、C三个类,C的mro是[C, A, B],那么C的子类的mro中,A、B的顺序一致就是单调的。

    多继承的缺点

    当类很多,继承复杂的情况下,继承路径太多,很难说清什么样的继承路径。Python语法是允许多继承,但Python代码是解释执行,只有执行到的时候,才发现错误。团队协作开发,如果引入多继承,那代码很有可能不可控。不管编程语言是否支持多继承,都应当避免多继承。Python的面向对象,我们看到的太灵活了,太开放了,所以要团队守规矩。

    Mixin

    类有下面的继承关系
    class3_002
    文档Document类是其他所有文档类的抽象基类;Word、Pdf类是Document的子类。
    需求:为Document子类提供打印能力

    思路1

    • 在Document中提供print方法
      假设已经有了下面3个类
    class Document:
        def __init__(self,content):
            self.content = content
    
        def print(self): #抽象方法
            raise NotImplementedError()
    
    class Word(Document): pass #其他功能略去
    class Pdf(Document): pass #其他功能略去
    
    • 基类提供的方法可以不具体实现,因为它未必适合子类的打印,子类中需要覆盖重写
    • 基类中只定义,不实现的方法,称为“抽象方法“。
      1. 在Python中,如果采用这种方式定义的抽象方法,子类可以不实 现,直到子类使用该方法的时候才报错。

    print算是一种能力 —— 打印功能,不是所有的Document的子类都需要的,所有,从这个角度出发,上面的基类 Document设计有点问题。

    思路2

    • 在需要打印的子类上增加打印功能
      如果在现有子类Word或Pdf上直接增加,虽然可以,却违反了OCP的原则,所以可以继承后增加打印功能。因此有 下图
      class3_003
    class Document: #第三方库,不允许修改
        def __init__(self,content):
            self.content = content
    class Word(Document): pass #第三方库,不允许修改
    class Pdf(Document): pass #第三方库,不允许修改
    
    #单继承
    class PrintableWord(Word):
        def print(self):
            print(self.content,self.__class__.__name__)
    
    print(PrintableWord.__dict__)
    print(PrintableWord.mro())
    
    pw = PrintableWord("我是word打印")
    pw.print()
    

    class3_004
    看似不错,如果需要还要提供其他能力,如何继承?
    例如,如果该类用于网络,还应具备序列化的能力,在类上就应该实现序列化。
    可序列化还可能分为使用pickle、json、messagepack等。
    这个时候发现,为了增加一种能力,就要增加一次继承,类可能太多了,继承的方式不是很好了。
    功能太多,A类需要某几样功能,B类需要另几样功能,它们需要的是多个功能的自由组合,继承实现很繁琐。

    思路3

    • 装饰器。用装饰器增强一个类,把功能给类附加上去,那个类需要,就装饰它。
    class Document: #第三方库,不允许修改
        def __init__(self,content):
            self.content = content
    class Word(Document): pass #第三方库,不允许修改
    class Pdf(Document): pass #第三方库,不允许修改
    
    #类装饰器,为类动态增加打印功能
    def printtable(cls):
        def _print(self):
            print(self.content,"---装饰器")
        cls.print = _print
        return cls
    
    @printtable
    class PrintableWord(Word):pass
    
    @printtable
    class PrintablePdf(Pdf):pass
    
    print(PrintableWord.__dict__)
    print(PrintableWord.mro())
    
    pw = PrintableWord("我是word打印")
    pw.print()
    
    pf = PrintablePdf("我是pdf打印")
    pf.print()
    

    class3_005

    • 优点:
      1. 简单方便,在需要的地方动态增加,直接使用装饰器。
      2. 可以为类灵活的增加功能。

    思路4

    • 使用Mixin类,为类增强功能
    class Document: #第三方库,不允许修改
        def __init__(self,content):
            self.content = content
    class Word(Document): pass #第三方库,不允许修改
    class Pdf(Document): pass #第三方库,不允许修改
    
    class PrintableMixin:
        def print(self):
            print(self.content,"--Mixin类增强功能")
    
    class PrintableWord(PrintableMixin,Word):pass
    
    class PrintablePdf(PrintableMixin,Pdf):pass
    
    print(PrintableWord.__dict__)
    print(PrintableWord.mro())
    
    pw = PrintableWord("我是word打印")
    pw.print()
    
    pf = PrintablePdf("我是pdf打印")
    pf.print()
    

    class3_006
    Mixin就是其它类混合进来,同时带来了类的属性和方法。Mixin类和装饰器效果一样,也没有什么特别的。但是Mixin是类,就可以继承。
    如果在上需代码完成后,再次开发,需要对原有的print功能再次增强,可以重新在写个增强的print功能,再类需要使用这种增强的print功能时,可以继承增强后的print功能。

    class Document: #第三方库,不允许修改
        def __init__(self,content):
            self.content = content
    class Word(Document): pass #第三方库,不允许修改
    class Pdf(Document): pass #第三方库,不允许修改
    
    class PrintableMixin:
        def print(self):
            print(self.content,"--Mixin类增强功能")
    
    class PrintableWord(PrintableMixin,Word):pass
    
    class PrintablePdf(PrintableMixin,Pdf):pass
    
    print(PrintableWord.__dict__)
    print(PrintableWord.mro())
    
    pw = PrintableWord("我是word打印")
    pw.print()
    
    pf = PrintablePdf("我是pdf打印")
    pf.print()
    
    #为print类进行功能增强
    class SuperPrintableMixin(PrintableMixin):
        def print(self):
            print("- "*10,"打印前增强")
            super().print()
            print("- "*10,"打印后增强")
    
    #使用新增强的功能
    class SuperPrintablePdf(SuperPrintableMixin,Pdf):pass
    
    print("")
    print(SuperPrintablePdf.__dict__)
    print(SuperPrintablePdf.mro())
    spp = SuperPrintablePdf("我是super Pdf")
    spp.print()
    

    class3_007

    Mixin类

    • Mixin本质上就是多继承实现的。

    • Mixin体现的是一种组合的设计模式。
      在面向对象的设计中,一个复杂的类,往往需要很多功能,而这些功能有来自不同的类提供,这就需要很多的类组 合在一起。
      从设计模式的角度来说,多组合,少继承。

    • Mixin类的使用原则

      1. Mixin类中不应该显式的出现__init__初始化方法
      2. Mixin类通常不能独立工作,因为它是准备混入别的类中的部分功能实现
      3. Mixin类的祖先类也应是Mixin类
      4. 使用时,Mixin类通常在继承列表的第一个位置
        • 例如: class PrintableWord(PrintableMixin, Word): pass

    Mixin类和装饰器,这两种方式都可以使用,看个人喜好。如果还需要继承就得使用Mixin类的方式。

    展开全文
  • 继承的概念:继承是指之间的继承关系 ,子类继承父类,子类可以将父类的属性方法继承下来,现实生活继承指的是对象与对象的继承关系,程序间的继承是指之间的继承关系继承关系的梳理:A是B(is ...
  • java中类继承详解。

    万次阅读 多人点赞 2018-07-11 21:42:48
     实现继承类被称为子类. 被继承类被称为父类. 父类和子类的关系, 是一种一般和特殊的关系. 例如水果和苹果的关系, 苹果继承了水果, 苹果是水果的子类, 水果是苹果的父类.Java里子类继承父类的语法格式如下:...
  • Java——继承

    千次阅读 2020-01-27 21:38:25
    \quadJava的继承通过extends关键字实现,实现继承类称为子类,被继承类称为父类或基类。父类与子类的关系其实就是一般与特殊的关系,例如水果和苹果的关系,苹果继承了水果,苹果是水果的子类,则苹果是一种特殊...
  • C++ 继承关系

    千次阅读 2019-04-26 23:16:56
    1.继承的概念及定义 1.1继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员保持原有特性的基础上进行扩展,增加功能,这样产生新的,...1.2.2继承关系和访问限定...
  • 基类(父类):在继承关系中处于上层的 派生(子类):在继承关系中处于下层的 class A; class B; class C:public A //C为A的子类,A为C的父类 { }; class D:public A,public B //D为A和B的子类,A和B均为...
  • 面向对象的优势在于的...之间有一种父与子的关系,子类继承父类的属性和方法,称为继承在继承里,子类可以拥有父类的属性(除私有属性之外),也可以拥有父类的方法(除私有方法之外),同时,子类还可以有
  • Java中继承

    万次阅读 多人点赞 2019-06-06 17:25:11
    面向对象编程继承是一个比较重要的概念,继承描述的是事物之间的所属关系,是从已有的类中构建出新的,已有的类称为父类,新的类称为子类,子类具有父类的数据属性和方法,并能扩展新的属性和方法(不能继承...
  • 设计三种继承方式

    千次阅读 2014-07-28 00:13:59
    通过继承能够从已有的派生出新的,而派生类继承了基类的特征,包括方法。正如继承一笔财产要比自己白手起家容易一样,通过继承派生出的通常比设计新要容易得多。下面是可以通过继承完成的一些工作。 ①可以...
  • 和对象之间的关系?封装继承多态?

    千次阅读 2018-03-04 09:19:00
    面向对象的编程思想把对象的属性和行为分别称为对象的属性和方法,是构成对象的两个主要因素;编程,对象的属性存储一些变量里;对象的行为则通过定义方法来实现。三大基本特征:封装,继承,多态。 ...
  • java继承关系

    千次阅读 2017-03-08 12:43:06
    Tips:1)Java只支持单重继承,即每一个有且仅有一个直接父类,一个父类可以产生多个子类  2)子类只能继承父类中非private的成员变量  3)子类的成员变量和父类的成员变量重名时,父类的成员变量将覆盖,而...
  • C++ (多继承和虚继承)

    万次阅读 2018-07-29 16:00:46
    一个有多个基类,这样的继承关系称为多继承; b. 多继承声明语法: class 派生类名: 访问控制符 基类名1,访问控制符 基类名2 { 数据成员和成员函数声明; } class A: public B,public c { } 图示: c. 多个...
  • C++中类成员和类继承的private、protected和public关键字作用。
  • C++ 继承与派生

    千次阅读 2017-05-16 10:20:39
    继承性是面向对象程序设计最重要的特性之一,使软件有了可...所以继承和派生的关系就像小学时把字句和字句的造句一样。有了继承与派生后,就有了父类/基类与子类/派生,C++中将B称为父类/基类,将A称为子类/
  • C#中类继承与多态

    千次阅读 热门讨论 2014-05-16 16:07:03
    为了提高软件模块的可复用性和可扩充性,以便提高软件的开发效率,我们总是希望能够利用前人或...使用继承可以在类之间建立一种相交关系,使得新定义的类继承已有的类的特征和能力,而且可以加入新的特性或者修改已有
  • Java继承详解

    万次阅读 2019-10-20 00:52:45
    Java继承详解继承的基本知识成员访问与继承构造函数与继承super关键字使用super调用超类构造函数使用...Java语言,被继承类被称为超类,继承类被称为子类2。 比如狗类是动物类,牧羊犬类又是狗类。于是我...
  • 识别类之间的关系: 继承关系,组合关系,利用关系,实例化关系继承关系: 超类和子类:当子类继承了另一类的一组属性,此类被称为子类,的到继承的类被称为超类。 重载:派生类定义基类函数的过程称为重载。 ...
  • 抽象与接口之间的继承和实现关系  对于面向对象编程来说,抽象是它的一大特征之一。Java,可以通过两种形式来体现OOP的抽象:接口和抽象。这两者有太多相似的地方,又有太多不同的地方。很多人初学的...
  • JAVA面向对象 继承

    千次阅读 多人点赞 2016-07-26 20:10:40
    本页面更新日期: 2016年07月30日前言 继承是面向对象的三大特征之一.... 实现继承类被称为子类. 被继承类被称为父类. 父类和子类的关系, 是一种一般和特殊的关系. 例如水果和苹果的关系, 苹果继承了水
  • 【C++】C++继承和派生、虚基类

    千次阅读 多人点赞 2018-06-07 18:46:17
    从已有的对象类型出发建立一种新的对象类型,使它部分或全部继承原对象的特点和功能,这是面向对象设计方法的基本特性之一。...定义一个A时,若它使用了一个已定义B的部分或全部成员,则称A继承...
  • Qt面试以及常用类继承关系

    万次阅读 多人点赞 2016-08-21 14:33:27
    他们利用eventLoop从事件队列获取事件之后,将事件转义称为QEvents,分发给相应的QObject来进行处理。这个分发的过程不同的平台有着不同的实现。 继承eventFilter()方法可以对事件进行处理: bool ...
  • 一、接口、抽象继承、实现定义接口 接口是一种比抽象更加抽象的“”。这里给“”加引号是我找不到更好的词来表示,但是我们要明确一点就是,接口本身就不是,从我们不能实例化一个接口就可以看出。如new...
  • 通过继承机制,可以利用已有的数据类型来定义... C++语言,一个派生可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承。  派生的定义格式
  • String能否被继承

    万次阅读 多人点赞 2016-03-31 12:49:16
    不能被继承
  • JavaScript类继承

    千次阅读 2006-05-21 16:20:00
    JavaScript类继承DouglasCrockfordwww.crockford.com翻译 ShiningRay @ www.nirvanastudio.orgAnd you thinkyoure so clever and classless and free--John LennonJavaScript一种没有的,面向对象的语言,它...
  • C++ 继承

    千次阅读 多人点赞 2021-04-26 13:23:12
    继承1 什么是继承1.1 继承的概念1.2 继承的定义1.2.1 定义格式1.2.2 继承关系和访问限定符1.2.3 继承基类成员访问方式的变化2 基类(父类)对象和派生(子类)对象之间的赋值转换3 继承的作用域4 派生(子类)的默认...
  • 第7.6节 Python中类继承机制详述

    千次阅读 多人点赞 2019-06-12 18:41:25
    本章第一节,介绍了面向对象程序设计的三个特征:封装、继承和多态,前面章节重点介绍了封装和多态,由于Python语言是多态语言,对象的类型不再由继承等方式决定,而由实际运行时所表现出的具体行为来决定,因此不...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 215,337
精华内容 86,134
关键字:

在继承关系中被继承的类称为