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

做个简单的导航网站网站页面设计欣赏

做个简单的导航网站,网站页面设计欣赏,河南郑州最新消息,湖南网站建设oqiandu文章目录#xff1a; C 多态1. 多态的概念2. 多态的定义和实现2.1 构成多态的必要条件2.2 虚函数和重写2.3 虚函数重写的两个例外2.4 例题运用2.5 final 和 override2.6 重载、重写、重定义 3. 抽象类4. 多态的原理4.1 虚表指针4.2 多态和非多态调用4.3 回想满足条件4.4 虚函数… 文章目录 C 多态1. 多态的概念2. 多态的定义和实现2.1 构成多态的必要条件2.2 虚函数和重写2.3 虚函数重写的两个例外2.4 例题运用2.5 final 和 override2.6 重载、重写、重定义 3. 抽象类4. 多态的原理4.1 虚表指针4.2 多态和非多态调用4.3 回想满足条件4.4 虚函数表4.5 知识补充4.6 多继承中的虚函数表4.7 动态绑定与静态绑定4.8 多继承例题 C 多态 前面介绍了封装和继承本文就来介绍面向对象三大特征的最后一个—多态多态就是面对不同对象时所展现的不同状态下面就一起来看看吧 1. 多态的概念 多态通俗来说就是多种形态具体点就是去完成某个行为当不同的对象去完成时会产生出不同的状态。 举个例子比如买票这个行为当普通人买票时是全价买票学生买票时是半价买票军人买票时是优先买票 多态就是指不同继承关系的类对象去调用同一函数产生了不同的行为 2. 多态的定义和实现 实现多态需要借助虚函数表(虚表)而构成虚表又需要虚函数最后再使用虚表指针来进行函数定位和调用 2.1 构成多态的必要条件 必须通过基类的指针或者引用调用虚函数被调用的函数必须是虚函数且派生类必须对基类的虚函数进行重写 2.2 虚函数和重写 被virtual修饰的类成员函数称为虚函数 举个例子 class A { public:virtual void fun() { cout A::fun() endl; } };上面的func() 就是虚函数下面再来见见多态吧 #include iostream using namespace std;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 ps;Student st;Func(ps);Func(st);return 0; }是否满足多态满足继承前提Func()函数中通过基类的引用p调用函数被调用的函数BuyTicket()是虚函数且派生类对基类的虚函数BuyTicket()进行了重写 上面说到了重写那到底什么是虚函数的重写呢 虚函数的重写也叫覆盖派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)称子类的虚函数重写了基类的虚函数。虚函数的作用是在目标函数想要构成多态的函数之间构成 重写一旦构成了 重写那么子类对象在实现此虚函数时会继承父类中的虚函数接口返回值、函数名、参数列表然后覆盖至子类对应的虚函数处因此重写又叫做覆盖 下面我们去掉派生类中的virtual关键字看看运行结果 这里为什么还是构成重写呢 这是因为在重写基类虚函数后派生类的虚函数在不加virtual关键字时也可以构成重写这里叫做接口继承因为继承后基类的虚函数被继承下来了继承了参数也就是拷贝了一份基类的虚函数的参数在派生类依旧保持虚函数属性但是该种写法不是很规范不建议这样使用。 这里如果只是把基类中的virtual修饰关键字去掉能否构成虚函数重写呢 显然是不能的这是因为此时已经不满足派生类对基类的虚函数进行重写了也就是不满足多态了这里p.BuyTicket()中的p对象是Person类型的所以两次调用都是调用的基类的BuyTicket()函数 最后再来看看父子类中的函数都不加virtual的情况 结果是必然的因为此时已经不满足多态条件中的任何一条了。但是观察得到这里构成了继承中的隐藏但是这里是基类对象去调用的基类成员函数这里虽然构成隐藏但是这里没有用子类对象去调用子类的成员函数BuyTicket()也就不体现隐藏关系只有子类调用才会体现隐藏关系 讲个经验 满足多态看调用对象不满足多态看调用对象类型 2.3 虚函数重写的两个例外 例外一析构函数重写(虚函数名不同) #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的指针调用虚函数同时也Person()构成了虚函数并且子类的虚函数Studen()对父类进行了重写但是这里的虚函数重写违反了派生类虚函数与基类虚函数的三同(返回值类型、函数名字、参数列表完全相同)中的函数名相同但是为什么还成立呢 这是编译器会对析构函数的名称做特殊处理编译后析构函数的名称统一处理成destructor这就可能存在析构错误调用的问题因此可以利用 virtual 修饰父类的析构函数这样子类在继承时会自动形成多态确保不同对象的析构函数能被成功调用避免内存泄漏 这里如果把基类的析构函数中的virtual关键字去掉呢 这里发现程序直接崩溃了 这是因为这里没有构成多态导致看对象类型调用函数又因为p1和p2都是Person类型所以都是调用的~Person()函数析构了两次导致程序崩溃 例外二协变(返回值类型不同) 派生类重写基类虚函数时与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用派生类虚函数返回派生类对象的指针或者引用时称为协变。不常用了解即可 #include iostream using namespace std;class A {}; class B : public A {};class Person { public:virtual A* f() {cout Person() - f() endl;return new A;} };class Student : public Person { public:virtual B* f() {cout Student() - f() endl;return new B;} };void fun(Person p) {p.f(); }int main() {Person p;fun(p);Student s;fun(s);return 0; }2.4 例题运用 下面来看一个题目 #include iostream using namespace std;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-0B: B-1C: A-1D: B-0E: 编译出错F: 以上都不正确 解析 题目改编一 #include iostream using namespace std; 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[]) {A* p new B; //B* -- A* 对象赋值转换p-test(); return 0; }最终结果和原题一样输出 B-1 题目改编二 #include iostream using namespace std; class A { public:virtual void func(int val 1) { std::cout A- val std::endl; } }; class B : public A { public:void func(int val 0) { std::cout B- val std::endl; }virtual void test() { func(); } }; int main(int argc, char* argv[]) {B* p new B;p-test();return 0; }解析不满足多态 2.5 final 和 override final和override是C11中新增的两个多态相关的关键字 override关键字 修饰子类的虚函数检查派生类虚函数是否重写了基类某个虚函数如果没有重写编译报错 可见因为参数列表不同无法完成重写override关键字可以在编译之前就将其检查出来以保证完成实现多态 final关键字 修饰父类的虚函数表示该虚函数不能再被子类的虚函数重写 可见父类的虚函数加上了final关键字后无法被子类的虚函数重写 2.6 重载、重写、重定义 三者总结 重载就是函数重载函数名相同参数不同构成重载不同的函数参数最终修饰结果不同确保链接时不会出错 重写覆盖父子类中当出现虚函数且符合重写的三同原则时就会发生重写覆盖行为 重定义隐藏父子类中当子类中的函数名与父类中的函数名相同时会隐藏父类同名函数默认调用子类的函数可以通过 :: 指定调用 3. 抽象类 在虚函数的后面写上 0 则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类也叫接口类 纯虚函数也可以与普通虚函数构成重写也能实现多态但是抽象类不能实例化出对象。派生类继承后也不能实例化出对象只有重写纯虚函数派生类才能实例化出对象。纯虚函数规范了派生类必须重写另外纯虚函数更体现出了接口继承。 #include iostream using namespace std;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;} };int main() {Car* pBenz new Benz;pBenz-Drive();Car* pBMW new BMW;pBMW-Drive();return 0; }抽象类适合用于描述无法拥有实体的类比如动物、植物等这些都是不能直接使用的需要经过继承赋予特殊属性后才能作为一个对象 4. 多态的原理 4.1 虚表指针 先来看一道笔试题这里问sizeof(Base)是多少 #include iostream using namespace std;class Base { public:virtual void Func1(){cout Func1() endl;} private:int _a 0;int _b 1;char _c; };int main() {Base B;cout sizeof(B) endl;return 0; }在32位平台下如果按照内存对齐的规则这里应该是12字节大小但真是如此吗 这多出来的4个字节是哪里来的呢调整到64位平台下大小会变成24大小能随平台变化而发生变化我们第一个想到的自然是指针了通过监视再来看一下 这里我们发现果然多了一个void**类型的指针_vfptr的指针 对象中的这个指针我们叫做虚函数表指针(v代表virtualf代表functionptr代表pointer)。一个含有虚函数的类中都至少都有一个虚函数表指针因为虚函数的地址要被放到虚函数表中虚函数表也简称虚表多态也就是依靠虚表指针虚表实现了的 多态满足条件中有个条件是必须通过基类的指针或者引用调用虚函数。那么这里怎么来实现的用引用或者指针就可以调用不同类的成员函数为什么用普通类型就不能实现调用不用类的成员函数呢 #include iostream using namespace std;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 pxx;Func(pxx);Student sxx;Func(sxx);return 0; }这就验证了_vfptr指针就是用来调用不同类的成员函数的 4.2 多态和非多态调用 先来看看非多态调用 不是多态时直接按照对象的类型来调用对应的成员函数 再来看看多态调用 上述观察到进行执行p.BuyTicket()语句时编译器是不知道调用哪个类的成员函数的当完成p.BuyTicket()语句后 编译器做出了处理工作直接跳转到Person::BuyTicket()成员函数。 证明一下在执行p.BuyTicket()语句时编译器是不知道调用哪个类的成员函数 虚函数的调用过程 存在虚函数且构成重写的前提下使用父类指针或父类引用指向对象其中包含切片行为切片后将子类中不属于父类的切掉只保留父类指针可调用到的部分函数在调用时父类指针的调用逻辑就是虚表第一个位置调用第一个函数虚表第二个位置调用第二个函数但是因为此时的虚表是切片得到的所以同一位置可以调用到不同的函数这就是多态 4.3 回想满足条件 满足虚函数重写 我们描述了虚函数表那么如果父类中没有虚函数的话就不能构成多态再来看这句话不难理解了因为没有虚函数就没有虚函数表当使用父类的引用和指针来调用对应的函数时就不会在虚函数表中查找而是直接依据类型来进行调用 满足父类的指针或引用 为什么指针或者引用可以调用相对应的成员函数但是对象不行呢对象也能够完成切片啊原因还是在虚函数表上对象的话就要拷贝这里也就要拷贝虚函数表如果拷贝就会有很大的问题就比如子类拷贝给了父类此时就分不清了父类的虚函数表也是子类的了 4.4 虚函数表 有虚函数的类的对象中都有虚表指针_vfptr虚表指针所指向的就是对应类的虚函数表(虚表)即virtual function table - vft虚表中存储的是虚函数指针可以在调用函数时根据不同的地址调用不同的方法 下面来看一段代码 #include iostream using namespace std;class Person { public:virtual void func1() { cout Person::fun1() endl; };virtual void func2() { cout Person::fun2() endl; };void func3() { cout Person::fun3() endl; }; }; class Student : public Person { public:virtual void func1() { cout Student::fun1() endl; };virtual void func4() { cout Student::fun4() endl; }; };int main() {Person p;Student s;return 0; }通过监视和内存来详细观察一下虚表指针虚函数地址以及虚表的关系 再来看看把虚函数重写也叫做覆盖的原理 上述我们观察到BuyTicket()成员函数进行了重写但是travel()和ClaimCoupon()成员函数并没有重写此时观察到Person类维护的有一个虚函数表Student维护的也有一个虚函数表BuyTicket()成员函数重写了也就覆盖了 可以这么理解原本的子类是继承父类的成员函数的那么当前子类的虚函数表也就是父类的虚函数表但是当子类对父类的虚函数重写后原本的父类的虚函数就被覆盖为新的重写的虚函数这个例子的体现就在_vfptr[0]位置上的BuyTicket()虚函数指针。 _vfptr[1]和 _vfptr[2]两个虚函数并没有被重写所以就是继承的父类的虚函数。 我们可以打印虚函数表来验证其真实性 虚函数表是一个函数指针数组所以打印这里的函数指针数组即可。虚函数表是以nullptr为结束标记的vs中。虚函数表指针是对象前4个或者8个字节可以先将虚表指针强转为指向首个虚函数的指针然后遍历虚表打印各个虚函数地址即可 #include iostream using namespace std; 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(*_vfptr)() -- 函数指针 -- 起别名为_vfptrvoid print_virtual_function(_vfptr table[]) //打印虚函数表 table - 数组名 - _vfptr*(类型) {for (int i 0; table[i]; i) //虚函数表是以nullptr为结束标记的{printf([%d]:%p\n, i, table[i]);}cout endl; } int main() {Base b;Derive d;//只是支持32位机器下//b - Base* - (int*)b - 强制转换为int* - (*(int*)b) - 拿到b对象的前四个字节 - (_vfptr*)(*(int*)b) - 强制转换为_vfptr*(函数指针的指针)print_virtual_function((_vfptr*)(*(int*)b));print_virtual_function((_vfptr*)(*(int*)d));//支持32和64位机器下print_virtual_function((_vfptr*)(*(long long*)b));print_virtual_function((_vfptr*)(*(long long*)d));//支持32和64位机器下//b - Base* - (_vfptr**)b - Base*强制转换为_vfptr**(函数指针的指针) - (*(_vfptr**)b) - _vfptr*print_virtual_function((*(_vfptr**)b));print_virtual_function((*(_vfptr**)d));return 0; }我们可以发现Func4()这个函数是在Derive这个子类的虚函数表中的只不过这个监视窗口进行了修饰而已其实是有的。可以对打印窗口优化 typedef void(*_vfptr)(); //void(*_vfptr)() -- 函数指针 -- 起别名为_vfptrvoid print_virtual_function(_vfptr table[]) //打印虚函数表 table - 数组名 - _vfptr*(类型) {for (int i 0; table[i]; i) //虚函数表是以nullptr为结束标记的{printf([%d]:%p-, i, table[i]);_vfptr f table[i]; //拿到指针f(); //调用类中的虚函数}cout endl; }4.5 知识补充 虚函数表补充 虚表是在编译阶段生成的因为编译就有函数的地址了对象中的虚表指针是在构造函数的初始化列表中初始化的虚表一般存储在常量区代码段比如visual studio 2019有的平台中可能存储在静态区数据段 4.6 多继承中的虚函数表 上面讲解的时单继承中的虚表总结一下就是子类中的虚函数对父类中对应的虚函数进行重写覆盖 单继承中新增虚函数 父类新增虚函数父类的虚表中会新增同时子类会继承到自己的虚表中子类新增虚函数只有子类的虚表中会新增父类看不到也无法调用 多继承也就会出现多个虚函数重写的情况那么父类是如何处理不同虚表中的相同虚函数的重写呢 #include iostream using namespace std;class Base1 { public:virtual void func1() { cout Base1::func1() endl; }virtual void func2() { cout Base1::func2() endl; } };class Base2 { public:virtual void func1() { cout Base2::func1() endl; }virtual void func2() { cout Base2::func2() endl; } };class Derive : public Base1, public Base2 { public:virtual void func1() { cout Derive::func1() endl; }virtual void func3() { cout Derive::func3() endl; } //子类新增虚函数 };int main() {Derive d;return 0; }可以看到此时子类拥有两张虚表 那么此时就出现了多继承中的两个主要问题了 子类中新增的虚函数func3位于哪个虚表中为什么重写的同一个 func1 函数在两张虚表中的地址不相同 首先来回答第一个问题子类中新增的虚函数默认添加到第一张虚表中可以通过打印虚表来验证 利用直接取地址再类型强转来打印第一张虚表在第一张虚表的起始地址处跳过第一张虚表的大小来获取第二张虚表的起始地址 typedef void(*VF_T)();typedef void(*VF_T)();void PrintVFTable(VF_T table[]) {//虚函数表是以nullptr为结束标记int i 0;while (table[i]){printf([%d]:%p-, i, table[i]);VF_T f table[i];f(); //调用函数相当于func()i;}cout endl; }int main() {Derive d;PrintVFTable(*(VF_T**)d); //打印第一张虚表PrintVFTable(*(VF_T**)((char*)d sizeof(Base1))); //跳过第一张虚表打印第二张虚表return 0; }运行代码可以看出子类中新增的虚函数func3位于第一张虚表中 也可以通过切片的方式天然的取到第二张虚表的地址 Base2* table2 d; //切片 PrintVFTable(*(VF_T**)table2); //打印第二张虚表再来看看第二个问题的原因这里会存在多继承中的虚函数冗余调用问题编译器在调用时根据不同的地址寻找到同一函数来解决这一问题 通过反汇编来看看 根据汇编可以看的出来调用p1-func1()和p2-func1()最终都是调用Derive::func1()。需要注意的是p2-func1()函数中第一次jmp跳转到sub指令这里的ecx寄存器就是存的this指针通过减少4个字节找到Base1类的 这个过程叫做this指针修正用于解决冗余虚函数的调用问题这里会修正后继承的父类 这也就得到了第二个问题的答案 两张虚表中同一个函数的地址不同是因为调用方式不同后继承类中的虚表需要通过this指针修正的方式调用虚函数根据不同的地址寻找到同一函数 感谢大佬北 海提供的好图 4.7 动态绑定与静态绑定 静态绑定 静态绑定又称为前期绑定(早绑定)在程序编译期间确定了程序的行为也称为静态多态比如函数重载 动态绑定 动态绑定又称后期绑定(晚绑定)是在程序运行期间根据具体拿到的类型确定程序的具体行为调用具体的函数也称为动态多态 静态绑定就像函数重载在编译阶段就确定了不同函数的调用而动态绑定是虚函数的调用过程需要 虚表指针虚表在程序运行时根据不同的对象调用不同的函数 4.8 多继承例题 class Base1 { public: int _b1; }; class Base2 { public: int _b2; }; class Derive : public Base1, public Base2 { public: int _d; };int main() {Derive d;Base1* p1 d;Base2* p2 d;Derive* p3 d;return 0; }关于多继承中指针偏移问题下面说法正确的是什么 Ap1 p2 p3Bp1 p2 p3Cp1 p3 ! p2Dp1 ! p2 ! p3 由多继承虚函数表的相关知识可以的出选择CBase1和Base2是Derive父类这里Base1先声明先继承Base1 C 多态到这里就介绍结束了本篇文章对你由帮助的话期待大佬们的三连你们的支持是我最大的动力 文章有写的不足或是错误的地方欢迎评论或私信指出我会在第一时间改正
http://www.zqtcl.cn/news/732299/

相关文章:

  • 清华大学网站建设方案郑州建网站企业
  • 闸北网站优化公司网站表格代码
  • 网站里面如何做下载的app深圳企业社保登录入口
  • 中国网站建设哪家公司好网站开头flash怎么做
  • 南磨房做网站公司黑马程序员就业情况
  • 电子商务网站运营方案建设银行网站查询密码设置
  • 网站服务器哪些好用php做的录入成绩的网站
  • 网站建设需要哪些信息vi设计什么意思
  • 苏州吴中区专业做网站玉树市公司网站建设
  • wordpress 不换行沈阳网站制作优化
  • 要维护公司的网站该怎么做怎么联系创意设计网站
  • 阿里云wordpress搭建网站网站如何做app
  • 做微商哪个网站比较好wordpress5.0.2运行慢
  • 中牟高端网站建设建自己的个人网站
  • 网站前台架构WordPress 分类 调用
  • 腾讯用户体验网站哈尔滨百姓网
  • 上海品质网站建设深圳自适应网站制作
  • gta5此网站正在建设更换wordpress后台登陆地址
  • 做花馍网站怎么做自己的简历网站
  • 旅游网站建设网站目的做饲料推广哪个网站好
  • 高网站排名吗网站网站集约化建设
  • 站长之家网站素材WordPress显示访客ip
  • 网上做兼职网站有哪些宁波seo关键词优化服务
  • 玉溪市网站建设推广商城做网站哪家好
  • 企业网站的管理系统人人秀h5制作软件
  • 好的做外贸的网站可口可乐广告策划书范文
  • 湖北分行建设银行网站北京平台网站建设价位
  • 重庆荣昌网站建设wordpress主题 微博
  • 邢台网站建设行情访问外国网站很慢
  • 江东外贸seo网站建设猎奇网站模板