多态相关知识2
- 抽象基类和纯虚函数
- 纯虚函数和多继承
- 虚析构函数
- 虚析构函数作用
- 纯虚析构函数
- 重写 重载 重定义
抽象基类和纯虚函数
在设计时,常常希望基类仅仅作为其派生类的一个接口。这就是说,仅想对基类进行向上类型转换,使用它的接口,而不希望用户实际的创建一个基类的对象。同时创建一个纯虚函数允许接口中放置成员原函数,而不一定要提供一段可能对这个函数毫无意义的代码。
做到这点,可以在基类中加入至少一个纯虚函数(pure virtual function),使得基类称为抽象类(abstract class).
- 纯虚函数使用关键字virtual,并在其后面加上=0。如果试图去实例化一个抽象类,编译器则会阻止这种操作。
- 当继承一个抽象类的时候,必须实现所有的纯虚函数,否则由抽象类派生的类也是一个抽象类。
- Virtual void fun() = 0;告诉编译器在vtable中为函数保留一个位置,但在这个特定位置不放地址。
建立公共接口目的是为了将子类公共的操作抽象出来,可以通过一个公共接口来操纵一组类,且这个公共接口不需要事先(或者不需要完全实现)。可以创建一个公共类.
案例: 模板方法模式
//抽象制作饮品
class AbstractDrinking{
public:
//烧水
virtual void Boil() = 0;
//冲泡
virtual void Brew() = 0;
//倒入杯中
virtual void PourInCup() = 0;
//加入辅料
virtual void PutSomething() = 0;
//规定流程
void MakeDrink(){
Boil();
Brew();
PourInCup();
PutSomething();
}
};
//制作咖啡
class Coffee : public AbstractDrinking{
public:
//烧水
virtual void Boil(){
cout << "煮农夫山泉!" << endl;
}
//冲泡
virtual void Brew(){
cout << "冲泡咖啡!" << endl;
}
//倒入杯中
virtual void PourInCup(){
cout << "将咖啡倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething(){
cout << "加入牛奶!" << endl;
}
};
//制作茶水
class Tea : public AbstractDrinking{
public:
//烧水
virtual void Boil(){
cout << "煮自来水!" << endl;
}
//冲泡
virtual void Brew(){
cout << "冲泡茶叶!" << endl;
}
//倒入杯中
virtual void PourInCup(){
cout << "将茶水倒入杯中!" << endl;
}
//加入辅料
virtual void PutSomething(){
cout << "加入食盐!" << endl;
}
};
//业务函数
void DoBussiness(AbstractDrinking* drink){
drink->MakeDrink();
delete drink;
}
void test(){
DoBussiness(new Coffee);
cout << "--------------" << endl;
DoBussiness(new Tea);
}
纯虚函数和多继承
多继承带来了一些争议,但是接口继承可以说一种毫无争议的运用了。
绝大数面向对象语言都不支持多继承,但是绝大数面向对象对象语言都支持接口的概念,c++中没有接口的概念,但是可以通过纯虚函数实现接口。
接口类中只有函数原型定义,没有任何数据定义。
多重继承接口不会带来二义性和复杂性问题。接口类只是一个功能声明,并不是功能实现,子类需要根据功能说明定义功能实现。
注意:除了析构函数外,其他声明都是纯虚函数。
虚析构函数
虚析构函数作用
虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
class People{
public:
People(){
cout << "构造函数 People!" << endl;
}
virtual void showName() = 0;
virtual ~People(){
cout << "析构函数 People!" << endl;
}
};
class Worker : public People{
public:
Worker(){
cout << "构造函数 Worker!" << endl;
pName = new char[10];
}
virtual void showName(){
cout << "打印子类的名字!" << endl;
}
~Worker(){
cout << "析构函数 Worker!" << endl;
if (pName != NULL){
delete pName;
}
}
private:
char* pName;
};
void test(){
People* people = new Worker;
people->~People();
}
纯虚析构函数
纯虚析构函数在c++中是合法的,但是在使用的时候有一个额外的限制:必须为纯虚析构函数提供一个函数体。
那么问题是:如果给虚析构函数提供函数体了,那怎么还能称作纯虚析构函数呢?
纯虚析构函数和非纯析构函数之间唯一的不同之处在于纯虚析构函数使得基类是抽象类,不能创建基类的对象。
//非纯虚析构函数
class A{
public:
virtual ~A();
};
A::~A(){}
//纯析构函数
class B{
public:
virtual ~B() = 0;
};
B::~B(){}
void test(){
A a; //A类不是抽象类,可以实例化对象
B b; //B类是抽象类,不可以实例化对象
}
如果类的目的不是为了实现多态,作为基类来使用,就不要声明虚析构函数,反之,则应该为类声明虚析构函数。
重写 重载 重定义
重载,同一作用域的同名函数
- 同一个作用域
- 参数个数,参数顺序,参数类型不同
- 和函数返回值,没有关系
- const也可以作为重载条件 //do(const Teacher& t){} do(Teacher& t)
重定义(隐藏)
- 有继承
- 子类(派生类)重新定义父类(基类)的同名成员(非virtual函数)
重写(覆盖)
- 有继承
- 子类(派生类)重写父类(基类)的virtual函数
- 函数返回值,函数名字,函数参数,必须和基类中的虚函数一致
class A{
public:
//同一作用域下,func1函数重载
void func1(){}
void func1(int a){}
void func1(int a,int b){}
void func2(){}
virtual void func3(){}
};
class B : public A{
public:
//重定义基类的func2,隐藏了基类的func2方法
void func2(){}
//重写基类的func3函数,也可以覆盖基类func3
virtual void func3(){}
};