• C++
  • 【C++11】final, override,重载,重写覆盖,重写隐藏

  • @ 2025-8-11 13:15:49

2 条评论

  • @ 2025-8-11 13:18:18

    【C++11】final, override,重载,重写覆盖,重写隐藏学习笔记教程

    一、C++11新增关键字:final与override

    在C++11标准中,引入了finaloverride两个关键字,用于增强类继承和虚函数使用的安全性与可读性。

    1. final关键字

    final主要用于限制类的继承或虚函数的重写,有两种使用场景:

    • 修饰类:被final修饰的类不能被继承。

      class Base final {  // Base类被final修饰,无法被继承
      public:
          void func() {}
      };
      
      // class Derived : public Base {};  // 编译错误,Base是final类
      
    • 修饰虚函数:被final修饰的虚函数,派生类不能重写该函数。

      class Base {
      public:
          virtual void func() final {}  // 虚函数被final修饰
      };
      
      class Derived : public Base {
      public:
          // void func() {}  // 编译错误,无法重写final虚函数
      };
      

    2. override关键字

    override用于显式声明派生类中的函数是重写基类的虚函数,主要作用是检查重写的正确性(如函数名、参数列表、返回值是否与基类虚函数一致)。

    • 正确用法

      class Base {
      public:
          virtual void func(int x) {}  // 基类虚函数
      };
      
      class Derived : public Base {
      public:
          void func(int x) override {}  // 正确重写,编译通过
      };
      
    • 错误示例(参数不一致):

      class Derived : public Base {
      public:
          // void func(double x) override {}  // 编译错误,参数与基类不一致
      };
      
    • 错误示例(非虚函数重写):

      class Base {
      public:
          void func() {}  // 非虚函数
      };
      
      class Derived : public Base {
      public:
          // void func() override {}  // 编译错误,基类中无对应虚函数
      };
      

    二、重载(Overload)

    重载指同一作用域内,函数名相同但参数列表(参数类型、个数、顺序)不同的函数,与返回值类型无关。

    特点:

    • 函数名相同,参数列表不同;
    • 必须在同一类中或同一作用域;
    • 与是否为虚函数无关;
    • 调用时根据参数自动匹配。

    示例:

    class Math {
    public:
        // 重载:参数个数不同
        int add(int a, int b) { return a + b; }
        int add(int a, int b, int c) { return a + b + c; }
    
        // 重载:参数类型不同
        double add(double a, double b) { return a + b; }
    
        // 重载:参数顺序不同
        void print(int a, double b) { cout << a << " " << b << endl; }
        void print(double a, int b) { cout << a << " " << b << endl; }
    };
    

    三、重写(覆盖,Override)

    重写指派生类重新定义基类中虚函数的实现,要求函数名、参数列表、返回值完全一致(协变返回类型除外)。

    特点:

    • 基类函数必须是虚函数(用virtual修饰);
    • 派生类函数与基类函数函数名、参数列表、返回值完全一致;
    • 派生类函数访问权限可以比基类更宽松(如基类protected,派生类public);
    • 通过基类指针/引用调用时,会根据对象实际类型动态绑定(多态)。

    示例:

    class Animal {
    public:
        virtual void speak() {  // 基类虚函数
            cout << "动物叫" << endl;
        }
    };
    
    class Dog : public Animal {
    public:
        void speak() override {  // 重写基类虚函数
            cout << "汪汪叫" << endl;
        }
    };
    
    int main() {
        Animal* animal = new Dog();
        animal->speak();  // 输出"汪汪叫",动态绑定
        return 0;
    }
    

    四、重写隐藏(名字隐藏,Name Hiding)

    隐藏指派生类中的函数屏蔽了基类中同名函数(无论参数是否相同),导致基类函数在派生类中不可见。

    产生场景:

    1. 派生类函数与基类非虚函数同名(无论参数是否相同);
    2. 派生类函数与基类虚函数同名但参数不同(此时不构成重写,而是隐藏)。

    特点:

    • 基类函数被派生类同名函数“隐藏”,无法直接通过派生类对象调用;
    • 若要调用基类函数,需显式使用基类名::函数名
    • 不涉及多态,调用时根据指针/引用的类型(静态类型)匹配。

    示例1:非虚函数同名隐藏

    class Base {
    public:
        void func(int x) {  // 非虚函数
            cout << "Base: " << x << endl;
        }
    };
    
    class Derived : public Base {
    public:
        void func() {  // 同名函数,隐藏基类func(int)
            cout << "Derived: no param" << endl;
        }
    };
    
    int main() {
        Derived d;
        d.func();  // 正确,调用派生类func()
        // d.func(10);  // 错误,基类func(int)被隐藏
        d.Base::func(10);  // 正确,显式调用基类函数
        return 0;
    }
    

    示例2:虚函数参数不同导致隐藏

    class Base {
    public:
        virtual void func(int x) {  // 虚函数
            cout << "Base: " << x << endl;
        }
    };
    
    class Derived : public Base {
    public:
        void func(double x) {  // 参数不同,不构成重写,隐藏基类func(int)
            cout << "Derived: " << x << endl;
        }
    };
    
    int main() {
        Base* b = new Derived();
        b->func(10);  // 调用基类func(int)(因派生类未重写,静态绑定)
        return 0;
    }
    

    五、重载、重写、隐藏对比表

    特性 重载(Overload) 重写(覆盖,Override) 隐藏(Name Hiding)
    作用域 同一类/同一作用域 基类与派生类
    函数关系 同名不同参 虚函数,同名同参同返回值 同名(参数可同可不同)
    多态性 无(静态绑定) 有(动态绑定) 无(静态绑定)
    调用依据 参数列表 对象实际类型 指针/引用的静态类型
    关键字关联 可配合override检查
    基类函数调用 直接调用 通过基类指针/引用调用基类版本 需显式用基类名::函数名

    六、总结

    1. final限制继承或虚函数重写,override显式声明重写并检查正确性,二者均用于增强代码安全性;
    2. 重载是同一作用域的同名不同参函数,与继承无关;
    3. 重写是派生类重写基类虚函数(同名同参),支持多态;
    4. 隐藏是派生类同名函数屏蔽基类函数,需显式调用基类版本。

    掌握这些概念有助于避免继承中的常见错误,写出更清晰、安全的C++代码。

    • @ 2025-8-11 13:18:07

      重载、重写覆盖和重写隐藏的具体示例

      一、重载(Overload)示例

      重载是指在同一作用域内,函数名相同但参数列表不同的函数集合:

      #include <iostream>
      using namespace std;
      
      class Calculator {
      public:
          // 1. 参数个数不同
          int add(int a, int b) {
              cout << "两个int相加: ";
              return a + b;
          }
          
          int add(int a, int b, int c) {
              cout << "三个int相加: ";
              return a + b + c;
          }
          
          // 2. 参数类型不同
          double add(double a, double b) {
              cout << "两个double相加: ";
              return a + b;
          }
          
          // 3. 参数顺序不同
          void print(int num, const string& str) {
              cout << "int在前: " << num << " " << str << endl;
          }
          
          void print(const string& str, int num) {
              cout << "string在前: " << str << " " << num << endl;
          }
      };
      
      int main() {
          Calculator calc;
          
          cout << calc.add(2, 3) << endl;               // 调用add(int, int)
          cout << calc.add(2, 3, 4) << endl;            // 调用add(int, int, int)
          cout << calc.add(2.5, 3.5) << endl;           // 调用add(double, double)
          
          calc.print(10, "hello");                      // 调用print(int, string)
          calc.print("world", 20);                      // 调用print(string, int)
          
          return 0;
      }
      

      输出结果:

      两个int相加: 5
      三个int相加: 9
      两个double相加: 6
      int在前: 10 hello
      string在前: world 20
      

      二、重写覆盖(Override)示例

      重写覆盖是指派生类重新实现基类中的虚函数,函数签名完全一致:

      #include <iostream>
      using namespace std;
      
      // 基类
      class Shape {
      public:
          // 虚函数,可被重写
          virtual void draw() {
              cout << "绘制基本图形" << endl;
          }
          
          virtual double area() {
              return 0.0;
          }
      };
      
      // 派生类Circle
      class Circle : public Shape {
      private:
          double radius;
      public:
          Circle(double r) : radius(r) {}
          
          // 重写基类的draw函数
          void draw() override {  // 使用override明确表示重写
              cout << "绘制圆形" << endl;
          }
          
          // 重写基类的area函数
          double area() override {  // 使用override明确表示重写
              return 3.14159 * radius * radius;
          }
      };
      
      // 派生类Rectangle
      class Rectangle : public Shape {
      private:
          double width, height;
      public:
          Rectangle(double w, double h) : width(w), height(h) {}
          
          // 重写基类的draw函数
          void draw() override {
              cout << "绘制矩形" << endl;
          }
          
          // 重写基类的area函数
          double area() override {
              return width * height;
          }
      };
      
      int main() {
          Shape* shape1 = new Circle(5.0);
          Shape* shape2 = new Rectangle(4.0, 6.0);
          
          // 多态行为:调用实际对象类型的方法
          shape1->draw();  // 输出"绘制圆形"
          cout << "面积: " << shape1->area() << endl;  // 输出圆形面积
          
          shape2->draw();  // 输出"绘制矩形"
          cout << "面积: " << shape2->area() << endl;  // 输出矩形面积
          
          delete shape1;
          delete shape2;
          return 0;
      }
      

      输出结果:

      绘制圆形
      面积: 78.5397
      绘制矩形
      面积: 24
      

      三、重写隐藏(Name Hiding)示例

      隐藏是指派生类中的函数屏蔽了基类中同名函数,无论参数是否相同:

      示例1:非虚函数同名导致隐藏

      #include <iostream>
      using namespace std;
      
      class Base {
      public:
          void show(int x) {  // 非虚函数
              cout << "Base::show(int): " << x << endl;
          }
          
          void display() {    // 非虚函数
              cout << "Base::display()" << endl;
          }
      };
      
      class Derived : public Base {
      public:
          // 与基类show函数参数不同,导致隐藏
          void show(double x) {
              cout << "Derived::show(double): " << x << endl;
          }
          
          // 与基类display函数参数相同,同样导致隐藏
          void display() {
              cout << "Derived::display()" << endl;
          }
      };
      
      int main() {
          Derived d;
          
          d.show(3.14);      // 调用派生类的show(double)
          // d.show(10);     // 错误!基类的show(int)被隐藏
          
          d.display();       // 调用派生类的display()
          
          // 要调用基类被隐藏的函数,必须显式指定
          d.Base::show(10);  // 显式调用基类的show(int)
          d.Base::display(); // 显式调用基类的display()
          
          return 0;
      }
      

      输出结果:

      Derived::show(double): 3.14
      Derived::display()
      Base::show(int): 10
      Base::display()
      

      示例2:虚函数参数不同导致隐藏(而非重写)

      #include <iostream>
      using namespace std;
      
      class Base {
      public:
          virtual void func(int x) {  // 基类虚函数
              cout << "Base::func(int): " << x << endl;
          }
      };
      
      class Derived : public Base {
      public:
          // 函数名相同但参数不同,不构成重写,而是隐藏
          void func(double x) {
              cout << "Derived::func(double): " << x << endl;
          }
      };
      
      int main() {
          Base* basePtr = new Derived();
          Derived* derivedPtr = new Derived();
          
          // 调用基类版本,因为派生类没有重写func(int)
          basePtr->func(10);      // 输出"Base::func(int): 10"
          
          // 调用派生类版本
          derivedPtr->func(3.14); // 输出"Derived::func(double): 3.14"
          
          // derivedPtr->func(10); // 错误!基类的func(int)被隐藏
          
          // 显式调用基类函数
          derivedPtr->Base::func(10); // 输出"Base::func(int): 10"
          
          delete basePtr;
          delete derivedPtr;
          return 0;
      }
      

      输出结果:

      Base::func(int): 10
      Derived::func(double): 3.14
      Base::func(int): 10
      

      这些示例清晰展示了重载、重写覆盖和重写隐藏的区别,重点在于理解它们的作用域、函数签名要求以及多态行为的差异。

      • 1