二、继承

作者: __bba3 | 来源:发表于2020-05-09 14:43 被阅读0次

(1)继承

<1>语法
  • 原则 :
    父类/子类 基类/派生类
    是否可以是继承:【子类 is a 父类】
  • 语法
class 派生类:访问限定符 基类{
        结构体
};

如果不写访问限定符,默认是private继承。

(2)访问权限

  • 1.子类继承了父类所有的成元变量和成员函数。与访问限定符无关。访问限定符只是限制了访问。
  • 2.子类内部要想访问父类的private成员,可以把private改为public或者protected(常用)
<1>子类内部的访问权限
  • 1.无论那种继承方式,子类内部可以访问除了private的所有成员。
  • 2.子类继承父类,成员访问权限的变化:
继承方式\父类成员 public protected private
public继承 public protected 不可见
protected继承 protected protected 不可见
private继承 private private 不可见

子类内部访问父类成员,只能访问publicprotected成员。

<2>子类对象的访问权限
  • 子类对象只能访问其父类的public成员,不能访问其他成员。

注意:
(1)子类只有public继承父类的时候,才能访问父类的public成员,其他都不能访问。通常子类使用public继承父类。
(2)public继承,可以访问,不能直接修改基类里的private成员,可以在父类中用接口来修改其私有变量的值。

(3)继承关系的构造顺序

<1>派生类的构造顺序
  • 派生类的构造顺序:父类---->子类成员变量------>子类
  • 派生类的析构顺序:和构造顺序相反。

注意:没有默认构造函数的基类在派生类的初始化,必须在初始化列表中初始化。比如在三角形实例中。

class Member{
public:
    Member(int i){ cout << __func__ << endl;}
    ~Member(){ cout << __func__ << endl;}
};
class Father{
public:
    Father(int i){ cout << __func__ << endl;}
    ~Father(){ cout << __func__ << endl;}
};
class Son:public Father{
    Member m;
public:
    //没有默认构造函数的父类在子类中初始化,必须使用初始化列表的方式
    Son():m(1),Father(1){ cout << __func__ << endl;}
    ~Son(){ cout << __func__ << endl;}
};
int main(){
    Son s;
}
结果:
Father
Member
Son
~Son
~Member
~Father

(4)同名隐藏规则

<1>概念

如果子类与父类成员函数同名,无论参数是否相同,子类会隐藏父类的成员函数。

<2>访问同名隐藏的父类
  • 1.在子类中添加【using 父类名::同名函数名;】,只能解决同名但不同参的情况;
  • 2.使用【子类对象.父类名::父类函数名;】:可以解决任何情况。
class Father{
public:
    void Test(){
        cout << "Father::Test()" << endl;
    }
    void Test(int i){
        cout << "Father::Test(" <<i <<")" <<endl;
    }
};
class Son:public Father{
public:
    using Father::Test;//(1)只能解决参数不同的同名隐藏
    void Test(){
        cout << "Son::Test()" << endl;
    }
};
int main(){
    //不采用任何情况
    Son s;
    s.Test();//访问的是子类的成员函数
    s.Test(3)//会报错!
    //如果要调用父类的方法:
//方法1:可以完全解决同名隐藏
    s.Father::Test(2);
    s.Father::Test();   
//法(2):使用using 。。。,只能解决同名不同参的问题
    s.Test(3);
    //s.Test();//不能调用父类的Test()  
}
<3>对象查找函数的规则

对象首先在子类中查找,如果没有在子类找到,会到父类中查找;如果子类中找到了同名的函数,并且参数相同就会执行该函数,如果参数不同就会报错,并不会再往父类中查找了。
注意:指针访问和.访问的规则一样。

(5)赋值兼容

<1>前提

必须是公有继承,才能谈赋值兼容。

<2>概念
  • 在任何需要基类对象的地方都可以使用公有的派生类对象来代替。反之,不可。
  • 对象切割(Object Slicing):在赋值时舍弃派生类自己的成员,只进行基类数据成员的赋值。
  • 赋值兼容可能会产生对象切割的代价。
<3>赋值兼容有三种情况:
  • 1. 派生类的对象可以赋值给基类对象
    会生成一个新的父类对象。
Base base;
Derive derive;
base = derive;
  • 注意:
    (1) 子类特有的成员变量不能被父类访问。
    (2)子类给父类赋值以后,父类的大小是其本身成员变量的大小,子类的大小是继承于父类的大小加上子类本身的大小。
    (3)子类对象是可以初始化父类对象。
  • 例子:
class Base{
public:
    int  n;
};
class Derive:public Base{
public:
    int m;
};
void Func(Base b){}
Base Func(){
      Derive d;
      return d;
}
int main(){
    Base b;
    Derive d;
    d.n=10;
    d.m=100;
    b=d;//调用Base的赋值运算符重载
    //d=b;//错误,不可以这样赋值
    cout << b.n<<endl;//4
    //cout << b.m<<endl;//Base对象不能访问到子类的任何成员(对象切割)
    cout << sizeof(b)<<endl;//4
    cout << sizeof(d)<<endl;//8
    Base b2(d);//子类对象初始化父类对象(调用Base的拷贝构造函数)
子类对象给父类对象赋值的应用:
//1.函数参数对象传值  
    Func(d);//调用Base的拷贝构造函数
//2.函数返回对象
    Func();////调用Base的拷贝构造函数
  • 2. 派生类的对象可以初始化基类的引用
    没有生成新的对象,只是起了一个别名。
Derive d;
Base& fb = d;//子类对象初始化父类的引用,类型决定大小和能否访问的成员
1.d的地址和fb的地址是一样的!
cout << &d <<endl;
cout << &fb <<endl;
2.初始化父类的对象是不能访问子类成员的。
cout << sizeof(d) <<endl;//8
cout << sizeof(fb)<<endl;//4
d.Test();//子类对象可以访问子类的成员
fb.Test();//父类引用虽然指向子类对象,但是不能访问子类成员,只能访问父类的成员
3. 应用:
void Func(Base& b){}//定义
Func(d)//调用.(这里是引用不会调用拷贝构造函数,值传才会调用拷贝构造)
  • 3. 派生类的对象地址可以初始化基类的指针
    没有生成一个新的对象,只是父类指针指向父类特有的那一部分。
Base* pb=&d;
1.d的地址和fb的地址是一样的!
cout << pb <<endl;
cout << &d <<endl;
2.初始化父类的对象是不能访问子类成员的。
pb->Test();//只能访问父类特有的成员 
pb->m;
3. 应用;
void Func(Base* b){}//定义
Func(&d);//调用

注意:
1.父类引用子类对象与父类指针指向子类对象,都只能访问子类所继承的那部分成员
2.即使父类和子类函数名和参数完全相同,也不能访问到子类的成员,要想访问与其同名的子类成员,就要实现多态

(6)多重继承

一个类可以同时继承多个父类的行为和特征功能。

<1>格式
class 类名 : public 基类1,public 基类2{};
<2>大小

多重继承的大小,等于继承的所有父类大小之和。

<3>多重继承基类构造顺序

父类在子类的继承顺序就是子类对象的初始化顺序,如果父类有继承先会初始化父类的继承。

class Base{
public:
    Base(){cout << __func__ <<endl;}
    ~Base(){cout << __func__ <<endl;}
    int n;
};
class A:public Base{
public:
    A(){cout << __func__ <<endl;}
    ~A(){cout << __func__ <<endl;}
};
class B:public Base{
public:
    B(){cout << __func__ <<endl;}
    ~B(){cout << __func__ <<endl;}
};
class C:public A,public B{
public:
    C(){cout << __func__ <<endl;}
    ~C(){cout << __func__ <<endl;}
}; 
int main(){
     C c;
     cout << "Base size:" << sizeof(Base) << endl;//4
     cout << "A size:" << sizeof(A) << endl;//4
     cout << "B size:" << sizeof(B) << endl;//4
     cout << "C size:" << sizeof(C) << endl;//4+4=8
执行结果:Base--->A--->Base--->B---->C---->~C---->~B---->~Base---->~A----->~Base
}
<4>多重继承的危害

钻石继承/菱形继承。(有两套相同的成员函数和成员变量,造成成员冲突)

(7)钻石继承/菱形继承

<1>概念

两个子类B,C继承同一个父类A,而又有子类D同时继承这两个子类B,C,继承关系画成一个图刚好是菱形。

<2>产生的问题
  • (1)成员冲突:子类D有两套名字完全相同的成员变量和成员函数来自B和C。要想访问某个成员必须在其类D中指明是那个类的成员。
  • (2)多次构造和析构:见上面的多重继承。在构造父类时,由于父类都虚继承了祖父类,所以在构造是都会调用祖父类的构造函数,并且会多次释放。
void Print()const{
//成员变量
        cout << "a:" << EqualTriangle::a <<endl;//指明成员变量属于那个类。
        cout << "b:" << EqualTriangle::b <<endl;
        cout << "c:" << EqualTriangle::c <<endl;
        cout << "a:" << RightTriangle::a <<endl;
        cout << "b:" << RightTriangle::b <<endl;
        cout << "c:" << RightTriangle::c <<endl;
//成员函数
         cout << er.EqualTriangle::Getlength() << endl;
      cout << er.RightTriangle::Getlength() << endl;

说明:等腰直角三角形同时继承了等腰三角形和直角三角形,当调用Getlength时,不知道调用的是等腰三角形的还是直角三角形的。

<3>虚继承
  • 解决成员冲突有两种方法:
    (1)在访问时加上类型(类名)来限制访问的是那个成员。
    (2)虚继承
  • 定义
    虚继承:在继承定义中包含了virtual关键字的继承关系。(A虚继承于Base)
    虚基类:在虚继承体系中的通过virtual继承而来的基类。(Base是A的虚基类)

虚基类是一个相对概念,在虚继承关系中,父类相对与子类是虚基类

  • 格式
class 类名:public virtual 基类{
}
  • 注意:
    1.格式的写法
    2.关于孙子类的构造函数要加上前面父类和祖父类
    3.sizeof的变化
    4.孙子类对象成员函数的调用(调用的是父类的,不是祖父类的)
    5.初始化祖父类的次数的变化,之前是两次构造和析构,加上虚继承后只构造一次、析构一次。
class EqualTriangle:public virtual Triangle{//添加虚继承
class RightTriangle:public virtual Triangle{//添加虚继承
class EqualRightTriangle:public EqualTriangle,public RightTriangle{
      EqualRightTriangle(floatright):EqualTriangle(sqrt(right*right*2),right),
RightTriangle(right,right),Triangle(right,right,sqrt(right*right*2)){}//所有的构造函数都必须初始化,但是如果父类和祖父类是默认的构造函数,则不用写。 
void Print()const{
        cout << "a:" << a <<endl;//虚继承后可以直接访问祖父类的成员变量
        cout << "b:" << b <<endl;
        cout << "c:" << c <<endl;
}
};
int main(){
cout << sizeof(EqualTriangle) <<endl;//12(3*4)  ---->24(8(一个指向虚继承的指针)+3*4+4(补齐))
cout << sizeof(RightTriangle) << endl;//12  ---->24
cout << sizeof(EqualRightTriangle) <<endl;//24(12+12)---->32(8*2(加了两个指针)+3*4+4(补齐))
cout << er.GetArea() << endl;//调用的是父类(RightTriangle)函数,不是祖父类的。
}

虚继承的构造和析构顺序:

class Base{
public:
    Base(){cout<<__func__<<endl;}
    ~Base(){cout<<__func__<<endl;}
};
class A:public virtual Base{
public:
    A(){cout<<__func__<<endl;}
    ~A(){cout<<__func__<<endl;}
};
class B:public virtual Base{
public:
    B(){cout<<__func__<<endl;}
    ~B(){cout<<__func__<<endl;}
};
class C:public A,public B{ // 父类在子类中的继承顺序就是子对象中父类的构造顺序
public:
    C(){cout<<__func__<<endl;}
    ~C(){cout<<__func__<<endl;}
};
class Emply{};
int main(){
    C c;
    cout << sizeof(Base) << endl;
    cout << sizeof(A) << endl;
    cout << sizeof(B) << endl;
    cout << sizeof(C) << endl;
    // 空类
    cout << sizeof(Emply) << endl;
}
结果:
Base
A
B
C
1
8
8
16
1
~C
~B
~A
~Base
<4>关于多重继承总结

(1)什么是多重继承?同时继承多个父类。
(2)多重继承有什么危害?菱形继承/钻石继承。
(3)什么是菱形继承/钻石继承?多重继承的两个或多个父类具有相同的祖先类。
(4)菱形继承/钻石继承有什么危害?因为多重继承的两个或多个父类具有相同的祖先类。所以会有完全相同的属性和方法。因此当前多重继承类有两份相同的属性和方法。使用时会出现冲突。
(5)如何解决菱形继承/钻石继承导致的冲突?使用虚继承
(6)什么是虚继承?父类在继承具有相同的祖先类时,加上virtual.

(8)对象构造顺序总结

  • 先父后子(继承)
  • 从左到右(多重继承)
  • 先虚后实(虚继承 )
  • 从上到下 (成员变量)
  • 由内及外 (组合)

相关文章

  • java基础面向对象之继承性(六-2)

    一、继承概念 二、继承的优点 三、继承体现

  • 二、继承

    (1)继承 <1>语法 原则 :父类/子类 基类/派生类是否可以是继承:【子类 is a 父类】...

  • (二)继承

    1.继承写法 公共继承(父类) 继承公共函数(子类) 2.super&this 1.我们可以通过super关键字来...

  • 原型继承

    原型链的继承 1.第一种继承方式(原型链继承) 2.第二种继承方式(第二种继承方式) 3.第三种继承方式(组合继承)

  • js的继承方式

    js的继承方式 一、原型链继承 原型继承的缺点: 二. 构造函数继承 构造函数继承的缺点: 三. 组合式继承 组合...

  • Python学习基础知识之 面向对象的简单基础,常见操作(单继承

    目录 一、继承介绍以及单继承 1.1程序中的继承(extend) 1.2单继承 1.3多继承 二、子类重写父类的同...

  • Java继承

    Java继承 一、继承类型 Java不支持多继承,但是支持多重继承 二、继承的特性 子类拥有父类非 private...

  • C++学习三

    一、运算符重载 二、继承 1、基本 2、多继承、二义性 3、属性二义性 4、虚基类(虚继承) 三、多态(虚函数) ...

  • Java中关于多重继承的问题

    多重继承的含义 一.用接口实现多重继承 二.用内部类实现多重继承

  • Js的继承

    js的继承 @(js)[继承, js, 前端] 组合继承是原性链继承和构造函数继承的合体,它汲取了二者各自的有点,...

网友评论

    本文标题:二、继承

    本文链接:https://www.haomeiwen.com/subject/tksrqhtx.html