C++ Primer 中文第 5 版练习答案 第 13 章 拷贝控制(1~32)

C++ Primer 中文版(第 5 版)练习解答合集

自己写的解答,如有错误之处,烦请在评论区指正!


1

如果构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。

以下情况会用到拷贝构造函数:

  • 拷贝初始化(用 = 初始化变量)

  • 将对象作为实参传递给一个非引用类型的形参

  • 从一个返回类型为非引用类型的函数返回一个对象

  • 用花括号列表初始化一个数组中的元素或一个聚合类中的成员

  • 初始化标准库容器或者调用其 push / insert 等操作时(emplace_back 用的是直接初始化)

2

因为在将对象作为实参传递给一个非引用类型的形参的时候需要调用拷贝构造函数,所以此函数在被调用的时候,需要调用 Sales_data 类的拷贝构造函数,也就是这个函数本身,于是无限循环,所以非法。

3

拷贝 StrBlob 的时候,拷贝其 data 成员,该成员的 shared_ptr 引用计数加 1

拷贝 StrBlobPtr 时,拷贝其 wptr 成员,引用计数不变,然后对 curr 直接进行内存复制

4

  1. Point 类以实参传递给函数形参时
  2. local 进行拷贝初始化时
  3. local 被拷贝到 *heap 地址中时
  4. pa 数组通过花括号列表初始化时
  5. 该函数返回 Point 类时

5

#include <iostream>
#include <string>
class HasPtr {
public:
    HasPtr(const std::string &s = std::string()) :
        ps(new std::string(s)), i(0) { }
    HasPtr(const HasPtr& orig) :
        ps(new std::string(*(orig.ps))), i(orig.i){ }
public:
    std::string* ps;
    int i;
};

int main()
{
    HasPtr testA(std::string("hello"));
    HasPtr testB = testA;
    std::cout << testA.ps << std::endl;
    std::cout << testB.ps << std::endl;
    std::cout << *(testA.ps) << std::endl;
    std::cout << *(testB.ps) << std::endl;
}

运行结果:

01355290
0135DCB0
hello
hello

6

拷贝赋值运算符是一种对赋值运算符的重载,并且是成员函数,左侧运算对象绑定为隐式的 this 参数,右侧运算对象作为显式参数传递,一般为相同类的常量引用,一般返回左侧运算对象的引用

7

赋值 StrBlob 的时候,引用计数加 1;赋值 StrBlobPtr 的时候,引用计数不变

8

#include <iostream>
#include <string>
class HasPtr {
public:
    HasPtr(const std::string &s = std::string()) :
        ps(new std::string(s)), i(0) { }
    HasPtr(const HasPtr& orig) :
        ps(new std::string(*(orig.ps))), i(orig.i){ }
    HasPtr& operator=(const HasPtr& rhs) {
        std::cout << "copy assignment" << std::endl;
        if (this == &rhs)
            return *this;
        *ps = *(rhs.ps);
        i = rhs.i;
        return *this;
    }
public:
    std::string* ps;
    int i;
};

int main()
{
    HasPtr testA(std::string("hello"));
    HasPtr testB;
    testB = testA;
    std::cout << testA.ps << std::endl;
    std::cout << testB.ps << std::endl;
    std::cout << *(testA.ps) << std::endl;
    std::cout << *(testB.ps) << std::endl;
}

运行结果:

copy assignment
00F55290
00F5DDD0
hello
hello

9

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

合成析构函数函数体为空,函数体执行完毕后,该类的成员被自动销毁。

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

10

销毁 StrBlob 的时候,引用计数减 1;销毁 StrBlobPtr 的时候,引用计数不变

11

#include <iostream>
#include <string>
class HasPtr {
public:
    HasPtr(const std::string &s = std::string()) :
        ps(new std::string(s)), i(0) { }
    HasPtr(const HasPtr& orig) :
        ps(new std::string(*(orig.ps))), i(orig.i){ }
    HasPtr& operator=(const HasPtr& rhs) {
        std::cout << "copy assignment" << std::endl;
        if (this == &rhs)
            return *this;
        *ps = *(rhs.ps);
        i = rhs.i;
        return *this;
    }
    ~HasPtr() {
        std::cout << "deconstructor" << std::endl;
        delete ps;
    }
private:
    std::string* ps;
    int i;
};

int main()
{
    HasPtr testA(std::string("hello"));
    HasPtr testB;
    testB = testA;
}

运行结果

copy assignment
deconstructor
deconstructor

12

函数结束的时候,accum、item1、item2 会被调用析构函数

13

#include <iostream>
#include <string>
class HasPtr {
public:
    HasPtr(const std::string &s = std::string()) :
            ps(new std::string(s)), i(0) {
        std::cout << "constructor" << std::endl;
    }
    HasPtr(const HasPtr& orig) :
            ps(new std::string(*(orig.ps))), i(orig.i){
        std::cout << "copy constructor" << std::endl;
    }
    HasPtr& operator=(const HasPtr& rhs) {
        std::cout << "copy assignment" << std::endl;
        if (this == &rhs)
            return *this;
        *ps = *(rhs.ps);
        i = rhs.i;
        return *this;
    }
    ~HasPtr() {
        std::cout << "deconstructor" << std::endl;
        delete ps;
    }
private:
    std::string* ps;
    int i;
};

int main() {
    HasPtr testA(std::string("hello"));
    HasPtr testB;
    HasPtr testC = testA;
    testB = testA;
}

运行结果

constructor
constructor
copy constructor
copy assignment
deconstructor
deconstructor
deconstructor

14

会输出 3 个一样的数字

15

会改变,输出 3 个不一样的数字,但是因为调用函数的时候作为实参传递给形参的过程中又调用了一次拷贝构造函数,所以输出的序号和 a、b、c 本身的序号是不一样的

16

会改变,此时输出的就是 a、b、c 本身的序号了

17

#include <iostream>
class Numbered {
public:
    int mysn;

    Numbered() {
        mysn = ++mysnCount;
    }
    //Numbered(const Numbered& numbered) {
    //    mysn = ++mysnCount;
    //}
    //Numbered& operator=(const Numbered& rhs) {
    //    mysn = ++mysnCount;
    //}
    ~Numbered() = default;

private:
    static int mysnCount;
};
int Numbered::mysnCount = 0;

void f(Numbered s) {
    std::cout << s.mysn << std::endl;
}

int main() {
    Numbered a, b = a, c = b;
    f(a);
    f(b);
    f(c);
    return 0;
}

没有拷贝构造函数,输出:

1
1
1

加了拷贝构造函数之后:

4
5
6

f 的 参数改成 const numbered& 之后:

1
2
3

验证了之前的结果

18

#include <iostream>
#include <string>
using std::string;
using std::cout;
using std::endl;

class Employee {
public:
    Employee() = default;
    Employee(const string& str) : name(str) {
        no = ++number;
    }
public:
    string name;
    int no;
    static int number;
};
int Employee::number = 0;

int main() {
    Employee george("George");
    Employee david("David");
    cout << george.no << endl;
    cout << david.no << endl;
    return 0;
}

运行结果

1
2

19

不需要拷贝控制成员,因为不存在把一个成员的信息拷贝给另一个成员的需求

// 在类内加上:
    Employee(const Employee& employee) = default;
    Employee& operator=(const Employee& employee) = default;

20

两个类都没有定义拷贝控制函数,因此所有的拷贝构造函数都是合成的。

TextQuery 类:

拷贝构造、拷贝赋值:file 的引用计数加一,调用 wm 的拷贝构造、拷贝赋值函数

销毁:file 的引用计数减一,如果减了之后引用计数为 0,则销毁智能指针 file 指向的对象。调用 wm 的析构函数

QueryResult 类也类似。

21

不需要,因为这两个类的成员包括:智能指针、string 类、map 类等,都是标准库中的类或容器,所有的成员都有良好的拷贝控制函数,不需要再额外编写。

22

class HasPtr {
public:
    HasPtr(const std::string& s = std::string()) :
        ps(new std::string(s)), i(0) { }
    HasPtr(const HasPtr& orig) :
        ps(new std::string(*(orig.ps))), i(orig.i) { }
    HasPtr& operator=(const HasPtr& rhs) {
        if (this == &rhs)
            return *this;
        *ps = *(rhs.ps);
        i = rhs.i;
        return *this;
    }
    ~HasPtr() {
        delete ps;
    }
private:
    std::string* ps;
    int i;
};

23

代码和书上的有区别,但是都保证了拷贝赋值函数中,如果用同一个对象来赋值自己,不会出问题。书上是用临时成员复制指针指向的对象,我的是先特判一下 rhs 和 *this 是否是同一个对象

24

未定义析构函数虎易造成内存泄漏。如果未定义拷贝构造函数,那么拷贝构造新对象时,会有两个 HasPtr 类的对象,它们的 ps 成员指向内存中同一个 string

25

不需要析构函数是因为 StrBlob 类唯一的数据成员是智能指针,智能指针作为标准库的类有良好的析构函数。

class StrBlobPtr;

class StrBlob {
	friend class StrBlobPtr;

public:
	typedef vector<string>::size_type size_type;

	StrBlob();
	StrBlob(initializer_list<string> il);
	StrBlob(const StrBlob& strBlob);

	size_type size() const { return data->size(); }
	bool empty() const { return data->empty(); }
	void push_back(const string& t) {
		data->push_back(t);
	}
	void pop_back();
	string& front();
	const string& front() const;
	string& back();
	const string& back() const;

	StrBlobPtr begin();
	StrBlobPtr end();

private:
	shared_ptr<vector<string>> data;

	void check(size_type i, const string& msg) const;
	StrBlob& operator= (const StrBlob& rhs) {
		if (&rhs == this)
			return *this;
		data = make_shared<vector<string>>(*(rhs.data));
		return *this;
	}
};

StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) :
	data(make_shared<vector<string>>(il)) {}
StrBlob::StrBlob(const StrBlob& strBlob) {
	data = make_shared<vector<string>>(*(strBlob.data));
}

void StrBlob::check(size_type i, const string& msg) const {
	if (i >= data->size())
		throw std::out_of_range(msg);
}

string& StrBlob::front() {
	check(0, "front on empty StrBlob");
	return data->front();
}
const string& StrBlob::front() const {
	check(0, "front on empty StrBlob");
	return data->front();
}
string& StrBlob::back() {
	check(0, "back on empty StrBlob");
	return data->back();
}
const string& StrBlob::back() const {
	check(0, "back on empty StrBlob");
	return data->back();
}
void StrBlob::pop_back() {
	check(0, "pop_back on empty StrBlob");
	return data->pop_back();
}

26

同上。

27

#include <iostream>
#include <string>
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& p) :
		ps(p.ps), i(p.i), use(p.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;
	}
	~HasPtr() {
		if (-- * use == 0) {
			delete ps;
			delete use;
		}
	}

private:
	string *ps;
	int i;
	size_t *use;
};

28

如果 TreeNode 类的 count 是引用计数的话,应该用书上的方法,用动态分配的内存来管理引用计数,然后 BinStrTree 的成员用智能指针就可以了。说实话这个题有点没想明白,看了别人的解答总觉得有不完美的地方,欢迎指正。

class TreeNode {
public:
	TreeNode() : value(string()), count(new int(1)), left(nullptr), right(nullptr) { }
	TreeNode(const TreeNode& treenode) :
		value(treenode.value), count(new int(*treenode.count + 1)),
		left(treenode.left), right(treenode.right) {
		++*treenode.count;
	}
	~TreeNode() {
		if (--*count == 0) {
			delete count;
			delete left;
			delete right;
		}
	}

	TreeNode& operator=(const TreeNode& rhs) {
		if (this == &rhs)
			return *this;
		++*rhs.count;
		if (--*count == 0) {
			delete count;
			delete left;
			delete right;
		}
		value = rhs.value;
		count = rhs.count;
		left = new TreeNode(*rhs.left);
		right = new TreeNode(*rhs.right);
		return *this;
	}

private:
	string value;
	int *count;
	TreeNode *left;
	TreeNode *right;
};

class BinStrTree {
private:
	shared_ptr<TreeNode> root;
};

29

不会递归循环,因为我们正在定义两个参数是 HasPtr 类型的 swap 函数,而函数中用到的两个 swap 分别是 string* 类型和 int 类型的参数,根据函数重载的规则,不会递归调用正在定义的这个函数

30

class HasPtr {
    friend void swap(HasPtr& a, HasPtr& b);

public:
    HasPtr(const std::string& s = std::string()) :
        ps(new std::string(s)), i(0) {
        std::cout << "constructor" << std::endl;
    }
    HasPtr(const HasPtr& orig) :
        ps(new std::string(*(orig.ps))), i(orig.i) {
        std::cout << "copy constructor" << std::endl;
    }
    ~HasPtr() {
        std::cout << "deconstructor" << std::endl;
        delete ps;
    }

    HasPtr& operator=(const HasPtr& rhs) {
        std::cout << "copy assignment" << std::endl;
        if (this == &rhs)
            return *this;
        *ps = *(rhs.ps);
        i = rhs.i;
        return *this;
    }

private:
    std::string* ps;
    int i;
};

inline
void swap(HasPtr& a, HasPtr& b) {
    swap(a.ps, b.ps);
    swap(a.i, b.i);
}

31

实测当待排序元素数量多的时候才会调用 swap,下面两次分别是元素数量少和较多的时候:

#include <iostream>
using std::cout;
using std::endl;
#include <string>
#include <vector>
#include <algorithm>

class HasPtr {
    friend void swap(HasPtr& a, HasPtr& b);

public:
    HasPtr(const std::string& s = std::string()) :
        ps(new std::string(s)), i(0) {
        cout << "constructor" << endl;
    }
    HasPtr(const HasPtr& orig) :
        ps(new std::string(*(orig.ps))), i(orig.i) {
        cout << "copy constructor" << endl;
    }
    ~HasPtr() {
        cout << "deconstructor" << endl;
        delete ps;
    }

    HasPtr& operator=(const HasPtr& rhs) {
        cout << "copy assignment" << endl;
        if (this == &rhs)
            return *this;
        *ps = *(rhs.ps);
        i = rhs.i;
        return *this;
    }

    bool operator<(const HasPtr& rhs) {
        return *ps < *rhs.ps;
    }

    void display() const {
        cout << *ps << endl;
    }

private:
    std::string* ps;
    int i;
};

inline
void swap(HasPtr& a, HasPtr& b) {
    using std::swap;
    cout << "swapping" << endl;
    swap(a.ps, b.ps);
    swap(a.i, b.i);
}

int main() {
    std::vector<HasPtr> vec{
        HasPtr("abc"),
        HasPtr("skdhfjasdg"),
        HasPtr("adsdabc"),
        HasPtr("bbbb"),
    };
    //HasPtr hp("skdhfjasdg");
    //for (int i = 0; i < 100; ++i) {
    //    vec.push_back(hp);
    //}
    cout << "Start sorting---------------------------------" << endl;
    std::sort(vec.begin(), vec.end());
    cout << "End sorting-----------------------------------" << endl;
    //for (auto& hp : vec) {
    //    hp.display();
    //}
	return 0;
}

运行结果:

copy constructor
copy constructor
copy constructor
copy constructor
deconstructor
deconstructor
deconstructor
deconstructor
Start sorting---------------------------------
copy constructor
copy assignment
deconstructor
copy constructor
copy assignment
copy assignment
deconstructor
copy constructor
copy assignment
copy assignment
deconstructor
End sorting-----------------------------------
deconstructor
deconstructor
deconstructor
deconstructor
#include <iostream>
using std::cout;
using std::endl;
#include <string>
#include <vector>
#include <algorithm>

class HasPtr {
    friend void swap(HasPtr& a, HasPtr& b);

public:
    HasPtr(const std::string& s = std::string()) :
        ps(new std::string(s)), i(0) {
        cout << "constructor" << endl;
    }
    HasPtr(const HasPtr& orig) :
        ps(new std::string(*(orig.ps))), i(orig.i) {
        cout << "copy constructor" << endl;
    }
    ~HasPtr() {
        cout << "deconstructor" << endl;
        delete ps;
    }

    HasPtr& operator=(const HasPtr& rhs) {
        cout << "copy assignment" << endl;
        if (this == &rhs)
            return *this;
        *ps = *(rhs.ps);
        i = rhs.i;
        return *this;
    }

    bool operator<(const HasPtr& rhs) {
        return *ps < *rhs.ps;
    }

    void display() const {
        cout << *ps << endl;
    }

private:
    std::string* ps;
    int i;
};

inline
void swap(HasPtr& a, HasPtr& b) {
    using std::swap;
    cout << "swapping" << endl;
    swap(a.ps, b.ps);
    swap(a.i, b.i);
}

int main() {
    std::vector<HasPtr> vec{
        HasPtr("abc"),
        HasPtr("skdhfjasdg"),
        HasPtr("adsdabc"),
        HasPtr("bbbb"),
    };
    HasPtr hp("skdhfjasdg");
    for (int i = 0; i < 100; ++i) {
        vec.push_back(hp);
    }
    cout << "Start sorting---------------------------------" << endl;
    std::sort(vec.begin(), vec.end());
    cout << "End sorting-----------------------------------" << endl;
    //for (auto& hp : vec) {
    //    hp.display();
    //}
	return 0;
}

运行结果:(省略了排序以外的部分)

Start sorting---------------------------------
swapping
copy constructor
copy assignment
deconstructor
copy constructor
copy assignment
copy assignment
deconstructor
End sorting-----------------------------------

32

不会呀,首先引用计数是不会造成错误的。从性能上来说,本来就是交换指针,自己写一个交换指针的版本也不会带来性能上的提升。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值