return0;}
这时程序编译就会出错。
因为在预编译后,头文件中的内容取代了对应的#include命令行,这样就在同一个程序文件中出现了两个Student类和两个fun函数,显然是重复定义,这就是名字冲突,即在同一个作用域中有两个或多个同名的实体。
3、全局命名空间污染(globalnamespacepollution)。
在程序中还往往需要引用一些库(包括C++编译系统提供的库、由软件开发商提供的库或者用户自己开发的库),为此需要包含有关的头文件。
如果在这些库中包含有与程序的全局实体同名的实体,或者不同的库中有相同的实体名,则在编译时就会出现名字冲突。
为了避免这类问题的出现,人们提出了许多方法,例如:
将实体的名字写得长—些(包含十几个或几十个字母和字符);把名字起得特殊一些,包括一些特殊的字符;由编译系统提供的内部全局标识符都用下划线作为前缀,如_complex(),以避免与用户命名的实体同名;由软件开发商提供的实体的名字用特定的字符作为前缀。
但是这样的效果并不理想,而且增加了阅读程序的难度,可读性降低了。
C语言和早期的C++语言没有提供有效的机制来解决这个问题,没有使库的提供者能够建立自己的命名空间的工具。
人们希望ANSIC++标准能够解决这个问题,提供—种机制、一种工具,使由库的设计者命名的全局标识符能够和程序的全局实体名以及其他库的全局标识符区别开来。
二、什么是命名空间(解决方案)
命名空间:
实际上就是一个由程序设计者命名的内存区域,程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分隔开来。
如:
namespacens1//指定命名中间nsl
{inta;
doubleb;}
namespace是定义命名空间所必须写的关键字,nsl是用户自己指定的命名空间的名字(可以用任意的合法标识符,这里用ns1是因为ns是namespace的缩写,含义请楚),在花括号内是声明块,在其中声明的实体称为命名空间成员(namespacemember)。
现在命名空间成员包括变量a和b,注意a和b仍然是全局变量,仅仅是把它们隐藏在指定的命名空间中而已。
如果在程序中要使用变量a和b,必须加上命名空间名和作用域分辨符“:
:
”,如nsl:
:
a,nsl:
:
b。
这种用法称为命名空间限定(qualified),这些名字(如nsl:
:
a)称为被限定名(qualifiedname)。
C++中命名空间的作用类似于操作系统中的目录和文件的关系,由于文件很多,不便管理,而且容易重名,于是人们设立若干子目录,把文件分别放到不同的子目录中,不同子目录中的文件可以同名。
调用文件时应指出文件路径。
命名空间的作用:
是建立一些互相分隔的作用域,把一些全局实体分隔开来。
以免产生老点名叫李相国时,3个人都站起来应答,这就是名字冲突,因为他们无法辨别老师想叫的是哪一个李相国,同名者无法互相区分。
为了避免同名混淆,学校把3个同名的学生分在3个班。
这样,在小班点名叫李相国时,只会有一个人应答。
也就是说,在该班的范围(即班作用域)内名字是惟一的。
如果在全校集合时校长点名,需要在全校范围内找这个学生,就需要考虑作用域问题。
如果校长叫李相国,全校学生中又会有3人一齐喊“到”,因为在同一作用域中存在3个同名学生。
为了在全校范围内区分这3名学生,校长必须在名字前加上班号,如高三甲班的李相国,或高三乙班的李相国,即加上班名限定。
这样就不致产生混淆。
可以根据需要设置许多个命名空间,每个命名空间名代表一个不同的命名空间域,不同的命名空间不能同名。
这样,可以把不同的库中的实体放到不同的命名空间中,或者说,用不同的命名空间把不同的实体隐蔽起来。
过去我们用的全局变量可以理解为全局命名空间,独立于所有有名的命名空间之外,它是不需要用namespace声明的,实际上是由系统隐式声明的,存在于每个程序之中。
在声明一个命名空间时,花括号内不仅可以包括变量,而且还可以包括以下类型:
·变量(可以带有初始化);
·常量;
·数(可以是定义或声明);
·结构体;
·类;
·模板;
·命名空间(在一个命名空间中又定义一个命名空间,即嵌套的命名空间)。
例如
namespacensl
{constintRATE=0.08;//常量
doublepay;//变量
doubletax()//函数
{returna*RATE;}
namespacens2//嵌套的命名空间
{intage;}
}
如果想输出命名空间nsl中成员的数据,可以采用下面的方法:
cout<:
RATE<cout<:
pay<cout<:
tax()<cout<:
ns2:
:
age<可以看到命名空间的声明方法和使用方法与类差不多。
但它们之间有一点差别:
在声明类时在右花括号的后面有一分号,而在定义命名空间时,花括号的后面没有分号。
三、使用命名空间解决名字冲突(使用指南)
有了以上的基础后,就可以利用命名空间来解决名字冲突问题。
现在,对例4程序进行修改,使之能正确运行。
例5利用命名空间来解决例4程序名字冲突问题。
修改两个头文件,把在头文件中声明的类分别放在两个不同的命名空间中。
//例8.5中的头文件1,文件名为header1.h
usingnamespacestd;
#include
#include
namespacens1//声明命名空间ns1
{classStudent//在命名空间nsl内声明Student类
{public:
Student(intn,stringnam,inta)
{num=n;name=nam;age=a;}
voidget_data();
private:
intnum;
stringname;
intage;};
voidStudent:
:
get_data()//定义成员函数
{cout<doublefun(doublea,doubleb)//在命名空间n引内定义fun函数
{returnsqrt(a+b);}
}
//例8.5中的头文件2,文件名为header2.h
#include
#include
namespacens2//声明命名空间ns2
{classStudent
{public:
Student(intn,stringnam,chars)
{num=n;name=nam;sex=s;}
voidget_data();
private:
intnum;
stringname;
charsex;};
voidStudent:
:
get_data()
{cout<doublefun(doublea,doubleb)
{returnsqrt(a-b);}
}
//mainfile
#include
#include"header1.h"//包含头文件l
#include"header2.h"//包含头文件2
intmain()
{ns1:
:
Studentstud1(101,"Wang",18);//用命名空间nsl中声明的Student类定义studt
stud1.get_data();//不要写成ns1:
:
studl.get_data();
cout<:
fun(5,3)<ns2:
:
Studentstud2(102,"Li",'f');//用命名空间ns2中声明的Student类定义stud2
stud2.get_data();
cout<:
fun(5,3)<return0;}
解决本题的关键是建立了两个命名空间nsl和ns2,将原来在两个头文件中声叫的类分别放在命名空间nsl和ns2中。
注意:
在头文件中,不要把#include命令放在命名空间中,在上一小节的叙述中可以知道,命名空间中的内容不包括命令行,否则编译会出错。
分析例4程序出错的原因是:
在两个头文件中有相同的类名Student和相同的函数名fun,在把它们包含在主文件中时,就产生名字冲突,存在重复定义。
编译系统无法辨别用哪一个头文件中的Student来定义对象studl。
现在两个Student和fun分别放在不同的命名空间中,各自有其作用域,互不相干。
由于作用域不相同,不会产:
生名字冲突。
正如同在两个不同的类中可以有同名的变量和函数而不会产生冲突一样。
在定义对象时用ns1:
:
Student(命名空间nsl中的Student)来定义studl,用ns2:
:
Student(命名空间ns2中的Student)来定义stud2。
显然,nsl:
:
Student和ns2:
:
Student是两个不同的类,不会产生混淆。
同样,在调用fun函数时也需要用命名空间名ns]或ns2加以限定。
ns1:
:
fun()和ns2:
:
fun()是两个不同的函数。
注意:
对象studl是用nsl:
:
Student定义的,但对象studl并不在命名空间nsl中。
studl的作用域为main函数范围内。
在调用对象studl的成员函数get_data时,应写成studl.get_data(),而不应写成nsl:
:
studl.get_data()。
程序能顺利通过编译,并得到以下运行结果:
101Wangl9(对象studl中的数据)
2.82843(/5+3的值)
102Lif(对象studg中的数据)
1.41421(/5-2的值)
四、使用命名空间成员的方法
从上面的介绍可以知道,在引用命名空间成员时,要用命名空间名和作用域分辨符对命名空间成员进行限定,以区别不同的命名空间中的同名标识符。
即:
命名空间名:
:
命名空间成员名
这种方法是有效的,能保证所引用的实体有惟一的名字。
但是如果命名空间名字比较长,尤其在有命名空间嵌套的情况下,为引用一个实体,需要写很长的名字。
在一个程序中可能要多次引用命名空间成员,就会感到很不方便。
1、使用命名空间别名
可以为命名空间起一个别名(namespacealias),用来代替较长的命名空间名。
如
namespaceTelevision//声明命名空间,名为Television
{...}
可以用一个较短而易记的别名代替它。
如:
namespaceTV=Television;//别名TV与原名Television等价
也可以说,别名TV指向原名Television,在原来出现Television的位置都可以无条件地用TV来代替。
2、使用using命名空间成员名
using后面的命名空间成员名必须是由命名空间限定的名字。
例如:
usingnsl:
:
Student;
以上语句声明:
在本作用域(using语句所在的作用域)中会用到命名空间ns1中的成员Student,在本作用域中如果使用该命名空间成员时,不必再用命名空间限定。
例如在用上面的using声明后,在其后程序中出现的Student就是隐含地指nsl:
:
Student。
using声明的有效范围是从using语句开始到using所在的作用域结束。
如果在以上的using语句之后有以下语句:
Studentstudl(101,"Wang",18);//此处的Student相当于ns1:
:
Student
上面的语句相当于
nsl:
:
Studentstudl(101,"Wang",18);
又如
usingnsl:
:
fun;//声明其后出现的fun是属于命名空间nsl中的fun
cout<:
fun(5,3)
显然,这可以避免在每一次引用命名空间成员时都用命名空间限定,使得引用命名空间成员变得方便易用。
但是要注意:
在同一作用域中用using声明的不同命名空间的成员中不能有同名的成员。
例如:
usmgnsl:
:
Student;//声明其后出现的Student是命名空间nsl中的Student
usmgns2:
:
Student;//声明其后出现的Student是命名空间ns2小的Student
Studentstud1;//请问此处的Student是哪个命名中间中的Student?
产生了二义性,编译出错。
3、使用usingnamespace命名空间名
用上面介绍的using命名空间成员名,一次只能声明一个命名空间成员,如果在一个命名空间中定义了10个实体,就需要使用10次using命名空间成员名。
能否在程序中用一个语句就能一次声明一个命名空间中的全部成员呢?
C++提供了usingnamespace语句来实现这一目的。
usingnamespace语句的一般格式为
usingnamespace命名空间名;
例如
usingnanlespacensl;
声明了在本作用域中要用到命名空间nsl中的成员,在使用该命名空间的任何成员时都不必用命名空间限定。
如果在作了上面的声明后有以下语句:
Studentstudl(101,”Wang”,18);//Student隐含指命名中间nsl中的Student
cout<在用usmgnamespace声明的作用域中,命名空间nsl的成员就好像在全局域声明的一样。
因此可以不必用命名空间限定。
显然这样的处理对写程序比较方便。
但是如果同时用usingnamespace声明多个命名空间时,往往容易出错。
例5中的main函数如果用下面程序段代替,就会出错。
intmain()
{usingnamespacensl;//声明nsl中的成员在本作用域中可用
usingnamespacens2;//声明ns2中的成员在本作用域中可用
Studentstudl(101,”Wang",18);
studl.8ct_data();
cout<Studentstud2(102,"Li",'r');
stud2.get_data();
coutt<returnO;}
因为在同一作用域中同时引入了两个命名空间nsl和ns2,其中有同名的类和函数。
在出现Student时,无法判定是哪个命名空间中的Student,出现二义性,编译出错。
因此只有在使用命名空间数量很少,以及确保这些命名空间中没有同名成员时才用usingnamespace语句。
五、无名的命名空间
以上介绍的是有名字的命名空间,C++还允许使用没有名字的命名空间,如在文件A中声明了以下的无名命名空间:
namespace//命名空间没有名字
{voidfun()//定义命名空间成员
{cout<<"OK."<}
由于命名空间没有名字,在其他文件中显然无法引用,它只在本文件的作用域内有效。
无名命名空间的成员fun函数的作用域为文件A(确切地说,是从声明无名命名空间的位置开始到文件A结束)。
在文件A中使用无名命名空间的成员,不必(也无法)用命名空间名限定。
如果在文件A中有以下语句:
fun();
则执行无名命名空间中的成员fun函数,输出”OK.”。
在本程序中的其他文件中也无法使用该fun函数,也就是把fun函数的作用域限制在本文件范围中。
可以联想到:
在C浯言中可以用static声明一个函数,其作用也是使该函数的作用域限于本文件。
C++保留了用static声明函数的用法,同时提供了用无名命名空间来实现这一功能。
随着越来越多的C++编译系统实现了ANSIC++建议的命名空间的机制,相信使用无名命名空间成员的方法将会取代以前习惯用的对全局变量的静态声明。
六、标准命名空间std
为了解决C++标准库中的标识符与程序中的全局标识符之间以及不同库中的标识符之间的同名冲突,应该将不同库的标识符在不同的命名空间中定义(或声明)。
标准C++库的所有的标识符都是在一个名为std的命名空间中定义的,或者说标准头文件(如iostream)中函数、类、对象和类模板是在命名空