C++中重载、覆盖以及隐藏的区别

C++中重载、覆盖以及隐藏的区别

C++中重载、覆盖以及隐藏是经常让人混淆的三个概念。
1、重载
有两个或多个函数名相同的函数,但是函数的形参列表不同。在调用相同函数名的函数时,根据形参列表确定到底该调用哪一个函数。

重载是C++提供的一种灵活运用的操作,它不止可以用到函数,还可以运用到运算符中进行重载。

重载的函数必须位于同一个命名空间中,在类的层次上来看,每个类就相当于是一个命名空间,所以重载只能发生在同一个类中

成员函数被重载的特征:

(1)相同的范围(在同一个类中);

(2)函数名字相同;

(3)参数不同;

(4)virtual 关键字可有可无。

举例:

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;}
};

int main()
{
    Derived d;

    d.f(42);
    d.f(3.14f);
 
    return 0;
}

运行结果:


2、覆盖
在基类中定义了一个虚拟函数,然后在派生类中又定义了一个同名同参数同返回类型的函数,这就是覆盖了。

在派生类对象上直接调用这个函数名,只会调用派生类中的那个。

覆盖是发生在不同类之间(基类和派生类)。
覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。

举例:
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:
    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;
}

运行结果:


注意:此时如果将基类中函数g()前的virtual去掉,会是什么结果?答案是,这种情况不会构成覆盖。


2、覆盖

既然是和虚拟函数挂钩,说明了这个是一个多态支持的特性,所谓的覆盖指的是用基类对象的指针 或者引用时访问虚拟函数的时候会根据实际

的类型决定所调用的函数,因此此时派生类的成员函数可以"覆盖"掉基类的成员函数注意唯有同名且参数相同还有带有virtual关键字并且分别

在派生类和基类的函数才能构成虚拟函数,这个也是派生类的重要特征而且,由于是和多态挂钩的,所以只有在使用类对象指针或者引用的时候

才能使用上总之一句话:覆盖函数都是虚函数,反之不然~~ (如果基类和继承类的函数名称,产生返回值都是一样的[如果返回值不同应该无法

编译],如果基类用到了virtual,那么无论继承类的实现中是否加入virtual 这个keyword ,还是会构成覆盖的关系)。

多态:
在基类中定义了一个虚拟函数,然后在派生类中又定义一个同名,同参数表的函数,这就是多态。多态是这3种

情况中唯一采用动态绑定技术的一种情况。也就是说,通过一个基类指针来操作对象,如果对象是基类对象,

就会调用基类中的那个函数,如果对象实际是派生类对象,就会调用派声类中的那个函数,调用哪个函数并不

由函数的参数表决定,而是由函数的实际类型决定。
举例:

class A
{
    public:
    virtual void ShowMessage(){cout<<"Hello,This is A.\n";} // 分析加不加virtual的区别
};

class B:public A
{
    public:
    void ShowMessage(){cout<<"Hello,This is B.\n";}
};

int main()
{
    A* p;

    p=new A();
    p->ShowMessage();

    p=new B();
    p->ShowMessage();

    return 0;
}

运行结果:


3、隐藏
的是派生类的成员函数隐藏了基类函数的成员函数.隐藏一词可以这么理解:在调用一个类的成员函数的时候,编译器会沿着类的继承链逐级

的向上查找函数的定 ,如果找到了那么就停止查找了,所以如果一个派生类和一个基类都有同一个同名(暂且不论参数是否相同)的函数,而编

译器最终选择了在派生类中的函数,么我们就说这个派生类的成员函数"隐藏"了基类的成员函数,也就是说它阻止了编译器继续向上查找函数

的定义回到隐藏的定义中,前面已经说了有virtual关键字并且分别位于派生类和基类的同名,同参数函数构成覆盖的关系,因此隐藏的关系只有

如下的可能:
1)必须分别位于派生类和基类中 ;
2)必须同名;
3)参数不同的时候本身已经不构成覆盖关系了,所以此时是否是virtual函数已经不重要了。
当参数相同的时候就要看时候有virtual关键字了,有的话就是覆盖关系,没有的时候就是隐藏关系了很多人分辨不清隐藏和覆盖的区别,因为他们

都是发生在基类和派生类之中的.但是它们之间最为重要的区别就是覆盖的函数是多态的,是存在于vtbl之中的函数才能构成"覆盖"的关系,

隐藏的函数都是一般的函数,不支持多态,在编译阶段就已经确定下来了。
直接使用类对象才会出现隐藏。
举例:

struct foo
{
    void func(int x)
    {
        cout <<"foo::func(int)"<<endl;
    }
    void func(float x)
    {
        cout <<"foo::func(float)"<<endl;
    }
    void func(double x)
    {
        cout <<"foo::func(double)"<<endl;
    }
};

struct a
{
    void func(int x)
    {
        cout <<"struct a :: fun(int x)"<<endl;
    }
};

struct b : public a
{
    void func(float x)
    {
        cout <<"struct b::fun(float x)"<<endl;
    }
};

struct c : public b
{
    //using a::func;
    //using b::func;
    void func(double x)
    {
        cout <<"struct c::func(double x)"<<endl;
    }
};

int main()
{
    foo  fo;
    fo.func(1);
    fo.func((float)1);

    c  oc; // 直接根据类对象来调用
    oc.func(1);
    oc.func((float)1);
}

注意:怎么样取消隐藏?可以申明出基类命名空间(每个类都相当于是一个独立的命名空间),这样隐藏就变成了重载,因为不同的函数成

为了一个命名空间(注意,此时基类和派生类中的同名函数的参数一定要不同)。

struct foo
{
    void func(int x)
    {
        cout <<"foo::func(int)"<<endl;
    }
    void func(float x)
    {
        cout <<"foo::func(float)"<<endl;
    }
    void func(double x)
    {
        cout <<"foo::func(double)"<<endl;
    }
};

struct a
{
    void func(int x)
    {
        cout <<"struct a::fun(int x)"<<endl;
    }
};

struct b : public a
{
    void func(float x)
    {
        cout <<"struct b::fun(float x)"<<endl;
    }
};

struct c : public b
{
    using a::func;
    using b::func;
    void func(double x)
    {
        cout <<"struct c::func(double x)"<<endl;
    }
};

int main()
{
    foo  fo;
    fo.func(1);
    fo.func((float)1);

    c  oc;
    oc.func(1);
    oc.func((float)1);
}

运行结果:


总结:在基类和派生类中,如果直接使用类对象进行操作,容易引起隐藏。当使用指针调用类的操作时,如果该成员函数已用virtual进行

了声明,那么会发生覆盖,这时候具体的操作应该依据指针所指的对象类型;反之,当没有virtual时,不会发生覆盖,这时候具体的操作

要依据指针的类型来进行判断。
举例:

class Base
{
    public:
    virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
    void g(float x){ cout << "Base::g(float) " << x << endl; } // 加不加virtual没有关系,派生类与基类该函数的参数不同
    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; }
    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(定义为虚函数virtual)
    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(未定义为虚函数virtual)
    pb->g(3.14f); // Base::g(float) 3.14
    pd->g(3.14f); // Derived::g(int) 3 (surprise!)
    // Bad : behavior depends on type of the pointer
    pb->h(3.14f); // Base::h(float) 3.14 (surprise!)(未定义为虚函数virtual)
    pd->h(3.14f); // Derived::h(float) 3.14

    // 隐藏基类的操作
    d.f(3.14f);
    d.g(3.14f);
    d.h(3.14f);

    return 0;
}

运行结果:


最后,再提醒一个值得注意的地方,经常遇到的虚析构函数,实质上就是覆盖操作。虚析构函数(基类的析构函数前+virtual)的使用主要

是为了防止内存的泄露,详情见我上篇博文《深度解析构造函数和析构函数》(http://blog.csdn.net/shenbo2030/article/details/44

588203)。
举例:

class ClxBase
{
public:
    ClxBase() {};
    virtual ~ClxBase() {cout << "Output from the destructor of class ClxBase!" << endl;} // 加不加virtual的区别

    virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; } // 加不加virtual的区别
};

class ClxDerived : public ClxBase
{
public:
    ClxDerived() {};
    ~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; }

    void DoSomething() { cout << "Do something in class ClxDerived!" << endl; }
};

int main()
{
    //ClxBase Test;
    ClxBase *pTest = new ClxDerived;
    pTest->DoSomething();
    delete pTest;

    return 0;
}

运行结果:


如果基类中析构函数前不加virtual,则运行结果如下(派生类没有进行释放),此时会造成内存泄露,产生无法预测的错误。





发布了25 篇原创文章 · 获赞 18 · 访问量 16万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章