《C++程序设计第11章 继承和派生教学课件.ppt》由会员分享,可在线阅读,更多相关《C++程序设计第11章 继承和派生教学课件.ppt(61页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、C+程序设计第11章 继承和派生教学课件第十一章第十一章 继承和派生继承和派生http:/ 继承和派生的概念 单继承 多重继承 派生类的继承方式3http:/ 本章内容提要4一、继承和派生的概念二、继承的类型和选择继承的方式 三、将派生类对象转换为基类对象 四、派生类的构造和析构 五、使用基类成员 六、基类类型的指针和引用http:/ 单继承 继承就是在一个已经存在的类的基础上重新定义一个新类,新类中只写出增加的成员,这样可以使程序简单,没有过多的重复成员。现在把已经存在的类称为“基类”或者“父类”,新的类称为“派生类”或者“子类”。例如,定义一个学生,而学生中又包括大学生、中学生和小学生,大
2、学生中又包括普通大学生和大专生,中学生中包括普通中学生和中专生。单继承的示意图如图11-1所示。5http:/ 单继承图11-1中的学生就是基类,大学生、中学生和小学生都是学生派生出来的分支,这3个分支下面又分出了普通大学生、大专生、普通中学生和中专生。这些派生出来的分支都叫做派生类。派生出来的新类从已有的类中获取已有的数据,叫做类的继承。通过类的继承,子类就获得了父类的特性,从父类的基础上建立一个新的子类,就叫做类的派生。派生类中包含了基类中所有的数据成员和成员函数,还可以根据需要,增加一些新的数据成员和成员函数,这样可以使类中的数据更加具体、多样。在图11-1中,一个派生类只有一个基类,这
3、种继承方式称为单继承。6http:/ 多重继承图11-2中的研究生为派生类,而它的基类有3个,研究生类就继承了这3个基类的所有特性,而自己本身还可以增加更多的特性,这样就把抽象的类变得具体,既有基类的特性,也有自身特性的类型。在了解了继承和派生的概念之后,最重要的是要知道怎样使用继承和派生。派生类的一般形式如下。class 派生类名称:继承方式 基类名称派生类中新增的成员;7http:/ 派生类的继承方式派生类的继承的方式有3种:public、private和protected。如果不写明继承方式,系统就会自动默认继承方式为private类型。class Studentprivate:int
4、num;string name;char sex;public:void display()cout num:num endl;cout name:name endl;cout sex:sex endl;8http:/ 派生类的继承方式class NewStudent:public Student /定义派生类NewStudentprivate:int age;/增加数据成员agestring addr;/增加数据成员addrpublic:void new_display()cout age:age endl;cout addr:addr endl;9http:/ 派生类的继承方式派生类的继承
5、方式如图11-3所示。从图11-3可以看出,派生类可分为两部分:从基类继承过来的成员和派生类中新增加的成员。在构造一个派生类时应该考虑以下3种情况,这也是派生类的特性。(1)派生类在接收基类的成员时,系统会将基类中的所有成员都接收到派生类中,但基类中的构造函数和析构函数是不会被派生类接收的。10http:/ 派生类的继承方式(2)在基类到派生类的继承过程中,可以改变基类成员在派生类中的属性,如将基类中的公用数据成员和成员函数指定为在派生类中为private类型,在派生类外就不能调用private类型的成员了。(3)因为在派生类中不需要写出基类中的成员,所以在派生类中要写出的就是需要增加的成员,
6、这部分在C+编程中体现出了派生类对类的扩展功能。在派生类成员的访问问题上,有几点需要读者注意:(1)和基类成员函数一样,派生类成员函数可以访问派生类自己的数据成员。(2)基类的成员函数不能访问派生类中的成员。11http:/ 派生类的继承方式(3)在派生类成员函数访问基类的成员时,要看基类的成员是什么类型,如果基类成员为public或protected类型,那么派生类成员函数可直接访问基类成员;如果基类成员为private类型,那么派生类成员函数不能直接访问基类成员。(4)在派生类外访问派生类中成员时,若成员为public类型,可以调用;若成员为private或protected类型,不能调用
7、。12http:/ 继承的类型和选择继承的方式13 继承的类型 继承方式的选择http:/ 继承的类型l继承的类型有3种:公用继承(public)、私有继承(private)和受保护的继承(protected)。1 1)公用继承)公用继承 公用继承,即基类中的公用成员或保护成员在派生类中还是公用或受保护的,而基类中的私有成员仍然为基类私有的。在建立派生类时声明派生类的继承类型为公用继承,那么此派生类的基类就叫做公用基类。在基类中是私有成员的,在派生类中就不能够直接使用成员函数来引用此私有成员,只能在派生类中调用基类中的公用成员函数,来间接引用基类中的私有成员。请读者仔细阅读下面一段关于在派生类
8、中引用基类成员的例子,找出下面例子中出现的错误。14http:/ 继承的类型 class Studentprivate:int num;string name;char sex;public:void display()cout num:num endl;cout name:name endl;cout sex:sex endl;15http:/ 继承的类型class NewStudent:public Student /定义派生类NewStudentprivate:int age;/增加数据成员agestring addr;/增加数据成员addrpublic:void new_display
9、()cout num:num endl;/在派生类中引用基类数据成员numcout name:name endl;/在派生类中引用基类数据成员namecout sex:sex endl;/在派生类中引用基类数据成员sexcout age:age endl;cout addr:addr endl;16http:/ 继承的类型上面例子的错误在于,在派生类中使用成员函数去引用基类的私有数据成员。无论派生类的继承方式是怎样的,基类中的私有数据成员始终都是基类中的,它只能被基类中的成员函数调用,不能被派生类中成员函数调用,更不能被类外调用。如果在公用继承中,可以直接访问基类中的私有数据成员,那么就破坏了
10、C+中类的封装性。下面将上面错误的例子用两种不同的方法加以改正。(1 1)分别调用基类和派生类中的成员函数。)分别调用基类和派生类中的成员函数。NewStudent t;t.display();/调用基类中的成员函数t.new_display();/调用派生类中的成员函数return 0;17http:/ 继承的类型(2 2)使用函数嵌套的方式来输出数据成员。)使用函数嵌套的方式来输出数据成员。class NewStudent:public Student /定义派生类NewStudentprivate:int age;/增加数据成员agestring addr;/增加数据成员addrpubl
11、ic:void new_display()display();/调用基类成员函数displaycout age:age endl;cout addr:addr endl;18http:/ 继承的类型int main()NewStudent t;t.new_display();/调用派生类中的成员函数,输出所有数据成员return 0;19http:/ 继承的类型2 2)私有继承)私有继承私有继承,即基类的公用成员和保护成员在派生类中都成为了私有成员,而基类的私有成员仍然为基类所私有。在建立派生类时声明派生类的继承类型为私有继承,那么此派生类的基类就叫做私有基类。在私有基类中,公用成员和受保护的
12、成员在派生类中都变成了私有成员,所以派生类中的成员函数可以访问它们,而在派生类外就不能访问它们了。私有基类的私有成员同公用基类的私有成员一样,只能被基类本身的成员函数调用,其他地方是不能调用私有基类的私有成员的。基类中的私有成员在派生类中是不可见的。私有继承是把原先在基类中可见的公用成员或保护成员都隐藏起来,在派生类中变成私有的,这样外界就不能访问私有成员了。20http:/ 继承的类型请结合图请结合图11-511-5来理解。来理解。私有继承一般在程序中不使用,原因在于私有继承后,基类中的公有和保护成员在派生类中都成了私有成员,不能被外界调用,这样就仿佛使一个类与外界隔离开来了。21http:
13、/ 继承的类型class Studentclass Student private:private:int num;int num;string name;string name;char sex;char sex;public:public:void display()/void display()/定义输出函数定义输出函数displaydisplay cout num:num endl;cout num:num endl;22http:/ 继承的类型class NewStudent:class NewStudent:privateprivate Student/Student/建立私有继承
14、的派生类建立私有继承的派生类 private:private:int age;/int age;/增加数据成员增加数据成员ageagestring addr;/string addr;/增加数据成员增加数据成员addraddrpublic:public:void input()/void input()/定义输入函数定义输入函数inputinput cout please input age and addr endl;cout please input age and addr age;cin age;void new_display(void new_display()/在派生类中定义输出
15、函数在派生类中定义输出函数new_displaynew_displaycout age:age endl;cout age:age endl;23http:/ 继承的类型int main()int main()NewStudent t;NewStudent t;t.input();/t.input();/调用派生类中的成员函数调用派生类中的成员函数inputinputt.new_display();t.new_display();/调用派生类中的成员函数调用派生类中的成员函数new_displaynew_displayreturn 0;return 0;上面程序中,只调用了派生类中的两个成员函
16、数,而没有调用基类中的成员函数display,因为程序中的继承方式为私有继承,基类中的成员函数被私有继承之后,在派生类中就成为了private类型的成员,所以如果在主函数中增加如下语句就是错误的。24http:/ 继承的类型int main()NewStudent t;t.input();t.new_display();t.display();/增加调用基类的成员函数的语句return 0;不能在类外直接调用私有成员,无论是派生类还是基类,只要是私有数据成员,直接调用都是错误的。派生类中的私有继承就相当于在派生类中定义了一个私有的成员函数display。25http:/ 继承的类型此时若想调用
17、display函数,可以使用下面的语句:void new_display()display();/在派生类中公有成员函数中调用display函数cout age:age endl;cout addr:addr endl;在这里,用这种方法调用display函数是最方便的,也是最容易看懂的,因为在派生类中,可以调用从私有基类继承过来的成员函数。需要注意的是,派生类中的成员函数不能访问私有基类中的私有成员,若此例子中的display函数是基类中的私有成员函数,则在基类外不能调用此函数。26http:/ 继承的类型3 3)受保护的继承)受保护的继承定义一个派生类时,若定义的继承类型为受保护的继承,那
18、么此派生类的基类就叫做保护基类,派生类就叫做保护派生类。受保护的继承和私有继承既有区别又有相同之处,下面是对两种继承方式的对比。(1)从用户的角度看来,保护成员和私有成员是没有区别的,因为前面讲到了:在类外是不能调用保护成员和私有成员的。(2)从编程者的角度看来,受保护的继承和私有继承是有很大区别的,受保护的继承对派生类本身是没有访问限制的,因此在编程中可以定义一连串的派生类,即从第一个到最后一个派生类都可以访问前面派生类中的保护成员,但这种方式在私有继承中是无法实现的。27http:/ 继承方式的选择 在选择继承方式时,应该根据派生类中成员的访问属性和基类成员在派生类中的访问属性这两个大的方
19、面来确定。1 1)派生类中成员的访问属性)派生类中成员的访问属性28http:/ 继承方式的选择2 2)基类成员在派生类)基类成员在派生类 中的访问属性中的访问属性29http:/ 将派生类对象转换为基对象前面学习了基类和派生类的相关知识,这两个类的对象间是可以转换的,但只能用派生类对象向基类对象赋值,而基类对象不能向派生类对象赋值,原因在下面会说明。int main()Student t;NewStudent t1;t=t1;/注意:如果写成t1=t错误在主函数中,Student类是基类,NewStudent类是派生类,上面的语句表示建立两个对象t和t1,用派生类对象t1对基类对象t进行赋值
20、。30http:/ 将派生类对象转换为基对象前面学习了基类和派生类的相关知识,这两个类的对象间是可以转换的,但只能用派生类对象向基类对象赋值,而基类对象不能向派生类对象赋值,原因在下面会说明。int main()Student t;NewStudent t1;t=t1;/注意:如果写成t1=t错误在主函数中,Student类是基类,NewStudent类是派生类,上面的语句表示建立两个对象t和t1,用派生类对象t1对基类对象t进行赋值。31http:/ 将派生类对象转换为基对象对于类对象的地址,也可以对类对象的指针变量赋值。这句话看上去比较抽象,下面就用一个例子来说明这个问题。class St
21、udent /建立基类Studentprivate:int num;string name;char sex;public:void display();/声明输出函数Student(int n,string n1,char s);/声明基类中构造函数;void Student:display()/定义输出函数 32http:/ 将派生类对象转换为基对象cout num:num endl;cout name:name endl;cout sex:sex endl;Student:Student(int n,string n1,char s)/定义基类中构造函数num=n;name=n1;sex
22、=s;33http:/ 将派生类对象转换为基对象class NewStudent:public Student/建立派生类NewStudentprivate:int age;public:void display();/声明输出函数NewStudent(int n,string n1,char s,int a);/声明派生类构造函数;void NewStudent:display()/定义输出函数Student:display();/调用基类中输出函数34http:/ 将派生类对象转换为基对象cout age:age display();/输出display函数中数据p=&t2;/使指针指向t
23、2p-display();/输出display函数中数据return 0;程序运行结果如图11-9所示。36http:/ 将派生类对象转换为基对象在上面的程序中,可以看到主函数中用派生类对象的地址为指针变量赋值,即可以把派生类对象的地址赋值给指向基类的指针变量。此程序中需要注意以下几点。(1)在定义基类Student时,建立了构造函数,此构造函数在继承时不能被继承到派生类中。(2)有些读者读到声明派生类构造函数时,会有疑问:为什么形参是4个而不是1个?这个问题将在后面具体学习派生类构造函数时讲到。(3)在定义对象赋值时,性别一栏是用char来定义的,而不是使用string,所以在赋值时,只能用
24、一个字符来对其赋值,而不能用一串字符来对其赋值。37http:/ 将派生类对象转换为基对象(4)在建立指针变量时,指针变量是指向基类对象的,而程序中出现了两个display函数,在前面学习过覆盖知识后,很多读者会认为派生类中的display函数覆盖了基类中的display函数,在主程序中调用的应该是派生类的display函数。38http:/ 派生类的构造和析构39 派生类的构造 派生类的析构http:/ 派生类的构造派生类构造函数和普通的构造函数一样,只是位于在派生类中。派生类构造函数有以下几种。1 1)只有一个基类和一个派生类的构造函数)只有一个基类和一个派生类的构造函数只有一个基类的派生
25、类构造函数是最基础的构造函数,派生类构造函数的一般形式如下。派生类构造函数名(总参数列表):基类构造函数名(参数列表)派生类中新增加数据成员的初始化总参数列表包括了基类的数据成员和派生类的数据成员,不能只把派生类的数据成员写在其中。40http:/ 派生类的构造class NewStudent:public Student /建立派生类NewStudentprivate:char sex;int age;public:void display();/声明输出函数NewStudent(int n,string n1,char s,int age);/声明派生类构造函数;NewStudent:Ne
26、wStudent(int n,string n1,char s,int a):Student(n,n1)/在类外定义派生类构造函数sex=s;age=a;41http:/ 派生类的构造在上面程序中,需要注意以下几点。(1)在定义派生类构造函数时,冒号后面的Student(n,n1)用于调用基类构造函数及参数。(2)在总参数列表中,必须写出基类和派生类数据成员的参数类型和参数名称,而在基类构造函数后面的括号中,就只需写出参数名称,不写参数类型,这里的参数是调用基类构造函数时的实参,而不是总参数列表中的形参。(3)在类中声明派生类构造函数时,只写该函数的声明,而不写冒号和冒号后面的那一部分。声明和
27、定义分开时的一般格式如下。在类中声明:派生类构造函数名(总参数列表);派生类中新增加数据成员的初始化42http:/ 派生类的构造在类外定义:派生类名:派生类构造函数名(总参数列表):基类构造函数名(参数列表)派生类中新增加数据成员的初始化(4)可以使用对数据成员的初始化表来对数据成员进行初始化。例如NewStudent:NewStudent(int n,string n1,char s,int a):Student(n,n1),sex(s),age(a)这样就可以节约很多空间,看上去也非常清晰。派生类的构造函数执行的顺序是:在建立对象时,派生类构造函数首先调用基类的构造函数,在系统处理完基类
28、的构造函数之后,再调用派生类构造函数。43http:/ 派生类的构造2 2)有子对象的派生类构造函数)有子对象的派生类构造函数子对象就是指对象中的对象,它一般是在派生类中,用基类建立的对象。class NewStudent:public Student /建立派生类NewStudentprivate:char sex;int age;Student graduate;/定义子对象NewStudent:NewStudent(int n,string n1,int n2,string n3,char s,int a):Student(n,n1),graduate(n2,n3)/定义派生类构造函数s
29、ex=s;age=a;44http:/ 派生类的构造在上面的程序中,需要注意以下几点。(1)在定义派生类构造函数时,在总参数列表中,千万不要把子对象的参数漏掉了,很多读者在学习了这部分内容之后,在编写总参数列表时,容易写成下面的形式。(int n,string n1,char s,int a)这种写法忽略了子对象的存在,在编译时就会出现错误。(2)在派生类中的成员函数中调用基类成员函数或者数据成员时,要注意基类成员的属性,若基类成员为private,则不能调用此成员 3)含有子对象的派生类构造函数定义的格式如下。派生类构造函数名称(总参数列表):基类构造函数名称(参数列表),子对象名称(参数列
30、表)派生类中新增成员的初始化45http:/ 派生类的构造3 3)有多个派生类时的构造函数)有多个派生类时的构造函数class NewStudent1:public Student /建立派生类NewStudent1NewStudent1(int n,string n1,char s);/声明派生类构造函数NewStudent1:NewStudent1(int n,string n1,char s):Student(n,n1)/定义派生类构造函数sex=s;class NewStudent2:public NewStudent1 /建立派生类NewStudent2NewStudent2(int
31、 n,string n1,char s,int a);/声明派生类构造函数NewStudent2:NewStudent2(int n,string n1,char s,int a):NewStudent1(n,n1,s)/定义派生类构造函数age=a;46http:/ 派生类的构造这就是多个派生类构造函数和单个派生类构造函数定义时的不同。在有多个派生类定义构造函数时,在冒号后面只写出上一个类中构造函数即可,不能把上面所有的构造函数都写出来,如下面一种写法。NewStudent2:NewStudent2(int n,string n1,char s,int a):Student(n,n1),Ne
32、wStudent1(n,n1,s)age=a;这样的写法是错误的。47http:/ 派生类的析构在建立派生类时,派生类可以继承基类的数据成员和成员函数,但是派生类的缺点就在于不能继承基类的构造函数和析构函数,因此派生类中需要析构函数时,就要在派生类中重新定义析构函数来供派生类使用。派生类析构函数的作用和前面所学的一样,就是用来在对象撤销之前,进行一些清理的工作。派生类析构函数的调用顺序和构造函数相反,先调用派生类的析构函数,然后再调用子对象的析构函数,最后调用基类的析构函数,进行的工作都是对派生类对象进行清理。48http:/ 使用基类成员49 使用基类成员http:/ 使用基类成员基类成员的
33、使用有两种方法,分别是直接调用和通过基类的成员函数调用。基类成员的运用比较广泛,是当基类中的数据成员的类型为private时,就必须使用基类的成员函数才能调用其数据成员。先定义一个类。class Studentprivate:public:int num;void input()string name;ing age;cin num;cin name;cin age;50http:/ 使用基类成员int main()Student t;t.num=2010;t.name=zhangsan;t.age=19;return 0;int main()Student t;t.input();retur
34、n 0;第一种方法第一种方法第二种方法第二种方法51http:/ 使用基类成员上面的两种调用方法中,第一种是错误的,因为类中的3个数据成员都是私有的,所以在main函数中不能直接调用。但其好处就是在数据成员不是私有类型时,可以不必单独为其定义成员函数。第二种方法是正确的,此时第二种方法就使用了基类的成员函数去调用私有数据成员。使用基类的成员好处就在于可以对其基类中其他的成员进行操作,而不受访问属性的限制。52http:/ 基类类型的使用53 基类类型的使用http:/ 基类类型的使用1 1)派生类和基类的访问范围)派生类和基类的访问范围(1)派生类可以直接访问自身的公有成员函数和基类中的公有成
35、员函数。(2)私有成员变量、私有成员函数只能通过调用类中的公有成员函数来访问,而不能通过对象名直接访问,派生类不能访问基类的私有成员函数。(3)保护成员变量、保护成员函数也只能通过调用类中的公有成员函数来访问,而不能通过对象名直接访问,派生类可以通过公有成员函数来访问基类的保护成员函数。2 2)指向派生类的基类指针和基类引用的访问范围)指向派生类的基类指针和基类引用的访问范围(1)指向派生类的基类指针或者引用,其类型仍然属于基类,而不是派生类,尽管它指向的是派生类。54http:/ 基类类型的使用(2)对于虚函数,使用指向派生类的基类指针或基类引用访问时,将会体现出多态性,调用的实际上是派生类
36、的对应函数。(3)对于指向派生类的基类引用,虽然引用通常是被引用对象的一个别名,但在这里,基类引用的访问范围与被引用对象的访问范围明显不同。55http:/ 多重继承和虚拟继承56 多重继承的声明方法 访问基类成员 多重继承存在的问题 虚拟继承http:/ 多重继承的声明方法多重继承和单继承的声明方法差不多,只是多重继承有多个基类,每一个基类都应该在声明派生类时声明。下面是多重继承的声明方法。class D:public A,private B,protected C类D中新增加的成员上面的A、B、C分表代表不同的基类,D代表派生类。每一个基类前面都要加上继承时的属性,它们的属性分别为公用、私
37、有和受保护的。57http:/ 访问基类成员访问基类成员是派生类和基类之间联系的方式,可以调用基类中的公用成员函数来访问基类成员,若基类的成员是公用的,那么也可以在类外直接访问基类的成员。1 1)利用调用函数的方法访问基类成员)利用调用函数的方法访问基类成员访问基类中的数据成员,不能使用直接调用的方法,因为这些数据成员的访问属性是私有的,只能被基类中的成员函数调用,而不能被外界任何函数或对象调用。2 2)直接访问基类成员)直接访问基类成员58http:/ 多重继承存在的问题在多重继承中存在一个严重的问题:二义性。顾名思义,二义性就是对一个事物有两种不同的解释,使其不能准确地表达出本意。在C+中,虚拟继承使得派生类可以继承基类多次,而只有一份基类的拷贝在派生类对象中。若想要一个类既得到一个基类的拷贝,又得到另外两个类的数据成员和成员函数,则可通过虚拟继承来实现。虚拟继承的基本形式如下。class 派生类名称:virtual 基类1,virtual 基类2,.,virtual 基类n派生类成员初始化59虚拟继承http:/ 虚拟继承class B:virtual public A /定义派生类Bpublic:B();60http:/ ThanksLOGO
限制150内