34
继继 (inheritance) 继继继继继继继继继继继继继继继继继继继继继继继继继 继继继继继继继继继继继继继继继 继继继继继继继 继继继继继 ,。 继继继继继继继 继继继继继 继继继继继继继继继继继继继继继继继继继 ,。。 继继继继继继继继继继继继继 继继继继inheritance 继继继继 )( class 继继继 ), C++ 继继继继继继class derivation 继继继继继继继继继 继继继继继继继 )。 base class 继继superclass 继继继继 ), 继继继deri ved class 继继subclass )。 继继继继继继继继继继继继 继继继继继继继继继继继继继继 继继继继继继继继继继继 ,。 继继 继继继继继继继 继继继继继继继继继继继继继继 继继继继继 ,,, 继继继继继继继继

继承 (inheritance) :

  • Upload
    edie

  • View
    120

  • Download
    1

Embed Size (px)

DESCRIPTION

继承与派生. 继承 (inheritance) : 继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。 这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。 体现了由简单到复杂的认识过程 。. - PowerPoint PPT Presentation

Citation preview

Page 1: 继承 (inheritance) :

继承 (inheritance) : 继承是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加功能。这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构。体现了由简单到复杂的认识过程。

  通过继承( inheritance )可对类( class )分层, C++ 通过类的派生( class derivation )的机制来支持继承。被继承的类称为基类( base class )或超类( superclass ),新的类为派生类( derived class )或子类( subclass )。

派生反映了事物之间的联系,事物的共性与个性之间的关系。 派生与独立设计若干相关的类,前者工作量少,重复的部分可以从基类继承来,不需要单独编程实现代码复用。

Page 2: 继承 (inheritance) :

继承与派生的概念 虚基类

多重继承与派生类成员标识

派生类应用讨论 派生类的构造函数与析构函数

Page 3: 继承 (inheritance) :

一、类的派生与继承

二、公有派生与私有派生

继承与派生的概念

Page 4: 继承 (inheritance) :

1、派生类的定义格式:class 派生类名:访问限定符 基类名 1 ,访问限定符 基类名 2 ,……,访问限定符 基类名 n{private:

成员表 1 ; // 派生类增加或替代的私有成员public:

成员表 2 ; // 派生类增加或替代的公有成员protected:

成员表 3 ; // 派生类增加或替代的保护成员} ; // 分号不可少

其中基类 1,基类 2,……是已声明的类。 在派生类定义的类体中给出的成员称为派生类成员,它们是新增加成员,它们给派生类添加了不同于基类的新的属性和功能。派生类成员也包括取代基类成员的更新成员。

一、类的派生与继承

Page 5: 继承 (inheritance) :

基 类1

基 类2

…… 基 类n

派生类 1 派生类 2

基类

派生类 1 派生类 2

( a )多重继承 ( b )单继承

多重继承与单继承

一个基类可以直接派生出多个派生类

派生类可以由多个基类共同派生出来,称多重继承。

2、多重继承: 如果一个派生类可以同时有多个基类,称为多重继承( multiple-inheritance )。3、单继承: 派生类只有一个直接基类称为单继承( single-inheritance )。

Page 6: 继承 (inheritance) :

在派生过程中,派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。直接参与派生出某类称为直接基类,而基类的基类,以及更深层的基类称为间接基类。  类族: 同时一个基类可以直接派生出多个派生类。这样形成了一个相互关联的类族。如 MFC 就是这样的族类,它由一个 CObject类派生出 200 个 MFC 类中的绝大多数。

4、多层次继承:

一、类的派生与继承

Page 7: 继承 (inheritance) :

1)、吸收基类的成员

2)、改造基类成员

3)、发展新成员

4)、重写构造函数与析构函数

不论是数据成员,还是函数成员,除构造函数与析构函数外全盘接收

声明一个和某基类成员同名的新成员 , 派生类中的新成员就屏蔽了基类同名成员称为同名覆盖( override )

派生类新成员必须与基类成员不同名,它的加入保证派生类在功能上有所发展。

5、派生编程步骤:

编制派生类时可分四步

Page 8: 继承 (inheritance) :

注意:Ø在第二步中,新成员如是成员函数,参数表也必须一样,否则是重载。Ø第三步中,独有的新成员才是继承与派生的核心特征。Ø第四步是重写构造函数与析构函数,派生类不继承这两种函数。不管原来的函数是否可用一律重写可免出错。

Page 9: 继承 (inheritance) :

6 、访问控制: 亦称为继承方式,是对基类成员进一步的限制,访问控制有三种: 公有( public )方式,亦称公有继承 保护( protected )方式,亦称保护继承 私有( private )方式, 亦称私有继承。

不可直接访问 不可直接访问 private

不可直接访问 private protected

不可直接访问 private public 私有派生 不可直接访问 不可直接访问 private

不可直接访问 protected protected

可直接访问 public public 公有派生

在派生类对象外访问派生类对象的基类成员

在派生类中对基类成员的访问限定

基类中的访问限定

派生方式

Page 10: 继承 (inheritance) :

例题:分析下列程序的访问权限,并回答所提出的问题

#include<iostream.h>

class A

{

public :void f1();

protected:int j1;

private:int i1;

};

class B:public A

{

public:

void f2();

protected:

int j2;

private:

int i2;

};

class C:public B

{

public:

void f3();

};

回答下列问题:

1 、派生类 B 中成员函数 f2() 能否访问基类 A 中的成员: f1(),i1,j1?

2 、派生类 B 的对象能否访问基类 A 中的成员:f1(),i1,,j1?

3 、派生类 C 中的成员函数能否访问基类 B 中的成员: f2(),i2,,j2?

Page 11: 继承 (inheritance) :

1 、派生类构造函数的定义:派生类名 :: 派生类名(参数总表) : 基类名 1 (参数名表1 ),基类名 2 (参数名表 2 ),……,基类名 n (参数名表 n ),成员对象名 1 (成员对象参数名表 1 ),……,成员对象名 m (成员对象参数名表 m ) {……// 派生类新增成员的初始化;

} // 所列出的成员对象名全部为新增成员对象的名字注意: 1 )在构造函数的声明中,冒号及冒号以后部分必须略去。 2 )基类名仅指直接基类,写了底层基类,编译器认为出错。 3 )冒号后的基类名,成员对象名的次序可以随意,这里的次序与调用次序无关。

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

Page 12: 继承 (inheritance) :

2 、派生类构造函数各部分执行次序: 1 )调用基类构造函数,按它们在派生类定义的先后顺序,顺序调用。 2 )调用成员对象的构造函数,按它们在类定义中声明的先后顺序,顺序调用。3 )派生类的构造函数体中的操作。注意: 1 )在派生类构造函数中,只要基类不是使用无参的默认构造函数都要显式给出基类名和参数表。 2 )如果基类没有定义构造函数,则派生类也可以不定义,全部采用系统给定的默认构造函数。 3 )如果基类定义了带有形参表的构造函数时,派生类就应当定义构造函数。

Page 13: 继承 (inheritance) :

3 、析构函数: 析构函数的功能是作善后处理工作。 派生类的析构函数只需把派生类新增一般成员处理好就可以了,而对新增的成员对象和基类的善后工作,系统会自己调用成员对象和基类的析构函数来完成。 析构函数各部分执行次序与构造函数相反,首先对派生类新增一般成员析构,然后对新增对象成员析构,最后对基类成员析构。

Page 14: 继承 (inheritance) :

例题:分析下列程序 的输出结果

代码一:

#include<iostream.h>

class A

{

public:

A(){a=0;cout<<“A’s default constructor called.\n”;

A(int i){a=i,;cout<<“A’s constructor called.\n”);

~A(){cout<<“ A’s destructor called.\n”;}

void Print() const {cout<<a<<“,”}

int GetA(){return a;}

private:

int a;

}

Page 15: 继承 (inheritance) :

class B:public A

{

public:

B ()

{b=0;

cout<<“A’s default constructor called.\n”;}

B(int I,int j,int k);

~B(){cout<<“B’s destructor called.\n”;}

void Print();

private:

int b;

A aa;

};

B::B(int I,int j,int k):A(i),aa(j)

{

b=k;

cout<<“B’s constructor called”;

}

void B:;Print()

{A;:Print();

cout<<b<<“,”<<aa.Geta()<<endl;

}

void main()

{

B bb[2];

bb[0]=B(1,2,5);

bb[1]={3,4,7};

for(int i=0;i<2;i++)

bb[i].Print();}

Page 16: 继承 (inheritance) :

代码二:

#include<iostream.h>

class M

{public:

M(){m1=m2=0;}

M(int I,int j){m1=I;m2=j;}

void print(){cout<<m1<<“,”<<m2<<“,”;

~M(){cout<<“M’s destructor called.\n”;}

private:

int m1,m2;

};

class N:public M

{public:

N(){n=0;}

N(int I,int j,int k);

void print()

{M::print();

cout<<n<<endl;

}

~N()

{cout<<“N’s destructor called\n”;}

private:

int n;}

N::N(int I,int j,int k):M(I,j),n(k){}

void mian()

{

N n1(5,6,7);

n2(-2,-3,-4);

n1.print(),n2.print();}

Page 17: 继承 (inheritance) :

例题:由“人”类派生出“学生”类。我们希望基类和派生类共享相同的公有方法 , 因此,只能采用公有派生来实现。

人类:enum Tsex{mid,man,woman}; class Person{

string IdPerson; // 身份证号 ,18 位数字string Name; // 姓名Tsex Sex; // 性别int Birthday; // 生日 , 格式 1986 年 8 月 18 日写作 19860818string HomeAddress; //家庭地址

public:Person(string, string,Tsex,int, string);// 构造函数

Person(); // 默认的构造函数 ~Person(); // 析构函数

Page 18: 继承 (inheritance) :

void SetName(string); // 修改名字string GetName(){return Name;} // 提取名字void SetSex(Tsex sex){Sex=sex;} // 修改性别Tsex GetSex(){return Sex;} // 提取性别void SetId(string id){IdPerson=id;}// 修改身份证号string GetId(){return IdPerson;} // 提取身份证号void SetBirth(int birthday)

{Birthday=birthday;} // 修改生日int GetBirth(){return Birthday;} // 提取生日void SetHomeAdd(string ); // 修改住址string GetHomeAdd(){return HomeAddress;} // 提取住址void PrintPersonInfo(); // 输出个人信息

};

// 接口方法:

Page 19: 继承 (inheritance) :

派生的学生类 :

class Student:public Person{ // 定义派生的学生类string NoStudent; //学号course cs[30]; //30门课程与成绩

public: Student(string id, string name,Tsex sex,int birthday, string homeadd, string nostud);

// 注意派生类构造函数声明方式 Student(); // 默认派生类构造函数 ~Student(); // 派生类析构函数 SetCourse(string ,int); //课程设置 int GetCourse(string ); //查找成绩 void PrintStudentInfo(); //打印学生情况};

struct course{ string coursename; int grade;};

Page 20: 继承 (inheritance) :

学生 ( 单继承)

教职工 ( 单继承)

兼职教师 ( 单继承 )

教师 ( 单继承)

行政人员 ( 单继承 )工人 ( 单继承 ) 研究生 ( 单继承)

行政人员兼教师( 多重继承 )

在职研究生( 多重继承 )

研究生助教( 多重继承 )

学校人员与人继承关系

派生出来的新类同样可以作为基类再继续派生出更新的类,依此类推形成一个层次结构。

多重继承与派生类成员标识

多重继承实例:

Page 21: 继承 (inheritance) :

二义性问题 :在上图中,比如行政人员兼教师,在其基类教师中有一个“教职工编号”,另一基类行政人员中也有一个“教职工编号”,如果只讲教职工编号到底那是哪一个基类中的呢?或者这两者是一回事?

解决的方法 :1) 通常采用作用域分辨符“ ::” ,进行区别:基类名 :: 成员名 ; // 数据成员基类名 :: 成员名(参数表) ; // 函数成员2)引入”虚基类”

多重继承与派生类成员标识

Page 22: 继承 (inheritance) :

例题 : 分析下列程序的输出结果

#include<iostream.h>

class A

{

public:

A(int i){a=I;cout<<“con A\n”;

void print(){cout<<a<<endl;

~A(){cout<<“des A\n”;}

private:

int a;

};

class B1:public A

{

public :

B1(int I,int j):A(i){b1=j;cout<<“con B1\n”;

void print()

{A::print();

cout<<b1<<ednl;}

~B1(){cout<<des B1\n”};

private:

int b1;

};

class B2:public A

{

public:

B2(int I,int j):A(i)

{b2=j;cout<<“con B2\n”;

}

Page 23: 继承 (inheritance) :

void print(){A::print();cout<<b2<<ednl;}~B2(){cout<<des B2\n”};private:int b2;};class C:public B1,B2{public:C(int I,int j,int k,int l,int m):B1(I,j),B2(k,l),c(m){cout<<“con C\n”;}void print(){B1::print();B2::print();cout<<c<<endl;}~C(){cout<<“des C\n”;}

private:int c;

};

void main()

{

C c1(1,2,3,4,5);

c1.print();

}

result:

con A1

con B1

con A

con B2

con c

1

2

3

4

5

des C

des B2

des A

des B1

des A

Page 24: 继承 (inheritance) :

注意:virtual 关键字只对紧随其后的基类名起作用 :例如:class Student:virtual public Person{...};class Employee:virtual public Person{...};

一、虚基类 (virtual base class) 定义:class 派生类名 :virtual 访问限定符 基类类名{...};或class 派生类名 : 访问限定符 virtual 基类类名{...};

虚基类

Page 25: 继承 (inheritance) :

例如 :

class A

{

public:

void f();

protected:

int a;

};

class B:virtual public A

{

protected:

int b;};

class C:virtual public A

{ protected:

int c;};

class D:public B,public C

{

public:

int g();

private:

int d;};

由于使用了虚基类 , 使得类 A ,类 B ,类 C 和类 D 之间的关系用 DAG 图示法表示如下:

A{f(),a}

B{b} C{c}

D{g(),d}

Page 26: 继承 (inheritance) :

说明:引进虚基类后,派生类的对象中只有一个虚基类的子对象。当一个类有虚基类时,编译系统将为该类的对象定义一个指针成员,让它指向虚基类的子对象。该指针被称为虚基类指针。上例中,各类的存储结构如下图所示:

B

C

A

D

虚基类指针

Page 27: 继承 (inheritance) :

二、虚基类的构造函数

如果一个派生类有一个直接或者间接的虚基类,那么派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用,如果未列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象。

从虚基类直接或者间接继承的派生类中的构造函数的成员初始化列表中都要列出这个虚基类构造函数的调用。但是,只有最后的派生类对象调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数的调用在执行中被忽略。

在一个成员初始化列表中出现对虚基类和非虚基类构造函数的调用,则虚基类的构造函数先于非虚基类的构造函数的执行。

Page 28: 继承 (inheritance) :

例题 : 分析下列程序的输出结果#include<iostream.h>

class A

{

public:

A(const char *s)

{

cout<<s<<endl;}

~A(){}

};

class B:virtual public A

{

public:

B(const char *s1,const char *s2):A(s1)

{cout<<s2<<endl;}};

class C:virsual public A

{

public:

C(const char *s1,const char *s2):A(s1)

{cout<<s2<<endl;

}

};

class D:public B,public C

{

public:

D(const char *s1,const char *s2,const *s3,const *s4):B(s1,s2),C(s1,s3),A(s1)

{

cout<<s4<<endl;

}};

Page 29: 继承 (inheritance) :

void main()

{

D *ptr=new D(“class A”,”class B”,”class C”,”class D”);

delete ptr;

}

运行结果 :

class A

class B

class C

class D

Page 30: 继承 (inheritance) :

例题 :分析下列程序的输出结果

#include<iostream.h> class A { public: int n; }; class B:public A{}; class C:public A{}; class D:public B,public C { int getn(){return B::n;} }; void main() { D d; d.B::n=10; d.C::n=20; cout<<d.B::n<<","<<d.C::n<<endl; }

D 类是从类 B 和类 C派生的而类 B 和类 C又都是从类 A 派生的,但各有自己的副本。所以对于对象 d , d.B::n 与 d.C::n 是两个不同的数据成员它们互无联系。 所以输出为: 10 , 20

Page 31: 继承 (inheritance) :

例题 :假设图书馆的图书包含书名、编号、作者属性, 读者包含姓名和借书证属性,每位读者最多可借 5本书,编写程序列出某 读者的借书情况。

分析:设计一个类, 从它派生出图书类 book 和读者类 reader ,在 reader 类中有一个 rentbook() 成员函数用于借阅图书。 程序代码如下:#include<iostream.h> #include<string.h> class object { char name[20]; int no; public: object(){} object(char na[],int n) { strcpy(name,na);no=n; } void show() { cout<<name<<“(”<<no<<“)”; } } ;

Page 32: 继承 (inheritance) :

class book:public object { char author[10]; public: book(){} book(char na[],int n,char auth[]):object(na,n) { strcpy(author,auth); } void showbook() { show(); cout<<" 作者: "<<author; } }; void main() { book b1("C 语言 ",100," 谭浩强 "),b2(" 数据结构 ",110," 严蔚敏 "); reader r1(" 王华 ",1234); r1.rentbook(b1); r1.rentbook(b2); r1.showreader(); }

Page 33: 继承 (inheritance) :

class reader:public object { book rent[5]; int top; public: reader(char na[],int n):object(na,n){top=0;} void rentbook(book &b) { rent[top]=b; top++; } void showreader() { cout<<" 读者: ";show(); cout<<endl<<" 所借图书 :"<<endl; for(int i=0;i<top;i++) { cout<<" "<<i+1<<":"; // 5 个空格 rent[i].show(); cout<<endl; } } };

void main() { book b1("C++ 语言 ",101," 谭浩强 "),b2(" 数据结构 ",110," 严蔚敏 "); reader r1(" 王华 ",1234); r1.rentbook(b1); r1.rentbook(b2); r1.showreader(); }

Page 34: 继承 (inheritance) :

总结:1. 派生类具有基类的所有成员。2. 派生类的构造函数自动执行基类的构造函数,且基类的构造函数先执行。基类构造函数的参数由派生类传递。3. 派生类中可对已有的成员进行重新定义。 4. 可定义多重派生类。且可利用虚基类使派生类只保持一个基类的成员拷贝。