概述
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:分别编写前三题中所描述的numbered
和f
,验证你的预测?
答:
#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:解释当我们拷贝,赋值或销毁TextQuery
和QueryResult
类对象时会发生什么?
答:
由于两个类都未定义拷贝控制成员,所以会定义其合成版本。
13.21:你认为TextQuery
和QueryResult
类需要定义他们自己版本的拷贝控制成员吗?
答:
他们都采用智能指针管理共享的动态对象。而这些标准库都有良好的拷贝控制成员,用合成的拷贝控制成员简单地拷贝赋值销毁,即可得到正确的资源管理。
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
类就仍能指向vector
的weak_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
类定义一个<运算符,并定义一个HasPtr
的vector
,为这个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
的成员save
和remove
的参数是一个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
中包含的Message
的set
答:
//头文件实现,依旧有问题,但是不知道出现在哪
#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
类的addMsg
和remMsg
操作
答:
不想写
13.38:我们并未使用拷贝并交换方式来设计Message
的赋值运算符。你认为其原因是啥?
答:
需要自己定义swap?
13.5节练习
13.39:编写你自己的版本的StrVec
,包括自己版本的reserve
,capacity
和resize
答:
#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:在你的TextQuery
和QueryResult
类中用StrVec
代替``vector< string >
答:
不想写-.-
13.43:重写free
函数,用for_each
和lambda
代替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;
}
最后
以上就是虚拟绿茶为你收集整理的C++Primer第13章:拷贝控制(习题解答)13.1.1节练习13.1.6节练习13.1.3节练习13.1.4节练习13.1.16节练习13.2节练习13.2.1节练习13.2.2节练习13.3节练习13.4节练习13.5节练习13.6.1节练习13.6.2节练习13.6.3节练习的全部内容,希望文章能够帮你解决C++Primer第13章:拷贝控制(习题解答)13.1.1节练习13.1.6节练习13.1.3节练习13.1.4节练习13.1.16节练习13.2节练习13.2.1节练习13.2.2节练习13.3节练习13.4节练习13.5节练习13.6.1节练习13.6.2节练习13.6.3节练习所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复