《C++ Primer》笔记 高级主题部分

《C++ Primer》笔记 高级主题部分,时间关系只看了其中一部分

命名空间

大型程序往往会使用多个独立开发的库,这些库又会定义大量的全局名字,如类、函数和模板等。当应用程序用到多个供应商提供的库时,不可避免地会发生某些名字相互冲突的情况。多个库将名字放置在全局命名空间中将引发命名空间污染( namespace pollution)。

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

命名空间定义

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

1
2
3
4
5
6
namespace cplusplus_primer { 
class Sales_data { / * ... * /};
Sales_data operator+(const Sales_data&, const Sales_data&);
class Query { /* ... */ };
class Query_base { /* ... */};
} // like blocks, namespaces do not end with a semicolon

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

和其他作用域类似,命名空间中的每个名字都必须表示该空间内的唯一实体。因为不同命名空间的作用域不同,所以在不同命名空间内可以有相同名字的成员。

命名空间可以不连续

命名空间可以定义在几个不同的部分,这点与其他作用域不太一样。命名空间的定义可以不连续的特性使得我们可以将几个独立的接口和实现文件组成一个命名空间。此时,命名空间的组织方式类似于我们管理自定义类及函数的方式

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

定义命名空间成员

假定作用域中存在合适的声明语句,则命名空间中的代码可以使用同一命名空间定义的名字的简写形式:

全局命名空间

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

使用命名空间成员

像 namespace_name:: member_name 这样使用命名空间的成员显然非常烦琐,特别是当命名空间的名字很长时尤其如此。幸运的是,我们可以通过一些其他更简便的方法使用命名空间的成员。

命名空间的别名

命名空间的别名( namespace alias)使得我们可以为命名空间的名字设定一个短得多的同义词。

1
namespace primer = cplusplus_primer;

using声明:扼要概述

一条using声明( using declaration)语句一次只引入命名空间的一个成员。它使得我们可以清楚地知道程序中所用的到底是哪个名字。

using声明引入的名字遵守与过去一样的作用域规则:它的有效范围从uS1ng声明的地方开始,一直到 using声明所在的作用域结束为止。在此过程中,外层作用域的同名实体将被隐藏。未加限定的名字只能在 using声明所在的作用域以及其内层作用域中使用。

using 指示

using指示( using directive)和 using声明类似的地方是,我们可以使用命名空间名字的简写形式:和 using声明不同的地方是,我们无法控制哪些名字是可见的,因为所有名字都是可见的

using指示以关键字using开始,后面是关键字 namespace以及命名空间的名字。

using 指示与作用域

using指示引入的名字的作用域远比 using声明引入的名字的作用域复杂。如我们所知,uS1ng声明的名字的作用域与uS1ng声明语句本身的作用域一致,从效果上看就好像 using声明语句为命名空间的成员在当前作用域内创建了一个别名一样。

using指示所做的绝非声明别名这么简单。相反,它具有将命名空间成员提升到包含命名空间本身和using指示的最近作用域的能力

类、命名空间与作用域

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

tuple类型

tuple是类似pair的模板。每个pair的成员类型都不相同,但每个pair都恰好有两个成员。不同 tuple类型的成员类型也不相同,但一个tup1e可以有任意数量的成员。每个确定的 tuple类型的成员数目是固定的,但一个tuple类型的成员数目可以与另一个 tuple类型不同。

当我们希望将一些数据组合成单一对象,但又不想麻烦地定义一个新数据结构来表示这些数据时,tup1e是非常有用的。

我们可以将 tuple看作一个“快速而随意”的数据结构。

定义和初始化tuple

当我们定义一个 tuple时,需要指出每个成员的类型:

1
2
tuple<size_t, size_t, size_t> threeD; // all three members set to 0 tuple<string,
vector<double>, int, list<int>> someVal("constants", {3.14, 2.718}, 42, {0,1,2,3,4,5});

当我们创建一个tup1e对象时,可以使用 tuple的默认构造函数,它会对每个成员进行值初始化;

1
2
tuple<size_t, size_t, size_t> threeD = {1,2,3};  // error
tuple<size_t, size_t, size_t> threeD{1,2,3}; // OK

类似 make_pair函数,标准库定义了 make_tuple函,我们还可以用它来生成 tuple对象。

访问tuple的成员

tuple的成员都是未命名的。要访问一个 tuple的成员,就要使用一个名为get的标准库函数模板。为了使用get,我们必须指定一个显式模板实参,它指出我们想要访问第几个成员。我们传递给qet一个tup1e对象,它返回指定成员的引用:

尖括号中的值必须是一个整型常量表达式。

如果不知道一个tup1e准确的类型细节信息,可以用两个辅助类模板来查询tuple成员的数量和类型:

关系和相等运算符

tuple 的关系和相等运算符的行为类似容器的对应操作。

使用tuple返回多个值

tuple的一个常见用途是从一个函数返回多个值。

返回tuple的函数