多态
- 多态性是面向对象程序设计的重要特征之一。
- 多态性是指发出同样的信息被不同类型的对象接收时有可能导致完全不同的行为。
- 多态的实现:
- 函数重载
- 运算符重载
- 模板
- 虚函数
前三者为静态绑定,虚函数为动态绑定
静态绑定与动态绑定
- 静态绑定
- 绑定过程出现在编译阶段,在编译期就已经确定要调用的函数
- 动态绑定
- 绑定过程工作在程序运行时执行,在程序运行时才确定将要调用的函数
虚函数
概念与定义
- 虚函数的概念:在基类中冠以关键字virtual的成员函数
- 虚函数的定义:
- virtual 函数类型 函数名称(参数列表)
- 如果一个函数在基类中被声明为虚函数,则他在所有派生类中都是虚函数
- 只有通过基类指针或引用调用虚函数才能引发动态绑定
- 虚函数不能被声明为静态
#include<iostream>
using namespace std;
class Base
{
public:
virtual void Fun1()
{
cout << "Base::Fun1 ..." << endl;
}
virtual void Fun2()
{
cout << "Base::Fun2 ..." << endl;
}
void Fun3()
{
cout << "Base::Fun3 ..." << endl;
}
};
class Derived : public Base
{
public:
virtual void Fun1()
{
cout << "Derived::Fun1 ..." << endl;
}
void Fun2()
{
cout << "Derived::Fun1 ..." << endl;
}
void Fun3()
{
cout << "Derived::Fun1 ..." << endl;
}
};
int main(void)
{
Base* p;
Derived d;
p = &d;
p->Fun1(); //Fun1是虚函数,基类指针指向派生类对象,调用的是派生类对象的虚函数
p->Fun2();
p->Fun3(); //Fun3非虚构函数,根据p指针实际类型来调用相应的成员函数
return 0;
}
输出结果:
虚析构函数
-
何时需要虚析构函数?
- 当你可能通过基类指针删除派生类对象时
- 如果你打算允许其他人通过基类指针调用对象的析构函数(通过delete这样做是正常的),并且被析构的对象是有重要的析构函数的派生类对象,就需要让基类的析构函数作为虚函数
- 析构函数可以声明为虚函数
- delete 基类指针
- 程序会根据基类指针指向的对象的类型确定要调用的析构函数
- 基类的析构函数为虚函数,所有派生类的析构函数都是虚函数
- 构造函数不得是虚函数
- 析构函数还可以是纯虚函数
- 对于一个没有任何接口的类,如果想要将它定义为抽象类,只能将虚构函数声明为纯虚的
- 通常情况下基类中的纯虚函数不需要实现,例外是纯虚析构函数要给出实现(给出一个空的实现方法即可)。
#include<iostream>
using namespace std;
class Base
{
public:
virtual void Fun1()
{
cout << "Base::Fun1 ..." << endl;
}
Base()
{
cout << "Base ..." << endl;
}
virtual ~Base()
{
cout << "~Base ..." << endl;
}
};
class Derived : public Base
{
public:
/*virtual */void Fun1()
{
cout << "Derived::Fun1 ..." << endl;
}
Derived()
{
cout << "Derived ..." << endl;
}
~Derived()
{
cout << "~Derived ..." << endl;
}
};
int main(void)
{
Base* p;
p = new Derived;
p->Fun1();
delete p;
return 0;
}
输出结果:
虚表指针
- 虚函数的动态绑定是通过虚表来实现的。
- 包含虚函数的类头4个字节存放指向虚表的指针
#include<iostream>
using namespace std;
class Base
{
public:
virtual void Fun1()
{
cout << "Base::Fun1 ..." << endl;
}
virtual void Fun2()
{
cout << "Base::Fun2 ..." << endl;
}
int data1_;
};
class Derived : public Base
{
public:
void Fun2()
{
cout << "Derived::Fun2 ..." << endl;
}
virtual void Fun3()
{
cout << "Derived::Fun3 ..." << endl;
}
int data2_;
};
typedef void(*FUNC)();
int main(void)
{
Base b;
Derived d;
long** p = (long**)(&b);
FUNC fun = (FUNC)p[0][0];
fun();
fun = (FUNC)p[0][1];
fun();
p = (long**)(&d);
fun = (FUNC)p[0][0];
fun();
fun = (FUNC)p[0][1];
fun();
fun = (FUNC)p[0][2];
fun();
return 0;
}
输出结果:
内存分析:
overlaod、overwrite、override
- 成员函数被重载的特征:
- 相同的范围(在同一类中)
- 函数名字相同
- 参数不同
- virtual关键字可有可无
- 覆盖是指派生类函数覆盖基类函数,特征是:
- 不同的范围(分别位于派生类和基类)
- 函数名字相同
- 参数相同
- 基类名必须有virtual关键字
- 重定义(派生类与基类)
- 不同的范围(分别位于派生类与基类)
- 函数名与参数都相同,无virtual关键字
- 函数名相同,参数不同,virtual可有可无
纯虚函数
- 在基类中不能给出有意义的虚函数定义,这时可以把它说明成纯虚函数,把它的定义留给派生类来做
-
定义纯虚函数:
- class 类名
{
virtual 返回值类型 函数名(参数表)=0;
};
- class 类名
纯虚函数不需要实现
只要类中含有一个纯虚函数,则该类为抽象类
抽象类
- 作用
- 抽象类为抽象和设计的目的而声名,将有关的数据和行为组织在一个继承层次结构中,保证派生类具有要求的行为
- 对于暂时无法实现的函数,可以声明为纯虚函数,留给派生类去实现
- 注意
- 抽象类只能作为基类来使用
- 不能声明抽象类的对象
- 构造函数不能是虚函数,析构函数可以是虚函数
- 抽象类不能用于直接创建对象实例,可以声明抽象类的指针和引用
- 可使用指向抽象类的指针支持运行时多态性
- 派生类中必须实现基类中的纯虚函数,否则它仍将被看作一个抽象类
#include<iostream>
#include<vector>
using namespace std;
class Shape
{
public:
virtual void Draw() = 0;
};
class Circle :public Shape
{
public:
void Draw()
{
cout << "Circle::Draw() ..." << endl;
}
};
class Square :public Shape
{
public:
void Draw()
{
cout << "Square::Draw() ..." << endl;
}
};
void DrawAllShape(const vector<Shape*>& v)
{
vector<Shape*>::const_iterator it;
for (it = v.begin(); it != v.end(); ++it)
{
(*it)->Draw();
}
}
int main(void)
{
//Shape S; //Error,不能实例化抽象类
vector<Shape*> v;
Shape* ps;
ps = new Circle;
v.push_back(ps);
ps = new Square;
v.push_back(ps);
DrawAllShape(v);
return 0;
}
输出结果:
多态的优点
- 多态性有助于更好地对程序进行抽象
- 控制模块能专注于一般性问题得处理
- 具体的操作交给具体的对象去做
- 多态性有助于提高程序的可扩展性
- 可以把控制模块与被操作的对象分开
- 可以添加已定义类的新对象,并能管理该对象
- 可以添加新类(已有类的派生类)的新对象,并能管理该对象