继承——多态与虚拟函数——使用集成解决问题——一个简单的聚丙类——使用句柄类——微妙之处
继承
继承(inheritance)继承是OOP的基石之一。在比较一个类与另一个类时,除了一些扩充以外,其余部分完全相同,此时我们可以考虑使用使用继承特性。类A是B类的一个基类,由于B类是A类的派生类,因此基类A中的每个成员(构造函数、赋值运算符函数和析构函数除外)也是类B的成员。在派生类中可以创建新的成员,也可以重定义基类的成员,但是在派生类中不能删除任何基类成员。
class B:public A{
public:
private:
};
回顾保护类型
基类的私有成员不能被派生类访问,如果派生类需要使用基类的成员,可以利用protected(保护类)关键字。这赋予了派生类访问基类中保护成员的权利,同时又能够使这些成员不被类的其他使用者所访问。
对于基类的私有成员,只有基类的成员函数以及它的友元函数可以访问,而基类的派生类如果想访问基类的私有成员,只能通过基类的共有成员函数对私有成员进行访问。
操作函数
在定义函数时,在定义派生类自己的函数(基类没有的)时,只能写成:派生类的类名称::函数名称{}。如果需要具体特定的某个函数,需要添加范围运算符。
min函数在头文件<algorithm>中定义,具有两个参数,并且这两个参数是同一类型的。
继承与构造函数
编译器生成派生类对象:
- 为整个对象分配内存空间(包括基类与派生类中定义的数据)
- 调用基类的构造函数以便初始化对象中的基类部分数据
- 使用构造初始化器对对象的派生类部分数据进行初始化
- 如果有的话,执行派生类构造函数的函数体
那么如何决定调用基类中的哪个构造函数呢?使用构造初始化器指定想要调用的基类构造函数。在派生类的构造初始化器中使用它的基类构造函数。在派生类的构造初始化器中使用它的基类名并在基类名后面附上一个参数列表(可以为空)。这些参数用于构造对象中基类部分的初始值;同时,编译器根据参数的个数与类型选择调用积累中的哪一个构造函数。如果初始化时没有指定调用基类中的那一个构造函数,那么编译器将调用基类中的哪一个构造函数。如果初始化时没有指定调用基类中的哪一个构造函数,那么编译器将调用基类默认构造函数以构造对象的基类部分。
C++没有要求派生类构造函数一定要带有与基类构造函数相同的参数类型。
多态与虚拟函数
派生类B继承自基类A,所以每个B类对象都有一个A类的部分;还可以用A&参数的函数传递一个B&的对象作为参数。如果函数以指针作为参数,可以将指向B类的指针传给它,编译器会将B*转换成A*,并将指针绑定到B对象中的基类A部分。如果该函数以一个基类A类型对象为参数,那么实际传递过去的只是对象的基类A部分。
在不确定对象类型时获得对象的值
当我们写一个函数,这个函数的参数类型将会决定着调用哪个函数(基类中的/派生类中的),而这个参数类型只有到运行的时候才能知道。这个时候就需要用到虚拟函数了。
虚拟函数是要在函数的定义里加入关键字virtual,而且这个关键字只能在类的定义里被使用。如果函数体在声明之外单独定义,那么我们无法在定义时重复virtual关键字。与此类似,如果类中一个函数是虚拟的,那么在派生类中它的虚拟特性也会被继承,因此在派生类中同一个函数的声明不需要再重复virtual关键字。
动态绑定
只有在以引用或者指针为参数调用虚拟函数时,它的运行时选择特性才会有意义。如果我们以对象(相对于通过引用或指针而言)的名义调用一个虚拟函数,那么我们可以在编译时了解对象的类型。对象的类型一旦确定,即使在运行时也不会改变。与此相反,引用或者指针指向的对象的实际类型在运行时是可以变化的。针对于编译时期就可以确定类型的函数时静态绑定的。
在要求一个指向基类对象的指针或引用的地方,我们可以用一个指向派生类的指针或引用来代替,这是OOP中的一个关键概念,即多态性(polymorphism)。C++通过虚拟函数的动态绑定特性以支持多态性。
有两种方法可以生成基类类型对象或者派生类类型对象:默认构造函数用来生成一个被适当初始化的空对象;而另一个带istreame&类型参数的构造函数从一个指定的流中获得初始值。
*注意*:对于虚拟函数,无论我们是否调用他们,都要在程序中对他们进行定义。是因为如果只对虚拟函数进行了声明而没有定义,许多编译器就会产生一些奇怪的错误信息。当然对于非虚拟的函数,只要不调用,可以只声明不定义。
使用继承解决问题
实际类型待定的容器
对于类型待定的容器,可以用:
vector<A*> aa; //指向基类的指针,也可指向派生类。回到具体时候确定对象类型。
虚拟析构函数
在对一个指针调用delete函数时发生了两件事情:第一件是调用了指针所指对象的析构函数,第二件是对象所占用的内存空间被释放回系统。
当删除对象时,因为基类和派生类采用的析构函数是不一样的,所以需要区分如何能区分是基类对象还是派生类对象呢?
这时候也可以用虚拟函数,也就是虚拟的析构函数。析构函数的函数体是空的。在全部通过一个指向基类的指针以删除一个派生类对象时,都要用到虚拟析构函数。当然没有必要在派生类中添加一个析构函数,因为在继承时,已经继承了基类的这个虚拟析构函数。
一个简单的句柄类
对于希望程序能够同时处理两种不同类型的记录,可以利用句柄类来完成。
静态成员函数与一般的成员函数不同,静态成员函数不能对类的对象进行操作。与其他成员函数不同的是,静态成员函数与类关联,而不是与一个特定的类型对象关联。因此,静态成员函数不能访问类型对象的非静态数据成员,由于没有与该函数关联的对象,因此他不能访问任何成员。
不能再一个静态成员函数中使用this关键字,这种函数只能访问静态成员数据。由于静态成员数据是在类定义的范围以外被初始化的,因此必须在初始化他的时候在成员数据前面加上类名限制:
value-type class-name::static-member-name=value;// 对class-name类中的static-member-name这个静态成员数据
//进行初始化,对该成员赋以初始值value。
读取句柄
在C++语言中删除一个零指针是无害的。
复制句柄对象
将一个类声明为另一个类的友元类可以使该类中的全部成员都成为另一个类的友元。例如:
class A{
friend class B; //B类就可以访问A类中的所有成员了,包括私有和保护的
……
};
友元关系不能被继承,也不能过渡:友元的友元以及从友元派生的类并没有任何特殊的特权。
通常,在派生类中重定义基类的一个函数时,参数列表与返回类型都是相同的。但是如果基类中的函数返回一个指向基类对象的指针(或引用),那么派生类中相应的函数将返回一个指向派生类对象的指针(或引用)。
微妙之处
需要哪个函数
重载
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual
关键字可有可无。
覆盖
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
隐藏
不属于上面那俩,就是隐藏了
*注意*:如果一个基类函数与一个派生类函数具有相同的函数名,但是两个函数的参数个数与参数类型都不相同,那么他们就是两个完全不相干的两个函数。