C++学习笔记十八-命名空间

概述:

为什么需要命名空间:

       在一个给定作用域中定义的每个名字在该作用域中必须是唯一的,对庞大、复杂的应用程序而言,这个要求可能难以满足。

库倾向于定义许多全局名字——主要是模板名、类型名或函数名。在使用来自多个供应商的库编写应用程序的时候,这些名字中有一些几乎不可避免地会发生冲突,这种名字冲突问题称为命名空间污染问题。

      命名空间为防止名字冲突提供了更加可控的机制,命名空间能够划分全局命名空间,这样使用独立开发的库就更加容易了。一个命名空间是一个作用域,通过在命名空间内部定义库中的名字,库的作者(以及用户)可以避免全局名字固有的限制.

 

一、命名空间的定义:命名空间定义以关键字 namespace 开始,后接命名空间的名字。

       1.像其他名字一样,命名空间的名字在定义该命名空间的作用域中必须是唯一的。命名空间可以在全局作用域或其他命名空间作用域内部定义,但不能在函数或类内部定义。

       2. 命名空间名字后面接着由花括号括住的一块声明和定义,可以在命名空间中放入可以出现在全局作用域的任意声明:类、变量(以及它们的初始化)、函数(以及它们的定义)、模板以及其他命名空间。

      3.命名空间作用域不能以分号结束。

 

二、每个命名空间是一个作用域:

      1.定义在命名空间中的实体称为命名空间成员。像任意作用域的情况一样,命名空间中的每个名字必须引用该命名空间中的唯一实体。因为不同命名空间引入不同作用域,所以不同命名空间可以具有同名成员。

      2.从命名空间外部使用命名空间成员:总是使用限定名     namespace_name::member_name

 

三、命名空间可以是不连续的:与其他作用域不同,命名空间可以在几个部分中定义。命名空间由它的分离定义部分的总和构成,命名空间是累积的。一个命名空间的分离部分可以分散在多个文件中,在不同文本文件中的命名空间定义也是累积的。当然,名字只在声明名字的文件中可见,这一常规限制继续应用,所以,如果命名空间的一个部分需要定义在另一文件中的名字,仍然必须声明该名字。

 

四、接口和实现的分离:(命名空间的使用规则是,先在头文件中声明,再在相应的cpp文件中实现)

     1. 命名空间定义可以不连续意味着,可以用分离的接口文件和实现文件构成命名空间,因此,可以用与管理自己的类和函数定义相同的方法来组织命名空间:
         A.定义类的命名空间成员,以及作为类接口的一部分的函数声明与对象声明,可以放在头文件中,使用命名空间成员的文件可以包含这些头文件。
         B.命名空间成员的定义可以放在单独的源文件中.

         C.按这种方式组织命名空间,也满足了不同实体(非内联函数、静态数据成员、变量等)只能在一个程序中定义一次的要求,这个要求同样适用于命名空间中定义的名字。通过将接口和实现分离,可以保证函数和其他我们需要的名字只定义一次,但相同的声明可以在任何使用该实体的地方见到。

 

五、定义命名空间成员:

     1.在命名空间定义的外部定义命名空间成员,用类似于在类外部定义类成员的方式:名字的命名空间声明必须在作用域中,并且定义必须指定该名字所属的命名空间。

// namespace members defined outside the namespace must use qualified names
     cplusplus_primer::Sales_item
     cplusplus_primer::operator+(const Sales_item& lhs,
                                 const Sales_item& rhs)
     {
         Sales_item ret(lhs);
         // ...
     }

这个定义看起来类似于定义在类外部的类成员函数,返回类型和函数名由命名空间名字限定。一旦看到完全限定的函数名,就处于命名空间的作用域中。因此,形参表和函数体中的命名空间成员引用可以使用非限定名引用 Sales_item。

     2.不能在不相关的命名空间中定义成员:虽然可以在命名空间定义的外部定义命名空间成员,对这个定义可以出现的地方仍有些限制,只有包围成员声明的命名空间可以包含成员的定义。例如,operator+ 既可以定义在命名空间 cplusplus_primer中,也可以定义在全局作用域中,但它不能定义在不相关的命名空间中。

 

六、全局命名空间:定义在全局作用域的名字(在任意类、函数或命名空间外部声明的名字)是定义在全局命名空间中的。全局命名空间是隐式声明的,存在于每个程序中。在全局作用域定义实体的每个文件将那些名字加到全局命名空间。

      可以用作用域操作符引用全局命名空间的成员。因为全局命名空间是隐含的,它没有名字,所以记号

     ::member_name
七、嵌套命名空间:
      1.一个嵌套命名空间即是一个嵌套作用域——其作用域嵌套在包含它的命名空间内部。嵌套命名空间中的名字遵循常规规则:外围命名空间中声明的名字被嵌套命名空间中同一名字的声明所屏蔽。嵌套命名空间内部定义的名字局部于该命名空间。外围命名空间之外的代码只能通过限定名引用嵌套命名空间中的名字。
      2.当库提供者需要防止库中每个部分的名字与库中其他部分的名字冲突的时候,嵌套命名空间是很有用的。
 
八、未命名的命名空间:命名空间可以是未命名的,未命名的命名空间在定义时没有给定名字。未命名的命名空间以关键字 namespace 开头,接在关键字 namespace 后面的是由花括号定界的声明块。

1.未命名的命名空间与其他命名空间不同,未命名的命名空间的定义局部于特定文件,从不跨越多个文本文件。

2.未命名的命名空间可以在给定文件中不连续,但不能跨越文件,每个文件有自己的未命名的命名空间。
      3.

未命名的命名空间用于声明局部于文件的实体。在未命名的命名空间中定义的变量在程序开始时创建,在程序结束之前一直存在。

      4.

未命名的命名空间中定义的名字可直接使用,毕竟,没有命名空间名字来限定它们。不能使用作用域操作符来引用未命名的命名空间的成员。

      5.

未命名的命名空间中定义的名字只在包含该命名空间的文件中可见。如果另一文件包含一个未命名的命名空间,两个命名空间不相关。两个命名空间可以定义相同的名字,而这些定义将引用不同的实体。

      6.未命名空间中定义的名字可以在定义该命名空间所在的作用域中找到。如果在文件的最外层作用域中定义未命名的命名空间,那么,未命名的空间中的名字必须与全局作用域中定义的名字不同
      7.像任意其他命名空间一样,未命名的命名空间也可以嵌套在另一命名空间内部。如果未命名的命名空间是嵌套的,其中的名字按常规方法使用外围命名空间名字访问:
     namespace local {
        namespace {
            int i;
        }
     }
        // ok: i defined in a nested unnamed namespace is distinct from global i
        local::i = 42;
       

           8.如果头文件定义了未命名的命名空间,那么,在每个包含该头文件的文件中,该命名空间中的名字将定义不同的局部实体

     建议使用未命名的命名空间取代文件中的静态声明。

 

八、命名空间成员的使用:除了在函数或其他作用域内部,头文件不应该包含 using 指示或 using 声明。在其顶级作用域包含 using 指示或 using声明的头文件,具有将该名字注入包含该头文件的文件中的效果。头文件应该只定义作为其接口的一部分的名字,不要定义在其实现中使用的名字。

       1.using 声明:一个 using声明一次只引入一个命名空间成员,它使得无论程序中使用哪些名字,都能够非常明确。如:using std::vector;

       2.using 声明的作用域:using 声明中引入的名字遵循常规作用域规则。从 using 声明点开始,直到包含 using 声明的作用域的末尾,名字都是可见的。外部作用域中定义的同名实体被屏蔽。

       3.简写名字只能在声明它的作用域及其嵌套作用域中使用,一旦该作用域结束了,就必须使用完全限定名。
       4.using 声明可以出现在全局作用域、局部作用域或者命名空间作用域中。类作用域中的 using 声明局限于被定义类的基类中定义的名字。

 

九、命名空间别名:命名空间别名声明以关键字 namespace 开头,接(较短的)命名空间别名名字,再接 =,再接原来的命名空间名字和分号。如果原来的命名空间名字是未定义的,就会出错。

       1.可用命名空间别名将较短的同义词与命名空间名字相关联。例如,像
           namespace cplusplus_primer { /* ... */ };
       2.这样的长命名空间名字,可以像下面这样与较短的同义词相关联:
           namespace primer = cplusplus_primer;

       3.命名空间别名也可以引用嵌套的命名空间。

 

十、using 指示:using 声明不同,using 指示无法控制使得哪些名字可见——它们都是可见的。using 指示以关键字 using 开头,后接关键字 namespace,再接命名空间名字。如果该名字不是已经定义的命名空间名字,就会出错。

         1.using 指示使得特定命名空间所有名字可见,没有限制。短格式名字可从 using 指示点开始使用,直到出现 using 指示的作用域的末尾。

         2.using 指示可以出现在函数,命名空间,及块作用域中。

         3.可以尝试用 using 指示编写程序,但在使用多个库的时候,这样做会重新引入名字冲突的所有问题。

         4.using 指示不声明命名空间成员名字的别名,相反,它具有将命名空间成员提升到包含命名空间本身和 using 指示的最近作用域的效果。该作用域中的局部变量会屏蔽命名空间中名字。 命名空间中的名字可能会与外围作用域(如全局作用域)中定义的其他名字冲突。(注意这里的using指示所在的作用域与使用命名空间名字所在的作用域)

         5.using 指示有用的一种情况是,用在命名空间本身的实现文件中。

         6.警告:避免 Using 指示,using 指示注入来自一个命名空间的所有名字,它的使用是靠不住的:只用一个语句,命名空间的所有成员名就突然可见了。虽然这个方法看似简单,但也有它自身的问题。如果应用程序使用许多库,并且用 using 指示使得这些库中的名字可见,那么,全局命名空间污染问题就重新出现。

 

十一、类、命名空间和作用域:

       1.命名空间是作用域。像在任意其他作用域中一样,名字从声明点开始可见。名字的可见性穿过任意嵌套作用域,直到引入名字的块的末尾。

       2.命名空间内部使用的名字的查找遵循常规 C++ 查找规则:当查找名字的时候,通过外围作用域外查找。对命名空间内部使用的名字而言,外围作用域可能是一个或多个嵌套的命名空间,最终以全包围的全局命名空间结束。只考虑已经在使用点之前声明的名字,而该使用仍在开放的块中。

       3.可以从函数的限定名推断出查找名字时所检查作用域的次序,限定名以相反次序指出被查找的作用域。

 

十二、实参相关的查找与类类型形参:

       1. 接受类类型形参(或类类型指针及引用形参)的函数(包括重载操作符),以及与类本身定义在同一命名空间中的函数(包括重载操作符),在用类类型对象(或类类型的引用及指针)作为实参的时候是可见的。

  2.隐式友元声明与命名空间:回忆一下,当一个类声明友元函数的时候,函数的声明不必是可见的。如果不存在可见的声明,那么,友元声明具有将该函数或类的声明放入外围作用域的效果。如果类在命名空间内部定义,则没有另外声明的友元函数在同一命名空间中声明,因为该友元接受类类型实参并与类隐式声明在同一命名空间中,所以使用它时可以无须使用显式命名空间限定符。

 

十三、重载与命名空间:每个命名空间维持自己的作用域,因此,作为两个不同命名空间的成员的函数不能互相重载。但是,给定命名空间可以包含一组重载函数成员。

         命名空间对函数匹配有两个影响。一个影响是明显的:using 声明或 using 指示可以将函数加到候选集合。另一个影响则微妙得多。

     有一个或多个类类型形参的函数的名字查找包括定义每个形参类型的命名空间。这个规则还影响怎样确定候选集合,为找候选函数而查找定义形参类(以及定义其基类)的每个命名空间,将那些命名空间中任意与被调用函数名字相同的函数加入候选集合。即使这些函数在调用点不可见,也将之加入候选集合。将那些命名空间中带有匹配名字的函数加入候选集合:

    namespace NS {
        class Item_base { /* ... */ };
        void display(const Item_base&) { }
    }
    // Bulk_item's base class is declared in namespace NS
    class Bulk_item : public NS::Item_base { };
    int main() {
        Bulk_item book1;
        display(book1);
        return 0;
    }
display 函数的实参 book1 具有类类型 Bulk_item。display 调用的候选函数不仅是在调用 display 函数的地方其声明可见的函数,还包括声明 Bulk_item 类及其基类 Item_base 的命名空间中的函数。命名空间 NS 中声明的函数 display(const Item_base&) 被加到候选函数集合中。

 

十四、重载与 using声明:

    1.没有办法编写 using声明来引用特定函数声明:

    using NS::print(int); // error: cannot specify parameter list
如果命名空间内部的函数是重载的,那么,该函数名字的 using 声明声明了所有具有该名字的函数。    using NS::print;      // ok: using declarations specify names only
  2.重载与 using 指示:using 指示将命名空间成员提升到外围作用域。如果命名空间函数与命名空间所在的作用域中声明的函数同名,就将命名空间成员加到重载集合中。如果存在许多 using 指示,则来自每个命名空间的名字成为候选集合的组成部分
 
十五、命名空间与模板:模板的显式特化必须在定义通用模板的命名空间中声明,否则,该特化将与它所特化的模板不同名。

为了提供命名空间中所定义模板的自己的特化,必须保证在包含原始模板定义的命名空间中定义特化。

posted @ 2012-08-13 17:33  ForFreeDom  阅读(1374)  评论(0编辑  收藏  举报