清华郑莉C++语言程序设计学习笔记(3)- 继承与派生、多态性
2022/3/20 22:29:46
本文主要是介绍清华郑莉C++语言程序设计学习笔记(3)- 继承与派生、多态性,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
第七章 继承与派生
7.1 继承的基本概念和语法
概述:保持已有类的特性而构造新类的过程称为继承,在已有类的基础上新增自己的特性而产生新类的过程称为派生。
基类:被继承的已有类
派生类:派生出的新类
直接基类:直接参与派生出某类的基类
间接基类:基类的基类甚至更高层的基类
继承目的:实现设计与代码的重用
派生目的:新的问题出现时,原有程序无法解决时需对原有程序进行改造
单继承时派生类的定义:
class 派生类名:继承方式 基类名 { 成员声明; }
class Derived: public Base { public: Derived(); ~Derived(); }
多继承时派生类的定义:
class Derived: public Base1, private Base2 { public: Derived(); ~Derived(); }
注意:每一个继承方式,只用于限制对紧随其后之基类的继承
派生类的构成:吸收、改造基类成员,添加新的成员
吸收基类成员:默认情况下包含全部基类中除构造和析构函数之外的所有成员,C++11规定可以用using语句继承基类构造函数
改造基类成员:如果派生类声明了一个和某基类成员同名的新成员,派生的新成员就隐藏或覆盖了外层同名成员
7.2 继承方式
不同继承方式的影响体现在:派生类成员对基类成员的访问权限;通过派生类对象对基类成员的访问权限。
三种继承方式:公有继承、私有继承、保护继承
公有继承:
1、继承的访问控制:基类的public和protected成员,访问属性在派生类中保持不变;基类的private成员不可以直接访问,但可以通过调用公有函数有条件的访问。
2、访问权限:派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;通过派生类的对象只能访问public成员
私有继承:
1、继承的访问控制:基类的public和protected成员都以private身份出现在派生类中,基类的private成员不可直接访问。
2、访问权限:派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;通过派生类的对象不能直接访问从基类继承的任何成员。
例:矩形以私有继承方式继承点类
保护继承:
1、继承的访问控制:基类的public和protected成员都以protected身份出现在派生类中,基类的private成员不可直接访问。
2、访问权限:派生类中的成员函数可以直接访问基类中的public和protected成员,但不能直接访问基类的private成员;通过派生类的对象不能直接访问从基类继承的任何成员。
protected成员的特点与作用:对建立其所在类对象的模板来说,它与Private成员性质相同;对于其派生类来说,它又与public成员的性质相同。既实现了数据隐藏,又方便继承,实现代码重用。
7.3 基类与派生类类型转换
类型转换:
1、公有派生类对象可以被当作基类的对象使用,反之则不可。
2、派生类的对象可以隐含转换为基类对象。
3、派生类的对象可以初始化基类的引用。
4、派生类的指针可以隐含转换为基类的指针。
5、通过基类对象名、指针只能使用从基类继承的成员。
例:通用的显式函数,实参为派生类对象
7.4 派生类的构造和析构
7.4.1 派生类的构造函数
默认情况下,基类的构造函数不被继承,派生类需要定义自己的构造函数。C++11规定可用using语句继承基类的构造函数,但是只能初始化从基类继承的成员,对新增成员无能为力。语法形式using B::B;
若不继承基类的构造函数:派生类新增成员由派生类定义构造函数初始化,继承来的成员自动调用基类构造函数进行初始化,派生类的构造函数需要给基类的构造函数传递参数。
单继承时构造函数的定义语法:
派生类名::派生类名(基类所需的形参,本类成员所需的形参):基类名(参数表),本类成员初始化列表 {//其他初始化;};
多继承时构造函数的定义语法:
派生类名::派生类名(参数表):基类名1(基类1初始化参数表),...基类名n(基类n初始化参数表) 本类成员初始化列表 {//其他初始化;};
当基类有默认构造函数时:派生类构造函数可以不向基类构造函数传递参数,构造派生类对象时,基类的默认构造函数将被调用。否则,若需执行基类中带参数的构造函数,派生类构造函数应为基类构造函数提供参数。
多继承且有对象成员时派生的构造函数的定义语法:
派生类名::派生类名(形参表):基类名1(基类1初始化参数表),...基类名n(基类n初始化参数表),本类成员(含对象成员)初始化列表 {//其他初始化;};
构造函数的执行顺序:
1、调用基类构造函数。顺序按照它们被继承时声明的顺序(从左向右)
2、对初始化列表中的成员进行初始化。顺序按照它们在类中定义的顺序;对象成员初始化时自动调用其所属类的构造函数。
3、执行派生类的构造函数体中的内容。
例:先后构造Base2, Base1, Base3, member1, member2, member3
7.4.2 派生类的复制构造函数
若派生类没有声明复制构造函数:编译器会在需要时生成一个隐含的复制构造函数,先调用基类的复制构造函数,再为派生类新增的成员执行复制。
若派生类没有声明复制构造函数:一般都要为基类的复制构造函数传递参数。复制构造函数只能接受一个参数,既用来初始化派生类定义的成员,也将被传递给基类的复制构造函数。基类的复制构造函数形参类型是基类对象的引用,实参可以是派生类对象的引用。
C::C(const C &c1): B(c1){...}
派生类的析构函数
1、析构函数不被继承,派生类如果需要,要自行声明析构函数。
2、声明方法与无继承关系时类的析构函数相同。
3、不需要显式地调用基类的析构函数,系统会自动隐式调用。
4、先执行派生类析构函数的函数体,再调用基类的析构函数。(和构造时相反)
7.5 派生类成员的标识与访问
7.5.1 访问从基类继承的成员
当派生类与基类中有同名成员时:若未特别限定,则通过派生类对象使用的是派生类中的同名成员;如果通过派生类对象访问基类中被隐藏的同名成员,应使用基类名和作用域操作符来限定。
二义性问题:如果从不同基类继承了同名成员,但是在派生类中没有定义同名成员,“派生类对象名或引用名.成员名”、“派生类指针->成员名”访问成员存在二义性问题。解决方式:用类名限定。
例:
例:多继承时的二义性和冗余问题,尽量避免
7.5.2 虚基类
需解决问题:当派生类从多个基类派生,而这些基类又有共同基类,则在访问此共同基类中的成员时,将产生冗余,并有可能因冗余带来不一致性。
虚基类声明:以virtual说明基类继承方式 class B1:virtual public B
作用:为最远的派生类提供唯一的基类成员,而不重复产生多次复制,避免二义性。
注意:在第一级继承时就要将共同基类设计为虚基类。在写大型公共软件时慎用。
虚基类及其派生类构造函数:
1、建立对象时所指定的类称为最远派生类。
2、虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的,其他派生类传参不起作用。(跨层初始化,如下例)
3、在整个继承结构中,直接或间接继承虚基类的所有派生类,都必须在构造函数的初始化表中为虚基类的函数列出参数。若未列出,则表示调用该虚基类的默认构造函数。
4、在建立对象时,只有最远派生类的构造函数调用虚基类的构造函数,其他类对虚基类构造函数的调用被忽略。
例:
第八章 多态性
8.1 运算符重载
8.1.1 运算符的重载规则
1、C++几乎可以重载全部的运算符,而且只能够重载C++中已有的,不能重载的运算符有“:”, “*”, “::”, “?:”
2、重载之后运算符的优先级和结核性都不会改变
3、运算符重载是针对新类型数据的实际需要,对原有运算符进行适当的改造。例如使复数类的对象可以用“+”运算符实现加法,时钟类对象可以用“++”运算符实现时间增加1秒。
4、重载为类的非静态成员函数 & 重载为非成员函数
8.1.2 双目运算符重载为成员函数
参数个数=原操作数个数-1 (后置++,–除外)
函数类型 operator 运算符(形参) {...}
重载规则:如果要重载B为类成员函数,使之能够实现表达式oprd1 B oprd2
,其中oprd1为A类对象,则B应该重载为A类的成员函数,形参类型应该是oprd2所属的类型。经重载后,表达式oprd1 B oprd2
相当于oprd1.operator B(oprd2)
。
例:复数类加减法运算重载为成员函数
8.1.3 单目运算符重载为成员函数
前置单目运算符重载规则:如果要重载U为类成员函数,使之能够实现表达式U oprd
,其中oprd为A类对象,则U应被重载为A类的成员函数,无形参。经重载后,U oprd
相当于oprd.operator U()
。
后置单目运算符++和–重载规则:如果要重载++或–为类成员函数,使之能够实现表达式oprd++
或oprd--
,其中oprd为A类对象,则++或–应被重载为A类的成员函数,且具有一个int类型实参。经重载后,表达式oprd++
相当于oprd.operator ++(0)
前置++返回一个左值,可以写++i=3
;后置++返回一个右值,实际是原来对象的副本,不可以写i++=3
。
例:将前置++和后置++重载为时钟类的成员函数,实现时间增加1秒钟
8.1.4 运算符重载为非成员函数
运算符重载为非成员函数的规则:
1、函数的形参代表依自左至右次序排列的各操作数。
2、重载为非成员函数时,参数个数=原操作数个数(后置++,–除外),且至少应该有一个自定义类型的参数。
3、后置单目运算符++和–的重载函数,形参列表中要增加一个int,但不必写形参名。
4、如果在运算符的重载函数中需要操作某类对象的私有成员,可以将此函数声明为该类的友元。
运算符重载为非成员函数的规则:
1、双目运算符B重载后,表达式oprd1 B oprd2
等同于operator B(oprd1,oprd2)
2、前置单目运算符B重载后,表达式B oprd
等同于operator B(oprd)
3、后置单目运算符++和–重载后,表达式oprd B
等同于operator B(oprd, 0)
例:重载Complex的加减法和“<<”运算符为非成员函数
要求:将+,-(双目)重载为非成员函数,并将其声明为复数类的友元,两个操作数都是复数类的常引用;将<<(双目)重载为非成员函数,并将其声明为复数类的友元,它的左操作数是std::ostream
引用;右操作数为复数类的常引用,返回std::ostream
引用,用以支持下面形式的输出:
cout << a << b;
该输出调用的是:
operator << (operator << (cout, a), b);
8.2 虚函数
虚函数和虚基类作用不同:虚基类解决的是类成员标识二义性和信息冗余问题,而虚函数是实现多态性的基础。
8.2.1 虚函数
1、用virtual关键字说明的函数。
2、虚函数是实现运行时多态性基础。
3、C++中的虚函数是动态绑定的函数。
4、虚函数必须是非静态的成员函数,虚函数经过派生之后,就可以实现运行过程中的多态。
例:告诉编译器,遇到display()时,不要着急调用该函数的函数体(做静态绑定),要在运行阶段做动态绑定。
由于内联函数是在编译阶段处理的,因此virtual函数要在类外实现函数体,不能写成内联的了。
什么函数可以是虚函数?
1、一般成员函数可以是虚函数。
2、构造函数不能是虚函数。
3、析构函数可以是虚函数。
一般虚函数成员:
1、虚函数的声明:virtual 函数类型 函数名(形参表);
。
2、虚函数声明只能出现在类定义中的函数原型声明中,而不能在成员函数实现的时候。
3、在派生类中可以对基类中的成员函数进行覆盖,可以实现运行时的多态性。
4、虚函数一般不声明为内联函数,因为对虚函数的调用需要动态绑定,而对内联函数的处理是静态的。
virtual关键字:
1、派生类可以不显式地用virtual声明虚函数,这时系统就会用以下规则来判断派生类的一个函数成员是不是虚函数:(1)该函数是否与基类的虚函数有相同的名称、参数个数及对应参数类型;(2)该函数是否与基类的虚函数有相同的返回值或者满足类型兼容规则的指针、引用型的返回值。
2、如果从名称、参数、返回值三个方面检查之后,派生类的函数满足上述条件,就会自动确定为虚函数。这时,派生类的虚函数便覆盖了基类的虚函数。
3、派生类中的虚函数还会隐藏基类中同名函数的其他重载形式。
4、一般习惯于在派生类的函数中也使用virtual关键字,以增加程序的可读性。
8.2.2 虚析构函数
如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),就需要让基类的析构函数成为虚函数,否则执行delete的结果是不确定的。
8.2.3 虚表与动态绑定
虚表:每个多态类有一个虚表,虚表中有当前类的各个虚函数的入口地址,每个对象有一个指向当前类的虚表的指针(虚指针vptr)。
动态绑定的实现:构造函数中为对象的虚指针赋值,通过多态类型的指针或引用调用成员函数时,通过虚指针找到虚表,进而找到所调用的虚函数的入口地址,通过该入口地址调用虚函数。
8.3 抽象类
纯虚函数:是一个在基类中声明的虚函数,它在该基类中没有定义具体的操作内容,要求各派生类根据实际需要定义自己的版本(如:二维图形的面积),纯虚函数的声明格式为:virtual 函数原型 函数名(参数表)=0;
抽象类:带有纯虚函数的类
抽象类作用:将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为。对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现。
注意:抽象类只能作为基类来使用,不能定义抽象类的对象。
8.4 override与final
override
1、多态性为的基础:基类声明虚函数,派生类声明一个函数覆盖该虚函数。
2、覆盖要求:函数签名(signature)完全一致。函数签名包括:函数名 参数列表 const
C++11引入显式函数覆盖,在编译期而非运行期捕获此类错误。在虚函数显式重载中运用,编译器会检查基类是否存在一虚拟函数,与派生类中带有声明override的虚拟函数,有相同的函数签名(signature);若不存在,则会回报错误。
final:说明该类或某函数不能被覆盖。
例:
这篇关于清华郑莉C++语言程序设计学习笔记(3)- 继承与派生、多态性的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享