?? C Primer 0x0D 练习题解
更好的阅读体验(实时更新和修正)
推荐阅读 《C Primer 5th》知识点总结&练习题解
13.1 复制、赋值和销毁
13.1.1 复制构造函数
13.1 什么是复制结构函数?什么时候用?
如果构造函数的第一个参数是自己的类型(通常是一个const
引用),任何额外参数都有默认值,因此该构造函数是复制构造函数
复制构造函数将用于复制初始化
复制的初始化不仅仅是我们使用的=
定义变量也会发生在以下情况下
- 将对象作为实参传递给非引用形参
- 从返回类型为非引用类型的函数返回对象
- 用花括列表初始化一个数组中的元素或聚合成员
- 有些类型也会使用复制来初始化他们分配的对象(
insert
、push
将复制初始化,emplace
会直接初始化)
13.2 解释为什么下面的声明是非法的:
Sales_data::Sales_data(Sales_data rhs);
参数应引用自己类型的参数
13.3 当我们复制一个时
StrBlob
会发生什么?复制一个StrBlobPtr
呢?
StrBlob
使用了shared_ptr
引用计数加一
StrBlobPtr
使用了weak_ptr
不增加引用计数
其他数据成员直接复制
13.4 假定
Point
它有一种类型public
复制构造函数,指出复制构造函数在以下程序片段中使用的地方:
Point global; Point foo_bar(Point arg) // 1 {
Point local = arg, *heap = new Point(global); // 2,3 *heap = local; //4 Point pa[4] = {
local, *heap }; // 5, 6 return *heap; // 7 }
- 将一个对象作为实参传递给非引用形参(1)
- 使用=定义变量(2,3,4)
- 数组中的元素或聚合物中的成员(5,6 用了两次)
- 从返回类型为非引用类型的函数返回一个对象(7)
13.5 给定下面的类框架,编写一个拷贝构造函数,拷贝所有成员。你的构造函数应该动态分配一个新的
string
,并将对象复制到ps
所指向的位置,而不是复制ps本身:
class HasPtr {
public: HasPtr(const std::string& s = std::string()): ps(new std::string(s)), i(0) {
}
private:
std::string *ps;
int i;
}
#include <string>
class HasPtr {
public:
HasPtr(const std::string& s = std::string()):
ps(new std::string(s)), i(0) {
}
HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){
}
private:
std::string *ps;
int i;
};
13.1.2 拷贝赋值运算符
13.6 拷贝赋值运算符是什么?什么时候使用它?合成拷贝赋值运算符完成什么工作?什么时候会生成合成拷贝赋值运算符?
- 拷贝运算符是函数
operator=
的一个重载,拷贝赋值运算符接受一个与其所在类相同类型的参数
在使用赋值运算时会使用拷贝赋值运算符
- 如果一个类未定义自己的拷贝赋值运算符,编译器会为它生产一个合成拷贝赋值运算符
- 对于某些类,合成拷贝赋值运算符用来禁止该类型对象的赋值
- 一般的,拷贝赋值运算符会将右侧运算对象的每个非
static
成员赋予左侧运算对象的对应成员,这一工作是通过的拷贝赋值运算符来完成的。对于数组类型的成员,逐个赋值数组元素 - 合成拷贝赋值运算符返回一个指向左侧运算对象的引用
13.7 当我们将一个
StrBlob
赋值给另一个StrBlob
时,会发生什么?赋值StrBlobPtr
呢?
StrBlob
:会通过shared_ptr
类的拷贝赋值运算符将shared_ptr
拷贝赋值,且其计数器自增。
StrBlobPtr
:会通过weak_ptr
类的拷贝赋值运算符将weak_ptr
拷贝赋值。curr
调用内置类型的赋值运算符。
13.8 为13.1.1节练习13.5中的
HasPtr
类编写赋值运算符。类似拷贝构造函数,你的赋值运算符应该将对象拷贝到ps
指向的位置。
记住处理自赋值和异常安全问题,这个是类值拷贝赋值运算符编写方式
#include <string>
class HasPtr {
public:
HasPtr(const std::string& s = std::string()):
ps(new std::string(s)), i(0) {
}
HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){
}
HasPtr& operator=(const HasPtr& rhs){
auto newp = new std::string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
~HasPtr() {
delete ps;}
private:
std::string *ps;
int i;
};
13.9 析构函数是什么?合成析构函数完成什么工作?什么时候会生成合成析构函数?
- 析构函数:释放对象使用的资源,并销毁对象的非
static
数据成员 - 析构函数是类的一个成员函数,名字波浪号接类名,没有返回值,也不接受参数,不能被重载,对一个给定类唯一
- 在析构函数中,首先执行函数体,然后销毁成员,成员按初始化顺序的逆序销毁
- 不像构造函数有初始化列表,析构函数的析构部分是隐式的,成员销毁时发生什么完全依赖成员的类型
- 销毁类类型的成员执行类类型的析构函数
- 内置类型没有析构函数,销毁内置类型成员什么也不需要做
- 隐式销毁一个内置指针类型的成员不会
delete
它所指向的对象 - 与普通指针不同,智能指针是类类型,所以具有析构函数。智能指针成员在析构阶段会被自动销毁
- 当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数
- 对于某些类,合成析构函数被用来阻止该类型的对象被销毁
- 一般,合成析构函数的函数体为空
- **认识到析构函数体自身并不直接销毁成员很重要。成员是在析构函数体之后隐含的析构阶段被销毁的。**在整个对象销毁过程中,析构函数体是作为成员销毁步骤之外的另一部分进行的
13.10 当一个
StrBlob
对象销毁时会发生什么?一个StrBlobPtr
对象销毁时呢?
StrBlob
:shared_ptr
的引用计数减少
StrBlobPtr
:不影响引用计数
13.11 为前面练习中的
HasPtr
类添加一个析构函数。
#include <string>
class HasPtr {
public:
HasPtr(const std::string& s = std::string()):
ps(new std::string(s)), i(0) {
}
HasPtr(const HasPtr& hp):ps(new std::string(*hp.ps)),i(hp.i){
}
HasPtr& operator=(const HasPtr& rhs){
auto newp = new std::string(*rhs.ps);
delete ps;
ps = newp;
i = rhs.i;
return *this;
}
~HasPtr(){
delete ps;}
private:
std::string *ps;
int i;
};
13.12 下面的代码片段中会发生几次析构函数调用?
bool fcn(const Sales_data *trans, Sales_data accum)
{
Sales_data item1(*trans), item2(accum);
return item1.isbn() != item2.isbn();
}
3次
item1
、item2
、accum
离开作用域被销毁
trans
是指向对象的指针,析构函数不会执行
13.13 理解拷贝控制成员和构造函数的一个好方法的定义一个简单的类,为该类定义这些成员,每个成员都打印出自己的名字:
struct X {
X() {
std::cout << "X()" << std::endl;}
X(const X&) {
std::cout << "X(const X&)" << std::endl;}
};
给 X
添加拷贝赋值运算符和析构函数,并编写一个程序以不同的方式使用 X
的对象:将它们作为非引用参数传递;动态分配它们;将它们存放于容器中;诸如此类。观察程序的输出,直到你确认理解了什么时候会使用拷贝控制成员,以及为什么会使用它们。
#include <iostream>
#include <string>
#include <vector>
struct X {
X() {std::cout << "X()" << std::endl;}
X(const X&) {std::cout << "X(const X&)" << std::endl;}
X& operator=(const X&rhs){std::cout << "X& operator=(const X&rhs)" << std::endl;}
~X(){std::cout << "~X()" << std::endl;}
};
void f(X a,const X& b){
std::vector<X>v;
std::cout << "push_back a" << std::endl;
v.push_back(a);
std::cout << "push_back b" << std::endl;
v.push_back(b);
}
int main(){
std::cout << "create pb" << std::endl;
X a;
std::cout << "create pb" << std::endl;
X* pb = new X(a);
std::cout << "f(a,*pb)" << std::endl;
f(a,*pb);
delete pb;
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
定义了一个拷贝构造函数,能生成一个新的序列号。这会改变上一题中调用的输出结果吗?如果会改变,为什么?新的输出结果是什么?
输出三个不一样的数字,但是和 a,b,c 也没有关系
13.16 如果
f
中的参数是const numbered&
,将会怎样?这会改变输出结果吗?如果会改变,为什么?新的输出结果是什么?
输出三个不一样的数字,分别是a,b,c的数字
13.17 分别编写前三题中所描述的
numbered
和f
,验证你是否正确预测了输出结果。
3.14
#include <iostream>
#include <string>
#include <vector>
class numbered{
public:
friend void f(numbered s);
numbered() {
mysn = num++;}
private:
static int num;
int mysn;
};
int numbered::num = 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;
}
输出三个0
3.15
#include <iostream>
#include <string>
#include <vector>
class numbered{
public:
friend void f(numbered s);
numbered() {
mysn = num++;}
numbered(const numbered &s){
mysn = num++;
}
private:
static int num;
int mysn;
};
int numbered::num = 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;
}
输出3,4,5
3.16
#include <iostream>
#include <string>
#include <vector>
class numbered{
public:
friend void f(const numbered &s);
numbered() {
mysn = num++;}
numbered(const numbered &s){
mysn = num++;
}
private:
static int num;
int mysn;
};
int numbered::num = 0;
void f (const numbered &s) {
std::cout << s.mysn << std::endl; }
int main(){
numbered a,b=a,c=b;
f(a);f(b);f(c);
return 0;
}
输出0,1,2
13.1.6 阻止拷贝
3.18 定义一个
Employee
类,它包含雇员的姓名和唯一的雇员证号。为这个类定义默认构造函数,以及接受一个表示雇员姓名的string
的构造函数。每个构造函数应该通过递增一个static
数据成员来生成一个唯一的证号。
class Employee{
public:
Employee(){
id = id_num++;}
Employee(const std::string &s):name(s),id(id_num){
id_num++;
}
private:
std::string name;
int id;
static int id_num;
};
static int id_num = 1000;
3.19 你的
Employee
类需要定义它自己的拷贝控制成员吗?如果需要,为什么?如果不需要,为什么?实现你认为Employee
需要的拷贝控制成员。
不需要拷贝,雇员没有必要因为拷贝而递增编号。将拷贝构造和赋值构造运算符定义为删除来阻止拷贝和赋值
class Employee{
public:
Employee(){
id = id_num++;}
Employee(const std::string &s):name(s),id(id_num){
id_num++;
}
Employee(const Employee &)=delete;
Employee& operator=(const Employee&)=delete;
private:
std::string name;
int id;
static int id_num;
};
static int id_num = 1000;
13.20 解释当我们拷贝、赋值或销毁
TextQuery
和QueryResult
类对象时会发生什么?
因为这两个类中使用的是智能指针,因此在拷贝时,类的所有成员都将被拷贝,在销毁时所有成员也将被销毁。
13.21你认为
TextQuery
和QueryResult
类需要定义它们自己版本的拷贝控制成员吗?如果需要,为什么?实现你认为这两个类需要的拷贝控制操作。
不需要,因为用的智能指针,合成版本可以自动控制内存释放
,需要析构函数的类也需要拷贝和赋值操作
13.2 拷贝控制和资源管理
13.22 假定我们希望
HasPtr
的行为像一个值。即,对于对象所指向的string
成员,每个对象都有一份自己的拷贝。我们将在下一节介绍拷贝控制成员的定义。但是,你已经学习了定义这些成员所需的所有知识。在继续学习下一节之前,为HasPtr
编写拷贝构造函数和拷贝赋值运算符。
同13.8
13.2.1 行为像值的类
13.23 比较上一节练习中你编写的拷贝控制成员和这一节中的代码。确定你理解了你的代码和我们的代码之间的差异。
因为先把整章看了一遍才写练习的,所以基本没区别。
朴素一些的写法是用if
判一下是不是自赋值,高级一点的写法是用swap
来实现,后面会有
13.24 如果本节的
HasPtr
版本未定义析构函数,将会发生什么?如果未定义拷贝构造函数,将会发生什么?
三/五法则,拷贝成员控制的几个操作应该看成一个整体,一般要写就都写
未定义析构函数会内存泄漏
未定义拷贝构造函数会只拷贝指针的值,几个指针指向同一个地址
13.25 假定希望定义
StrBlob
的类值版本,而且希望继续使用shared_ptr
,这样我们的StrBlobPtr
类就仍能使用指向vector
的weak_ptr
了。你修改后的类将需要一个拷贝的构造函数和一个拷贝赋值运算符,但不需要析构函数。解释拷贝构造函数和拷贝赋值运算符必须要做什么。解释为什么不需要析构函数。
拷贝构造函数和拷贝赋值运算符要重新动态分配内存
因为用的智能指针,可以自动控制内存释放,引用计数为0自动销毁对象,所以不需要析构函数
13.26 对上一题中描述的
strBlob
类,编写你自己的版本。
StrBlob
里添加
//拷贝构造
StrBlob(const StrBlob& sb):data(std::make_shared<std::vector<std::string>>(*sb.data)){
}
//拷贝赋值
StrBlob& operator=(const StrBlob& sb){
data = std::make_shared<std::vector<std::string>>(*sb.data);
return *this;
}
13.2.2 定义行为像指针的类
13.27 定义你自己的使用引用计数版本的
HasPtr
。
class HasPtr {
public:
HasPtr(const std::string &s = std::string())
:ps(new std::string(s)),i(0),use(new std::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:
std::string *ps;
int i;
std::size_t *use;//引用计数
};
13.28 给定下面的类,为其实现一个默认构造函数和必要的拷贝控制成员。
TreeNode 做了修改,count应该是引用计数,照道理这个应该放内存而不属于任何一个对象,所以改成了int* 类型
(a)
class TreeNode {
private:
std::string value;
int *count;
TreeNode *left;
TreeNode *right;
};
(b)
class BinStrTree{
private:
TreeNode *root;
};
#include <cstddef> #include <string> class TreeNode { public: TreeNode() :value(std::string()),count(new int(1)),left(nullptr),right(nullptr){ } TreeNode(const TreeNode& t) :value(t.value),count(t.count),left(t.left),right(t.right){ ++*count; } TreeNode& operator=(const TreeNode& rhs){ ++*rhs.count; if(--*count == 0){ delete left; delete right; delete count; } value 标签:
t48kb配k型传感器