C++Primer 第13章复制控制课后习题答案.docx
《C++Primer 第13章复制控制课后习题答案.docx》由会员分享,可在线阅读,更多相关《C++Primer 第13章复制控制课后习题答案.docx(22页珍藏版)》请在冰点文库上搜索。
C++Primer第13章复制控制课后习题答案
第十三章复制控制
1.什么是复制构造函数?
何时使用它?
只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数叫复制构造函数。
copyconstructorwillbeusedunderthesesituations:
根据一个同类型的对象显式或隐式初始化一个对象;
复制一个对象,将它作为实参传给一个函数;
从函数返回时复制一个对象;
初始化顺序容器中的对象;
根据元素初始化式列表初始化数组元素。
2.下面第二个初始化不能编译。
可以从vector的定义得出什么推断?
vectorv1(42);
vectorv2=42;
若能编译成功说明,这是个复制初始化,创建v2时,首先调用接受一个int型形参的vector构造函数,创建一个临时vector对象,然后再调用复制构造函数用这个临时对象来初始化v2。
现在不能编译,说明vector没有定义复制构造函数。
3.假定Point为类类型,该类类型有一个复制构造函数,指出下面程序段中每一个使用了复制构造函数的地方:
Pointglobal;
Pointfoo_bar(Pointarg)//调用此函数时,将实参对象的副本传递给形参Point的对象arg
{
Pintlocal=arg;//调用复制构造函数,将局部对象local初始化为形参arg的副本。
Point*heap=newPoint(global);//调用复制构造函数用全局对象global来初始化
//Point对象*heap
*heap=local;
Pointpa[4]={local,*heap};//使用数组初始化列表来初始化数组的每个元素。
return*heap;//从函数返回Point对象*heap的副本
}
4.对于如下的类的简单定义,编写一个复制构造函数所有成员。
复制pstring指向的对象而不是复制指针。
structNoName{
NoName():
pstring(newstd:
:
string),i(0),d(0){}
private:
std:
:
string*pstring;
inti;
doubled;
};
NoName(constNoName&orig):
pstring(newstring(*(orig.pstring))),i(orig.i),d(orig.d)
{
//pstring=newstring;
//*pstring=*(orig.pstring);
}
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(constSales_itemrhs);
它不能工作的原因是:
当形参为非引用类型时,将复制实参的值,给这个copyconstructor,但是,每当以传值方式传递参数时,会导致调用复制构造函数,因此,如果要使用以传值方式传递参数的copyconstructor,必须使用一个“不以传值方式传递参数”的copyconstructor,否则就会导致copyconstructor的无穷递归调用。
这个“不以传值方式传递参数”的方法就是使用形参是一个引用的copyconstructor,即以传地址的方式传递参数。
7.类何时需要定义复制操作符?
在需要定义复制构造函数时,也需要定义赋值操作符,即如果一个类
(1)类中包含指针型数据成员,
(2)或者在进行赋值操作时需要做一些特定工作,则该类需要定义赋值操作符。
8.对于习题13.5中列出的每个类型,指出类是否需要赋值操作符。
(b)需要赋值操作符,因为涉及指针和动态分配内存;
(c)需要,在用已存在的Payroll对象给另一个Payroll对象赋值时,需要提供唯一的ID。
9.习题13.4中包括NoName类的简单定义,确定这个类是否需要赋值操作符,如果需要,实现它。
因为有指针类的数据成员,所以需要赋值操作符,实现为:
NoName&operator=(constNoName&Nn)
{
i(Nn.i);
d(Nn.d);
pstring=newstring;
*pstring=*(Nn.pstring);
return*this;
}
10.定义一个Employee类,包含雇员名字和一个唯一的雇员标识,为该类定义默认构造函数和参数为表示雇员名字的string构造函数。
如果该类需要复制构造函数或赋值操作符,实现这些函数。
classEmployee
{
public:
Employee():
ID(cnt){cnt++;}//默认构造函数
Employee(conststd:
:
string&na):
name(na),ID(cnt)
{cnt++;}//构造函数
//拷贝构造函数
Employee(constEmployee&rhs):
name(rhs.name),ID(cnt)
{cnt++;}
//赋值操作符
Employee&operator=(constEmployee&rhs)
{
name=rhs.name;
return*this;
}
private:
stringname;
intID;
staticintcnt;
};
另外需要在类外对static成员进行初始化:
intEmployee:
:
cnt=1;
11.什么是析构函数?
合成析构函数有什么用?
什么时候会合成析构函数?
什么时候一个类必须定义自己的析构函数?
析构函数是一个成员函数,它的名字与类的名字相同,在名字前加一个代字符~,没有返回值,没有形参,用于类的对象超出作用域时释放对象所获取的资源,或删除指向动态分配对象的指针。
合成析构函数的作用:
1,按对象创建时的逆序撤销每个非static成员,2,对于类类型的成员,合成析构函数调用该成员的析构函数来撤销对象。
编译器总会为每个类合成一个析构函数。
当1,需要释放指针成员的资源时,2,需要执行某些特定工作时,必须自己定义析构函数。
12.确定在习题13.4中概略定义的NoName类是否需要析构函数,如果需要,实现它。
根据“三法则”需要显式定义析构函数,实现为:
NoName:
:
~NoName
{
deletepstring;
}
13.理解复制控制成员和构造函数的一个良好方式是定义一个简单类,该类具有这些成员,每个成员打印自己的名字:
structExmp1{
Exmp1(){std:
:
cout<<“Exmp1()”<:
endl;}
Exmp1(constExmp1&)
{std:
:
cout<<“Exmp1(constExmp1&)”<:
endl;}
//…
};
编写一个像Exmp1这样的类,给出复制控制成员和其他构造函数。
然后写一个程序,用不同方式使用Exmp1类型的对象:
作为非引用和引用形参传递,动态分配,放在容器中,等等,研究何时执行哪个构造函数和复制控制成员,可以帮助你融会贯通第理解这些概念。
//13.14_CopyControlMember.cpp:
定义控制台应用程序的入口点。
//
#include"stdafx.h"
#include
#include
classExmp
{
public:
//constructor
Exmp()
{
std:
:
cout<<"UsingExmp()."<:
endl;
}
//
//copyconstructor
Exmp(constExmp&)
{
std:
:
cout<<"UsingExmp(constExmp&)_copyconstructor."<:
endl;
}
//overloadoperator
Exmp&operator=(constExmp&)
{
std:
:
cout<<"UsingExmp&operator=(constExmp&)_overloadoperator."<:
endl;
return*this;
}
//destructor
~Exmp()
{
std:
:
cout<<"Using~Exmp()."<:
endl;
}
};
voidfunc1(Exmpobj)//形参为Exmp的对象
{
}
voidfunc2(Exmp&obj)//形参为Exmp对象的引用
{
}
Exmpfunc3()
{
Exmpobj;
returnobj;//返回exmp对象
}
int_tmain(intargc,_TCHAR*argv[])
{
Exmpexmp1;
Exmpexmp2(exmp1);
exmp2=exmp2;
func1(exmp1);
func2(exmp1);
exmp1=func3();
Exmp*p=newExmp;
std:
:
vectorevec(3);
deletep;
system("pause");
return0;
}
15.下面的代码中发生了多少次析构函数的调用?
voidfcn(constSales_item*trans,Sales_itemaccm)
{
Sales_itemitem1(*trans),item2(accm);
if(!
item1.same_isbn(item2))return;
if(item1.avg_price()<=99)return;
elseif(item2.avg_price()<=99)return;
//…
}
3次,分别用在当函数执行完毕后的撤销非static的形参对象accm和局部对象item1,item2.
16.编写本节中描述的Message类。
classMessage
{
public:
Message(conststd:
:
string&str=""):
contents(str){}
Message(constMessage&);
Message&operator=(constMessage&);
~Message();
voidsave(Folder&);
voidremove(Folder&);
voidaddFldr(Folder*);
voidremFldr(Folder*);
private:
std:
:
stringcontents;
std:
:
setfolders;
voidput_Msg_in_Folders(conststd:
:
set&);
voidremove_Msg_from_Folders();
};
Message:
:
Message(constMessage&m):
contents(m.contents),folders(m.folders)
{
put_Msg_in_Folders(folders);
}
voidMessage:
:
put_Msg_in_Folders(conststd:
:
set&rhs)
{
for(std:
:
set:
:
const_iteratorbeg=rhs.begin();beg!
=rhs.end();++beg)
{
(*beg)->addMsg(this);
}
}
Message&Message:
:
operator=(constMessage&rhs)
{
if(&rhs!
=this)
{
remove_Msg_from_Folders();
contents=rhs.contents;
folders=rhs.folders;
put_Msg_in_Folders(rhs.folders);
}
return*this;
}
voidMessage:
:
remove_Msg_from_Folders()
{
for(std:
:
set:
:
const_iteratorbeg=folders.begin();beg!
=folders.end();++beg)
{
(*beg)->remMsg(this);
}
}
Message:
:
~Message()
{
remove_Msg_from_Folders();
}
voidMessage:
:
save(Folder&fldr)
{
addFldr(&fldr);
fldr.remMsg(this);
}
voidMessage:
:
remove(Folder&fldr)
{
remFldr(&fldr);
fldr.remMsg(this);
}
voidMessage:
:
addFldr(Folder*fldr)
{
folders.insert(fldr);
}
voidMessage:
:
remFldr(Folder*fldr)
{
folders.erase(fldr);
}
17.为message类增加与Folder的addMsg和remMsg操作类似的函数。
这些函数可以命名为addFldr和remFldr,应接受一个指向Folder的指针并将该指针插入到folders。
这些函数可为private的,因为它们将仅在Message类的实现中使用。
voidMessage:
:
addFldr(Folder*fldr)
{
folders.insert(fldr);
}
voidMessage:
:
remFldr(Folder*fldr)
{
folders.erase(fldr);
}
18.编写相应的Folder类。
该类应保存一个set,包含指向Message的元素。
classMessage;
classFolder
{
public:
Folder(){}
Folder(constFolder&);
Folder&operator=(constFolder&);
~Folder();
voidsave(Message&);
voidremove(Message&);
voidaddMsg(Message&);
voidremMsg(Message&);
private:
std:
:
setmessages;
voidput_Fldr_In_Messages(conststd:
:
set&);
voidremove_Fldr_Messages();
};
Folder:
:
Folder(constFolder&f):
messages(f.messages)
{
put_Fldr_In_Messages(messages);
}
voidFolder:
:
put_Fldr_In_Messages(conststd:
:
set&rhs)
{
for(std:
:
set:
:
const_iteratorbeg=rhs.begin();beg!
=rhs.end();++beg)
(*beg)->addFldr(this);
}
Folder&Folder:
:
operator=(constFolder&rhs)
{
if(&rhs!
=this)
{
remove_Fldr_Messages();
messages=rhs.messages;
put_Fldr_In_Messages(rhs.messages);
}
return*this;
}
voidFolder:
:
remove_Fldr_Messages()
{
for(std:
:
set:
:
const_iteratorbeg=messages.begin();
beg!
=messages.end();++beg)
{
(*beg)->remFldr(this);
}
}
Folder:
:
~Folder()
{
remove_Fldr_Messages();
}
voidFolder:
:
save(Message&msg)
{
addMsg(&msg);
msg.addFldr(this);
}
voidFolder:
:
remove(Message&msg)
{
remMsg(&msg);
msg.remFldr(this);
}
voidFolder:
:
addMsg(Message&msg)
{
messages.insert(mag);
}
voidFolder:
:
remMsg(Message&msg)
{
messages.erase(msg);
}
19.在Message类和Folder类中增加save和remove操作,这些操作应接受一个Folder,并将该Folder加入到指向这个Message的Folder集中(或从其中删除该Folder)。
操作还必须更新Folder以反映它指向该Message,这可以通过调用addMsg或remMsg完成。
voidMessage:
:
save(Folder&fldr)
{
addFldr(&fldr);
fldr.remMsg(this);
}
voidMessage:
:
remove(Folder&fldr)
{
remFldr(&fldr);
fldr.remMsg(this);
}
20.对于HasPtr类的原始版本(依赖于复制控制的默认定义),描述下面代码中会发生什么:
inti=42;
HasPtrp1(&i,42);
HasPtrp2=p1;
cout<p1.set_ptr_val(0);
cout<21.如果给HasPtr类添加一个析构函数,用来删除指针成员,会发生什么?
如果这样,则撤销一个HasPtr对象时会删除其指针成员所指向的对象,从而使得当一个HasPtr对象被撤消后,其他由该对象复制而创建的HasPtr对象中的指针成员也无法使用。
22.什么是使用计数?
使用计数是复制控制成员中使用的编程技术。
将一个计数器与类指向的对象相关联,用于跟踪该类有多少个对象共享同一指针。
创建一个单独类指向共享对象并管理使用计数。
由构造函数设置共享对象的状态并将使用计数置为1。
每当由复制构造函数或赋值操作符生成一个新副本时,使用计数加1。
由析构函数撤销对象或作为赋值操作符的左操作数撤销对象时,使用计数减少1。
赋值操作符和析构函数检查使用计数是否已减至0,若是,则撤销对象。
23.什么是智能指针?
智能指针如何与实现普通指针行为的类相区别?
智能指针式一个行为类似指针但也提供其他功能的类。
这个类与实现普通指针行为的类区别在于:
智能指针通常接受指向动态分配对象的指针并负责删除该对象。
用户分配对象,但由智能指针类删除它,因此智能指针类需要实现赋值控制成员来管理指向共享对象的指针。
只有在撤销了指向共享对象的最后一个智能指针后,才能删除该共享对象。
使用计数就是实现智能指针类最常用的一个方式。
24.实现你自己的使用计数式HasPtr类的版本。
classU_Ptr
{
friendclassHasPtr;
int*ip;
size_tuse;
U_Ptr(int*p):
ip(p),use
(1){}
~U_Ptr(){deleteip;
};
classHasPtr
{
public:
HasPtr(int*p,inti):
ptr(newU_Ptr(p),val(i){}
HasPtr(constHasPtr&rhs):
ptr(rhs.ptr),val(rhs.val)
{
++ptr->use;
}
HasPtr&operator=(constHasPtr&);
~HasPtr()
{
if(--ptr->use==0)deleteptr;
}
int*get_ptr()const{returnptr->ip;}
intget_int()const{returnval;}
voidset_ptr(int*p){ptr->ip=p;}
voidset_int(inti){val=i;}
intget_ptr_val()const{return*ptr->ip;}
voidset_ptr_val(inti){*ptr->ip=i;}
private:
U_Ptr*ptr;
intval;
};
HasPtr&HasPtr:
:
operator=(constHasPtr&rhs)
{
++rhs.ptr->use;
if(--ptr->use==0)
deleteptr;
ptr=rhs.ptr;
val=rhs.val;