精华内容
下载资源
问答
  • Python 子类继承多个父类属性可能存在的误区正确继承方法 可能存在的误区 python允许子类同时继承多个父类。但是在子类初始化父类时,单继承和多继承稍有不同。使用单继承的初始化方法会导致父类属性无法全部继承。 ...

    Python 子类继承多个父类属性

    可能存在的误区

    python允许子类同时继承多个父类。但是在子类初始化父类时,单继承和多继承稍有不同。使用单继承的初始化方法会导致父类属性无法全部继承。
    单继承方式是错误的,如下:

    class A:
        def __init__(self):
            self.a = 'a'
    
    class B:
        def __init__(self):
            self.b = 'b'
    
    class C(A, B):
        def __init__(self):
            super().__init__()
    
    c = C()
    print(c.a)
    print(c.b)
    

    能够正确打印出a的值,但是print(c.b)会报错:
    AttributeError: ‘C’ object has no attribute ‘b’

    正确继承方法

    同时继承多个父类时,可以显式的初始化每个父类,并且是用父类名初始化。如下所示:

    class A:
        def __init__(self):
            self.a = 'a'
    
    class B:
        def __init__(self):
            self.b = 'b'
    
    class C(A, B):
        def __init__(self):
            A.__init__(self)
            B.__init__(self)
    
    c = C()
    print(c.a)
    print(c.b)
    
    展开全文
  • Java为什么不能继承多个父类

    千次阅读 2017-03-11 23:17:59
    Java为什么不能继承多个父类 多重继承是件很复杂、很容易产生问题的功能。它跟Goto语句一样,利弊交织。 以下分两部分叙述,第部分是Python多重继承遇到的麻烦,第二部分是Jav

    链接:https://www.zhihu.com/question/21476063/answer/18351313
    来源:知乎
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    多重继承是一件很复杂、很容易产生问题的功能。它跟Goto语句一样,利弊交织。
    以下分两部分叙述,第一部分是Python多重继承遇到的麻烦,第二部分是Java和Ruby如何折中多重继承需求。

    第一部分

    Python是支持多重继承的,但为了解决多重继承的方法查找顺序问题(被称为MRO),有一场苦难史:

    1. 传统模式

    直接使用深度优先算法(并且,从左向右),每个类选择其第一次出现的位置。比如

    class A:
      def save(self): pass
    class B(A): pass
    class C:
      def save(self): pass
    class D(B, C): pass

    作为D类的一个实例,它的成员寻找顺序是:D、B、A、C。
    但当碰到如下这样一个“菱形继承”就麻烦了:

    class A:
      def save(self): pass
    class B(A): pass
    class C(A):
      def save(self): pass
    class D(B, C): pass

    作为D类的一个实例,寻找顺序也是D、B、A、C,但调用其save方法时,到底是调用A中的呢?还是C中的呢?直观上看,应该是C。这里有产生矛盾了。

    2. Python2.2的new-style class模式

    Python2.2中引进了new-style class,说白了就像java一样,所有类都继承自最根本的object类。这就让“菱形继承”变成十分普遍,于是只能改变MRO策略。仍然使用深度优先搜索、从左向右,但是每个类选择其最后一次出现的位置。这样一来,对上面的“菱形继承”就处理比较完美了,形成的顺序是:D、B、C、A,越是公用基础类,越放在后面。但是,碰到交叉继承,就又懵了
    这里写图片描述
    这里,X和Y都继承自O,A继承(X,Y)(注意,有先后顺序),B继承(Y,X),再有个最下面的类继承(A,B)。
    按照上述算法出来的结果是:?、A、B、Y、X、O。这就有些奇怪了,明明A在B前,A继承的X在Y前,可为啥X的优先级比Y要低呢?

    3. C3算法

    1996年,一帮牛人写了一篇文章A monotonic superclass linearization for Dylan,提供了一个基于层级计算的线性MRO算法,被称为C3,整体上比较合理了。(算法不详说了)在2012年被加入了Dylan语言,2007年加入了Python2.3,此外还有Perl 6、Parrot等语言之中。这样虽说基本解决了计算问题,但多重继承本身就有很多让人疑惑的地方,比如:
    这里写图片描述

    第二部

    多重继承那么复杂,可单一继承又那么拘束,咋办呢?

    1. 接口继承

    Java就是那么做的,只允许单一继承类(方法的实现),但可以继承多个接口(方法的定义)。

    Java的接口继承功能,既实现了静态语言的多重继承性,又避免了多重继承的数据构造的冲突和类层次的复杂性。但是,我们并不能说接口是解决问题的完美方案。接口也有不能共享实现的缺点。本来只是为了跨越继承层次来共享代码,现在却需要另外生成一个独立对象,而且每次方法调用都要委派给这个对象,这实在是不太合理,而且执行的效率也不高。——《松本行弘的程序世界》

    2. Mix-in

    这是Ruby所推崇的,最初在Lisp中开始使用。规则如下:
    通常的继承都是单一继承。
    第二个以及两个以上的父类必须是Mix-in的抽象类(即不能单独生成实例,不能继承普通类)。
    这种规则下,既能够保持单一继承的结构,又能用Mix-in来共享方法的实现。

    结论

    对于多重继承的实现方式。其实并不应该像很多人说的那样使用实现多个接口实现的。如果这样,将产生大量的“CTRL C+CTRL V”,当你发现你大量的CTRL C+CTRL+V时,就几乎可以断定。你的代码写得不好了。首先,在Thinking in Java里面,作者已经指出——如果确定在不需要多态(把子类转为父类)的情况下,应该优先考虑组合而不是继承。至于原因,我认为是继承本身就是一件很复杂很麻烦很谨慎的事情。以致于多少大牛在吐槽面向对象?

    “实现上的继承就跟过度使用goto语句一样,使程序拧巴和脆弱。结果就是,面向对象系统通常遭受复杂和缺乏复用的痛苦。” — John Ousterhout( Tcl and Tk 的创始人) Scripting, IEEE Computer, March 1998

    “面向对象编程语言的问题在于,它总是附带着所有它需要的隐含环境。你想要一个香蕉,但得到的却是一个大猩猩拿着香蕉,而其还有整个丛林。” — Joe Armstrong(Erlang语言发明人)

    展开全文
  • 1.存在一个类a,被类b继承,a是b的父类,b是a的子类 2.子类拥有弗雷德所有方法和参数,相当于将父类中的代码搬运到了子类中 3.子类相当于拥有父类中的左右代码,这就可以在此基础上作出修改和补充 4.子类继承父类后...

    1.什么是父类,什么是子类

    1.存在一个类a,被类b继承,a是b的父类,b是a的子类
    2.子类拥有父类的所有方法和参数,相当于将父类中的代码搬运到了子类中
    3.子类相当于拥有父类中的左右代码,这就可以在此基础上作出修改和补充
    4.子类继承父类后,子类中就相当于有了父类的代码
    5.当有很多类的一部分内容都相同时,可以将这一部分提取出来作为父类,当作模板,这可以减少代码量
    6.只能继承(extends)一个类
    

    2.继承父类关键字 “extends”

    在类中,使用 extends 继承父类
    

    使用方法

    Demo01为父类
    Demo02为子类
    

    创建父类 Demo01

    public class Demo01 {
        
        public void show(){
            System.out.println("这是父类的show方法");
        }
        
    }
    

    创建子类 Demo02 继承 Demo01

    public class Demo02 extends Demo01 {
    
        public void demo02Show(){
            System.out.println("这是子类的demo02Show()方法");
        }
    
    }
    

    在 mian 中 运行

    public class main {
        public static void main(String[] args) {
    
            //创建子类 Demo02 对象 d
            Demo02 d = new Demo02();
    		
    		//通过子类对象调用父类方法
            d.demo01Show();
            //调用子类中的方法
            d.demo02Show();
    
        }
    }
    

    输出结果

    这是父类的demo01Show()方法
    这是子类的demo02Show()方法

    3.子类中的重载和重写

    1.重载

    子类继承父类后,子类中相当于拥有了父类的代码,子类就可以对父类的方法进行重载
    

    2.重写

    当子类中出现和父类一样的方法时,子类的方法会覆盖掉父类的方法,称为重写
    

    创建父类 Demo01

    public class Demo01 {
        
        public void show(){
            System.out.println("这是父类的show方法");
        }
        
    }
    

    创建子类 Demo02 继承 Demo01

    public class Demo02 extends Demo01 {
    
        public void Show(){
            System.out.println("这是子类的Show()方法");
        }
    
    }
    

    在 mian 中 运行

    public class main {
        public static void main(String[] args) {
    
            //创建子类 Demo02 对象 d
            Demo02 d = new Demo02();
    
            d.Show();
    
        }
    }
    

    输出结果

    这是子类的Show()方法

    解释:父类和子类都有Show()方法存在,父类的Show()被子类的Show()方法覆盖了
    
    展开全文
  • 继承(基类,父类,超类),派生,子类
    本文作者:黄邦勇帅
    学习本文首先你应熟悉C++中的构造函数,基本的类的声明及怎样初始化类,关于这些问题,请参看本人所作的《C++构造函数,复制构造函数和析构函数》一文,在这篇文章中作了详细的介绍。
    本文分两部分即继承和虚函数与多态性,本文第一部分详细讲解了继承时的构造函数和析构函数的问题,父类与子类的同名变量和函数问题,最后介绍了多重继承与虚基类。本文第二部分重点介绍了虚函数与多态性的问题,因此学习虚函数的基础是继承,因此在学习虚函数前应学好继承。本文详细易懂,内容全面,是学习C++的不错的资料。
    本文内容完全属于个人见解与参考文现的作者无关,其中难免有误解之处,望指出更正。
    声明:禁止抄袭本文,若需要转载本文请注明转载的网址,或者注明转载自“黄邦勇帅”。
    主要参考文献:
    1、C++.Primer.Plus.第五版.中文版[美]Stephen Prata 著孙建春韦强译人民邮电出版社2005 年5 月
    2、C++.Primer.Plus.第四版.中文版Stanley B.Lippman、Barbara E.Moo 著李师贤等译人民邮电出版社2006 年3 月
    3、C++.Primer.Plus.第三版.中文版Stanley B.Lippman 等著潘爱民张丽译中国电力出版社2002 年5 月
    4、C++入门经典第三版[美]Ivor Horton 著李予敏译清华大学出版社2006 年1 月
    5、C++参考大全第四版[美]Herbert Schidt 著周志荣朱德芳于秀山等译电子工业出版社2003 年9 月
    6、21 天学通第四版C++ [美]Jesse Liberty 著康博创作室译人民邮电出版社2002 年3 月

    继承(基类,父类,超类),派生类,子类
    一:继承中的访问权限关系。
    1.基类,父类,超类是指被继承的类,派生类,子类是指继承于基类的类.


    2.在C++中使用:冒号表示继承,如class A:public B;表示派生类A从基类B继承而来


    3.派生类包含基类的所有成员,而且还包括自已特有的成员,派生类和派生类对象访问基类中的成员就像访问自已的成员一样,可以直接使用,不需加任何操作符,但派生类仍然无法访问基类中的私有成员.


    4.在C++中派生类可以同时从多个基类继承,Java 不充许这种多重继承,当继承多个基类时,使用逗号将基类隔开.


    5.基类访问控制符,class A:public B 基类以公有方式被继承,A:private B 基类以私有方式被继承,A:protected B 基类以受保护方式被继承,如果没有访问控制符则默认为私有继承


    6. protected 受保护的访问权限;使用protected 保护权限表明这个成员是私有的,但在派生类中可以访问基类中的受保护成员。派生类的对象就不能访问受保护的成员了。


    7. 如果基类以public 公有方式被继承,则基类的所有公有成员都会成为派生类的公有成员.受保护的基类成员成为派生类的受保护成员.


    8.如果基类以private 私有被继承,则基类的所有公有成员都会成为派生类的私有成员.基类的受保护成员成为派生类的私有成员.


    9.如果基类以protected 受保护方式被继承,那么基类的所有公有和受保护成员都会变成派生类的受保护成员.


    10.不管基类以何种方式被继承,基类的私有成员,仍然保有其私有性,被派生的子类不能访问基类的私有成员.

    例:继承中的访问权限关系

    class A   {int a; protected:int b; public:int c;   A(){a=b=c=1;} };

    //类B以公有方式从基类A继承
    class B:public A
    {public: int d;
    B(){//a=2; //错误,不能访问基类中的私有成员,因为a=2已经默认为私有保护了。
    b=2;   //正确,可以在类中访问基类中的受保护成员,但类的对象不能访问,基类中的受保护成员b在类B中仍然是受保护成员
    c=d=2;} }; //基类中的公有成员c在类B中仍然是公有成员


    //以受保护和私有方式从基类A继承。

    class C:protected A {public: int e; C(){//a=3; //错误,不能访问基类中的私有成员
    b=c=e=3; }};//这里基类受保护成员b和公有成员c都成为类C中的受保护成员。
    class D:private A  {public: D(){b=c=4;} };//基类中的公有和受保护成员都成为了类D中的私有成员。
    //验证受保护和私有方式继承的访问权限。
    class C1:public C
    {public: C1(){b=c=e=4;} };//正确;类A中的成员b和c在类C中是以受保护方式被继承的,b和c都成为了类C中的受保护成员。
    class D1:public D
    {public:D1(){//b=5; //错误,在A中受保护的成员b在类D中是以私有方式继承的,这样b就成为了类D中的私有成员,所以无法访问。
    //c=5; //错误,在A中公有的成员c在类D中是以私有方式继承的,这样c就成为了类D中的私有成员,所以无法访问。} };
    int main()
    {A m1; B m2; C m3; D m4;
    //cout<<m1.b<<m2.b<<m3.b<<m4.b;  //错误;不能用类的对象访问受保护的成员,只有在类中才能访问。
    cout<<m1.c; cout<<m2.c;
    //cout<<m3.c; //错误,类C是以受保护的方式从A继承的,基类中的变量c在类C中就是受保护的,所以类的对象不能访问
    //cout<<m4.c; //错误,类C是以私有的方式从A继承的,基类中的变量c在类C中就是私有的,所以类的对象不能访问}



    二:覆盖和隐藏基类成员变量或成员函数。

    1. 基类的成员变量或函数被覆盖:如果派生类覆盖了基类中的成员函数或成员变量,则当派生类的对象调用该函数或变量时是调用的派生类中的版本,当用基类对象调用该函数或变量时是调用的基类中的版本。


    2. 隐藏基类成员函数的情况:如果在派生类中定义了一个与基类同名的函数,不管这个函数的参数列表是不是与基类中的函数相同,则这个同名的函数就会把基类中的所有这个同名的函数的所有重载版本都隐藏了,这时并不是在派生类中重载基类的同名成员函数,而是隐藏,比如类A中有函数f(int i,intj)和f(int i)两个版本,当在从A派生出的类B中定义了基类的f()函数版本时,这时基类中的f(int i)和f(int i,int j)就被隐藏了,也就是说由类B创建的对象比如为m,不能直接访问类A中的f(int i)版本,即使用语句m.f(2)时会发生错误。


    3. 怎样使用派生类的对象访问基类中被派生类覆盖或隐藏了的函数或变量:

    3.1. 方法1 使用作用域运算符::,在使用对象调用基类中的函数或变量时使用作用域运算符即语句m.A::f(2),这时就能访问基类中的函数或变量版本。注意,访问基类中被派生类覆盖了的成员变量只能用这种方法。


    3.2. 方法2 使用using:该方法只适用于被隐藏或覆盖的基类函数,在派生类的类定义中使用语句using 把基类的名字包含进来,比如using A::f;就是将基类中的函数f()的所有重载版本包含进来,重载版本被包含到子类之后,这些重载的函数版本就相当于是子类的一部分,这时就可以用派生类的对象直接调用被派生类隐藏了的基类版本,比如m.f(2),但是使用这种语句还是没法调用基类在派生类中被覆盖了的基类的函数,比如m.f()调用的是派生类中定义的函数f,要调用被覆盖的基类中的版本要使用语句m.A::f()才行。


    4. 在派生类的函数中调用基类中的成员变量和函数的方法:就是在函数中使用的被派生类覆盖的基类成员变量或函数前用作域解析符加上基类的类名,即a::f()就是在派生类的函数中调用基类中被派生类覆盖了的函数f()的方法。


    5. 派生类以私有方式被继承时改变基类中的公有成员为公有的方法:

    5.1.使用::作用域运算符,不提倡用这种方法,在派生类的public 后面用作用域运算符把基类的公有成员包函进来,这样基类的成员就会成为派生类中的公有成员了,注意如果是函数的话后面不能加括号,如A::f;如果f是函数的话不能有括号。


    5.2.使用using语句,现在一般用这种方法,也是在派生类的public使用using把基类成员包函进来,如using A::f。

    例:隐藏或覆盖基类中的成员,使用::作用域运算符访问
    class A {int a; protected:int b; public:int c,d; void f(int i){cout<<"class A"<<"\n";} A(){a=b=c=d=1;} };
    class B:public A {public:int d;  //覆盖基类中的成员变量d。
    B(){b=c=d=2;  //这里是给子类B中的成员变量d赋值,而不是基类中的d
    A::d=3; } //给基类中被覆盖的成员d赋值,注意在类中访问的被覆盖成员的方式。
    void f(){cout<<"class B"<<"\n";//在子类中重定义基类中的同名函数,虽然参数列表不一样,但同样会隐藏基类中的同名函数
    A::f(1);   //在函数中调用基类中被隐藏了的同名函数的方法,使用作用域解析运算符。
    //f(1);  //错误,因为基类中的函数被子类中的同名函数隐藏了,在这里子类不知道有一个带参数的函数f。} };
    int main()
    {B m; cout<<m.d<<"\n";    //输出子类中的成员变量d的值,注意派生类中覆盖了基类成员d.
    cout<<m.A::d<<"\n"; //输出基类中的成员变量d的值,注意这是使用对象访问被覆盖的基类成员的方式
    m.f();            //调用子类中的不带参数的函数f。
    //m.f(2);  //错误,因为基类中的带一个参数的函数f被子类中的同名函数隐藏掉了,不能这样访问,须用作用域解析运算符来访问。
    m.A::f(1); }  //使用子类对象访问基类中被隐藏的函数的方法。


    例:使用using 语句以便访问基类中被隐藏的函数

    class A{int a; protected:int b; public:int c,d; void f(){cout<<"Amoren"<<"\n";}
    void f(int i){cout<<"class A"<<"\n";} A(){a=b=c=d=1;} };
    class B:public A {public:int d;  //覆盖基类中的成员变量d。
    B(){b=c=d=2;  //这里是给类B中的成员变量d赋值,而不是基类中的d
    A::d=3; } //给基类中被覆盖的成员d赋值,注意在类中访问的被覆盖成员的方式。
    using A::f;  //使用语句using把类A中的函数f包含进来,以便以后可以直接访问基类被隐藏了的函数,注意函数f没有括号
    void f(){cout<<"class B"<<"\n";//在子类中覆盖基类中的同名函数,注意这里是覆盖,同时会隐藏基类中的其他同名重载函数
    f(1);  //正确,因为使用了using语句,所以可以在类中直接使用基类中f函数的重载版本。
    A::f(2) ;//正确,虽然使用了using语句,但同样可以按这种方法访问基类中的函数。
    A ma; 
    ma.f();  //正确,在子类中创建的基类对象,可以直接用对象名调用基类中被子类覆盖或隐藏了的函数,因为这时不会出现二义性。
    ma.f(1);}  //正确,在子类中创建的基类对象,可以直接用对象名调用基类中被子类覆盖或隐藏了的函数,因为这时不会出现二义性。
    void g(){cout<<"this g"<<"\n";  f();  //正确,但该语句访问的是子类中的不带参数函数f,虽然在类中使用了using语句,但直接调用被子类覆盖了的基类函数时不能使用这种方法
    A::f();  }};//正确,调用被子类覆盖了的基类中的函数f,注意,虽然使用了using但要访问被子类覆盖了的函数,只能这样访问。
    int main()
    {B m; m.f();   //调用子类中的不带参数的函数,这里不会调用基类中的不带参数的被覆盖的函数f。
    m.A::f(); //调用基类中被子类覆盖了的函数f,虽然子类使用了using语句,但要访问基类中被覆盖的方法只能像这样使用。
    m.f(1);  //调用基类重载的f函数,注意这里可以不用::运算符,因为在子类中使用了using,只要子类没有覆盖基类中的方法,都可以这样直接调用。
    m.A::f(2); } //当然,使用了using后,也可以使用这种方法


    例:派生类以私有方式被继承时改变基类中的公有成员为公有的方法

    class A {public: int a,b; void f(){cout<<"f"<<"\n";}  void g(){cout<<"g"<<"\n";} };
    class B:private A
    {public:  A::f; A::a; //使用::运算符使基类中的成员成为公有的。注意函数名后不能有括号。
    using A::g; }; //使用using语句使基类中的成员函数g成为类B中的公有成员,注意函数名后不能有括号。
    int main()
    { B m;
    //m.b=1;  //错误,因为类B是以私有方式继承的,类A中的成员在类B中是私有的,这里不能访问私有成员。
    m.f(); m.g(); m.a=1;}



    三:继承时的构造函数和析构函数问题

    1. 在继承中,基类的构造函数构建对象的基类部分,派生类的构造函数构建对象的派生类部分。


    2. 当创建派生类对象时先用派生类的构造函数调用基类的构造函数构建基类,然后再执行派生类构造函数构造派生类。即先构造基类再构造派生类的顺序。执行析构函数的顺序与此相反。


    3. 调用基类带参数的构造函数的方法:在派生类的构造函数中使用初始化列表的形式就可以调用基类带参数的构造函数初始化基类成员,如B():A(int i){},类B是类A的派生类。


    4. 派生类的构造函数调用基类的构造函数的方法为:

    4.1 如果派生类没有 显示用初始化列表调用基类的构造函数时,这时就会用派生类的构造函数调用基类的默认构造函数,构造完基类后,才会执行派生类的构造函数函数体,以保证先执行基类构造函数再执行派生类构造函数的顺序,如果基类没有默认构造函数就会出错。


    4.2 如果派生类用 显示的初始化列表调用基类的构造函数时,这时就会检测派生类的初始化列表,当检测到显示调用基类的构造函数时,就调用基类的构造函数构造基类,然后再构造派生类,以保证先执行基类构造函数再执行派生类构造函数的顺序,如果基类没有定义派生类构造函数初始化列表调用的构造函数版本就会出错。


    5. 如果在基类中没有定义默认构造函数,但定义了其他构造函数版本,这时派生类中定义了几个构造函数的不同版本,这时只要派生类有一个构造函数没有显示调用基类中定义的构造函数版本就会发生错误,因为编译器会首先检查派生类构造函数调用基类构造函数的匹配情况,如果发现不匹配就会出错,即使没有创建任何类的对象都会出错,而不管这个派生类的对象有没有调用派生类的这个构造函数。比如:基类有一个构造函数版本A(int i)而没有定义默认构造函数,派生类B,有这几个版本的构造函数B():A(4){},B(int i):A(5){},再有语句B(int i, int j){}没有显示调用基类定义的构造函数而是调用基类的默认构造函数,如果创建了B m和语句B m(1)时都会提示没有可用的基类默认构造函数可用的错误,虽然这时类B的对象m没有调用派生类B的带有两个形参的构造函数,但同样会出错。


    6. 同样的道理,如果基类中定义了默认构造函数,却没有其他版本的构造函数,而这时派生类却显示调用了基类构造函数的其他版本,这时就会出错,不管你有没有创建类的对象,因为编译器会先在创建对象前就检查构造函数的匹配问题。


    7. 派生类只能初始化他的直接基类。比如类C是类B的子类,而类B又是类A的子类,这时class C:public B{public:

    B():A(){} };将会出错,该语句试图显示调用类B的基类类A的构造函数,这时会出现类A不是类C的基类的错误。


    8. 继承中的复制构造函数和构造函数一样,基类的复制构造函数复制基类部分,派生类的复制构造函数复制派生类部分。


    9.派生类复制构造函数调用基类复制构造函数的方法为:A(const A& m):B(m){}其中B是基类,A是派生类。


    10.如果在派生类中定义了复制构造函数而没有用初始化列表显示调用基类的复制构造函数,这时不管基类是否定义了复制构造函数,这时出现派生类对象的复制初始化情况时就将调用基类中的默认构造函数初始化基类的成员变量,注意是默认构造函数不是默认复制构造函数,如果基类没有默认构造函数就会出错。也就是说派生类的复制构造函数的默认隐藏形式是B(const B& j):A(){}这里B是A的派生类,也就是说如果不显示用初始化列表形式调用基类的复制构告函数时,默认情况下是用初始化列表的形式调用的是基类的默认构造函数。


    11.当在派生类中定义了复制构造函数且显示调用了基类的复制构造函数,而基类却没有定义基类的复制构造函数时,这时出现派生类对象的复制初始化情况就将调用基类中的默认复制构造函数初始化基类部分,调用派生类的复制构造函数初始化派生类部分,因为复制构造函数只有一种形式,即A(const A& m){},比如当出现调用时A(const A&m):B(m){}如果这时基类B没有定义复制构造函数,则该语句将会调用派生类A的默认复制构造函数。


    12.如果基类定义了复制构造函数,而派生类没有定义时,则会调用基类的复制构造函数初始化基类部分,调用派生类的默认复制构造函数初始化派生类部分。

    例:基类不定义默认构造函数,派生类不定义复制构造函数而基类定义复制构造函数的情形。
    //类A不定义默认构造函数

    class A {public: int a,a1; A(int i){a=a1=11;cout<<"goucaoA1"<<"\n";} 

    A(const A & J ){a=4,a1=4,cout<<"fugouA"<<"\n";}

    ~A(){cout<<"hahaA"<<"\n";} };
    //类B不定义复制构造函数
    class B:public A
    {public: int b,b1;
    //B(){b=b1=2; cout<<"goucaoB"<<"\n";}    //错误,此语句会调用基类中的默认构造函数,而基类没有默认构造函数。
    //B(int i,intj){b=b1=3}错误,同上,此语句没有显示调用基类构造函数,就会调用基类的默认构造函数,而这时基类没有默认构造函数。
    B(int i):A(2){b=b1=3;cout<<"goucaoB1"<<"\n";} ~B(){cout<<"hahaB"<<"\n";}};//显示调用基类带一个形参的构造函数,注意语法
    int main()
    { B m(0);  cout<m.a<<m.b<<"\n"; //输出113
    B m1(m);  cout<<m1.a<<m1.b;} //输出43,注意,此语句将调用基类定义的复制构造函数,然后调用派生类的默认复制构造函数。
    例:派生类B使用默认的B(const B& K):A(){}形式,而不使用初始化列表显示调用基类A中的复制构造函数的情况
    class A

    {public: int a,a1; 

    A(){a=a1=2;cout<<"goucaoA"<<"\n";} 

    A(int i){a=a1=11;cout<<"goucaoA1"<<"\n";}

    A(const A & J){a=4,a1=4; cout<<"fugouA"<<"\n";} 

    ~A(){cout<<"hahaA"<<"\n";} };

    //类中B不使用初始化列表调用基类中的复制构造函数的情况
    class B:public A
    {public: int b,b1; B(){b=b1=2; cout<<"goucaoB"<<"\n";}   
    //B(int i,int j):A(4,4){b=b1=3}//错误,调用了基类没有定义的带两个参数的构造函数。
    B(int i):A(2){b=b1=3;cout<<"goucaoB1"<<"\n";}
    B(const B&K) {b=b1=5;cout<<"fugouB"<<"\n";} //注意,这里没有显示调用基类的复制构造函数,而是用默认的B(const B&K):A() {}的形式调用的基类中的默认构造函数,注意是默认构造函数而不是默认复制构造函数。
    ~B(){cout<<"hahaB"<<"\n";}};
    int main()
    { B m(0); cout<<m.a<<m.b<<"\n";// 输出113。
    B m1(m); cout<<m1.a<<m1.b;}//输出25,注意此语句将调用基类的默认构造函数将基类中的成员初始化为,再调用派生类的复制构造函数将类B中的成员b初始化为。


    14.1.6 多重继承与虚基类

    1. C++允许一个派生类从多个基类继承,这种继承方式称为多重继承,当从多个基类继承时每个基类之间用逗号隔开,比如class A:public B, public C{}就表示派生类A从基类B和C继承而来。


    2. 多重继承的构造函数和析构函数:多重继承中初始化的次序是按继承的次序来调用构造函数的而不是按初始化列表的次序,比如有class A:public B, public C{}那么在定义类A的对象A m时将首先由类A的构造函数调用类B的构造函数初始化B,然后调用类C的构造函数初始化C,最后再初始化对象A,这与在类A中的初始化列表次序无关。


    3. 多重继承中的二义性问题:

    3.1. 成员名重复:比如类A从类B和C继承而来,而类B和C中都包含有一个名字为f的成员函数,这时派生类A创建一个对象,比如A m; 语句m.f()将调用类B中的f函数呢还是类C中的f函数呢?


    3.2. 多个基类副本:比如类C和B都从类D继承而来,这时class A:public B, public C{}类A从类C和类B同时继承而来,这时类A中就有两个类D的副本,一个是由类B继承而来的,一个是由类C继承而来的,当类A的对象比如A m;要访问类D中的成员函数f时,语句m.f()就会出现二义性,这个f函数是调用的类B继承来的f还是访问类C继承来的函数f呢。


    3.3. 在第2种情况下还有种情况,语句classA:public B,public C{},因为类A首先使用类B的构造函数调用共同基类D的构造函数构造第一个类D的副本,然后再使用类C的构造函数调用共同基类D的构造函数构造第二个类D的副本。类A的对象m总共有两个共享基类D的副本,这时如果类D中有一个公共成员变量d,则语句m.B::d和m.D::d都是访问的同一变量,类B和类D都共享同一个副本,既如果有语句m.D::d=3 则m.B::d也将是3。这时m.C::d的值不受影响而是原来的值。为什么会这样呢?因为类A的对象m总共只有两个类D的副本,所以类A的对象m就会从A继承来的两个直接基类B和C中,把从共同基类D 中最先构造的第一个副本作为类A的副本,即类B构造的D的副本。因为class A:public B,public C{}最先使用B的构造函数调用共同基类类D创造D的第一个副本,所以类B和类D共享同一个副本。


    3.4. 解决方法:对于第1 和第2 种情况都可以使用作用域运算符::来限定要访问的类名来解决二义性。但对于第二种情况一般不允许出现两个基类的副本,这时可以使用虚基类来解决这个问题,一旦定义了虚基类,就只会有一个基类的副本。

    例:多重继承及其二义性
    class A {public:int a; A(int i){a=i;cout<<"A";}}; //共同的基类A
    class B:public A {public: int b; B():A(4){cout<<"B";}};
    class C:public A{public: int c; C():A(5){cout<<"C";}};
    class D:public B,public C
    {public: int d;D():C(),B(){cout<<"D";}}; //先调用类B的构造函数而不会先调用类C的构造函数,初始化的顺序与初始化列表顺序无关
    int main()
    {D m;  //输出ABACD,调用构造函数的顺序为类ABACD,注意这里构造了两个类A的副本,调用了两次类A的构造函数
    // m.a=1; //错误,出现二义性语句,因为类D的对象m有两个公共基类A的副本,这里不知道是调用由类B继承来的A的副本还是由类C继承来的A的副本
    m.B::a=1;cout<<m.B::a; //输出1。
    m.A::a=3; cout<<m.B::a<<m.A::a;//输出33,类B和类A共享相同的副本,调用类A中的a和类B继承来的a是一样的,因此最后a的值为3。
    m.C::a=2;  cout<<m.C::a;} //输出2,类C和类A类B的副本是彼此独立的两个副本,因此,这里不会改变类B和类A的副本中的a的值。


    4. 虚基类:方法是使用virtual关见字,比如class B:public virtual D{},class C:virtual public D{}注意关见字virtual的次序不关紧要。类B和类C以虚基类的方式从类D 继承,这样的话从类B和类C同时继承的类时就会只创见一个类D的副本,比如class A:public B, public C{}这时类A的对象就只会有一个类D的副本,类A类B类C类D四个类都共享一个类D的副本,比如类D 有一个公有成员变量d,则m.d和m.A::d,m.B::d,m.C::d,m.D::d 都将访问的是同一个变量。这样类A的对象调用类D中的成员时就不会出现二义性了。


    5. 虚基类的构造函数:比如class B:public virtual D{};class C:virtual public D{}; class A:public B,public C{};这时当创建类A的对象A m时初始化虚基类D将会使用类A的构造函数直接调用虚基类的构造函数初始化虚基类部分,而不会使用类B或者类C的构造函数调用虚基类的构造函数初始化虚基类部分,这样就保证了只有一个虚基类的副本。但是当创建一个类B和类C的对象时仍然会使用类B和类C中的构造函数调用虚基类的构造函数初始化虚基类。

    例:虚基类及其构造函数
    class A {public:int a; A(){cout<<"moA"<<"\n";} A(int i){a=i;cout<<"A";} };
    class B:public virtual A {public: int b; B(int i):A(4){cout<<"B";}  }; //以虚基类的方式继承
    class C:public virtual A {public: int c;  C():A(5){cout<<"C";} };
    class D:public B, public C {public: int d; D():A(4),C(),B(2){cout<<"D";}};
    //因为类D是虚基类,所以类D会直接调用虚基类的构告函数构造虚基类部分,而不会使用类B或者类C的构造函数来调用虚基类的构造函数初始化虚基类部分。要调用虚基类中的带参数的构造函数必须在这里显示调用,如果不显示调用就将调用虚基类的默认构造函数。
    int main()
    {D m;  //输出ABCD,注意这里没有重复的基类副本A。
    C m1;  //输出AC,虽然D是虚基类,但当创建类C的对象时仍然会使用类C的构造函数调用虚基类的构造函数初始化虚基类部分。
    cout<<m.a;  //输出4,因为使用了虚基类所以这里就不存在二义性问题。
    m.B::a=1;

    cout<<m.a<<m.A::a<<m.B::a<<m.C::a;} //输出1111,类A,类B,类C,类D共享的是同一个虚基类的副本,所以这里输出四个1。



    展开全文
  • Java 子类继承父类方法

    千次阅读 2019-05-22 09:13:32
    例如 一个 B 类继承 A ,或称从A派生 B A 称为基类(父类), B 称为派生(子类) 类继承关系语法形式 Class 派生类名:基本名表 { 数据成员函数声明 } 基本名表 构成 访问控制 基类...
  • 一.继承: ...继承的根类:大多都继承自 NSObject 类,所以在定义一个类时,要继承NSObject 类。 继承就是代码优化公共部分交给父类 例如: (Person是父类Worker 和Worker都继承了Person成为子
  • 2.继承中子父类的概念 3.继承的作用 4. 查看继承父类   5.方法的复写 6.super()  7.__init__()方法  8. 派生属性 9.私有属性私有方法在继承中的表现 10.抽象   二、多继承 1、语法 3、...
  • 在Java中,继承是让一个类获得另一个类的属性和方法。继承的是子类,被继承的被称为父类继承的关键字是 extends ,在子类类名后直接使用extends + 父类类名即表示子类继承父类。 此外,Object是默认的父类,不含...
  • 关于继承父类的初始化方法

    千次阅读 2017-10-19 09:29:38
    python和其他面向对象语言类似,每个类可以拥有一个或者多个父类,它们从父类那里继承了属性和方法。如果一个方法在子类的实例中被调用,或者一个属性在子类的实例中被访问,但是该方法或属性在子类中并不存在,那么...
  • 新类可以称为子类,已有的这个类称为父类或者超类。例如:猫和豹子都是猫科动物,那么就可以说描述猫这个对象创建的类,是子类;而描述猫科动物这个对象创建的类,就是父类。   形象理解: 将学生和工人
  • 继承 mammals dogs cats humans lions tigers leopards
  • 这是多态的一种应用:把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应...比如从某个基类继承多个对象,其基类有一个虚方法Tdoit,然后其子类也有这个方
  • Java——继承

    千次阅读 2020-01-27 21:38:25
    Java继承具有单继承的特点,每个子类只有一个直接父类。 \quadJava的继承通过extends关键字实现,实现继承类称为子类,被继承类称为父类或基类。父类与子类的关系其实就是一般与特殊的关系,例如水果和苹果的...
  • 观察下列代码, 父类和子类中都定义有 String name变量,那最终继承之后是不是只剩下1name?还是父类子类的name互相不相干? 通过赋值后,最后会输出什么?见如下 public class TestTest extends son { String ...
  • python中新定义一个类称为子类,被继承的类称为父类;子类继承父类后就拥有了父类的所有特性。 #语法格式 class 子类名(父类名): pass 如下:定义了Person类,一个Student类,但是Student类完全可以使用Person类...
  • 父类:被继承类称为父类; 子类:继承
  • Java继承

    2019-01-23 17:26:28
    继承是面向对象程序设计的一个重要特性。在现有的基础上建立新的处理过程称为继承。由继承而得来的类称为子类,被继承类称为父类。直接或者间接被继承都是父类。子类继承父类的状态和行为,同时也可以修改...
  • 的数据成员不能在的声明时候初始化,为了解决这问题,使用构造函数处理对对象的初始化。构造函数是种特殊的成员函数,与其他函数不同,不需要用户调用它,而是创建对象的时候自动调用。析构函数是对象不再...
  • 继承  一个子类只能有一个直接... 一个子类有多个直接父类称为继承。一对多关系。   class AA { public: int _a; }; class BB { public: int _a; int _b; }; class CC :public BB,public AA { publ...
  • python中子调用父类构造方法

    千次阅读 2014-06-02 02:03:27
    python和其他面向对象语言类似,每个类可以拥有一个或者多个父类,它们从父类那里继承了属性和方法。如果一个方法在子类的实例中被调用,或者一个属性在子类的实例中被访问,但是该方法或属性在子类中并不存在,那么...
  • java继承

    2015-03-19 20:04:12
    java中的继承用extends关键字来实现,被继承叫做超类、父类、基类,实现继承类称为子类、派生。  子类是种特殊的父类,因此可以看作父类是大,子类是小。  extends实际上是拓展的意思,而父类和子类...
  • 继承:是一种由已有的类创建新类的机制。 1、利用继承这个机制,可以先...2、使用Java关键字 extends 来定义一个类的子类,来实现继承机制; 基本格式如下: class Student extends People { .... } 类 Stud...
  • Python类继承及super()函数

    万次阅读 多人点赞 2019-06-27 19:57:36
    文章目录Python中单...新的类称为子类(Subclass),被继承类称为父类、基类或者超类。子类继承父类后,就拥有父类的所有特性。类继承的简单例子: 普通方法继承 class Fruit(): def color(self): print(...
  • python中子调用父类的初始化方法

    千次阅读 2017-12-18 20:38:03
    python和其他面向对象语言类似,每个类可以拥有一个或者多个父类,它们从父类那里继承了属性和方法。如果一个方法在子类的实例中被调用,或者一个属性在子类的实例中被访问,但是该方法或属性在子类中并不存在,那么...
  • 概念:一个类(子类)可以使用从另一个类(父类、超类)继承属性和方法。 java中的继承是单一继承,即一个子类只能有一个父类。 当然,接口(interface)也可以继承。2、如何使用继承广义上讲,继承类、继承抽象类、...
  • C++ (多继承和虚继承)

    万次阅读 2018-07-29 16:00:46
    一个类多个基类,这样的继承关系称为继承; b. 多继承声明语法: class 派生类名: 访问控制符 基类名1,访问控制符 基类名2 { 数据成员和成员函数声明; } class A: public B,public c { } 图示: c. 多个...
  • 面向对象的优势在于的...之间有种父与子的关系,子类继承父类的属性和方法,称为继承。在继承里,子类可以拥有父类的属性(除私有属性之外),也可以拥有父类的方法(除私有方法之外),同时,子类还可以有
  • Python中多继承

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 131,586
精华内容 52,634
关键字:

一个类继承自多个父类称为