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

广安 网站建设休闲吧网站建设

广安 网站建设,休闲吧网站建设,广州公司电商网站建设,做导购网站用什么样的主机多态 多态的概念多态的定义及实现1.多态的构成条件2.虚函数3.虚函数的重写4.虚函数重写的两个例外5.C11 override 和 final6.重载、覆盖(重写)、隐藏(重定义)的对比 抽象类1.概念2.接口继承和实现继承 多态的原理1.虚函数表2.多态的原理3.动态绑定与静态绑定 单继承和多继承关系… 多态 多态的概念多态的定义及实现1.多态的构成条件2.虚函数3.虚函数的重写4.虚函数重写的两个例外5.C11 override 和 final6.重载、覆盖(重写)、隐藏(重定义)的对比 抽象类1.概念2.接口继承和实现继承 多态的原理1.虚函数表2.多态的原理3.动态绑定与静态绑定 单继承和多继承关系的虚函数表1.单继承中的虚函数表2.多继承中的虚函数表 继承和多态常见的面试问题1.概念查考2.问答题 多态的概念 多态的概念通俗来说去完成某个行为当不同的对象去完成时会产生出不同的状态 。 在C中多态Polymorphism是面向对象编程的一个重要概念它允许你使用统一的接口来处理不同的数据类型从而增加代码的灵活性和可扩展性。多态分为编译时多态性静态多态性和运行时多态性动态多态性两种类型。 编译时多态性静态多态性编译时多态性是通过函数重载Function Overloading和运算符重载Operator Overloading实现的。这种多态性在编译阶段就能够确定要调用的函数或操作符根据函数或操作符的参数类型来选择执行不同的代码。 例如函数重载允许你定义多个同名函数但参数列表不同编译器根据传递的参数类型来选择调用合适的函数。 void print(int num) {cout Printing an integer: num endl; }void print(double num) {cout Printing a double: num endl; }运算符重载则允许你定义自定义类型的操作符行为。 运行时多态性动态多态性运行时多态性是通过继承和虚函数Virtual Function实现的。这种多态性允许你在运行时根据对象的实际类型来决定调用哪个函数从而实现动态的函数分发。 在运行时多态性中基类可以定义虚函数并且派生类可以重写这些虚函数。通过使用基类指针或引用指向派生类对象可以在运行时调用派生类的虚函数。 class Shape { public:virtual void draw() {cout Drawing a shape. endl;} };class Circle : public Shape { public:void draw() override {cout Drawing a circle. endl;} };class Square : public Shape { public:void draw() override {cout Drawing a square. endl;} };使用时 Shape* shapePtr;Circle circle; Square square;shapePtr circle; shapePtr-draw(); // 调用 Circle 的 draw()shapePtr square; shapePtr-draw(); // 调用 Square 的 draw()这样根据指针指向的实际对象类型实现了在运行时根据对象类型调用不同的函数。 总而言之C的多态性允许你通过统一的接口处理不同类型的对象无论是在编译时还是运行时。这大大提高了代码的可维护性和可扩展性。 多态的定义及实现 1.多态的构成条件 那么在继承中要构成多态还有两个条件 必须通过基类的指针或者引用调用虚函数被调用的函数必须是虚函数且派生类必须对基类的虚函数进行重写 2.虚函数 虚函数即被virtual修饰的类成员函数称为虚函数 class Person { public:virtual void BuyTicket() { cout 买票-全价 endl;} };3.虚函数的重写 虚函数的重写(覆盖)派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同)称子类的虚函数重写了基类的虚函数 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; }注意在重写基类虚函数时派生类的虚函数在不加virtual关键字时虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范不建议这样使用 多态的本质原理符合多态的两个条件那么调用时会到指向对象的虚表中找到对应的虚函数地址进行调用 多态调用运行时去指向对象的虚表中找到函数地址进行调用 普通调用在编译链接时就已经确认了函数地址运行时直接调用 4.虚函数重写的两个例外 协变(基类与派生类虚函数返回值类型不同) 派生类重写基类虚函数时与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用派生类虚函数返回派生类对象的指针或者引用时称为协变 class A{}; class B : public A {};class Person { public:virtual A* f() {return new A;} };class Student : public Person { public:virtual B* f() {return new B;} };析构函数的重写(基类与派生类析构函数的名字不同) 如果基类的析构函数为虚函数此时派生类析构函数只要定义无论是否加virtual关键字都与基类的析构函数构成重写虽然基类与派生类析构函数名字不同。虽然函数名不相同看起来违背了重写的规则其实不然这里可以理解为编译器对析构函数的名称做了特殊处理编译后析构函数的名称统一处理成destructor。 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; }5.C11 override 和 final 从上面可以看出C对函数重写的要求比较严格但是有些情况下由于疏忽可能会导致函数名字母次序写反而无法构成重载而这种错误在编译期间是不会报出的只有在程序运行时没有得到预期结果才来debug会得不偿失因此C11提供了override和final两个关键字可以帮助用户检测是否重写。 final修饰虚函数表示该虚函数不能再被重写 class Car { public:virtual void Drive() final {} }; class Benz :public Car { public:virtual void Drive() {cout Benz-舒适 endl;} };错误(活动) E1850 无法重写“final”函数 Car::Drive (已声明 所在行数:4) override: 检查派生类虚函数是否重写了基类某个虚函数如果没有重写编译报错 class Car { public:virtual void Drive() {} }; class Benz :public Car { public:virtual void test() override { cout Benz-舒适 endl; } };错误 C3668 “Benz::test”: 包含重写说明符“override”的方法没有重写任何基类方法 错误(活动) E1455 使用“override”声明的成员函数不能重写基类成员6.重载、覆盖(重写)、隐藏(重定义)的对比 需要注意的是子类虚函数即使不加virtual依旧构成重写重写的协变返回值可以不相同但必须是父子关系的指针或引用 抽象类 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(); }2.接口继承和实现继承 普通函数的继承是一种实现继承派生类继承了基类函数可以使用函数继承的是函数的实现。虚函数的继承是一种接口继承派生类继承的是基类虚函数的接口目的是为了重写达成多态继承的是接口。所以如果不实现多态不要把函数定义成虚函数 在面向对象编程中接口继承Interface Inheritance和实现继承Implementation Inheritance是两种不同的继承方式用于在类之间共享功能和行为。让我们更详细地了解这两种继承方式 接口继承Interface Inheritance 接口继承是一种继承方式其中一个类称为子类或派生类从另一个类称为父类或基类继承方法声明而不继承实际实现。这种继承方式用于定义一组方法的标准接口而不关心具体的实现。在C中接口继承通常通过定义纯虚函数Pure Virtual Function实现。 示例 class Shape { public:virtual void draw() 0; // 纯虚函数定义接口 };class Circle : public Shape { public:void draw() override {// 实现具体的绘制圆的代码} };class Square : public Shape { public:void draw() override {// 实现具体的绘制正方形的代码} };在上面的例子中Shape 类定义了一个纯虚函数 draw它作为接口继承被继承类 Circle 和 Square 实现。 实现继承Implementation Inheritance 实现继承是一种继承方式其中一个类从另一个类继承方法的声明和实际实现。这种继承方式用于共享已经存在的代码和实现。在C中通过普通的继承机制实现实现继承。 示例 class Vehicle { public:void startEngine() {// 启动引擎的代码}void stopEngine() {// 关闭引擎的代码} };class Car : public Vehicle { public:void drive() {// 具体的驾驶代码} };在上面的例子中Car 类从 Vehicle 类进行实现继承它继承了 startEngine 和 stopEngine 的实现。 需要注意的是C中的单继承限制了一个类只能从一个父类继承这样有助于避免多继承可能带来的复杂性和歧义。 总结 接口继承用于定义方法的标准接口而不关心实际实现。通过纯虚函数来实现接口继承。实现继承用于共享已有代码和实现子类继承父类的方法和实现。在实际编程中应根据需要选择合适的继承方式避免继承关系过于复杂从而保持代码的可读性和维护性。 多态的原理 1.虚函数表 sizeof(Base)是多少 class Base { public:virtual void Func1(){cout Func1() endl;} private:int _b 1; }; 通过观察测试我们发现b对象是8bytes除了_b成员还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面这个跟平台有关)对象中的这个指针我们叫做虚函数表指针(v代表virtualf代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针因为虚函数的地址要被放到虚函数表中虚函数表也简称虚表那么派生类中这个表放了些什么呢我们接着往下分析 针对上面的代码我们做出以下改造 1.我们增加一个派生类Derive去继承Base 2.Derive中重写Func1 3.Base再增加一个虚函数Func2和一个普通函数Func3 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;} 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下是存在代码段的 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 存储到栈上// rcx 寄存器包含了传递的参数 p 的引用mov qword ptr [rsp8],rcx push rbp // 保存调用函数之前的 rbp 寄存器状态push rdi // 保存调用函数之前的 rdi 寄存器状态sub rsp,0E8h // 在栈上为局部变量分配空间0xE8 字节的空间lea rbp,[rsp20h] // 计算 rbp 的值指向当前栈帧的基址lea rcx,[__B666B148_testcpp (07FF629704067h)] call __CheckForDebuggerJustMyCode (07FF6296F1410h) // 调用某个函数可能是与调试器相关的用于检查是否只调试自己的代码p.BuyTicket(); // 调用对象 p 的 BuyTicket() 方法mov rax,qword ptr [p] // 将对象 p 的地址存储到 rax 寄存器mov rax,qword ptr [rax] // 将 p 对象的 vtable虚函数表的地址加载到 raxmov rcx,qword ptr [p] // 将对象 p 的地址存储到 rcx 寄存器call qword ptr [rax] // 通过虚函数表调用 p.BuyTicket() 方法// 清理栈上的局部变量分配add rsp, 0E8hpop rdi // 恢复调用函数之前的 rdi 寄存器状态pop rbp // 恢复调用函数之前的 rbp 寄存器状态 }3.动态绑定与静态绑定 动态绑定Dynamic Binding和静态绑定Static Binding是与多态性相关的两个重要概念用于描述在编译时和运行时如何决定调用哪个函数或方法。 静态绑定Static Binding 静态绑定是在编译时编译阶段确定调用哪个函数或方法的过程。在静态绑定中编译器会根据调用表达式中的信息确定要调用的函数这通常发生在编译器生成目标代码时。静态绑定适用于非虚函数普通的函数重载以及运算符重载等。 示例 class Base { public:void print() {cout Base class endl;} };class Derived : public Base { public:void print() {cout Derived class endl;} };int main() {Derived d;Base b d;b.print(); // 静态绑定编译时确定调用 Base::print() }在上述例子中尽管 b 引用的是 Derived 类的对象但因为 print 函数不是虚函数所以在编译时就已经确定了调用 Base::print()。 动态绑定Dynamic Binding 动态绑定是在运行时运行阶段根据对象的实际类型来决定调用哪个函数或方法的过程。这通常涉及虚函数的使用其中基类中声明的函数被标记为 virtual并且派生类中进行了重写。运行时会根据对象的实际类型而不仅仅是引用或指针的类型调用适当的函数。 示例 class Shape { public:virtual void draw() {cout Drawing a shape. endl;} };class Circle : public Shape { public:void draw() override {cout Drawing a circle. endl;} };int main() {Circle c;Shape s c;s.draw(); // 动态绑定运行时根据实际对象类型调用 Circle::draw() }在上述例子中由于 draw 函数被声明为虚函数调用 s.draw() 会根据实际对象类型 Circle 调用 Circle::draw()。 总结 静态绑定 是在编译时确定函数调用的方式主要适用于非虚函数和函数重载。动态绑定 是在运行时根据对象的实际类型确定函数调用的方式主要适用于虚函数和多态性的实现。动态绑定使得多态性成为可能使代码更加灵活和可扩展。 单继承和多继承关系的虚函数表 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; };观察下图中的监视窗口中我们发现看不见func3和func4。这里是编译器的监视窗口故意隐藏了这两个函数也可以认为是他的一个小bug。那么我们如何查看d的虚表呢下面我们使用代码打印出虚表中的函数 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]);VFPTR f vTable[i];f();}cout endl; } int main() {Base b;Derive d;VFPTR* vTableb (VFPTR*)(*(int*)b);PrintVTable(vTableb);VFPTR* vTabled (VFPTR*)(*(int*)d);PrintVTable(vTabled);return 0; }思路取出b、d对象的头4bytes就是虚表的指针前面我们说了虚函数表本质是一个存虚函数指针的指针数组这个数组最后面放了一个nullptr 1.先取b的地址强转成一个int*的指针 2.再解引用取值就取到了b对象头4bytes的值这个值就是指向虚表的指针 3.再强转成VFPTR*因为虚表就是一个存VFPTR类型(虚函数指针类型)的数组。 4.虚表指针传递给PrintVTable进行打印虚表 5.需要说明的是这个打印虚表的代码经常会崩溃因为编译器有时对虚表的处理不干净虚表最后面没有放nullptr导致越界这是编译器的问题。我们只需要点目录栏的-生成-清理解决方案再编译就好了。 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]);VFPTR f vTable[i];f();}cout endl; } int main() {Derive d;VFPTR* vTableb1 (VFPTR*)(*(int*)d);PrintVTable(vTableb1);VFPTR* vTableb2 (VFPTR*)(*(int*)((char*)d sizeof(Base1)));PrintVTable(vTableb2);return 0; }多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中 继承和多态常见的面试问题 1.概念查考 下面哪种面向对象的方法可以让你变得富有 A A: 继承 B: 封装 C: 多态 D: 抽象 B 是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关而对方法的调用则可以关联于具体的对象。 A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定 动态绑定Dynamic Binding是面向对象编程中的一种机制也被称为运行时多态性。它使得方法的调用可以在运行时根据对象的实际类型来确定而不是在编译时就确定。这允许你在统一的接口下使用不同类型的对象并根据对象的实际类型来决定调用哪个方法。动态绑定实现了方法的调用与具体的对象关联使得多态性成为可能。 面向对象设计中的继承和组合下面说法错误的是 C A继承允许我们覆盖重写父类的实现细节父类的实现对于子类是可见的是一种静态复用也称为白盒复用 B组合的对象不需要关心各自的实现细节之间的关系是在运行时候才确定的是一种动态复用也称为黑盒复用 C优先使用继承而不是组合是面向对象设计的第二原则 D继承可以使子类能自动继承父类的接口但在设计模式中认为这是一种破坏了父类的封装性的表现 这个说法是错误的。在面向对象设计中通常推荐使用组合而不是继承。这被称为组合优于继承的原则指的是在设计中更倾向于通过组合对象之间的关联来构建新的类而不是通过继承来获得现有类的功能。这是因为继承可能引入不必要的紧耦合破坏封装性并导致继承链的脆弱性。正确的做法是根据具体的情况选择继承或组合。继承在某些情况下是合适的例如当子类是父类的一种特例并且子类不需要对父类的行为进行修改时。而在其他情况下组合可以更好地实现代码的灵活性、可维护性和松耦合。 以下关于纯虚函数的说法,正确的是 A A声明纯虚函数的类不能实例化对象 B声明纯虚函数的类是虚基类 C子类必须实现基类的纯虚函数 D纯虚函数必须是空函数 关于虚函数的描述正确的是 B A派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B内联函数不能是虚函数 C派生类必须重新定义基类的虚函数 D虚函数可以是一个static型的函数 关于虚表说法正确的是 D A一个类只能有一张虚表 B基类中有虚函数如果子类中没有重写基类的虚函数此时子类与基类共用同一张虚表 C虚表是在运行期间动态生成的 D一个类的不同对象共享该类的虚表 虚表是一个存储着虚函数指针的表格每个类包括基类和派生类都可能有一个对应的虚表。下面是对其他选项的讨论A一个类只能有一张虚表。这个说法是不正确的。每个类包括基类和派生类都有可能有一个虚表以支持多态性。B基类中有虚函数如果子类中没有重写基类的虚函数此时子类与基类共用同一张虚表。这个说法是不正确的。每个类都有自己的虚表即使子类没有重写虚函数它也会有自己的虚表。C虚表是在编译期间就生成的而不是在运行时动态生成的。每个类的虚表在编译时就被创建它存储了指向类的虚函数的指针。 假设A类中有虚函数B继承自AB重写A中的虚函数也没有定义任何虚函数则 D AA类对象的前4个字节存储虚表地址B类对象前4个字节不是虚表地址 BA类对象和B类对象前4个字节存储的都是虚基表的地址 CA类对象和B类对象前4个字节存储的虚表地址相同 DA类和B类虚表中虚函数个数相同但A类和B类使用的不是同一张虚表 下面程序输出结果是什么? A #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 多继承中指针偏移问题下面说法正确的是 C 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 在多继承中每个基类都有自己的成员变量和虚表指针如果有虚函数。派生类的对象包含每个基类的成员变量按照它们在派生类的声明中出现的顺序排列。虚表指针在派生类对象的开头。在给定的代码中Derive 类从两个基类 Base1 和 Base2 继承按照继承的顺序它的内存布局是 _b1, _b2, _d。而指针 p1 和 p3 都指向对象 d 的开头所以它们的值相等。指针 p2 指向对象 d 中 _b2 成员的位置所以它的值与 p1 和 p3 不同。所以正确的描述是Cp1 p3 ! p2。 以下程序输出结果是什么 B 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: 以上都不正确 首先这里开辟的是派生类的对象主要和new有关我们调用test函数时因为此函数并没有被重写所以我们正常调用的基类的test函数传入的是A的this指针但在其中调用的其实是B中重写的func函数虚函数重写是接口继承所以这里传入的val值实际为基类func函数缺省值1所以这题选B 2.问答题 什么是多态答参考上面的多态概念和定义小节 什么是重载、重写(覆盖)、重定义(隐藏)答参考上面重载、覆盖(重写)、隐藏(重定义)的对比小节 多态的实现原理答参考上面多态的实现原理小节 inline函数可以是虚函数吗答可以不过编译器忽略inline属性这个函数就不再是inline因为虚函数要放到虚表中去。 静态成员可以是虚函数吗答不能因为静态成员函数没有this指针使用类型::成员函数的调用方式无法访问虚函数表所以静态成员函数无法放进虚函数表。 构造函数可以是虚函数吗答不能因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的。 拷贝构造和operator可以是虚函数吗答拷贝构造不可以拷贝构造也是构造函数。operator可以但无实际价值。 析构函数可以是虚函数吗什么场景下析构函数是虚函数答可以并且最好把基类的析构函数定义成虚函数。参考虚函数重写的两个例外小节 对象访问普通函数快还是虚函数更快答首先如果是普通对象是一样快的。如果是指针对象或者是引用对象则调用的普通函数快因为构成多态运行时调用虚函数需要到虚函数表中去查找。 虚函数表是在什么阶段生成的存在哪的答虚函数表是在编译阶段就生成的一般情况下存在代码段(常量区)的。 C菱形继承的问题虚继承的原理答参考继承博客。注意这里不要把虚函数表和虚基表搞混了 什么是抽象类抽象类的作用答参考抽象类小节。抽象类强制重写了虚函数另外抽象类体现出了接口继承关系。
http://www.dnsts.com.cn/news/95861.html

相关文章:

  • 无锡网站怎么做wordpress搜索所有类
  • 做酒店网站wordpress网站地图插件
  • 个人怎么开网站马可波罗网介绍
  • 网站怎样做wap端济宁正德网站建设
  • 苏州自助建站模板wordpress外贸建站教程
  • 做网站充值微信必须是企业中国万网是做什么的
  • php网站开发有前景吗关键词搜索引擎工具
  • python做网站怎么样求一个免费的
  • 建站快车加盟wordpress右边小工具栏
  • 学校微网站模板wordpress安装模版500
  • 免费建站平台哪家好遵义市汇川区建设厅网站
  • 太原网站域名开发国内软件外包公司
  • 六安论坛网站动漫制作软件
  • 南京装饰公司100排名太原网站排名优化价格
  • 国外网站建设软件大连html5网站建设价格
  • 做宠物店网站的素材wordpress品牌分类
  • 泉州网站公司答题app怎么制作
  • 网站建设小组五类成员自定义wordpress页面
  • 一站式网站建设用途品牌运营具体做什么
  • 建设银行网站多少wordpress访问权限插件
  • 企业网站的功能网站开发课题背景
  • 白糖贸易怎么做网站浅谈电子商务网站建设与规划
  • 网站制作公司嘉兴梵客家装口碑怎么样
  • 自己写小说的网站甘肃制作网站
  • 一个网站做数据分析要多少钱wordpress 微信支付
  • asp.net2.0网站开发全程解析 pdf工业和信息化部发短信是怎么回事
  • 网站整站模板温州专业手机网站制作哪家好
  • 就是做网站的.....电子商务网站的建设过程
  • 做网站一个月可以赚多少钱wordpress数据库没有填写
  • 柯桥区交通投资建设集团网站网站代码跑偏了怎么做