C++及数据结构复习笔记(七)(继承与派生)

1.10 继承与派生

1.10.1 继承与派生的概念

       在C++中,可重用性是通过继承这一机制来实现的。所谓继承,就是在一个已存在的类的基础上建立一个新的类。已存在的类称为基类,新建立的类成为派生类。(与对象的复制做区别)一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。

       派生类继承了基类的所有数据成员和成员函数,并可以对成员作必要的增加或调整。

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

       多重继承:一个派生类可以从多个基类派生。

       基类和派生类的关系:派生类是基类的具体化,而基类则是派生类的抽象。

1.10.2 派生类的声明方式

声明派生类的一般形式:

       class 派生类名: 集成方式 积累名

       {

         派生类新增加的成员;

       }

Eg:假设已经声明了一个基类Student

class Student1: public Student //声明基类是Student,集成方式是public
{
  public:
void display_1()  //新增加的成员函数
{
cout<<”age:”<<age<<endl;
  cout<<”address:”<<addr<<endl;
}
  private:
int age;  //新增加的数据成员
string addr;
};

      继承方式包括:public公用的,private私有的和protected受保护的。C++默认为私有继承。派生类会接收基类的所有成员,无法选择。但是可以改变积累成员在派生类中的访问属性。也可以通过同名覆盖来使新成员取代基类成员。

1.10.3 派生类成员的访问属性

公用继承public

基类的公用成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有。其基类称为公用基类。

私有继承private

基类的公用成员和保护成员在派生类中成了私有成员,其私有成员仍为基类私有。其基类称为私有基类。

受保护的继承protected

基类的公用成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有。其基类称为保护基类。

三种继承方式的访问属性

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

私有成员

共用成员

保护成员

public

不可访问

公用

保护

private

不可访问

私有

私有

protected

不可访问

保护

保护

       不管是何种继承方式,基类的私有成员只有基类的成员函数才可以引用,派生类的成员函数是无法引用的。即基类的私有成员是不可见的,它在被派生类继承后变为不可访问成员

       不能通过派生类的对象引用从私有基类继承过来的任何成员。派生类的成员函数不能访问私有基类的私有成员,但可以访问私有基类的公用成员。如何调用私有基类的公用成员函数如下:

void Student::display_1()//输出学号、姓名、分数、年纪和地址
{
  display();//基类中的公用成员函数,目的是输出学号、姓名和分数
  cout<<”age:”<<age<<endl;
  cout<<”address:”<<addr<<endl;
}
int main()
{
  Student stud1;
  stud1.display();//在main中调用派生类中的公用成员函数
  return 0;
}

       回顾:保护成员不能被类外访问,类似于私有成员。但有一点与私有成员不同,保护成员可以被派生类的成员函数引用!如果希望在派生类中可以访问基类的“私有”成员,则应当把它们声明为保护成员。

例 1.10.1在派生类中引用保护成员

#include<iostream>
#include<string>
using namespace std;
class Student  //声明基类
{
  public:
void display();//基类共用成员函数
  protected:
int num;//基类保护成员
string name;
char sex;
};
void Student::display()//定义基类成员函数
{
  cout<<”num:”<<num<<endl;
  cout<<”name:”<<name<<endl;
  cout<<”sex:”<<sex<<endl;
}
class Student1: protected Student   //用protected方式声明派生类
{
  public:
void display1();//声明派生类共用成员函数
  private:
int age;//派生类私有数据成员
string addr;
};
void Student1::display1()//定义派生类共用成员函数
{
  display();
  cout<<”age:”<<age<<endl;
  cout<<”address:”<<addr<<endl;
}
int main()
{
  Student1 stud1;
  stud1.display();//合法,stud1是派生类中的共用成员函数
  stud1.num=10023;//错误,外界不能访问派生类的保护成员
  return 0;
}

1.10.4 派生类的构造函数与析构函数

       构造函数的主要作用是对数据成员进行初始化,且基类的构造函数是不能继承的。对继承过来的基类成员的初始化工作主要由派生类的构造函数承担。

一、简单的派生类的构造函数

       简单的派生类只有一个基类且只有一级派生,在派生类的数据称成员中不包含基类的子对象。派生类构造函数首行的写法格式:

       派生类构造函数名 (总参数列表): 基类构造函数名(参数列表)

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

       Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s)

例 1.10.2简单的派生类的构造函数

#include<iostream>
#include<string>
using namespace std;
class Student  //声明基类
  public:
Student(int n,string nam,char s):num(n),name(nam),sex(s){}//基类构造函数
~Student(){}  //基类析构函数
  protected:
int num;
string name;
char sex;
};
class Student1: public Student  //声明派生类Student1
{
  public:
    Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s)
    //派生类构造函数
    {
age=a;在函数体中仅对派生类新增的数据成员进行初始化
  addr=ad;
}
void show();
  private:
int age;
string addr;
};
void Student1::show()
{
  cout<<”num:”<<num<<endl;
  cout<<”name:”<<name<<endl;
  cout<<”sex:”<<sex<<endl;
  cout<<”age:”<<age<<endl;
  cout<<address:”<<addr<<endl;
}
int main()
{
  Student1 stud1(10010,”Wang li”,’f’,19,”115 Beijing Road, Shanghai”);
  Student1 stud2(10011.”Zhang fun”,’m’,21,”213 Shanghai Road, Beijing”);
  stud1.show();
  stud2.show();
  return 0;
}

       实参首先传给派生类构造函数的形参,然后派生类构造函数再将前面几个传递给基类构造函数的形参。上例的具体过程见图1.5。

图 1.5 参数传递过程

       在类内声明派生类的构造函数,然后在类外定义派生类的构造函数

class Student1: protected Student
{
  public:
    Student1(int n,string nam,char s,int a,string ad);
};
Student1::Student1(int n,string nam,char s,int a,string ad):Student(n,nam,s)
//定义时才给出基类构造函数名及其参数列表,声明时不给出
{
  age=a;
  addr=ad;
}

二、有子对象的派生类构造函数

       类的数据成员还可包含对象,称为子对象。

格式:

       派生类构造函数名(总参数列表):基类构造函数名(参数列表),子对象名(参数列表)

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

       Student1(intn,string nam,int n1,string nam1,int a,string ad): Student(n,nam),monitor(n1,nam1)

       执行派生类构造函数的顺序:

       ①   调用基类构造函数,对基类数据成员初始化。

       ②   调用子对象构造函数,对子对象数据成员初始化。

       ③   在执行派生类构造函数本身,对派生类数据成员初始化。

       在派生时,派生类是不能够继承基类的析构函数的,也需要通过派生类的析构函数去调用基类的析构函数。

1.10.5 多重继承

       一个派生类有2个或多个基类,派生类从2个或多个基类中继承所需的属性。如果已经声明了类A和类B,则可以声明多重继承的派生类C:

       class C: public A, protected B

          {类C新增加的成员};

一、多重继承派生类的构造函数

       类似于单继承,但是在初始表中包含多个基类构造函数。多重继承派生类构造函数首行的写法格式:

       派生类构造函数名(总参数列表):基类1构造函数(参数列表),基类2构造函数(参数列表)…

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

例 1.10.3声明一个教师类和一个学生类,然后用多重继承的方式声明一个研究生类。教师类包括数据成员name,age和title(职称)。学生类包括数据成员name1,sex和score。

#include<iostream>
#include<string>
using namespace std;
class Teacher  //声明类Teacher
{
  public:
Teacher(string nam,int a,string t):name(nam),age(a),title(t){}//构造函数初始化表
void display();//输出教师的数据
  protected:
string name;
int age;
string title;
};
void Teacher::display()
{
  cout<<”name:”<<name<<endl;
  cout<<”age:”<<age<<endl;
  cout<<”title:”<<title<<endl;
}
class Student  //声明类Student
{
  public:
Student(char nam[],char s,float sco)
{
  strcpy(name1,nam);
sex=s;
  score=sco;
}
void display1();
  protected:
string name1;
char sex;
float score;
};
void Student::display1()
{
  cout<<”name:”<<name1<<endl;
  cout<<”sex:”<<sex<<endl;
  cout<<”score:”<<score<<endl;
}
class Graduate: public Teacher,public Student  //声明多重继承的派生类Graduate
{
  public:
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();//输出研究生有关数据
  private:
float wage;//工资
};
void Graduate::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;
}
int main()
{
  Graduate grad1(“Wang li”,24,’f’,”assistant”,89.5,1234.5);
  grad1.show();
  return 0;
}

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

       继承的成员同名会产生二义性问题。

两个基类有同名成员

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

类A中有公用数据成员a和公用成员函数display(),类B也中有公用数据成员a和公用成员函数display()。C类是A和B的派生类。

类A、B和C三者都有公用数据成员a和公用成员函数display()。

c1.A::a=3;

//引用c1对象中基类A的数据成员a

c1.A::display();

//引用c1对象中基类A的成员函数

C c1;

c1.a=3;

c1.display();

//此时访问的是派生类C中的成员,出现同名覆盖

      同名覆盖:基类的同名成员在派生类中被屏蔽,成为不可见的,或者说,派生类新增加的同名成员覆盖了基类中的同名成员。解决这种情况需要虚函数。

       如果类A和类B都是从一个基类派生的,情况如下图1.6:

图 1.6 二义性问题的一种情况

       解决这种情况需要用到虚基类。

1.10.6 虚基类

一、虚基类的声明

       由图1.6可知,如果一个派生类有多个直接接类,而这些直接基类又有一个共同的基类,则在最终的派生类中会保留该间接共同基类数据成员的多份同名成员。为避免二义性,必须在派生类对象名后增加直接基类名,如c1.A::display()。C++提供虚基类的方法,使得在继承间接共同基类时只保留一份成员。

声明虚基类的一般形式:

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

Eg:

class A //声明基类A
{……};
class B: virtual public A  //声明类B是类A的共用派生类,A是B的虚基类
{……};

       虚基类不是在声明基类时声明的,而是在声明派生类时声明的。virtual关键字不仅可以声明虚基类,也用于声明虚函数。在声明派生类时,需要把virtual加到相应的继承方式前面。经过这样的声明后,当基类通过多条派生路径被一个派生类级城市,基类成员只保留一次。如图1.7。

图 1.7 虚基类的情况

       为了保证虚基类在派生类中只被继承一次,应当在该基类的所有直接派生类中声明为虚基类,否则仍会出现对基类的多次继承。

二、虚基类的初始化

构造函数形式

Eg:

派生类名(总参列表):间接基类(参数列表),直接基类1(参数列表),直接基类2(参数列表)

class A

class B:virtual public A

{B(int n,int a):A(n)};

class C:virtual public A

{C(int n,int a):A(n)};

class D:public B,public C

{D(int n,int a):A(n),B(n,a),C(n,a)};

       由于虚基类在派生类中只有一份数据成员,所以这份数据成员的初始化必须由派生类直接给出。在最后的派生类中,不仅需要负责对直接基类进行初始化,也需要对虚基类进行初始化。

例 1.10.4 在例1.10.3的基础上,在教师类和学生类之上添加一个共同的基类Person。
#include<iostream>
#include<string>
using namespace std;
class Person  //声明公共基类Peroson
{
  public:
Person(string nam,char s,int a):name(name),sex(s),age(a){}//构造函数
  protected:
string name;
char sex;
int age;
};
class Teacher:virtual public Teacher 
//声明Person的直接派生类Teacher,且声明Person为公用继承的虚基类
{
  public:
Teacher(string nam,char s,int a,string t):Person(nam,s,a)//构造函数
{title=t;}
  protected:
string title;
};
class Student:virtual public Person  
//声明Person的直接继承类Student,且声明Person为公用继承的虚基类
{
  public:
Student(string nam,char s,int a,float sco):Person(nam,s,a)
{score=sco;}
  protected:
float score;
};
class Graduate: public Teacher,public Student  //声明多重继承的派生类Graduate
{
  public:
Graduate(string nam,char s,int a,string t,float sco,float w):Person(nam,s,a),Teacher(name, s,a,t),Student(nam,s,a,sco)  //在派生类中的构造函数,对直接基类和间接基类进行初始化
{wage=w;}
void show();
  private:
float wage;
};
void Graduate::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;
}
int main()
{
  Graduate grad1(“Wang li”,’f’,24,”assistant”,89.5,1234.5);
  grad1.show();
  return 0;
}

猜你喜欢

转载自blog.csdn.net/lao_tan/article/details/80976256