练习18.1

在下列 throw 语句中异常对象的类型是什么?

  1. (a) range_error r("error");
  2. throw r;
  3. (b) exception *p = &r;
  4. throw *p;

解:

  • (a): range_error
  • (b): exception

练习18.2

当在指定的位置发生了异常时将出现什么情况?

  1. void exercise(int *b, int *e)
  2. {
  3. vector<int> v(b, e);
  4. int *p = new int[v.size()];
  5. ifstream in("ints");
  6. //此处发生异常
  7. }

解:

指针p指向的内容不会被释放,将造成内存泄漏。

练习18.3

要想让上面的代码在发生异常时能正常工作,有两种解决方案。请描述这两种方法并实现它们。

解:

方法一:不使用指针,使用对象:

  1. struct intArray
  2. {
  3. intArray() : p(nullptr) { }
  4. explicit intArray(std::size_t s):
  5. p(new int[s]) { }
  6. ~intArray()
  7. {
  8. delete[] p;
  9. }
  10. // data meber
  11. int *p;
  12. };
  13. intArray p(v.size());

方法二:使用智能指针:

  1. std::shared_ptr<int> p(new int[v.size()], [](int *p) { delete[] p; });

练习18.4

查看图18.1所示的继承体系,说明下面的 try 块有何错误并修改它。

  1. try {
  2. // 使用 C++ 标准库
  3. } catch (exception) {
  4. // ...
  5. } catch (const runtime_error &re) {
  6. // ...
  7. } catch (overflow_error eobj) { /* ... */ }

解:

细化的异常类型应该写在前面:

  1. try {
  2. // 使用 C++ 标准库
  3. } catch (overflow_error eobj) {
  4. // ...
  5. } catch (const runtime_error &re) {
  6. // ...
  7. } catch (exception) { /* ... */ }

练习18.5

修改下面的main函数,使其能捕获图18.1所示的任何异常类型:

  1. int main(){
  2. // 使用 C++标准库
  3. }

处理代码应该首先打印异常相关的错误信息,然后调用 abort 终止函数。

解:

练习18.6

已知下面的异常类型和 catch 语句,书写一个 throw 表达式使其创建的异常对象能被这些 catch 语句捕获:

  1. (a) class exceptionType { };
  2. catch(exceptionType *pet) { }
  3. (b) catch(...) { }
  4. (c) typedef int EXCPTYPE;
  5. catch(EXCPTYPE) { }

解:

  1. (a): throw exceptionType();
  2. (b): throw expection();
  3. (c): EXCPTYPE e = 1; throw e;

练习18.7

根据第16章的介绍定义你自己的 BlobBlobPtr,注意将构造函数写成函数try语句块。

解:

练习18.8

回顾你之前编写的各个类,为它们的构造函数和析构函数添加正确的异常说明。如果你认为某个析构函数可能抛出异常,尝试修改代码使得该析构函数不会抛出异常。

解:

练习18.9

定义本节描述的书店程序异常类,然后为 Sales_data 类重新编写一个复合赋值运算符并令其抛出一个异常。

练习18.10

编写程序令其对两个 ISBN 编号不相同的对象执行 Sales_data 的加法运算。为该程序编写两个不同的版本:一个处理异常,另一个不处理异常。观察并比较这两个程序的行为,用心体会当出现了一个未被捕获的异常时程序会发生什么情况。

解:

练习18.11

为什么 what 函数不应该抛出异常?

解:

练习18.12

将你为之前各章练习编写的程序放置在各自的命名空间中。也就是说,命名空间chapter15包含Query程序的代码,命名空间chapter10包含TextQuery的代码;使用这种结构重新编译Query代码实例。

解:

练习18.13

什么时候应该使用未命名的命名空间?

解:

需要定义一系列静态的变量的时候。

参考:https://stackoverflow.com/questions/154469/unnamed-anonymous-namespaces-vs-static-functions

练习18.14

假设下面的 operator* 声明的是嵌套的命名空间 mathLib::MatrixLib 的一个成员:

  1. namespace mathLib {
  2. namespace MatrixLib {
  3. class matrix { /* ... */ };
  4. matrix operator* (const matrix &, const matrix &);
  5. // ...
  6. }
  7. }

请问你应该如何在全局作用域中声明该运算符?

解:

  1. mathLib::MatrixLib::matrix mathLib::MatrixLib::operator* (const mathLib::MatrixLib::matrix &, const mathLib::MatrixLib::matrix &);

练习18.15

说明 using 指示与 using 声明的区别。

解:

  • 一条using声明语句一次只引入命名空间的一个成员。
  • using 指示使得某个特定的命名空间中所有的名字都可见。

有点像python中的import:

  1. from lib import func
  2. from lib import *

练习18.16

假定在下面的代码中标记为“位置1”的地方是对命名空间 Exercise 中所有成员的using声明,请解释代码的含义。如果这些using声明出现在“位置2”又会怎样呢?将using声明变为using指示,重新回答之前的问题。

  1. namespace Exercise {
  2. int ivar = 0;
  3. double dvar = 0;
  4. const int limit = 1000;
  5. }
  6. int ivar = 0;
  7. //位置1
  8. void main() {
  9. //位置2
  10. double dvar = 3.1416;
  11. int iobj = limit + 1;
  12. ++ivar;
  13. ++::ivar;
  14. }

解:

练习18.17

实际编写代码检验你对上一题的回答是否正确。

解:

练习18.18

已知有下面的 swap 的典型定义,当 mem1 是一个 string 时程序使用 swap 的哪个版本?如果 mem1int 呢?说明在这两种情况下名字查找的过程。

  1. void swap(T v1, T v2)
  2. {
  3. using std::swap;
  4. swap(v1.mem1, v2.mem1);
  5. //交换类型的其他成员
  6. }

解:

std::swap是一个模板函数,如果是string会找到string版本;反之如果是int会找到int版本。

练习18.19

如果对 swap 的调用形如 std::swap(v1.mem1, v2.mem1) 将会发生什么情况?

解:

会直接调用std版的swap,但对后面的调用无影响。

练习18.20

在下面的代码中,确定哪个函数与compute调用匹配。列出所有候选函数和可行函数,对于每个可行函数的实参与形参的匹配过程来说,发生了哪种类型转换?

  1. namespace primerLib {
  2. void compute();
  3. void compute(const void *);
  4. }
  5. using primerLib::compute;
  6. void compute(int);
  7. void compute(double, double = 3.4);
  8. void compute(char*, char* = 0);
  9. void f()
  10. {
  11. compute(0);
  12. }

解:

练习18.21

解释下列声明的含义,在它们当作存在错误吗?如果有,请指出来并说明错误的原因。

  1. (a) class CADVehicle : public CAD, Vehicle { ... };
  2. (b) class DbiList : public List, public List { ... };
  3. (c) class iostream : public istream, public ostream { ... };

练习18.22

已知存在如下所示的类的继承体系,其中每个类都定义了一个默认构造函数:

  1. class A { ... };
  2. class B : public A { ... };
  3. class C : public B { ... };
  4. class X { ... };
  5. class Y { ... };
  6. class Z : public X, public Y { ... };
  7. class MI : public C, public Z { ... };

对于下面的定义来说,构造函数的执行顺序是怎样的?

  1. MI mi;

练习18.23

使用练习18.22的继承体系以及下面定义的类 D,同时假定每个类都定义了默认构造函数,请问下面的哪些类型转换是不被允许的?

  1. class D : public X, public C { ... };
  2. p *pd = new D;
  3. (a) X *px = pd;
  4. (b) A *pa = pd;
  5. (c) B *pb = pd;
  6. (d) C *pc = pd;

练习18.24

在第714页,我们使用一个指向 Panda 对象的 Bear 指针进行了一系列调用,假设我们使用的是一个指向 Panda 对象的 ZooAnimal 指针将会发生什么情况,请对这些调用语句逐一进行说明。

练习18.25

假设我们有两个基类 Base1Base2 ,它们各自定义了一个名为 print 的虚成员和一个虚析构函数。从这两个基类中文名派生出下面的类,它们都重新定义了 print 函数:

  1. class D1 : public Base1 { /* ... */};
  2. class D2 : public Base2 { /* ... */};
  3. class MI : public D1, public D2 { /* ... */};

通过下面的指针,指出在每个调用中分别使用了哪个函数:

  1. Base1 *pb1 = new MI;
  2. Base2 *pb2 = new MI;
  3. D1 *pd1 = new MI;
  4. D2 *pd2 = new MI;
  5. (a) pb1->print();
  6. (b) pd1->print();
  7. (c) pd2->print();
  8. (d) delete pb2;
  9. (e) delete pd1;
  10. (f) delete pd2;
  1. struct Base1 {
  2. void print(int) const;
  3. protected:
  4. int ival;
  5. double dval;
  6. char cval;
  7. private:
  8. int *id;
  9. };
  10. struct Base2 {
  11. void print(double) const;
  12. protected:
  13. double fval;
  14. private:
  15. double dval;
  16. };
  17. struct Derived : public Base1 {
  18. void print(std::string) const;
  19. protected:
  20. std::string sval;
  21. double dval;
  22. };
  23. struct MI : public Derived, public Base2 {
  24. void print(std::vector<double>);
  25. protected:
  26. int *ival;
  27. std::vector<double> dvec;
  28. };

练习18.26

已知如上所示的继承体系,下面对print的调用为什么是错误的?适当修改MI,令其对print的调用可以编译通过并正确执行。

  1. MI mi;
  2. mi.print(42);

练习18.27

已知如上所示的继承体系,同时假定为MI添加了一个名为foo的函数:

  1. int ival;
  2. double dval;
  3. void MI::foo(double cval)
  4. {
  5. int dval;
  6. //练习中的问题发生在此处
  7. }
  8. (a) 列出在MI::foo中可见的所有名字。
  9. (b) 是否存在某个可见的名字是继承自多个基类的?
  10. (c) Base1dval成员与Derived dval 成员求和后赋给dval的局部实例。
  11. (d) MI::dvec的最后一个元素的值赋给Base2::fval
  12. (e) 将从Base1继承的cval赋给从Derived继承的sval的第一个字符。

练习18.28

已知存在如下的继承体系,在 VMI 类的内部哪些继承而来的成员无须前缀限定符就能直接访问?哪些必须有限定符才能访问?说明你的原因。

  1. struct Base {
  2. void bar(int);
  3. protected:
  4. int ival;
  5. };
  6. struct Derived1 : virtual public Base {
  7. void bar(char);
  8. void foo(char);
  9. protected:
  10. char cval;
  11. };
  12. struct Derived2 : virtual public Base {
  13. void foo(int);
  14. protected:
  15. int ival;
  16. char cval;
  17. };
  18. class VMI : public Derived1, public Derived2 { };

练习18.29

已知有如下所示的类继承关系:

  1. class Class { ... };
  2. class Base : public Class { ... };
  3. class D1 : virtual public Base { ... };
  4. class D2 : virtual public Base { ... };
  5. class MI : public D1, public D2 { ... };
  6. class Final : public MI, public Class { ... };
  7. (a) 当作用于一个Final对象时,构造函数和析构函数的执行次序分别是什么?
  8. (b) 在一个Final对象中有几个Base部分?几个Class部分?
  9. (c) 下面的哪些赋值运算符将造成编译错误?
  10. Base *pb; Class *pc; MI *pmi; D2 *pd2;
  11. (a) pb = new Class;
  12. (b) pc = new Final;
  13. (c) pmi = pb;
  14. (d) pd2 = pmi;

练习18.30

Base中定义一个默认构造函数、一个拷贝构造函数和一个接受int形参的构造函数。在每个派生类中分别定义这三种构造函数,每个构造函数应该使用它的形参初始化其Base部分。