虚函数:在类的成员函数前面加上关键字“virtual”,就称这个成员函数为虚函数。
虚函数重写:在子类中定义了一个与父类完全相同(函数名和返回类型以及参数列表都相同)的虚函数时,称子类的这个函数重写(也称覆盖)了父类的这个虚函数。
多态:一个类继承另一个类时,对一个函数进行重写,则此函数呈多态(多种形态)。
为了指明某个成员函数具有多态性,就用关键字“virtual”来标志其为虚函数。
#include<iostream>
#include<string>
using namespace std;
class Person//父类
{
public:
virtual void BuyTickets()//虚函数
{
cout << "全价票" << endl;
}
};
class Student :public Person//子类
{
public:
virtual void BuyTickets()//虚函数
{
cout << "半价票" << endl;
}
};
void Fun(Person& p)
{
p.BuyTickets();
}
void Test()
{
Person p;
Student s;
Fun(p);
Fun(s);
}
上面的函数中void BuyTickets()
函数就呈多态
上图的前提条件式是void BuyTickets()
必须构成多态
这里我们要注意判断调用的函数是否构成多态?
1、父类的指针或者引用
class Person
{
public:
virtual void BuyTickets()
{
cout << "全价票" << endl;
}
};
class Student:public Person
{
public:
virtual void BuyTickets()
{
cout << "半价票" << endl;
}
protected:
int _num;
};
//父类的指针或者引用
void Fun(Person* p)
//void Fun(Person& p)
{
p->BuyTickets();
}
void Test()
{
Person p;
Student s;
Fun(&p);
Fun(&s);
}
2、虚函数的重写(virtual+函数完全相同(协变例外))
协变是一种例外,函数返回值可以不同但有条件,条件是返回类型必须是构成父子关系的指针或引用
下面就是协变的情况
//返回值类型构成父子关系的指针或者引用
//指针
class Person
{
public:
virtual Person* BuyTickets()
{
cout << "全价票" << endl;
return this;
}
};
class Student :public Person
{
public:
virtual Student* BuyTickets()
{
cout << "半价票" << endl;
return this;
}
};
//引用
class Person
{
public:
virtual Person& BuyTickets()
{
cout << "全价票" << endl;
return *this;
}
};
class Student :public Person
{
public:
virtual Student& BuyTickets()
{
cout << "半价票" << endl;
return *this;
}
};
这种协变的情况也是能构成多态的
注:BuyTickets()
在父类中声明为virtual,该虚函数的性质自动的继承给子类,所以Student子类中的virtual可以省略;
如果要在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual;
注意:1、最好把基类的析构函数声明为虚函数
Why?
void func(Base* p)
{
//...
delete p;
}
p是传递过来的一个对象指针,它或者指向基类对象,或者子类对象。在执行delete p
时,要调用析构函数,但不知道是指向基类的析构还是子类的析构,将析构函数声明为虚函数就可以解决这个问题(基类的析构函数与子类的析构函数构成多态)。
注意:2、 构造函数不能是虚函数并且构造函数里也不能调用虚函数
因为构造时对象是不完整的,对象还是一片未定型的空间,可能会发生未定义的行为,只有在构造完成后,对象才能成为一个完整的类的实例。
注意:3、静态成员函数不能定义为虚函数,因为静态成员函数不受限于某个对象
纯虚函数
class Person
{
virtual void Display()=0;//纯虚函数
protected:
string _name;
}
class Student:class Person
{
//...
}
在成员函数的形参后面写上=0,则成员函数为虚函数。包括纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。
其实纯虚函数就是在基类中为子类保留的一个位置,以便以后子类用自己实在的函数定义来覆盖它。如果在基类中没有保留位置,则就不能覆盖了。