文章目录
- 多态的定义及实现
-
- 构成多态两个条件
- ⭐虚函数
- 虚函数检查
- 重载,重写,重定义区别
- 抽象类
-
- 实现继承和接口继承
- 多态的原理🚩
-
- 虚函数表
- 原理
- 静态绑定与动态绑定
- 多继承
多态的定义及实现
坐公交时,爱心卡,学生卡,老人卡车票不一样价钱,这就是一种多态
多态在不同继承关系的类对象,调用同一函数,产生不同行为
构成多态两个条件
- 必须是基类指针或引用调用虚函数
- 派生类虚函数必须重写
#include <iostream>
using namespace std;
class person
{
public:
virtual void print()
{
cout << "person" << endl;
}
};
class student :public person
{
public:
virtual void print()
{
cout << "student" << endl;
}
};
void func(person& people)
{
people.print();
}
int main()
{
person p;
student s;
func(p);
func(s);
return 0;
}
person student
⭐虚函数
🚩前面加virtual就是虚函数 🚩虚函数重写(覆盖):派生类有一个跟基类相同的函数(返回类型,函数名字,参数列表),三相同,称子类虚函数重写了基类的虚函数
虚函数两个例外: 1,协变 基类和派生类虚函数返回类型不同。即基类函数返回基类指针或引用,派生类函数返回派生类指针或引用。
class person
{
public:
virtual person* test()
{
return new person;
}
};
class student :public person
{
public:
virtual student* test()
{
return new student;
}
};
2,析构函数 🚩析构函数默认构成多态,无论加不加virtual,编译器对析构函数名称特殊处理,处理后析构函数通称destructor
#include <iostream>
using namespace std;
class person
{
public:
virtual ~person()
{
cout << "~person()" << endl;
}
};
class student :public person
{
public:
virtual ~student()
{
cout << "~student()" << endl;
}
};
int main()
{
person* p1 = new person();
person* p2 = new student();
delete p1;
delete p2;
return 0;
}
~person() ~student() ~person()
虚函数检查
函数重写比较严格,有时函数名写错,编译是检查不出来的,运行后没得到预期结果才去找bug,c++11提供两个关键词==final,override,==来检查重写 1,final:虚函数不准被重写,否则编译不通过
class person
{
public:
virtual void A() final
{ }
};
class student :public person
{
public:
virtual void A()
{ }
};
2,override:检查函数是否重写了某个基类函数,没重写则编译报错
class person
{
public:
virtual void A()
{ }
};
class student : public person
{
public:
virtual void A() override
{ }
};
重载,重写,重定义区别
抽象类
在虚函数后面加上 =0 则为纯虚函数,包含纯虚函数的类叫抽象类,也叫接口类,抽象类不能实例化对象,派生类继承后也不能实例化对象,只有重写虚函数才能实例化对象。纯虚函数规范了派生类必须重写,更体现了接口继承。
class A
{
public:
virtual void print() = 0
{}
};
class B
{
public:
virtual void print()
{
cout << "B" << endl;
}
};
实现继承和接口继承
普通函数的继承是实现继承,派生类继承基类的函数,可以使用,继承的是函数的实现, 虚函数的继承是一种接口继承,派生类继承的是虚函数的接口,目的是实现重写,所以不实现多态,不要把普通函数定义为虚函数。
多态的原理🚩
虚函数表
求A类大小
#include <iostream>
using namespace std;
class A
{
virtual void func1()
{}
private:
int _a;
};
int main()
{
cout<<sizeof(A)<<endl;
return 0;
}
16 这里_vfptr是虚函数表指针,我是64位系统,指针8字节和整型4字节,对齐规则是16
🚩 一个含有虚函数的类都至少含有一个虚函数表指针(虚表),虚函数的地址要放进虚表中
接着分析虚表
#include <iostream>
using namespace std;
class A
{
public:
virtual void func1()
{}
virtual void func2()
{}
void func3()
{}
int _a;
};
class B :public A
{
public:
virtual void func1()
{ }
};
int main()
{
A a;
B b;
return 0;
}
结论: 🚩1,派生类有自己的虚表,内容继承基类,如果有重写(如func1)则覆盖基类虚表自己代替,如果派生类有新增的虚函数,则按照声明顺序依次放在虚表最后 2,func2(),是虚函数,继承下来放进虚表,func3(),是普通函数就没进 3,虚函数表本质是指针数组,一般情况下数组末尾放一个nullptr。
4,虚表存的是虚函数指针,虚函数和普通函数一样,都存在代码段 对象中存的是虚表指针,vs中虚表存在代码段
原理
class person
{
public:
virtual void print()
{
cout << "person" << endl;
}
};
class student :public person
{
public:
virtual void print()
{
cout << "student" << endl;
}
};
void func(person& people)
{
people.print();
}
int main()
{
person p;
student s;
func(p);
func(s);
return 0;
}
开篇的代码, 🚩当传p时,基类指针调用基类的虚函数,当传s时,基类指针调用派生类的虚函数, 这样就完成不同对象完成同一行为时,展现不同形态。
🚩满足多态的函数调用不是在编译时确定的,是在运行时到对象中现找的。不满足多态的函数编译时就确认好了。
静态绑定与动态绑定
静态绑定:又称前期绑定,在程序编译时就确认程序行为,也称静态多态,如函数重载 动态绑定:又称后期绑定,在程序运行时根据拿到的具体类型确定具体行为去调用函数,也称动态多态
多继承
多继承未重写的虚函数放在第一个虚表的末尾
评论前必须登录!
注册