目录
一:什么是多态
多态的概念:通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态。
举个例子:买票
普通人全价,学生半价,军人全价但优先购票....
这就是对买票这一个具体行为,不同人去完成时产生的不同结果。
二:如何构建多态
- 必须通过基类的指针或者引用调用虚函数
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
好吧是不是看不懂,那我还是举例说明
class human//定义普通人的类
{
public:
virtual void buy()
{
cout << "普通人 全价 100元" << endl;
}
};
class student :public human//定义继承了普通人类的学生类
{
public:
virtual void buy()//子类完成对父类虚函数的重写
{
cout << "学生 半价 50元" << endl;
}
};
void BuyTicket(human* p)
{
p->buy();//必须通过指针和引用调用虚函数
}
//void BuyTicket(human& p)也可以这样子,这样传参的时候下面就不加引号
//{
// p.buy();
//}
int main()
{
student st;
human hu;
BuyTicket(&st);//子类传对象切片
BuyTicket(&hu);父类对象传地址
return 0;
}
大概是这样的
好吧,我们大概搞懂了是这样传值的流程,可是这个virtual虚函数是个什么东西?重写又是个啥?
三:虚函数
定义:
虚函数:即被virtual修饰的类成员函数称为虚函数。
格式:
virtual +成员函数;
好吧看起来是不是挺简单的
重写:
在介绍重写这部分的时候,不知道大家对于函数重载还有没有概念。
函数重载举一个例子:在同一个作用域内,函数名相同,参数不同
class A
{
public:
void fun(int i)
{
cout << i << endl;
}
void fun()
{
cout << "haha" << endl;
}
};
重写三大特性
1.函数名相同
2.返回值类型相同
3.参数列表相同
好的我们看看刚才的代码
class human//第一个类
{
public:
virtual void buy()//返回类型相同,函数名相同,参数相同
{
cout << "普通人 全价 100元" << endl;
}
};
class student :public human//第二个类
{
public:
virtual void buy()//返回类型相同,函数名相同,参数相同
{
cout << "学生 半价 50元" << endl;//这里就是重写了
}
};
所以说只要牢记重写的三大特点,以及是否在同一作用域,就很容易区分重写和重载的区别
这里还是有两个例外,大家作为了解就可以了,现实中也不常用到
协变:
返回值类型不同,子类返回子类虚函数的指针或引用,父类返回父类虚函数的指针或引用
举例:
class Father
{};
class Son : public Father
{};
class A {
public:
virtual Father* f()//父类返回父类的指针或引用
{
return new Father;
}
};
class B : public A {
public:
virtual Son* f()//子类返回子类的指针或引用
{
return new Son;
}
};
析构函数的重写:
在c++的编译器中,析构函数这里默认都给了同一个名称destructor,你看上去,这里是virtual~A和virtual~B,实际上都是~virtual destructor
这样其实也就满足了我们重写的特性,返回值类型相同,函数名相同,参数相同
class A
{
public:
virtual~A()//实际上是virtual~destructor()
{
cout << "HHHHHHHHHHHHHHHHHH" << endl;
}
};
class B :public A
{
public:
virtual~B()//实际上是virtual~destructor()
{
cout << "666666666666666" << endl;
}
};
int main()
{
B b;
return 0;
}
C++11新增的 override 和 final
override
检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。(定义在子类)
class A
{
public:
virtual void fun()
{
}
};
class B :public A
{
public:
virtual void fun(int a)override//我这里在参数列表中加入了一个int,就不构成重写,编译器就会报错
{
}
};
final
修饰虚函数,表示该虚函数不能再被继承(定义在父类)
class A
{
public:
virtual void fun()final//加了final后,子类无法继承该函数
{
}
};
class B :public A
{
public:
virtual void fun()//所以这里会报错
{
}
};
重载、覆盖(重写)、隐藏(重定义)的对比
四、多态的原理:
虚函数表
我们先来看一个经典例题
//求B,也就是sizeof(A)的大小
class A
{
public:
virtual void fun()
{
cout << "fun()" << endl;
}
private:
int age;
};
int main()
{
A a;
int B=sizeof(a);
cout << B << endl;
return 0;
}
按照正常的思维,这里我们只定义了一个int age的private,那么应该是只有四个字节
然后,结果如下:
这是因为一个含有虚函数的类中都至少都有一个虚函数表指针
我们看调试代码
这个_vfptr就是我们的虚函数表指针因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。
然后我们再对上面的代码改造一下
我们对classA增加两个函数,然后再新增一个class B继承
class A
{
public:
virtual void fun1()
{
cout << "fun1()" << endl;
}
virtual void fun2()//新增有virtual
{
cout << "fun2()" << endl;
}
void fun3()//新增无virtual
{
cout << "fun3()" << endl;
}
private:
int age;
};
class B :public A//新增类继承
{
public:
virtual void fun1()//重写
{
cout << "fun1()" << endl;
}
private:
int id;
};
int main()
{
A a;
B b;
int B=sizeof(a);
int C = sizeof(b);
cout << B << endl << C << endl;
return 0;
}
然后我们调用调试代码
同时结果如下:
解释一下,8=我们自定义int的4个字节+virtual的4个字节(父类就一共八个字节)
12= 8(子类拷贝父类的虚表,有覆盖就覆盖,没覆盖就直接拷贝)+4我们自定义的int类型4个字节(子类拷贝覆盖的父类虚函表+子类自定义)
同时我们也需要知道,其实我们并不是直接创立的虚函数表,而是virtual这个虚表指针指向的虚函数表头,如下:
这就表明了,我们子类的虚函数,不仅继承了父类虚函数中的_vfptr还能重写了父类的虚函数
好吧看一大堆是不是有点看不懂,那直接看总结吧
总结:
1.虚函数自身自带一个_vfptr的虚函数表
2.子类虚函数继承父类虚函数时,不仅继承父类虚函数表,也可以对虚函数进行重写。
3.子类虚函数继承父类虚函数时,非虚函数不会存放在_vfptr中
4.虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
5.虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr。
动态绑定与静态绑定
动态绑定:
动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。
理解到这一点之后,我们重新来看最开始多态是如何用重写来找到对应的函数的。
在运行该代码的时候,我们编译器自动在_vfptr,也就是虚函数表中寻找对应的函数来匹配,所以不是在编译时确定的,是运行起来以后到对象的中去找的我们称为动态绑定
静态绑定:
静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,比如:函数重载