C++ Primer Plus(十三)——类继承
1. 两种构造函数的区别
// first one
Table::Table(const string& fn) : name(fn){}
// another one
Table::Table(const string& fn)
{
name = fn;
}
第一种直接调用string的复制构造函数将name初始化为fn,第二种先为name调用string的默认构造函数,再调用string的赋值运算符将name初始化为fn。
2. 使用公有派生,基类的公有成员成为派生类的公有成员,基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护成员访问,不能直接访问
3. 创建派生类对象时,程序首先调用基类的构造函数,再调用派生类的构造函数。基类的构造函数负责初始化继承的数据成员,派生类的构造函数主要用于初始化新增的数据成员。派生类的构造函数总是调用一个基类的构造函数,可以使用初始化器列表来指明要使用的基类构造函数,否则将使用默认的基类构造函数。
4. 派生类对象过期时,程序将首先调用派生类析构函数,再调用基类析构函数。
5. 除了虚基类外,类只能将值传回相邻的基类,但后者可以使用相同的机制将信息传递给相邻的基类,以此类推。
6. 派生类对象可以使用基类的方法,条件是方法不是私有的。
7. 基类指针可以在不进行显式类型转换的情况下,指向派生类对象,基类引用可以在不进行显式类型转换的情况下引用派生类对象,然而基类的指针或引用只能用于调用基类的方法。通常,C++要求引用和指针类型与赋给的类型匹配,但这一规则对继承来说,是个例外,然而这种例外是单向的,不可以将基类对象和地址赋给派生类引用和指针。
8. 公有继承建立一种is-a关系,即派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行,但它不能建立has-a、is-like-a、is-implemented-as-a、use-a关系。
9. 如果没有使用关键字virtual,程序将根据引用类型和指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。
10. 方法在基类中被声明为虚的后,它在派生类中将自动成为虚方法。然而,在派生类中使用关键字virtual来指出哪些函数是虚函数也不失为一个好方法。
11. 基类声明虚析构函数是为了确保释放派生类对象时,按正确的顺序调用析构函数。
12. 派生类的构造函数在初始化基类的私有数据时,采用的是成员初始化列表语法;非构造函数不能使用成员列表语法,但派生类方法可以使用作用域解析符 :: 调用公有的基类方法,如果派生类没有重新定义该方法,则可省略解析符。
13. 函数名联编:将源代码中的函数调用解析为执行特定的函数代码块
静态联编(早期联编):在编译过程中进行的联编
动态联编(晚期联编):编译器生成能够在程序运行时选择正确的虚方法的代码
14. 为什么有两种类型的联编以及为什么默认为静态联编?
原因有两个:效率和概念模型
(1)为使程序在运行阶段进行决策,必须使用一些方法来跟踪基类指针或引用指向的对象类型,这增加了额外的处理开销。由于静态联编效率更高,因此被设置为C++的默认选择。Strousstrup说,C++的指导原则之一是,不要为不使用的特性付出代价(内存或处理时间),仅当程序设计确实需要虚函数时,才使用他们。
(2)在设计类时,可能包含一些不在派生类重新定义的成员函数,不将这些函数设置为虚函数,有两方面的好处:首先效率更高,其次指出不要重新定义该函数。这表明,仅将那些预期将被重新定义的方法声明为虚的。
14. 在使用虚函数时,在内存和执行速度上都有一定的成本,包括:
(1)每个对象都将增大,增大量为存储地址的空间
(2)对于每个类,编译器都将创建一个虚函数地址表(数组)
(3)对于每个函数的调用,都需要执行一项额外的操作,即在表中查找地址
15. 通常,应该给基类提供一个虚析构函数,即使他并不需要析构函数
16. 虚函数经验规则:
(1)如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针,这种特性被称为返回类型协变(这种例外只适用于返回值,而不适用于参数)
(2)如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。如果只重新定义其中的一个版本,则另外版本就被隐藏,派生类对象将无法使用他们
17. 最好对类数据成员采用私有访问控制,不要使用保护访问控制,同时通过基类方法使派生类能访问基类数据;然而,对于成员函数来说,保护访问控制很有用,它让派生类能够访问公众不能使用的内部函数。
18. 在设计前,首先应开发一个模型,指出编程问题所需的类,以及他们之间的相互关系。如果要设计类继承层次,则只能将那些不会被用作基类的类设计为具体的类。
19. 如果基类使用动态内存分配,并重新定义赋值和复制构造函数,如果派生类不使用动态内存分配,那派生类的复制构造函数将会调用基类的复制构造函数,派生类的默认赋值运算符将会自动使用基类的赋值运算符对基类组件进行赋值;如果派生类使用动态内存分配,派生类的显式赋值运算符必须负责多有继承的基类的赋值,可以通过显式的调用基类赋值运算符,或通过函数表示法,即使用作用域解析符来完成这项工作。
20. 下列情况下,将使用复制构造函数:
(1)将新对象初始化为一个同类对象
(2)按值将对象传递给函数
(3)函数按值返回对象
(4)编译器生成临时对象
21. 不要将赋值和初始化弄混淆了,如果语句创建新的对象,则使用初始化;如果语句修改已有对象的值,则是赋值。默认赋值为成员赋值,如成员为类对象,则默认成员赋值将使用相应类的赋值运算符,如果需要显式定义复制构造函数,也需要显示定义赋值运算符。
22. 要将类对象转换为其他类型,应定义转换函数,转换函数可以是没有参数的类成员函数,也可以是返回类型为目标类型的类成员函数,C++11支持将关键字explicit用于转换函数,它允许使用强制类型转换进行显式转换,但不允许隐式转换。
23. 函数不能返回在函数中创建的临时对象的引用,因为当函数结束时,临时对象将消失,因此这种引用时非法的。通用的规则是。如果函数返回在函数中创建的临时对象,则不要使用引用;如果函数返回的是通过引用或指针返回给它的对象,则应按引用返回对象。
24. 如果派生类使用了new,则必须提供显式赋值运算符,必须给类的每个成员提供赋值运算符,而不仅仅是新成员。
25. is-a关系允许基类引用指向子类对象,但赋值运算符只处理基类成员。基类的对象一般不能赋值给派生类对象,除非具有基类向派生类转换的转换构造函数,该函数可以接受一个类型为基类的参数和其他参数,条件是其他参数有默认值,还有一种方法是定义一个用于基类赋值给派生类的赋值运算符。
26. 使用私有数据成员比使用保护数据成员要好,但保护方法很有用。
27. 如果希望派生类能够重新定义方法,则应在基类中将方法设为虚的,这样可以启用动态联编;如果不希望重新定义方法,则不必将其声明为虚的,这样虽然无法禁止他人重新定义方法,但表达了这样的意思:不希望它被重新定义。
28. 基类的析构函数是虚的,这样当通过指向对象的基类指针或引用来删除派生类对象时,程序首先调用派生类的析构函数,然后调用基类的析构函数,而不仅仅是调用基类的析构函数。
29. 由于友元并非类成员,因此不能继承,如果希望派生类的友元函数能够使用基类的友元函数,则可以通过强制类型转换符,将派生类引用或指针转化为基类引用或指针,然后使用转换后的指针或引用来调用基类的友元函数。
30. 公有继承建立is-a的关系,派生类继承基类的数据成员和大多数方法,但不继承基类的构造函数,析构函数,赋值运算符。
转载于:https://my.oschina.net/shou1156226/blog/836730
上一篇: PHP:declare的用法详解
下一篇: iOS 之多线程