13.1节练习

练习13.1 拷贝构造函数是什么?是什么时候使用它?

接受对象类型本身的引用的构造函数。当要求编译器将右侧运算符对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。


练习13.2 解释为什么下面的声明是非法的:

Sales_data::Sales_data(Sales_data rhs);

rhs要拷贝给参数匹配的构造函数,此时需要调用拷贝构造函数,然后得调用上述函数,如此无限循环。


练习13.3 当我们拷贝一个StrBlob时,会发生什么?拷贝一个StrBlobPtr呢?

拷贝一个是shared_ptr类的StrBlob,引用计数会加1。

拷贝一个是weak_ptr类的StrBlobPtr,引用计数不改变。


练习13.4 假定Point是一个类类型,它有一个public的拷贝构造函数,指出下面程序片段中哪些地方使用了拷贝构造函数:

Point global;
Point foo_bar(Point arg)//将一个对象作为实参传递给一个非引用类型的形参
			//以及从一个返回类型为非引用类型的函数返回一个对象
{
	Point local = arg, *heap = new Point(global); //用=定义变量
	*heap = local;
	Point pa[4] = { local,*heap };    //用花括号列表初始化一个数组中的元素
	return *heap;
}



练习13.5 给定下面的类框架,编写一个拷贝构造函数,拷贝所有成员。你的构造函数应该动态分配一个新的string,并将对象拷贝到ps指向的位置,而不是拷贝ps本身:

class HasPtr {
public:
	HasPtr(const std::string &s = std::string()) :ps(new std::string(s)), i(0) {}
	HasPtr(const Hasptr& s2) :ps(new std::string(*s2.ps)), i(s2.i) {}
private:
	std::string *ps;
	int i;
};

练习13.6 拷贝赋值运算符是什么?什么时候使用它?合成拷贝赋值运算符完成什么工作?什么时候会生成合成拷贝赋值运算符?

拷贝赋值运算符控制对象如何赋值。

赋值运算符通常用来返回一个指向其左侧运算对象的引用。

合成拷贝赋值运算符用来禁止该类型对象的赋值。

如果一个类未定义自己的拷贝赋值运算符,编译器会为它生成一个合成拷贝赋值运算符。


练习13.7 当我们将一个StrBlob赋值给另一个StrBlob时,用发生什么?赋值StrBlobPtr呢?

引用计数值加1,两个对象指向同一个。

被赋值的StrBlobPtr弱共享右侧StrBlob,引用计数未改变。


练习13.8 为13.1.1节练习中的HasPtr类编写赋值运算符。类似拷贝构造函数,你的赋值运算符应该将对象拷贝到ps指向的位置。

	HasPtr& operator=(const HasPtr& rhs)
	{
		ps = new std::string(*rhs.ps);
		i = rhs.i;
		return *this;
	}

练习13.9 析构函数是什么?合成析构函数完成什么工作?什么时候会生成合成析构函数?

析构函数是用来释放对象使用的资源,并销毁对象的非静态数据成员。

合成析构函数被用来阻止该类型的对象被销毁。如果不是这种情况,合成析构函数的函数体就为空。

当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。


练习13.10 当一个StrBlob对象销毁时会发生什么?一个StrBlobPtr对象销毁时呢?

StrBlob对象的引用计数减一,若引用计数为0,对象被销毁。

而StrBlobPtr的对象不再享有管理控制权,引用计数不变。


练习13.11 为前面练习的HasPtr类添加一个析构函数。

	~HasPtr() { delete ps; }

练习13.12 在下面的代码片段中会发生几次析构函数调用?

#include 
#include 
using namespace std;
static int i = 0;
class Sales_data {
public:
	Sales_data() = default;
	Sales_data(const std::string &s) :book(s){}
	~Sales_data() { cout << "used Sales_data: "<< ++i << endl; }
	const std::string isbn() const { return book; }
private:	
	std::string book;
};
bool fcn(const Sales_data *trans, Sales_data accum)
{
	Sales_data item1(*trans), item2(accum);
	return item1.isbn() != item2.isbn();
}
int main()
{
	Sales_data m1;
	Sales_data m2;
	cout << fcn(&m1, m2) << endl;
	
}
在题中指定的代码段中会调用三次析构函数。

trans离开作用域不会执行析构函数(指向一个对象的引用或指针离开作用域)。

整段代码一共调用五次析构函数。


练习13.13 理解拷贝控制成员和构造函数的一个好方法是定义一个简单的类,为该类定义这些成员,每个成员都打印自己的名字:

struct X {
	X() { std::cout << "X()" << std::endl; }
	X(const X&) { std::cout << "X(const X&)" << std::endl; }
};

给X添加拷贝赋值运算符和析构函数,并编写一个程序以不同方式使用X的对象:

将它们作为非引用和引用参数传递;

动态分配它们;

将它们存放于容器中;

诸如此类。观察程序的输出,直到你确认理解了什么时候用使用拷贝控制成员,以及为什么会使用它们。当你观察程序输出时,记住编译器可以略过对拷贝构造函数的调用。

#include 
#include 
#include 
using namespace std;
struct X {
	X() { std::cout << "X()" << std::endl; }       //构造函数
	X(const X&) { std::cout << "X(const X&)" << std::endl; }     //拷贝构造函数
	~X() { cout << "used X()" << endl; }       //析构函数
	X& operator= (const X&) { cout << "X& operator" << endl; return *this; } //赋值运算符
};
void func_1(X) {};
void func_2(X&) {};
int main()
{
	X a1;                 //直接初始化,下面三个调用拷贝构造函数
	X a2 = a1;   
	X a3(a1);              //类类型使用拷贝初始化
	X a4{ a1 };
	cout << "test 2" << endl;
	func_1(a1);         //编译器略过对拷贝构造函数的调用
	func_2(a2);         //拷贝构造函数
	cout << "test 3" << endl;
	X* p = new X[5];     //输出5个构造函数X()
	delete[]p;
	cout << "test 4" << endl;
	vector xvec;     
	xvec.push_back(a1); //容器调用拷贝构造函数
	a3 = a1;
}


练习13.14 假定numbered是一个类,它有一个默认构造函数,能为每个对象生成一个唯一的序号,保存在名为mysn的数据成员中。假定numbered使用合成的拷贝控制成员,并给定如下函数:

#include 
using namespace std;
static int i = 7;
struct numbered {
	numbered() { ++i ; }
	int mysn = i;
};
void f(numbered s)
{
	cout << s.mysn << endl;
}
int main()
{
	numbered a, b = a, c = b;
	numbered d;
	f(a);
	f(b);
	f(c);
	f(d);
}

f(a),f(b),f(c)会输出相同的结果。

f(d) 则不同。


练习13.15 假定numbered定义了一个拷贝构造函数,能生成一个新的序号。这会改变上一题中调用的输出结果吗?如果会改变,为什么?新的输出结果是什么?

	numbered(const numbered&) { ++i; }  //新添加的拷贝构造函数

会改变,因为拷贝初始化在用=定义变量时候会发生,以及传递非引用实参。


练习13.16 如果f中的参数是const numbered& 将会怎样?还会改变输出结果吗?如果会改变,为什么?新的输出结果是什么?

void f(const numbered &s)
{
	cout << s.mysn << endl;
}

传递引用实参不会调用拷贝函数,会影响到mysn的数值。


练习13.17分别编写前三题中所描述的numbered和f,验证输出结果。

(编译器可能略过拷贝构造函数,最终输出结果可能与预想的有偏差)


练习13.18 定义一个Employee类,它包含雇员的姓名和唯一的雇员证号。为这个类定义默认构造函数,以及接受一个表示雇员姓名的string的构造函数。每个构造函数应该通过递增一个static数据成员来生成唯一的证号。

#include 
#include 
using namespace std;
static int isbn = 1;
struct Employee {

	Employee() = default;
	Employee(const std::string s) :name(s) ,i(isbn){ ++isbn; }
	std::string name;
	int i = isbn;
};
int main()
{
	Employee name1("Rose");
	Employee name2("Irving");
	cout << name1.name << " " << name1.i << endl;
	cout << name2.name << " " << name2.i << endl;
}

练习13.19 你的Employee类需要定义它自己的拷贝控制成员吗?如果需要,为什么?如果不需要,为什么?实现你认为Employee需要的拷贝控制成员。

。。。需要拷贝赋值运算符。

因为有个同名的。

#include 
#include 
using namespace std;
static int isbn = 1;
struct Employee {

	Employee() = default;
	Employee(const std::string s) :name(s), i(isbn) { ++isbn; }

	Employee& operator=(const Employee& m)
	{
		name = m.name;
		return *this;
	}
	std::string name;
	int i = isbn;
};
int main()
{
	Employee name1("Rose");
	Employee name2("Irving");
	Employee name2_ = name2;
	name2_.i = 20;
	
	cout << name1.name << " " << name1.i << endl;
	cout << name2.name << " " << name2.i << endl;
	cout << name2_.name << " " << name2_.i << endl;
}


你可能感兴趣的:(C++primer)