《C++ Primer》第18章 用于大型程序的工具
18.2节 命名空间 习题答案
练习18.11:为什么what函数不应该抛出异常?
【出题思路】
深入理解what函数的作用和在异常处理中所处的重要位置。
【解答】
what函数是在catch异常后用于提取异常基本信息的虚函数,what函数是确保不会抛出任何异常的。如果what函数抛出了异常,则会在新产生的异常中由于what函数继续产生异常,将会产生抛出异常的死循环。所以what函数必须确保不抛出异常。
练习18.12:将你为之前各章练习编写的程序放置在各自的命名空间中。也就是说,命名空间chapter15包含Query程序的代码,命名空间chapter10包含TextQuery的代码;使用这种结构重新编译Query代码示例。
【出题思路】
本题练习将不同程序放置在不同的命名空间中。
【解答】
将Query类以及Query_base类层次定义为命名空间chapter15的成员,并相应修改主函数中的代码(使用限定名引用这些类,或者使用相关的using声明)。
练习18.13:什么时候应该使用未命名的命名空间?
【出题思路】
理解未命名的命名空间的作用和意义。
【解答】
通常,当需要声明局部于文件的实体时,可以使用未命名的命名空间,即在文件的最外层作用域中定义未命名的命名空间。
练习18.14:假设下面的operator*声明是嵌套的命名空间mathLib::MatrixLib的一个成员:
namespace mathLib{
namespace MatrixLib {
class matrix { /* ... */ };
matrix operator*(const matrix &, const matrix &);
// ...
}
}
请问你应该如何在全局作用域中声明该运算符?
【出题思路】
了解在嵌套的命名空间中如何声明全局的变量。
【解答】
将函数返回类型及函数名加上命名空间名字限定即可:
mathLib::MatrixLib::matrix mathLib::MatrixLib::operator*(const matrix &, const matrix &)
{ /* ... */ }
练习18.15:说明using指示与using声明的区别。
【出题思路】
深入了解using指示和using声明的特点和用处。
【解答】
一个using指示使得特定命名空间中的所有名字都成为可见的;而一个using声明只能引入特定命名空间中的一个成员。
练习18.16:假定在下面的代码中标记为“位置1”的地方是对于命名空间Exercise中所有成员的using声明,请解释代码的含义。如果这些using声明出现在“位置2”又会怎样呢?将using声明变为using指示,重新回答之前的问题。
namespace Exercise {
int ivar = 0;
double dvar = 0;
const int limit = 1000;
}
int ivar = 0;
//位置1
void manip() {
//位置2
double dvar = 3.1416;
int iobj = limit + 1;
++ivar;
++::ivar;
}
【出题思路】
理解命名空间的声明和指示在主函数内和主函数外的差别。
【解答】
如果命名空间Exercise的所有成员的using声明放在标记为“位置1”的地方,则Exercise中的成员在全局作用域中可见。using Exercise::ivar;会导致ivar重复定义的编译错误,因为在全局作用域中也定义了一个同名变量(注意,由using声明引起的二义性错误在声明点检测);而manip中的double dvar = 3.1416;声明了一个局部变量dvar,在函数体作用域中它将屏蔽Exercise::dvar; int iobj = limit + 1;声明了一个局部变量iobj,并用Exercise::limit加1的结果对其进行初始化。
如果命名空间Exercise的所有成员的using声明放在标记为“位置2”的地方,则manip中的double dvar = 3.1416;属于对变量dvar的重复定义,会出现编译错误;int iobj = limit + 1;声明了一个局部变量iobj,并用Exercise::limit加1的结果对其进行初始化;++ivar;访问到的是Exercise::ivar,而++::ivar访问的是全局变量ivar。
如果命名空间Exercise的using指示放在标记为“位置1”的地方,则manip中的double dvar = 3.1416;声明了一个局部变量dvar,在函数体作用域中它将屏蔽Exercise::dvar; int iobj = limit + 1;声明了一个局部变量iobj,并用Exercise::limit加1的结果对其进行初始化;++ivar;访问到的是Exercise::ivar,而++::ivar;访问的是全局变量ivar。
如果命名空间Exercise的using指示放在标记为“位置2”的地方,则Exercise的成员看来好像是声明在全局作用域中的一样,manip中的double dvar = 3.1416;声明了一个局部变量dvar,在函数体作用域中它将屏蔽Exercise::dvar;;int iobj =limit + 1;声明了一个局部变量iobj,并用Exercise::limit加1的结果对其进行初始化;++ivar;出现二义性错误,因为编译器无法分辨是访问Exercise::ivar,还是访问全局变量ivar;而++::ivar访问的是全局变量ivar。
练习18.17:实际编写代码检验你对上一题的回答是否正确。
【出题思路】
通过观察代码中相关变量的变化,深入了解命名空间的作用域。
【解答】
一共有4种情况,所以编程时需要编写4个对应的程序。程序一:
程序一:
#include <iostream>
using namespace std;
namespace Exercise
{
int ivar = 10;
double dvar = 0;
const int limit = 1000;
}
int ivar = 20;
//位置1:插入using声明
//using Exercise::ivar;//编译错误ivar重复定义
using Exercise::dvar;
using Exercise::limit;
int main()
{
//位置2
double dvar = 3.1416; //局部dvar
int iobj = limit + 1; //Exercise::limit;
++ivar; //二义性
++::ivar; //二义性
cout << "dvar = " << dvar << endl;
cout << "iobj = " << iobj << endl;
cout << "ivar = " << ivar << endl;
return 0;
}
运行结果:
程序二:
#include <iostream>
using namespace std;
namespace Exercise
{
int ivar = 10;
double dvar = 0;
const int limit = 1000;
}
int ivar = 20;
//位置1:插入using声明
int main()
{
//位置2:插入using声明
using Exercise::ivar;
using Exercise::dvar;
using Exercise::limit;
//double dvar = 3.1416; //编译错误ivar重复定义
int iobj = limit + 1; //Exercise::limit;
++ivar; //Exercise::ival;
++::ivar; //二义性
cout << "dvar = " << dvar << endl;
cout << "iobj = " << iobj << endl;
cout << "ivar = " << ivar << endl;
return 0;
}
运行结果:
程序三:
#include <iostream>
using namespace std;
namespace Exercise
{
int ivar = 10;
double dvar = 0;
const int limit = 1000;
}
int ivar = 20;
//位置1:插入using声明
using namespace Exercise;
int main()
{
//位置2
double dvar = 3.1416; //Exercise::dvar;
int iobj = limit + 1; //Exercise::limit;
//++ivar; //二义性
++::ivar; //二义性
cout << "dvar = " << dvar << endl;
cout << "iobj = " << iobj << endl;
cout << "ivar = " << ::ivar << endl;
return 0;
}
运行结果:
程序四:
#include <iostream>
using namespace std;
namespace Exercise
{
int ivar = 10;
double dvar = 0;
const int limit = 1000;
}
int ivar = 20;
//位置1
int main()
{
//位置2:插入using声明
using namespace Exercise;
double dvar = 3.1416; //Exercise::dvar;
int iobj = limit + 1; //Exercise::limit;
//++ivar; //二义性
++::ivar; //二义性
cout << "dvar = " << dvar << endl;
cout << "iobj = " << iobj << endl;
cout << "ivar = " << ::ivar << endl;
return 0;
}
运行结果:
练习18.18:已知有下面的swap的典型定义(参与13.3节,第457页),当mem1是一个string时程序使用swap的哪个版本?如果mem1是int呢?说明在这两种情况下名字查找的过程。
void swap(T v1, T v2)
{
using std::swap;
swap(v1.mem1, v2.mem1);
//交换类型T的其他成员
}
【出题思路】
深入理解同一函数在拥有不同类型的参数时名字查找的过程。
【解答】
如果mem1是string类型,编译器除了在常规作用域中查找匹配的swap外,还会查找string所属的命名空间中是否有string类型特定版本的swap函数。但对string而言,找到的就是std::swap,完成两个字符串内容的交换。若mem1是int类型,由于int是内置类型,没有特定版本的swap,只会在常规作用域中查找。由于using声明的作用,最终会调用std::swap,完成两个int的交换。
练习18.19:如果对swap的调用形如std::swap(v1.mem1, v2.mem1)将发生什么情况?
【出题思路】
理解强制调用特定版本的函数时需要如何操作。
【解答】
将直接使用标准库版本的swap,而不会查找特定版本的swap或常规作用域中的其他swap。
练习18.20:在下面的代码中,确定哪个函数与compute调用匹配。列出所有候选函数和可行函数,对于每个可行函数的实参与形参的匹配过程来说,发生了哪种类型转换?
namespace primerLib{
void compute();
void compute(const void *);
}
using primerLib::compute;
void compute(int);
void compute(double, double = 3.4;
void compute(char *, char * = 0);
void f()
{
compute(0);
}
如果将using声明置于函数f中compute的调用点之前将发生什么情况?重新回答之前的那些问题。【出题思路】
理解主函数内外using声明和重载的特性。
【解答】
全局作用域中声明的函数void compute(int)与compute函数的调用匹配。
候选函数:命名空间primerLib中声明的两个compute函数(因using声明使得它们在全局作用局中可见),以及全局作用域中声明的三个compute函数。
可行函数:因函数调用中给出的实参0为int型,所以可行函数为以下4个函数:
void compute(int);
void compute(double, double = 3.4;
void compute(char *, char * = 0);
primerLib中声明的void compute(const void*)
其中,第一个为完全匹配,第二个需要将实参隐式转换为double类型,第三个需要将实参隐式转换为char*类型,第四个需要将实参隐式转换为void*类型方可匹配,所以第一个为最佳匹配。
如果将using声明置于函数f中compute的调用点之前,则primerLib中声明的voidcompute(const void*)与compute函数的调用匹配。
候选函数:命名空间primerLib中声明的两个compute函数(因using声明使得它们在函数f的函数体作用域中可见,并屏蔽了全局作用域中的三个compute函数)。
可行函数:因函数调用中给出的实参0为int型,所以可行函数为primerLib中声明的void compute(const void*)。需要将实参隐式转换为void*类型方可匹配。