2026/5/21 10:18:36
网站建设
项目流程
公司网站空间怎么续费,长沙微信网站制作,可以看小视频的浏览器,免费企业网站建立一、继承1.1继承的概念 继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段#xff0c;它允许我们在保持原有类特性的基础上进⾏扩展#xff0c;增加⽅法(成员函数)和属性(成员变量)#xff0c;这样产⽣新的类#xff0c;称派⽣类。继承呈现了⾯向对象程…一、继承1.1继承的概念继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段它允许我们在保持原有类特性的基础上进⾏扩展增加⽅法(成员函数)和属性(成员变量)这样产⽣新的类称派⽣类。继承呈现了⾯向对象程序设计的层次结构体现了由简单到复杂的认知过程。以前我们接触的函数层次的复⽤继承是类设计层次的复⽤。1.2继承的定义定义格式下⾯我们看到Person是基类也称作⽗类。Student是派⽣类也称作⼦类。继承基类成员访问方式的变化二、多态1.多态的概念通俗来说就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)这⾥我们重点讲运⾏时多态编译时多态(静态多态)和运⾏时多态(动态多态)。编译时多态(静态多态)主要就是我们前⾯讲的函数重载和函数模板他们传不同类型的参数就可以调⽤不同的函数通过参数不同达到多种形态之所以叫编译时多态是因为他们实参传给形参的参数匹配是在编译时完成的我们把编译时⼀般归为静态运⾏时归为动态。2.实现多态还有两个必须重要条件1.必须是基类的指针或者引⽤调⽤虚函数2.被调⽤的函数必须是虚函数并且完成了虚函数重写/覆盖。说明要实现多态效果第⼀必须是基类的指针或引⽤因为只有基类的指针或引⽤才能既指向基类对象⼜指向派⽣类对象第⼆派⽣类必须对基类的虚函数完成重写/覆盖重写或者覆盖了基类和派⽣类之间才能有不同的函数多态的不同形态效果才能达到。虚函数类成员函数前⾯加virtual修饰那么这个成员函数被称为虚函数。注意⾮成员函数不能加virtual修饰。1 class Person 2 { 3 public: 4 virtual void BuyTicket() { cout 买票-全价 endl;} 5 };2.1.3虚函数的重写/覆盖虚函数的重写/覆盖派⽣类中有⼀个跟基类完全相同的虚函数(即派⽣类虚函数与基类虚函数的返回值 类型、函数名字、参数列表完全相同)称派⽣类的虚函数重写了基类的虚函数。注意在重写基类虚函数时派⽣类的虚函数在不加virtual关键字时虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派⽣类依旧保持虚函数属性)但是该种写法不是很规范不建议这样使⽤不过在考试选择题中经常会故意买这个坑让你判断是否构成多态。class Person { public: virtual void BuyTicket() { cout 买票-全价 endl; } }; class Student : public Person { public: virtual void BuyTicket() { cout 买票-打折 endl; } }; void Func(Person* ptr) { // 这⾥可以看到虽然都是Person指针Ptr在调⽤BuyTicket // 但是跟ptr没关系⽽是由ptr指向的对象决定的。 ptr-BuyTicket(); } int main() { Person ps; Student st; Func(ps); Func(st); return 0; } 13.三个重要概念三、一些问题1.以下程序输出结果是什么A: A-0 B: B-1 C: A-1 D: B-0 E: 编译出错 F: 以上都不正确class A { public: virtual void func(int val 1){ std::coutA- val std::endl;} virtual void test(){ func();} }; class B : public A { public: void func(int val 0){ std::coutB- val std::endl; } }; int main(int argc ,char* argv[]) { B*p new B; p-test(); return 0; }这道题是 C 中非常经典的一个“陷阱题”考察了虚函数多态与默认参数在绑定机制上的本质区别。正确答案是B: B-1以下是详细的逻辑拆解核心考点这道题看似简单实则包含两个冲突的机制1. 虚函数使用的是动态绑定2. 默认参数使用的是静态绑定详细执行流程分析1. 调用 p-test();在 main 函数中B* p new B; 创建了一个 B 类型的对象。执行 p-test();。因为子类 B 没有重写Overridetest() 函数所以这里实际调用的是继承自父类A::test()。2. 进入 A::test() 内部在 A::test() 函数体中执行了一句 func();。这行代码等价于 this-func();。此时的 this 指针虽然在运行时指向的是 B 类型的对象但在编译阶段它的静态类型是 A因为代码是在 A 类里面写的。3. 决定调用“谁”的函数体动态绑定* 由于 func 是**虚函数C 会根据 this 指针实际指向的对象类型即 B 类型来决定调用哪个函数。结论程序会调用子类 B::func的函数体。结果输出的前半部分必然是B-。4. 决定参数传“多少”静态绑定 —— 最坑的地方这是本题的关键默认参数的值是在编译阶段确定的而不是运行时。编译器在编译 A::test() 中的 func() 调用时它只知道 this 指针的类型是 A*。因此编译器会去查找 A::func 的声明来确定默认参数。A::func 的默认参数是 1 (int val 1)。结论编译器把代码处理成了 this-func(1)。子类 B 中定义的默认参数 0 被完全忽略了因为它在编译 A::test 时是不可见的。---总结程序最终执行的逻辑变成了调用 **B 类** 的 func 函数但是传入的参数是A 类规定的默认值 1。所以输出结果是B-1。编程启示Effective C永远不要重定义继承而来的默认参数值。因为默认参数是静态绑定的而虚函数是动态绑定的两者混用会导致“你调用了子类的函数却使用了父类的默认参数”产生极具迷惑性的 Bug。为什么基类中的析构函数建议设计为虚函数。核心原因避免内存泄漏和未定义行为当你使用多态基类指针 / 引用指向派生类对象时如果基类析构函数不是虚函数那么通过基类指针销毁派生类对象时只会调用基类的析构函数而派生类独有的成员不会被析构从而导致内存泄漏或资源释放不完整。举个直观的例子先看基类析构函数非虚的情况#include iostream using namespace std; // 基类 class Base { public: Base() { cout Base 构造函数\n; } // 非虚析构函数 ~Base() { cout Base 析构函数\n; } }; // 派生类 class Derived : public Base { public: Derived() { cout Derived 构造函数\n; } ~Derived() { cout Derived 析构函数\n; } private: int* data new int[10]; // 派生类独有的动态资源 }; int main() { // 多态基类指针指向派生类对象 Base* ptr new Derived(); delete ptr; // 通过基类指针销毁对象 return 0; }输出结果Base 构造函数Derived 构造函数Base 析构函数问题Derived的析构函数没有被调用其内部的int* data指向的动态数组永远无法释放造成内存泄漏。再看基类析构函数设为虚函数的情况#include iostream using namespace std; class Base { public: Base() { cout Base 构造函数\n; } // 虚析构函数 virtual ~Base() { cout Base 析构函数\n; } }; class Derived : public Base { public: Derived() { cout Derived 构造函数\n; } ~Derived() { cout Derived 析构函数\n; delete[] data; // 释放派生类的动态资源 } private: int* data new int[10]; }; int main() { Base* ptr new Derived(); delete ptr; return 0; }输出结果Base 构造函数Derived 构造函数Derived 析构函数Base 析构函数效果先调用派生类析构函数释放自身资源再调用基类析构函数释放基类资源实现资源的完整释放。补充说明仅当类作为基类且可能被多态使用时需要如果一个类不会被继承或者不会通过基类指针 / 引用销毁派生类对象析构函数无需设为虚函数虚函数会增加微小的内存开销因为类会生成虚函数表。如果基类析构函数是虚函数派生类析构函数自动成为虚函数即使派生类析构函数不写virtual关键字也会继承虚属性。C11 的 override 关键字派生类析构函数可以加override明确表示重写基类虚析构函数增强代码可读性。总结核心目的解决多态场景下通过基类指针销毁派生类对象时派生类析构函数不被调用的问题避免内存泄漏和资源释放不完整。本质原理虚函数通过虚函数表vtable实现动态绑定确保调用的是对象实际类型派生类的析构函数而非指针类型基类的析构函数。使用场景仅当类作为基类且可能被多态使用时才需要将析构函数设为虚函数非基类无需额外设置。