C++Primer第13章:拷贝控制(习题解答)

13.1.1节练习

13.1:构造拷贝函数是什么?什么时候使用它?
答:如果构造函数的第一个参数是自身类型的引用,且所有其他参数(如果有的话)都有默认值,则此构造函数是拷贝构造函数。拷贝构造函数在以下几种情况下会被使用 1.拷贝初始化(用=定义变量) 2.将一个对象作为实参传递给非引用类型的实参 3.一个返回类型为非引用类型的函数返回一个对象 4.用花括号列表初始化一个数组中的元素或者一个聚合类中的成员 5.初始化标准库容器或调用其insert/push操作时,容器会对其元素进行初始化
13.2:解释下为什么下面的声明是非法的

Sale_data::Sales_data(Sales_data rhs)

答:我们需要调用拷贝构造函数,但是永远都不会成功,因为其自身的参数也是非引用类型,为了调用它,必须拷贝其实参,而为了拷贝其实参,又需要调用拷贝构造函数,也就是自身,从而造成死循环。
13.3:当我们拷贝一个StrBlob类时,会发生什么?拷贝一个StrBlobPtr呢?
答:这两个类都没有定义拷贝构造函数,因此编译器为他们定义了合成拷贝构造函数。合成的拷贝构造函数逐个拷贝非const成员,对内置类型的成员,直接进行内存拷贝,对类类型的成员,调用其拷贝构造函数进行拷贝,因此拷贝一个StrBlob时,拷贝其唯一的成员data,使用其shared_ptr的拷贝构造函数来进行拷贝,因此其引用计数加1,拷贝一个StrBlobPtr时,拷贝成员wptr,用weak_ptr的拷贝构造函数进行拷贝,引用计数不变,然后拷贝curr,直接进行内存复制
13.4:假定Point是一个类类型,它有一个public的拷贝构造函数,指出下面的程序片段中哪些地方使用了拷贝构造函数

Point global;
Point foo_bar(Point arg)
{
	Point local=arg,*heap=new Point(global);//将arg拷贝给local
	*heap=local;//将local拷贝给*heap
	Point pa[4]={local,*heap};//将local和*heap拷贝给pa的前两个元素
	return *heap//函数的返回需要拷贝
}

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

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

答:

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

13.1.6节练习

13.6:拷贝赋值运算符是什么?什么时候使用它?合成拷贝赋值运算符完成什么工作?什么时候会生成合成拷贝赋值运算符?
答:拷贝赋值运算符本身是一个重载的赋值运算符,定义为类的成员,左侧运算对象绑定到隐含的this参数,而右侧运算对象是所属类类型的,作为函数的参数,参数返回指向其右侧运算符对象的引用。 通常情况下,合成的拷贝赋值运算符会将右侧对象的非static成员逐个赋予左侧对象对应的成员,这些赋值操作时由成员类型的拷贝构造赋值运算符来完成的。 若一个类未定义自己的拷贝赋值运算符,编译器会为其合成拷贝赋值运算符,完成赋值操作,但对于某些类,还会起到该类型对象赋值的效果。
13.7:当我们将一个StrBlob赋值给另一个StrBlob时,会发生什么?赋值StrBlobPtr
答:编译器会为其定义合成拷贝构造函数
13.8:为HasPtr编写赋值运算符。类似拷贝构造函数,你的赋值运算符应该将对象拷贝到ps指向的位置
答:

#include<iostream>
using namespace std;

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

13.1.3节练习

13.9:析构函数是什么?合成析构函数完成什么工作?什么时候发生合成析构函数?
答:析构函数完成与构造函数相反的工作:释放对象资源,销毁非静态数据成员。从语法上来看,它是类的一个成员函数,名字是波浪号接类名,没有返回值,当一个类没有定义析构函数时,编译器会为它合成析构函数。合成的析构函数体为空,但这并不意味着它啥也不干。当函数体执行完后,非静态数据会逐个销毁。也就是说,成员是在析构函数之后隐含的析构阶段中进行销毁的。
13.10:当一个StrBlob对象销毁时会发生什么?一个StrBlobPtr对象销毁时呢?
答:这两个类都没有定义析构函数,因此编译器会为他们合成析构函数
13.11:为前面练习的HasPtr类添加一个析构函数
答:

#include<iostream>
using namespace std;

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

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

bool fcn(const Sales_data *trans,Sales_data accum)
{
	Sales_data item1(*trans),item2(accum);
	return item1.isbn()!=item2.isbn();
}

答:形参两次,函数中定义两次,考虑到trans是指针,它指向的对象的生命周期没有结束,所以调用三次析构函数
13.13理解拷贝控制成员和构造函数的一个好方法是定义一个简单的类,为该类定义这些成员,每个成员都打出自己的名字?
答:

#include<iostream>
#include<fstream>
#include<iterator>
#include<vector>
using namespace std;

struct x
{
    x(){cout<<"x()"<<endl;}
    x(const x& ){cout<<"(const x&)"<<endl;}
    x&operator=(const x &rhs){cout<<"=(const x&)"<<endl;return *this;}
    ~x(){cout<<"~x()"<<endl;}
};
void f1(x num)
{

}

void f2(x &num)
{

}
int main(){
    cout<<"1:"<<endl;
    x x1;
    cout<<endl;
    cout<<"2:"<<endl;
    f1(x1);
    cout<<endl;
    cout<<"3"<<endl;
    f2(x1);
    cout<<endl;
    cout<<"4"<<endl;
    x*ps=new x();
    cout<<endl;
    cout<<"5"<<endl;
    vector<x>vec;
    vec.push_back(x1);
    cout<<endl;
    cout<<"6"<<endl;
    delete ps;
    cout<<endl;
    cout<<"7"<<endl;
     x y1=x1;
     y1=x1;
     cout<<endl;
    return 0;
}

13.1.4节练习

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

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

则下列代码输出哪些内容?

numbered a,b=a,c=b;
f(a);f(b);f(c);

答:

输出的内容相同

13.15:假定numbered定义了一个拷贝构造函数,能生成一个新的序号,这回改变上一题中调用的输出结果吗?
答:

初始化拷贝,然后函数调用又有拷贝

13.16:如果f中的参数是const numbered&会发生什么结果?
答:

函数中没有拷贝了,但是初始化有拷贝

13.17:分别编写前三题中所描述的numberedf,验证你的预测?
答:

#include<iostream>
using namespace std;

class numbered
{
public:
       numbered():mysn(rand()){};
       numbered(const numbered&):mysn(rand()){};
public:
      int mysn;
};

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

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

13.1.16节练习

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

static int num=0;
class Employee
{
public:
    Employee():id(num){num++;};
    Employee(string s):id(num){num++;};
private:
    string name;
    int id;
};

int main()
{
    //numbered a,b=a,c=b;
    //f(a);f(b);f(c);
    Employee a;
    return 0;
}

13.19:你的Employee类需要定义它自己的拷贝控制成员?如果需要为什么?
答:

我觉得不需要-.-,会让拷贝的序号相同

13.20:解释当我们拷贝,赋值或销毁TextQueryQueryResult类对象时会发生什么?
答:

由于两个类都未定义拷贝控制成员,所以会定义其合成版本。

13.21:你认为TextQueryQueryResult类需要定义他们自己版本的拷贝控制成员吗?
答:

他们都采用智能指针管理共享的动态对象。而这些标准库都有良好的拷贝控制成员,用合成的拷贝控制成员简单地拷贝赋值销毁,即可得到正确的资源管理。

13.2节练习

13.22:假定我们希望HasPtr的行为像一个值,即,对于对象所指向的string成员,每个对象都有一份自己的拷贝。我们将在下一节介绍拷贝控制成员的定义,但是,你已经学习了这些成员需要的知识,在继续学习下一节之前,为HasPtr编写拷贝构造函数和拷贝赋值运算符。
答:

#include<bits/stdc++.h>
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(){delete ps;};
private:
    string *ps;
    int i;
};
HasPtr& HasPtr::operator=(const HasPtr& rhs)
{
    delete ps;
    ps=new string(*rhs.ps);
    i=(rhs.i);
    return *this;
}
int main()
{
    //Employee a;
    HasPtr has;
    return 0;
}

13.2.1节练习

13.23:比较上一节练习中你编写的拷贝控制成员和这一节的代码?确定你理解了你的代码和我们的代码之间的差异
答:

理解了差异,主要是如果是拷贝自己的话,会把自己空间释放掉,这样再拷贝时本来需要拷贝的资源就没了。

13.24:如果本节中的HasPtr版本未定义析构函数,将会发生什么?如果未定义拷贝构造函数,将会发生什么
答:

编译器会自动合成析构函数,这样对于指针无法释放掉其内存

13.25:假定希望定义StrBlob的类值版本,而且希望继续使用shared_ptr,这样我们的StrBlobPtr类就仍能指向vectorweak_ptr了,你修改后的类将需要一个拷贝构造函数和一个拷贝赋值函数,但不需要析构函数,解释拷贝构造函数和拷贝赋值运算符必须要做什么。解释为什么不需要析构函数。
答:

不需要析构函数时因为其数据是智能指针,其自身已有其优秀的内存释放机制。

13.26:编写你自己的StrBlob类,编写你自己的版本

不想写-.-

13.2.2节练习

13.27:定义你自己的使用引用计数版本的HasPtr
答:

#include<bits/stdc++.h>
#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&);
    ~HasPtr();
    int  get_use(){
        return *use;
    }
private:
    string *ps;
    int i;
    size_t *use;

};
HasPtr::~HasPtr()
{
    if(--*use==0)
    {
        delete ps;
        delete use;
    }
}

HasPtr&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;
}

int main()
{
    HasPtr has;
    HasPtr temp(has);
    cout<<temp.get_use()<<endl;
    return 0;
}

13.28:给定下面的类,为其实现一个默认构造和必要的拷贝构造成员
答:

(a)class TreeNode{
	private:
		std::string value;
		int count;
		TreeNode *left;
		TreeNode *right;
};
(b)class BinStrTree{
			private:
					TreeNode*root;
}	

答:

#include<bits/stdc++.h>

using namespace std;

class TreeNode
{
private:
    string value;
    int count;
    TreeNode *left;
    TreeNode*right;

public:
     TreeNode(const string&s=string()):value(s),count(0),left(nullptr),right(nullptr){};
     TreeNode(const TreeNode&rhs):value(rhs.value),count(rhs.count)
     {
             delete left;
             delete right;
             value=rhs.value;
             count=rhs.count;
             left=new TreeNode(*rhs.left);
             right=new TreeNode(*rhs.right);
     }
};

class BinStrTree
{
private:
    TreeNode*root;

public:
    BinStrTree():root(nullptr){};
    BinStrTree(const BinStrTree&rhs)
    {
        delete root;
        root=new TreeNode(*rhs.root);
    }
};
int main()
{
    TreeNode node;
    BinStrTree node1;

    TreeNode node2(node);

    BinStrTree node3(node1);

    return 0;

}

13.3节练习

13.29:解释swap(HasPtr&,HasPtr&)中对swap``的调用不会产生递归循环
答:

参数的类型不同,属于函数重载

13.30:为你的类值版本的HasPtr编写swap函数,并测试它。为你的swap函数添加一个打印语句,指出函数什么时候执行。
答:

#include<bits/stdc++.h>

using namespace std;

class HasPtr
{
    friend void swap(HasPtr&,HasPtr&);//其他成员定义
private:
    int i;
    string *ps;
public:
    HasPtr(const string &s=std::string()):i(0),ps(new string(s)){};
    HasPtr(const HasPtr&rhs)
    {
        delete ps;
        ps=new string(*rhs.ps);
        i=rhs.i;
    }
    HasPtr&operator=(HasPtr);
    //inline void swap(HasPtr&lhs,HasPtr&rhs);

};

HasPtr&HasPtr::operator=(HasPtr rhs)
{
    swap(*this,rhs);
    return *this;
}
inline void swap(HasPtr&lhs,HasPtr&rhs)
{
    using std::swap;
    swap(lhs.ps,rhs.ps);
    swap(lhs.i,rhs.i);
}

int main()
{
    HasPtr has1,has2;
    has1=has2;
    return 0;
}

13.31:为你的HasPtr类定义一个<运算符,并定义一个HasPtrvector,为这个vector添加一些元素,并对它执行sort,注意何时会调用swap

#include<bits/stdc++.h>

using namespace std;

class HasPtr
{
    friend void swap(HasPtr&,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&operator*();
    bool operator<(const HasPtr&)const;
    ~HasPtr();

private:
    string *ps;
    int i;
};

HasPtr::~HasPtr()
{
    delete ps;
}

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

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

string &HasPtr::operator*()
{
    return *ps;
}

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

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


int main()
{
    vector<HasPtr>vh;

    int n;
    cin>>n;
    for(int i=0;i<n;i++)
    {
        vh.emplace_back(to_string(n-i));
    }
    for(auto p:vh)
    {
        cout<<*p<<" ";
    }
    cout<<endl;
    sort(vh.begin(),vh.end());
    for(auto p:vh)
    {
        cout<<*p<<" ";
    }
    cout<<endl;

    return 0;
}

13.32:类指针的HasPtr版本会从swap函数受益吗?如果会,得到了什么?
答:

默认swap版本交换对其类指针的引用计数正常处理,能够正确处理类指针的交换,专用并不能带来收益。

13.4节练习

13.33:为什么Message的成员saveremove的参数是一个Folder&
答:

因为需要将Folder的指针添加到当前的Message的集合,这样话必须要引用类型,如果不是,则添加的就是拷贝,哪地址就变了,而由于需要该改变数据,也不能是const

13.34:编写本节描述的Message
答:

//头文件实现,其本身有问题,但是我不知到出现再哪了-.-
#pragma once
#ifndef MESSAGE_H_INCLUDED
#define MESSAGE_H_INCLUDED
#include<string>
#include"Folder1.h"
using namespace std;
class Message
{
    friend class Folder;
public:
    explicit Message(const std::string& str = "") :contents(str) {};
    Message(const Message&);
    Message& operator=(const Message&);
    ~Message();

    void save(Folder&);
    void remove(Folder&);

private:
    string contents;
    set<Folder*>folders;
    void add_to_Folders(const Message&);
    void remove_from_Folders();

};

void Message::save(Folder&f)
{
    folders.insert(&f);
    f.addMsg(this);//将信息添加到f所在的存储信息的集合中
}

void Message::remove(Folder&f)
{
    folders.erase(&f);
    f.removeMsg(this);
}

void Message::add_to_Folders(const Message& m)
{
    for (auto f : m.folders)
    {
        f->addMsg(this);
    }
}
Message::Message(const Message& m):contents(m.contents), folders(m.folders)
{
    add_to_Folders(m);
}

void Message::remove_from_Folders()
{
    for (auto f : folders)
    {
        f->removeMsg(this);
    }
}

Message::~Message()
{
    remove_from_Folders();
}

Message& Message::operator=(const Message& rhs)
{
    remove_from_Folders();
    contents = rhs.contents;
    folders = rhs.folders;
    add_to_Folders(rhs);
    return *this;
}


#endif // MESSAGE_H_INCLUDED

13.36:如果Message使用合成的拷贝控制成员,会发生什么
答:

拷贝set,对Message来说应该是没啥问题的,但是对于Folder来说,并未将这个拷贝的数据存入其set中

13.36:设计并实现对应的Folder类,此类应该保存每一个指向Folder中包含的Messageset
答:

//头文件实现,依旧有问题,但是不知道出现在哪
#pragma once
#ifndef FOLDER_H_INCLUDED
#define FOLDER_H_INCLUDED
#include<iostream>
#include<set>
#include<algorithm>
#include"Message1.h"
typedef Message mmm;
using namespace std;
class Folder
{
public:
    friend class Message;
    Folder() {};
    Folder(const Folder& p) {
        messages = p.messages;
    }
private:
    set<Message*>messages;
    void  addMsg(mmm*);
    void removeMsg(mmm*);
};

void Folder::addMsg(mmm *m)
{
    messages.insert(m);
}

void Folder::removeMsg(mmm *m)
{
    messages.erase(m);
}



#endif // FOLDER_H_INCLUDED

13.37:为Message类添加成员,实现向folders添加或删除一个给定的Folder*。这两个成员类似Folder类的addMsgremMsg操作
答:

不想写

13.38:我们并未使用拷贝并交换方式来设计Message的赋值运算符。你认为其原因是啥?
答:

需要自己定义swap?

13.5节练习

13.39:编写你自己的版本的StrVec,包括自己版本的reservecapacityresize
答:

#include<iostream>
#include<string>
#include<algorithm>
#include<atlalloc.h>
//#define alloc alloccc
//using namespace std;
class StrVec {
public:
	StrVec() :
		elements(nullptr), first_free(nullptr), cap(nullptr) {};//默认初始化
	StrVec(const StrVec&);//拷贝构造函数
	StrVec& operator=(const StrVec&);//拷贝赋值运算符
	~StrVec();//析构函数
	void push_back(const std::string&);//拷贝元素
	
	size_t size() const { return first_free - elements; };
	size_t capacity() const { return cap - elements; };
	std::string* begin()const { return elements; };
	std::string* end() const { return first_free; };
	void reserve(std::string*, std::string*);
	void resize(int n);
	 std::allocator<std::string>alloc;//被添加的元素使用
private:
	 
	void chk_n_alloc() {
		if (size() == capacity())reallocate();//工具函数,被拷贝构造函数
	}
	std::pair<std::string*, std::string*>alloc_n_copy(const std::string*, const std::string*);

	void free();//销毁并释放内存
	void reallocate();//获得更多内存并拷贝已有的元素
	std::string* elements;//指向数组首元素的指针
	std::string* first_free;//指向数组第一个空闲元素的指针
	std::string* cap;//指向数组尾后位置的指针
	
};

void StrVec::push_back(const std::string& s) {
	chk_n_alloc();//确保有空间容纳新的元素
	alloc.construct(first_free++, s);//在first_free指向的元素中构造s的副本
}
//using std::string;
std::pair<std::string*,std::string*>
StrVec::alloc_n_copy(const std::string* b, const std::string* e) {
	//分配空间保存给定范围内的元素
	auto data = alloc.allocate(e - b);
	//
	return { data, uninitialized_copy(b, e, data) };
}

void StrVec::free() {
	if (elements) {
		for (auto p = first_free; p != elements; alloc.destroy(--p));
		alloc.deallocate(elements, cap - elements);
	}
}

StrVec::StrVec(const StrVec& s) {
	auto newdata = alloc_n_copy(s.begin(), s.end());
	elements = newdata.first;
	first_free = cap = newdata.second;
}
StrVec::~StrVec() {
	free();
}
StrVec& StrVec::operator=(const StrVec& rhs) {
	auto data = alloc_n_copy(rhs.begin(), rhs.end());
	free();
	elements = data.first;
	first_free = cap = data.second;
	return *this;
}
//自己编写
void StrVec::reserve(std::string* start, std::string* beg) {
	if (start >= begin() && start <= end() && beg >= begin() && beg <= end()) {
		beg--;
		while (start < beg) {
			swap(*start, *beg);
			start++;
			beg--;
		}
	 }
	 
}
void StrVec::resize(int n) {
	auto newsize = n;
	auto newdata = alloc.allocate(newsize);
	auto dest = newdata;
	auto elem = elements;
	size_t i;
	for ( i = 0; i != size()&&i!=newsize; i++) {
		alloc.construct(dest++, std::move(*elem++));
	}
	for (; i < newsize; i++) {
		alloc.construct(dest++, "");
	}
	free();
	elements = newdata;
	first_free = cap = dest;
}

//end
void StrVec::reallocate() {
	//分配当前大小两倍的内存空间
	auto newcapacity = size() ? 2 * size() : 1;
	//分配内存
	auto newdata = alloc.allocate(newcapacity);
	//将数据从旧内存移动到新内存
	auto dest = newdata;//指向数组中下一个空闲位置
	auto elem = elements;//指向就数组中下一个位置

	for (size_t i = 0; i != size(); i++) {
		alloc.construct(dest++, std::move(*elem++));
	}
	free();

	elements = newdata;
	first_free = dest;
	cap = elements + newcapacity;
}
int main() {
	using std::cout;
	using std::endl;
	StrVec s;
	s.push_back("hello");
	s.push_back("world");
	s.reserve(s.begin(), s.end());
	s.resize(5);
	for (auto start = s.begin(); start != s.end(); start++)cout << *start << " ";
	cout << endl;
	return 0;
}

13.40:为你的StrVec类添加一个构造函数,它接受一个``initializer_list< string >参数。
答:

StrVec::StrVec(initializer_list<string>s) {
	resize(s.size());
	auto dest = elements;
	for (auto iter = s.begin(); iter != s.end(); iter++) {
		alloc.construct(dest++, *iter);
	}
}

13.41:在push_back中,我们为什么在construct调用后置递增运算符,如果使用前置递增运算符,会发生什么?
答:

后置运算符会操作到每个元素,而前置运算符会把第一个漏掉

13.42:在你的TextQueryQueryResult类中用StrVec代替``vector< string >
答:

不想写-.-

13.43:重写free函数,用for_eachlambda代替for循环destroy元素。
答:

for_each(elements, first_free,
			[this](string &s) { this->alloc.destroy(&s); });

13.44:编写标准库string类的简化版本,命名为String。你的类应该至少有一个默认的构造函数和一个接受c风格字符串指针参数的构造函数,使用allocator为你的String列分配所需内存。
答:

写了最基本的要求
#include<iostream>

using namespace std;

class String {
public:
	String() :elements(nullptr) {};
	String(const char*);
	allocator<char>alloc;
private:
	char* elements;
};
String::String(const char* s) {
	auto newdata = alloc.allocate(strlen(s));
	elements = newdata;
	auto dest = elements;
	for (int i = 0;i<=strlen(s); i++) {
		alloc.construct(dest++, s[i]);
	}
}

int main() {
	String s("hello");
	return 0;
}

13.6.1节练习

13.45:解释右值引用和左值引用的区别?
答:

所谓右值引用就是必须绑定到右值的引用,通常通过&&获取,右值引用只能绑定到一个将要销毁的对象中去,因此可以自由的移动其资源,左值引用,也就是常规引用,不能绑定到要转换的表达式,字面常量,或者返回右值的表达式,而右值引用恰好相反,可以绑定到这类中。返回左值的表达式包括返回左值引用的类型的函数和算术,关系,位,后置递增,递减,可以看到,左值的特点是有持久化的状态,而右值是短暂的。

13.46:什么类型的引用可以绑定到下面的初始化器上。

int f()
vector<int>vi(100)
int ?r1=f()
int ?r2=vi[0]
int ?r3=r1;
int ?r4=vi[0]*f()

答:

1.右值
2.左值
3.左值
4.右值

左值具有持久性,右值具有短暂性

13.47:不打算做这个-.-
13.48:定义一个vector<string>并在其上多次调用push_back。运行你的程序,并观察string被拷贝了多少次?
答:

理解拷贝何时发生

13.6.2节练习

13.49:为你的StrVec类添加一个移动构造函数和移动赋值函数
答:

StrVec::StrVec(StrVec&& s) noexcept{
	elements = s.elements;
	first_free = s.first_free;
	s.elements = 0;
	s.first_free = 0;
}
StrVec& StrVec::operator=(StrVec&& rhs) noexcept{
	if (this != &rhs) {
		if (elements)alloc.deallocate(elements, first_free-elements);
		elements = rhs.elements;
		first_free = rhs.first_free;
		rhs.elements = 0;
		rhs.first_free = 0;
	}
	return *this;
}

13.50:不打算写-.-
13.51:虽然unique_ptr不能拷贝,但是我们定义了一个clone函数,它以值的方式返回一个unique_ptr。解释为什么函数时合法的。
答:

unique_ptr不能拷贝,但是将要销毁的unique_ptr是可以拷贝的,因此clone返回局部unique_ptr对象ret是可以的,因为ret马上就要销毁了。而此时的
拷贝,其实是触发移动构造函数。

13.52:详细解释HasPtr对象的赋值发生了什么,特别是,一步一步描述hp,hp1以及HasPtr的赋值运算符参数rhs发生了什么变化?
答:

在进行拷贝赋值时,先通过拷贝构造创造出hp2的拷贝,然后再交换hp和rhs,rhs作为一个中间媒介,只是起到了将值从hp2传递给hp的作用
是一个冗余的操作,移动赋值页类似。

13.54如果我们为HasPtr定义移动赋值运算符,但并未改变拷贝并交换元素符,会发生什么?编写代码验证你的答案。
答:

会产生编译错误,因为产生了二义性。

13.6.3节练习

13.56:为你的StrBlob添加一个右值引用版本的push_back
答:

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>

using namespace std;
class StrBlob {
public:
	typedef std::vector<std::string>::size_type size_type;
	StrBlob();
	StrBlob(std::initializer_list<std::string> i1);
	size_type size() const { return data->size(); };
	bool empty() const {
		return data->empty();
	};
	void push_back(const std::string& t) { data->push_back(t); };
	void push_back(string&&);
	void pop_back();
	//元素访问
	std::string& front() const {
		return data->front();
	}
	std::string& back() const {
		return data->back();
	}
private:
	std::shared_ptr<std::vector<std::string>>data;
	void check(size_type i, const std::string& msg) const;

};

inline StrBlob::StrBlob() :data(make_shared<vector<string>>()) {};
void StrBlob::push_back(string&& rhs) {
	cout << "移动" << endl;
	data->push_back(move(rhs));
	
}

int main() {
	//测试
	StrBlob a;
	string s = "hello";
	a.push_back(s);
	a.push_back("hello");
	//a.push_back();
	return 0;
}

13.56:如果sorted定义如下,会发生什么

Foo Foo:sorted()const&{
	Foo ret(*this);
	return ret.sorted();
}

答:

编译器认为是左值,于是会递归下去

13.57:如果sorted定义如下,会发生什么?

Foo Foo::sorted()const &{return Foo(*this).sorted();}

答:

副本是一个右值,所以会正常执行。

13.58:编写新版本的Foo类,其sorted中有打印语句,测试这个类,来验证自己的答案
答:

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;

class Foo {
public:
	Foo sorted()&&;
	Foo sorted()const&;
	Foo() {
		data.emplace_back(3);
		data.emplace_back(4);
	}
private:
	vector<int>data;
};
Foo Foo::sorted()&& 
{
	cout << "222" << endl;
	sort(data.begin(), data.end());
	return *this;
}
Foo Foo::sorted() const&
{
	//cout << "111" << endl;
	//return Foo(*this).sorted();
	Foo ret(*this);
	return ret.sorted();
	
}
int main() {
	Foo f1;
	f1.sorted();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值