为什么我们要有命名空间这个概念,主要是因为,在大型C++项目的开发过程中,很有可能出现各种作用域中的名字重复的情况。有可能是一个程序中不能模块中的名字重复,甚至可能是因为使用别的公司的库而产生的命名重复。在没有命名空间这个概念之前,程序员只能把名字起的比较长。但这样也只是减少了重复的可能性。而且大量非常长的名字给程序员增添了负担。
命名空间的出现解决了这个问题。简单的说,命名空间就是一个起了名字的作用域。当我们在这个作用域外引用作用域里的变量、函数时,必须声明使用的是哪个空间中的。先看一个简单的例子:
#include <iostream>
namespace name1
{
int ival = 1;
void print()
{
std::cout<<ival<<std::endl;
}
}
namespace name2
{
int ival = 0;
void print()
{
std::cout<<ival<<std::endl;
}
}
int main()
{
name1::print();
std::cout<<name1::ival<<std::endl;
name2::print();
std::cout<<name2::ival<<std::endl;
return 0;
}
虽然我们定义了两个相同的变量ival和相同的函数print,但是我们能够通过命名空间来区分它们。
我们可以看出,命名空间以关键字namespace后面加上名字,再加上一对花括号组成的。注意花括号后面没有分号。有意思的是,命名空间并不需要连续,可以把一个命名空间分成好几段。这意味着我们也可以在命名空间中实现接口程序和实现代码的分离。看一个复杂的例子:
//"test.h"
#include <string>
#include <iostream>
using std::string;
using std::cout;
using std::endl;
namespace myNameSpace1
{
class A
{
public:
A();
void printWords();
private:
string wordInclass;
};
static int num = 0;
void printHello();
}
//"test.cpp"
#include "test.h"
myNameSpace1::A::A():wordInclass("defult word"){}
void myNameSpace1::A::printWords()
{
cout<<wordInclass<<endl;
}
void myNameSpace1::printHello()
{
cout<<"hello"<<endl;
}
//"test2.h"
#include <iostream>
using std::cout;
using std::endl;
namespace myNameSpace1
{
void printGoodBye();
}
//"test2.cpp"
#include "test2.h"
void myNameSpace1::printGoodBye()
{
cout<<"good bye! "<<endl;
}
//"main.cpp"
#include "test.h"
#include "test2.h"
int main(int argc, char** argv)
{
myNameSpace1::A a;
a.printWords();
cout<<myNameSpace1::num<<endl;
myNameSpace1::printHello();
myNameSpace1::printGoodBye();
return 0;
}
我们要特别注意,在头文件中不要定义普通变量或者函数,当这个头文件只被一个源文件包含时,是没有问题的,但是当它被多个源文件包含时,每个每次include都相当于复制代码上去,所以当编译多个源文件时,就会产生多个变量的定义,而当连接器把它们链接到一起的时候,就会出错了。
命名空间也可以嵌套定义:
#include <iostream>
namespace name1
{
int ival = 1;
void print()
{
std::cout<<ival<<std::endl;
}
namespace name2
{
int ival = 0;
void print()
{
std::cout<<ival<<std::endl;
std::cout<<name1::ival<<std::endl;
}
}
}
int main()
{
name1::print();
std::cout<<name1::ival<<std::endl;
name1::name2::print();
std::cout<<name1::name2::ival<<std::endl;
return 0;
}
使用的时候变量的查找规则跟一般变量的查找规则类似:外围作用域的变量会被内层的屏蔽,除非你显式的指定。这里name2空间中的print函数中第一条语句打印为0,第二条打印为1.
有些时候,每一次引用命名空间的成员时都要加上命名空间名会让人觉得很麻烦,所以我们使用using声明或者using指示来解决这个问题。using声明就是指出我们要使用的命名空间中的某个特别的成员,之后在使用它时就可以直接使用了,比如using std::cout;此时,从using声明开始,到包含该声明的的作用域(不是文件:我们可以在某个函数内使用using声明!),这个名字都是可见的。而using指示直接将命名空间的成员放入到包含命名空间的作用域中。比如using namespace std;。对于这两个语句(尤其是第二条),很多初学者都会频繁使用,觉得这样做是一劳永逸的,其实对于一些复杂的程序,这样做是反而会再次重新引入命名空间污染。正确的作法一般是这样:对于头文件,除非是函数的内部,否则不该使用using指示或者声明,而在对应的源文件中使用。
最后讲1个例外的情况,先看程序:
#include <iostream>
#include <string>
int main()
{
std::string s;
getline(std::cin,s);
std::cout<<s<<" over! "<<std::endl;
return 0;
}
对于getline函数,我们并没有使用std::限定符!原来C++规定:接受类类型形参(或者类类型指针或者引用的形参)的且与类本身定义在同已命名空间中的函数(包括重载操作符),在用类类型对象(或者类类型的引用或者指针)作为实参的时候是可见的。在这里getline函数接受string类的对象,且getline与string类都在#include <string>中,而我们用使用string类的对象s调用,所以getline函数是可见的。