这是在面试张江的某个公司的时候被问到的问题,当时并不知道,后来在看别人博客的时候发现的。
#include <iostream>
using namespace std;
class A
{
public:
void foo(){cout << "FOOA" << endl;}
};
class B:public A
{
public:
void foo(int x){cout << "FOOB" << endl;}
};
int main(int argc, char const *argv[])
{
B b;
b.foo();
return 0;
}
上面的代码,B继承A,A中有一个参数为空的foo函数,B中有一个参数为int的foo函数,然后让B来调用A中的foo,会报错:
./test.cpp:20:4: error: too few arguments to function call, expected 1, have 0; did you mean 'A::foo'? b.foo(); ~~^~~ A::foo ./test.cpp:8:7: note: 'A::foo' declared here void foo(){cout << "FOOA" << endl;} ^ 1 error generated.
也就是说B中foo函数的定义隐藏掉了A中foo的定义,尽管两者函数签名不同,但是还是不能这样来调用A中foo。当然,如果写成以下这样,是可以正常调用的:
int main(int argc, char const *argv[])
{
B b;
b.A::foo();
return 0;
}
C++之所以这么设计,可能是有一定的考量的,比如说我们定义一个A中foo参数为void *
,然后在继承它的类B中有一个函数foo参数为int
,这种情况下在调用B中的foo,参数为NULL
的时候,会发生什么呢,到底NULL
被解释为0还是一个指针。这个例子是在StackOverflow的一个问题中看到的,当然现在可以通过将NULL替代成nullptr来实现,但是早期是没有办法的。
可能就是出于这样的考量,c++选择了这种设计。不过其实我觉得这个说法是不对的,因为下面的代码同样会有这样的问题:
#include <iostream>
using namespace std;
void foo(void *p)
{
cout << "AAA\n";
}
void foo(int p)
{
cout << "BBB\n";
}
int main(int argc, char const *argv[])
{
foo(NULL);
return 0;
}
这样编译器会报错是
./test.cpp:17:2: error: call to 'foo' is ambiguous foo(NULL); ^~~ ./test.cpp:5:6: note: candidate function void foo(void *p) ^ ./test.cpp:10:6: note: candidate function void foo(int p) ^ 1 error generated.
证明如果是如例子中所讲,这样应该也是不允许的,但是却是报错有二义性,那么上面那种子类与父类重载隐藏的问题应该也可以通过这种报二义性来处理,这样可以使得在绝大多数情况下子类与父类之间可以实现函数的重载。
结论
所以结论就是,不知道c++是出于什么目的,总之就是这样的设定,在子类与父类的作用域中出现的同名不同参的函数,父类中此类函数会被隐藏。
同时,父类中与子类同名的变量也会像这样处理,不会报错。比如
#include <iostream>
using namespace std;
class A
{
public:
A(){cout << "Construct A\n";}
~A(){cout << "Destruct A\n";}
int a;
};
class B
{
public:
B(){cout << "Construct B\n";}
~B(){cout << "Destruct B\n";}
int c;
};
class C: public A, public B
{
public:
C(){cout << "Construct C\n";}
~C(){cout << "Destruct C\n";}
int c;
};
int main(int argc, char const *argv[])
{
C c;
c.B::c = 1;
c.c = 2;
cout << sizeof(c) << endl;
cout << c.B::c << endl;
return 0;
}
注意就好。