用jquery做的网站,vs 2017网站开发php,潍坊程序设计网站建设公司,seo门户前言 在学完继承之后#xff0c;紧接着我们来认识多态#xff0c;建议继承不太熟的先把继承部分的知识点搞熟#xff0c;再来学习多态#xff0c;否则会走火入魔#xff0c;会混乱。因为多态是建立在继承的基础之上#xff0c;而且多态中还存在与继承类似的概念#xff…前言 在学完继承之后紧接着我们来认识多态建议继承不太熟的先把继承部分的知识点搞熟再来学习多态否则会走火入魔会混乱。因为多态是建立在继承的基础之上而且多态中还存在与继承类似的概念所以学习多态的过程中多多与继承区别做些对比会容易区分并且记住。 对于多态通俗点说就是多种形态具体来说就是不同的对象去完成某个行为时产生出不同的状态。举两个例子比如买票这个行为成人买是全票学生是半价买票军人是优先买票再比如支付宝抢红包老用户就少些新用户就多些这些都是一种多态行为具体向下看看实现和原理等相关知识点吧。 多态介绍 构成条件 ①通过基类的指针或引用调用虚函数 ②被调用的函数是虚函数且派生类必须重写基类的虚函数 如下图通过Func函数进而使用引用去调用虚函数在Person-Student的父子类中子类重写了父类的虚函数BuyTicket由此构成了多态满足了不同的人购买票的条件不一样。 eg 虚函数重写 虚函数被virtual修饰的类成员函数。 eg 重写也叫覆盖派生类的一个虚函数与基类的一个虚函数的函数名、返回值类型、参数列表完全相同叫做派生类的虚函数重写了基类的虚函数。 eg 以上就是构成多态的两个条件除此之外还有两个例外这两个例外也构成多态。 注意 ①除了两个例外不符合重写就是符合隐藏关系 ②子类的的虚函数不加virtual关键字也可以但建议加上。 两个例外 1.协变 基类和派生类的虚函数返回值可以不同但必须分别是具有父子关系的类的指针或引用这样也可以构成多态。当然也不局限于当前基类和派生类其他具有父子关系的也可以但一定要父类返回父类子类返回子类。 eg 2.析构函数的重写 如果基类的析构函数处理成虚函数无论派生类的析构函数加不加virtual那么基类和派生类的析构函数构成重写进而构成多态。 一是多态关系中即使派生类中的函数不加virtual关键字也依旧构成多态 二是编译器会对每个类的析构函数做处理编译后析构函数的名会被统一处理成destructor所以满足了同名的条件 存在这个特例的主要目的就是为析构函数构成多态做铺垫所以建议在继承中析构函数定义成虚函数那为什么要让析构函数构成多态呢看下面这种情况 通过new的方式实例化对象因为是new了Person和Student类的空间所以希望delete可以将这两块空间成功释放掉但是如果是下面这种类的实现就会产生问题 我们发现delete p2调用的是Person的析构函数new出来的Student空间没有完全释放掉为什么因为这是普通调用并没构成多态delete p2①p2-destructor;②operator delete(p2);中在编译时期就确定了destructor的函数地址再看到p2是Person类型的指针所以调用Person的destructor导致意想不到的错误。如何处理见下方类实现 只有派生类的析构函数重写基类的析构函数才能构成多态进而保证p1、p2指向的对象正确调用析构函数。 注意上面这种特例不要与下面两种普通情况搞混下面两种无需构成多态也能正确调用析构函数 一般情况下大家只需要记住这种特例就行或者习惯在具有继承关系的两个类的析构函数加上virtual关键字进而构成多态否则在需要构成多态的情境下会出现意想不到的结果。 override和final overdide和final这是两个关键字由c11提供可以帮助用户检测虚函数是否被重写因为重写的条件比较多有时候会因为疏忽比如函数名并没有相同并没有构成多态但编译器不会报错所以有时候会加大debug的难度。 final表示该函数不能被重写。 注意 ①使用场景极少 ②final还可用修饰类表示禁止其他类继承比如class DontDerive final{};。 eg override一般修饰子类的虚函数检测此虚函数是否重写了父类的某个虚函数。 eg 抽象类 纯虚函数在虚函数后面加上【 0】。 抽象类包含纯虚函数的类也叫接口类。 抽象类不能实例化出对象派生类继承抽象类后也不能实例化出对象只有重写纯虚函数才能实例化出对象。这属于一种间接强制重写的手段而且纯虚函数的重写体现出了接口继承即派生类继承的是基类虚函数的接口函数头使用自己的函数体也就是基类的虚函数的出现本质就是给派生类虚函数重写的。与接口继承对应的就是实现继承普通函数的继承就是实现继承基类实现的函数派生类继承以后可以使用。 eg 多态原理 如下图Person对象中有一个虚函数和一个属性sno通过监视我们可以看到Person类实例化出的对象中除了sno外还有一个指针变量__vfptr实际上这个指针是虚函数表指针简称虚表指针指向一个虚函数表简称虚表而在虚函数表中就存在着类中的虚函数地址。值得注意的是一个含有虚函数的类中都至少有一个虚表指针且指向一个虚表。 eg 到底多态的原理是什么呢每个包含虚函数的类都会有一个虚函数表虚函数表是一个存放着虚函数地址的指针数组其中每个指针指向该类的虚函数。当进行函数调用时通过对象的虚函数指针找到对应的虚函数表然后根据函数在虚函数表中的索引找到实际要调用的函数这个过程不是在编译时发生的而是在运行时发生的不满足多态的函数调用则是在编译时就确认函数地址然后运行时直接调用。 单继承和多继承的虚表 单继承的虚表 如下图可以看到父类Person的虚表中有func2和func3func1不是虚函数所以不存在虚表中而在子类Student的虚表之中我们可以看到不仅存在父类中的虚函数且重写了对应虚函数但是并没有看到子类自己的虚函数其实有的只是编译器的监视窗口并未显示下面会用代码打印出两个虚表的虚函数具体看一下两个类中虚函数情况。不过在此之前先总结一下子类的虚表生成过程 ①先将基类中的虚表内容拷贝一份到派生类虚表中 ②如果派生类重写了基类中某个虚函数用派生类自己的虚函数覆盖虚表中基类的虚函数 ③派生类自己的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。 注意虚函数并不存在虚表中虚表也不存在类和对象中。而是虚表中存在着虚函数的地址虚函数和普通函数一样存在于代码段中只是地址存在了虚表中而已并且对象中存的是虚表指针不是虚表而虚表也是存在于代码段之中的不同平台结果不一样。 eg: 打印类的虚函数的代码 VFPTR是被typedef的一个函数指针函数传入一个虚表指针遍历虚表中的虚函数地址分别调用一下对应虚函数。虚表本质上就是一个存放虚函数地址的指针数组最后会有一个nullptr所以调用到空指针为止。 那如何使用这个函数呢也就是如何取到一个类的虚表指针呢 在vs下虚表指针总是在一个类的开头32位下指针大小为4个字节所以取到前4个字节就能拿到虚表指针那如何取到对象的前4个字节呢 ①先取对象p的地址强转成一个int*的指针 ②再解引用就得到了前4个字节 ③将这4个字节强转成VFPTR*因为函数需要传入一个VFPTR*。 看到运行结果在子类的虚表中除了继承的父类的虚函数还有自己的虚函数。 typedef void(*VFPTR)();
void PrintVFTable(VFPTR* table)
{for (size_t i 0; table[i] ! nullptr; i){printf(vft[%d]:%p-, i, table[i]);VFPTR pf table[i];pf();}cout endl;
}
运行 多继承的虚表 多继承的情况与单继承差不多就是派生类多了一个或多个基类之后派生类对象的虚函数地址是放在哪一个基类的虚表中看下面的例子 我们发现派生类的虚函数重写了所有父类的虚函数则所有父类的虚函数都会被覆盖但是在监视窗口中并没有发现派生类的虚函数地址放在了哪个基类的虚表中运行打印类的虚函数的代码试试看 观察上图发现在多继承中派生类的虚函数会放在第一个继承基类的虚表中。结论很号看出但是C类中的B类的前四个字节如何取出的呢 方法一在C类中先继承的基类肯定先放在前面所以先取地址c强转成char类型指针因为后面要加上A大小的字节所以不能直接强转成int类型的指针再跨过A大小个字节强转为int指针类型然后再强转成VFPTR*防止类型不匹配。 方法二利用切片取到C中B的部分再按照之前的方法取到前4个字节相比之下方法二更容易理解。 面试常见问题
1.inline函数可以是虚函数吗 内联函数不可以是虚函数或者说内联函数与虚函数本质上是相斥的因为内联函数直接在代码中展开无函数地址而作为虚函数函数地址将会存进虚表中。但作为虚函数时再加上inline关键字编译也不会报错因为inline对编译器是一种建议编译器会自动忽略inline。 2.静态成员函数可以是虚函数吗 不可以因为静态成员函数没有this指针虚函数地址存进虚表中需要this指针取到虚函数地址进而调用虚函数。 3.构造函数、拷贝构造函数、operator可以是虚函数吗 构造函数不可以是虚函数因为对象中的虚表指针是在构造函数中初始化的可用监视窗口验证调用构造函数时还找不到虚表来存储构造函数的地址 拷贝构造函数也是如此 而operator函数语法上可以是虚函数不会报错但无实际价值。因为将函数定义成虚函数是为了构成多态而基类的派生类operator函数的返回值始终不同所以始终构成不了多态所以无实际作用。 4. 对象访问普通函数快还是访问虚函数更快 若定义成了虚函数的同时也构成了多态那么普通函数快若没构成多态则一样快。 5.虚函数表是在什么阶段生成的存在哪里 虚表在编译阶段就生成了在一般情况下虚表是存在代码段的常量区中。 后记 通过以上的学习想必大家也是知道我所说的易混淆之处在哪了比如虚表与虚基表、重写与隐藏等等这些都是要重点关注的知识点也是面试当中经常会问到的点除此之外还有多态的原理比较难以理解需要多花些时间去理解。除了这些基本的知识点之外最最重要的最后的常见问题都是面试当中面试官的“杀手锏”不专门去了解的话还是很难回答的建议在面试之前看一遍。以上就是多态相关的重点内容介绍了好好学拜拜