OOP3 – 继承与派生

一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。从已有的类(父类)产生一个新的子类,称为类的派生

派生类没有选择地接收基类全部成员(除构造函数和析构函数外),并可以对成员作必要的增加或调整。所以派生类的成员包括两部分:

  • 从基类继承过来的成员
  • 自己增加的成员

单继承: 指一个派生类只从一个基类派生。

多重继承:指一个派生类有两个或多个基类。称为直接基类间接基类

声明派生类的一般形式为:
class 派生类名:[ 继承方式 ] 基类名
{
派生类新增加的成员
};

通过指定继承方式来调整改变基类成员在派生类中的访问属性,包括:
public(公用的):都不变
private(私有的):全变私有 // 默认的继承方式
protected(受保护的):公有变保护

基类成员在派生类中的访问属性

基类中的成员在公用派生类中的访问属性在私有派生类中的访问属性在保护派生类中的访问属性
私有成员不可访问不可访问不可访问
公共成员公用私有保护
保护成员保护私有保护
派生类中的成员在派生类中在派生类外部在下层公用派生类中
派生类中访问属性为公用的成员可以可以可以
派生类中访问属性为受保护的成员可以不可以可以
派生类中访问属性为私有的成员可以不可以不可以
在派生类中不可访问的成员(即基类私有成员等)不可以不可以不可以

派生类的构造函数

基类的构造函数不能被派生类继承。

派生类对象创建时,执行构造函数的顺序

  • 调用基类构造函数,初始化 基类 成员;
  • 初始化 子对象(即派生类中包含的其他类类型成员);
  • 初始化 派生类的成员

在派生类对象释放时,析构函数的执行顺序(反过来了)
1、先执行派生类析构函数
2、再执行其基类的析构函数

派生类构造函数一般形式:
派生类构造函数名(总参数列表):基类构造函数名(参数列表),子对象名(参数列表)
{ 派生类中新增数据成员初始化语句}

#include <iostream>

using namespace std;

// 定义两个基类
class Base1
{
public:
    Base1()
    {
        cout << "+ Constructor of Base1\n";
    }
    ~Base1()
    {
        cout << "- Destructor of Base1\n";
    }
};

class Base2
{
public:
    Base2()
    {
        cout << "+ Constructor of Base2\n";
    }
    ~Base2()
    {
        cout << "- Destructor of Base2\n";
    }
};

// 定义两个子对象类
class Member1
{
public:
    Member1()
    {
        cout << "+ Constructor of Member1\n";
    }
    ~Member1()
    {
        cout << "- Destructor of Member1\n";
    }
};

class Member2
{
public:
    Member2()
    {
        cout << "+ Constructor of Member2\n";
    }
    ~Member2()
    {
        cout << "- Destructor of Member2\n";
    }
};

// 定义派生类
class Derived: public Base2, public Base1
// 先继承Base2,再继承Base1
{
public:
// 先定义一个Member2的对象,再定义一个Member1的对象
    Member2 m2;
    Member1 m1;
    // 定义派生类的构造函数
    Derived(): m1(), m2(), Base1(), Base2()
    // 与定义时相反,按照Member1、Member2、Base1、Base2初始化对象
    {
        cout << "+ Constructor of Derived\n";
    }
    // 定义派生类的析构函数
    ~Derived()
    {
        cout << "- Destructor of Derived\n";
    }
};

int main()
{
    Derived d;
    return 0;
}
1. 初始化基类成员,先调用基类的构造函数
+ Constructor of Base2 
+ Constructor of Base1
2. 初始化子对象成员,再调用子对象类(成员变量)的构造函数
+ Constructor of Member2
+ Constructor of Member1
3. 初始化派生类成员,最后调用派生类的构造函数
+ Constructor of Derived
- Destructor of Derived
- Destructor of Member1
- Destructor of Member2
- Destructor of Base1
- Destructor of Base2
情况示例用谁?说明
初始化基类Base(参数)类名必须显式调用哪个基类构造函数
初始化成员变量m(参数)变量名成员对象或变量名,用哪个变量就写哪个

初始化基类必须显示调用哪个基类的构造函数,所以用类名(x,x,x);初始化本派生类中的变量和成员对象,用变量名(x,x,x)

多重继承派生类的构造函数的形式
基本同于单继承,只是在初始化时包含多个基类构造函数。如,
派生类构造函数名(总参数表列):
基类1构造函数名(参数表列),
基类2构造函数名(参数表列),
基类3构造函数名(参数表列), ……

{派生类中新增数据成员初始化语句}

声明一个教师Teacher类和一个学生类Student,用多重继承的方式声明一个研究生Graduate派生类。教师类中包括数据成员name(姓名)、age(年龄)、title(职称)。学生类中包括数据成员name1(姓名)、sex(性别)、score(成绩)。在定义派生类对象时给出初始化的数据,然后输出这些数据。

#include <iostream>
#include <string>
using namespace std;

// 教师类
class Teacher {
public:
    // 构造函数
    Teacher(string nam, int a, string t) {
        name = nam;
        age = a;
        title = t;
    }

    // 显示教师信息
    void display() {
        cout << "name: " << name << endl;
        cout << "age: " << age << endl;
        cout << "title: " << title << endl;
    }

protected:
    string name;
    int age;
    string title;
};

// 学生类
class Student {
public:
    // 构造函数
    Student(string nam, char s, float sco) {
        name1 = nam;
        sex = s;
        score = sco;
    }

    // 显示学生信息
    void display1() {
        cout << "name: " << name1 << endl;
        cout << "sex: " << sex << endl;
        cout << "score: " << score << endl;
    }

protected:
    string name1;
    char sex;
    float score;
};

// 研究生类,多重继承自 Teacher 和 Student
class Graduate : public Teacher, public Student {
public:
    // 构造函数:调用 Teacher 和 Student 的构造函数,以及初始化私有成员 wage
    Graduate(string nam, int a, char s, string t, float sco, float w)
        : Teacher(nam, a, t), Student(nam, s, sco), wage(w) { }

    // 显示研究生所有信息
    void show() {
        cout << "name: " << name << endl;
        cout << "age: " << age << endl;
...
    }

private:
    float wage;
};

// 主函数
int main() {
    Graduate grad1("Wang-li", 24, 'f', "assistant", 89.5, 1234.5);
    grad1.show();
    return 0;
}
  }
   

多重继承引起的二义性问题

1.两个基类有同名成员

 class A
 { public: 
       int a;                                    // A类中数据成员a
       void display();                     // A类中成员函数display()
 };
 class B
 { public: 
       int a;                                   // B类中数据成员a
       void display();   };            // B类中成员函数display()
class C:public A,public B
 { public: 
       int b;
       void show();
 };

如果在main函数中定义C类对象c1,并调用数据成员a和成员函数display( ):

 C c1; 
 c1.a=3;
 c1.display( );

则会出错,因为编译系统无法判断a是哪一个基类(类A或类B)的数据成员。可以用基类名来限定

c1.A::a =3; // 引用c1对象中的基类A的数据成员a
c1.A::display( );// 调用c1对象中的基类A的成员函数display( )

如果在派生类C中通过派生类成员函数show( )访问基类A的display( )和a,可以不必写对象名,系统默认为当前对象,写成下列形式即可。

A::a =3;          // this->A::a = 3;指当前对象
A::display( );

2.两个基类和派生类三者都有同名成员。

将上面的C类声明改为:

class C:public A,public B
{ public:
int a; // 改b变量为a
void display( ); // 改show()变量为display()
};

则能通过编译,因为此时派生类新增加的同名成员覆盖了基类(类A或类B)中的同名成员。

要在派生类外访问基类A的成员,应指明作用域A。

3.类A和类B是从同一个基类派生的

class N
{ public: 
      int a; 
      void display()
      { cout<<a<<endl;}
 };

class A:public N
{ public: 
       int a1; 
 };
class B:public N
 { public: 
       int a2;
 };

class C:public A,public B
 { public: 
       int a3;
       void show();
 };

通过类N的直接派生类名来指出要访问的是类N的哪一个  派生类中的基类成员。

c1. A::a=3;   // 要访问的是类N的派生类A中的基类成员                               

写成下列方法是错误的,因为存在二义性

c1.a=3; c1.display();     // 错误
c1. N::a=3;   c1. N::display();    // 错误

派生类的析构函数

调用析构函数的顺序:

  • 先执行派生类自己的析构函数,对派生类新增加的成员进行清理
  • 再调用子对象的析构函数,对子对象进行清理
  • 最后调用基类的析构函数,对基类进行清理

 需要通过派生类的析构函数去调用基类的析构函数

虚基类(Virtual Base Class)

作用:使得在继承间接共同基类时只保留一份成员

class 派生类名:virtual 继承方式  基类名
class B:virtual public A  // 声明类B是类A的公用派生类,A是B的虚基类

为了保证虚基类在派生类中只继承一次,应当在该虚基类的所有直接派生类中声明为虚基类。虚拟继承只有在每一层都声明 virtual 才生效;否则最下层类仍然会重复继承基类。

用关键字 virtualA 声明为虚基类,让 BC 共享这份 A,从而 D 只有一份 A

class A { public: int a; };

class B : virtual public A { };
class C : virtual public A { };

class D : public B, public C { };

现在,BC 不再各自复制一份 A,而是都指向 共享的唯一那一份 A。非虚拟继承时:
D 对象中内存布局是 → A(B) + A(C) + 自己;虚拟继承时:D 对象中内存布局是 → A(一份共享) + B + C + 自己

虚基类的初始化通过构造函数的初始化表对虚基类进行初始化

以前,在派生类中只需负责对直接基类初始化,再由其直接基类负责对间接基类初始化

现在,由于虚基类 A 只保留 一份实例;所以必须由最底层类派生类负责初始化 A;在最后的派生类中不仅要负责对其直接基类进行初始化,还要负责对虚基类初始化。

在例5.8的基础上,在Teacher类和Student类之上增加一个共同的基类Person,如图5.25所示。 作为人员的一些基本数据都放在类Person中,在Teacher类和Student类中再增加一些必要的数据。

#include <iostream>
#include <string>
using namespace std;
class Person                                         //定义公共基类Person
{public:
      Person(char *nam,char s,int a)           //构造函数     
       {  strcpy(name,nam); sex=s; age=a; }
 protected:                                                      //保护成员
       char name[20];
      char sex;  
      int age;
};
//定义类Teacher
class Teacher: virtual public Person  // 声明Person为公用继承的虚基类
 {public:                                 
       Teacher( char *nam,char s,int a ,char *t):
       Person(nam,s,a)            // 构造函数
        { 
           strcpy (title, t); 
        }
  protected:                                       // 保护成员
        char title[10];                           // 职称
 };
//定义类Student
class Student: virtual public Person   // 声明Person为公用继承的虚基类                                          
{ public:
       Student( char *nam,char s,int a ,float sco):    
            Person(nam,s,a),score(sco){ }   // 构造函数初始化表
   protected:                                                       // 保护成员
        float score;                                                // 成绩
 };
//定义多重继承的派生类Graduate
class Graduate :public Teacher,public Student    
//声明Teacher和Student类为公用继承的直接基类{ public:
     Graduate( char *nam,char s,int a, char *t,float sco,float w): Person(nam,s,a) ,Teacher(nam,s,a,t),Student(nam,s,a,sco),wage(w){} 
                                                                // 构造函数初始化表
   void show( )                                        // 输出研究生的有关数据
    { cout<<"name:"<<name<<endl; cout<<"age:"<<age<<endl;
      cout<<“sex:”<<sex<<endl;  cout<<"score:"<<score<<endl;
      cout<<"title:"<<title<<endl; cout<<"wages:"<<wage<<endl;
     }
  private:
      float wage;                                          // 工资
 };
int main( )
{ Graduate grad1("Wang-li",'f',24,"assistant",89.5,1234.5);
  grad1.show( );
  return 0;
}

在Graduate类中只保留一份基类的成员,因此可以用Graduate类中的show函数引用Graduate类对象中的公共基类Person的数据成员name、sex和age的值,不需要加类名和域运算符(::),不会产生二义性。

注意如果不是虚基类Graduate类的构造函数应该去掉 Person(nam,s,a),改由各个基类自己初始化自己的部分。

Graduate(char *nam, char s, int a, char *t, float sco, float w): Teacher(nam, s, a, t), Student(nam, s, a, sco), wage(w){ }

基类与派生类的转换

因为派生类中包含从基类继承的成员,所以

  • 可以将派生类的值赋值给基类对象;
  • 在用到基类对象的时候,可以用其子类对象代替。

派生类对象可以向基类对象赋值/初始化

A  a1;    // 定义基类A的对象a1
B  b1;    // 定义A的公用派生类B的对象b1
a1=b1;   // 用派生类B对象b1对基类对象a1赋值
  • 赋值时,只是对基类中的数据成员赋值,舍弃派生类自己的成员。
  • 对成员函数不存在赋值问题
A  a1;        // 定义基类A的对象a1
B  b1;        // 定义A的公用派生类B的对象b1
A& r =a1;   // 正确,定义基类A对象的引用变量r,并用a1对其初始化  
或 A& r=b1; // 正确,定义基类A对象的引用变量r,
// 并用派生类B对象b1对其初始化

r 只是b1中基类部分的别名,r与b1中基类部分共享同一段存储单元。r 与b1具有相同的起始地址

总结来讲就是基类出现的地方都可以用派生类对象替换,此外还有函数参数、

基类对象的指针变量也可以指向派生类对象…

Student stud1(1001,”Li”,87.5);
Graduate grad1(2001,”Wang”,98.5,563.5);
Student *pt=&stud1;
pt->display();
pt=&grad1;

学习笔记如有侵权,请提醒我,我会马上删除
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇