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

广州网站建设gzqiyiwordpress 客户

广州网站建设gzqiyi,wordpress 客户,网站开发研,兰州网站建设实验总结目录 一、多态的概念 二、多态的定义及实现 2.1 多态的构成条件 2.2 虚函数 2.3 虚函数的重写 2.4 虚函数重写的两个例外 2.4.1 协变 2.4.2 析构函数的重写 2.5 C11 override 和 final 2.5.1 final 2.5.2 override 2.6 重载、覆盖(重写)、隐藏(重定义)的对比 三、…目录 一、多态的概念 二、多态的定义及实现 2.1 多态的构成条件 2.2 虚函数 2.3 虚函数的重写 2.4 虚函数重写的两个例外 2.4.1 协变 2.4.2 析构函数的重写 2.5 C11 override 和 final 2.5.1 final 2.5.2 override 2.6 重载、覆盖(重写)、隐藏(重定义)的对比 三、抽象类 3.1 抽象类概念 3.2 接口继承和实现继承 四、多态的原理 4.1虚函数表 4.2 多态原理 4.3 动态绑定与静态绑定 五、单继承和多继承关系的虚函数表 5.1 单继承中的虚函数表 5.2 多继承中的虚函数表 5.3. 菱形继承、菱形虚拟继承 六、继承和多态常见的面试问题 6.1 概念查考 6.2 问答题 一、多态的概念 通俗来说就是多种形态具体点就是去完成某个行为当不同的对象去完成时会产生出不同的状态 比如在现实生活中买票这个行为当普通人买票时是全价买票学生买票时是半价买票军人买票时是优先买票 二、多态的定义及实现 2.1 多态的构成条件 多态是在不同继承关系的类对象去调用同一函数产生了不同的行为在继承中要构成多态还有两个条件 必须通过基类的指针或者引用调用虚函数被调用的函数必须是虚函数且派生类必须对基类的虚函数进行重写2.2 虚函数 虚函数即被virtual修饰的类成员函数称为虚函数 class Person { public:virtual void BuyTicket() { cout 买票-全价 endl; } }; 注虚函数的 virtual 和虚继承中的 virtual 是同一个关键字但是它们之间没有任何关系虚函数的 virtual 是为了实现多态而虚继承的 virtual 是为了解决菱形继承的数据冗余和二义性  2.3 虚函数的重写 虚函数的重写(覆盖)派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)称子类的虚函数重写了基类的虚函数重写也叫覆盖 测试代码比如买票 //基类 -- 普通人 class Person { public://基类的虚函数virtual void BuyTicket() { cout Person-买票-全价 endl; } }; //派生类 -- 学生 class Student : public Person { public://派生类的虚函数重写了父类的虚函数virtual void BuyTicket() { cout Student-买票-半价 endl; } }; //派生类 -- 军人 class Soldier : public Person { public://派生类的虚函数重写了父类的虚函数virtual void BuyTicket(){ cout Soldier-优先-买票 endl;} }; 上面的派生类已经对基类的虚函数进行重写下面再通过基类的引用调用虚函数即可构成多态 void Func(Person p)//基类的引用 {p.BuyTicket();//基类的引用调用虚函数 }int main() {Person p;Student s;Soldier sd;Func(p);Func(s);Func(sd);return 0; } 运行结果多态已经构成BuyTicket 函数进行多态调用 下面测试使用基类的指针 基类的指针调用虚函数也可以形成多态 void Func(Person* p)//基类的指针 {p-BuyTicket();//基类的指针调用虚函数 }int main() {Person p;Student s;Soldier sd;Func(p);Func(s);Func(sd);return 0; } 运行结果多态可以构成 注意在重写基类虚函数时派生类的虚函数在不加 virtual 关键字时虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性)但是该种写法不是很规范不建议这样使用派生类的虚函数也建议加上 virtual 关键字 不满足多态的任何一个条件构造不了多态 1基类是引用或指针调用函数但是基类调用的不是虚函数无法形成多态基类一定要有 virtual 2基类不是引用或指针调用虚函数也无法形成多态 重写覆盖和隐藏重定义的区别 1隐藏的定义是基类和派生类的成员函数名字相同就构成隐藏 2虚函数重写的条件是虚函数virtual 三同 三个相同 基类和派生类的成员函数名字相同参数列参数类型、数量表完全相同函数返回值相同可以看出虚函数的重写比隐藏的条件更苛刻 2.4 虚函数重写的两个例外 2.4.1 协变 派生类重写基类虚函数时与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用派生类虚函数返回派生类对象的指针或者引用时仍可构造虚函数的重写这种行为称为协变协变简单了解即可平时基本不使用 测试代码 class Person { public:virtual Person* fun() {cout Person endl; return new Person;} }; class Student : public Person { public:virtual Student* fun() {cout Student endl;return new Student;} };int main() {Person p;Student s;//基类的引用调用虚函数Person rp p;rp.fun();Person rs s;rs.fun();cout endl;//基类的指针调用虚函数Person* ptr1 p;ptr1-fun();Person* ptr2 s;ptr2-fun();return 0; } 运行结果 注基类虚函数返回基类对象的指针或者引用派生类虚函数返回派生类对象的指针或者引用返回基类和返回派生类的对象也可以是其它的基类或派生类也可构成虚函数的重写 测试代码 //基类 class A {}; //派生类 class B : public A {}; //基类 class Person { public://返回基类A的指针virtual A* fun(){cout A* Person::f() endl;return new A;} }; //派生类 class Student : public Person { public://返回派生类B的指针virtual B* fun(){cout B* Student::f() endl;return new B;} };2.4.2 析构函数的重写 析构函数的重写基类与派生类析构函数的名字不同          如果基类的析构函数为虚函数此时派生类析构函数只要定义无论是否加virtual关键字都与基类的析构函数构成重写虽然基类与派生类析构函数名字不同。         虽然函数名不相同看起来违背了重写的规则其实不然这里可以理解为编译器对析构函数的名称做了特殊处理编译后析构函数的名称统一处理成destructor 例如下面代码中基类 Person 和派生类 Student 的析构函数构成重写 //基类 class Person { public://基类和派生类析构函数构成重写virtual ~Person() { cout ~Person() endl; } }; //派生类 class Student : public Person { public:virtual ~Student() { cout ~Student() endl; } }; 为什么要把析构函数进行重写 因为在某种场景下析构函数的重写是必要的 比如如果析构函数不构成重写在这种场景下会造成内存泄漏 //基类 class Person { public://没有加virtual基类和派生类析构函数不构成重写~Person() { cout ~Person() endl; } }; //派生类 class Student : public Person { public:~Student() { cout ~Student() endl; } };int main() {//分别new一个父类对象和子类对象并均用父类指针指向它们Person* p1 new Person;Person* p2 new Student;//使用delete调用析构函数并释放对象空间delete p1;delete p2;return 0; }运行结果很明显派生类的部分没有进行析构造成了内存泄漏因为此时delete p1和delete p2都是调用的父类的析构函数 而我们所期望的是p1调用父类的析构函数p2调用子类的析构函数即我们期望析构函数的行为是的是一种多态行为 只有派生类Student的析构函数重写了 Person 的析构函数delete 对象调用析构函数才能构成多态才能保证 p1和 p2指向的对象正确的调用析构函数  此时只有基类和派生类的析构函数构成了重写才能实现多态才能使得 delete 按照我们的预期进行析构函数的调用。因此为了避免出现这种情况非常建议将基类类的析构函数定义为虚函数在什么情况下都没有错 注在继承的时候基类和派生类的析构函数构成隐藏原因也在这里编译器编译后把析构函数的名称统一处理成destructor()目的就是为了形成多态 2.5 C11 override 和 final 从上面可以看出C对函数重写的要求比较严格但是有些情况下由于疏忽可能会导致函数名字母次序写反而无法构成重载而这种错误在编译期间是不会报出的只有在程序运行时没有得到预期结果才来debug会得不偿失因此C11提供了override和final两个关键字可以帮助用户检测是否重写 2.5.1 final final修饰虚函数表示该虚函数不能再被重写 测试代码 //基类 class Person { public://被final修饰该虚函数不能再被重写virtual void BuyTicket() final{ cout Person-买票-全价 endl; } }; //派生类 class Student : public Person { public://派生类的虚函数重写了父类的虚函数virtual void BuyTicket() { cout Student-买票-半价 endl; } }; 进行编译直接报错 final 关键字也可用于实现一个不能被继承的类  如何实现一个不能被继承的类 把构造函数进行私有这是 C98 的做法 类定义时 加 final 关键字这是 C11的做法测试代码 //基类使用 final 修饰 class A final {}; //派生类无法进行继承基类 class B : public A {}; 编译也是直接报错 2.5.2 override override: 检查派生类虚函数是否重写了基类某个虚函数如果没有重写编译报错 测试代码  //基类 class Person { public://基类的虚函数virtual void BuyTicket() { cout Person-买票-全价 endl; } }; //派生类 class Student : public Person { public://派生类完成了虚函数的重写编译通过virtual void BuyTicket()override{ cout Student-买票-半价 endl; } }; //派生类 class Soldier : public Person { public://派生类没有完成虚函数的重写编译报错virtual void BuyTicket(int n)override{ cout Soldier-优先-买票 endl;} }; 编译结果 2.6 重载、覆盖(重写)、隐藏(重定义)的对比 三、抽象类 3.1 抽象类概念 在虚函数的后面写上 0 则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类也叫接口类抽象类不能实例化出对象。派生类继承后也不能实例化出对象只有重写纯虚函数派生类才能实例化出对象。         纯虚函数规范了派生类必须重写另外纯虚函数更体现出了接口继承 测试代码 class Car { public://纯虚函数virtual void Drive() 0; };int main() {Car c;return 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;} };int main() {Benz B;BMW bmw;return 0; } 编译通过可以实例化出对象 3.2 接口继承和实现继承 普通函数的继承是一种实现继承派生类继承了基类函数可以使用函数继承的是函数的实现。         虚函数的继承是一种接口继承派生类继承的是基类虚函数的接口目的是为了重写达成多态继承的是接口。所以如果不实现多态不要把函数定义成虚函数 四、多态的原理 4.1虚函数表 下面 Base 类实例化出对象的大小是多少 // 这里常考一道笔试题sizeof(Base)是多少 class Base { public:virtual void Func1(){cout Func1() endl;} private:int _b 1; };int main() {Base b;cout sizeof(b) endl; return 0; }运行结果是 8 结果令人诧异居然是8为什么是 8 呢 调试模式下监视窗口查看 b 通过监视窗口我们发现除了 _b 成员外还有了一个 _vfptr 在 b 对象中 (注意有些平台可能会放到对象的最后面这个跟平台有关) _vfptr 是一个指针对象中的这个指针我们叫做虚函数表指针(v代表 virtualf代表 function)。一个含有虚函数的类中都至少都有一个虚函数表指针因为虚函数的地址要被放到虚函数表中虚函数表也简称虚表  32 位平台下指针为 4字节所以上面实例化出的对象 b 的大小是8虚表指针4字节 成员 _b4字节 这个表放了些什么呢 我们继续往下分析 测试代码 基类有三个成员函数Func1 和 Func2 是虚函数Func3 是普通成员函数派生类仅对基类的 Func1 函数进行了重写 //基类 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://重写虚函数Func1virtual void Func1(){cout Derive::Func1() endl;} private:int _d 2; }; int main() {Base b;Derive d;return 0; } 调试模式下在监视窗口查看 b 和 d 对象 通过观察发现派生类对象d中也有一个虚表指针d对象由两部分构成一部分是父类继承下来的成员虚表指针也就是存在部分的另一部分是自己的成员  对象模型如下 基类b对象和派生类d对象虚表是不一样的这里我们发现 Func1 完成了重写所以d的虚表中存的是重写的 Derive::Func1所以虚函数的重写也叫作覆盖覆盖就是指虚表中虚函数的覆盖         重写是语法的叫法覆盖是原理层的叫法  另外 Func2 派生类继承下来后是虚函数所以放进了虚表Func3 也继承下来了但是不是虚函数所以不会放进虚表所以 Func3 不会在虚表里面 虚函数表本质是一个存虚函数指针的指针数组一般情况这个数组最后面放了一个nullptr  内存窗口查看 总结一下派生类的虚表生成 先将基类中的虚表内容拷贝一份到派生类虚表中如果派生类重写了基类中某个虚函数用派生类自己的虚函数覆盖虚表中基类的虚函数派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后 虚表是什么阶段初始化的虚函数存在哪里 虚表实际上是在构造函数初始化列表阶段进行初始化的         注意虚表当中存的是虚函数的地址不是虚函数虚函数和普通函数一样都是存在代码段的只是他的地址又存到了虚表当中。另外对象中存的是指向虚表的指针不是虚表 虚表存在哪里 可以通过以下段代码进行判断 //基类 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://重写虚函数Func1virtual void Func1(){cout Derive::Func1() 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 b1;cout 虚表: (void*)*((int*)b1) endl;Derive de;cout 虚表: (void*)*((int*)de) endl;return 0; } 运行结果可以发现代码当中打印了对象b1 和 对象de 的虚表指针也就是虚表的地址可以发现虚表地址与代码段的地址非常接近由此我们可以得出虚表实际上是存在代码段的 4.2 多态原理 谈论那么久的虚表那多态的原理到底是什么 下面进行测试测试代码依旧是买票的例子 //基类 class Person { public:virtual void BuyTicket() { cout 买票-全价 endl; } private:int _a 1; }; //派生类 class Student : public Person { public:virtual void BuyTicket() { cout 买票-半价 endl; } private:int _b 2; };void Func(Person p)//基类引用 {p.BuyTicket();//基类引用调用虚函数 } int main() {Person A;Func(A);Student B;Func(B);return 0; } 也是与上面一样进行调试查看 观察发现对象A中包含一个虚表指针和 _a对象 B 中也包含一个虚表指针 和 两个成员变量 _a 和 _b这两个对象当中的虚表指针分别指向自己的虚表 模型如下 进行分析 基类引用 p 指向 A 对象时p.BuyTicket() 在 A 的虚表中找到虚函数是 Person::BuyTicket基类引用 p 指向 B 对象时p.BuyTicket() 在 B 的虚表中找到虚函数是 Student::BuyTicket 传递的是 A 对象就直接去基类虚表中寻找该虚函数的地址 传递的是 B 对象先会完成切片操作再去派生类的虚表中寻找 重写的虚函数进而完成调用实现多态  基类的指针或者引用调用谁就去谁的虚表中找到对应的位置的虚函数就实现了对应的多态的功能  这样就实现出了不同对象去完成同一行为时展现出不同的形态 注意同类型的对象共享一张虚表他们的虚表指针指向的虚表是一样的 总结 构成多态指向谁就调用谁的虚函数跟对象有关不构成多态对象类型是什么就调用谁的虚函数跟类型有关 4.3 动态绑定与静态绑定 静态绑定静态绑定又称为前期绑定(早绑定)在程序编译期间确定了程序的行为也称为静态多态比如函数重载 动态绑定动态绑定又称后期绑定(晚绑定)是在程序运行期间根据具体拿到的类型确定程序的具体行为调用具体的函数也称为动态多态 测试代码 //基类 class Person { public:virtual void BuyTicket() { cout 买票-全价 endl; } }; //派生类 class Student : public Person { public:virtual void BuyTicket() { cout 买票-半价 endl; } };int main() {Student A;Person B A;//不够成多态B.BuyTicket();Person* C A;//够成多态C-BuyTicket();Person D A;//够成多态D.BuyTicket();return 0; } 在调试模式下查看反汇编 不构成多态的对象 A 函数调用的反汇编如下 下面看构成多态的对象 C 和 D 函数调用的反汇编如下 我们进行对比一下函数调用的 call 484: Person B A;//不够成多态 004027A6 call Person::BuyTicket (04011BDh)488: C-BuyTicket(); 004027BD call eax 490: D.BuyTicket(); 004027D8 call eax 对于对象B首先 BuyTicket 虽然是虚函数但是 B 对象不满足多态的条件所以这里是普通函数的调用转换成地址时是在编译时已经从符号表确认了函数的地址直接 call 地址         对于对象 C、Dcall eax中存虚函数的指针。这里可以看出满足多态的调用不是在编译时确定的是运行起来以后到对象的中取找的 这样就很好的体现了静态绑定是在编译时确定的而动态绑定是在运行时确定的 五、单继承和多继承关系的虚函数表 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; }virtual void func4() { cout Derive::func4 endl; } private:int b; };int main() {Base b;Derive d;return 0; } 在调试下的监视窗口查看 很奇怪在监视窗口居然没有看见 func3 和 func4  原因是可能是VS编译器的监视窗口故意隐藏了这两个函数也可以认为这是一个小bug此时如果我们想要看到派生类对象完整的虚表有两个方法 直接去内存窗口查看虚表看看有没有 func3 和 func4内存窗口确实有 func3 和 func4 但是这种方式我们很难看出另一个方法就是把虚表打印到输出屏上 我们可以使用以下代码打印上述基类和派生类对象的虚表内容在打印过程中可以顺便用虚函数地址调用对应的虚函数从而打印出虚函数的函数名这样可以进一步确定虚表当中存储的是哪一个函数的地址 typedef void(*VFPTR) (); //虚函数指针类型重命名 //打印虚表地址及其内容 void PrintVTable(VFPTR vTable[]) {// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout 虚表地址 vTable endl;for (int i 0; vTable[i] ! nullptr; i){printf( 第%d个虚函数地址0x%x--, i, vTable[i]);vTable[i](); // 使用虚函数地址调用虚函数}cout endl; }// 思路取出b、d对象的头4bytes就是虚表的指针前面我们说了虚函数表本质是一个存虚函数指针的指针数组这个数组最后面放了一个nullptr // 1.先取b的地址强转成一个int*的指针 // 2.再解引用取值就取到了b对象头4bytes的值这个值就是指向虚表的指针 // 3.再强转成VFPTR*因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。 // 4.虚表指针传递给PrintVTable进行打印虚表 // 5.需要说明的是这个打印虚表的代码经常会崩溃因为编译器有时对虚表的处理不干净虚表最后面没有放nullptr导致越界这是编译器的问题。我们只需要点目录栏的 - 生成 - 清理解决方案再编译就好了。int main() {Base b;Derive d;VFPTR* vTableb (VFPTR*)(*(int*)b);PrintVTable(vTableb);VFPTR* vTabled (VFPTR*)(*(int*)d);PrintVTable(vTabled);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(*VFPTR) (); //虚函数指针类型重命名 //打印虚表地址及其内容 void PrintVTable(VFPTR vTable[]) {// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数cout 虚表地址 vTable endl;for (int i 0; vTable[i] ! nullptr; i){printf( 第%d个虚函数地址0x%x--, i, vTable[i]);vTable[i](); // 使用虚函数地址调用虚函数}cout endl; }int main() {Base1 b1;Base2 b2;PrintVTable((VFPTR*)(*(int*)b1)); //打印基类对象b1的虚表地址及其内容PrintVTable((VFPTR*)(*(int*)b2)); //打印基类对象b2的虚表地址及其内容Derive d;VFPTR* vTableb1 (VFPTR*)(*(int*)d);PrintVTable(vTableb1);//打印派生类对象d的第一个虚表地址及其内容VFPTR* vTableb2 (VFPTR*)(*(int*)((char*)d sizeof(Base1)));PrintVTable(vTableb2);//打印派生类对象d的第2个虚表地址及其内容return 0; } 两个基类 Base1 和  Base2 的虚表模型如下 派生类的虚表模更复杂了派生类的虚表模型如下  内存查看 模型如下 在多继承关系当中派生类的虚表生成过程如下 分别继承各个基类的虚表内容到派生类的各个虚表当中对派生类重写了的虚函数地址进行覆盖(派生类中的各个虚表中存有该被重写虚函数地址的都需要进行覆盖)比如func1在派生类第一个继承基类部分的虚表当中新增派生类当中新的虚函数地址比如 func3 运行结果如下 5.3. 菱形继承、菱形虚拟继承 实际中我们不建议设计出菱形继承及菱形虚拟继承一方面太复杂容易出问题另一方面这样的模型访问基类成员有一定得性能损耗。所以菱形继承、菱形虚拟继承我们的虚表我们就不看了一般我们也不需要研究清楚因为实际中很少用 六、继承和多态常见的面试问题 6.1 概念查考 1. 下面哪种面向对象的方法可以让你变得富有( ) A: 继承 B: 封装 C: 多态 D: 抽象 2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关而对方法的调用则可以关联于具体的对象。 A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定 3. 面向对象设计中的继承和组合下面说法错误的是 A继承允许我们覆盖重写父类的实现细节父类的实现对于子类是可见的是一种静态复用也称为白盒复用 B组合的对象不需要关心各自的实现细节之间的关系是在运行时候才确定的是一种动态复用也称为黑盒复用 C优先使用继承而不是组合是面向对象设计的第二原则 D继承可以使子类能自动继承父类的接口但在设计模式中认为这是一种破坏了父类的封装性的表现 4. 以下关于纯虚函数的说法,正确的是( ) A声明纯虚函数的类不能实例化对象 B声明纯虚函数的类是虚基类 C子类必须实现基类的纯虚函数 D纯虚函数必须是空函数 5. 关于虚函数的描述正确的是( ) A派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B内联函数不能是虚函数 C派生类必须重新定义基类的虚函数 D虚函数可以是一个static型的函数 6. 关于虚表说法正确的是 A一个类只能有一张虚表 B基类中有虚函数如果子类中没有重写基类的虚函数此时子类与基类共用同一张虚表 C虚表是在运行期间动态生成的 D一个类的不同对象共享该类的虚表 7. 假设A类中有虚函数B继承自AB重写A中的虚函数也没有定义任何虚函数则 AA类对象的前4个字节存储虚表地址B类对象前4个字节不是虚表地址 BA类对象和B类对象前4个字节存储的都是虚基表的地址 CA类对象和B类对象前4个字节存储的虚表地址相同 DA类和B类虚表中虚函数个数相同但A类和B类使用的不是同一张虚表 8. 下面程序输出结果是什么? #includeiostream using namespace std; class A { public:A(char* s) { cout s endl; }~A() {} }; class B :virtual public A { public:B(char* s1, char* s2) :A(s1) { cout s2 endl; } }; class C :virtual public A { public:C(char* s1, char* s2) :A(s1) { cout s2 endl; } }; class D :public B, public C { public:D(char* s1, char* s2, char* s3, char* s4) :B(s1, s2), C(s1, s3), A(s1){cout s4 endl;} }; int main() {D* p new D(class A, class B, class C, class D);delete p;return 0; } Aclass A class B class C class D Bclass D class B class C class A Cclass D class C class B class A Dclass A class C class B class D 9. 多继承中指针偏移问题下面说法正确的是( ) 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 10. 以下程序输出结果是什么 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: 以上都不正确 参考答案 1. A 2. D 3. C 4. A 5. B 6. D 7. D 8. A 9. C 10. B  6.2 问答题 1. 什么是多态 答参考本节课件内容 2. 什么是重载、重写(覆盖)、重定义(隐藏) 答参考本节内容 3. 多态的实现原理 答构成多态的父类对象和子类对象的成员当中都包含一个虚表指针这个虚表指针指向一个虚表虚表当中存储的是该类对应的虚函数地址。因此当父类指针指向父类对象时通过父类指针找到虚表指针然后在虚表当中找到的就是父类当中对应的虚函数当父类指针指向子类对象时通过父类指针找到虚表指针然后在虚表当中找到的就是子类当中对应的虚函数 4. inline函数可以是虚函数吗 答可以不过编译器就忽略inline属性这个函数就不再是inline因为虚函数要放到虚表中去。 5. 静态成员可以是虚函数吗 答不能因为静态成员函数没有this指针使用类型::成员函数的调用方式无法访问虚函数表所以静态成员函数无法放进虚函数表。 6. 构造函数可以是虚函数吗 答不能因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的 7. 析构函数可以是虚函数吗什么场景下析构函数是虚函数 答可以并且最好把基类的析构函数定义成虚函数。参考本节课件内容 8. 对象访问普通函数快还是虚函数更快 答首先如果是普通对象是一样快的。如果是指针对象或者是引用对象则调用的普通函数快因为构成多态运行时调用虚函数需要到虚函数表中去查找 9. 虚函数表是在什么阶段生成的存在哪的 答虚函数表是在编译阶段就生成的一般情况下存在代码段(常量区)的 10. C菱形继承的问题虚继承的原理 答参考上节继承内容。注意这里不要把虚函数表和虚基表搞混了 11. 什么是抽象类抽象类的作用 答参考本节内容。抽象类强制重写了虚函数另外抽象类体现出了接口继承关系 注以上问题答案仅供参考自己组织语言即可 ----------------我是分割线--------------- 文章到这里就结束了下一篇即将更新
http://www.tj-hxxt.cn/news/222054.html

相关文章:

  • 皂君庙网站建设微软网站开发技术
  • wordpress+andriod新站优化
  • 建设网站不会写代码上海做网站建设
  • 有域名 如何免费建设网站茶叶建设网站市场分析
  • 网站建设项目总结网站建设合同标的怎么写
  • 做影视网站需要的软件南通制作公司网站
  • 莆田网站建设模板做国外网站衣服码数要怎么写
  • 怎么免费做一个网站域名商的网站
  • 西安商城网站制作威县做网站哪儿好
  • 卡片式设计网站制作建设网站的服务费是指什么
  • 网站开发vs设计报告wordpress添加磁力下载
  • 万网建站流程电子商务平台在家能干吗
  • 昆明学校网站设计公司百度图片查找
  • 广州seo网站推广优化半厘米wordpress
  • 全栈网站开发流行框架网站制作公司制作网站
  • 网站案例上海百度广告推广费用
  • 建网站后如何运营一加官网
  • 娄底建设网站电商网站建设的内容
  • 百度的网站哪来的网站开发的销售
  • wordpress网站在哪里修改谷歌搜索关键词排名
  • 大学网站建设考核办法wordpress php5.6版本
  • 什么网站做蔬菜生鲜比较好成都信用网企业查询系统
  • 四川住房和城乡建设网站西安做网站app
  • 前端作业做一个网站国内域名网站有那些
  • 网站建设项目经理wordpress本地写文章
  • 易优建站南昌公路建设有限公司网站
  • 北京网页设计公司网站广州建设教育网站
  • 网站建设 官网芜湖的网站建设公司
  • 临沂品牌网站制作高端网站定制的方法
  • 做网站项目流程国内最近新闻