999999999999999## C++primer第十三章(第五版)答案
13.1
拷贝构造函数定义了当用同类型的对象初始化本对象的时候做什么。如果构造函数的第一个参数是自身的引用,且所有参数都有默认值,则这个构造函数是一个拷贝构造函数。
拷贝构造函数发生在需要拷贝初始化的时候,拷贝初始化发生的条件如下
- 使用=定义变量时
- 将一个对象作为实参传递给一个非引用类型的形参
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表初始化一个数组中的元素或一个聚合类中的成员
13.2
拷贝构造函数的第一个参数必须是一个引用类型
13.3
拷贝StrBlob时,其成员shared_ptr的引用计数加一。
拷贝StrBlboPtr时,成员unique_ptr引用计数不变。
13.4
发生拷贝构造的条件 - 使用=定义变量时
- 将一个对象作为实参传递给一个非引用类型的形参
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表初始化一个数组中的元素或一个聚合类中的成员
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
#include <iostream>
using namespace std;
class HasPtr
{
public:
HasPtr(const string& s = string()) :
ps(new string(s)), i(0) {
};
HasPtr(const HasPtr& h) :ps(new string(*(h.ps))), i(h.i) {
}
private:
string* ps;
int i;
};
13.6
-
拷贝赋值运算符是重载”=“运算符,即为一个名为operator=的函数,其参数与其所在类的的类型相同。
-
在发生赋值操作的时候使用。
-
合成拷贝赋值的工作:将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员。
-
当类未定义自己的拷贝赋值运算符,编译器会生成一个合成拷贝运算符。
13.7
发生浅拷贝,赋值运算符两侧的指针指向同一块内存空间。shared_ptr引用计数加1,unique_ptr计数不变。
13.8
HasPtr& operator=(const HasPtr& has)
{
delete ps;
ps = new string(*has.ps);
i = has.i;
return *this;
}
13.9
-
析构函数是类的一个成员函数,名字由波浪号接类名构成。它没有返回值,也不接受参数。
-
通常的,析构函数负责释放对象在生存期分配的所有资源。对于某些类,合成析构函数被用来阻止该类型的对象被销毁。如果不是这种情况,合成析构函数的函数体就为空。
-
当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。
13.10
所有对象的数据成员被销毁,智能指针的计数减一。
所有对象的数据成员被销毁,弱指针不影响计数器。
13.11
~HasPtr() {
delete ps;
}
13.12
三次,分别为item1,item2,accum。
当指向一个对象的引用或指针离开作用域时,析构函数不会执行。所以trans不会执行析构函数。
13.13
可以看到除了拷贝赋值运算符的不算,因为它并没有构造一个对象,构造函数和拷贝构造函数一共7个,而析构函数也有7个。其中引用形参传递没有调用拷贝构造函数,而非引用形参调用了拷贝构造函数,在离开了函数体时,对象调用析构函数,销毁对象。我们动态分配的内存空间需要亲自删除,如果没有删除,内存空间泄露,析构函数不会调用(去除最后的delete,析构函数会变为5个)。
#include <iostream>
#include<vector>
using namespace std;
struct X
{
int v;
X(int i):v(i) {
cout << "构造函数" << endl; }
X(const X&) {
cout << "拷贝构造函数" << endl; }
X& operator=(const X&) {
cout << "拷贝复制运算符" << endl; return *this; }
~X() {
cout << "析构函数" << endl; }
};
void reference(X&) {
};
void unreference(X) {
};
int main()
{
X x1(1);
X x2 = x1;
X x3(x2);
X * x4 = new X(2);
X* x5 = new X(x1);
x2 = x1;
vector<X>x6;
x6.push_back(x1);
cout << "引用传值 " << endl << endl;
reference(x1);
cout << "非引用传值" << endl;
unreference(x1);
cout << "程序结束" << endl;
delete x4,x5;
}
输出结果如下
构造函数
拷贝构造函数
拷贝构造函数
构造函数
拷贝构造函数
拷贝复制运算符
拷贝构造函数
引用传值
非引用传值
拷贝构造函数
析构函数
程序结束
析构函数
析构函数
析构函数
析构函数
析构函数
13.14
相同的内容
13.15
会改变,因为定义了一个拷贝构造函数,会生成新的序号。输出三个不同的序号。
13.16
如果有拷贝构造函数还是会生成三个不同的序号,因为b和c是通过拷贝初始化的。
13.17
#include <iostream>
#include<time.h>
using namespace std;
class numbered
{
public:
int mysn;
numbered() {
mysn = rand() % 100000; };
/*numbered(numbered& a) {
mysn = rand() % 100000;
}*/
};
// void f(numbered s) {
// cout << s.mysn << endl;
// }
void f(const numbered& s) {
cout << s.mysn << endl;
}
int main() {
srand(time(0));
numbered a, b = a, c = b;
f(a); f(b); f(c);
return 0;
}
13.18
静态数据成员在类内定义,在类外声明。
#include <iostream>
using namespace std;
class Employee
{
Employee() = default;
Employee(const string& s)
private:
string name;
int idNum;
static int id ;
};
int Employee::id = 0;
Employee::Employee()
{
idNum = id++;
}
Employee::Employee(const string& s):name(s)
{
idNum = id++;
}
13.19
不需要,因为每个雇员都是唯一的。
#include <iostream>
using namespace std;
class Employee
{
Employee() = default;
Employee(const string& s);
Employee(const Employee&) = delete;
Employee& operator= (const Employee&) = delete;
private:
string name;
int idNum;
static int id ;
};
int Employee::id = 0;
Employee::Employee()
{
idNum = id++;
}
Employee::Employee(const string& s):name(s)
{
idNum = id++;
}
13.20
TextQuery和QueryResult的所有数据成员被拷贝,包括智能指针和容器,智能指针的引用计数加一。
析构智能指针引用计数减一,其他数据成员被销毁。
13.21
判断一个类是否需要定义他们自己版本的拷贝控制成员,可以看一个类是否需要自定义析构函数,如果一个类需要自定义析构函数,几乎可以肯定他也需要自定义拷贝控制函数成员。本类中的内存控制由智能指针自动控制,不需要定义析构函数,用合成默认析构函数即可。而且智能指针的拷贝不需要自定义拷贝构造函数,所以都设为=default即可。
13.22
编写一个赋值运算符时,一个好的模式是先将右侧的运算对象拷贝到一个局部临时对象中,当拷贝完成号,销毁左侧运算对象的现有成员就是安全的了。一旦左侧运算对象的资源被销毁,就只剩下将数据从临时对象拷贝到左侧运算对象的成员中了。
#include <iostream>
using namespace std;
class HasPtr
{
public:
HasPtr(const string& s = string()) :
ps(new string(s)), i(0) {
};
HasPtr(const HasPtr& h) :ps(new string(*(h.ps))), i(h.i) {
}
HasPtr& operator=(const HasPtr& has)
{
auto new_ps = new string(*has.ps);
delete ps;
ps = new_ps;
i = has.i;
return *this;
}
~HasPtr() {
delete ps;
}
private:
string* ps;
int i;
};
13.23
第一次我就是直接释放本对象的内存空间。明显本节的代码安全性更强,在出现自我赋值时也可以正确工作。
13.24
未定义析构函数会导致内存泄漏。
未定义拷贝构造函数,会发生浅拷贝,也就是两个指针指向同一块区域,当发生析构时,会导致对同一块空间释放两次,也就是释放了非法内存。
13.25
拷贝构造函数和拷贝赋值运算符需要给对象分配一块新的内存空间。
不需要析构函数是因为智能指针能够自己控制内存释放,不需要delete。
13.26
StrBlob::StrBlob(const StrBlob& str)
{
data = make_shared<vector<string>>(*(str.data));//分配一块新的内存空间给对象,并用被拷贝对象的值初始化
}
StrBlob& StrBlob::operator=(const StrBlob&str)
{
data = make_shared<vector<string>>(*(str.data));//智能指针不需要亲自释放内存空间,=右侧的引用计数会减少,为零自动释放。
return*this;
}
13.27
#include <iostream>
using namespace std;
class HasPtr
{
public:
HasPtr(const string& s = string()) :
ps(new string(s)), i(0),use(new size_t(1)) {
};
HasPtr(const HasPtr& has) :ps(has.ps), i(has.i),use(has.use)
{
++*use;
}
HasPtr& operator=(const HasPtr& rhs)
{
++*(rhs.use);
if (-- * use==0)
{
delete ps;
delete use;
}
ps = rhs.ps;
i = rhs.i;
use = rhs.use;
return *this;
}