14.2.4节:
这一节介绍了与命名空间相关的依赖参数的向上查找法则(Argument-Dependent Lookup):
1:如果一个函数在它的源文件中没有发现被定义,那么编译器就会去查找该函数的参数所对应的命名空间,看该命名空间中是否定义了该函数。例如:
namespace Chrono{
class Date{/*...*/};
bool operator==(const Date&,const std::string&);
std::string format(const Date&);
}
void f(Chrono::Data d, int i)
{
std::string s=format(d); //Okay, Chrono::format();
}
在函数f(Chrono::Date, int)的scope中没有发现format()函数,因此编译器就去查找其参数d所对应的命名空间Chrono中是否定义了相关的format函数,恰好在命名空间Chrono中定义了该format()函数,因此f()函数中对于format()的使用是正确的。
2:当我们不是在类中调用一个函数时,它会加载调用点所在作用域以及该函数参数所对应命名空间中所有有相同名字的函数,然后从中选择最佳的匹配。例如:
namespace N1{
class X{/*...*/};
void f(X,int);
}
namespace N2{
N1::X x;
void f(N1::X,unsigned);
void g()
{
f(x,1);
}
}
当我们在g()函数中调用f(x,1)函数时,编译器会找到两个相关的函数。一个是在调用点所在的作用域中的函数f(N1::X, unsigned),另外一个是其参数x所对应的命名空间N1中所定义的函数N1::f(x, int),编译器会从这两个函数中寻找与f(x,1)的最佳匹配,因为1是int而不是unsigned,若调用N2::f(N1::x,unsigned)则要进行相应转换,而若调用N1::f(x,int)不需要进行任何转换,因此编译器会调用命名空间N1中定义的函数f(x,int),而不是命名空间N2中定义的函数N2::f(N1::x,unsigned)。如果一定想要调用N2::f(N1::x,unsigned),则在f()函数调用点处把f(x,1)改成N2::f(x,1)即可。
3:当在类中调用一个普通函数时,编译器会优先查找在该类及其基类中定义的函数,如果没有找到相同的函数名则会在其参数所对应的命名空间中查找。例子如下:
namespace N{
struct S{ int i;};
void mf(S);
}
struct A{
void mf(N::S);
void g(N::S s)
{
mf(s); //调用A::mf(N::S);
}
};
在上面例子中,g函数中的mf()函数调用的是A::mf()而不是N::mf()。
但是当我们在类中调用重载操作符函数时,就和我们在非类中调用函数一样,不存在上述所说的优先性,编译器会加载所有满足条件的重载操作符,从中选择一个最匹配的。
14.3.1节:
1:命名空间可以看成是能够表达逻辑分组的一种机制,也就是说我们可以把依据某些标准而放在一起的声明(比如函数声明,类声明)放在同一个命名空间中。
2:我们一般把声明与定义分开,这样便于浏览代码。声明放在.h文件中,定义放在.cc文件中。对于大部分用户来说,他们只想知道该程序实现了什么功能而不会去关心该功能是怎么实现的,这样的话他们只需浏览.h文件即可。例如:
// 在example这个命名空间中声明f()函数,放在一个.h文件中
namespace example{
void f();
}
// 在对应的.cc文件中给出example::f()函数的定义
void example::f(){\* ...*/};
14.3.2节:
1:当我们在调用自己的命名空间成员,我们可以直接调用。但是当我们想要调用其它命名空间的成员时,我们可以采用显示限定(explicit qualification),using声明,using 指示这三种方法。比如如果我们想要调用上面例子中命名空间example中的f()函数,则显示限定指的是example::f,using声明指的是using example::f,using指示是指using namespace example。
因为using指示会把命名空间中的所有成员全部暴露出来,因此使用using指示很容易导致命名空间污染(不同的命名空间有相同名字的声明)问题,所以一般不要在.h文件中使用using指示,因为我们很难预料这个.h文件会在哪里被#include的,我们一般在.cc文件中使用using指示,但是如果这个.cc文件中有多个using指示,那么就要提防一下命名空间污染这个问题。
14.4.1
1:using声明中引入的名字遵循常规作用域规则,从using声明点开始,直到包含using声明所在作用域的末尾,名字都是可见的。外部作用域中定义的同名实体被屏蔽了。简写名字只能在声明它的作用域及嵌套作用域中使用,一旦该作用域结束,就必须使用完全限定名。
因此,我们可以把using声明的变量看成是using声明所在作用域中的局部变量。一个作用域中的局部变量(声明的要么是通过普通的声明,要么是通过using声明)屏蔽了非局部的变量,并且局部变量不能被声明多次。例子如下:
namespace X{
int i,j,k;
}
int k=3; //global variable.
voif f2()
{
int i=0;
using X::i; //error!! 此时using声明的i可以看成是函数f2()中的局部变量,与int i=0这语句中声明的i冲突;
using X::j;
using X::k; //屏蔽了全局作用域中i。
j++; //X::j
k++; // X::k
2:1):using指示具有将命名空间成员提升到包含命名空间本身和using指示的最近作用域的效果。
2):using声明等效于在局部作用域中添加了一个名字,但是using指示并不是在作用域中添加了名字,而是表明其命名空间的成员在这个作用域中是可以被访问的。
namespace blip{
int bi=16,bj=15,bk=23;
}
int bj=0; //ok: bj inside blip is hidden inside a namespce
void manip()
{
//using指示具有将命名空间blip中的成员添加到了全局作用域中的效果。此时blip::bj与::bj有潜在冲突,但因为using指示只是表明blip::bj是可访问的,并不是和using声明一样在作用域中声明了这个变量,因此编译器不报错
using namespace blip;
++bi; //Okay,调用blip::bi;
++bj; //error! 因为blip::bj具有全局作用域的效果,因此编译器不知道是要调用blip::bj还是::bj.
++::bj //okay;
++blip::bj //okay
int bk=97; //局部变量bk屏蔽了blip::bk;
++bk; //set bk=98;
从上面代码例子中编译器对bj变量处理的情况,我们不难推断出如果有个using指示,可能这些不同的命名空间中有相同名字的声明,但是只要这些名字在程序中没有被使用,编译器就不会报错。但如果使用了,编译器就会报错,所以说我们还是要尽可能少的使用using指示,如果存在多个命名空间的话。