C++的重载(overload)与重写(override)

本文深入解析C++中的重载(overload)与重写(override)概念,对比两者之间的关键区别,包括作用域、参数、virtual关键字的使用等。并通过实例演示隐藏规则及其对派生类和基类函数的影响,强调正确使用虚函数的重要性。

C++的重载(overload)与重写(override)

成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual关键字可有可无。

重写是指派生类函数重写基类函数,是C++的多态的表现,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual关键字。

示例中,函数Base::f(int)与Base::f(float)相互重载,而Base::g(void)被Derived::g(void)重写。

 

#include <iostream>
using namespace std;

class Base
{
public:
    void f(int x){ cout << "Base::f(int) " << x << endl; }
    void f(float x){ cout << "Base::f(float) " << x << endl; }
    virtual void g(void){ cout << "Base::g(void)" << endl;}
};

class Derived : public Base
{
public:
    virtual void g(void){ cout << "Derived::g(void)" << endl;}
};

int main()
{
    Derived  d;
    Base *pb = &d;
    pb->f(42);        // Base::f(int) 42
    pb->f(3.14f);     // Base::f(float) 3.14
    pb->g();          // Derived::g(void)

    return 0;
}

 

令人迷惑的隐藏规则

本来仅仅区别重载与重写并不算困难,但是C++的隐藏规则(遮蔽现象)使问题复杂性陡然增加。这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏。
这种隐藏规则,不仅仅是表现在对成员函数上,对同名的data member也是如此。

示例程序中:
(1)函数Derived::f(float)重写了Base::f(float)。
(2)函数Derived::g(int)隐藏了Base::g(float)。
(3)函数Derived::h(float)隐藏了Base::h(float)。

 

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
    virtual void g(float x){ cout << "Base::g(float) " << x << endl; }
    void h(float x){ cout << "Base::h(float) " << x << endl; }
};

class Derived : public Base
{
public:
    virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
    virtual void g(int x){ cout << "Derived::g(int) " << x << endl; }
    void h(float x){ cout << "Derived::h(float) " << x << endl; }
};

int main()
{
    Derived  d;
    Base *pb = &d;
    Derived *pd = &d;

    // Good : behavior depends solely on type of the object
    pb->f(3.14f); // Derived::f(float) 3.14
    pd->f(3.14f); // Derived::f(float) 3.14

    // Bad : behavior depends on type of the pointer
    pb->g(3.14f); // Base::g(float) 3.14 (surprise!)
    pd->g(3.14f); // Derived::g(int) 3

    // Bad : behavior depends on type of the pointer
    pb->h(3.14f); // Base::h(float) 3.14  (surprise!)
    pd->h(3.14f); // Derived::h(float) 3.14

    return 0;
}

 

另一个关于虚函数很微妙的错误情况:参数相同,但是基类的函数是const的,派生类的函数却不是。

 

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
};

class Derived : public Base
{
public:
    virtual void f(float x) const { cout << "Derived::f(float) " << x << endl; }
};

int main()
{
    Derived  d;
    Base *pb = &d;
    Derived *pd = &d;

    // Bad : behavior depends solely on type of the object
    pb->f(3.14f); // Base::f(float) 3.14
    pd->f(3.14f); // Derived::f(float) 3.14

    return 0;
}

 

(1)一个函数在基类申明一个virtual,那么在所有的派生类都是是virtual的。
(2)一个函数在基类为普通函数,在派生类定义为virtual的函数称为越位,函数行为依赖于指针/引用的类型,而不是实际对象的类型。

 

#include<iostream>
using namespace std;

class Base
{
public:
    void f(){ cout << "Base::f() " << endl; }
    virtual void g(){ cout << "Base::g() " << endl; }
};

class Derived : public Base
{
public:
    virtual void f(){ cout << "Derived::f() " << endl; }
    void g(){ cout << "Derived::g() " << endl; }
};

class VirtualDerived : virtual public Base
{
public:
    void f(){ cout << "VirtualDerived::f() " << endl; }
    void g(){ cout << "VirtualDerived::g() " << endl; }
};

int main()
{
    Base *d = new Derived;
    Base *vd = new VirtualDerived;

    d->f(); // Base::f() Bad behavior
    d->g(); // Derived::g()

    vd->f(); // Base::f() Bad behavior
    vd->g(); // VirtualDerived::g()

    delete d;
    delete vd;

    return 0;
}

 

《Effective C++》条款: 决不要重新定义继承而来的非虚函数。说明了不能重新定义继承而来的非虚函数的理论依据是什么。
以下摘自《Effective C++》:
公有继承的含义是 "是一个","在一个类中声明一个非虚函数实际上为这个类建立了一种特殊性上的不变性"。如果将这些分析套用到类B、类D和非虚成员函数B::mf,那么:

(1)适用于B对象的一切也适用于D对象,因为每个D的对象 "是一个" B的对象。
(2)B的子类必须同时继承mf的接口和实现,因为mf在B中是非虚函数。

那么,如果D重新定义了mf,设计中就会产生矛盾。如果D真的需要实现和B不同的mf,而且每个B的对象(无论怎么特殊)也真的要使用B实现的mf,那么每个D将不 "是一个" B。这种情况下,D不能从B公有继承。相反,如果D真的必须从B公有继承,而且D真的需要和B不同的mf的实现,那么,mf就没有为B反映出特殊性上的不变性。这种情况下,mf应该是虚函数。最后,如果每个D真的 "是一个" B,并且如果mf真的为B建立了特殊性上的不变性,那么,D实际上就不需要重新定义mf,也就决不能这样做。

不管采用上面的哪一种论据都可以得出这样的结论:任何条件下都要禁止重新定义继承而来的非虚函数。

 

作者:阿凡卢

出处:http://www.cnblogs.com/luxiaoxun/

### C++ 中函数重载重写的区别 #### 定义基本概念 函数重载Overload)指的是在同一作用域内定义多个具有相同名称但不同参数列表的函数。这种机制允许开发者通过不同的参数组合调用同一个函数名,从而提高代码的可读性和灵活性[^3]。 而函数重写Override),也称为覆盖,主要发生在继承关系中。当派生类提供了一个其基类中虚函数签名完全一致的新实现时,则认为该函数被重写了。这通常是用来支持动态绑定或多态行为的关键技术之一[^2]。 --- #### 主要区别对比表 | **特性** | **函数重载 (Overload)** | **函数重写 (Override)** | |---------------------|------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------| | **发生位置** | 同一作用域内 | 继承层次结构之间 | | **关键字需求** | 不需要特殊关键字 | 需要在基类中使用 `virtual` 关键字声明,并推荐在派生类中使用 `override` 显式标记 | | **参数列表要求** | 参数列表必须有所不同 | 参数列表必须严格匹配 | | **返回类型要求** | 返回类型可以不同 | 返回类型通常需保持一致,但在协变返回类型的条件下也可以有所变化 | | **目的** | 提供多种功能接口,使同一函数名适用于不同类型的数据 | 实现多态性,允许子类自定义父类的功能 | --- #### 示例代码展示 ##### 函数重载示例 以下是关于如何利用函数重载来适应不同输入的例子: ```cpp #include <iostream> using namespace std; class Example { public: void display(int value) { cout << "Integer Value: " << value << endl; } void display(double value) { cout << "Double Value: " << value << endl; } }; int main() { Example e; e.display(10); // 调用第一个版本 e.display(10.5); // 调用第二个版本 return 0; } ``` 上述例子展示了两个名为 `display` 的成员函数分别接受整型和浮点型作为其唯一参数的情况[^3]。 --- ##### 函数重写示例 下面是一个典型的涉及虚拟函数以及重写的案例说明: ```cpp #include <iostream> using namespace std; // 基类 class Animal { public: virtual void sound() { cout << "Animal makes a sound" << endl; } // 声明为虚函数 }; // 派生类 Dog class Dog : public Animal { public: void sound() override { cout << "Dog barks" << endl; } // 使用 override 标记重写 }; // 派生类 Cat class Cat : public Animal { public: void sound() override { cout << "Cat meows" << endl; } // 使用 override 标记重写 }; int main() { Animal* myAnimal = new Dog(); myAnimal->sound(); // 输出 “Dog barks” delete myAnimal; myAnimal = new Cat(); myAnimal->sound(); // 输出 “Cat meows” delete myAnimal; return 0; } ``` 这里强调了只有当基类中的方法被指定成 `virtual` 类型之后,在衍生出来的类别里头对该方法所做的修改才会生效并体现出来[^2]。 --- ### 总结 综上所述,函数重载主要用于扩展单一命名下的操作多样性;而函数重写则是面向对象编程的核心部分——它实现了运行时期间的决策制定过程即所谓的“后期绑定”,进而达成真正的多态效果[^1]^。 --- 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值