您的位置:首页 > 编程语言 > C语言/C++

c++继承与派生

2015-08-16 21:42 507 查看
1、相关概念

在C++中可重用性(software reusability)是通过继承(inheritance)这一机制来实现的。一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。从已有的类(父类)产生一个新的子类,称为类的派生。派生类是基类的具体化,而基类则是派生类的抽象。

基类名前面有public的称为“公用继承(public inheritance)”。

声明派生类的一般形式为

class 派生类名: [继承方式] 基类名

{

派生类新增加的成员

} ;

继承方式包括: public(公用的),private(私有的)和protected(受保护的),此项是可选的,如果不写此项,则默认为private(私有的)。所谓公用的,是说所定义的变量或函数在类外可以引用,而私有的,则说明定义的变量或函数只能在类内使用,受保护的意为不能被外界引用,但可以被派生类的成员引用。



    构造一个派生类包括以下3部分工作: 

(1) 从基类接收成员。派生类把基类全部的成员(不包括构造函数和析构函数)接收过来。

(2) 调整从基类接收的成员。接收基类成员是程序人员不能选择的,但是程序人员可以对这些成员作某些调整。

(3) 在声明派生类时增加的成员。

此外,在声明派生类时,一般还应当自己定义派生类的构造函数和析构函数,因为构造函数和析构函数是不能从基类继承的。

在派生类中,成员有4种不同的访问属性:  

① 公用的,派生类内和派生类外都可以访问。

② 受保护的,派生类内可以访问,派生类外不能访问,其下一层的派生类可以访问。

③ 私有的,派生类内可以访问,派生类外不能访问。

④ 不可访问的,派生类内和派生类外都不能访问。

2、继承方式

(1) 公用继承(public inheritance)基类的公用成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有。

(2) 私有继承(private inheritance)基类的公用成员和保护成员在派生类中成了私有成员。其私有成员仍为基类私有。

(3) 受保护的继承(protected inheritance)基类的公用成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有。保护成员的意思是: 不能被外界引用,但可以被派生类的成员引用。

在定义一个派生类时将基类的继承方式指定为protected的,称为保护继承,用保护继承方式建立的派生类称为保护派生类(protected derived class),其基类称为受保护的基类(protected base class),简称保护基类。



stud.display( ); //调用基类的公用成员函数,输出基类中数据成员的值

stud.display_1();//调用派生类的公用成员函数,输出派生类中数据成员的值

    对于不需要再往下继承的类的功能可以用私有继承方式把它隐蔽起来,这样,下一层的派生类无法访问它的任何成员。可以知道: 一个成员在不同的派生层次中的访问属性可能是不同的。它与继承方式有关。

私有继承和保护继承方式在使用时需要十分小心,一般不常用。在实际中,常用的是公用继承。

基类的私有成员被派生类继承后变为不可访问的成员,派生类中的一切成员均无法访问它们。如果需要在派生类中引用基类的某些成员,应当将基类的这些成员声明为protected,而不要声明为private。



3、继承类型

1)单继承

一个派生类只从一个基类派生,这称为单继承(single inheritance)。

2)多继承

一个派生类有两个或多个基类的称为多重继承(multiple inheritance)。应避免深层次继承和多继承。

如:

class D:public A,private B,protected C

{类D新增加的成员}

构造函数形式为:

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

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

       各基类的排列顺序任意。派生类构造函数的执行顺序同样为: 先调用基类的构造函数,再执行派生类构造函数的函数体。调用基类构造函数的顺序是按照声明派生类时基类出现的顺序。

 Graduate(string nam,int a,char s, string t,float sco,float w):

        Teacher(nam,a,t),Student(nam,s,sco),wage(w) { }

nam赋给了Teacher和Student两个类的名字属性,实质上是一个人。

而在成员函数中引用数据成员时指明其作用域,如

cout<<″name:″<<Teacher::name<<endl;则不会引起二义性。

A)二义性问题

(1) 两个基类有同名成员



可以用基类名来限定:

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

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



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

 基类的同名成员在派生类中被屏蔽,成为“不可见”的。

c1.display( ); 

c1.A::display();     //表示是派生类对象c1中的基类A中的成员函数display

c1.B::display();     //表示是派生类对象c1中的基类B中的成员函数display



(3) 如果类A和类B是从同一个基类派生的





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

4、派生类的访问属性

(1) 基类的成员函数可以访问基类成员。

(2) 派生类的成员函数可以访问派生类自己增加的成员。

(3) 基类的成员函数访问派生类的成员:

基类的成员函数只能访问基类的成员,而不能访问派生类的成员

(4) 派生类的成员函数访问基类的成员:

如继承中所讨论的。

(5) 在派生类外访问派生类的成员:

在派生类外可以访问派生类的公用成员,而不能访问派生类的私有成员。

(6) 在派生类外访问基类的成员:

如继承中所讨论的。

5、派生类的构造函数和析构函数

在设计派生类的构造函数时,不仅要考虑派生类所增加的数据成员的初始化,还应当考虑基类的数据成员初始化:在执行派生类的构造函数时,调用基类的构造函数。

简单的派生类只有一个基类,而且只有一级派生(只有直接派生类,没有间接派生类),在派生类的数据成员中不包含基类的对象(即子对象)。

1)简单的派生类的构造函数

其一般形式为

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

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



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

 //派生类构造函数

{age=a;   //在函数体中只对派生类新增的数据成员初始化

 addr=ad;

}

在类中对派生类构造函数作声明时,不包括基类构造函数名及其参数表列,只在定义函数时才将它列出。

不仅可以利用初始化表对构造函数的数据成员初始化,而且可以利用初始化表调用派生类的基类构造函数,实现对基类数据成员的初始化。也可以在同一个构造函数的定义中同时实现这两种功能。

Student1(int n, string nam,char s,int a, string ad): Student(n,nam,s), age(a),addr(ad){}

执行构造函数的顺序是: ①派生类构造函数先调用基类构造函数;②再执行派生类构造函数本身(即派生类构造函数的函数体)。

在派生类对象释放时,先执行派生类析构函数~Student1( ),再执行其基类析构函数~Student( )。

2)有子对象的派生类的构造函数

定义派生类构造函数的一般形式为

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

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

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

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

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

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

派生类构造函数的总参数表列中的参数,应当包括基类构造函数和子对象的参数表列中的参数。基类构造函数和子对象的次序可以是任意的,如果有多个子对象,派生类构造函数的写法依此类推,应列出每一个子对象名及其参数表列。

  Student1(int n, string nam,int n1, string nam1,int a, string ad)

      :Student(n,nam),monitor(n1,nam1) //派生类构造函数

      {......}

#include <iostream>

#include <string>

using namespace std;

class Student

{

public:

Student(int n,string nam)

{

num=n;

name=nam;

}

void display()

{

cout<<"num:"<<num<<endl<<"name:"<<name<<endl;

}

protected:

int num;

string name;

};

class Student1:public Student

{

public:

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

{

age=a;

addr=ad;

}

void show()

{

cout<<"this student is:"<<endl;

display();

cout<<"age:"<<age<<endl;

cout<<"addr:"<<addr<<endl<<endl;

}

void show_monitor()

{

cout<<endl<<"Class monitor is:"<<endl;

monitor.display();

}

private:

Student monitor;

int age;

string addr;

};

int main()

{

Student1 stud1(10010,"walg le",10001,"Li-sum",19,"115 beijing road,shanghai");

stud1.show();

stud1.show_monitor();

return 1;

}

3)多层派生时的构造函数

 Student1(int n,char nam[10],int a):Student(n,nam)//派生类构造函数

 Student2(int n, string nam,int a,int s):Student1(n,nam,a)

在声明Student2类对象时,调用Student2构造函数;在执行Student2构造函数时,先调用Student1构造函数;在执行Student1构造函数时,先调用基类Student构造函数。初始化的顺序是: 

① 先初始化基类的数据成员。

② 再初始化Student1的数据成员。

③ 最后再初始化Student2的数据成员。

当不需要对派生类新增的成员进行任何初始化操作时,派生类构造函数的函数体可以为空,即构造函数是空函数

Student1(int n, strin nam,int n1, strin nam1):Student(n,nam),

monitor(n1,nam1) { }

      如果在基类中没有定义构造函数,或定义了没有参数的构造函数,那么在定义派生类构造函数时可不写基类构造函数。

4)派生类的析构函数 

    在派生时,派生类不能继承基类的析构函数,需要通过派生类的析构函数去调用基类的析构函数。在派生类中可以根据需要定义自己的析构函数,用来对派生类中所增加的成员进行清理。在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子对象进行清理。调用的顺序与构造函数相反: 先执行派生类自己的析构函数,对派生类新增加的成员进行清理,然后调用子对象的析构函数,最后调用基类的析构函数。

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: