2.10 命名空间
1.命名空间
在最初的C++标准中并没有对命名空间的支持,要求程序在全局作用域中声明的每个变量、函数、类型、模板等都必须具有唯一的名称,如果在同一程序中有两个名称相同的全局变量,将产生命名冲突。
如果在程序中引入另一个系统或第三方软件商提供的库文件,就必须保证程序中定义的全局名(包括变量、函数、类型等的名称)都不能与所用库中的名称相同;如果一个程序由许多程序员共同编写,大家也不能命名相同的全量变量名和函数名,否则将产生名字冲突,这就是所谓的全局命名空间污染问题,处理起来并不容易,在大型程序中尤其困难。最新的C++标准为了解决这个问题,引入了命名空间。
在一个命名空间中,可以定义许多不同的对象(包括变量、函数、类型、类等),并将这些对象的有效范围局限在命名空间内。但在不同的命名空间中,可以定义同名的对象,只要两个同名对象不在同一命名空间中,就不会引起冲突。
2.命名空间的定义
定义命名空间的关键字是namespace,它的语法如下:
namespace namespace_name{ members }
namespace_name是命名空间的名字,只要是一个合法的C++标识符都可以;merbers是命名空间中包括的成员,可以是变量、函数声明、函数定义、结构声明,类的声明等。例如:
namespace ABC{ int count; typedef float house_price; struct student{ char *name; int age; }; double add(int a,int b) { return (double)a+b; inline int min(int a,int b); } } int ABC::min(int a,int b) { return a>b?a:b; };
这里定义了一个命名空间ABC,它有5个成员:count、house_price、student、add( )和min( ),有变量、结构、类型以及函数的声明或定义。函数的定义有两种方式:在命名空间内定义,如函数add( );也可以在命名空间外定义,如min()。当在命名空间外定义时,要用“命名空间::”作为函数名的前缀,表示该函数是属于某个命名空间的成员,它的有效范围仅在此命名空间内。
3.名字空间的应用
命名空间的成员的作用域局限于命名空间内部,可以通过作用域限定符::访问它,语法如下:
namespace_name::identifier
identifier就是命名空间的成员名。例如,访问上面的ABC命名空间中的成员:
void main(){ ABC::count=1; //访问ABC空间中的count int count=9; //main()函数中的count,与ABC中的count无关 ABC::student s; //用ABC空间中的student结构定义s s.age=9; int x=ABC::min(4,5); //调用ABC中的min函数计算两数最小值 }
另外,标准C++提供了using命令,它可将命名空间中的成员引入当前程序中,简化了命名空间的使用。using有如下两种使用方式。
① 引用命名空间的单个成员。用法如下:
using namespace_name::identifier
例如,用using简化ABC命名空间中count的使用:
void main(){ using ABC::count; //L1 count =2; //L2 //int count=9; //L3 …… count=count+2; //L4 }
语句L1表示本程序使用ABC命名空间中的count变量。语句L3是错误的,因为main()中已经有了count,它是using从ABC命名空间引入的。如果没有语句L1,则语句L2和L4应该写成:
ABC::count=2 //L2 ABC::count= ABC::count+2 //L4
这样的书写显然要麻烦得多。
② 引入命名空间的全部成员。用法如下:
using namespace_name
例如,用using将ABC命名空间的全部成员引入程序中:
using namespace ABC; //L1 void main(){ int count=9; //错误,已有来源于ABC中的count,成了重复定义 student s; count=5; s.age=min(43,32); }
语句L1用using语句将ABC命名空间中的全部成员引入程序中,所以在main()中就可以直接使用来源于ABC中的任何成员了。
命名空间为全局名称的冲突问题提供了一种解决方案。如果一个系统由多个文件组成,而每个文件又由不同的开发商或程序员提供,则开发商和程序员可以将他们定义的名称局限在自定义的命名空间中,即使大家都定义了相同的名称,也不会产生冲突。
4.std命名空间
C++经历了较长的发展和标准化过程,结果形成了两个版本的C++:一个是以Bjarne Stroustrup最初设计的C++为基础的版本,称为传统C++;另一个是以ANSI/ISO标准化委员会创建的C++,称为标准C++。
标准C++增加了传统C++中没有的一些特征,是传统C++的超集。两种版本的C++有大量相同的库和函数(标准C++更多),其区分方法是头文件和命名空间。传统C++采用与C语言同样风格的头文件;标准C++为了兼容传统C++,也支持C语言风格的头文件,但它创建了一种新式的头文件,为标准C++库所使用。标准C++的新式头文件没有扩展名,即不需要 .h之类的扩展名。例如,传统C++的头文件有iostream.h、fstream.h、string.h,标准C++对应的头文件有iostream、fstream、string。
标准C++包含了整个C函数库,支持在C++程序中引用C函数,允许用#include把包含C函数库的头文件包含到程序中。但标准C++也提供了与之对应的新式函数库,其命名方法是在原C函数库对应头文件名的前面加上“c”前缀,并去掉“.h”。例如,C语言的头文件有stdio.h、ctype.h、math.h,标准C++头文件有cstdio、cctype、cmath。
标准C++将新格式头文件中的内容全部放到了std命名空间中,非新格式头文件中的内容则被直接放到了全局命名空间中。当前的许多C++编译器都提供了对两种版本C++的支持,如果程序要引用标准C++新格式头文件中的函数,就需要在程序中使用下面的语句将std命名空间中的名称引入到全局命名空间中:
using namespace std;
【例2-18】 标准C++程序设计的简单程序。
//Eg2-18.cpp #include <iostream> #include <cstdio> #include <cmath> #include <string> using namespace std; void main(){ string s1="ddd",s2; //string是字符串,定义于<string> s2=s1; //字符串赋值,不需要strcpy函数 int i; int s=sin(30); //调用cmath库中的sin函数 scanf("%d",&i); //scanf、printf来源于cstdio printf("i=%d\n",i); cin>>i; //cin、cout来源于iostream cout<<"i="<<i<<endl; cout<<s2<<endl; }
本程序的函数调用及数据类型来源于标准C++提供的新式库函数,这些函数及数据类型来源于std命名空间,若去掉using namespace std,程序就会产生编译错误。
语句“using namespace std;”会将std命名空间中的标识符都引入到程序中,如果只需用到std命名空间中的个别标识符,可以在要使用的标识符前面加上前缀std::,不必用“using namespace std;”将std中的全部名称引入到程序中来。例如:
std::cout<<"not use using……"<<std::endl; std::cout<<"not use using……\n"; std::cin<<xx;
虽然C++编译器同时支持新、老格式头文件,但由于新格式与C++新标准相符合,新标准C++具有传统标准C++所没有的新特性,功能更强大,因此应该多用新标准中的库函数进行程序设计,常称为标准C++程序设计。