C++Primer_Chap18_用于大型程序的工具_List02_命名空间_笔记

满足在大规模应用程序的在在使用各种库(可能包含独立开发的库)进行协同开发的能力的特殊要求。

命名空间(namespace)为防止名字冲突提供了可控的机制。命名空间分割了全局命名空间,其中每个命名空间是一个作用域。通过在某个命名空间中定义库的名字,库的作者(用户)可以避免全局名字固有的限制。

命名空间定义

  命名空间的定义包含两个部分:首先是关键字namespace,随后是命名空间的名字。在命名空间名字后面是一系列由花括号括起来的声明和定义。只要能出现在全局作用域中的声明就能置于命名空间中,包括:类、变量(及其初始化操作)、函数(及其定义)、模板和其他命名空间:

namespace cplusplus_primer {
    class Sales_data { /*   */}
    Sales_data operator+(const Sales_data&, const Sales_data&);
    class Query {/*   */}
    class Query_base {/*   */}
}

每个命名空间都是一个作用域

cplusplus_primer::Query q = cplusplus_primer::Query("hello");

AddisonWeslry::Query q = AddisonWeslry::Query("hello");

命名空间可以是不连续的

    命名空间的定义可以不连续的特性使得我们可以将几个独立的接口和实现文件组成一个命名空间。此时,命名空间的组织方式类似我们管理自定义类及函数的方式:

  • 命名空间的一部分成员的作用是定义类,以及声明作为类接口的函数及对象,则这些成员应该置于头文件中,这些头文件将被包含在使用了这些成员的文件中。
  • 命名空间成员的定义部分则置于另外的源文件中。

  在程序中某些实体只能定义一次:如非内联函数、静态数据成员、变量等,命名空间中定义的名字也需要满足这一要求。我们可以通过上面的方式组织命名空间并达到这些目的。这种借口和实现分离的机制确保我们所需的函数和其他名字只定义一次,而只要是用到这些实体的地方都能看到对实体名字的声明。

模板特例化

  模板特例化必须定义在原始模板所属的命名空间中。只要在命名空间中声明了特例化,就能在命名空间外部定义它:

namespace std {
    template <> struct hash<Sales_data>;
}

template<> struct hash<Sales_data>
{
    size_t operator()(const Sales_data &s) const
    {
        returen hash<string>()(s.bookNo) ^
                hash<unsigned>()(s.units_sold) ^
                hash<double>()(s.revenue);
    }
}

全局命名空间

  全局作用域中定义的名字(即在所有类、函数及命名空间之外定义的名字)也就是定义在全局命名空间(global namespace)中。全局命名空间以隐式的方式声明,并且在所有程序中都存在。

::member_name;

嵌套的命名空间

  定义在其他命名空间中的命名空间。

内联命名空间

  内联命名空间中的名字可以被外层命名空间直接使用,:

inline namespace FifthEd {

}

namespace FifthEd {    //隐式内联
    class Query_base { /*    */  }
}

  定义内联命名空间的方式是在关键字namespace前添加关键字inline。关键字inline必须出现在命名空间第一次定义的地方,后续再打开命名空间的时候可以写inline也可以不写。

未命名的命名空间

  未命名的命名空间(unnamed namespace)是指关键字namespace后紧跟花括号括起来的一系列声明语句。未命名的命名空间中定义的变量拥有静态生命周期:他们在第一次使用前创建,并且直到程序结束才销毁。

  一个未命名的命名空间可以在某个给定的文件内不连续,但不能跨越多个文件。未命名的命名空间中定义的名字的作用域与该命名空间所在的作用域相同。

  如果未命名的命名空间定义在文件的最外层作用域中,则该命名空间中的名字一定要与全局作用域的名字有所区别:

int i;

namespace {
    int i;
}

//二义性:
i = 10;

  在标准C++引入命名空间的概念之前,程序需要将名字声明成static的以使得其对于整个文件是有效的。在C语言中,声明为static的全局实体在其所在的文件之外不可见。在文件中进行静态声明的做法已经被C++标准取消了,现在的做法是使用未命名的命名空间。

使用命名空间成员

命名空间的别名

namespace cplusplus_prime { /*   */  };
namespace primer = cplusplus;

namespace Qlib = cplusplus_primer::QueryLib;
Qlib::Query q;

using声明:扼要概述

    一条using声明(using declaration)语句一次只引入命名空间的一个成员。

  using声明的有效范围从声明的地方开始,一直到using声明所在的作用域结束为止。在此过程中,外层作用域的同名实体将被隐藏。未加限定的名字只能在using声明所在的作用域以及其内层作用域中使用。在有效作用域结束后,必须使用完整的经过限定的名字

  一条using声明语句可以出现在全局作用域、局部作用域、命名空间作用域以及类的作用域中。在类的作用域中,这种声明语句只能指向基类的成员。

using cplusplus_primer::Sales_data;

using指示

  using指示(using directive)和using声明类似的地方是:可以使用命名空间名字的简写形式,但所有名字都是可见的。

  using指示以关键字using开始,后面是关键字namespace以及命名空间的名字。using指示可以出现在全局作用域、局部作用域、命名空间作用域,但不能出现在类的作用域中。

  如果提供一个对std等命名空间的using指示而未做任何特殊控制的话,将重新引入由于使用了多个库而造成的名字冲突问题。

using namespace cplusplus_primer;

using指示与作用域

  using指示具有将命名空间成员提升到包含命名空间本身和using指示的最近作用域的能力。

//命名空间A和函数f定义在全局作用域中
namespace A {
    int i, j;
}

void f()
{
    using namespace A;    //把A中的名字注入到全局作用域中
    cout << i * j << endl;    //使用A::i和A::j
}

using指示示例 

namespace blip {
    int i = 16, j = 15, k = 23;
}
int j = 0;        //正确:blip::j隐藏在命名空间中

void manip()
{
    using namespace blip;
    ++i;    //blip::i = 17
    ++j;    //二义性错误
    ++::j;    //将全局变量j设为1
    ++blip::jj;    //blip::j = 16;
    int k = 97;    //当前局部的k隐藏了blip::k
    ++k;    
}

头文件和using声明或指示

  头文件如果在其顶层作用域中含有using指示或using声明,则会将名字注入到所有包含了该头文件的文件中。通常情况下,头文件应该只负责定义接口部分的名字,而不定义实现部分的名字。因此,头文件最多只能在它的函数或命名空间内使用功能using指示或using声明。

类、命名空间与作用域

  对于命名空间内部的名字的查找遵循常规的查找规则:由内向外依次查找每个外层作用域。外层作用域也可能是一个或多个嵌套的命名空间,知道最外层的全局命名空间查找过程终止。只有位于开放的块中且再使用点之前声明的名字才被考虑:

namespace A {
    int i;
    namespace B {
        int i;    //隐藏A::i
        int j;
        int f1()
        {
            int j;    //隐藏A::B::j
            return i;    //返回A::B::i
        }
    }
    //命名空间B结束,此后B中定义的名字不再可见
    int f2() {
        return j;    //错误:j没有被定义
    }
    int j = i;        //用A::i进行初始化
}

  对于位于命名空间中的类来说,常规的查找规则仍然适用:

namespace A {
    int i;
    int k;
    class C1 {
    public:
        C1():i(0), j(0) {}    //正确:初始化C1::i和C1::j
        int f1() { return k; }    //返回A::k
        int f2() { return h;}    //错误:h未定义
        int f3();
    private:
        int i;
        int j;
    };
}

int A::C1::f3()
{
    return h;    //正确:返回A::h
}

实参相关的查找与类类型形参

std::string s;
std::cin >> s;

operator>>(std::cin, s);

  代码两种调用等价。operator>>函数定义在标准库string中,string又定义在命名空间std中。我们可以不用std::限定符和using声明就可以调用operator>>,是因为命名空间中名字的隐藏规则的一个例外:当我们给函数传递一个类类型的对象时,除了在常规的作用域查找外还会查找实参类(以及实参类的基类)所属的命名空间。

查找与std::move和std::forward

  通常情况下,如果在应用程序中定义了一个标准库中已有的名字,将会出现以下两种情况中的一种:

  • 根据一般的重载规则确定某次调用应该执行函数的哪个版本
  • 应用程序根本不执行函数的标准库版本

  由于标准库move和forward函数是模板函数,且定义中它们都接受一个可以匹配任何类型的优质引用的函数形参。如果我们在程序中也定义了一个接受单一形参的move函数,不管形参是什么类型,都会导致冲突,所以推荐使用带限定语的完整版本。

友元声明和实参相关的查找

  当类声明了一个友元时,该友元声明并没有使得友元本身可见。然而,一个另外的未声明的类或函数如果第一次出现在友元声明中,我们认为它是最近的外层命名空间的成员。这条规则和实参相关的查找规则结合在一起会产生意想不到的效果:

namespace A {
    class C {
        //两个友元,在有元声明之外没有其他声明
        //这些函数隐式地称为命名空间A的成员
        friend void f2();    //除非另有声明,否则不会被找到
        friend void f(const C&);    //根据实参的相关查找规则可以被找到
    };
}

int main()
{
    A::C cobj;
    f(cobj);    //正确:通过在A::C中的友元声明找到A::f
    f2();    //错误:在A::f2没有被声明
}

重载与命名空间

与实参相关的查找与重载

    示例中的NS::display函数会在display函数调用时被添加到候选函数集中。

namespace NS {
    class Quote { /* ... */}
    void display(const Quote&) { /* ... */} 
}

class Bulk_item : public NS::Quote { /* ... */};

int main()
{
    Bulk_item bookl;
    display(bookl);
    return 0;
}

重载和using声明

  using声明语句声明的是一个名字,而非特定函数。即为函数书写using声明时,该函数的所有版本都被引入到当前作用域中。一个using声明引入的函数将重载该声明语句所属作用域已有的其他同名函数。

  如果using声明出现在局部作用域中,引入的名字将覆盖外层作用域的相关声明。如果using声明所在的作用域中已有一个函数与新引入的函数同名且形参列表相同,则该using声明将引发错误。除此之外,using声明将为引入的名字添加额外的重载实例,并最终扩充候选函数集的规模

重载与using指示

  using指示将命名空间的成员提升到外层作用域中,如果命名空间的某个函数与该命名空间所属作用域的函数同名,则命名空间的函数将被添加到重载集合中:

namespace libs_R_us {
    extern void print(int);
    extern void print(double);
}

void print(const std::string &);

using namespace libs_R_us;

void fooBar(int ival)
{
    print("Value: ");
    print(ival);
}

跨越多个using指示的重载

  如果存在多个using指示,则来自每个命名空间的名字都会成为候选函数集的一部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值