【c++学习合辑】多态
前言
在实习工作中,多态是经常使用到的,在此首先总结一下我的看法:多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。一、编译期多态(静态多态)
通过重载函数实现:先期联编
重载函数:在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同,不能仅通过返回类型的不同来重载函数。
下面的实例中,同名函数 print() 被用于输出不同的数据类型:
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
// 输出整数
pd.print(5);
// 输出浮点数
pd.print(500.263);
// 输出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
整数为: 5
浮点数为: 500.263
字符串为: Hello C++
二、运行期多态(动态多态)
通过虚函数实现 :滞后联编
C++运行时多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(Override),或者称为重写。
首先跳转参考虚函数介绍:
在掌握了虚函数的知识后,我们常说的多态实际上是指动态多态,多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。
需要注意:
1、只有类的成员函数才能声明为虚函数,虚函数仅适用于有继承关系的类对象。普通函数不能声明为虚函数。
2、静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。
3、内联函数(inline)不能是虚函数,因为内联函数不能在运行中动态确定位置。
构造函数不能是虚函数。
4、析构函数可以是虚函数,而且建议声明为虚函数。
三、重写 vs 重载
重写可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性。而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。
四、隐藏
个人认为隐藏是子类对象对于父类和子类函数的判断,是否将父类的方法隐藏而使用自己的方法。
除了重载与覆盖(重写),C++还有隐藏。隐藏是指派生类的函数屏蔽了与其同名的基类函数。隐藏规则如下:
如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual 关键字,基类的函数将被隐藏(注意别与重载混淆,重载是在同一个类中发生)。
如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆,覆盖有virtual关键字)。
举栗子
#include<iostream>
using namespace std;
class Base
{
public:
virtual void f(float x)
{
cout<<"Base::f(float)"<< x <<endl;
}
void g(float x)
{
cout<<"Base::g(float)"<< x <<endl;
}
void h(float x)
{
cout<<"Base::h(float)"<< x <<endl;
}
};
class Derived : public Base
{
public:
//子类与基类函数同名,有virtual关键字,运行时多态
virtual void f(float x)
{
cout<<"Derived::f(float)"<< x <<endl; //多态、覆盖
}
//子类与基类函数同名,且无virtual关键字,隐藏
//参数不同的隐藏
void g(int x)
{
cout<<"Derived::g(int)"<< x <<endl; //隐藏
}
//参数相同的隐藏
void h(float x)
{
cout<<"Derived::h(float)"<< x <<endl; //隐藏
}
};
int main(void)
{
Derived d; //子类对象
Base *pb = &d; //基类类型指针,指向子类对象
Derived *pd = &d; //子类类型指针,指向子类对象
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14 父类对象调用子类方法,多态
pd->f(3.14f); // Derived::f(float) 3.14 子类对象调用子类自己方法
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14 父类对象调用父类方法
pd->g(3.14f); // Derived::g(int) 3 子类对象调用子类自己方法
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 父类对象调用父类方法
pd->h(3.14f); // Derived::h(float) 3.14 子类对象调用子类自己方法
return 0;
}
有virtual才可能发生多态现象,不发生多态(无virtual)调用就按原类型调用 。
上面的程序中:
(1)函数Derived::f(float)覆盖了Base::f(float),发生多态重写(覆盖)。
(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。