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

设计的网站有哪些网站版面在线设计

设计的网站有哪些,网站版面在线设计,app渠道推广,锦州建设银行网站文章目录1、多态的概念2、多态的定义及实现2-1、多态的构成条件2-2、虚函数2-3、虚函数的重写2-4 多态样例2-5、协变2-6、 析构函数与virtual2-7、函数重载、函数隐藏#xff08;重定义#xff09;与虚函数重写#xff08;覆盖#xff09;的对比2-8、override 和 final重定义与虚函数重写覆盖的对比2-8、override 和 finalC11提供3、抽象类3-1、概念3-2、接口继承和实现继承4、多态的原理4-1、虚函数表虚表4-2、多态的原理4-3、动态绑定与静态绑定5、单继承和多继承关系的虚函数表5-1、单继承中的虚函数表5-2、多继承中的虚函数表5-3、菱形继承、菱形虚拟继承6、总结重点1、多态的概念 多态的概念 通俗来说就是多种形态具体点就是去完成某个行为当不同的对象去完成时会产生出不同的状态 举例 比如买票这个行为当普通人买票时是全价买票学生买票时是半价买票军人买票时是优先买票。 再举个例子 最近为了争夺在线支付市场支付宝年底经常会做诱人的扫红包-支付-给奖励金的活动。那么大家想想为什么有人扫的红包又大又新鲜8块、10块…而有人扫的红包都是1毛5毛…。其实这背后也是一个多态行为。支付宝首先会分析你的账户数据比如你是新用户、比如你没有经常支付宝支付等等那么你需要被鼓励使用支付宝那么就你扫码金额 random()%99比如你经常使用支付宝支付或者支付宝账户中常年没钱那么就不需要太鼓励你去使用支付宝那么就你扫码金额 random()%1总结一下同样是扫码动作不同的用户扫得到的不一样的红包这也是一种多态行为 注以上支付宝红包问题等例子纯属瞎编大家仅供娱 2、多态的定义及实现 2-1、多态的构成条件 多态是在不同继承关系的类对象去调用同一函数产生了不同的行为。比如Student继承了Person。Person对象买票全价Student对象买票半价。 那么在继承中要构成多态还有两个条件 必须通过基类的指针或者引用调用虚函数被调用的函数必须是虚函数且派生类必须对基类的虚函数进行重写(下面都会讲到先记住这个结论) 2-2、虚函数 虚函数即被virtual修饰的类成员函数称为虚函数。 这里的虚函数和前面的虚拟继承没有任何关系只不过是同时用了virtual关键字。虚拟继承是为了解决数据冗余和二义性的 class Person { public:virtual void BuyTicket() //这里的函数就是虚函数{ cout 买票-全价 endl;} };2-3、虚函数的重写 虚函数的重写(覆盖)派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)称子类的虚函数重写了基类的虚函数。 虚函数的重写又叫做虚函数的覆盖 函数的隐藏又叫做函数的重定义基类与派生类的成员函数名称相同那么基类的成员函数在派生类中被隐藏 简单理解为重写覆盖比隐藏重定义更加复杂——关键字virtual和三个相同 三个相同 1、函数名相同 2、函数参数的类型和数量相同 2、函数返回值类型相同 2-4 多态样例 class Person {public:virtual void BuyTicket() { cout 买票-全价 endl; } };class Student : public Person {public:virtual void BuyTicket() { cout 买票-半价 endl; } };class Soldier : public Person { public:virtual void BuyTicket() { cout 优先-买票 endl;} };//多态条件 //1、虚函数重写 //2、由父类/基类指针或者引用调用虚函数void Func(Person p){//父类的引用调用虚函数p.BuyTicket(); }int main() {Person ps;Student st;Soldier sd;Func(ps);Func(st);Func(sd);return 0; }//void Func(Person* p) { // p-BuyTicket(); //} // //int main() //{ // Person* ps new Person; // Student* st new Student; // Soldier* sd new Soldier; // // Func(ps); // Func(st); // Func(sd); // return 0; //}上面就是多态的调用。 传入参数的类型是派生类类型而函数参数是基类类型的引用。即便函数参数类型是父类的引用/指针但是本质上参数引用/指针引用的对象/指向的对象是派生类中包含父类成员的那一部分切片/切割 基类指针/引用调用虚函数指针/引用的内容是子类中父类的那一部分 得出结论 1、普通调用跟对象调用类型有关也就是将子类中包含父类的成员切割拷贝给父类 2、多态调用跟基类指针/引用指向的对象/引用的对象的类型有关子类包含的父类成员不用给父类直接由父类的指针/引用指向 不满足多态条件的情况 1、不是由父类/基类的指针/引用对虚函数的调用 这里就是把子类中包含父类的成员切割/切片给给父类了 2、没有形成虚函数重写 情况一父类和子类都不是虚函数 情况二父类不是虚函数子类是虚函数 情况三父类是虚函数子类不是虚函数 这里的情况三居然能够调用正确这是为什么呢 我们可以理解为派生类继承了父类的虚函数所以派生类中继承下来的函数就是具有虚函数属性的。然后又对函数体重写这样就构成了虚函数重写了 虚函数重写的本质就是对函数体进行重写函数体也就是函数的实现 因此子类的虚函数可以不加virtual关键字但是父类必须加上virtual关键字这里推荐都加上virtual那么出了以上的情况之外还有什么特殊情况也是构成多态的呢 特例——协变 2-5、协变 上面的三个相同条件中返回值可以不同但是返回值必须是一个父子类关系的指针/引用 甚至下面情况也可以 那么到现在为止除了上面两个特殊情况以外再不满足多态的条件就构成不了多态了 2-6、 析构函数与virtual 对于普普通调用析构函数而言 class Person { public://virtual ~Person()~Person(){cout Person delete: _p endl;delete[] _p;} protected:int* _p new int[10]; }; class Student : public Person { public:~Student()//一般情况下子类析构结束会自动调用父类的析构{cout Student delete: _s endl;delete[] _s;} protected:int* _s new int[20]; }; int main() {Person p;Student s;return 0; }确实上面调用的确析构函数是不是虚函数无所谓都是调用正确的 但是如果调用变成下面情况了呢 int main() {//Person p;//Student s;Person* ptr1 new Person;Person* ptr2 new Student;delete ptr1;delete ptr2;return 0; }delete的具体行为 1、使用指针调用析构函数 2、operator delete指针 调用方式 1、普通调用跟对象调用类型有关 2、多态调用跟基类指针/引用指向的对象/引用的对象的类型有关 所以原因就是 那么如果是多态调用是不是就避免内存泄漏了呢 我们在析构函数前面加上virtual关键字变成虚函数 析构函数没有参数和返回值最后只剩下一个函数名了这里的析构函数名我们看着好像不相同其实是相同的 析构函数的函数名会被自动处理成为destructor 最终通过多态调用我们避免了内存泄漏也了解了为什么析构函数的函数名都被转换成了destructor不这样做就发生了内存泄漏了 所以析构函数无脑加上virtual关键字就行了 注意: 构造函数不能无脑加virtual因为虚函数的运行是建立在对象之上的我们把构造函数变成虚函数在执行构造函数时对象都没有生成。所以构造函数不能加上virtual关键字 2-7、函数重载、函数隐藏重定义与虚函数重写覆盖的对比 2-8、override 和 finalC11提供 从上面可以看出C对函数重写的要求比较严格但是有些情况下由于疏忽可能会导致函数名字母次序写反而无法构成重载而这种错误在编译期间是不会报出的只有在程序运行时没有得到预期结果才来debug会得不偿失因此C11提供了override和final两个关键字可以帮助用户检测是否重写 1、 final修饰虚函数表示该虚函数不能再被重写 class A final//c11之后的方法——final关键字 { //private://c11之前的方法——构造函数私有 // A() // {} public:A(){} };class B : public A { };int main() {B bb;B* ptr new B;return 0; }两种方法使基类不能被继承 1、构造函数私有 2、定义类后面加上final关键字 2.、override: 检查派生类虚函数是否重写了基类某个虚函数如果没有重写编译报错 class Car{public:virtual void Drive(){} };class Benz :public Car {public:virtual void Drive() override {cout Benz-舒适 endl;} };3、抽象类 3-1、概念 在虚函数的后面写上 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(); } int main() {Test();return 0; }异常现象 当不需要基类生成对象的时候可以把基类写成抽象类 3-2、接口继承和实现继承 普通函数的继承是一种实现继承派生类继承了基类函数可以使用函数继承的是函数的实现。 虚函数的继承是一种接口继承派生类继承的是基类虚函数的接口目的是为了重写达成多态继承的是接口。 所以如果不实现多态不要把函数定义成虚函数 例题1 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 : 以上都不正确 p的类型是B*当我们执行p-test时会使用继承下来的test函数但是test函数来自于A而对于指针/引用来说都不会发生强转所以B原封不动的继承了A的test函数但是B继承的test函数中的this指针仍然是A*。也就是说func函数的调用this-func中的this是A*。这个时候A是父类的指针func函数构成了虚函数重写所以A-func()就是一个多态调用先打印B-,这个时候重点又来了虚函数重写重写的只是函数体函数接口没有被重写所以A*-func用着A中的函数接口调用B中的函数体也就是函数实现 例题2 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 p3 Bp1 p2 p3 Cp1 p3 ! p2 Dp1 ! p2 ! p3 这里的p3和p1虽然相同但是意义不一样。p3是整体而p1是局部 4、多态的原理 4-1、虚函数表虚表 // 这里常考一道笔试题sizeof(Base)是多少 class Base { public:virtual void Func1(){cout Func1() endl;} private:int _b 1; }; int main() {cout sizeof(Base) endl;return 0; }我们前面学过如果知道类的成员变量采用内存对齐就能够算出类的大小但是这里真的和我们想的一样吗 为什么是8呢 通过观察测试我们发现b对象是8bytes除了_b成员还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面这个跟平台有关)对象中的这个指针我们叫做虚函数表指针(v代表virtualf代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针因为虚函数的地址要被放到虚函数表中虚函数表也简称虚表。那么派生类中这个表放了些什么呢我们接着往下分析 // 针对上面的代码我们做出以下改造 // 1.我们增加一个派生类Derive去继承Base // 2.Derive中重写Func1 // 3.Base再增加一个虚函数Func2和一个普通函数Func3class 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; }可以看到子类继承父类的虚函数之后发生了虚函数重写那么子类的虚表继承下来的虚函数地址就不是原来继承下来的地址而是由重写的虚函数进行覆盖变成了一个新的虚函数 通过观察和测试我们发现了以下几点问题 派生类对象d中也有一个虚表指针d对象由两部分构成一部分是父类继承下来的成员虚表指针也就是存在部分的另一部分是自己的成员。基类b对象和派生类d对象虚表是不一样的这里我们发现Func1完成了重写所以d的虚表 中存的是重写的Derive::Func1所以虚函数的重写也叫作覆盖覆盖就是指虚表中虚函数 的覆盖。重写是语法的叫法覆盖是原理层的叫法。另外Func2继承下来后是虚函数所以放进了虚表Func3也继承下来了但是不是虚函 数所以不会放进虚表。虚函数表本质是一个存虚函数指针的指针数组一般情况这个数组最后面放了一个nullptr。总结一下派生类的虚表生成a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生 类重写了基类中某个虚函数用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。这里还有一个童鞋们很容易混淆的问题虚函数存在哪的虚表存在哪的 答虚函数存在 虚表虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意 虚表存的是虚函数指针不是虚函数虚函数和普通函数一样的都是存在代码段的只是 他的指针又存到了虚表中。另外对象中存的不是虚表存的是虚表指针。那么虚表存在哪的呢实际我们去验证一下会发现vs下是存在代码段的Linux g下大家自己去验证 所以__vfptr是一个指针全名为虚函数表指针虚表指针指向虚函数表虚表虚函数表里面存放的就是我们的虚函数地址 我们来看看虚表是不是存放在代码段的 class Base { public:virtual void Func1(){cout Base::Func1() endl;} private:int _b 1;char _ch; }; class Derive : public Base { public:virtual void Func1(){cout Derive::Func1() endl;}void Func2(){cout Derive::Func2() endl;} private:int _d 2; }; int main() {int a 0;cout 栈: a endl;int* p1 new int;cout 堆: p1 endl;const char* str hello world;cout 代码段/常量区: (void*)str endl;static int b 0;cout 静态区/数据段: b endl;Base be;cout 虚表: (void*)*((int*)be) endl;//拿到前面4个字节地址Base* ptr1 be;int* ptr2 (int*)ptr1;printf(虚表:%p\n, *ptr2);Derive de;cout 虚表: (void*)*((int*)de) endl;Base b1;Base b2;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;char _ch; }; class Derive : public Base { public:virtual void Func1(){cout Derive::Func1() endl;}void Func3(){cout Derive::Func3() endl;} private:int _d 2; }; int main() {cout sizeof(Base) endl;Base b;Derive d;// 普通调用 -- 编译时/静态 绑定Base* ptr b;ptr-Func3();ptr d;ptr-Func3();// 多态调用 -- 运行时/动态 绑定ptr b;ptr-Func1();ptr d;ptr-Func1();return 0; } 普通调用在编译的时候通过类型就能够锁定函数是谁直接call该函数地址进行调用 多态调用确定不了因为多态调用不确定函数是调用父类的还是子类的虽然看到的都是一个父类的对象但是存在两种情况 1、父类对象2、子类中父类的那一部分 无论是什么情况多态调用都是通过指向的对象的内部取虚表指针再到虚表里面找到对应的函数进行调用指向谁调用谁 简单来说就是编译器将子类虚表中重写的虚函数地址覆盖完成之后再一次性把所有虚表放到代码段/常量区当中 同一个类的对象共享同一个虚表 4-2、多态的原理 上面分析了这个半天了那么多态的原理到底是什么还记得这里Func函数传Person调用的Person::BuyTicket传Student调用的是Student::BuyTicket 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; }观察下图的红色箭头我们看到p是指向mike对象时p-BuyTicket在mike的虚表中找到虚 函数是Person::BuyTicket。观察下图的蓝色箭头我们看到p是指向johnson对象时p-BuyTicket在johson的虚表中 找到虚函数是Student::BuyTicket。这样就实现出了不同对象去完成同一行为时展现出不同的形态。反过来思考我们要达到多态有两个条件一个是虚函数覆盖一个是对象的指针或引用调 用虚函数。反思一下为什么再通过下面的汇编代码分析看出满足多态以后的函数调用不是在编译时确定的是运行 起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的 void Func(Person* p) {p-BuyTicket(); }int main() {Person mike;Func(mike);mike.BuyTicket(); return 0; }// 以下汇编代码中跟你这个问题不相关的都被去掉了 void Func(Person* p) { ...p-BuyTicket();// p中存的是mike对象的指针将p移动到eax中001940DE mov eax,dword ptr [p]// [eax]就是取eax值指向的内容这里相当于把mike对象头4个字节(虚表指针)移动到了edx001940E1 mov edx,dword ptr [eax]// [edx]就是取edx值指向的内容这里相当于把虚表中的头4字节存的虚函数指针移动到了eax00B823EE mov eax,dword ptr [edx]// call eax中存虚函数的指针。这里可以看出满足多态的调用不是在编译时确定的是运行起来以后到对象的中取找的。001940EA call eax 00头1940EC cmp esi,esp }int main() { ... // 首先BuyTicket虽然是虚函数但是mike是对象不满足多态的条件所以这里是普通函数的调用转换成地址时是在编译时已经从符号表确认了函数的地址直接call 地址mike.BuyTicket();00195182 lea ecx,[mike]00195185 call Person::BuyTicket (01914F6h) ... }4-3、动态绑定与静态绑定 静态绑定又称为前期绑定(早绑定)在程序编译期间确定了程序的行为也称为静态多态 比如函数重载动态绑定又称后期绑定(晚绑定)是在程序运行期间根据具体拿到的类型确定程序的具体 行为调用具体的函数也称为动态多态。 5、单继承和多继承关系的虚函数表 需要注意的是在单继承和多继承关系中下面我们去关注的是派生类对象的虚表模型因为基类的虚表模型前面我们已经看过了没什么需要特别研究的 5-1、单继承中的虚函数表 class Base {public:virtual void func1() { cout Base::func1 endl; }virtual void func2() { cout Base::func2 endl; }private:int a; };class Derive :public Base {public:virtual void func1() { cout Derive::func1 endl; }virtual void func3() { cout Derive::func3 endl; }void func4() { cout Derive::func4 endl; }private:int b; };int main() {Base b;Derive d;return 0; }我们可以看到子类的func1函数进行了重写func2函数没有进行重写但是子类的func3函数也是虚函数啊怎么子类虚表里面没有func3函数的地址呢 这是因为vs的监视窗口对原代码进行了处理我们监视窗口看到的并不是原生的内容 我们通过内存窗口来看看 linux下虚表不是以空结束 我们直接来打印出这两个虚表指针 typedef void(*func_t)(); void Print(func_t f[]) {for (int i 0; f[i] ! nullptr; i){printf([%d]:%p\n, i, f[i]);f[i]();}cout endl; } int main() {Base b;//Print((func_t*)(*(int*)b));//int*在x86下面才是4个字节x64下面是8个字节//Print((func_t*)(*(long long*)b));Print((func_t*)(*(void**)b));//指针都是4个字节大小void*不能解引用void**可以*void**一样是看4个字节Print((func_t*)(*(long long**)b));//*long long**一样是看4个字节Derive d;//Print((func_t*)(*(int*)d));//Print((func_t*)(*(long long*)d));Print((func_t*)(*(void**)b));Print((func_t*)(*(long long**)b));return 0; }所有的虚函数都是会进虚表的 单继承中派生类自己的虚函数在虚表中继承下来的基类虚函数的后面 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; };typedef void(*func_t)(); void Print(func_t f[]) {for (int i 0; f[i] ! nullptr; i){printf([%d]:%p\n, i, f[i]);f[i]();}cout endl; } int main() {Base1 b1;Base2 b2;Print((func_t*)(*(void**)b1));Print((func_t*)(*(void**)b2));Derive d;return 0; }也就是放到第一个继承的类里面 接下来打印虚表地址 int main() {Base1 b1;Base2 b2;//Print((func_t*)(*(void**)b1));//Print((func_t*)(*(void**)b2));Derive d;Print((func_t*)(*(void**)d));//第一张虚表Print((func_t*)(*(void**)((char*)d sizeof(Base1))));//char*不能少不然一次加Derive大小Base2* p d;Print((func_t*)(*(void**)p));//自动偏移找到子类中Base2类的一部分return 0; }所以多继承中如果子类有未重写的虚函数会放在第一个继承的父类的虚表中 5-3、菱形继承、菱形虚拟继承 这个就不多讲了吧实际中菱形继承本来用的就是更何况是菱形虚拟继承 C虚函数表解析 C对象的内存布局 想深入了解可以观看这两篇文章 6、总结重点 什么是多态 多态分为静态多态和动态多态。 静态多态是在编译时绑定比如说函数重载根据函数名修饰规则等等可以直接确定调用函数 动态多态是运行时绑定通过虚函数重写之后父类的指针/引用来调用重写的虚函数指向父类调用父类指向子类调用子类与指针/引用的类型无关与指向的对象类型有关。通过虚表指针找到代码段/常量区对应的虚表中的函数地址拿到虚函数之后调用 什么是重载、重写(覆盖)、重定义(隐藏) 函数重载两个函数在同一个作用域内并且函数名参数参数个数参数类型参数类型的顺序相同 虚函数重写覆盖两个函数分别在基类父类和派生类子类的作用域中都是虚函数有virtual关键字修饰并且两个函数的函数名、参数、返回值都相同协变除外 函数的重定义隐藏两个函数分别在基类父类和派生类子类的作用域中函数名相同就构成函数的重定义。并且在基类和子类的两个同名函数不构成重写就构成重定义 多态的实现原理 一个接口多种方法 用virtual关键字申明的函数叫做虚函数虚函数肯定是类的成员函数。存在虚函数的类都有一个一维的虚函数表简称为虚表。当类中声明虚函数时编译器会在类中生成一个虚函数表。类的对象有一个指向虚表开始的虚指针。虚表是和类对应的虚表指针是和对象对应的。虚函数表是一个存储类成员函数指针的数据结构——函数指针数组结构。虚函数表是由编译器自动生成与维护的这也说明了虚函数的重写/覆盖是编译器帮助我们完成的。virtual成员函数会被编译器放入虚函数表中。当存在虚函数时每个对象中都有一个指向虚函数的指针C编译器给父类对象子类对象提前布局vptr指针当进行test(parent *base)函数的时候C编译器不需要区分子类或者父类对象只需要再base指针中找到vptr指针即可。vptr一般作为类对象的第一个成员。 注vs的监视窗口vptr指针指向的虚表一般只存放两个虚函数地址分别是是vptr[0]和vptr[1]这是编译器自主处理的结果我们要通过内存窗口观察 inline函数可以是虚函数吗 对于多态调用而言 1、语法层面理论上理论上来说是不可以的inline是函数在类中展开将代码保存在了类里面但是这就与虚函数相违背了一个是编译时就将函数展开一个是在运行时通过虚表指针找到虚表拿到里面虚函数的地址最后再调用虚函数两种情况不可能同时存在。 2、实际操作实际操作我们会发生编译器vs系列只是发生警告并不会报错并且还能够正确编译。因为编译器在遇到inline和virtual两个关键字的时候自动的忽略了inline属性使得inline失效 但对于普通调用而言 是可以的普通调用会继续保存inline属性因为普通调用没有虚表指针虚表这些东西 静态成员可以是虚函数吗 不能因为静态成员函数没有this指针使用类型::成员函数 的调用方式无法访问虚函数表所以静态成员函数的地址无法放进虚函数表。 静态成员函数不属于类中的任何一个对象和实例属于类共有的一个函数。也就是说它不能用this指针来访问因为this指针指向的是每一个对象和实例 对于virtual虚函数它的调用恰恰使用this指针。在有虚函数的类实例中this指针调用vptr指针指向的是vtable(虚函数列表)通过虚函数列表找到需要调用的虚函数的地址。总体来说虚函数的调用关系是this指针-vptr(4字节-vtable -virtual虚函数。 所以说static静态函数没有this指针也就无法找到虚函数了。所以静态成员函数不能是虚函数。他们的关键区别就是this指针。 构造函数可以是虚函数吗 不能因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。我们把构造函数变成虚函数放到虚表之后虚表指针无法得到初始化这样虚表指针和虚表就断开连接了 析构函数可以是虚函数吗什么场景下析构函数是虚函数 可以并且最好把基类的析构函数定义成虚函数。 场景Person* ptr2 new Student;这种父类指针指向子类的时候就需要将析构函数定义为虚函数 对象访问普通函数快还是虚函数更快 首先如果是普通对象是一样快的。如果是指针对象或者是引用对象则调用的普通函数快因为构成多态运行时调用虚函数需要到虚函 数表中去查找。 虚函数表是在什么阶段生成的存在哪的 虚函数表是在编译阶段就生成的一般情况 下存在代码段(常量区)的。虚表指针的初始化是在构造函数初始化列表阶段完成初始化的 C菱形继承的问题虚继承的原理 1、数据冗余虚基类的成员在派生类中会保存两份这样多保存就产生了数据冗余 2、二义性虚基类的成员在派生类中会保存两份在调用的时候如果不指明基类就会产生二义性从而报错 注意这里不要把虚函数表和虚基表搞混了 虚函数表里面存放的是虚函数的地址用来构建多态 虚基表存放着类的偏移量为了防止数据冗余和二义性 什么是抽象类抽象类的作用 抽象类在虚函数的后面写上 0 则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类也叫接口类 抽象类作用抽象类不能实例化出对象。派生类继承后也不能实例化出对象只有重写纯虚函数派生类才能实例化出对象。纯虚函数规范了派生类必须重写另外纯虚函数更体现出了接口继承。 抽象类强制重写了虚函数另外抽 象类体现出了接口继承关系
http://www.w-s-a.com/news/302504/

相关文章:

  • 网站建设网络推广代理公司wordpress图片防盗链
  • 网站备案关站沈阳男科医院哪家好点
  • 王者荣耀网站建设的步骤网站页面用什么软件做
  • 典型网站开发的流程房屋装修效果图三室一厅
  • 制作微网站多少钱阿里巴巴做网站的电话号码
  • 风铃建站模板安卓手机软件开发外包
  • 深圳市住房和建设局门户网站域名转移影响网站访问吗
  • 做母婴网站赚钱汕头百姓网
  • 那个网站建设好动漫制作技术升本可以升什么专业
  • 网站建设企业响应式网站模板广西建设部投诉网站
  • app营销的特点wordpress优化方案
  • 静安网站建设公司如何编辑wordpress
  • 做网站的职位叫什么问题常州金坛网站建设
  • 保健品网站模板用jsp做的网站前后端交互
  • 网站带后台品牌网页设计图片
  • 保定清苑住房和城乡建设局网站分类信息网站程序
  • 可以做视频推广的网站选择大连网站建设
  • 在线网站开发网站在哪里
  • 建站的步骤上海快速优化排名
  • 招聘网站做一下要多少钱网站设计公司 国际
  • 巩义专业网站建设公司首选seo研究院
  • 大流量网站解决访问量友情链接如何添加
  • 教育网站建设网永康市住房和城乡建设局网站
  • 阿里巴巴官网网站django 做网站的代码
  • 网站建设 军报wordpress 订餐模板
  • 网站虚拟主机 会计处理石家庄站建设费用多少
  • 网站建设 服务内容 费用简述网站开发流程
  • 公司制作网站跟企业文化的关系空间制作网站
  • 浙江建设监理协会网站个人网站设计规划书
  • wordpress太卡了贵州seo推广