这一章的内容由下面一个例子引出:
namespace A
{
struct X;
struct Y;
void f(int);
void g(x);
}
namespace B
{
void f(int i)
{
f(i); // which f
}
void g(A::X x)
{
g(x); // which g
}
void h(A::Y y)
{
h(y); //which h
}
}
下面注意回答以上问题:
1. 很好回答,调用的是B中的f,也就是说调用的是自己。
2. 这里的调用时有歧义的,即这里需要明确指出调用的是A中的g还是B中的g。这个地方,大部分人可能会由疑问,这里难道不是该调用B中的g吗,为什么会和A中的g有歧义呢?为了解释这个问题,我们引出Koenig Lookup的概念:
如果你提供给一个函数的参数一个类型(在这里x具有类型A::X),那么编译器就会到相应的命名空间(这里是A)中去查找匹配的函数。
在这里,我们指定了x的类型A::X,所以编译器除了在默认的命名空间查找匹配的函数外,还会到A的命名空间中去查找相匹配的函数,所以这里就产生了歧义。
那么,问题是编译器为什么要这样做呢?
首先我们对类的定义如下:
一个类描述了一个数据的集合,和那些在这些数据上进行操作的函数。
首先回答下面一个问题:
一个类里有什么,或者说什么事类和接口的一部分?
很明显,类的成员变量、成员函数、静态函数等都是类和接口的一部分。
考虑下面一段代码:
//*** Example 1(a)
class X { /*...*/ }
/*...*/
void f( const X&);
//*** Example 1 (b)
class X
{
/*...*/
public:
void f() const;
}
认真思考一下上面两个f的区别,你会发现,除了一个是显示的用到X,一个是隐式的传一个指针外,这两个函数并没有任何的区别。所以,如果说第二个f算作是X的一部分的话,那么第一个f应该也算作X的一部分。
下面给出属于X的类的定义:
1. 用到了类X
2. 和类X在同一个文件或者同一个命名空间中。
只要满足以上这两个条件的函数,都可以看做是类X的一部分。
而编译器在看到满足以上两个条件的函数时,就会把此类和此函数之间建立一个关系。这里分两种情况:一种情况是此函数是类的成员函数,那么此类和此函数之间的关系就会强点儿;另一种情况是,此类不是类的成员函数,但此函数和类在同一个文件或者同一个命名空间中,那么此类和此函数之间的关系就会弱点。而编译器在选择用哪个函数时,肯定会选择关系强一点的那个函数。
而也正是因为类和函数之间的这种关系,C++才会利用Koenig Lookup 法则去匹配函数。
看下面这个例子:
//*** Example
namespace NS
{
class T {}
}
void f( NS::T); //global function
int main()
{
NS::T param;
f(param);
}
上面这个例子很明显,会调用全局的f函数。接着看下面这个例子:
namespace NS
{
class T{ };
void f(T);
}
void f(NS::T);
int main()
{
NS::T param;
f(param);
}
现在,很遗憾,这里的f就会出现歧义,因为f同时在NS命名空间和全局存在,所以产生了歧义。接着看下面一个例子:
namespace A
{
class x{};
void f(X);
}
namespace B
{
void f(A::X);
void g(A::X param)
{
f(param);
}
}
同样,以上代码也会产生歧义,因为f同时在A和B命名空间中存在,所以会产生歧义。但下面的例子会是例外:
namespace A
{
class X{};
void f(X);
}
class B
{
void f(A::X);
void g(A::X param)
{
f(param);
}
}
大家一眼看上去可能会说,这个代码和上面的有什么区别吗,不应该也有歧义吗?其实区别就在于这里的B是class而不是namespace,而f是作为一个成员函数存在的,而成员函数的优先级相比于命名空间中的是要高的,所以这里并没有歧义,而是会调用B::f(A::X)。
另外,要提到的是,我们在写类似于下面这种语句时
std::cout << i << std::endl;
我们并没有指明operator<<的命名空间(operator<<在std中),也没有写类似于using std::operator<<的语句,那怎么就通过编译了呢,其实这里用到的也正是Koenig Lookup 法则。
最后,我们再来说一下有关函数隐藏的知识,大家都知道如果在一个类的子类里声明了一个父类里的同名函数,但它们的参数是不一样的,那么子类里的函数就会隐藏掉父类里的函数,若是想调用父类里的同名函数,需要显示的把父类的函数引入到命名空间。为什么会这样呢?大家也许会问,既然它们的参数不同,那么在调用的时候应该能准确找到父类中的相应函数啊。其实这就涉及到了C++在寻找函数时所采用的一个规则,首先它会根据名称(这个时候不会考虑访问权限或者参数的问题)在自己的类里去找,找到后,它就不会继续寻找了。然后它再检查函数的访问权限、参数能否转换等问题。正是由于这个查找规则,才导致了以上的问题。
综上所述,其实就三个关键点:
要知道Koenig Lookup 法则,并知道此法则应用的两个条件:
(1)函数用到了类
(2)函数和类在同一个文件提供,或者在同一个命名空间中
只有满足了以上条件,c++才会认为此函数和类是有一定关系的,
在名称查找时,才能运用Koenig Lookup 法则。运用Koenig Lookup 法则关联的函数要比类的成员函数关系弱,c++会优先选择成员函数
- 了解C++在寻找函数时具体的规则,首先是按照名称查找,这个时候是不考虑访问权限和参数是否匹配的问题的,找到后才会再去检查这些条件