C++Primer 第13章-复制控制-课后习题答案

更新时间:2023-03-11 01:58:01 阅读量: 教育文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

第十三章 复制控制

1. 什么是复制构造函数?何时使用它? 只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数叫复制构造函数。 copy constructor will be used under these situations: 根据一个同类型的对象显式或隐式初始化一个对象; 复制一个对象,将它作为实参传给一个函数; 从函数返回时复制一个对象; 初始化顺序容器中的对象;

根据元素初始化式列表初始化数组元素。

2. 下面第二个初始化不能编译。可以从vector的定义得出什么推断? vector v1(42); vector v2 = 42;

若能编译成功说明,这是个复制初始化,创建v2时,首先调用接受一个int型形参的vector构造函数,创建一个临时vector对象,然后再调用复制构造函数用这个临时对象来初始化v2。现在不能编译,说明vector没有定义复制构造函数。

3. 假定Point为类类型,该类类型有一个复制构造函数,指出下面程序段中每一个使用了复制构造函数的地方: Point global;

Point foo_bar( Point arg ) // 调用此函数时,将实参对象的副本传递给形参Point的对象arg {

Pint local = arg; // 调用复制构造函数,将局部对象local初始化为形参arg的副本。 Point *heap = new Point( global ); // 调用复制构造函数用全局对象global来初始化 // Point对象 *heap *heap = local; Point pa[4] = { local, *heap }; // 使用数组初始化列表来初始化数组的每个元素。 return *heap; // 从函数返回Point对象*heap的副本 }

4. 对于如下的类的简单定义,编写一个复制构造函数所有成员。复制pstring指向的对象而不是复制指针。 struct NoName { NoName(): pstring( new std::string ), i(0), d(0) { } private: std::string *pstring; int i; double d; }; NoName( const NoName& orig ) : pstring(new string(*(orig.pstring))), i(orig.i), d(orig.d)

{

// // }

5. 哪个类定义可能需要一个复制构造函数? (a) 包含四个float成员的Point3w类。

(b) Matrix类,其中,实际矩阵在构造函数中动态分配,在析构函数中删除。 (c) Payroll类,在这个类中为每个对象提供唯一ID。

(d) Word类,包含一个string和一个以行列位置对为元素的vector。 (b)需要,涉及到指针及动态分配

(c)需要,在根据已存在的Payroll对象创建其副本时,需要提供唯一的ID. 其他的均可以调用编译器的提供的复制构造函数,或者调用类类型的string和vector的复制构造函数。

6.复制构造函数的形参并不限制为const,但必须是一个引用,解释这个限制的基本原理,例如,解释为什么下面的定义不能工作, Sales_item::Sales_item( const Sales_item rhs );

它不能工作的原因是:当形参为非引用类型时,将复制实参的值,给这个copy constructor,但是,每当以传值方式传递参数时,会导致调用复制构造函数,因此,如果要使用以传值方式传递参数的copy constructor,必须使用一个“不以传值方式传递参数”的copy constructor,否则就会导致copy constructor的无穷递归调用。这个“不以传值方式传递参数”的方法就是使用 形参是一个引用 的copy constructor,即以传地址的方式传递参数。

7.类何时需要定义复制操作符?

在需要定义复制构造函数时,也需要定义赋值操作符,即如果一个类(1)类中包含指针型数据成员,(2)或者在进行赋值操作时需要做一些特定工作,则该类需要定义赋值操作符。

8.对于习题13.5中列出的每个类型,指出类是否需要赋值操作符。 (b)需要赋值操作符,因为涉及指针和动态分配内存;

(c) 需要,在用已存在的Payroll对象给另一个Payroll对象赋值时,需要提供唯一的ID。

9.习题13.4中包括NoName类的简单定义,确定这个类是否需要赋值操作符,如果需要,实现它。

因为有指针类的数据成员,所以需要赋值操作符,实现为: NoName& operator= ( const NoName &Nn ) { i (Nn.i); d(Nn.d); pstring = new string; *pstring = *( Nn.pstring ); return *this; }

pstring = new string; *pstring = *(orig.pstring);

10.定义一个Employee类,包含雇员名字和一个唯一的雇员标识,为该类定义默认构造函数和参数为表示雇员名字的string构造函数。如果该类需要复制构造函数或赋值操作符,实现这些函数。 class Employee {

public: Employee(): ID(cnt) { cnt++ ; } // 默认构造函数 Employee( const std::string &na ): name( na ), ID(cnt) { cnt++; } // 构造函数

// 拷贝构造函数

Employee( const Employee & rhs ) : name( rhs.name ) , ID(cnt) { cnt++; }

// 赋值操作符 Employee & operator= ( const Employee & rhs ) { name = rhs.name; return *this; } private: string name; int ID; static int cnt; };

另外需要在类外对static成员进行初始化: int Employee::cnt =1 ;

11.什么是析构函数?合成析构函数有什么用?什么时候会合成析构函数?什么时候一个类必须定义自己的析构函数? 析构函数是一个成员函数,它的名字与类的名字相同,在名字前加一个代字符~,没有返回值,没有形参,用于类的对象超出作用域时释放对象所获取的资源,或删除指向动态分配对象的指针。

合成析构函数的作用:1,按对象创建时的逆序撤销每个非static成员,2,对于类类型的成员,合成析构函数调用该成员的析构函数来撤销对象。

编译器总会为每个类合成一个析构函数。

当1,需要释放指针成员的资源时,2,需要执行某些特定工作时,必须自己定义析构函数。

12.确定在习题13.4中概略定义的NoName类是否需要析构函数,如果需要,实现它。 根据“三法则”需要显式定义析构函数,实现为: NoName:: ~NoName {

}

delete pstring;

13. 理解复制控制成员和构造函数的一个良好方式是定义一个简单类,该类具有这些成员,每个成员打印自己的名字: struct Exmp1 { Exmp1() { std::cout << “Exmp1()” << std::endl; } Exmp1( const Exmp1& ) { std::cout << “Exmp1( const Exmp1& )” << std::endl; } // … };

编写一个像Exmp1这样的类,给出复制控制成员和其他构造函数。然后写一个程序,用不同方式使用Exmp1类型的对象:作为非引用和引用形参传递,动态分配,放在容器中,等等,研究何时执行哪个构造函数和复制控制成员,可以帮助你融会贯通第理解这些概念。 // 13.14_CopyControlMember.cpp : 定义控制台应用程序的入口点。 //

#include \#include #include class Exmp {

public: // constructor Exmp() { std::cout << \ << std::endl; } // // copy constructor Exmp(const Exmp&) { std::cout << \ << std::endl; } // overload operator Exmp& operator=( const Exmp&) { std::cout << \ << std::endl; return *this; }

};

// destructor ~Exmp() { std::cout << \ << std::endl; }

void func1(Exmp obj) // 形参为Exmp的对象 { }

void func2(Exmp & obj) // 形参为Exmp对象的引用 { }

Exmp func3() { Exmp obj; return obj; // 返回exmp对象 }

int _tmain(int argc, _TCHAR* argv[]) { Exmp exmp1; Exmp exmp2(exmp1); exmp2 = exmp2; func1( exmp1); func2( exmp1); exmp1 = func3(); Exmp *p = new Exmp; std::vector evec(3); delete p; system(\); return 0; }

15.下面的代码中发生了多少次析构函数的调用? void fcn( const Sales_item *trans, Sales_item accm ) { Sales_item item1(*trans), item2(accm); if ( !item1.same_isbn(item2) ) return; if ( item1.avg_price() <= 99 ) return; else if ( item2.avg_price() <= 99 ) return; // … }

3次,分别用在当函数执行完毕后的撤销非static的形参对象accm和 局部对象item1,item2.

16.编写本节中描述的Message类。 class Message {

public: Message(const std::string &str = \): contents (str) { } Message(const Message&); Message& operator=(const Message&); ~Message(); void save(Folder &);

void remove(Folder &); void addFldr(Folder*); void remFldr(Folder*); private: std::string contents; std::set folders; void put_Msg_in_Folders(const std::set&); void remove_Msg_from_Folders(); };

Message::Message(const Message &m) : contents(m.contents), folders(m.folders) { put_Msg_in_Folders(folders); }

void Message::put_Msg_in_Folders( const std::set &rhs) { for ( std::set::const_iterator beg = rhs.begin(); beg != rhs.end(); ++beg ) { (*beg)->addMsg(this); } }

Message& Message::operator =(const Message &rhs ) { if (&rhs != this) { remove_Msg_from_Folders(); contents = rhs.contents; folders = rhs.folders; put_Msg_in_Folders(rhs.folders); } return *this; }

void Message::remove_Msg_from_Folders() { for ( std::set::const_iterator beg = folders.begin(); beg != folders.end(); ++beg ) { (*beg)->remMsg(this); } }

Message::~Message() { remove_Msg_from_Folders(); }

void Message::save(Folder &fldr) { addFldr(&fldr); fldr.remMsg(this); }

void Message::remove(Folder & fldr) { remFldr(&fldr); fldr.remMsg(this); }

void Message::addFldr(Folder* fldr) { folders.insert(fldr); }

void Message::remFldr(Folder* fldr) { folders.erase(fldr); }

17.为message类增加与Folder的addMsg和remMsg操作类似的函数。这些函数可以命名为addFldr和remFldr,应接受一个指向Folder的指针并将该指针插入到folders。这些函数可为private的,因为它们将仅在Message类的实现中使用。 void Message::addFldr( Folder* fldr ) { folders.insert(fldr); }

void Message::remFldr(Folder* fldr) { folders.erase(fldr); }

18.编写相应的Folder类。该类应保存一个set, 包含指向Message的元素。 class Message; class Folder {

public: Folder() { } Folder( const Folder&); Folder& operator=(const Folder&); ~Folder(); void save( Message &); void remove(Message&); void addMsg( Message &); void remMsg( Message &);

private: std::set messages; void put_Fldr_In_Messages(const std::set&); void remove_Fldr_Messages(); };

Folder::Folder( const Folder &f ):messages(f.messages) { put_Fldr_In_Messages(messages); }

void Folder::put_Fldr_In_Messages(const std::set &rhs) { for(std::set::const_iterator beg = rhs.begin(); beg != rhs.end(); ++beg) (*beg)->addFldr(this); }

Folder& Folder::operator =(const Folder &rhs) { if (&rhs != this ) { remove_Fldr_Messages(); messages = rhs.messages; put_Fldr_In_Messages(rhs.messages); } return *this; }

void Folder::remove_Fldr_Messages() { for (std::set::const_iterator beg = messages.begin(); beg != messages.end(); ++beg ) { (*beg)->remFldr(this);

} }

Folder::~Folder() { remove_Fldr_Messages(); }

void Folder::save(Message & msg) { addMsg(&msg); msg.addFldr(this); }

void Folder::remove(Message& msg) { remMsg(&msg); msg.remFldr(this); }

void Folder::addMsg(Message & msg) { messages.insert(mag); }

void Folder::remMsg(Message & msg) { messages.erase(msg); }

19.在Message类和Folder类中增加save和remove操作,这些操作应接受一个Folder,并将该Folder加入到指向这个Message的Folder集中(或从其中删除该Folder)。操作还必须更新Folder以反映它指向该Message,这可以通过调用addMsg或remMsg完成。 void Message::save(Folder &fldr) { addFldr(&fldr); fldr.remMsg(this); }

void Message::remove(Folder & fldr) { remFldr(&fldr); fldr.remMsg(this); }

20.对于HasPtr类的原始版本(依赖于复制控制的默认定义),描述下面代码中会发生什么: int i =42; HasPtr p1( &i, 42); HasPtr p2 = p1;

cout << p2.get_ptr_val() << endll; 输出 42 p1.set_ptr_val(0);

cout << p2.get_ptr_val() << endl; 输出 0

21.如果给HasPtr类添加一个析构函数,用来删除指针成员,会发生什么?

如果这样,则撤销一个HasPtr对象时会删除其指针成员所指向的对象,从而使得当一个HasPtr对象被撤消后,其他由该对象复制而创建的HasPtr对象中的指针成员也无法使用。

22.什么是使用计数?

使用计数是复制控制成员中使用的编程技术。将一个计数器与类指向的对象相关联,用于跟踪该类有多少个对象共享同一指针。创建一个单独类指向共享对象并管理使用计数。由构造函数设置共享对象的状态并将使用计数置为1。每当由复制构造函数或赋值操作符生成一个新副本时,使用计数加1。由析构函数撤销对象或作为赋值操作符的左操作数撤销对象时,使用计数减少1。赋值操作符和析构函数检查使用计数是否已减至0,若是,则撤销对象。

23.什么是智能指针?智能指针如何与实现普通指针行为的类相区别? 智能指针式一个行为类似指针但也提供其他功能的类。这个类与实现普通指针行为的类区别在于:智能指针通常接受指向动态分配对象的指针并负责删除该对象。用户分配对象,但由智能指针类删除它,因此智能指针类需要实现赋值控制成员来管理指向共享对象的指针。只有在撤销了指向共享对象的最后一个智能指针后,才能删除该共享对象。使用计数就是实现智能指针类最常用的一个方式。

24.实现你自己的使用计数式HasPtr类的版本。 class U_Ptr { friend class HasPtr; int *ip; size_t use; U_Ptr( int *p ): ip (p ), use (1) { } ~U_Ptr() { delete ip; };

class HasPtr {

public: HasPtr( int *p, int i) : ptr(new U_Ptr(p), val(i) { } HasPtr( const HasPtr &rhs ): ptr( rhs.ptr), val(rhs.val) { ++ptr->use; } HasPtr& operator=( const HasPtr&); ~HasPtr() { if ( --ptr->use == 0 ) delete ptr; } int *get_ptr() const { return ptr->ip; } int get_int() const { return val; } void set_ptr( int *p ) { ptr->ip = p; } void set_int( int i ) { val = i; } int get_ptr_val() const { return *ptr->ip; } void set_ptr_val(int i) { *ptr->ip = i; } private: U_Ptr *ptr; int val; };

HasPtr& HasPtr::operator=( const HasPtr &rhs ) { ++rhs.ptr->use; if ( --ptr->use == 0 ) delete ptr; ptr = rhs.ptr; val = rhs.val; return *this; }

25.什么是值型类?

是指具有值语义的类,其特征是:对该类对象进行赋值时,会得到一个不同的新副本,对副本所做的改变不会影响原有对象。

26.实现自己的值型HasPtr类版本。 class HasPtr

{

public: HasPtr( const int &p, int i): ptr(new int(p)), val(i) { } HasPtr( const HasPtr &rhs ): ptr( new int (*rhs.ptr) ), val(rhs.val) { } HasPtr& operator=( const HasPtr&rhs) { *ptr = *rhs.ptr; val = rhs.val; return *this; } ~HasPtr() { delete ptr; } int *get_ptr() const { return ptr; } int get_ptr_val() const { return *ptr; } int get_int() const { return val; } void set_ptr( int *p ) { ptr = p; } void set_int( int i ) { val = i; } void set_ptr_val( int p ) const { *ptr = p; } private: int *ptr; int val; };

27.值型HasPtr类定义了所有复制控制成员。描述将会发生什么,如果该类: (a) 定义了赋值构造函数和析构函数但没有定义赋值操作符。 (b) 定义了赋值构造函数和复制操作符但没有定义析构函数。 (c) 定义了析构函数但没有定义复制构造函数和赋值操作符。

(a)此种情况下,使用编译器合成的赋值操作符。因此若将一个HasPtr对象赋值给另一个HasPtr对象,则两个对象的ptr成员值相同,即二者指向同一基础int对象,当其中一个对象被撤销后,该基础int对象也被撤销,使得另一个对象中的ptr成员指向一个失效的基础int对象。而且当另一个对象被撤销时,会因为析构函数对同一指针进行重复删除而出现内存访问非法的错误。此外,被赋值的HasPtr对象的ptr成员原来所指向的基础int对象不能再被访问,但也没有被撤销。

(b) 此时使用编译器合成的析构函数,当一个对象被撤销后,其成员所指向的基础int对象不会被撤销,会一直存在,从而导致内存泄露。

(c) 此时,使用编译器合成的复制构造函数和赋值操作符。当将一个对象赋值给另一个HasPtr对象时或复制构造另一个HasPtr对象时,则两个HasPtr对象的ptr成员值相同,其他出现的问题与(a)的情况相同。

28.对于如下的类,实现默认构造函数和必要的复制控制成员。 (a) class TreeNode {

public: // … private: std::string value; int count; TreeNode *left; TreeNode *right; }; (a)

TreeNode::TreeNode() : count(0), left(0), right(0) { }

TreeNode::TreeNode( const TreeNode &rhs ) : value( rhs.value) { count = rhs.count; if ( rhs.left ) left = new TreeNode( *rhs.left); else left = 0; if ( rhs.right ) right = new TreeNode( *rhs. right); else right = 0; }

TreeNode::~TreeNode() { if (left) delete left; if ( right ) delete right; }

(b) class BinStrTree {

public:

// … private: TreeNode *root; }; (b)

BinStrTree:: BinStrTree() : root(0) { }

BinStrTree:: BinStrTree( const BinStrTree & rhs ) { if ( rhs.root ) root = new TreeNode(*ths.root); else root = 0; }

BinStrTree::~ BinStrTree() { if ( root ) delete root; }

本文来源:https://www.bwwdw.com/article/pxow.html

Top