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