C++ 中的重载,隐藏与重写

首先,  这里重载指 overwrite, 即普通的函数重载, 重写指的是 override, 即虚函数的重写,隐藏:这里特指派生类成员隐藏基类成员的情况

 

一、重载

说到重载首先得说名字查找, C++在处理函数调用的方式是这样的: 首先根据函数的名字在当前作用于进行名字查找,如果没找到,则往上层作用域接着找,直到找到为止,但是对于函数参数类型为类类型时,还会将该类所在的命名空间加入查找的范围。

这里要说的重点是,一旦编译器在某个作用域找到了该名字,就不会往上继续找了,比如如下代码:

namespace out {
    void f(int) {}
    namespace in {
        void f(int, int) {}
        void g () { f(1); }    //编译错误,参数不匹配
    }
}

这里在定义 g() 时,调用了外层作用域的 f (int) ,然而编译器在内层作用域就找到了 f 这个名字,于是就看不见外层作用域的重载了。所以除非我们显式的指定作用域名字或者使用using声明,否则会一直报错。

 

二、隐藏

其实上面的情况已经说明了内层作用域会"隐藏" (这里的隐藏并不是指派生类隐藏基类成员的情况) 外层作用域的名字这一情况了,为了避免这种情况发生,我们通常使用using 声明来声明外层作用域中的其他重载,如:

namespace out {
    void f(int) { cout << "out" << endl; }
    namespace in {
        using out::f;
        void f(int, int) {}
        void f(int) { cout << "in" << endl; }
        void g () { f(1); }       //出错,对f(1)的调用有二义性
    }
}

int main() {
    out::in::g();
    return 0;
}

由于引入了外层的 f 的同时,内层定义的重载和外层的 f 的函数签名重合了,所以调用二义性。但是,如果 f 分别是子类和基类的成员函数, 则不会产生二义性调用,因为派生类会隐藏基类的成员。


1) 先看第一种情况,我们知道派生类的作用域是处于基类的包围之中的,这里就重现一下类版本的"隐藏"(其实这里的隐藏并不算类中的隐藏) :

void f() { cout << "global: f()" << endl; } //全局作用域的 f

class base {
public:
    void f () { cout << "base: f() " << endl; }
};

class drived : public base {
public:
    void f(int, int) { cout << "drived: f(int, int) " << endl; }   // 这里"隐藏"了基类的 f 这个名字,所以接下来派生类将看不见基类的任何f的重载
    void g() { f(); }           //由于基类的 f 这个名字被隐藏了,而派生类中有 f 这个名字,所以名字查找在派生类作用域就停止了, 所以发生编译错误:找不到匹配的函数调用
};

 

 

2) 上面说到,为了避免这种情况,使用using 声明, 但是这样可能引入签名相同的两个函数,我们来看看类类型中的这种情况:

void x();
void f() { cout << "global: f()" << endl; } //全局作用域的 f

class base {
public:
    void f () { cout << "base: f() " << endl; }
    void f (int, int, int) { cout << "base: f(int, int, int);" << endl; }
};

class drived : public base {
    friend void x(); //将x声明为友元
public:
    using base::f;  //这里其实还隐藏一层含义:由于 using 声明在public 下面,所以基类的 f 是public的,如果using 声明在 private下,则基类的 f 会变成私有的

private:

    void f() { cout << "drived: f()" << endl; }        //将 f 声明为私有的, 由于使用了using 声明,只隐藏了base::f()这一个重载版本
    void f(int, int) { cout << "drived: f(int, int) " << endl; }
    void g() { f(); }          //也正是由于隐藏了base::f(), 没有出现调用二义性
};

void x() {
    drived d;
    d.f();            //输出 drived f, 基类的 f() 被隐藏
    d.g();            //输出 drived f, base::f() 被隐藏
}

int main() {
    drived d;
//    d.f();       // 错误,派生类隐藏了base::f(), drived::f() 是私有的,除非显式的调用 d.base::f();
    d.f(1,1,1);  // 由于子类只隐藏了base::f(), 所以调用正确, 但是如果把using 声明置于private下,则依旧会缺少访问权限
    x();
    return 0;
}

 

总结一下就是:

1, 在类中,隐藏的只是某个固定的重载版本,隐藏所有的重载的情况是由于C++名字查找的机制所导致的

2,可以使用using 声明来避免由于名字查找而丢失的基类的函数重载候选集,同时using声明的位置决定了引入的名字是public还是private

 

三、重写

这里就没有太多可以讲的了,就是只有返回值,参数类型和个数,名字,以及virtual标记全部都满足的时候才是重写,其他一律按照重载和隐藏处理就可以了,注意如果返回的是实际类型的指针的话,只要指针类型可以转换,是允许不同的。

如 base 的虚函数返回 base*, drived 返回 dirved*, 只要 drived 向 base 的转换是允许的(也就是非私有继承啦,魂淡), 这种返回值的不同也是被允许的。

posted on 2016-03-15 10:41  远近闻名的学渣  阅读(290)  评论(0)    收藏  举报

导航