1、在一个给定的作用域中定义的每个名字,在该作用域中必须是唯一的。
对庞大、复杂的应用程序而言,这个很难满足。由独立开发的库构成的复杂程序更有可能遇到名字冲突,因为库倾向于使用全局名字:模板名、类型名或函数名。
命名冲突问题被称为命名空间污染。
2、命名空间为防止名字冲突,提供了更加可控的机制。命名空间能够划分全局命名空间。一个命名空间是一个作用域,通过在命名空间内定义库中的名字,就可以避免全局名字固有的限制。
命名空间以namespace开始,后接命名空间的名字。如:
namespace aa
{
class Bar
{
public:
Bar(){}
};
class Bar2{};
}
该命名空间定义了两个类。
3、在定义命名空间的作用域中,命名空间的名字必须是唯一的。命名空间可在全局作用域或其他作用域内部定义,但不能在函数或类内定义。
注意:命名空间不能以分号结束。
4、每个命名空间是一个作用域,在同一个命名空间中,每个名字也同样必须是唯一的。每个名字可以在其他的命名空间中引用,但必须指出名字所属的名字空间。
如:aa::Bar bar;
每次在使用某一命名空间的成员,都指定命名空间名非常麻烦。可以像std中定义的命名空间那样,使用using声明,来获得对我们经常使用的名字直接访问。
如:using std::cout;
using aa::Bar;
5、同一命名空间可以在几个部分定义,命名空间由各个部分的总和构成。一个命名空间的分离部分甚至可以分散在多个文件中。
namespace namespace_name
{
}
该定义既可以定义新的命名空间,也可以添加到现存命名空间。
6、命名空间可以不连续意味着:可以用分离的接口文件和实现文件构成命名空间。
1)定义类的命名空间成员,以及作为类接口一部分的函数声明与对象声明,可以放在头文件中。使用命名空间成员的文件可以包含这些文件。
2)命名空间成员的定义可以放在单独的源文件中。如:
//namespace_a.h
namespace namespace_a
{
class Bar
{
public:
Bar(){}
void something();
};
}
//namespace_a.cpp
#includer“namespace_a.h”
namespace namespace_a
{
void Bar::dosomething
{
//
}
void func();
}
7、在命名空间内部定义的函数,可以使用同一命名空间中定义的名字的简写形式。如:
namespace A
{
Bar bar;//无需写成namespace_a::Bar bar;
}
上例中的void func()也可以在命名空间的外部定义,但是必须指定该名字所属的命名空间。如:
void A::func()
{
}
虽然可以在命名空间定义的外部定义命名空间,但只有包围成员声明的命名空间,可以包含成员的定义。
如dosomething函数可以在定义在A作用域中,也可以定义在全局作用域中,但不能定义在不相关的命名空间中。
经过实践证实上述Bar类的成员函数dosomething也可以在名字空间之外定义。
要写成void A::Bar::dosomething(){};
8、全局命名空间是隐式声明的,没有名字。包括定义在全局作用域的名字,存在每个程序中。在全局作用域定义的每个文件中的名字将被加入到全局名字空间。
可以用作用域操作符引用全局命名空间的成员。如:::member_name
9、可以在其他命名空间定义新的命名空间,这成为命名空间的嵌套。
嵌套时,外围命名空间中声明的名字被嵌套命名空间中同一个名字的声明所屏蔽。外围命名空间之外的代码只能通过限定名来引用。如
int g;
namespace A
{
namespace B
{
int a;
}
namespace C
{
B::a=20;
::g=39;
}
}
嵌套命名空间的成员的名字,由外围命名空间的名字和嵌套命名空间的名字构成。
如需要引用B中的成员则应为:A::B::a;
10、使用namespace定义命名空间时,也可以不指定名字。此时命名空间是未命名的。
1)未命名的空间局部于特定的文件,不跨越多个文本文件。同一个未命名空间的定义也可以是不连续的。未命名的空间用于声明局部于文件的实体,它内的成员在程序开始时创建,在程序结束前一直存在。
2)因为没有空间名,所以未命名空间的成员可以直接使用。如果头文件定义了未命名的名字空间,那么,在每个包含该头文件的文件中,该命名空间的名字将定义不同的局部实体。
3)未命名空间中定义的名字可以在该命名空间所在的作用域找到,也就是说未命名空间好像不存在似的。可以假定所有命名空间的内部都是有未命名的空间组成。如全局命名空间的未命名空间,在全局空间和其内的未命名空间名字不能相同,也就很好理解了。
11、除了在函数或其他作用域内部,不应该在头文件中使用using声明或using指示。
头文件应该只定义作为其接口的一部分的名字。一个using声明一次只引入一个命名空间成员。
using声明引入的名字遵循作用域规则,从using声明点开始,直到包含该using声明的作用域的末尾,名字都是可见的。外部作用域定义的同名实体被屏蔽。
可以使用命名空间别名,将较短的名字与命名空间的名字关联。如
namespace cplusplus_primer;
namespace cpp=cplusplus_primer;
12、using声明以关键字using namespace开头,后接命名空间的名字。
它将名字直接放入出现using声明的作用域。而using指示将命名空间成员提升到包含命名空间本身和using指示的最近作用域的效果。
如namespace a
{
int i,j;
}
void f()
{
using namespace a;//将i,j提升到包含namespace a和f的全局空间中。
cout<<i*j<<endl;
}
此种提升规则可能会导致命名空间的名字会与外围作用域中定义的其他名字冲突。
如果在f外的全局作用域定义int j ;此时的using namespace a;将a的成员提升到全局作用域,在f内使用j就会出现歧义的情况。
这种冲突是允许的。但是为了使用j必须指明想使用哪个j。使用::j引用全局作用域的变量,使用a::j引用a中的变量。
13、using指示会注入另一个命名空间的所有名字,如果程序使用很多库,并且使用using指示使得这些库中的名字可见,那么全局名字空间污染的问题就会重新出现。使用using声明是不错的方法。
std::string s;
getline(std::cin,s);
为什么无需使用std::getline就可以使用该函数?
它给出了屏蔽命名空间名字规则的一个重要例外:与类本身定义在同一命名空间的函数,在用类类型对象作为实参时是可见的。
当编译器看到getline(std::cin,s)时,它会在当前作用域、包含调用的作用域以及定义cin的类型和string类型的命名空间中查找匹配的函数。
如namespace A
{
class Bar
{
public:
Bar(){};
};
void print(Bar &b){};
}
void func()
{
A::Bar bar;
print(bar);//调用A中的print.因为它与Bar类处于同一命名空间。
}
14、原来在介绍友元声明时提到,友元可以在类内声明时定义。在类内定义的友元与类具有相同的作用域,因此在类内定义的友元也适用于上述例外的情况。
15、在同一个作用域的函数才有重载。不同作用组仅仅存在函数名屏蔽。
16、在同一命名空间内,命名空间对函数匹配有两个影响:
1)using声明或using指示可以将函数加到候选集合中。
using声明声明一个名字,就像在模板那一章介绍的一样,没有办法用using声明来引用特定函数的声明。
如:using NS::print(int);//错误。必须写成:using NS::print;
如果命名空间内部的print是重载的,则该函数名字的using声明,声明了所有具有该名字的函数。它们被加入了当前作用域,都在当前作用域中可见。
如果使用using声明时,该作用于已存在相同函数名且形参表相同的函数,则会出错。如果函数名相同但形参表不同,那么声明过来的重载函数会与原来的函数构成新的更大的重载集合。
2)using指示将命名空间的成员提升到外围作用域。如果命名空间函数与命名空间所在的作用域中声明的函数同名,就将命名空间成员加到重载集合中。