多态的知识有以下几点
1.什么是多态?
简单来说就是不同的对象去完成同一个行为会产生不同的结果。
举个例子:国庆去华山游玩,一般的成人要买全价票,而我们学生买票买学生票。这就是两种不同的对象都去买票但是产生的结果却不同,一个是全价票一个是学生票。
2.如何使用多态?
使用多态有两个前提条件
1.调用函数的对象必须是指针或者引用
2.调用的函数必须是虚函数,而且完成了虚函数的重写
class Person
{
public:
virtual void BuyTicket()
{
cout << "全价票" << endl;
}
};
class Student : public Person
{
public:
virtual void BuyTicket()
{
cout << "学生票" << endl;
}
};
void Fun(Person & p)
{
p.BuyTicket();
}
那么为什么要是对象的指针或引用?
什么又是虚函数?什么又是虚函数的重写?
3.虚函数的重写(覆盖)
虚函数:
所谓的虚函数就是在函数前面加上virtual这个关键字。那么他就成为了虚函数。
虚函数的重写:
派生类中有一个跟基类的完全相同虚函数,我们就称子类的虚函数重写了基类的虚函数,完
全相同是指:函数名、参数、返回值都相同。另外虚函数的重写也叫作虚函数的覆盖。
正因为完成了虚函数的重写,才会产生不同的行为,所以这是多态的两个必要条件之一。
4.多态的原理
多态是如何实现的呢?
class Person
{
public:
virtual void BuyTicket()
{
cout << "全价票" << endl;
}
int a;
};
int main()
{
Person p;
return 0;
}
这个类里只有一个成员变量A。我们通过调试查看监视
我们发现不仅仅是一个成员变量还有一个_vfptr。
他是一个隐藏的指针,叫虚函数表指针,他指向了虚函数表的地址,虚函数表里存放的都是虚函数的地址。
我们可以看到BuyTicket这个函数的地址就是_vfptr[0]的值。
再次结合一段代码来看一下关于虚函数的继承关系
class A
{
public:
virtual void Fun1()
{
cout << "Fun1-A" << endl;
}
virtual void Fun2()
{
cout << "Fun2-A" << endl;
}
void Fun3()
{
cout << "Fun3-A" << endl;
}
};
class B : public A
{
public:
virtual void Fun1()
{
cout << "Fun1-B" << endl;
}
}
A类中有两个虚函数,一个普通函数,B类继承了A类且只对Fun1完成了重写
再次观察,我们发现B类继承了A类,b也有虚函数表,因为对Fun1完成了重写,我们可以发现两个类的Fun1的地址是不相同的。
而Fun2是直接继承的,没有完成重写,所以我们可以看到他们的地址是相同的。
并且我们发现Fun3没有在虚表里出现,也就是说只有虚函数才会被放进虚函数表,普通函数不会。
多态就是通过传参找到对象,然后去对应对象的虚表中找到对应的虚函数调用。
所以必须传指针或者引用,不能传值,并且必须完成虚函数的重写。只有完成了重写才能实现出不同的对象完成同一行为,产生不同的结果。
5.抽象类:
介绍抽象类之前我们先了解一下纯虚函数
virtual void Fun1() = 0
{
cout << "Fun1-A" << endl;
}
这就是纯虚函数在虚函数后面加上 =0 就是纯虚函数
抽象类就是由纯虚函数实现的。
抽象类的特点:
抽象类无法实例化出对象,派生类也无法实例化出对象,只有当派生类完成了虚函数的重写,才能实例化出对象。
这样就意味着强制我们派生类完成虚函数的重写,不然不能使用,这样的好处就是防止出现无用的虚函数。抽象类体现了接口继承关系。
了解完上面的知识,我们最后在了解一下有关虚函数的问题。
6.所有函数都可以是虚函数吗?
答案是NO。
1.首先内联函数无法成为虚函数,因为我们都知道虚函数的地址放在虚表里面,而内联函数没有地址,没有地址就没有办法放进虚表,所以不行。
2.静态成员函数也不行,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。
3.构造函数也不能成为虚函数,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。