C++ Primer 学习笔记_92_用于大型程序的工具 --命名空间[续1]

用于大型程序的工具

--命名空间[续1]

二、嵌套命名空间

一个嵌套命名空间即是一个嵌套作用域 —— 其作用域嵌套在包含它的命名空间内部。嵌套命名空间中的名字遵循常规规则:外围命名空间中声明的名字嵌套命名空间中同一名字的声明屏蔽。嵌套命名空间内部定义的名字局部于该命名空间。外围命名空间之外的代码只能通过限定名引用嵌套命名空间中的名字。

嵌套命名空间可以改进库中代码的组织:

namespace cplusplus_primer
{
namespace QueryLib
{
class Query
{
    //...
};

Query operator&(const Query &,const Query &);
}

namespace Bookstore
{
class Item_base
{
    //...
};

class Bulk_item : public Item_base
{
    //...
};
}
}

当库提供者需要防止库中每个部分的名字与库中其他部分的名字冲突的时候,嵌套命名空间是很有用的。

嵌套命名空间中成员的名字由外围命名空间的名字和嵌套命名空间的名字构成:

    cplusplus_primer::QueryLib::Query q;

//P604 习题17.17
namespace cplusplus_primer
{
namespace simpleSI
{
//...1
}

namespace handleSI
{
//...2
}
}

三、未命名的命名空间

命名空间可以是未命名的,未命名的命名空间在定义时没有给定名字。未命名的命名空间以关键字namespace开头,接在关键字namespace后面的是由花括号定界的声明块

未命名的命名空间与其他命名空间不同,未命名的命名空间的定义可以在给定的文件中不连续并且局部于特定文件,从不跨越多个文本文件。每个文件都有自己的未命名的命名空间。

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

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

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

未命名空间中定义的名字可以在定义该命名空间所在的作用域中找到。如果在文件的最外层作用域中定义未命名的命名空间,那么,未命名的空间中的名字必须与全局作用域中定义的名字不同:

int i;
namespace
{
    int i;
}

i = 10; //Error:ambiguous

像任意其他命名空间一样,未命名的命名空间也可以嵌套在另一命名空间内部。如果未命名的命名空间是嵌套的,其中的名字按常规方法使用外围命名空间名字访问:

namespace local
{
namespace
{
int i;
}
}

    local::i = 10;	//OK

【小心地雷】

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

在所有其他方式中,未命名的命名空间的成员都是普通程序实体。

【未命名的命名空间取代文件中的静态声明】

在标准C++中引入命名空间之前,程序必须将名字声明为static,使它们局部于一个文件。文件中静态声明的使用从C语言继承而来,C 语言中,声明为static的局部实体在声明它的文件之外不可见

C++不赞成文件静态声明。不赞成的特征是在未来版本中可能不支持的特征。应该避免文件静态而使用未命名空间代替[用于声明局部于文件的实体]。

//P605 习题17.19
namespace cplusplus_primer
{
namespace Matrix_primer
{
class matrix
{
    //...
};
matrix operator*(const matrix &,const matrix &);
}
}

cplusplus_primer::Matrix_primer::matrix
cplusplus_primer::Matrix_primer::operator*(const matrix &,const matrix &);


四、命名空间成员的使用

【最佳实践】

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

1、using声明,扼要重述

using std::map;
using std::pair;
using std::size_t;
using std::string;
using std::vector;

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

2、using声明的作用域

从using声明点开始,直到包含using声明的作用域的末尾,名字都是可见的。外部作用域中定义的同名实体被屏蔽。

简写名字只能在声明它的作用域及其嵌套作用域中使用,一旦该作用域结束了,就必须使用完全限定名。

using声明可以出现在全局作用域、局部作用域或者命名空间作用域中。类作用域中的 using声明局限于被定义类的基类中定义的名字。

3、命名空间别名

可用命名空间别名将较短的同义词与命名空间名字相关联。例如,像

namespace cplusplus_primer
{
    //...
}

可以与较短的同义词相关联:

namespace primer = cplusplus_primer;

如果原来的命名空间名字是未定义的,就会出错。

命名空间别名也可以引用嵌套的命名空间:

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

一个命名空间可以有许多别名,所有别名以及原来的命名空间都可以互换使用。

4、using指示

using指示无法控制哪些名字可见 -- 它们都是可见的。

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

【小心地雷】

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

5、using指示与作用域

using声明将名字直接放入出现 using声明的作用域,好像using声明是命名空间成员的局部别名一样。因为这种声明是局部化的,冲突的机会最小

using指示不声明命名空间成员名字的别名,相反,它具有将命名空间成员提升到包含命名空间本身和using指示的最近作用域的效果

如下例:在f中,将好像A中的名字出现在全局作用域中 f的定义之前一样:

namespace A
{
int i,j;
}

void f()
{
    using namespace A;
    cout << i * j << endl;
}

【最佳实践】

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

6、using指示例子

namespace blip
{
    int bi = 16,bj = 15,bk = 23;
}
int bj = 0;

void manip()
{
    using namespace blip;

    ++ bi;  //17
    ++ bj;  //Error:ambiguous

    ++ ::bj;    //1
    ++ blip::bj; //16

    int bk = 97;
    ++ bk;  //98
}

manip中的 using提示使 manip能够直接访问blip中的所有名字:使用它们的简化形式,该函数可以引用这些成员的名字。

【警告:避免使用using指示】

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

而且,当引入库的新版本的时候,正在工作的程序可能会编译失败。如果新版本引入一个与应用程序正在使用的名字冲突的名字,就会引发这个问题

另一个问题是,由using指示引起的二义性错误只能在使用处检测,这个后来的检测意味着,可能在特定库引入很久之后才引发冲突,如果程序开始使用该库的新部分,就可能引发先前未检测到的冲突。 相对于依赖于using指示,对程序中使用的每个命名空间名字使用 using声明更好,这样做减少注入到命名空间中的名字数目,由using 声明引起的二义性错误在声明点而不是使用点检测,因此更容易发现和修正。

C++ Primer 学习笔记_92_用于大型程序的工具 --命名空间[续1]

时间: 2024-10-07 14:02:00

C++ Primer 学习笔记_92_用于大型程序的工具 --命名空间[续1]的相关文章

C++ Primer 学习笔记_93_用于大型程序的工具 --命名空间[续2]

用于大型程序的工具 --命名空间[续2] 五.类.命名空间和作用域 名字的可见性穿过任意嵌套作用域,直到引入名字的块的末尾. 对命名空间内部使用的名字的查找遵循常规C++查找规则:当查找名字的时候,通过外围作用域外查找.对命名空间内部使用的名字而言,外围作用域可能是一个或多个嵌套的命名空间,最终以全包围的全局命名空间结束.只考虑已经在使用点之前声明的名字,而该使用仍在开放的块中: namespace A { int i; namespace B { int i; int j; int f1()

C++ Primer 学习笔记_94_用于大型程序的工具 --命名空间[续3]

用于大型程序的工具 --命名空间[续3] 六.重载与命名空间 正如我们所见,每个命名空间维持自己的作用域,因此,作为两个不同命名空间的成员的函数不能互相重载.但是,给定命名空间可以包含一组重载函数成员. 1.候选函数与命名空间 命名空间对函数匹配有两个影响.一个影响是明显的:using声明或using 指示可以将函数加到候选集合.另一个影响则微妙得多. 正如前节所见,有一个或多个类类型形参的函数的名字查找包括定义每个形参类型的命名空间.这个规则还影响怎样确定候选集合,为找候选函数而查找定义形参类

C++ Primer 学习笔记_89_用于大型程序的工具 --异常处理[续2]

用于大型程序的工具 --异常处理[续2] 八.自动资源释放 考虑下面函数: void f() { vector<string> v; string s; while (cin >> s) { v.push_back(s); } string *p = new string[v.size()]; //... delete p; } 在正常情况下,数组和vector都在退出函数之前被撤销,函数中最后一个语句释放数组,在函数结束时自动撤销vector. 但是,如果在函数内部发生异常,则将

C++ Primer 学习笔记_90_用于大型程序的工具 --异常处理[续3]

用于大型程序的工具 --异常处理[续3] 九.auto_ptr类[接上] 5.auto_ptr对象的复制和赋值是破坏性操作 auto_ptr和内置指针对待复制和赋值有非常关键的区别.当复制auto_ptr对象或者将它的值赋给其他auto_ptr对象的时候,将基础对象的所有权从原来的auto_ptr对象转给副本,原来的auto_ptr对象重置为未绑定状态. auto_ptr<string> strPtr1(new string("HELLO!")); auto_ptr<

C++ Primer 学习笔记_91_用于大型程序的工具 --命名空间

用于大型程序的工具 --命名空间 引言: 在一个给定作用域中定义的每个名字在该作用域中必须是唯一的,对庞大.复杂的应用程序而言,这个要求可能难以满足.这样的应用程序的全局作用域中一般有许多名字定义.由独立开发的库构成的复杂程序更有可能遇到名字冲突 -- 同样的名字既可能在我们自己的代码中使用,也可能(更常见地)在独立供应商提供的代码中使用. 库倾向于定义许多全局名字 -- 主要是模板名.类型名或函数名.在使用来自多个供应商的库编写应用程序的时候,这些名字中有一些几乎不可避免地会发生冲突,这种名字

C++ Primer 学习笔记_93_用以大型程序的工具 -命名空间[续2]

用于大型程序的工具 --命名空间[续2] 五.类.命名空间和作用域 名字的可见性穿过任意嵌套作用域,直到引入名字的块的末尾. 对命名空间内部使用的名字的查找遵循常规C++查找规则:当查找名字的时候,通过外围作用域外查找.对命名空间内部使用的名字而言,外围作用域可能是一个或多个嵌套的命名空间,最终以全包围的全局命名空间结束.只考虑已经在使用点之前声明的名字,而该使用仍在开放的块中: namespace A { int i; namespace B { int i; int j; int f1()

C++ Primer 学习笔记_88_用于大型程序的工具 --异常处理[续1]

用于大型程序的工具 --异常处理[续1] 四.又一次抛出 有可能单个catch不能全然处理一个异常.在进行了一些校正行动之后,catch可能确定该异常必须由函数调用链中更上层的函数来处理,catch能够又一次抛出将异常传递给函数调用链中更上层的函数.又一次抛出是后面不跟类型或表达式的一个throw: throw; 空throw语句将又一次抛出异常对象,它仅仅能出如今catch或从catch调用的函数中.假设在处理代码不活动时碰到空throw,就调用terminate函数. 尽管又一次抛出不指定自

C++ Primer 学习笔记_95_用于大型程序的工具 --多重继承与虚继承

用于大型程序的工具 --多重继承与虚继承 引言: 大多数应用程序使用单个基类的公用继承,但是,在某些情况下,单继承是不够用的,因为可能无法为问题域建模,或者会对模型带来不必要的复杂性. 在这些情况下,多重继承可以更直接地为应用程序建模.多重继承是从多于一个直接基类派生类的能力,多重继承的派生类继承其所有父类的属性. 一.多重继承 1.定义多个类 为了支持多重继承,扩充派生列表: class Bear : public ZooAnimal { //... }; 以支持由逗号分隔的基类列表: cla

C++ Primer 学习笔记_96_用于大型程序的工具 --多重继承与虚继承[续1]

用于大型程序的工具 --多重继承与虚继承[续1] 四.多重继承下的类作用域 成员函数中使用的名字和查找首先在函数本身进行,如果不能在本地找到名字,就继续在本类中查找,然后依次查找每个基类.在多重继承下,查找同时检察所有的基类继承子树 -- 在我们的例子中,并行查找 Endangered子树和Bear/ZooAnimal子树.如果在多个子树中找到该名字,则那个名字的使用必须显式指定使用哪个基类;否则,该名字的使用是二义性的. [小心地雷] 当一个类有多个基类的时候,通过对所有直接基类同时进行名字查