【C++初阶】多态
2022/1/27 22:34:29
本文主要是介绍【C++初阶】多态,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
目录
多态的条件
析构函数的重写
C++11 override 和 final
1.final:修饰虚函数,表示该虚函数不能再被重写
2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
重载、覆盖(重写)、隐藏(重定义)的对比
抽象类
多态的原理
虚函数表
多态的原理
单继承和多继承关系的虚函数表
单继承中的虚函数表
多态的条件
class Person { public: virtual void BuyTicket() { cout << "正常排队—全价买票" << endl; } protected: int _age; string _name; }; class Student:public Person { public: virtual void BuyTicket() { cout << "正常排队—半价买票" << endl; } protected: int _age; string _name; }; class Solider:public Person { public: virtual void BuyTicket() { cout << "优先排队—全价买票" << endl; } protected: int _age; string _name; }; //多态两个条件 //1,子类重写父类的虚函数 //2,必须是父类的指针或者引用去调用虚函数 void Func(Person* ptr) { //多态—ptr指向父类对象调用父类的虚函数,指向子类对象调子类虚函数 ptr->BuyTicket(); } int main() { Person ps; Student st; Solider sd; Func(&ps); Func(&st); Func(&sd); }
多态的两个条件
1.子类重写父类的虚函数
2.必须是父类的指针或者引用去调用虚函数
析构函数的重写
class Person { public: virtual ~Person() { cout << "~Person()" << endl; } }; class Student :public Person { public: virtual ~Student() { cout << "~Student()" << endl; } }; //如果不是虚函数,他们之间是隐藏关系 //是虚函数,他们是重写关系 int main() { 普通场景下面,虚函数是否重写都是OK的 //Person p; //Student s; //new对象特殊场景 Person* p1 = new Person; Person* p2 = new Student; delete p1;//p1->destructor() delete p2;//p2->destructor() return 0; }
注意!析构函数要定义成虚函数
有些书籍会把多态进行更多的划分:
1,静态的多态——函数的重载,调用同一个函数,传不同的参数,就有不同的行为/形态
2,动态的多态——调用一个虚函数,不同对象去调用,就有不同的行为/形态
多态的条件:
a,子类中重写父类的虚函数
b,必须是父类的指针或者引用去调用重写的虚函数
虚函数重写条件:
a,子类和父类都必须是虚函数
b,函数名,参数,返回值都必须相同
例外:
a,协变
b,析构函数被特殊处理
c,子类中的重写虚函数可以不加virtual
C++11 override 和 final
1.final:修饰虚函数,表示该虚函数不能再被重写
2.override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错
class Car{ public: virtual void Drive(){} }; class Benz :public Car { public: virtual void Drive() override { cout << "Benz-舒适" << endl; } };
重载、覆盖(重写)、隐藏(重定义)的对比
抽象类
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承
class Car { public: virtual void Drive() = 0; }; class Benz :public Car { public: virtual void Drive() { cout << "Benz-舒适" << endl; } }; class BMW :public Car { public: virtual void Drive() { cout << "BMW-操控" << endl; } }; void Test() { Car* pBenz = new Benz; pBenz->Drive(); Car* pBMW = new BMW; pBMW->Drive(); }
多态的原理
虚函数表
虚函数表:
如下代码的输出结果是?
class Base { public: virtual void Func1() { cout << "Func1()" << endl; } private: int _b = 1; }; int main() { Base b; cout << sizeof(Base) << endl; return 0; }
在看一段代码:
class Base { public: virtual void Func1() { cout << "Base::Func1()" << endl; } virtual void Func2() { cout << "Base::Func2()" << endl; } void Func3() { cout << "Base::Func3()" << endl; } private: int _b = 1; }; class Derive : public Base { public: virtual void Func1() { cout << "Derive::Func1()" << endl; } private: int _d = 2; }; int main() { Base b; Derive d; return 0; }
父子类无论是否完成虚函数的重载,都有各自独立的虚表
一个类的所有对象,共享一张虚表
多态的原理
class Person { public: virtual void BuyTicket() { cout << "买票-全价" << endl; } }; class Student : public Person { public: virtual void BuyTicket() { cout << "买票-半价" << endl; } }; void Func(Person& p) { p.BuyTicket(); } int main() { Person Mike; Func(Mike); Student Johnson; Func(Johnson); return 0; }
分析===》
这里存在问题:多态的这种调用也是付出代价的,多态的调用要到虚表里面去找,会有一定程度的性能损失
复习:普通函数是如何确认地址的?编译或链接时确定它的地址
编译:编译的时候往上去找有无这个函数的定义,编译完,找到地址,编程call
链接:只有他的声明,他的定义在其他的目标文件里面,这个时候先过,call(?),链接的时候拿这个函数名去找
多态的调用?
先检查是不是满足多态,多态的调用是运行时去确定地址,如何确定?==》去指向对象的虚函数表中知道虚函数的地址
单继承和多继承关系的虚函数表
单继承中的虚函数表
class Base { public: virtual void Func1() { cout << "Base::Func1()" << endl; } virtual void Func2() { cout << "Base::Func2()" << endl; } void Func3() { cout << "Base::Func3()" << endl; } private: int _b = 1; }; class Derive : public Base { public: virtual void Func1() { cout << "Derive::Func1()" << endl; } virtual void Func4() { cout << "Derive::Func4()" << endl; } private: int _d = 2; }; typedef void(*VFPTR)(); //打印虚表 void PrintVFT(void* vft[]) { printf("%p\n",vft); for (size_t i = 0; vft[i] != nullptr; i++) { printf("vft[%d]:%p->",i,vft[i]); VFPTR f = (VFPTR)vft[i]; f(); } printf("\n"); } int main() { Base b; Derive d; PrintVFT((void**)*(int*)&b); PrintVFT((void**)*(int*)&d); return 0; }
虚函数存在哪的?虚表存在哪的?
答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
那么虚表存在哪的呢?===>常量区(代码段)
虚函数编译出来函数指令跟普通函数一样,存在代码段,虚函数地址又被放到虚函数表中
int main() { Base bb; int a = 0; int* p1 = new int; const char* p2 = "hello world"; auto pf = PrintVFT; static int b = 1; printf("栈祯变量:%p\n",&a); printf("堆变量:%p\n", p1); printf("常量区变量:%p\n", p2); printf("函数地址变量:%p\n", pf); printf("静态区变量:%p\n", &b); printf("虚函数表地址:%p\n", *(int*)&bb); return 0; }
这篇关于【C++初阶】多态的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享