无论是身处学校还是步入社会,大家都尝试过写作吧,借助写作也可以提高我们的语言组织能力。大家想知道怎么样才能写一篇比较优质的范文吗?这里我整理了一些优秀的范文,希望对大家有所帮助,下面我们就来了解一下吧。
标准命名空间 命名空间的概念及作用篇一
对于命名空间,官方文档已经说得很详细[查看],我在这里做了一下实践和总结。
命名空间一个最明确的目的就是解决重名问题,php中不允许两个函数或者类出现相同的名字,否则会产生一个致命的错误。这种情况下只要避免命名重复就可以解决,最常见的一种做法是约定一个前缀。例:项目中有两个模块:article和message board,它们各自有一个处理用户留言的类comment。之后我可能想要增加对所有用户留言的一些信息统计功能,比如说我想得到所有留言的数量。这时候调用它们comment提供的方法是很好的做法,但是同时引入各自的comment类显然是不行的,代码会出错,在另一个地方重写任何一个comment也会降低维护性。那这时只能重构类名,我约定了一个命名规则,在类名前面加上模块名,像这样:article_comment、messageboard_comment 可以看到,名字变得很长,那意味着以后使用comment的时候会写上更多的代码(至少字符多了)。并且,以后如果要对各个模块增加更多的一些整合功能,或者是互相调用,发生重名的时候就需要重构名字。当然在项目开始的时候就注意到这个问题,并规定命名规则就能很好的避免这个问题。另一个解决方法可以考虑使用命名空间。
注明:
本文提到的常量:php5.3开始const关键字可以用在类的外部。const和define都是用来声明常量的(它们的区别不详述),但是在命名空间里,define的作用是全局的,而const则作用于当前空间。我在文中提到的常量是指使用const声明的常量。
基础
命名空间将代码划分出不同的空间(区域),每个空间的常量、函数、类(为了偷懒,我下边都将它们称为元素)的名字互不影响,这个有点类似我们常常提到的‘封装'的概念。创建一个命名空间需要使用namespace关键字,这样: 复制代码代码如下:
//创建一个名为'article'的命名空间 namespace article;?>
要注意的是,当前脚本文件的第一个命名空间前面不能有任何代码,下面的写法都是错误的: 复制代码代码如下: //例一
//在脚本前面写了一些逻辑代码
//例二
//在脚本前面输出了一些字符
为什么要说第一个命名空间呢?因为同一脚本文件中可以创建多个命名空间。
下面我创建了两个命名空间,顺便为这两个空间各自添加了一个comment类元素: 复制代码代码如下:
//创建一个名为'article'的命名空间 namespace article;//此comment属于article空间的元素 class comment { }
//创建一个名为'messageboard'的命名空间 namespace messageboard;//此comment属于messageboard空间的元素 class comment { } ?>
在不同空间之间不可以直接调用其它元素,需要使用命名空间的语法: 复制代码代码如下:
namespace article;class comment { }
namespace messageboard;class comment { } //调用当前空间(messageboard)的comment类 $comment = new comment();//调用article空间的comment类
$article_comment = new articlecomment();?>
可以看到,在messageboard空间中调用article空间里的comment类时,使用了一种像文件路径的语法: 空间名元素名
除了类之外,对函数和常量的用法是一样的,下面我为两个空间创建了新的元素,并在messageboard空间中输出了它们的值。复制代码代码如下:
namespace messageboard;const path = '/message_board';function getcommenttotal(){ return 300;} class comment { } //调用当前空间的常量、函数和类 echo path;///message_board echo getcommenttotal();//300 $comment = new comment();//调用article空间的常量、函数和类 echo articlepath;///article echo articlegetcommenttotal();//100 $article_comment = new articlecomment();?>
然后我的确得到了article空间的元素数据。子空间
命名空间的调用语法像文件路径一样是有道理的,它允许我们自定义子空间来描述各个空间之间的关系。抱歉我忘了说,article和message board这两个模块其实都是处于同一个blog项目内。如果用命名空间来表达它们的关系,是这样: 复制代码代码如下:
//我用这样的命名空间表示处于blog下的article模块 namespace blogarticle;class comment { }
//我用这样的命名空间表示处于blog下的message board模块 namespace blogmessageboard;class comment { } //调用当前空间的类
$comment = new comment();//调用blogarticle空间的类
$article_comment = new blogarticlecomment();?>
而且,子空间还可以定义很多层次,比如说 blogarticlearchivesdate
公共空间
我有一个脚本文件,里面有一些好用的函数和类: 复制代码代码如下:
function getip(){ } class filterxss { } ?>
在一个命名空间里引入这个脚本,脚本里的元素不会归属到这个命名空间。如果这个脚本里没有定义其它命名空间,它的元素就始终处于公共空间中: 复制代码代码如下:
namespace blogarticle;//引入脚本文件
include './';$filter_xss = new filterxss();//出现致命错误:找不到blogarticlefilterxss类 $filter_xss = new filterxss();//正确 ?>
调用公共空间的方式是直接在元素名称前加 就可以了,否则php解析器会认为我想调用当前空间下的元素。除了自定义的元素,还包括php自带的元素,都属于公共空间。
要提一下,其实公共空间的函数和常量不用加 也可以正常调用(不明白php为什么要这样做),但是为了正确区分元素,还是建议调用函数的时候加上
名称术语
在说别名和导入之前,需要知道关于空间三种名称的术语,以及php是怎样解析它们的。官方文档说得非常好,我就直接拿来套了。
1.非限定名称,或不包含前缀的类名称,例如 $comment = new comment()。如果当前命名空间是blogarticle,comment将被解析为blogarticlecomment。如果使用comment的代码不包含在任何命名空间中的代码(全局空间中),则comment会被解析为comment。
2.限定名称,或包含前缀的名称,例如 $comment = new articlecomment()。如果当前的命名空间是blog,则comment会被解析为blogarticlecomment。如果使用comment的代码不包含在任何命名空间中的代码(全局空间中),则comment会被解析为comment。
3.完全限定名称,或包含了全局前缀操作符的名称,例如 $comment = new articlecomment()。在这种情况下,comment总是被解析为代码中的文字名(literal name)articlecomment。
其实可以把这三种名称类比为文件名(例如 )、相对路径名(例如./article/)、绝对路径名(例如 /blog/article/),这样可能会更容易理解。我用了几个示例来表示它们: 复制代码代码如下:
//创建空间blog namespace blog;class comment { } //非限定名称,表示当前blog空间 //这个调用将被解析成 blogcomment();$blog_comment = new comment();//限定名称,表示相对于blog空间
//这个调用将被解析成 blogarticlecomment();$article_comment = new articlecomment();//类前面没有反斜杆 //完全限定名称,表示绝对于blog空间 //这个调用将被解析成 blogcomment();$article_comment = new blogcomment();//类前面有反斜杆 //完全限定名称,表示绝对于blog空间
//这个调用将被解析成 blogarticlecomment();$article_comment = new blogarticlecomment();//类前面有反斜杆
//创建blog的子空间article namespace blogarticle;class comment { } ?>
其实之前我就一直在使用非限定名称和完全限定名称,现在它们终于可以叫出它们的名称了。
别名和导入
别名和导入可以看作是调用命名空间元素的一种快捷方式。php并不支持导入函数或常量。它们都是通过使用use操作符来实现: 复制代码代码如下:
namespace blogarticle;class comment { }
//创建一个bbs空间(我有打算开个论坛)namespace bbs;//导入一个命名空间 use blogarticle;//导入命名空间后可使用限定名称调用元素 $article_comment = new articlecomment();//为命名空间使用别名 use blogarticle as arte;//使用别名代替空间名
$article_comment = new artecomment();//导入一个类
use blogarticlecomment;//导入类后可使用非限定名称调用元素 $article_comment = new comment();//为类使用别名
use blogarticlecomment as comt;//使用别名代替空间名
$article_comment = new comt();?>
我注意到,如果导入元素的时候,当前空间有相同的名字元素将会怎样?显然结果会发生致命错误。
例:
复制代码代码如下:
namespace blogarticle;class comment { }
namespace bbs;class comment { } class comt { }
//导入一个类
use blogarticlecomment;$article_comment = new comment();//与当前空间的comment发生冲突,程序产生致命错误 //为类使用别名
use blogarticlecomment as comt;$article_comment = new comt();//与当前空间的comt发生冲突,程序产生致命错误 ?> 动态调用
php提供了namespace关键字和__namespace__魔法常量动态的访问元素,__namespace__可以通过组合字符串的形式来动态访问: 复制代码代码如下:
namespace blogarticle;const path = '/blog/article';class comment { }
//namespace关键字表示当前空间 echo namespacepath;///blog/article $comment = new namespacecomment();//魔法常量__namespace__的值是当前空间名称 echo __namespace__;//blogarticle //可以组合成字符串并调用
$comment_class_name = __namespace__.'comment';$comment = new $comment_class_name();?>
字符串形式调用问题
上面的动态调用的例子中,我们看到了字符串形式的动态调用方式,如果要使用这种方式要注意两个问题。
1.使用双引号的时候特殊字符可能被转义 复制代码代码如下:
namespace blogarticle;class name { } //我是想调用blogarticlename $class_name = __namespace__.“name”;//但是n将被转义为换行符 $name = new $class_name();//发生致命错误 ?>
2.不会认为是限定名称
php在编译脚本的时候就确定了元素所在的空间,以及导入的情况。而在解析脚本时字符串形式调用只能认为是非限定名称和完全限定名称,而永远不可能是限定名称。复制代码代码如下:
namespace blog;//导入common类
use blogarticlecommon;//我想使用非限定名称调用blogarticlecommon $common_class_name = 'common';//实际会被当作非限定名称,也就表示当前空间的common类,但我当前类没有创建common类 $common = new $common_class_name();//发生致命错误:common类不存在 //我想使用限定名称调用blogarticlecommon $common_class_name = 'articlecommon';//实际会被当作完全限定名称,也就表示article空间下的common类,但我下面只定义了blogarticle空间而不是article空间
$common = new $common_class_name();//发生致命错误:articlecommon类不存在
namespace blogarticle;class common { } ?> 总结
我对php的命名空间刚刚接触,也不能随便给一些没有实践的建议。我个人认为命名空间的作用和功能都很强大,如果要写插件或者通用库的时候再也不用担心重名问题。不过如果项目进行到一定程度,要通过增加命名空间去解决重名问题,我觉得工作量不会比重构名字少。也不得不承认它的语法会对项目增加一定的复杂度,因此从项目一开始的时候就应该很好的规划它,并制定一个命名规范。
标准命名空间 命名空间的概念及作用篇二
进行对象的序列化,的引用,ization命名空间。这样就可以在文件中使用序列化所需要的各种特性了。
imports ization
如果对xml serialization缺少了解,请首先参考拙文:中实现对象序列化
2005-04-05
对象序列化
上面的例子包含了典型的xml中常见的各种元素:xml声明、xml根节点、xml节点、xml属性、xml集合。除xml声明外,中都有对应的特性用于定义这些元素。这些特性包括:xmlrootattribute、xmltypeattribute、xmlelementattribute、xmlattributeattribute、xmlarrayattribute和xmlarrayitemattribute。另外,还有两个常用的特性,xmlignoreattribute用于标记在对象序列化时需要被忽略的部分,xmlincludeattribute用于标记在生成xml schema时需要包括的类型。如果没有显式地标记任何特性,那么默认类的特性为xmltypeattribute、类成员的特性为xmlelementattribute,且名称为类或类成员的名称。例如:
public class order
public id as string
public orderdate as string
end class
如果不做任何特性标记,使用下面的代码序列化时: dim o as new order
with o
.id = 123456
.orderdate = tdatestring
end with
dim writer as new xmltextwriter(“”, 8)
dim serializer as new xmlserializer(gettype(order))
ting = ed ize(writer, o)
序列化后的xml为:
123456
2005-4-11
可以看到,对应order类,而
和
分别对应order类中的字段id和orderdate。另外,多了一个xml声明和两个xml命名空间。
自动添加的,但是encoding是在xmltextwriter中指定的,如果不指定encoding,那么xml声明只有。 1.1,这个版本中只支持xml 1.0版本。另外,如果不指定encoding,那么默认的编码可能也是utf8(没找到相关的资料)。
.net默认为order类添加了xmlschema和xmlschema-instance两个w3c的命名空间。该命名空间也可以自己指定,方法是使用xmlserializer的另一个serialize方法。
dim ns as new xmlserializernamespaces (“", ”“)ting = ed ize(writer, o, ns)
要将类序列化为xml节点:
_
public class order‘ any code class
要将类序列化为xml根节点:
_
public class order‘ any code class
当在类中同时使用xmlrootattribute、xmltypeattribute时,序列化文档中的类型以xmlrootattribute为准:
_
public class order‘ any code class
要将类成员序列化为xml节点:
_
public id as string要将类成员序列化为xml属性:
_
public id as string要将类成员序列化为xml集合:
_
public class order_
public id as stringpublic orderdate as string
_
public items as new arraylistend class
_
public class orderitempublic name as string
end class
使用特性的一个好处是:可以在代码和序列化的文档中使用不同的编码规范。
标准命名空间 命名空间的概念及作用篇三
基本技能 12.5:命名空间
我们曾经在第一章中对命名空间进行简单的介绍。这里我们将对命名空间进行深入的讨论。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突。在c++中,变量、函数和类都是大量存在的。如果没有命名空间,这些变量、函数、类的名称将都存在于全局命名空间中,会导致很多冲突。比如,如果我们在自己的程序中定义了一个函数toupper(),这将重写标准库中的toupper()函数,这是因为这两个函数都是位于全局命名空间中的。命名冲突还会发生在一个程序中使用两个或者更多的第三方库的情况中。此时,很有可能,其中一个库中的名称和另外一个库中的名称是相同的,这样就冲突了。这种情况会经常发生在类的名称上。比如,我们在自己的程序中定义了一个stack类,而我们程序中使用的某个库中也可能定义了一个同名的类,此时名称就冲突了。
namespace关键字的出现就是针对这种问题的。由于这种机制对于声明于其中的名称都进行了本地化,就使得相同的名称可以在不同的上下文中使用,而不会引起名称的冲突。或许命名空间最大的受益者就是c++中的标准库了。在命名空间出现之前,整个c++库都是定义在全局命名空间中的(这当然也是唯一的命名空间)。引入命名空间后,c++库就被定义到自己的名称空间中了,称之为std。这样就减少了名称冲突的可能性。我们也可以在自己的程序中创建自己的命名空间,这样可以对我们认为可能导致冲突的名称进行本地化。这点在我们创建类或者是函数库的时候是特别重要的。命名空间基础
namespace关键字使得我们可以通过创建作用范围来对全局命名空间进行分隔。本质上来讲,一个命名空间就定义了一个范围。定义命名空间的基本形式如下:
namespace 名称{//声明}
在命名空间中定义的任何东西都局限于该命名空间内。
下面就是一个命名空间的例子,其中对一个实现简单递减计数器的类进行了本地化。在该命名空间中定义了计数器类用来实现计数;其中的upperbound和lowerbound用来表示计数器的上界和下界。//演示命名空间
namespace counternamespace {
int upperbound;
int lowerbound;
class counter {
int count;
public:
counter(int n){
if(n <= upperbound){
count = n;}
else {
count = upperbound;} }
void reset(int n){
if(n < upperbound){
count = n;} }
int run(){
if(count > lowerbound){
return count--;}
else
{
return lowerbound;
}
} };}
其中的upperbound,lowerbound和类counter都是有命名空间counternamespace定义范围的组成部分。
在命名空间中声明的标识符是可以被直接引用的,不需要任何的命名空间的修饰符。例如,在counternamesapce命名空间中,run()函数中就可以直接在语句中引用lowerbound:
if(count > lowerbound){
return count--;}
然而,既然命名空间定义了一个范围,那么我们在命名空间之外就需要使用范围解析运算符来引用命名空间中的对象。例如,在命名空间counternamespace定义的范围之外给upperbound赋值为10,就必须这样写:
counternamespace::upperbound = 10;或者在counternamespace定义的范围之外想要声明一个counter类的对象就必须这样写:
counternamespace::counter obj;
一般来讲,在命名空间之外想要访问命名空间内部的成员需要在成员前面加上命名空间和范围解析运算符。
下面的程序演示了如何使用counternamespace这个命名空间: //演示命名空间
#include
using namespace std;
namespace counternamespace {int upperbound;
int lowerbound;
class counter {
int count;
public:
counter(int n){
if(n <= upperbound){
count = n;}
else {
count = upperbound;} }
void reset(int n){
if(n < upperbound){
count = n;} }
int run(){
if(count > lowerbound){
return count--;} else
return lowerbound;} };}
int main(){
counternamespace::upperbound = 100;
counternamespace::lowerbound = 0;
counternamespace::counter ob1(10);
int i;
do {
i = ();
cout << i << “ ”;
} while(i > counternamespace::lowerbound);
cout << endl;
counternamespace::counter ob2(20);
do {
i = ();
cout << i << “ ”;
} while(i > counternamespace::lowerbound);
cout << endl;
(100);
do {
i = ();
cout << i << “ ”;
} while(i > counternamespace::lowerbound);
cout << endl;
return 0;}
请注意:counter类以及upperbound和lowerbound的引用都是在前面加上了counternamespace修饰符。但是,一旦声明了counter类型的对象,就没有必须在对该对象的任何成员使用这种修饰符了。()是可以被直接调用的。其中的命名空间是可以被解析的。
相同的空间名称是可以被多次声明的,这种声明向相互补充的。这就使得命名空间可以被分割到几个文件中甚至是同一个文件的不同地方中。例如:
namespace ns {
int i;}
//...namespace ns {
int j;}
其中命名空间ns被分割成两部分,但是两部分的内容却是位于同一命名空间中的。也就是ns。最后一点:命名空间是可以嵌套的。也就是说可以在一个命名空间内部声明另外的命名空间。using关键字
如果在程序中需要多次引用某个命名空间的成员,那么按照之前的说法,我们每次都要使用范围解析符来指定该命名空间,这是一件很麻烦的事情。为了解决这个问题,人们引入了using关键字。using语句通常有两种使用方式:
using namespace 命名空间名称;using 命名空间名称::成员;
第一种形式中的命名空间名称就是我们要访问的命名空间。该命名空间中的所有成员都会被引入到当前范围中。也就是说,他们都变成当前命名空间的一部分了,使用的时候不再需要使用范围限定符了。第二种形式只是让指定的命名空间中的指定成员在当前范围中变为可见。我们用前面的counternamespace来举例,下面的using语句和赋值语句都是有效的:
using counternamespace::lowerbound;//只有lowerbound当前是可见的 lowerbound = 10;//这样写是合法的,因为lowerbound成员当前是可见的 using counternamespace;//所有counternamespace空间的成员当前都是可见的
upperbound = 100;//这样写是合法的,因为所有的counternamespace成员目前都是可见的
下面是我们对之前的程序进行修改的结果: //使用using
#include
using namespace std;namespace counternamespace { int upperbound;
int lowerbound;
class counter {
int count;
public:
counter(int n){
if(n < upperbound){
count = n;}
else {
count = upperbound;} }
void reset(int n){
if(n <= upperbound){
count = n;} }
int run(){
if(count > lowerbound){
return count--;}
else {
return lowerbound;} } };}
int main(){
//这里只是用counternamespace中的upperbound using counternamespace::upperbound;
//此时对upperbound的访问就不需要使用范围限定符了 upperbound = 100;
//但是使用lowerbound的时候,还是需要使用范围限定符的 counternamespace::lowerbound = 0;counternamespace::counter ob1(10);
int i;
do {
i = ();cout << i << “ ”;
}while(i > counternamespace::lowerbound);cout << endl;
//下面我们将使用整个counternamespace的命名空间
using namespace counternamespace;counter ob2(20);
do {
i = ();cout << i << “ ”;
}while(i > counternamespace::lowerbound);cout << endl;
(100);lowerbound = 90;
do {
i = ();cout << i << “ ”;}while(i > lowerbound);
return 0;}
上面的程序还为我们演示了重要的一点:当我们用using引入一个命名空间的时候,如果之前有引用过别的命名空间(或者同一个命名空间),则不会覆盖掉对之前的引入,而是对之前引入内容的补充。也就是说,到最后,上述程序中的std和counternamespace这两个命名空间都变成全局空间了。没有名称的命名空间
有一种特殊的命名空间,叫做未命名的命名空间。这种没有名称的命名空间使得我们可以创建在一个文件范围里可用的命名空间。其一般形式如下: namespace {
//声明 }
我们可以使用这种没有名称的命名空间创建只有在声明他的文件中才可见的标识符。也即是说,只有在声明这个命名空间的文件中,它的成员才是可见的,它的成员才是可以被直接使用的,不需要命名空间名称来修饰。对于其他文件,该命名空间是不可见的。我们在前面曾经提到过,把全局名称的作用域限制在声明他的文件的一种方式就是把它声明为静态的。尽管c++是支持静态全局声明的,但是更好的方式就是使用这里的未命名的命名空间。std命名空间
标准c++把自己的整个库定义在std命名空间中。这就是本书的大部分程序都有下面代码的原因:
using namespace std;
这样写是为了把std命名空间的成员都引入到当前的命名空间中,以便我们可以直接使用其中的函数和类,而不用每次都写上std::。
当然,我们是可以显示地在每次使用其中成员的时候都指定std::,只要我们喜欢。例如,我们可以显示地采用如下语句指定cout:
std::cout << “显示使用std::来指定cout”;
如果我们的程序中只是少量地使用了std命名空间中的成员,或者是引入std命名空间可能导致命名空间的冲突的话,我们就没有必要使用using namespace std;了。然而,如果在程序中我们要多次使用std命名空间的成员,则采用using namespace std;的方式把std命名空间的成员都引入到当前命名空间中会显得方便很多,而不用每次都单独在使用的时候显示指定。
标准命名空间 命名空间的概念及作用篇四
c++命名空间namespace 虽然使用命名空间的方法,有多种可供选择。但是不能贪图方便,一味使用using 指令,这样就完全背离了设计命名空间的初衷,也失去了命名空间应该具有的防止名称冲突的功能。
一般情况下,对偶尔使用的命名空间成员,应该使用命名空间的作用域解析运算符来直接给名称定位。而对一个大命名空间中的经常要使用的少数几个成员,提倡使用using声明,而不应该使用using编译指令。只有需要反复使用同一个命名空间的许多数成员时,使用using编译指令,才被认为是可取的。
例如,如果一个程序()只使用一两次cout,而且也不使用std命名空间中的其他成员,则可以使用命名空间的作用域解析运算符来直接定位。如: #include
……
std::cout << “hello, world!” << std::endl;std::cout << “outer::i = ” << outer::i << “, inner::i = ” << outer::inner::i << std::endl;又例如,如果一个程序要反复使用std命名空间中的cin、cout和cerr(),而不怎么使用其他std命名空间中的其他成员,则应该使用using 声明而不是using指令。如:
#include
……
using std::cout;cout << “hello, world!” << endl;cout << “outer::i = ” << outer::i << “, inner::i = ” << outer::inner::i << endl;4)命名空间的名称l
命名空间别名 标准c++引入命名空间,主要是为了避免成员的名称冲突。若果用户都给自己的命名空间取简短的名称,那么这些(往往同是全局级的)命名空间本身,也可能发生名称冲突。如果为了避免冲突,而为命名空间取很长的名称,则使用起来就会不方便。这是一个典型的两难问题。
标准c++为此提供了一种解决方案——命名空间别名,格式为: namespace 别名 = 命名空间名;例如:(at&t美国电话电报公司)
namespace american_telephone_and_telegraph { // 命名空间名太长
class string {
string(const char*);
// ……
} }
american_telephone_and_telegraph::string s1 // 使用不方便
= new american_telephone_and_telegraph::string(“grieg”);
namespace att = american_telephone_and_telegraph;// 定义别名
att::string s2 = new att::string(“bush”);// 使用方便 att::string s3 = new att::string(“nielsen”);
l
无名命名空间 标准c++引入命名空间,除了可以避免成员的名称发生冲突之外,还可以使代码保持局部性,从而保护代码不被他人非法使用。如果你的目的主要是后者,而且又为替命名空间取一个好听、有意义、且与别人的命名空间不重名的名称而烦恼的话,标准c++还允许你定义一个无名命名空间。你可以在当前编译单元中(无名命名空间之外),直接使用无名命名空间中的成员名称,但是在当前编译单元之外,它又是不可见的。无名命名空间的定义格式为: namespace {
声明序列可选 } 实际上,上面的定义等价于:(标准c++中有一个隐含的使用指令)
namespace $$$ {
声明序列可选 } using namespace $$$;例如: namespace {
int i;
void f(){/*……*/} } int main(){
i = 0;// 可直接使用无名命名空间中的成员i
f();// 可直接使用无名命名空间中的成员f()}
标准命名空间 命名空间的概念及作用篇五
本讲基本要求
* 掌握:命名空间的作用及定义;如何使用命名空间。
* 了解:使用早期的函数库
重点、难点
◆命名空间的作用及定义;如何使用命名空间。
在学习本书前面各章时,读者已经多次看到在程序中用了以下语句: using namespace std;
这就是使用了命名空间std。在本讲中将对它作较详细的介绍。
一、为什么需要命名空间(问题提出)
命名空间是ansic++引入的可以由用户命名的作用域,用来处理程序中常见的同名冲突。
在c语言中定义了3个层次的作用域,即文件(编译单元)、函数和复合语句。c++又引入了类作用域,类是出现在文件内的。在不同的作用域中可以定义相同名字的变量,互不于扰,系统能够区别它们。
1、全局变量的作用域是整个程序,在同一作用域中不应有两个或多个同名的实体(enuty),包括变量、函数和类等。
例:如果在文件中定义了两个类,在这两个类中可以有同名的函数。在引用时,为了区别,应该加上类名作为限定: class a //声明a类
{ public:
void funl();//声明a类中的funl函数
private:
int i; };
void a::funl()//定义a类中的funl函数
{„„„„}
class b //声明b类
{ public:
void funl(); //b类中也有funl函数
void fun2(); };
void b::funl()//定义b类中的funl函数
{ „„„„} 这样不会发生混淆。
在文件中可以定义全局变量(global variable),它的作用域是整个程序。如果在文件a中定义了一个变量a int a=3;
在文件b中可以再定义一个变量a int a=5;在分别对文件a和文件b进行编译时不会有问题。但是,如果一个程序包括文件a和文件b,那么在进行连接时,会报告出错,因为在同一个程序中有两个同名的变量,认为是对变量的重复定义。
可以通过extern声明同一程序中的两个文件中的同名变量是同一个变量。如果在文件b中有以下声明: extem int a;
表示文件b中的变量a是在其他文件中已定义的变量。由于有此声明,在程序编译和连接后,文件a的变量a的作用域扩展到了文件b。如果在文件b中不再对a赋值,则在文件b中用以下语句输出的是文件a中变量a的值: cout<
2、程序中就会出现名字冲突。
在简单的程序设计中,只要人们小心注意,可以争取不发生错误。但是,一个大型的应用软件,往往不是由一个人独立完成的,而是由若干人合作完成的,不同的人分别完成不同的部分,最后组合成一个完整的程序。假如不同的人分别定义了类,放在不同的头文件中,在主文件(包含主函数的文件)需要用这些类时,就用#include命令行将这些头文件包含进来。由于各头文件是由不同的人设计的,有可能在不同的头文件中用了相同的名字来命名所定义的类或函数。例4 名字冲突
程序员甲在头文件headerl.h中定义了类student和函数fun。//例4中的头文件header1(头文件1,没其文件名为cc8-4-h1.h)#include
#include
using namespace std;class student //声明student类
{ public: student(int n,string nam,int a){ num=n;name=nam;age=a;} void get_data();private: int num;string name;int age;};void student::get_data()//成员函数定义 { cout<wang 18 2.82843 如果程序员乙写了头文件header2.h,在其中除了定义其他类以外,还定义了类student和函数fun,但其内容与头文件headerl.h中的student和函数fun有所不同。//例4中的头文件header2 #include
#include
using namespace std;class student //声明student类 { public: student(int n,string nam,char s)//参数与headerl中的student不同
{ num=n;name=nam;sex=s;} void get_data();private: int num;string name;char sex;};//此项与headerl不同void student::get_data()//成员函数定义 { cout<
double fun(double a,double b)//定义全局函数
{ return sqrt(a-b);} //返回值与headerl中的fun函数不同 //头文件2中可能还有其他内容
假如主程序员在其程序中要用到headerl.h中的student和函数fun,因而在程序中包含了头文件headerl.h,同时要用到头文件header2.h中的一些内容(但对header2.h中包含与headerl.h中的student类和fun函数同名而内容不同的类和函数并不知情,因为在一个头文件中往往包含许多不同的信息,而使用者往往只关心自己所需要的部分,而不注意其他内容),因而在程序中又包含了头文件header2.h。如果主文件(包含主函数的文件)如下: #include
using namespace std;#include ”header1.h“//包含头文件l #include ”header2.h“//包含头文件2 int main(){ student stud1(101,”wang“,18);_data();cout<
3、全局命名空间污染(global namespace pollution)。
在程序中还往往需要引用一些库(包括c++编译系统提供的库、由软件开发商提供的库或者用户自己开发的库),为此需要包含有关的头文件。如果在这些库中包含有与程序的全局实体同名的实体,或者不同的库中有相同的实体名,则在编译时就会出现名字冲突。
为了避免这类问题的出现,人们提出了许多方法,例如:将实体的名字写得长—些(包含十几个或几十个字母和字符);把名字起得特殊一些,包括一些特殊的字符;由编译系统提供的内部全局标识符都用下划线作为前缀,如_complex(),以避免与用户命名的实体同名;由软件开发商提供的实体的名字用特定的字符作为前缀。但是这样的效果并不理想,而且增加了阅读程序的难度,可读性降低了。c语言和早期的c++语言没有提供有效的机制来解决这个问题,没有使库的提供者能够建立自己的命名空间的工具。人们希望ansi c++标准能够解决这个问题,提供—种机制、一种工具,使由库的设计者命名的全局标识符能够和程序的全局实体名以及其他库的全局标识符区别开来。
二、什么是命名空间(解决方案)
命名空间:实际上就是一个由程序设计者命名的内存区域,程序设计者可以根据需要指定一些有名字的空间域,把一些全局实体分别放在各个命名空间中,从而与其他全局实体分隔开来。
如: namespace ns1 //指定命名中间nsl
{ int a;
double b;} namespace是定义命名空间所必须写的关键字,nsl是用户自己指定的命名空间的名字(可以用任意的合法标识符,这里用ns1是因为ns是namespace的缩写,含义请楚),在花括号内是声明块,在其中声明的实体称为命名空间成员(namespace member)。现在命名空间成员包括变量a和b,注意a和b仍然是全局变量,仅仅是把它们隐藏在指定的命名空间中而已。如果在程序中要使用变量a和b,必须加上命名空间名和作用域分辨符“::”,如nsl::a,nsl::b。这种用法称为命名空间限定(qualified),这些名字(如nsl::a)称为被限定名(qualified name)。c++中命名空间的作用类似于操作系统中的目录和文件的关系,由于文件很多,不便管理,而且容易重名,于是人们设立若干子目录,把文件分别放到不同的子目录中,不同子目录中的文件可以同名。调用文件时应指出文件路径。
命名空间的作用:是建立一些互相分隔的作用域,把一些全局实体分隔开来。以免产生老点名叫李相国时,3个人都站起来应答,这就是名字冲突,因为他们无法辨别老师想叫的是哪一个李相国,同名者无法互相区分。为了避免同名混淆,学校把3个同名的学生分在3个班。这样,在小班点名叫李相国时,只会有一个人应答。也就是说,在该班的范围(即班作用域)内名字是惟一的。如果在全校集合时校长点名,需要在全校范围内找这个学生,就需要考虑作用域问题。如果校长叫李相国,全校学生中又会有3人一齐喊“到”,因为在同一作用域中存在3个同名学生。为了在全校范围内区分这3名学生,校长必须在名字前加上班号,如高三甲班的李相国,或高三乙班的李相国,即加上班名限定。这样就不致产生混淆。
可以根据需要设置许多个命名空间,每个命名空间名代表一个不同的命名空间域,不同的命名空间不能同名。这样,可以把不同的库中的实体放到不同的命名空间中,或者说,用不同的命名空间把不同的实体隐蔽起来。过去我们用的全局变量可以理解为全局命名空间,独立于所有有名的命名空间之外,它是不需要用namespace声明的,实际上是由系统隐式声明的,存在于每个程序之中。
在声明一个命名空间时,花括号内不仅可以包括变量,而且还可以包括以下类型: ·变量(可以带有初始化); ·常量;
·数(可以是定义或声明); ·结构体; ·类; ·模板;
·命名空间(在一个命名空间中又定义一个命名空间,即嵌套的命名空间)。例如
namespace nsl { const int rate=0.08; //常量 doublepay;
//变量
doubletax()
//函数
{return a*rate;} namespacens2
//嵌套的命名空间
{int age;} }
如果想输出命名空间nsl中成员的数据,可以采用下面的方法: cout<
cout<
可以看到命名空间的声明方法和使用方法与类差不多。但它们之间有一点差别:在声明类时在右花括号的后面有一分号,而在定义命名空间时,花括号的后面没有分号。
三、使用命名空间解决名字冲突(使用指南)有了以上的基础后,就可以利用命名空间来解决名字冲突问题。现在,对例4程序进行修改,使之能正确运行。
例5 利用命名空间来解决例4程序名字冲突问题。
修改两个头文件,把在头文件中声明的类分别放在两个不同的命名空间中。//例8.5中的头文件1,文件名为header1.h using namespace std;#include
#include
namespace ns1 //声明命名空间ns1 { class student //在命名空间nsl内声明student类
{ public: student(int n,string nam,int a){ num=n;name=nam;age=a;} void get_data();private: int num;string name;int age;};void student::get_data()//定义成员函数{ cout<
double fun(double a,double b)//在命名空间n引内定义fun函数 { return sqrt(a+b);} } //例8.5中的头文件2,文件名为header2.h #include
#include
namespace ns2 //声明命名空间ns2 { class student { public: student(int n,string nam,char s){ num=n;name=nam;sex=s;} void get_data();private: int num;string name;char sex;};
void student::get_data(){ cout<double fun(double a,double b){ return sqrt(a-b);} } //main file #include
#include ”header1.h“ //包含头文件l #include ”header2.h“ //包含头文件2 int main(){ ns1::student stud1(101,”wang“,18);//用命名空间nsl中声明的student类定义studt _data();//不要写成ns1::_data();
cout<ns2::student stud2(102,”li“,'f');//用命名空间ns2中声明的student类定义stud2 _data();cout<
分析例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::_data()。程序能顺利通过编译,并得到以下运行结果: 101 wang l9(对象studl中的数据)2.82843(/5+3的值)102 li f(对象studg中的数据)1.41421(/5-2的值)
四、使用命名空间成员的方法 从上面的介绍可以知道,在引用命名空间成员时,要用命名空间名和作用域分辨符对命名空间成员进行限定,以区别不同的命名空间中的同名标识符。即: 命名空间名::命名空间成员名
这种方法是有效的,能保证所引用的实体有惟一的名字。但是如果命名空间名字比较长,尤其在有命名空间嵌套的情况下,为引用一个实体,需要写很长的名字。在一个程序中可能要多次引用命名空间成员,就会感到很不方便。1、使用命名空间别名
可以为命名空间起一个别名(namespace alias),用来代替较长的命名空间名。如 namespace television //声明命名空间,名为television {...} 可以用一个较短而易记的别名代替它。如:
namespace tv=television; //别名tv与原名television等价
也可以说,别名tv指向原名television,在原来出现television的位置都可以无条件地用tv来代替。
2、使用using命名空间成员名
using后面的命名空间成员名必须是由命名空间限定的名字。例如: using nsl::student; 以上语句声明:在本作用域(using语句所在的作用域)中会用到命名空间ns1中的成员student,在本作用域中如果使用该命名空间成员时,不必再用命名空间限定。例如在用上面的using声明后,在其后程序中出现的student就是隐含地指nsl::student。
using声明的有效范围是从using语句开始到using所在的作用域结束。如果在以上的using语句之后有以下语句:
student studl(101,”wang“,18); //此处的student相当于ns1::student 上面的语句相当于
nsl::student studl(101,”wang“,18); 又如
using nsl::fun; //声明其后出现的fun是属于命名空间nsl中的fun cout<
但是要注意:在同一作用域中用using声明的不同命名空间的成员中不能有同名的成员。例如:
usmgnsl::student; //声明其后出现的student是命名空间nsl中的student usmgns2::student; //声明其后出现的student是命名空间ns2小的student student stud1; //请问此处的student是哪个命名中间中的student? 产生了二义性,编译出错。
3、使用using namespace命名空间名
用上面介绍的using命名空间成员名,一次只能声明一个命名空间成员,如果在一个命名空间中定义了10个实体,就需要使用10次using命名空间成员名。能否在程序中用一个语句就能一次声明一个命名空间中的全部成员呢? c++提供了using namespace语句来实现这一目的。using namespace语句的一般格式为 using namespace 命名空间名; 例如
using nanlespace nsl;
声明了在本作用域中要用到命名空间nsl中的成员,在使用该命名空间的任何成员时都不必用命名空间限定。如果在作了上面的声明后有以下语句: student studl(101,”wang”,18); //student隐含指命名中间nsl中的student cout<
cout<
student stud2(102,“li”,'r'); _data();
coutt<
五、无名的命名空间
以上介绍的是有名字的命名空间,c++还允许使用没有名字的命名空间,如在文件a中声明了以下的无名命名空间:
namespace //命名空间没有名字 { void fun()//定义命名空间成员 { cout<<“ok.”<
则执行无名命名空间中的成员fun函数,输出”ok.”。
在本程序中的其他文件中也无法使用该fun函数,也就是把fun函数的作用域限制在本文件范围中。可以联想到:在c浯言中可以用static声明一个函数,其作用也是使该函数的作用域限于本文件。c++保留了用static声明函数的用法,同时提供了用无名命名空间来实现这一功能。随着越来越多的c++编译系统实现了ansi c++建议的命名空间的机制,相信使用无名命名空间成员的方法将会取代以前习惯用的对全局变量的静态声明。
六、标准命名空间std 为了解决c++标准库中的标识符与程序中的全局标识符之间以及不同库中的标识符之间的同名冲突,应该将不同库的标识符在不同的命名空间中定义(或声明)。标准c++库的所有的标识符都是在一个名为std的命名空间中定义的,或者说标准头文件(如iostream)中函数、类、对象和类模板是在命名空间std中定义的。std是standard(标准)的缩写,表示这是存放标准库的有关内容的命名空间,含义请楚,不必死记。
这样,在程序中用到c++标准库时,需要使用std作为限定。如
std::cout<<“ok.”<
这样,在std中定义和声明的所有标识符在本文件中都可以作为全局量来使用。但是应当绝对保证在程序中不出现与命名空间std的成员同名的标识符,例如在程序中不能再定义一个名为cout的对象。由于在命名空间std中定义的实体实在太多,有时程序设计人员也弄不请哪些标识符已在命名空间std中定义过,为减少出错机会,有的专业人员喜欢用若干个"using命名空间成员”声明来代替“using namespace命名空间”声明,如 using std::string; using std::cout; using std::cin;
等。为了减少在每一个程序中都要重复书写以亡的using声明,程序开发者往往把编写应用程序时经常会用到的命名空间std成员的usmg声明组成一个头文件,然后在程序中包含此头文件即可。
如果阅读了多种介绍c++的书,可能会发现有的书的程序中有using namespace语句,有的则没有。有的读者会提出:究竟应该有还是应该没有?应当说:用标准的c++编程,是应该对命名空间std的成员进行声明或限定的(可以采取前面介绍过的任一种方法)。但是目前所用的c++库大多是几年前开发的,当时并没有命名空间,库中的有关内容也没有放在std命名空间中,因而在程序中不必对std进行声明。
七、使用早期的函数库
c语言程序中各种功能基本上都是由函数来实现的,在c语言的发展过程中建立了功能丰富的函数库,c++从c语言继承了这份宝贵的财富。在c++程序中可以使用c语言的函数库。如果要用函数库中的函数,就必须在程序文件中包含有关的头文件,在不同的头文件中,包含了不同的函数的声明。
在c++中使用这些头文件有两种方法。
1、用c语言的传统方法
头文件名包括后缀.h,如stdio.h,math.h等。由于c语言没有命名空间,头文件并不存放在命名空间中,因此在c++程序文件中如果用到带后缀.h的头文件时,不必用命名空间。只需在文件中包含所用的头文件即可。如 #include
2、用c++的新方法c++标准要求系统提供的头文件不包括后缀.h,例如iostream、string。为了表示与c语言的头文件有联系又有区别,c++所用的头文件名是在c语言的相应的头文件名(但不包括后缀.h)之前加一字母c。例如,c语言中有关输入与输出的头文件名为stdio.h在c++中相应的头文件名为cstdio。c语言中的头文件math.h,在c++中相应的头文什名为cmath。c语言中的头文件string.h在c++中相应的头文件名为cstring。注意在c++中,头文件cstnng和头文件strmg不是同一个文件。前者提供c语言中对字符串处理的有关函数(如strcmp,ctrcpy)的声明,后者提供c++中对字符串处理的新功能。此外,由于这些函数都是在命名空间std中声明的,因此在程序中要对命名空间std作声明。如:
#include
#include
using namespace std;
目前所用的大多数c++编译系统既保留了c的用法,又提供丁c++的新方法。下面两种用法等价,可以任选。c传统方法 c++新方法#include
#include
#include
#include
#include
#include
using namespace std;