3.8 命名空间

3.8.1 标准名空间

命名空间是C++语言的新特性。命名空间可以帮助程序员在开发新的软件组件时不会与已有的软件组件产生命名冲突。命名空间是类、函数、对象、类型和其他名字声明的集合。std是C++语言的标准名空间,包含了标准头文件中各种名字的声明。

C++语言标准头文件有:

            iostream  iomanip  limit  fstream  string  typeinfo  stdexcept

C++语言标准头文件没有扩展名。使用标准类库的组件时,需要指定名空间。例如:

            #include<iostream>
            using namespace std;

其中,namespace是C++的关键字,用于说明命名空间(或称为名字作用域)。声明之后,程序中就可以直接使用iostream中的元素(组件名),例如,cin、cout等。

如果不用using声明名空间,则需要在使用时指定组件的名空间。

若包含标准名空间没有定义的头文件,则不能省略扩展名,例如:

            #include <conio.h>

【例3-39】使用标准名空间。以下简单例程演示了使用C++语言标准组件的方法。

方法1:

            #include<iostream>
            using namespace std;             //使用标准名空间std
            int main()
            {  int a,b;
              cin>>a;                       //使用std的元素cin
              cin>>b;                       //使用std的元素cin
              cout<<"a+b="<<a+b<<'\n';      //使用std的元素cout
            }

方法2:

            #include<iostream>
            using std::cin;                  //指定使用std的元素cin
            using std::cout;                 //指定使用std的元素cout
            int main()
            {  int a,b;
              cin>>a;                        //使用std的元素cin
              cin>>b;                        //使用std的元素cin
              cout<<"a+b="<<a+b<<'\n';       //使用std的元素cout
            }

方法3:

            #include<iostream>
            int main()
            {  int a,b;
              std::cin>>a;                    //指定使用std的元素cin
              std::cin>>b;                    //指定使用std的元素cin
              std::cout<<"a+b="<<a+b<<'\n';   //指定使用std的元素cout
            }

方法1允许程序使用全部std的名字,相当于在当前程序中抄入一批命名符,但程序中没有使用的标识符会被编译器忽略,不影响运行效率。使用这种方法的代码比较简洁。方法2和方法3 使用名空间的特定名字,不会抄入全部命名符。本教材为了方便初学者阅读,在例程中采用方法1使用标准名空间。

对于C语言的标准头文件,如:

            stdlib.h  math.h  asser.h  string.h  ctype.h

在VC.NET程序中包含这些头文件时,可以在头文件名前加上前缀c,同时去掉文件扩展名。例如:

            #include<stdio.h>
            #include<math.h>
            #include<string.h>

等价于:

            #include<cstdio>
            #include<cmath>
            #include<cstring>

在C++语言中,还有:

            #include<string>

注意头文件cstring和string的区别。包含前者是为了调用C的字符串处理函数,如strcpy、strlen等;包含后者是为了使用标准类库中的string类及其操作。

3.8.2 定义命名空间

C++可以识别不同作用域的名字。例如,一个文件中的全局量和局部量可能有相同的名字,或不同类有同名的成员,系统可以根据作用域明确地区分开来。

开发一个应用程序常常需要多个库,它们源自不同的文件,这些文件通常用 include 指令包含进来,而不同的文件可能定义了相同名字的类或函数。例如,有以下两个头文件:

            //lib1.h
            class SameName
            { /*…*/ };
            //lib2.h
            class SameName
            { /*…* / };

其中,class关键字用于定义类类型,详见第6章。当用户要在一个程序文件中使用这两个库中的类时,编写以下代码:

            #include " lib1.h "
            #include " lib2.h "
            void UseSameName()
            {  SameName one;      //使用哪一个库中定义的类
              SameName two;       //使用哪一个库中定义的类
              //…
            }

由于include指令在编译之前执行,因此编译器将无法识别one和two是lib1.h还是lib2.h中定义的SameName类的对象。而且,lib1和lib2是操作系统识别的文件名,不是C++编译器可以识别的“名字”,因此,不能用域符加以区分。以下是错误的:

            lib1::SameName        //错误,lib1不是程序命名符
            lib2::SameName        //错误,lib2不是程序命名符

为此,标准C++引入了namespace和using机制。命名空间用于创建程序包,其中所有定义的名字都是命名空间的元素,可以用域符指明,从而防止意义模糊。

定义命名空间的语法很简单:

            namespace <标识符>
            { <语句序列> }

其中,namespace 是关键字;<标识符>是用户定义的命名空间的名字;<语句序列>可以包含类、函数、对象、类型和其他命名空间的说明和定义。

现在,用命名空间重新定义lib1和lib2库。

            //lib1.h
            namespace lib1
            {  class SameName
              { /*…*/ };
            }
            //lib2.h
            namespace lib2
            {  class SameName
              { /*…*/ };
            }

函数UseSameName可以明确识别不同命名空间的SameName类。

            #include ” lib1.h ”
            #include ” lib2.h ”
            void UseSameName()
            {  lib1::SameName one;  //正确,lib1命名空间定义的类
              lib2::SameName two;   //正确,lib2命名空间定义的类
              //…
            }

这里的 lib1、lib2 不是头文件名,是命名空间的名字。程序员也可以为命名空间定义另外一个名字,例如,可以把类名作为命名空间名的一部分:

            Lib1SameNameClass

而命名规则与普通标识符相同。

命名空间可以追加定义和说明,命名空间也可以嵌套,例如:

            namespace A
            {  void f();
              void g();
            }
            namespace B
            {  void h();
              namespace C         //嵌套命名空间
              {  void i();  }
            }
            namespace A           //为namespace A追加说明
            {  void j();  }

命名空间同样遵循先说明再使用的原则。例如,函数f和g不能使用命名空间B的成分,因为命名空间B还没有说明。同样,f和g也不能使用命名空间A追加的函数j。

3.8.3 使用命名空间

用using语句可以指定使用的命名空间。using语句有两种形式:

using namespace 名空间;

using名空间::元素;

其中,“名空间”为命名空间的名字;“元素”表示命名空间中的元素名,例如,类、函数、常量等的名字。

using语句指定的命名空间(或元素)的名字是当前作用域的一部分。

【例3-40】演示命名空间的使用。

            #include<iostream>
            using namespace std;
            namespace A
            {  void f()
              {  cout<<"f():from namespace A"<<endl;  }
              void g()
              {  cout<<"g():from namespace A"<<endl;  }
              namespace B
              {  void f()
                  {  cout<<"f():from namespace B"<<endl;  }
                  namespace C
                  { void f()
                      {  cout<<"f():from namespace C"<<endl;  }
                  }
              }
            }
            void g()
            {  cout<<"g():from global namespace"<<endl;  }
            int main()
            {  g();                   //调用非命名空间函数g()
              using namespace A;      //使用名空间
              f();                    //调用A::f()
              B::f();                 //调用A::B::f()
              B::C::f();              //调用A::B::C::f()
              A::g();                 //调用A::g()
            }

程序运行结果:

            g() : from global namespace
            f() : from namespace A
            f() : from namespace B
            f() : from namespace C
            g() : from namespace A

命名空间A中嵌套定义命名空间B,B中又定义了命名空间C。main函数在using语句之后,其中的域运算符就可以正确界定所使用的名字了。

程序在使用命名空间之前的调用

            g();

没有歧义。因为没有当前作用域的名字冲突。using语句启用A空间的名字

            f();

也没有歧义,因为此时能直接看到的是函数名f、g和空间名B。要调用B内的f和C内的g,必须用域运算符加以区别:

            B::f();
            B::C::f();

但是,为什么using之后调用命名空间A的g函数还要用

            A::g();

呢?因为此时编译器把非命名空间的g函数和命名空间的g函数都看成当前的有效名字了,它们是一种平等关系,是同名的重载函数而已。如果它们可以根据参数区分调用版本,就可以不指定空间名,否则还需要用域符指定空间名。同样,若有

            using namespace A::B;

为了调用B的相同原型的f函数,还要用

            B::f();

原因就是,using仅仅把命名空间的代码纳入当前作用域,效果和include指令类似。可见,命名空间的概念和程序中全局量、局部量的概念是不相同的。