0
点赞
收藏
分享

微信扫一扫

cppPrimer第十八章习题

朱悟能_9ad4 2022-04-24 阅读 28

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

(a)range_error r("error");
throw r;
(b) exception *p = &r;
throw *p;

如果将(b)中的throw语句写成了throw p将发生什么情况?

(a)的throw异常对象类型为 range_error

(b)throw语句异常对象类型为exception

如果写成throw p,则抛出的是一个指针,如果指针指向的是一个局部变量,那么是一个错误行为,因为到catch语句时指针指向的对象已经被销毁了。

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

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

p所指向的内存不会被释放,从而出现内存泄漏

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

  1. 自己写一个类来实现动态内存的释放

    class pointerArr{
    public:
        void pointerArr(int *pt):p(pt){ }
        ~pointerArr(){ delete []p;}
    private:
    	int *p;
    }
    void exercise(int* b, int* e)
    {
    	vector<int> v(b, e);
    	PointerArr p = new int[v.size()];
    	ifstream in("ints");
        
    }
    
  2. 使用shared_ptr,让智能指针管理动态分配的内存

void exercise(int* b, int* e)
{
	vector<int> v(b, e);
	std::shared_ptr<int> p(new int[v.size()], [](int *p){delete p});
	ifstream in("ints");
	//此处发生异常
}
  1. 用try…catch…在catch中释放p指向的内存空间
void exercise(int* b, int* e)
{
	vector<int> v(b, e);
	int* p = new int[v.size()];
	ifstream in("ints");	
	try{
        //此处发生异常
    }
    catch(exception e)
    {
		delete []p;
    }
}

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

try{
	//使用C++标准库
}
catch(exception){
	//...
}
catch(const runtime_error &re){
	//...
}
catch(overflow_error eobj)
{
	//....
}

不应该先捕获异常体系中的基类,这样会造成除了catch(excepiton)会被匹配,其的catch永远不会被捕获。应该把继承连最底端的类放在前面,而将继承链最顶端的类放在后面。应修改为:

try{
	//使用C++标准库
}
catch(overflow_error eobj)
{
	//....
}
catch(const runtime_error &re){
	//...
}
catch(exception){
	//...
}

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

int main()
{
	//使用C++标准库
}

处理代码应该首先打印异常相关的错误信息,然后调用abort(定义在cstdlib头文件中)终止main函数

#include<iostream>
#include<exception>
#include<cstdlib>

using std::cout;
using std::endl;

int main()
{
	try {
		//使用C++标准库
	}
	catch (std::bad_cast& e) {
		cout << e.what() << endl;
		abort();
	}
	catch (std::overflow_error& e) {
		cout << e.what() << endl;
		abort();
	}
	catch (std::bad_alloc& e) {
		cout << e.what() << endl;
		abort();
	}
	catch (std::underflow_error& e) {
		cout << e.what() << endl;
		abort();
	}
	catch (std::bad_cast& e) {
		cout << e.what() << endl;
		abort();
	}
	catch (std::range_error& e) {
		cout << e.what() << endl;
		abort();
	}
	catch (std::runtime_error& e) {
		cout << e.what() << endl;
		abort();
	}
	
	catch (std::domain_error& e) {
		cout << e.what() << endl;
		abort();
	}
	catch (std::invalid_argument& e) {
		cout << e.what() << endl;
		abort();
	}
	catch (std::out_of_range& e) {
		cout << e.what() << endl;
		abort();
	}
	catch (std::length_error& e) {
		cout << e.what() << endl;
		abort();
	}
	catch (std::logic_error& e) {
		cout << e.what() << endl;
		abort();
	}

	catch (std::exception& e) {
		cout << e.what() << endl;
		abort();
	}

	return 0;
}

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

(a) class exceptionType{};
catch(exceptionType *pet){}
(b) catch(...){ }
(c) typedef int EXCEPTYPE;
catch(EXCEPTYPE) {} 

答案:

(a) throw new exceptionType();
(b) throw std::runtime_error;
(c) EXCEPTYPE e = 4; throw e;

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

//处理异常的类
class out_of_stock:public runtime_error {
public:
	explicit out_of_stock(const string& s) :
		runtime_error(s) {}

};

class isbn_mismatch :public logic_error {
public:
	explicit isbn_mismatch(const string& s) :
		logic_error(s) {}

	isbn_mismatch(const string& s, const string& lhs, const string& rhs) :
		logic_error(s), left(lhs), right(rhs) {}

	const string left, right;
};

Sales_data& Sales_data::operator+=(const Sales_data& rhs)
{
	//如果参与 加法的两个对象不是同一书籍,则抛出一个异常
	if (isbn() != rhs.isbn())
		throw isbn_mismatch("wrong isbns", isbn(), rhs.isbn());
	//复合赋值运算符一般先+,后执行=
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;

	return *this; //返回左侧运算对象的引用
}

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

出现了未被捕获的异常时会直接terminate,提示有未被处理的异常,终止程序

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

what中如果抛出异常,需要try{}catch(){}捕获,再次调用what,导致一直循环,最终耗尽内存。

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

当需要在文件中进行声明静态全局变量(即希望在单个文件范围内使用静态变量)时需采用在未命名的命名空间声明变量替代

即希望声明的变量对所在文件外的其他文件不可见,并且在第一次使用前创建,直到程序结束才销毁(静态生命周期)

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

namespace mathLib {
	namespace MatrixLib {
		class matrix {
		};
		matrix operator*(const matrix&, const matrix&);
	}
}

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

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

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

using声明可以一次只引入命名空间的一个成员,它的有效范围从using声明的地方开始,一直到using声明所在的作用域结束为止,using std::endl;

using指示与using声明不同在于,无法控制哪些名字是可见的,因为所有名字都是可见的.using指示以关键字using开始,后面是关键字namespace以及命名空间的名字。using指示会把所有名字注入到全局作用域(如using namespace std;)

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;
}
  1. 在位置1进行using声明:

    namespace Exercise {
    	int ivar = 0;
    	double dvar = 0;
    	const int limit = 1000;
    }
    int ivar = 0;
    using Exercise::ivar; using Exercise::dvar; using Exercise::limit;//using声明导致多次声明
    void manip()
    {
    	//位置2 
    	double dvar = 3.1416; //隐藏Exercise::dvar
    	int iobj = limit + 1; //使用Exercise::limit
    	++ivar; 
    	++::ivar;
    }
    
  2. 在位置2进行using声明

    void manip()
    {
    	//位置2 
    	using Exercise::ivar; using Exercise::dvar; using Exercise::limit;
    	double dvar = 3.1416; //错误重定义,多次初始化
    	int iobj = limit + 1; //Exercise::limit
    	++ivar; //使用Exercise::ivar
    	++::ivar; //使用全局的ivar
    }
    
  3. 在位置1进行using指示

    namespace Exercise {
    	int ivar = 0;
    	double dvar = 0;
    	const int limit = 1000;
    }
    int ivar = 0;
    //位置1
    using namespace Exercise;
    void manip()
    {
    	//位置2 
    	double dvar = 3.1416; 
    	int iobj = limit + 1; //Exercise::limit
    	++ivar; //不明确,不知道是使用::ivar还是Exercise::ivar
    	++::ivar; //使用全局的ivar
    }
    
  4. 在位置2进行using指示

    void manip()
    {
    	//位置2 
    	using namespace Exercise; //会把Exercise中名字注入全局域
    	double dvar = 3.1416; 
    	int iobj = limit + 1; //Exercise::limit
    	++ivar; //不明确,不知道是使用::ivar还是Exercise::ivar
    	++::ivar; //使用全局的ivar
    }
    

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)
}

当mem1是string时,会先在string类中查找swap函数,找到则不用std版本的。若为int类型,则直接使用标准库版本的swap(通过using声明已注入std::swap函数声明)

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

只能使用标准库版本的swap。

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

namespace primerLib {
	void compute();
	void compute(const void*);
}

//using声明
using primerLib::compute;
void compute(int) {}
void compute(double, double = 3.4) {}
void compute(char*, char* = 0) {}

void f()
{
	compute(0);
}

如果将using声明置于f函数中compute的调用点之前将发生什么情况?请重新回答之前那些问题。

答:候选函数有:

void compute(int) {}
void compute(double, double = 3.4) {}
void compute(char*, char* = 0) {}
void compute();
void compute(const void*);

可行函数有:

void compute(int) {} // 1
void compute(double, double = 3.4) {} //2
void compute(char*, char* = 0) {} //3
void compute(const void*); //4

对于1来说没有任何类型转换,最佳匹配

对于2来说,发生了int->double的转换

对于3来说,发生了int->char*转换

对于4来说,发生了int->const void *转换

若将using声明置于f函数中compute的调用点之前,则把名字compute注入using声明语句所在的作用域中,覆盖了全局的compute函数,最佳匹配compute(const void *)

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

(a)class CADVehicle : public CAD,Vehicle{};
(b)class DblList:public List,public List{};
(c)class iostream: public istream, public ostream{}; 

(a)没错,CADVehicle public继承CAD类,private继承Vehicle类。但是忽略了Vehicle前的访问说明符,那么默认是private

(b)错误,重复继承List类

©没有错误,iostream类public继承istream类,public继承ostream类

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

class A{};
class B:public A{};
class C:public B{};
class X{};
class Y{};
class Z : public X, public Y{};
class MI : public C, public Z{};

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

MI mi;

顺序是:A-B-C-X-Y-Z-MI

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

class D :public X, public C {};

void p18_23()
{
	D* pd = new D;
	X* px = pd;
	A* pa = pd;//A是D的间接基类
	B* pb = pd;//B为D的间接基类
	C* pc = pd;
}

都是允许的

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

ZooAnimal *pz = new Panda("ying_yang");
pz->print();  //正确 Panda::print()
pz->cuddle(); //错误,不属于ZooAnimal的接口
pz->highlight();	//错误不属于ZooAnimal的接口
delete pz;	//正确,Panda::~Panda

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

class Base1 {
public:
	Base1() = default;
	virtual void print()
	{
		cout << "Base1-print()" << endl;
	}

	virtual ~Base1()
	{
		cout << "Base1-dtor" << endl;
	}
};

class Base2 {
public:
	Base2() = default;
	virtual void print()
	{
		cout << "Base2-print()" << endl;
	}

	virtual ~Base2()
	{
		cout << "Base2-dtor" << endl;
	}
};

class D1:public Base1 {
public:
	D1() = default;
	void print() override
	{
		cout << "D1-print()" << endl;
	}

};

class D2 :public Base1 {
public:
	D2() = default;
	void print() override
	{
		cout << "D2-print()" << endl;
	}

};

class MI :public D1, public D2 {
public:
	MI() = default;
	void print() override
	{
		cout << "MI-print()" << endl;
	}

};


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

void p18_25()
{
	Base1* pb1 = new MI;
	Base2* pb2 = new MI;
	D1* pd1 = new MI;
	D2* pd2 = new MI;

	pb1->print(); // a MI::print()
	pd1->print(); //b MI::print()
	pd2->print(); //c MI::print()
	delete pb2; //d MI::~MI() 
	delete pd1; //e MI::~MI()
	delete pd2; //f; MI::~MI()
}

18.26 已知如下所示的继承体系

struct Base1 {
	void print(int) const; // 默认情况下访问权限是public
protected:
	int ival;
	double dval;
	char cval;
private:
	int* id;
};

struct Base2 {
	void print(double) const;
protected:
	double fval;
private:
	double dval;
};

struct Derived :public Base1 {
	void print(string) const;
protected:
	string sval;
	double dval;
};

struct MI :public Derived, public Base2 {
	void print(vector<double>);
protected:
	int* ival;
	vector<double> dvec;
};

下面对print的调用为什么是错误的?适当修改MI,令其对print的调用可以编译通过并正确执行

MI mi;
mi.print(42);

答:因为在MI的作用域中只找到void print(std::vec<double>)函数,因此参数类型不匹配。MI中增加函数

struct MI :public Derived, public Base2 {
	void print(vector<double>);
    void print(int i) const
    {
		Base1::print(i);
    }
protected:
	int* ival;
	vector<double> dvec;
};

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

int ival;
double dval;
void MI::foo(double cval)
{
	int dval;
    //练习中的问题发生在此处
}

(a)列出在MI::foo中可见的所有名字

(b)是否存在某个可见的名字是继承自多个基类的

©将Base1的dval成员与Derived的dval成员求和后赋值给dval的局部实例

(d)将MI::dvec的最后一个元素赋值给Base2::fval

(e)将从Base1继承的cval赋给Derived继承的sval的第一个字符。

答:

(a)::ival, ::dval,

(MI中) dval, print, ival, dvec,

Derived::sval, Derived::dval,Derived::print

Base2::print,Base2::fval,

Base1::print, Base1::ival, Base1::dval, Base1::cval

(b)存在,(在多个基类中有同名成员)ival, dval, print

©

dval = Base1::dval + Derived::dval;

(d)

Base2::fval = MI::dvec.back();

(e)

Derived::sval[0] = Base1::cval;

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

struct Base {
	void bar(int);
protected:
	int ival;
};

struct Derived1 :virtual public Base {
	void bar(char);
	void foo(char);
protected:
	char cval;
};

struct Derived2 :virtual public Base {
	void foo(int);
protected:
	int ival;
	char cval;
};

class VMI :public Derived1, public Derived2 
{};

无须前缀限定符可访问的成员:Derived1::bar(char)覆盖了Base::bar(int) Derived2::ival 隐藏了 Base::ival

需要限定符访问的成员: Derived1::cval, Derived2::cval, 因为二义性

Base::bar(int), Base::ival 被Derived1或Derived2同名成员隐藏了

Derived1::foo(char), Derived2::foo(int)因为二义性

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

class Class {};

class Base:public Class{};
class D1:virtual public Base{};
class D2 :virtual public Base {};
class MI:public D1, public D2{};
class Final :public MI, public Class {};

(a) 当作用于一个Final对象时,构造函数和析构函数的执行次序分别是什么?

(b) 在一个Final对象中有几个Base部分?几个Class部分?

©下面哪些赋值运算将造成编译错误

Base* pb; Class* pc; MI* pmi; D2* pd2;
	pb = new Class;//a
	pc = new Final; //b
	pmi = pb; //c
	pd2 = pmi; //d

答:(a)构造函数执行次序为:Class、Base、D1、D2、MI、Class、Final

析构函数与构造函数执行次序相反

(b) 有一个Base部分,两个Class部分

© a、c错误,不能将父类指针转换为子类指针

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

举报

相关推荐

0 条评论