面向对象编程:多态性(C++)
文章目录
一、简介
1、介绍:同样的消息给各种不同的对象时,会产生多种形式的结果,这就是多态性。
注:
(1)、继承层次中的对象
(2)、消息是重载的函数 (因为重载的函数的签名才会一样,即函数名和类型)
2、多态性能设计和实现更具扩展性的软件系统。
3、多态性的两个底层技术:虚函数和动态绑定
(一)、静态绑定:系统在编译时就确定如何实现某一动作;
(二)、动态绑定:系统在运行时动态实现某一动作(在编译时不确定执行哪段代码)。
4、多态发生的两个要素:
(1)、虚函数的调用;
(2)、指向派生类对象或引用的基类指针(它们不同时刻指向不同派生类的对象)。
二、类继承层次中对象之间的关系
1、派生类对象可以被当做它的基类对象来处理。
不能把基类对象当做任何一个派生类的对象来处理。
GraduateStudent gs;//实例化一个派生类对象gs
Student s = gs;//基类对象指向派生类对象
Student &t = gs;//基类的引用可以指向派生类
Student *p = &gs;//基类的指针可以指向派生类对象
gs = s;//错误,不可以派生类对象指向基类对象
GraduateStudent *pGs = &s;//错误,派生类指针不可以指向基类对象
2、调用的函数功能是由调用函数的句柄(指针或引用)决定的,而不是由句柄所指向的对象类型决定。
例如:
/*一个基类指针指向派生类*/
commissionEmployeePtr = &basePlusCommissionEmployee;//commissionEmployeePtr为基类指针,basePlusCommissionEmployee为派生类对象
commissionEmployeePtr -> print();//print函数功能是由基类决定的,不是派生类。找句柄所属类的对象。
3、通过基类指针调用派生类的成员函数:
1、基类指针指向派生类对象,并试图访问只在派生类中拥有的成员函数,会产生编译错误(派生类中有的成员函数基类中没有)。
2、显示把基类指针强制转化为派生类指针(向下强制类型转换),可以通过指向派生类对象的基类指针访问只在派生类中拥有的成员。
例如:
/*getBaseSalary(),setBaseSalary()为派生类中的函数;commissionEmployeePtr为基类指针*/
double baseSalary = commissionEmployeePtr ->getBaseSalary();
commissionEmployeePtr -> setBaseSalary(500);//两条语句错误。
以上情况可以用强制类型转换的方法,把基类指针commissionEmployeePtr转换成派生类指针。
4、虚函数(动态绑定)
利用虚函数,调用哪个版本的虚函数就由句柄所指向的对象类型决定,而非句柄的类型。
虚函数的定义在函数原型前加关键字virtual.函数原型和实现都加关键字。
注意:动态的绑定只发生句柄必须是基类的指针或引用,发送消息的函数是虚函数;如果是一个确定的基类对象和派生类对象,没有动态的性质。
举例:
/*以下print()函数全是virtual函数*/
commissionEmployeePtr = &commissionEmployee;//基类指针指向基类对象
commissionEmployeePtr -> print();//此处的print()自然找基类的print()执行
basePlusCommissionEmployeePtr = &basePlusCommissionEmployee;//派生类指针指向派生类对象
basePlusCommissionEmployeePtr -> print();//print()找派生类中的print()操作
commissionEmployeePtr = &basePlusCommissionEmployee;//基类指针指向派生类对象
commissionEmployeePtr -> print();//此处print()将调用派生类中的print()函数
(1)、派生类中重载的函数必须和在基类中的具有相同的原型,如以上的print函数。
(2)、用虚函数进行 动态绑定,只能通过指针或引用句柄来实现。
(3)、一旦一个函数声明为virtual,那么从整个继承层次的那一点向下的所有类中,它将保持virtual的,即使当派生类重写此函数时并没有显示的将它声明为virtual.
(4)、当派生类选择不重写从其基类继承而来的virtual函数时,派生类就会简单地继承它的基类的virtual函数的实现。
三、抽象类和纯virtual函数
1、抽象类:有些类通常在类的继承层次结构中作为基类,从来不实例化任何对象,这种类称为抽象类。
注:
(1)、抽象类是不完整的,派生类必须定义缺少的部分。
(2)、构造一个抽象类的目的就是为其它的类提供合适的基类。
(3)、能实例化对象的类称为具体类,具体类实现了它所定义的每一个成员函数。
2、通过声明类的一个或多个virtual函数为纯虚函数,就能定义其为抽象类。
纯虚函数定义:
virtual void draw() = 0;
3、纯虚函数不提供函数的具体实现,每个派生的具体类必须重写所有类的纯虚函数的定义。
4、抽象类为类层次结构中的各种类定义公共的接口。抽象类包含一个或多个纯虚函数,这些函数必须在派生类中重写。
(1)、试图实例化抽象类的对象将导致编译错误。
(2)、未能在派生类中重写纯virtual函数,然后试图实例化这个派生类对象将产生编译错误。
四、应用向下强制类型转换
1、向下强制类型转换
BasePlusCommissionEmployee *derivedPtr = dydynamic_cast<BasePlusCommissionEmployee *>
(employee[i]);
返回值:derivedPtr不为零,说明其指向一个BasePlusCommissionEmployee 对象
否则,它指的不是这种BasePlusCommissionEmployee 的对象。
返回运算符操作数类型的信息(要写出类型的名字)
cout << typeid(int).name() << endl;
或
int a ;
cout << typeid(a).name() << endl;
输出的都是int
五、virtual析构函数
1、将基类的析构函数声明为虚析构函数,这样就会使所有派生类的析构函数自动成为虚析构函数(即使它们与基类析构函数名不同)。
2、当基类指针指向其派生类对象,用delete删除基类指针时,就会调用其所指对象(即其对应派生类)的析构函数,所有类的析构函数将执行,如果派生类要求执行析构函数,那么基类必须声明为虚析构函数。
class A
{
public:
A()
{}
virtual ~A();
};
A::~A()
{
cout << "~A"<< endl;
}
class B:public A
{
public:
B(int);
virtual~B();
private:
int *b;
};
B::B(int n)
{
b = new int(n);//实例化B类对象
}
B::~B()
{
cout << "~B" << endl;
delete b;//要回收开辟的
}
在继承层次关系中,在某一层出现开辟空间的操作,则要把析构函数声明为虚析构函数。
3、构造函数不能是virtual函数。
六、(补充)类模板和模板类
1、类模板和模板类
所谓类模板,实际上是建立一个通用类,其数据成员、成员函数的返回类型和形参类型不具体指定,用一个虚拟的类型来代表。
使用类模板定义对象时,系统会根据实参的类型(模板实参)来取代类模板中虚拟类型从而实现了不同类的功能。
2、定义一个类模板,格式如下:
template<typename Type>
class 类名
{
类成员函数
}
1、template是一个声明模板的关键字
2、Type是类型参数(自定义)
或
template<class Type>
class 类名
{
类成员函数
}
例如:建立一个用来实现求和的类模板
类模板定义对象时用以下形式
类模板名<实际类型名> 对象名[{实参表列}]
int main()
{
Three <int> sum3_1(3,5,7);//用类模板定义对象sum3_1,此时T被int取代
Three <double> sum3_2(12.34,34.56,56.78);//用类模板定义对象sum3_2,此时T被double取代
cout << "三个整数之和是" << sum3_1.sum() << endl;
cout << "三个双精度数之和是:" << sum3_2.sum() << endl;
return 0;
3、类模板中的成员函数可以在类模板体外定义。此时,若成员函数中有类型参数存在,则C++有一些特殊的规定:
(1)、需要在成员函数定义之前进行模板声明;
(2)、在成员函数名前缀上“类名<类型参数>::” 在类模板体外定义的成员函数的一般形式如下:
template <typename 类型参数>
函数类型 类名<类型参数>::成员函数(形参表)
{
...........
}
例如,上例这种成员函数Three在类模板体外定义时,应该写成:
template<typename T>
T Three<T>::sum()
{
return x + y + z;
}