《C++ Primer Plus》读书笔记之十一—类继承
第十三章 类继承
1、类继承:扩展和修改类。
2、公有继承格式:冒号指出B类的基类是A,B是派生类。
class B :public A
{
。。。
};
3、派生类对象包含基类对象。使用公有派生,基类的公有成员将成为派生类的公有成员;基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问。派生类对象存储了基类的数据成员(派生类继承了基类的实现);派生类对象可以使用基类的方法(派生类继承了基类的接口)。
4、需要在继承特性添加什么呢?首先,派生类需要自己的构造函数。然后,派生类可以根据需要添加额外的数据成员和成员函数。
5、派生类的构造函数必须给新成员(有的话)和继承的成员提供数据。但是,派生类不能直接访问基类的私有成员,而必须通过基类的方法进行访问。具体地说,派生类构造函数必须使用基类构造函数。
6、创建派生类对象时,程序首先创建基类对象。这意味着基类对象应当在程序进入派生类构造函数之前被创建。C++使用成员初始化列表句法完成这种工作。例如,下面是派生类B的一个构造函数: 其中,A(fn,ln,ht) 是成员初始化列表。
B::B(int r,const char *fn,const char *ln,bool ht):A(fn,ln,ht) // (fn,ln,ht)是基类私有数据成员
{
rating =r; // rating是派生类成员
}
如果省略成员初始化列表,会如何?
B::B(int r,const char *fn,const char *ln,bool ht) // (fn,ln,ht)是基类私有数据成员
{
rating =r; // rating是派生类成员
}
基类对象必须首先被创建,如果不调用基类构造函数,程序将使用默认的基类构造函数,因此上面额代码与下面的等效:
B::B(int r,const char *fn,const char *ln,bool ht):A() // (fn,ln,ht)是基类私有数据成员
{
rating =r; // rating是派生类成员
}
注:除非要使用默认构造函数,否则应显式调用正确的基类构造函数。
7、下面看第二个派生类B的构造函数: 因为tp的类型为const A &,因此将调用基类的复制构造函数。如果需要使用复制构造函数,而又没有定义,编译器将自动生成一个。
B::B(int r,const A & tp):A(tp)
{
rating =r; // rating是派生类成员
}
8、如果愿意,也可以对派生类成员使用成员初始化列表句法。
9、记住:创建派生类对象时,程序首先调用基类构造函数,然后再调用派生类构造函数。基类构造函数负责初始化继承的数据成员;派生类构造函数主要用于初始化新增的数据成员。派生类的构造函数总是调用一个基类的构造函数。可以使用成员初始化列表指明要使用的基类构造函数,否则将使用默认的基类构造函数。派生类对象过期时,程序将首先调用派生类析构函数,然后再调用基类析构函数。
10、派生类和基类之间的特殊关系:一、派生类对象可以使用基类的方法(方式和基类对象调用方法一样),条件是方法不是私有的。二、基类指针可以在不进行显式类型转换的情况下指向派生类对象;基类引用可以在不进行显式类型转换的情况下引用派生类对象。不过,基类指针或引用只能用于调用基类方法。注:不可以将基类对象和地址赋给派生类引用和指针。
、对于一个函数,如果其形参是一个基类引用,则可以使用基类对象或派生类对象作为实参。
对于一个函数,如果其形参是一个指向基类的指针,则可以使用基类对象的地址或派生类对象的地址作为实参。
12、引用兼容性属性也能够将基类对象初始化为派生类对象,尽管不那么直接。如下:
B b=(1840,"abc","def",true);// 派生类对象
A a(b); // 用派生类对象初始化基类对象
要初始化a,匹配的构造函数原型为:A (const B &);类中没有这样的构造函数,但存在隐式复制构造函数:A (const A &);形参是基类引用,因此它可以引用派生类。隐式构造函数将a初始化为嵌套在B对象b中的A对象。
13、同样,也可以将派生类对象赋给基类对象:
B b=(1840,"abc","def",true);// 派生类对象
A a;
a=b;// 用派生类对象赋给基类对象
在这种情况下,程序将使用隐式重载赋值操作符:
A & operator=(const A &)const;基类引用指向的也是派生类对象,因此b的基类部分被复制给a。
14、C++有3种继承方式:公有继承、保护继承和私有继承。公有继承最常用,它建立一种is-a关系,即派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行。
15、类方法的行为应取决于调用该方法的对象。这种较复杂的行为称为多态——具有多种形态,就是指同一个方法的行为将随上下文而异。有两种重要的机制可以用于实现多态公有继承:①在派生类中重新定义基类的方法。②使用虚方法。
16、对于在两个类中行为相同的方法,则只在基类中声明。
17、基类方法如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。
18、经常在基类中将派生类会重新定义的方法声明为虚方法。方法在基类中被声明为虚拟的后,它在派生类中将自动成为虚方法。
19、在派生类方法中,标准的技术是使用作用域解析操作符来调用基类方法。
20、可以创建指向基类的指针数组,那么数组里的指针既可以指向基类对象,也可以指向派生类对象,因此可以使用一个数组来表示多种类型的对象,这就是多态!!
21、为何需要虚拟析构函数?如果析构函数不是虚拟的,则将只调用对应于指针类型的析构函数。如果析构函数是虚拟的,将调用相应对象类型的析构函数。因此,使用虚拟析构函数可以确保正确的析构函数序列被调用。
22、静态联编(早期联编):编译器负责回答程序调用函数时,将使用哪个可执行代码块。将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编。在C++中,由于函数重载的缘故,编译器必须查看函数参数以及函数名才能确定使用哪个函数。然而,C++编译器可以在编译过程完成这种联编。在编译过程中进行联编被称为静态联编。动态联编(晚期联编):使用哪个函数不能在编译时确定,编译器必须生成能够在程序运行时选择正确的虚方法的代码,这被称为动态联编。
23、将派生类引用或指针转换为基类引用或指针被称为向上强制转换,这使公有继承不需要进行显式类型转换。如下:
B b=(1840,"abc","def",true);// 派生类对象
A *a=&b; // A &a=b;
将基类指针或引用转换为派生类指针或引用,称为向下强制转换,如果不使用显式类型转换,则向下强制转换是不允许的。
A a;// 基类
B b;// 派生类
A *a=&b;
B *b=(B *)&a;
24、记住:派生类对象都是基类对象,因为它继承了基类对象所有的数据成员和成员函数。所以,可以对基类对象执行的操作,都适用于派生类对象。
25、编译器对非虚方法使用静态联编;对虚方法使用动态联编。
26、如果类不会用作基类,则不需要动态联编。同样,如果派生类不重新定义基类的任何方法,也不需要使用动态联编。在这些情况下,使用静态联编更合理,效率更高。因此,静态联编被设置为C++的默认选择。
27、虚函数的工作原理:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(vtbl)。虚表中存储了为类对象进行声明的虚函数的地址。基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数将保存新函数的地址。如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本的定义。如果派生类重新定义了新的虚函数,该函数的地址也将被添加到vtbl中。注意:无论类中包含的虚函数是一个还是10个,都只需要在对象中添加一个地址成员,只是表的大小不同而已。
28、使用虚函数的内存和执行速度的成本:①每个对象都将增大,增大量为存储地址的空间。②对每个类,编译器都创建一个虚函数地址表(数组)。③每个函数调用都需要执行以不额外的操作,即到表中查找地址。
29、构造函数不能是虚函数;析构函数应当是虚函数,除非类不用作基类;友元不能是虚函数,因为友元不是类成员,只有类成员才能是虚函数。
30、如果派生类没有重新定义函数,将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本,例外情况是基类版本是隐藏的。
31、重新定义继承的方法并不是重载。如果在派生类中重新定义函数,将不是使用相同的函数特征标覆盖基类声明,而是隐藏同名的基类方法,不管特征标如何。
32、两条经验规则:①如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针。这种特性被称为返回类型协变,因为允许返回类型随类类型的变化而变化。②如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。如果只重新定义了一个版本,则另外两个版本将被隐藏,派生类对象将无法使用它们。注意,如果不需要修改,则新定义可只调用基类版本。
33、private和protected之间的区别只有在基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。因此,对于外部世界来说,保护成员的行为与私有成员相似;但对于派生类来说,保护成员的行为与公有成员类似。
34、C++通过使用纯虚函数提供未实现的函数。纯虚函数声明的结尾处为=0.当类中包含纯虚函数时,则不能创建该类的对象,包含纯虚函数的类只用作基类!!要成为纯虚函数,必须至少包含一个纯虚函数。纯虚函数可以定义也可以不定义!!
35、抽象基类(ABC)的派生类被称为具体类。ABC描述的是至少使用一个纯虚函数的接口,从ABC派生出的类根据派生类的具体特征,使用常规函数来实现这种接口。ABC包含派生类共有的所有方法和数据成员,而那些在派生类中的行为不同的方法应被声明为虚函数。
36、继承和动态内存分配:①派生类不使用new:不需要为派生类定义显式析构函数、复制构造函数和赋值操作符。②派生类使用new:必须为派生类定义显式析构函数、复制构造函数和赋值操作符。注:派生类复制构造函数只能访问派生类的数据,因此它必须调用基类复制构造函数来处理共享的基类数据。
37、总结:当基类和派生类都采用动态内存分配时,派生类的析构函数、复制构造函数和赋值操作符都必须使用相应的基类方法来处理基类元素。这种要求是通过三种不同的方式来满足的。对于析构函数,这是自动完成的;对于构造函数,这是通过在初始化成员列表中调用基类的复制构造函数来完成的;如果别这样做,将自动调用基类的默认构造函数。对于赋值构造函数,这是通过使用作用域解析操作符显式的调用基类的赋值操作符来完成的。
38、关于友元函数:如果希望派生类的友元函数能够使用基类的友元函数,可以通过强制类型转换将派生类引用或指针转换为基类引用或指针,然后使用转换后的指针或引用来调用基类的友元函数。
39、如果函数将参数声明为指向const的引用或指针,则不能将该参数传递给另一个函数,除非后者也确保了参数不会被修改。
40、什么不能被继承?构造函数、析构函数、赋值操作符、友元函数。
41、可以将基类对象赋给派生类对象么?可以!!法①派生类有一个转换构造函数:B(const A &),转换构造函数可以接受一个类型为基类的参数和其他参数,条件是其他参数有默认值:B(const A & a,double c=1.0,double r=0.1);如果有转换函数,程序将通过它根据基类对象来创建一个派生类对象,然后将它用做赋值操作符的参数。法②定义一个用于将基类赋给派生类的赋值操作符:B & B::operator=(const A &){...}
《C++ Primer Plus》读书笔记之十一—类继承的更多相关文章
- C primer plus 读书笔记第十一章
本章标题是字符串和字符串函数.主要是了解和字符串有关的函数. 1.字符串表示和字符串I/O 主要内容:字符串常量和字符串数组的初始化,对比了指针和字符串. 其中要注意的是,数组初始化是从静态存储区把一 ...
- TJI读书笔记10-复用类
TJI读书笔记10-复用类 组合语法 继承语法 代理 final关键字 final的数据 final的参数 final的方法 final的类 初始化和类的加载 乱七八糟不知道怎么归类的知识点 代码复用 ...
- 《Linux内核设计与实现》第五周读书笔记——第十一章
<Linux内核设计与实现>第五周读书笔记——第十一章 20135301张忻 估算学习时间:共2.5小时 读书:2.0 代码:0 作业:0 博客:0.5 实际学习时间:共3.0小时 读书: ...
- C++ primer plus读书笔记——第13章 类继承
第13章 类继承 1. 如果购买厂商的C库,除非厂商提供库函数的源代码,否则您将无法根据自己的需求,对函数进行扩展或修改.但如果是类库,只要其提供了类方法的头文件和编译后的代码,仍可以使用库中的类派生 ...
- C++ primer plus读书笔记——第11章 使用类
第11章 使用类 1. 运算符重载是一种形式的C++多态. 2. 不要返回指向局部变量或临时对象的引用.函数执行完毕后,局部变量和临时对象将消失,引用将指向不存在的数据. 3. 运算符重载的格式如下: ...
- 《C#图解教程》读书笔记之四:类和继承
本篇已收录至<C#图解教程>读书笔记目录贴,点击访问该目录可获取更多内容. 一.万物之宗:Object (1)除了特殊的Object类,其他所有类都是派生类,即使他们没有显示基类定义. ( ...
- 《android开发艺术探索》读书笔记(十一)--Android的线程和线程池
接上篇<android开发艺术探索>读书笔记(十)--Android的消息机制 No1: 在Android中可以扮演线程角色的有很多,比如AsyncTask.IntentService.H ...
- 《C++ Primer》读书笔记 第一章
读<C++ Primer>才知道,自己对C++知之甚少... 写个博客记录下自己C++的成长,只是读书笔记,不是对<C++ Primer>知识点的总结,而是对自己在书上看到的以 ...
- C++ Primer Plus读书笔记
第五章 循环和关系表达式 1. 2.类别别名: (1) #define FLOAT_POINTER float * FLOAT_POINTER pa, pb; 预处理器置换将该声明转换成 flo ...
随机推荐
- 编写代码:ATM的登陆界面(用户验证、主菜单的选择) 查询-- 存款-- 取款-- 退出
#include <stdio.h>#include <windows.h>int main (void){ int password,one,two,money1=10 ...
- JBoss Web和Tomcat的区别
在Web2.0的时代,基于Tomcat内核的JBoss在J2EE应用服务器领域已成为发展最为迅速的应用服务器.这一青出于蓝而胜于蓝的产品与Tomcat的区别又在哪里? 基于Tomcat内核,青胜于蓝. ...
- 开源项目Log4j
一:Log4j入门简介学习 Log4j是Apache的一个开放源代码项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台.文件.GUI组件.甚至是套接口服务器.NT的事件记录器.UNIX ...
- Rails 建立一个资源
在blog 应用程序中.你可以通过脚手架(scaffolded)开始建立一个资源. 这将是单一的blog 提交.请输入以下命令 $ rails generate scaffold Post name: ...
- Linux errno详解
1. 错误码 / errno Linux中系统调用的错误都存储于 errno中,errno由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误. PS: 只有当系统调用或者调用li ...
- orcle查询记录的每天的第一条
select * from ( select elec,time,Row_Number() OVER (partition by trunc(TIME) order by time) ran ...
- iOS开源项目周报0330
由OpenDigg 出品的iOS开源项目周报第十四期来啦.我们的iOS开源周报集合了OpenDigg一周来新收录的优质的iOS开源项目,方便iOS开发人员便捷的找到自己需要的项目工具等. FengNi ...
- 脚手架(create-react-app)没有eject情况下,使用react-scripts的时候,动态设置环境变量
在实际开发中,例如:有时候打包发布时,需要手动更新版本,比如修改package.json中的version,但是如果有时候忘了修改,那么又得build一次: 如果能动态设置多好,webpack下可以在 ...
- Android Studio开发笔记
工欲善其事,必先利其器. 来分享下一些tips吧. android studio优化 我的习惯是从visual studio沿袭过来的,所以快捷键都是参照VS改过来的. 设置调优 不打开上次打开的工程 ...
- hdu 1828 Picture 切割线求周长
Picture Time Limit: 6000/2000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Sub ...