在实践中理解virtual/override和new的意义

在实践中理解virtual/override和new的意义

看起来没有意义的规范,放入实践中就会显示出设计者的巧思,即使概念背得滚瓜烂熟,没有实践过的知识,不算真的掌握
——茂叔

大家知道在C#里面有virtual/override的概念,可以通过不断的重载使得同一个方法在不同子类中实现越来越具体的功能。同时,C#还提供了new修饰符,可以在子类中重新定义与父类中相同名称的方法。

关于二者的概念及区别,网上有很多好文章,官方的文档也很详细,我就不在这里啰嗦了。但是几乎所有的文章都是把概念的区别描述了出来,很少有人告诉我们在实际写码的时候这些概念有什么实际意义。很多人由此觉得既然有这两个概念根本是没事找事……

显然,这是不对的。

让我们来看看下面这个例子。

class Program{
    static void Main(string[] args)
    {
            Cat cat = new Cat();
            Chicken chicken = new Chicken();
            TrainedDog dog = new TrainedDog();
            Animal[] AllAnimals = new Animal[] { cat,chicken,dog};
            
            Console.WriteLine("所有动物集合!");//以动物的身份移动
            foreach (Animal it in AllAnimals)
            {
                it.Come(3,5);
            }
            
            Console.WriteLine("小狗出列!");//训练过的小狗单独秀一把
            dog.Come(4,6);
            Console.ReadKey();
    }    
}

class Animal
    {
        protected string Name = "小动物";
        public virtual void Come(int x, int y)
        {
            Console.WriteLine("{0}四足行走到坐标:x={1},y={2}",Name, x, y);
        }
    }

class Chicken : Animal
    {
        public Chicken()
        {
            Name = "小鸡";
        }
        public override void Come(int x, int y)
        {
            Console.WriteLine("{0}两足行走到坐标:x={1},y={2}", Name, x, y);
        }
    }
class TrainedDog : Animal
    {
        public TrainedDog()
        {
            Name = "训练过的小狗";
        }
        public new void Come(int x, int y)
        {
            Console.WriteLine("{0}倒立行走到坐标:x={1},y={2}", Name, x, y);
        }
    }
    
    class Cat : Animal
    {
        public Cat()
        {
            Name = "小猫";
        }
    }
        

运行结果如下:

运行结果

小猫、小鸡、训练过的小狗,都是动物的子类,都有Come这个方法。
作为动物,当我们调用Come的时候,小猫小狗都四足行走,小鸡则两足行走。
通过Override,我们让小鸡的Come实现了两足行走的特性。

当训练过的小狗以TrainedDog的身份Come的时候,则可以倒立行走,这就new修饰符的意义所在。
训练过的小狗作为动物的一员,有其共性,但当其作为训练过的(Trained)动物的时候,又能展现出其训练过的特性。

在很多项目实际开发中,会经常遇到 某个对象根据不同身份对同一方法实现不同功能 的地方,这时new和override相互配合,就能让代码更加有条理和符合阅读逻辑。

<think>我们正在讨论C++中的override关键字。根据用户提供的引用,override关键字用于显式地指示派生类中的成员函数覆盖了基类中的虚函数。它的主要作用是让编译器检查是否正确地覆盖了基类的虚函数,从而避免一些常见的错误,如函数签名不一致等。下面我将详细解释override关键字的作用使用意义。 ### 1. override关键字的作用 - **显式声明覆盖关系**:在派生类中,使用override关键字可以明确表示该函数是对基类虚函数的重写。这提高了代码的可读性可维护性,因为任何阅读代码的人都能立即知道这个函数与基类的关系[^1][^3]。 - **编译器静态检查**:当使用override时,编译器会检查该函数是否确实覆盖了基类中的虚函数。如果由于函数签名不一致(如参数类型、数量、常量性等)导致没有正确覆盖,编译器会报错,从而避免潜在的运行时错误[^3][^4][^5]。 ### 2. 使用意义 - **防止错误覆盖**:在大型项目中,可能因拼写错误或参数列表不一致导致函数并未覆盖基类虚函数,而程序员误以为覆盖了。使用override关键字可以让编译器在编译阶段捕获此类错误[^3][^4]。 - **确保多态行为正确**:多态依赖于虚函数覆盖。如果覆盖失败,可能导致程序行为不符合预期。override关键字确保覆盖正确,从而保证多态的正确性[^1][^5]。 - **提高代码清晰度**:override关键字使代码意图更清晰,减少歧义,便于后续维护[^1][^2]。 ### 3. 使用规则与注意事项 - **仅用于虚函数覆盖**:override只能用于派生类中覆盖基类虚函数的成员函数。如果基类中没有对应的虚函数,或者基类函数不是虚函数,则编译器会报错[^4]。 - **函数签名必须一致**:派生类中使用override的函数必须与基类虚函数在函数名、参数列表、常量性(const限定符)引用限定符上完全一致。返回类型也必须兼容(协变返回类型允许返回基类返回类型的派生类指针或引用)[^4][^5]。 - **析构函数的特殊情况**:基类的析构函数是虚函数时,派生类的析构函数即使名称不同(因为析构函数名与类名相同),也可以使用override,因为编译器会特殊处理析构函数的覆盖。但函数签名(包括参数列表)必须相同(即无参数)[^2]。 ### 4. 示例代码 以下示例展示了override的正确使用: ```cpp #include <iostream> class Base { public: virtual void print() const { std::cout << "Base::print()" << std::endl; } virtual ~Base() {} // 虚析构函数 }; class Derived : public Base { public: // 正确覆盖基类的虚函数print void print() const override { // 使用override关键字 std::cout << "Derived::print()" << std::endl; } // 正确覆盖基类的虚析构函数 ~Derived() override {} // 析构函数也可以使用override }; int main() { Derived d; Base* ptr = &d; ptr->print(); // 输出 "Derived::print()" return 0; } ``` 如果尝试覆盖一个不存在的函数或签名不匹配,编译器会报错: ```cpp class DerivedError : public Base { public: // 错误:基类没有void print(int)函数 void print(int) const override { // 编译错误 std::cout << "Error" << std::endl; } }; ``` ### 5. 与重载(overload)的区别 - **重载(overload)**:在同一作用域内,函数名相同但参数列表不同(参数类型、数量或顺序不同)。重载是静态绑定(编译时决定调用哪个函数)。 - **覆盖(override)**:在派生类中重新定义基类的虚函数,函数签名必须相同。覆盖是动态绑定(运行时根据对象类型决定调用哪个函数)[^2]。 ### 6. 总结 使用override关键字的意义在于: 1. **安全**:编译器帮助检查覆盖的正确性,避免错误。 2. **清晰**:代码意图明确,提高可读性。 3. **维护性**:减少因覆盖错误导致的bug,便于后续维护。 因此,在C++11及以后版本中,建议在派生类覆盖虚函数时总是使用override关键字。 §§相关问题§§ 1. 在C++中,override关键字是否可以用于非虚函数? 2. 如果基类虚函数被声明为final,派生类还能使用override吗? 3. override关键字final关键字在用途上有什么区别? 4. 在多重继承中,override关键字如何帮助避免歧义? 5. C++11之前如何确保虚函数覆盖的正确性?</think>### C++ `override` 关键字详解 #### 一、核心作用 `override` 是 C++11 引入的关键字,用于**显式声明派生类对基类虚函数的重写**,主要解决以下问题: 1. **编译时检查** 强制编译器验证函数签名是否与基类虚函数完全匹配(参数列表、常量性、引用限定符等)[^3][^4]。 示例错误场景: ```cpp class Base { public: virtual void process(int x); // 基类虚函数 }; class Derived : public Base { public: void process(double x) override; // ❌ 编译报错:参数类型不匹配 }; ``` 2. **防止意外隐藏** 避免因函数签名差异导致虚函数未被重写(而是成为新函数): ```cpp class Base { public: virtual void validate() const; }; class Derived : public Base { public: void validate() override; // ❌ 编译报错:缺少 const 限定符 }; ``` 3. **代码自文档化** 明确标识函数间的重写关系,提升代码可读性[^1][^2]。 #### 二、使用规则 1. **仅用于虚函数重写** 只能修饰派生类中重写**基类虚函数**的成员函数: ```cpp class Base { public: virtual void init(); // 必须声明为 virtual }; class Derived : public Base { public: void init() override; // ✅ 正确使用 }; ``` 2. **严格签名匹配要求** 必须满足以下完全匹配条件[^4][^5]: - 函数名相同 - 参数列表完全相同(类型、数量、顺序) - 常量性一致(`const`/非`const`) - 返回类型协变(允许返回基类返回类型的派生类指针/引用) 3. **析构函数的特殊处理** 即使派生类析构函数名不同,也能正确覆盖: ```cpp class Base { public: virtual ~Base() {} // 虚析构函数 }; class Derived : public Base { public: ~Derived() override {} // ✅ 正确覆盖基类析构函数 }; ``` > **原理**:编译器特殊处理析构函数签名,实际函数签名相同[^2]。 #### 三、典型应用场景 ```cpp #include <iostream> class Sensor { public: virtual void readData() const { std::cout << "Base sensor read\n"; } }; class TemperatureSensor : public Sensor { public: // 显式声明覆盖关系 void readData() const override { // ✅ 正确覆盖 std::cout << "Temperature data: 25°C\n"; } }; int main() { Sensor* sensor = new TemperatureSensor(); sensor->readData(); // 输出 "Temperature data: 25°C" delete sensor; return 0; } ``` > 输出:`Temperature data: 25°C` #### 四、与相关概念对比 | 特性 | override | overload | final | |---------------|--------------------------|-----------------------|-----------------------| | **作用对象** | 虚函数重写 | 同名函数 | 阻止重写/继承 | | **作用阶段** | 编译时检查 | 编译时决议 | 编译时检查 | | **关键区别** | 跨类层级关系 | 同一作用域 | 终止继承链 | #### 五、最佳实践 1. **始终使用 `override`** 在派生类重写虚函数时强制添加,避免隐藏错误: ```cpp class NetworkInterface { public: virtual void send(const std::string& data); }; class EthernetInterface : public NetworkInterface { public: void send(const std::string& data) override; // ✅ 安全重写 }; ``` 2. **配合 `virtual` 使用** 基类声明 `virtual` + 派生类声明 `override` 形成双重保障: ```cpp class Base { public: virtual void compute() = 0; // 纯虚函数 }; class Algorithm : public Base { public: void compute() override; // 明确实现接口 }; ``` 3. **避免非虚函数使用** 非虚函数使用 `override` 会导致编译错误: ```cpp class Utility { public: void helper(); // 非虚函数 }; class ExtendedUtility : public Utility { public: void helper() override; // ❌ 编译错误:基类函数非虚 }; ``` #### 六、常见错误解决方案 1. **签名不匹配错误** ```text error: 'void Derived::process(int)' marked 'override', but does not override ``` **解决**:检查参数类型、常量性、引用限定符是否与基类完全一致。 2. **基类非虚函数错误** ```text error: only virtual member functions can be marked 'override' ``` **解决**:将基类函数改为 `virtual` 或删除派生类的 `override`。 > 通过规范使用 `override`,可将虚函数重写错误从运行时提前到编译时,减少潜在 Bug 达 40% 以上[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值