C++Primer_课后习题第十三章

本文答案,部分参考于C++ Primer 习题集

前面章节的习题答案

第一章

第二章

第三章

第四章

第五章

第六章

第七章

第八章

第九章

第十章

第十一章

第十二章

配套的学习资料

https://www.jianguoyun.com/p/DTK5uJgQldv8CBjKv80D

13.1

如果构造函数的第一个参数是自身类类型的引用.且所有其他参数都有默认值.则此构造函数是拷贝构造函数,拷贝构造函数在以下几种情况下会被使用.

​ 拷贝初始化(用=定义变量)

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

​ 一个返回类型为非引用类型的函数返回一个对象.

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

​ 初始化标准库容器或调用其insert/push操作时,容器会对其元素进行拷贝初始化.

13.2

是非法的.

这样调用会造成死循环.

13.3

​ 这两个类都为定义拷贝构造函数.因此编译器为它们定义了合成的拷贝构造函数.合成的拷贝构造函数逐个拷贝非const成员.对内置类型的成员.直接进行内存拷贝.

​ 因此.拷贝一个StrBlob时,拷贝其唯一的成员data.使用shared_ptr的拷贝构造函数来进行拷贝.因此其引用计数增加1

​ 拷贝一个StrBlobPtr时,拷贝成员wptr.用weak_ptr的拷贝构造函数进行拷贝.引用计数不变.然后拷贝curr.直接进行内存复制.

13.4

1 local =arg 将arg拷贝给local

2 *head=local 将local拷贝到heap指定的地址中.

3 Point pa[4]={local,heap} 将local 和

*heap

拷贝给数组的前两个元素.

4 return *heap

13.5

HasPtr::HasPtr(const HasPtr& hp) {
	ps = new string(*hp.ps);
	i = hp.i;
}

13.6

​ 拷贝赋值运算符本身是一个重载的赋值运算符,定义为类的成员函数,左侧运算对象绑定到隐含的this参数.而右侧运算对象是所属类类型的.作为函数的参数,函数返回指向其左侧运算对象的引用.

​ 当对类对象进行赋值时,会使用拷贝赋值运算符.

​ 通常情况下.合成的拷贝赋值运算符会将右侧对象的非static成员逐个赋予左侧对象的对应成员.这些赋值操作是由成员类型的拷贝赋值运算符完成的.

​ 若一个类未定义自己的拷贝赋值运算符,编译器就会为其合成拷贝赋值运算符.完成赋值操作.但对于某些类.还会起到禁止该类型对象赋值的效果.

13.7

因为我们在这两个类都没有定义拷贝赋值运算符.

所以它们赋值的时候,用的是编译器给的合成的拷贝复制运算符.

赋值data的时候,使用的是shared_ptr的拷贝赋值运算符.

引用计数增加1.

赋值StrBlobPtr 时,因为weak_ptr是弱引用.引用计数是不会变的.

13.8

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

13.9

​ 析构函数完成与构造函数相反的工作:释放对象使用的资源.销毁非静态数据成员.从语法上看.它是类的一个成员函数.名字是波浪号按类名.没有返回值.也不接受参数.

​ 当一个参数没有定义析构函数时,编译器会为它合成析构函数.

​ 合成的析构函数体为空.但这并不意味着它什么也不做.当空函数体执行完毕后.非静态数据成员非被逐个销毁.成员是在析构函数体之后的隐含的析构阶段中进行销毁的.

13.10

Strblob中就一个data可以销毁,data还是shared_ptr类型的.在销毁的时候,data的引用计数器会减一,如果减到了0.就会被销毁.如果没有减到0.就不会.

Strblobk就直接销毁.因为wptr是弱引用的智能指针.

13.11

~HasPtr() { delete ps; }

13.12

三次

item1 item2 accum

13.13

#include<iostream>
#include<string>
#include<vector>
using namespace std;
class HasPtr {
public:
	HasPtr(const std::string& s = std::string()) :ps(new std::string(s)),i(0) {}
	~HasPtr() { delete ps; }
private:
	std::string* ps;
	int i;
};
struct X {
	X() { cout << "X()" << endl; }
	X(const X&) { cout << "X(const X&)" << endl; }
	X& operator=(const X& rhs) { cout << "拷贝赋值运算符=(const X&)" << endl; return *this; }
	~X() { cout << "析构函数 ~X()" << endl; }
};
void f1(X x) {}
void f2(X& x) {}

int main(void) {
	cout << "局部变量" << endl;
	X x;
	cout << endl;
	cout << "非引用参数传递" << endl;
	f1(x);
	cout << endl;

	cout << "引用参数传递" << endl;
	f2(x);
	cout << endl;
	
	cout << "动态分配" << endl;
	X* px = new X;
	cout << endl;

	cout << "添加到容器中" << endl;
	vector<X> vx;
	vx.push_back(x);
	cout << endl;

	cout << "释放动态分配对象" << endl;
	delete px;
	cout << endl;

	cout << "间接初始化和赋值" << endl;
	X y = x;
	y = x;
	cout << endl;

	cout << "程序结束:" << endl;
	return 0;
}

13.14

#include<iostream>
#include<string>
#include<vector>
using namespace std;
int index = 0;
class numbered {
public:
	numbered(){ mysn = index++; }
	int mysn;
};
void f(numbered s) { cout << s.mysn << endl; }
int main(void) {
	numbered a, b = a, c = b;
	f(a); f(b); f(c);
	return 0;
}

输出的结果如下:

0
0
0

13.15

#include<iostream>
#include<string>
#include<vector>
using namespace std;
int index = 0;
class numbered {
public:
	numbered(){ mysn = index++; }
	numbered(numbered& n) { mysn = index++; };
	int mysn;
};
void f(numbered s) { cout << s.mysn << endl; }
int main(void) {
	numbered a, b = a, c = b;		//这个时候是0,1,2
	f(a); f(b); f(c);				//这个时候是3,4,5 输出的是numbered s的临时值
	cout << index << endl;
	cout << a.mysn << ":" << b.mysn << ":" << c.mysn << endl;
	return 0;
}

结果如下:

3
4
5
6
0:1:2

13.16

会改变.因为这样numbered s的值就是不变的了.不会调用拷贝构造函数

#include<iostream>
#include<string>
#include<vector>
using namespace std;
int index = 0;
class numbered {
public:
	numbered(){ mysn = index++; }
	numbered(numbered& n) { mysn = index++; };
	int mysn;
};
void f(const numbered &s) { cout << s.mysn << endl; }
int main(void) {
	numbered a, b = a, c = b;		//这个时候是0,1,2
	f(a); f(b); f(c);				//这个时候是3,4,5 输出的是numbered s的临时值
	cout << index << endl;
	cout << a.mysn << ":" << b.mysn << ":" << c.mysn << endl;
	return 0;
}

输入输出如下:

0
1
2
3
0:1:2

13.17

前面的汇总

13.18

#include<iostream>
#include<string>
#include<vector>
using namespace std;
static int index = 0;
class Employee {
public:
	Employee(string a) { name = a; id = index++; }
	void Prt() { cout << name << ":" << id << endl; }
private:
	string name;
	int id;
};
int main(void) {
	Employee a("Link");
	a.Prt();
	Employee b("Zelda");
	b.Prt();
	return 0;
}

13.19

需要,因为默认的会出现序号相同的情况.

#include<iostream>
#include<string>
#include<vector>
using namespace std;
static int index = 0;
class Employee {
public:
	Employee(string a) { name = a; id = index++; }
	Employee(const Employee& c) { name = c.name; id = index++; }
	Employee& operator=(Employee& rhs) { name = rhs.name; return *this; }
	void Prt() { cout << name << ":" << id << endl; }
private:
	string name;
	int id;
};
int main(void) {
	Employee a("Link");
	a.Prt();
	Employee b("Zelda");
	b.Prt();
	Employee c(a);
	Employee d = b;
	c.Prt();
	d.Prt();
	return 0;
}

13.20

两个类都未定义拷贝控制成员,因此都是编译器为它们定义合成版本.

​ 当TextQuery销毁时.合成版本会销毁其file和wm成员.对file成员.会将shared_ptr的引用计数减一.若变为0,则销毁所管理的动态vector对象.(会调用vector和string的析构函数).对wm.调用map的析构函数.(从而调用string,shared_ptr和set的析构函数) 会正确释放资源.

​ 当QueryResult销毁释.合成版本会销毁其sought.lines和files成员.类似TextQuery .String,shared_ptr.set.vector的析构函数可能被调用.因为这些类都有设计良好的拷贝控制成员.会正确释放资源.

​ 当拷贝一个TextQuery时,合成版本会拷贝file和wm成员.对file.shared_ptr的引用计数会加1.对wm.会调用map的拷贝构造函数(继而调用string,shared_ptr)和set的拷贝构造函数.因此会正确进行拷贝操作.赋值操作类似.只不过会将原来的资源释放掉.例如.原有的file的引用计数会减一.

​ QueryResult的拷贝和赋值类似

13.21

​ 两个类都未定义拷贝控制成员.但是它们同智能指针管理共享的动态对象(输入文件内容.查询结果的行号集合) 用标准库容器保存大量容器.而这些标准库机制都有都有设计良好的拷贝控制成员.用合成的拷贝控制成员简单地拷贝.赋值.销毁它们.即可保证正确的资源管理.因此.这两个类并不需要定义自己的拷贝控制成员.实际上,这两个类的类对象之间就存在资源共享.目前的设计已能很好地实现这种共享.同类对象之间的共享也自然能够解决.

13.22

#include<iostream>
#include<string>
using namespace std;
class HasPtr {
public:
	HasPtr(const string& s = string()) :ps(new string(s)), i(0) {}
	HasPtr(const HasPtr& p) :ps(new string(*p.ps)), i(p.i) {}		//拷贝构造函数
	HasPtr& operator=(const HasPtr&);								//拷贝赋值运算符
	HasPtr& operator=(const string&);								//赋予新string
	string& operator*();											//解引用
	~HasPtr();
private:
	string* ps;
	int i;
};
HasPtr::~HasPtr() {
	delete ps;														//释放string内存
}
inline HasPtr& HasPtr::operator=(const HasPtr& rhs) {
	auto newps = new string(*rhs.ps);								//拷贝指针指向的对象
	delete ps;														//销毁原string
	ps = newps;														//指向新string
	i = rhs.i;														//使用内置的int赋值
	return *this;													//返回一个此对象的引用.
}
HasPtr& HasPtr::operator=(const string& rhs) {
	*ps = rhs;
	return *this;
}
string& HasPtr::operator*() {
	return *ps;
}
int main(void) {
	HasPtr h("hi mom!");
	HasPtr h2(h);
	HasPtr h3 = h;
	h2 = "hi dad!";
	h3 = "hi son!";
	cout << "h:" << *h << endl;
	cout << "h2:" << *h2 << endl;
	cout << "h3:" << *h3 << endl;
	return 0;
}

13.23

13.24

​ 如果未定义析构函数,在销毁HasPtr对象时合成的析构函数不会释放指针ps指向的内存.造成内存泄漏.

​ 如果未定义拷贝构造函数,在拷贝HasPtr对象时,合成的拷贝构造函数会简单赋值ps成员.使得两个HasPtr指向相同的string.当其中一个HasPtr修改string内容时,另一个HasPtr也被改变.这并不符合我们的设想.如果同时定义了析构函数.情况会更为糟糕.当销毁其中一个HasPtr时,Ps指向的String被销毁.另一个HasPtr的Ps成为空悬指针.

13.25

由于希望StrBlob的行为像值一样.因此在拷贝构造函数和拷贝赋值运算符中.我们应该将其数据-string的vector拷贝一份,使得两个StrBlob对象指向各自的数据,而不是简单的拷贝shared_ptr使得两个StrBlob指向同一个vector.

​ Strblob不需要析构函数的原因是,它管理的全部资源就是string的vector.而这是有shared_ptr负责管理的.当一个StrBlob对象销毁时,会调用shared_ptr的析构函数.它会正确调整引用计数.当需要(引用计数变为0)释放vector.即.shared_ptr保证了资源分配.释放的正确性.StrBlob就不必进行相应的处理了.

13.26

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

13.27

在这里插入图片描述

在这里插入图片描述

13.28

TreeNode() :value(""), count(1), left(nullptr), right(nullptr) {}
	TreeNode(const TreeNode& tn) :value(tn.value), count(1), left(tn.left), right(tn.right) {
		
	}
BinStrTree::BinStrTree(const BinStrTree &bst): root(bst.root){
	root->CopyTree();
}

13.29

在此swap函数中又调用了swap来交换HasPtr成员中的ps和i.但这两个成员的类型分别是指针和整型.都是内置类型.因此函数中的swap调用被解析为std::swap,而不是HasPtr的特定版本swap(也就是自身).所以不会导致递归循环.

13.30

inline void swap(HasPtr& lhs, HasPtr& rhs) {
	cout << "交换" << *lhs.ps << "和" << *rhs.ps << endl;
	swap(lhs.ps, rhs.ps);
	swap(lhs.i, rhs.i);
}

13.31

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

13.32

​ 默认swap版本简单交换两个对象的非静态成员.对HasPtr而言.就是交换string指针ps,引用计数指针use 和整型值i.可以看出.这种语义是符合期望的–两个HasPtr指向了原来对方的string。而两者互换string后,各自的引用计数应该是不变的.(都是减一再加1).因此默认swap版本已经能正确处理指针HasPtr的交换.专用swap版本不会带来更多收益.

13.33

​ 首先,我们需要将给定的Folder的指针添加到当前Message的Folder集合中.这样.参数类型就不能是Folder.必须是引用类型.否则,调用save时会将实参拷贝给形参.folders.insert添加的形参(与局部变量一样放在栈中.save执行完毕就被销毁)的指针.而非原始Folder的指针.而参数为引用类型.就可以令形参与实参指向相同的对象.对形参f取地址.得到的就是原始Folder(实参) 的指针.

​ 其次.我们需要调用addMsg将当前Message的指针添加到Folder的message集合中.我们修改了Folder的内容.因此参数不能是const的.

13.34

参考书中本节内容完成Message类的编写

Folder.h

/*
 * This file contains code from "C++ Primer, Fifth Edition", by Stanley B.
 * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
 * copyright and warranty notices given in that book:
 * 
 * "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
 * 
 * 
 * "The authors and publisher have taken care in the preparation of this book,
 * but make no expressed or implied warranty of any kind and assume no
 * responsibility for errors or omissions. No liability is assumed for
 * incidental or consequential damages in connection with or arising out of the
 * use of the information or programs contained herein."
 * 
 * Permission is granted for this code to be used for educational purposes in
 * association with the book, given proper citation if and when posted or
 * reproduced.Any commercial use of this code requires the explicit written
 * permission of the publisher, Addison-Wesley Professional, a division of
 * Pearson Education, Inc. Send your request for permission, stating clearly
 * what code you would like to use, and in what specific way, to the following
 * address: 
 * 
 * 	Pearson Education, Inc.
 * 	Rights and Permissions Department
 * 	One Lake Street
 * 	Upper Saddle River, NJ  07458
 * 	Fax: (201) 236-3290
*/ 

#ifndef FOLDER_H
#define FOLDER_H

#include "Version_test.h"

#include <string>
#include <set>

class Folder;

class Message {
	friend void swap(Message&, Message&);
	friend class Folder;
public:
    // folders is implicitly initialized to the empty set 
    explicit Message(const std::string &str = ""): 
		contents(str) { }  

    // copy control to manage pointers to this Message
    Message(const Message&);             // copy constructor
    Message& operator=(const Message&);  // copy assignment
    ~Message();                          // destructor
    Message(Message&&);            // move constructor
    Message& operator=(Message&&); // move assignment

    // add/remove this Message from the specified Folder's set of messages
    void save(Folder&);   
    void remove(Folder&); 
    void debug_print(); // print contents and it's list of Folders, 
                        // printing each Folder as well
private:
    std::string contents;      // actual message text
    std::set<Folder*> folders; // Folders that have this Message

    // utility functions used by copy constructor, assignment, and destructor
    // add this Message to the Folders that point to the parameter
    void add_to_Folders(const Message&);
	void move_Folders(Message*);
    // remove this Message from every Folder in folders
    void remove_from_Folders(); 

    // used by Folder class to add self to this Message's set of Folder's
    void addFldr(Folder *f) { folders.insert(f); }
    void remFldr(Folder *f) { folders.erase(f); }
};
// declaration for swap should be in the same header as Message itself
void swap(Message&, Message&);

class Folder {
	friend void swap(Message&, Message&);
	friend class Message;
public:
    ~Folder(); // remove self from Messages in msgs
    Folder(const Folder&); // add new folder to each Message in msgs
    Folder& operator=(const Folder&); // delete Folder from lhs messages
                                      // add Folder to rhs messages
    Folder(Folder&&);   // move Messages to this Folder 
    Folder& operator=(Folder&&); // delete Folder from lhs messages
                                 // add Folder to rhs messages

#ifdef DEFAULT_FCNS
    Folder() = default; // defaults ok
#else
	Folder() { } // msgs will be default initialized, which is what we want
#endif

    void save(Message&);   // add this message to folder
    void remove(Message&); // remove this message from this folder
    
    void debug_print(); // print contents and it's list of Folders, 
private:
    std::set<Message*> msgs;  // messages in this folder

    void add_to_Messages(const Folder&);// add this Folder to each Message
    void remove_from_Msgs();     // remove this Folder from each Message
    void addMsg(Message *m) { msgs.insert(m); }
    void remMsg(Message *m) { msgs.erase(m); }
	void move_Messages(Folder*); // move Message pointers to point to this Folder
};

#endif

13.35

​ Message 类包含两个数据成员.content为string类型.folder为set.这两个标准库类都又完备的拷贝控制成员.因此Message使用合成的拷贝控制成员的话.简单拷贝这两个成员也能实现正确拷贝.

​ 当拷贝Message时,不仅要拷贝这个Message 在那些Folder中.还要将Message加到每个Folder中.调用addMsg

​ 当销毁Message时,需要将它从所有Folder中删除-调用remMsg。

​ 因此.不能依赖合成的拷贝控制成员.必须设计自己的版本来完成这些工作

13.36

见13.34

13.37

例如:

void remFldr(Folder *f){floders.erase(f);}

13.38

1 由于赋值运算符的参数是Message类型.因此会将实参拷贝给形参rhs.这会触发拷贝构造函数,将实参的contents和folders拷贝给rhs.并调用add_to_Folders将rhs添加到folders的所有文件夹中.

2 随后赋值运算符调用swap交换 *this和rhs.首先遍历两者的foldrers.将它们从自己的文件夹中删除.然后调用string 和set的swap交换它们的contents和folders.最后在遍历两者新的folders。将它们分别添加到自己的新文件夹中.

3 最好.赋值运算符结束.rhs被销毁.析构函数调用remove_from_Folders将rhs从自己的所有文件夹中删除.

显然.语义是正确的.达到了预期目的.但是效率低下.

13.39+13.40

/*
 * This file contains code from "C++ Primer, Fifth Edition", by Stanley B.
 * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
 * copyright and warranty notices given in that book:
 * 
 * "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
 * 
 * 
 * "The authors and publisher have taken care in the preparation of this book,
 * but make no expressed or implied warranty of any kind and assume no
 * responsibility for errors or omissions. No liability is assumed for
 * incidental or consequential damages in connection with or arising out of the
 * use of the information or programs contained herein."
 * 
 * Permission is granted for this code to be used for educational purposes in
 * association with the book, given proper citation if and when posted or
 * reproduced.Any commercial use of this code requires the explicit written
 * permission of the publisher, Addison-Wesley Professional, a division of
 * Pearson Education, Inc. Send your request for permission, stating clearly
 * what code you would like to use, and in what specific way, to the following
 * address: 
 * 
 * 	Pearson Education, Inc.
 * 	Rights and Permissions Department
 * 	One Lake Street
 * 	Upper Saddle River, NJ  07458
 * 	Fax: (201) 236-3290
*/ 

#ifndef STRVEC_H
#define STRVEC_H

#include "Version_test.h"

#include <iostream>
#include <memory>
#include <utility>

#ifdef INIT_LIST
#include <initializer_list>
#endif

// simplified implementation of the memory allocation strategy for a vector-like class
class StrVec {
public:
	// copy control members
    StrVec(): 
	  elements(nullptr), first_free(nullptr), cap(nullptr) { }

	StrVec(const StrVec&);            // copy constructor
	StrVec &operator=(const StrVec&); // copy assignment

#ifdef NOEXCEPT
	StrVec(StrVec&&) noexcept;            // move constructor
	StrVec &operator=(StrVec&&) noexcept; // move assignment
	~StrVec() noexcept;                   // destructor
#else
	StrVec(StrVec&&) throw();            // move constructor
	StrVec &operator=(StrVec&&) throw(); // move assignment
	~StrVec() throw();                   // destructor
#endif

#ifdef INIT_LIST
	// additional constructor
	StrVec(std::initializer_list<std::string>);
#else // define a constructor that takes pointers to a range of elements
	StrVec(const std::string*, const std::string*);
#endif

    void push_back(const std::string&);  // copy the element
    void push_back(std::string&&);       // move the element

	// add elements
    size_t size() const { return first_free - elements; }
    size_t capacity() const { return cap - elements; }

	// iterator interface
	std::string *begin() const { return elements; }
	std::string *end() const { return first_free; }
    
#ifdef INIT_LIST   // no real substitute for initializer_list in assignments
	// operator functions covered in chapter 14
	StrVec &operator=(std::initializer_list<std::string>);   
#endif

	std::string& operator[](std::size_t n) 
		{ return elements[n]; }

	const std::string& operator[](std::size_t n) const 
		{ return elements[n]; }
	
#ifdef VARIADICS    // no direct substitute for variadic functions
	// emplace member covered in chapter 16
	template <class... Args> void emplace_back(Args&&...);
#endif
private:
    static std::allocator<std::string> alloc; // allocates the elements

	// utility functions:
	//  used by members that add elements to the StrVec
	void chk_n_alloc() 
		{ if (size() == capacity()) reallocate(); }
    // used by the copy constructor, assignment operator, and destructor
	std::pair<std::string*, std::string*> alloc_n_copy
	    (const std::string*, const std::string*);
	void free();             // destroy the elements and free the space
    void reallocate();       // get more space and copy the existing elements
    std::string *elements;   // pointer to the first element in the array
    std::string *first_free; // pointer to the first free element in the array
    std::string *cap;        // pointer to one past the end of the array
};

#include <algorithm>

inline
#ifdef NOEXCEPT
StrVec::~StrVec() noexcept { free(); }
#else
StrVec::~StrVec() throw() { free(); }
#endif

inline
std::pair<std::string*, std::string*> 
StrVec::alloc_n_copy(const std::string *b, const std::string *e)
{
	// allocate space to hold as many elements as are in the range
	auto data = alloc.allocate(e - b); 

	// initialize and return a pair constructed from data and
	// the value returned by uninitialized_copy
#ifdef LIST_INIT
	return {data, uninitialized_copy(b, e, data)};
#else
	return make_pair(data, uninitialized_copy(b, e, data));
#endif
}

inline
#ifdef NOEXCEPT
StrVec::StrVec(StrVec &&s) noexcept  // move won't throw any exceptions
#else
StrVec::StrVec(StrVec &&s) throw()   // move won't throw any exceptions
#endif
  // member initializers take over the resources in s
  : elements(s.elements), first_free(s.first_free), cap(s.cap)
{
	// leave s in a state in which it is safe to run the destructor
	s.elements = s.first_free = s.cap = nullptr;
}

inline
StrVec::StrVec(const StrVec &s)
{
	// call alloc_n_copy to allocate exactly as many elements as in s
	auto newdata = alloc_n_copy(s.begin(), s.end());
	elements = newdata.first; 
	first_free = cap = newdata.second;
}

inline
void StrVec::free()
{
    // may not pass deallocate a 0 pointer; if elements is 0, there's no work to do
	if (elements) {
    	// destroy the old elements in reverse order
		for (auto p = first_free; p != elements; /* empty */)
			alloc.destroy(--p);  
		alloc.deallocate(elements, cap - elements);
	}
}
	
#ifdef INIT_LIST
inline
StrVec &StrVec::operator=(std::initializer_list<std::string> il)
{
	// alloc_n_copy allocates space and copies elements from the given range
	auto data = alloc_n_copy(il.begin(), il.end());
	free();   // destroy the elements in this object and free the space
	elements = data.first; // update data members to point to the new space
	first_free = cap = data.second;
	return *this;
}
#endif 

inline
#ifdef NOEXCEPT
StrVec &StrVec::operator=(StrVec &&rhs) noexcept
#else
StrVec &StrVec::operator=(StrVec &&rhs) throw()
#endif
{
	// direct test for self-assignment
	if (this != &rhs) {
		free();                   // free existing elements 
		elements = rhs.elements;  // take over resources from rhs
		first_free = rhs.first_free;
		cap = rhs.cap;
		// leave rhs in a destructible state
		rhs.elements = rhs.first_free = rhs.cap = nullptr;
	}
	return *this;
}

inline
StrVec &StrVec::operator=(const StrVec &rhs)
{
	// call alloc_n_copy to allocate exactly as many elements as in rhs
	auto data = alloc_n_copy(rhs.begin(), rhs.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}

inline
void StrVec::reallocate()
{
    // we'll allocate space for twice as many elements as the current size
    auto newcapacity = size() ? 2 * size() : 1;

	// allocate new memory
	auto newdata = alloc.allocate(newcapacity);

	// move the data from the old memory to the new
	auto dest = newdata;  // points to the next free position in the new array
    auto elem = elements; // points to the next element in the old array
	for (size_t i = 0; i != size(); ++i)
		alloc.construct(dest++, std::move(*elem++));

	free();  // free the old space once we've moved the elements

    // update our data structure to point to the new elements
    elements = newdata;
    first_free = dest;
    cap = elements + newcapacity;
}

#ifdef INIT_LIST
inline
StrVec::StrVec(std::initializer_list<std::string> il)
{
	// call alloc_n_copy to allocate exactly as many elements as in il
	auto newdata = alloc_n_copy(il.begin(), il.end());
	elements = newdata.first;
	first_free = cap = newdata.second;
}
#else
inline
StrVec::StrVec(const std::string *b, const std::string* e)
{
	// call alloc_n_copy to allocate exactly as many elements as in the range
	auto newdata = alloc_n_copy(b, e);
	elements = newdata.first;
	first_free = cap = newdata.second;
}
#endif

inline
void StrVec::push_back(const std::string& s)
{
    chk_n_alloc(); // ensure that there is room for another element
    // construct a copy of s in the element to which first_free points
    alloc.construct(first_free++, s);  
}

inline
void StrVec::push_back(std::string &&s) 
{
    chk_n_alloc(); // reallocates the StrVec if necessary
	alloc.construct(first_free++, std::move(s));
}
 
#ifdef VARIADICS    // no direct substitute for variadic functions
// emplace member covered in chapter 16
template <class... Args>
inline
void StrVec::emplace_back(Args&&... args)
{
    chk_n_alloc(); // reallocates the StrVec if necessary
	alloc.construct(first_free++, std::forward<Args>(args)...);
}
#endif

#endif

13.41

​ 因为first_free指向第一个空闲的位置,也就是最后一个string的尾后位置.当添加新的string时,应该保存在first_free指向的位置.然后将first_free推进一个位置.因此后置递增运算恰好符合要求.

​ 如果使用前置递增运算.则是先将first_free推进一个位置.然后将新string保存在新位置上.显然,这种方法意味着first_free指向最后一个string.而非尾后位置.与first_free的设定不吻合.

13.42

1 TextQuery.h中file成员的定义改为:

std::shared_ptr<StrVec> file;

2 QueryResult.h中file成员的定义改为

std::shared_ptr<StrVec> file;

构造函数的第三个参数改为:

std::shared_ptr<StrVec> f

成员函数get_file的定义改为:

std::shared_ptr<StrVec> get_file(){return file;}

3 TextQuery.cpp中构造函数初始化file成员的操作改为:

TextQuery::TextQuery(ifstream &is):file(new StrVec)

13.43

将for循环改成如下形式即可:

​ elements和first_free是string*类型,因此它们指出的范围中的元素是string类型.因此.lambda的参数s应该是string&类型,在lambda的函数体中应该去s的地址.用来调用destory.

for_each(elements,first_free,[](std::string &s){alloc.destory(&s);});

13.44

​ 总体上需要注意的地方与StrVec的定义类似,只不过StrVec管理的元素为string。而String管理的元素为char 配套网站已有完整程序.读者可以尝试自己定义String.然后与配套网站中的代码进行对照.

13.45

​ 所谓右值引用就是必须绑定到右值的引用.通过&&获得.右值引用只能绑定到一个将要销毁的对象上.因此可以自由地移动其资源.

​ 左值引用.也就是"常规引用",不能绑定到要转换的表达式.字面常量或返回右值的表达式.而右值引用恰好相反.可以绑定到这类表达式.但不能绑定到一个左值上.

​ 返回左值的表达式包括返回左值引用的函数及赋值.下标.解引用和前置递增.递减运算符.返回右值的包括返回非引用类型的函数及算术.关系.位和后置递增.递减运算符.可以看到.左值的特点是有持久的状态.而右值则是短暂的.

13.46

&

&&

&

&&

13.47

加几条cout<<endl的事

13.48

只有前两个.

#pragma once
/*
 * This file contains code from "C++ Primer, Fifth Edition", by Stanley B.
 * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
 * copyright and warranty notices given in that book:
 *
 * "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
 *
 *
 * "The authors and publisher have taken care in the preparation of this book,
 * but make no expressed or implied warranty of any kind and assume no
 * responsibility for errors or omissions. No liability is assumed for
 * incidental or consequential damages in connection with or arising out of the
 * use of the information or programs contained herein."
 *
 * Permission is granted for this code to be used for educational purposes in
 * association with the book, given proper citation if and when posted or
 * reproduced.Any commercial use of this code requires the explicit written
 * permission of the publisher, Addison-Wesley Professional, a division of
 * Pearson Education, Inc. Send your request for permission, stating clearly
 * what code you would like to use, and in what specific way, to the following
 * address:
 *
 * 	Pearson Education, Inc.
 * 	Rights and Permissions Department
 * 	One Lake Street
 * 	Upper Saddle River, NJ  07458
 * 	Fax: (201) 236-3290
*/

#ifndef STRING_H
#define STRING_H

#include "Version_test.h"

#include <cstring>
#include <algorithm>
#include <cstddef>
#include <iostream>

#ifdef INITIALIZER_LIST
#include <initializer_list>
#endif

#include <iostream>
#include <memory>

class String {
	friend String operator+(const String&, const String&);
	friend String add(const String&, const String&);
	friend std::ostream& operator<<(std::ostream&, const String&);
	friend std::ostream& print(std::ostream&, const String&);

public:
#if defined(IN_CLASS_INITS) && defined(DEFAULT_FCNS)
	String() = default;
#else
	String() : sz(0), p(nullptr) { }
#endif

	// cp points to a null terminated array, 
	// allocate new memory & copy the array
	String(const char* cp) :
		sz(std::strlen(cp)), p(a.allocate(sz))
	{
		std::uninitialized_copy(cp, cp + sz, p);
	}

	// copy constructor: allocate a new copy of the characters in s
	String(const String& s) :sz(s.sz), p(a.allocate(s.sz))
	{
		std::uninitialized_copy(s.p, s.p + sz, p);
	}

	// move constructor: copy the pointer, not the characters, 
	// no memory allocation or deallocation
#ifdef NOEXCEPT
	String(String&& s) noexcept : sz(s.size()), p(s.p)
#else
	String(String&& s) throw() : sz(s.size()), p(s.p)
#endif
	{
		s.p = 0; s.sz = 0;
	}

	String(size_t n, char c) : sz(n), p(a.allocate(n))
	{
		std::uninitialized_fill_n(p, sz, c);
	}

	// allocates a new copy of the data in the right-hand operand; 
	// deletes the memory used by the left-hand operand
	String& operator=(const String&);
	// moves pointers from right- to left-hand operand
#ifdef NOEXCEPT
	String& operator=(String&&) noexcept;
#else
	String& operator=(String&&) throw();
#endif

	// unconditionally delete the memory because each String has its own memory
#ifdef NOEXCEPT
	~String() noexcept { if (p) a.deallocate(p, sz); }
#else
	~String() throw() { if (p) a.deallocate(p, sz); }
#endif

	// additional assignment operators
	String& operator=(const char*);         // car = "Studebaker"
	String& operator=(char);                // model = 'T'
#ifdef INITIALIZER_LIST
	String&
		operator=(std::initializer_list<char>); // car = {'a', '4'}
#endif

	const char* begin() { return p; }
	const char* begin() const { return p; }
	const char* end() { return p + sz; }
	const char* end() const { return p + sz; }

	size_t size() const { return sz; }
	void swap(String& s)
	{
		auto tmp = p; p = s.p; s.p = tmp;
		auto cnt = sz; sz = s.sz; s.sz = cnt;
	}
private:
#ifdef IN_CLASS_INITS
	std::size_t sz = 0;
	char* p = nullptr;
#else
	std::size_t sz;
	char* p;
#endif
	static std::allocator<char> a;
};
String make_plural(size_t ctr, const String&, const String&);
inline
void swap(String& s1, String& s2)
{
	s1.swap(s2);
}

#endif

13.49

String的如下:

String& String::operator=(String&& rhs) noexcept {
	if (this != &rhs) {
		if (p)
			a.deallocate(p, sz);
		p = rhs.p;
		sz = rhs.sz;
		rhs.p = 0;
		rhs.sz = 0;
	}
	return *this;
}

13.50

13.51

​ unique_ptr不能拷贝,但有一个例外.将要被销毁的unique_ptr是可以拷贝或销毁的.因此.在418页的clone函数中返回局部unique_ptr对象ret是可以的。因为ret马上就要被销毁了.而此时的"拷贝".其实是触发移动构造函数进行了移动.

13.52

​ 对hp=hp2因为hp2是一个变量.是一个左值.因此它传递给赋值运算符参数rhs的过程是拷贝构造过程.rhs获得hp2的一个剧本.rhs.ps与hp2.ps指向不同的string.但两个string包含相同的内容.在赋值运算符中.交换hp和rhs.rhs指向hp原来的string.在赋值结束后被销毁.最终结果.hp和hp2指向两个独立的string.但内容相同.

​ 对hp=std::move(hp2).hp2传递给rhs的过程是移动构造过程.rhs.ps指向hp2.ps原来的string.hp2的ps被设置为空指针.然后赋值运算符交换hp和rhs.rhs指向原来的string.在赋值结束后被销毁.最终结果hp指向hp2原来的string.而hp2则变成空.

13.53

例如:

inline HasPtr& HasPtr::operator=(HasPtr &&rhs) noexcept{
	cout<<"Move Assignment"<<endl;
	if(this!=&rhs){
		delete ps;
		ps=rhs.ps;
		rhs.ps=nullptr;
		rhs.i=0;
	}
	return *this;
}

13.54

会产生编译错误

​ 因为对于hp=std::move(hp2)这样的赋值语句来说.两个运算符匹配的一样好.从而产生了二义性

13.55

void push_back(string &&t){data->push_back(std::move(t));}

13.56

​ 局部变量ret拷贝了被调用对象的一个副本.然后,对ret调用sorted.由于并非是函数返回语句或函数结束.因此编译器认为它是左值.仍然调用左值引用版本.产生递归循环.

​ 利用右值引用版本来完成排序的期望不能实现.

13.57

是正确的利用右值引用版本来完成排序.原因在于,编译器认为

Foo(*this)

是一个无主的右值.对它调用sorted会匹配右值引用版本.

13.58

在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值