名字空间

  名字空间是一种描述逻辑分组的机制。也就是说,如果有一些声明按照某种准则在逻辑上属于同一个集团,就可以将它们放入同一个名字空间,以表明这个事实。例如,在桌面计算器(6.1.1节)中有关分析器的声明就可以放入一个名字空间Parser:

    namespace Parser {
        double expr(bool);
        double prim(bool get) { /* ... */ }
        double term(bool get) { /* ... */ }
        double expr(bool get) { /* ... */ }
    }

函数expr()必须首先声明而后再定义,以便能打开在6.1.1节所说的依赖性循环。

  桌面计算器的输入部分也可以放入自己的名字空间:

    namespace Lexer {
        enum Token_value {
            NAME,        NUMBER,        END,
            PLUs='+',    MINUS='-',     MUL='*',    DIV='/',
            PRINT=';',   ASSIGN='=',    LP='(',     RP=')'
        };

        Token_value curr_tok;
        double number_value;
        string string_value;

        Token_value get_token() { /* ... */ }
    }

这样使用名字空间,将词法处理器和分析器为用户提供了些什么情况表现得相当明显。然而,如果我将函数的源代码也包括进来,这个结构就会变模糊了。在实际大小的名字空间里,如果将各个函数体也包括在声明中,你将经常需要翻过许多页或者许多屏信息,去寻找模块所提供的服务,即寻找它的界面。

  也存在另一种方式可以代替基于分开描述的界面,那就是提供一种从包含着实现细节的模块里抽取界面信息的工具。我并不认为那是一种好办法。描述模块的界面是一种最基本的设计活动(23.4.3.4节),同一个模块可以为不同的用户提供不同的界面,而且,界面的设计通常也是远在实现细节变得更具体之前进行的。

  这里是Parser的一个版本,其中将界面与实现分离了:

    namespace Parser {
        double prim(bool);
        double term(bool);
        double expr(bool);
    }

    double Parser::prim(bool get) { /* ... */ }
    double Parser::term(bool get) { /* ... */ }
    double Parser::expr(bool get) { /* ... */ }

请注意,作为将实现与界面分离的结果,现在的每个函数只有一个声明和一个定义。用户看到的只是界面里所包含的声明,而实现---在这里就是函数体---将被放在“其他的什么地方”,用户不需要去看。

  如上所示,成员可以在名字空间的定义里声明,而后采用namespace-name::member-name的形式去定义。

  一个名字空间的成员必须采用如下的记法形式引入:

    namespace namespace-name {
        // 声明和定义
    }

我们不能在名字空间定义之外用加限定的语法形式为名字空间引进新成员。例如,

void Parser::logical(bool);            // 错误❌:Parser里没有logical()

这里的想法就是为了更容易找到一个名字空间里的所有名字,也能捕捉到例如拼写或者类型不匹配一类的错误。例如,

    double Parser::trem(bool);             // 错误❌:Parser里没有trem()
    double Parser::prim(int);              // 错误❌:Parser::prim()要求一个bool参数

一个名字空间也是一个作用域。这样,“名字空间”就是一种最基本的相对简单的概念。一个程序越大,通过名字空间去描述其中逻辑上独立的各个部分也就越重要。常规的局部作用域、全局作用域和类也都是名字空间(C.10.3节)。

理想情况是,程序里的每个实体都属于某个可以识别的逻辑单位(“模块”)。所以,在理想情况下,一个非平凡的程序里的每个声明都应该位于某个名字空间里,以此指明它在程序中所扮演的逻辑角色。例外的是main(),它必须是特殊的,以便运行时环境能够特殊对待它(8.3.3节)。

🔚