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

简介:

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/archive/2012/08/09/2630751.html,如需转载请自行联系原作者

相关文章
|
5月前
|
编译器 C++
C++进阶之路:何为运算符重载、赋值运算符重载与前后置++重载(类与对象_中篇)
C++进阶之路:何为运算符重载、赋值运算符重载与前后置++重载(类与对象_中篇)
47 1
|
2月前
|
C++
C++(十九)new/delete 重载
本文介绍了C++中`operator new/delete`重载的使用方法,并通过示例代码展示了如何自定义内存分配与释放的行为。重载`new`和`delete`可以实现内存的精细控制,而`new[]`和`delete[]`则用于处理数组的内存管理。不当使用可能导致内存泄漏或错误释放。
|
4月前
|
NoSQL 编译器 Redis
c++开发redis module问题之如果Redis加载了多个C++编写的模块,并且它们都重载了operator new,会有什么影响
c++开发redis module问题之如果Redis加载了多个C++编写的模块,并且它们都重载了operator new,会有什么影响
|
4月前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `&lt;`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
4月前
|
NoSQL Redis C++
c++开发redis module问题之避免多个C++模块之间因重载operator new而产生的冲突,如何解决
c++开发redis module问题之避免多个C++模块之间因重载operator new而产生的冲突,如何解决
|
5月前
|
安全 程序员 C++
C++一分钟之-重载运算符
【6月更文挑战第21天】C++的运算符重载让程序员能为自定义类型定制运算符行为,增强代码表达力。但要注意清晰性、优先级和返回类型。遵循运算符原有意义,充分测试,并用注释解释非直观设计。示例展示了如何为复数类重载`+`运算符。避免重载内置类型,注意结合性,且慎用隐式转换。重载应提升可读性而非复杂化代码。
45 2
|
5月前
|
C++
C++函数的默认参数、占位符、重载
C++函数的默认参数、占位符、重载
|
5月前
|
Unix 编译器 C语言
【C++航海王:追寻罗杰的编程之路】关键字、命名空间、输入输出、缺省、重载汇总
【C++航海王:追寻罗杰的编程之路】关键字、命名空间、输入输出、缺省、重载汇总
27 0
|
6月前
|
程序员 编译器 C++
c++重载运算符和重载函数
c++重载运算符和重载函数
35 1
|
5月前
|
编译器 C++
C++运算符的重载
C++运算符的重载
29 0