当前位置: 首页 > news >正文

素材网站的下载服务器怎么做定制一款app要多少钱

素材网站的下载服务器怎么做,定制一款app要多少钱,中国站长查询域名备案,桂林做网站的公司有哪些目录 一、多态的概念二、多态的定义及实现1.多态的构成条件2.虚函数的重写2.1 什么是虚函数#xff1f;2.2 虚函数的重写是什么#xff1f;2.3 虚函数重写的两个例外2.4 C11 override 和 final2.5 重载、覆盖(重写)、隐藏(重定义)的对比 三、抽象类3.1 概念3.2 接口继承和实现… 目录 一、多态的概念二、多态的定义及实现1.多态的构成条件2.虚函数的重写2.1 什么是虚函数2.2 虚函数的重写是什么2.3 虚函数重写的两个例外2.4 C11 override 和 final2.5 重载、覆盖(重写)、隐藏(重定义)的对比 三、抽象类3.1 概念3.2 接口继承和实现继承 多态绝命题超级坑四、多态的原理4.1虚函数表4.2多态的原理4.2.1虚基表和虚表 五、单继承和多继承关系的虚函数表5.1 单继承中的虚函数表5.2 多继承中的虚函数表 没有彩蛋有 前言 本篇我们来探索一下C中的多态欢迎大家和我一起走进多态 一、多态的概念 概念通俗说就是多种形态不同的对象去完成同一件事情会产生不同的结果(多种形态) 例子就拿买火车票来说普通人和学生还有军人买票是不一样普通人买票是全价学生买票是半价军人买票是优先买他们都是人但是买票的结果不一样体现了不同的对象干同一件事结果不同 二、多态的定义及实现 1.多态的构成条件 构成多态还有两个条件 ① 被调用的函数必须是虚函数且派生类必须对基类的虚函数进行重写 ② 必须通过基类的指针或者引用调用虚函数 2.虚函数的重写 2.1 什么是虚函数 虚函数即被virtual修饰的类成员函数称为虚函数 class Person { public://被virtual修饰的函数叫虚函数virtual void BuyTicke(){cout Person-全价 endl;} };2.2 虚函数的重写是什么 虚函数的重写(覆盖)派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)称子类的虚函数重写了基类的虚函数。 代码演示 class Person { public:virtual void BuyTicket(){coutPerson-买票-全价endl;} }; class Student:public Person { public://void BuyTicket()virtual void BuyTicket(){coutStudent-买票-全价endl;} };void func(Person p)//构成多态的必要条件之一,在调用虚函数时必须通过基类的指针或者引用来调用 {p.BuyTicket(); } int main() {Person p;Student s;func(p);func(s);return 0; }总结传Person的对象就调用Person类的虚函数传Student对象就调用Student类的虚函数传谁的对象就调用谁对应的虚函数指向谁调用谁的虚函数 2.3 虚函数重写的两个例外 1.协变 派生类重写基类虚函数时与基类虚函数返回值类型不同。即基类虚函数返基类对象的指针或者引用派生类虚函数返回派生类对象的指针或者引用时称为协变。 class A {}; class B :public A {}; class Person { public:virtual A* BuyTicke(){cout Person-全价 endl;} }; class Student :public Person { public:virtual B* BuyTicke(){cout Student-半价 endl;} };注意协变时相对应的虚函数的返回值要么是指针要么是引用不能一个指针一个引用这样不匹配另外返回类型要是一对父子类 2.析构函数的重写 如果基类的析构函数为虚函数此时派生类析构函数只要定义无论是否加virtual关键字都与基类的析构函数构成重写虽然基类与派生类析构函数名字不同。看起来违背了重写的则其实不然这里可以理解为编译器对析构函数的名称做了特殊处理编译后析构函数的名称统一处理成destructor这里也和前面讲的继承那块的析构函数隐藏相呼应 class Person { public:~Person() { cout ~Person() endl; } }; class Student :public Person { public:~Student() { cout ~Student() endl; delete[] _ptr;} private:int* _ptrnew int[10]; }; int main() {Person* p1 new Person;Person* p2 new Student;delete p1;delete p2;return 0; }大家看一下上面的代码有什么问题 我们的期望是指向父类调用父类析构指向子类调用子类析构这不就正是多态的特性嘛 我们直接让析构函数变成虚函数就可以实现我们的期望了 class Person { public://destructor()virtual ~Person() { cout ~Person() endl; } }; class Student :public Person { public://destructor()virtual ~Student() { cout ~Student() endl; delete[] _ptr;} private:int* _ptrnew int[10]; };总结建议析构函数写成虚函数防止发生内存泄漏的问题 其实除了上面的两个例外还有第三个例外就是派生类的虚函数可以不加virtual它也构成重写但是我们不建议不加一般还是加上 为什么派生类的虚函数可以不加virtual呢 正所谓重写子类是继承了对应虚函数的声明(函数名参数返回值等只是重写了它的定义所以不加也可以 2.4 C11 override 和 final C11提供了override和final两个关键字可以帮助用户检测是否重写。 1.final修饰虚函数表示该虚函数不能被重写 class Car { public:virtual void Drive() final {} }; class Benz :public Car { public:virtual void Drive() { cout Benz-舒适 endl; }//这里直接报红色波浪线了上面已经用final修饰虚函数了所以他不能被重写 };final修饰类叫最终类表示该类不能被继承了解一下 2.override: 检查派生类虚函数是否重写了基类某个虚函数如果没有重写编译报错 class Car { public:virtual void Drive() {} }; class Benz :public Car { public:virtual void Drive() override { cout Benz-舒适 endl; } };通俗来说这个override就是用来帮助我们检查这个虚函数有没有被正确重写 2.5 重载、覆盖(重写)、隐藏(重定义)的对比 三、抽象类 3.1 概念 在虚函数的后面写上 0 则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类也叫接口类抽象类不能实例化出对象。派生类继承后也不能实例化出对象只有重写纯虚函数派生类才能实例化出对象。纯虚函数规范了派生类必须重写另外纯虚函数更体现出了接口继承。 class Car//包含纯虚函数的类就是抽象类--抽象类不能被实例化 { public:virtual void Drive() 0;//纯虚函数 };3.2 接口继承和实现继承 普通函数的继承是一种实现继承派生类继承了基类函数可以使用函数继承的是函数的实现。虚函数的继承是一种接口继承派生类继承的是基类虚函数的接口目的是为了重写达成多态继承的是接口。所以如果不实现多态不要把函数定义成虚函数。 多态绝命题超级坑 class A { public:virtual void func(int val 1) { std::cout A- val std::endl; }virtual void test() { func(); } };class B : public A { public:void func(int val 0) { std::cout B- val std::endl; } };int main(int argc, char* argv[]) {B* p new B;p-test();return 0; }//A: A-0 B: B-1 C: A-1 D: B-0 E: 编译出错 F: 以上都不正确首先说一下这道题选B大家一定觉得很奇怪我做的时候选的D我们现在一起来看一下他为什么选B 四、多态的原理 4.1虚函数表 class Base { public:virtual void Func1(){cout Func1() endl;} private:int _b 1; };int main() {coutsizeof(Base)endl;return 0; }这里计算Base类的大小在x86的环境下计算的结果是8在我们之前的认知里类对象模型中类不应该是把成员函数放在了公共的代码段区域吗成员函数不在类里面不参与计算只需要计算他的成员变量就行按理来说的话这里的计算结果应该是4 那我们就来看一下他为什么会计算出8这个结果来 通过调试我们可以看到b对象里面不仅有成员_b而且还有一个_vfptr这个_vfptr又是干什么的呢b对象中的这个_vfptr我们叫做虚函数表指针(v代表virtualf代表function)在x86环境下一个指针的大小是4个字节加上这个int类型的_b算上内存对齐的规则它们刚好是8个字节 一个含有虚函数的类中都至少都有一个虚函数表指针因为虚函数的地址要被放到虚函数表中虚函数表也简称虚表 4.2多态的原理 class Person { public:virtual void BuyTicket() { cout 买票-全价 endl; } protected:int _p 1; }; class Student : public Person { public:virtual void BuyTicket() { cout 买票-半价 endl; } protected:int _s 2; }; void Func(Person p) {p.BuyTicket(); } int main() {Person Mike;Func(Mike);Student Johnson;Func(Johnson);return 0; }多态调用 运行时到指向对象的虚表中找虚函数调用指向父类调用父类的虚函数指向子类调用子类的虚函数 我们转到反汇编看 call eax中存虚函数的指针。这里可以看出满足多态的调用不是在编译时确定的是运行起来 以后到对象的中取找的。 普通调用 编译时调用对象是那个类型就调用它的函数 普通调用在编译链接时就已经确定了call地址首先BuyTicket虽然是虚函数但是Mike是对象不满足多态的条件所以这里是普通函数的调用转换成地址时是在编译时已经从符号表确认了函数的地址直接call地址 4.2.1虚基表和虚表 虚表虚函数表存放的是虚函数的地址目标是实现多态 虚基表存的是当前位置距离虚基类部分的偏移量目的是为了解决菱形继承数据冗余和二义性问题 注意 问题1虚函数存在哪 虚函数和普通函数一样都是存在代码段常量区中不是存在虚函数表中虚函数表中仅仅存放的是虚函数的地址 问题2虚表存在那个区域 我们打印出各个区域的数据地址来观察一下虚表和那个区域的地址最接近以此来判断虚表存在那个区域 class Person { public:virtual void BuyTicket() { cout 买票-全价 endl; } }; class Student : public Person { public:virtual void BuyTicket() { cout 买票-半价 endl; } }; int main() {int i 0;static int j 1;int* p1 new int;const char* p2 xxxxxxxx;printf(栈:%p\n, i);printf(静态区:%p\n, j);printf(堆:%p\n, p1);printf(常量区:%p\n, p2);Person p;Student s;Person* p3 p;Student* p4 s;printf(Person虚表地址:%p\n, *(int*)p);printf(Student虚表地址:%p\n, *(int*)s);return 0; }在vs x86的环境下_vfptr存放在头四个字节的位置 我们取出Person类对象的地址然后将其强制转化为int类型再解引用得到头四个字节的数据就可以取到第一个元素的地址也就是虚函数表指针的地址 根据运行得到的结果可知虚函数表的地址和常量区最接近所以虚函数表应该存放在常量区 五、单继承和多继承关系的虚函数表 5.1 单继承中的虚函数表 以下面的代码为例来进行分析 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; };我们调试来观察一下虚表中有哪些变化 ①基类b对象和派生类d对象虚表是不一样的这里我们发现Func1完成了重写所以d的虚表中存的是重写的Derive::Func1所以虚函数的重写也叫作覆盖覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法覆盖是原理层的叫法 ②另外Func2继承下来后是虚函数所以放进了虚表Func2没有重写所以派生类的虚表中依旧是Base::Func2Func3也继承下来了但是不是虚函数所以不会放进虚表 ③总结一下派生类的虚表生成 a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后 为什么Derive自己的虚函数Func4没有在虚函数表指针中看见 通过调试观察内存窗口我们可以看到_vfptr地址下肯定是存放了Derive::Func1和Base::Func2的地址的它下面还有一个地址00b014c9我们不确定这个地址是不是Func4的地址我们现在来验证一下我们的猜想 //函数指针 typedef void (*VFPTR) (); //依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数 void PrintVFT(VFPTR* vft) {for (size_t i 0; i 3; i){printf(%p-, vft[i]);VFPTR pf vft[i];(*pf)();//pf();调用函数} } int main() {Base b;Derive d;//函数指针数组VFPTR p[4];VFPTR* ptr (VFPTR*)(*(int*)(d));PrintVFT(ptr);return 0; }PrintVFT函数的思想 我们可以通过这个函数来打印出_vfptr中的存放的那个地址到底是不是Func4 事实上第三个位置存放的就是Func4的地址只是vs只显示前两个咱也不知道为什么你也可以认为这是vs下的一个bug 总结单继承虚函数对象模型如下 补充 同类型的对象的虚表是同一个虚表 5.2 多继承中的虚函数表 以下面代码为例来讲解 class Base1 { public:virtual void func1() { cout Base1::func1 endl; }virtual void func2() { cout Base1::func2 endl; } private:int b1; }; class Base2 { public:virtual void func1() { cout Base2::func1 endl; }virtual void func2() { cout Base2::func2 endl; } private:int b2; }; class Derive : public Base1, public Base2 { public:virtual void func1() { cout Derive::func1 endl; }virtual void func3() { cout Derive::func3 endl; } private:int d1; };int main() {Derive d;return 0; }在监视窗口下我们来看一下对象d的结构我们发现他有两个虚函数表两个直接父类各自都有一个虚函数表那Derive 类自生的虚函数func3应该存放在那个虚函数表中呢 我们还是和之前一样把虚函数打印出来看看func3到底存在了那个虚表中我们发现打印第一个虚函数表容易但是第二个不好打印 下面提供两种方法打印第二个虚表的內容 方法1 VFPTR* vTableb2 (VFPTR*)(*(int*)((char*)d sizeof(Base1))); (char*)d – 先取出d整体的地址来然后强转成char*类型这样N就跳过N个字节再sizeof(Base1)加上Base1大小的字节就可以跳过Base1到Base2的位置后面的操作就和以前的一样 缺陷 这个方法有个缺陷就是万一跳过Base1之后后面紧挨着的不是Base2的地址怎么办说不定有别的变量在Base1后面挨着我们的方法2就可以完美避开这个缺陷 方法2 Base2* ptr d; VFPTR* vTableb2 (VFPTR*)(*(int*)ptr); Base2* ptr d – 这一步直接就是取出d整体的地址然后赋值给Base2的指针ptr这里就发生了切片ptr直接就是指向了Base2处然后再进行之前的这个操作(VFPTR*)((int)ptr)就的很 看打印的结果我们可以知道Derive自己的虚函数func3放在了第一个继承的父类虚函数表中 所以多继承对象虚函数模型大体都是下面这样 没有彩蛋有 来个小题收收尾 class A{ public:A(char *s) { coutsendl; }~A(){} }; class B:virtual public A { public:B(char *s1,char*s2):A(s1) { couts2endl; } }; class C:virtual public A { public:C(char *s1,char*s2):A(s1) { couts2endl; } }; class D:public B,public C { public:D(char *s1,char *s2,char *s3,char *s4):B(s1,s2),C(s1,s3),A(s1){ couts4endl;} }; int main() {D *pnew D(class A,class B,class C,class D);delete p;return 0; }这题我们首先就可以排除BC对于派生类对象来说在构造时遵循的原则是先父后子所以不可能最后初始化A至于初始化class B和class C的顺序肯定是按照继承的顺序来所以选A 多态暂且到这里我后续会继续进行补充谢谢大家的观看哦✋
http://www.pierceye.com/news/513613/

相关文章:

  • 餐饮网站建设优化建站wordpress copyright
  • 腾讯建站官网设计网页步骤
  • 网站建设三方合同范本wordpress数字链接出现404
  • 下载用的网站怎么做网站模板怎么使用教程
  • 没有网站 可以做百度口碑吗展馆的科普网站建设
  • 河北网站备案查询系统商城网站seo
  • 网站申请页面网站空间不够用怎么办
  • 网站开发最合适的搭配螺栓球网架
  • 广东网站建设排名凡科建站下载
  • 建设厅网站预算员报名时间网站建设策划书的编制
  • 厦门手机网站建设公司哪家好鲜花网站源码
  • 北京家居网站建设如何制作软件手机软件
  • 北京网站建设策划解决方案长沙建设工程造价网站
  • 北京网站设计公司价格阿里云wordpress插件
  • 网站建设自助建站企业萧山人才网手机版
  • 长沙建站挺找有为太极wordpress 需要zend
  • 通信管理局 网站备案天猫网站设计教程
  • 营销型网站制作成都打造品牌的三点策略
  • 做查工资的网站如何下载网页在线视频
  • 北沙滩网站建设公司主页怎么填
  • 手机asp网站网站设计方案
  • 长春市网站开发广东一站式网站建设推荐
  • 企业网站推广策略商会联盟网站建设方案
  • 清丰网站建设百度推广建设网站是不是合发
  • 邢台12345网站哪个公司的装饰设计公司
  • 嘉兴网嘉兴网站建设手机网站管理软件
  • 网站主色调简介怎么说本地常州微信网站建设
  • 电子商务网站数据库建设怎样推广一个网站
  • illustrator 学习网站wordpress外链产品
  • 电脑端网站一般做多宽最好网页游戏制作成本