C++ Primer 答案 第十三章(一)

13.1

拷贝构造函数:第一个参数是自身类型的引用,且任何额外参数都有默认值
                         拷贝初始化是通过拷贝构造函数或移动构造函数完成的

13.2

第一个参数必须的引用

13.3

拷贝StrBlob时,智能指针的计数器加一
拷贝StrBlobPtr时,智能指针计数器不增加

13.4

记住4种使用拷贝构造函数的情况:
1. 用 = 初始化对象
2. 对象作实参传递给非引用类型
3. 对象作返回值返回给返回值为非引用类型的函数
4. 花括号初始化对象

13.5

HashPtr(const HashPtr& orig):
    ps(new string(*orig.ps)),
    i(orig.i)
    { }

13.6

拷贝赋值运算符: 一种函数,函数名是 opetator=,注意它的返回值是引用
当对象赋值时使用它
合成拷贝赋值运算符:也是函数,功能1: 禁止赋值
                         功能2: 将右边对象的非static成员赋值给左边对象的对应成员
当类没有定义拷贝运算符石,编译器会自动生成一个合成拷贝运算符 

13.7

如13.3

13.8

HashPtr& operator=(const HashPtr& rhs)
{   //这里很多细节操作,参考454页
    auto temp = new string(*rhs.ps);
    delete ps; //需要先delete已有的ps指向的内存,因为咱这是赋值操作,说明早就初始化了!!!
    ps = temp; //为啥咱们要弄个临时的呢,因为如果是复制自己,执行delete后,它就没法复制了
    i = rhs.i;
    return *this;
}

13.9

析构函数:形如 ~fun()的函数,作用于=与构造函数相反

13.10

析构StrBlob时,智能指针的计数器减一,为0时自动释放内存
拷贝StrBlobPtr时,智能指针计数器不减少,自然内存也不会被释放

13.11

~HashPtr() { delete ps;}

13.12

3次,分别是 accum, item1, item2 。
trans是指针,所以没它啥事

13.13

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

struct X
{
    X() {cout << "调用 构造" << endl;}
    X(const X&) {cout << "调用 拷贝" << endl;}
    X& operator=(const X&)
    {
        cout << "调用 赋值" << endl;
        return *this;
    }
    ~X() {cout << "调用 析构" << endl;}
};

void fun(const X x1, const X &x2)
{
    cout << " 2222222222 " << endl;
    vector<X> vec;
    cout << " 3333333333 " << endl;
    vec.push_back(x1);
    cout << " 4444444444 " << endl;
    vec.push_back(x2);
    cout << " 5555555555 " << endl;
}
int main(int argc, char *argv[])
{
    X *px = new X;
    cout << " 1111111111 " << endl;
    fun(*px, *px);
    cout << " 6666666666 " << endl;
    X x3 = *px;
    x3 = *px;
    delete px;
    return 0;
}

/*输出:
调用 构造      //构造我们申请的第一个X,用指针px指向它
 1111111111 
调用 拷贝      //只执行一次拷贝,因为引用传递不拷贝
 2222222222 
 3333333333 
调用 拷贝      //push_back(x1) : 调用一次拷贝
 4444444444 
调用 拷贝      //push_back(x2) : 正常情况只有一个调用 拷贝,为啥多了东西呢?? 
调用 拷贝      //因为vector是动态增加,加了x1后,capacity变为1,再加x2,会执行容量增加,
调用 析构      //该操作是把原vec拷贝到更大的空间,再析构原来的空间,因此多出一个拷贝和析构
 5555555555   //如果之前先设置 vec.reserve(2);  会发现444,555之间只有一句 调用 拷贝
调用 析构      
调用 析构
调用 析构      //函数fun生命期结束,析构 x1, 还有vec内的两个X
 6666666666 
调用 拷贝      //拷贝初始化x3
调用 赋值      //给x3赋值
调用 析构      //用delete析构最初动态申请的X
调用 析构      //main结束,自动析构x3
*/

13.14

输出三个相同的数字,因为b,c采用拷贝初始化,在调用函数时仍然是拷贝初始化,而拷贝构造函数是系统合成的,只是简单拷贝所以都相同

#include <iostream>
using namespace std;

class numbered{
public:
    numbered()
    {
        static int unique = 10;//共有变量,构造一次加1,保证每次构造值都不同
        mysn = unique++;
    }
    int mysn;
};
void f(numbered s)
{
    cout << s.mysn << endl;
}
int main(int argc, char *argv[])
{
    numbered a, b = a, c = b;
    f(a);f(b);f(c);
    return 0;
}
/*输出:
10
10
10
*/

13.15

如果自己定义了拷贝构造函数,那自然按自己设定的来,输出结果自然会改变

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

class numbered{
public:
    numbered()
    {
        static int unique = 10;//共有变量
        mysn = unique++;
    }
    numbered(const numbered& n){mysn = n.mysn + 1;}
    int mysn;
};
void f(numbered s)
{
    cout << s.mysn << endl;
}
int main(int argc, char *argv[])
{
    numbered a, b = a, c = b; //此时a中的数据为10,b中为11,c中为12
    f(a);f(b);f(c);           //拷贝初始化a中为11,b中为12,c中为13
    return 0;
}
/*输出:
11
12
13
*/

13.16

采用引用传递,每个对象都少了一次拷贝,所以输出为10 11 12

#include <iostream>
using namespace std;

class numbered{
public:
    numbered()
    {
        static int unique = 10;//共有变量
        mysn = unique++;
    }
    numbered(const numbered& n){mysn = n.mysn + 1;}
    int mysn;
};
void f(numbered &s)
{
    cout << s.mysn << endl;
}
int main(int argc, char *argv[])
{
    numbered a, b = a, c = b;
    f(a);f(b);f(c);
    return 0;
}
/*输出:(以后不写这两字了)
10
11
12
*/

13.17

见前三题

13.18

被static弄的很烦,初始化的方式太奇怪了
Employee.h

#ifndef PRO1_EMPLOYEE_H
#define PRO1_EMPLOYEE_H

#include <string>
#include <iostream>
using namespace std;

class Employee {
public:
    Employee();
    explicit Employee(const string &s);
    void print();
private:
    static int unique;
    string name;
    int num;
};
#endif //PRO1_EMPLOYEE_H

Employee.cpp

#include "Employee.h"
int Employee::unique = 10;  //试了n种办法,好像只能这样弄了
Employee::Employee()
{
    num = unique++;
}
Employee::Employee(const string &s)
{
    name = s;
    num = unique++;
}

void Employee::print()
{
    cout <<"name: "<< name << " num: " << num << endl;
}

main.cpp

#include <iostream>
#include <string>
#include "Employee.h"
using namespace std;

int main(int argc, char *argv[])
{

    Employee e1;
    e1.print();
    Employee e2("Tom");
    e2.print();
    return 0;
}
/*
name:  num: 10
name: Tom num: 11
*/

13.19

不需要,因为现实中一个是不会复制另一个人的,所以我们把它设为delete
Employee(const Employee&) = delete;
Employee& operator=(const Employee&) = delete;

13.20

它们的数据是标准容器和智能指针,它们都有自己的拷贝,赋值,析构函数,所以会都正确执行

13.21

参见13.21 自然就不需要了
注意三/五法则,如果析构需要自定义,那拷贝和赋值也需要自定义

13.22

以前内容的汇总,我们可以看到它是重新申请了新的内存存放string,自然是行为像一个值

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

13.24

未定义析构: 内存泄漏
未定义拷贝: 咱们就只是简单拷贝了指针,没有新建自己的内存,当别人销毁时,咱也就玩完了;
                      这个已经有点像类似指针的对象,但它是有问题的;
                      正确操作是使用智能指针,效果是当最后一个使用它的人销毁后才会释放内存

13.25

我的理解啊 : 智能指针执行拷贝赋值时 默认只是拷贝智能指针(浅拷贝,共用内存)
                       所以为了实现类值版本,我们就需要用智能指针申请新的内存
                      不需要析构函数,是因为智能指针自动会析构申请的内存

13.26

加这两个函数,从浅拷贝变成了深拷贝

StrBlob(const StrBlob& sb)
        : data(std::make_shared<vector<string>>(*sb.data)){}
StrBlob& operator=(const StrBlob& sb)
{
    data = std::make_shared<vector<string>>(*sb.data);
    return *this;
}

13.27

#include <iostream>
#include <string>
using namespace std;
class HasPtr {
public:
    explicit HasPtr(const string& s = string()) : ps(new string(s)), i(0), use(new size_t(1)) {}
    HasPtr(const HasPtr& hp) : ps(hp.ps), i(hp.i) , use(hp.use){++*use; }
    HasPtr& operator=(const HasPtr& hp)
    {
        ++*hp.use;  //递增右侧对象的引用计数
        if(--*use == 0)  //递减左侧对象的引用计数,如果左侧引用计数为0,析构左边分配的内存
        {
            delete ps;
            delete use;
        }
        ps = hp.ps;
        i = hp.i;
        use = hp.use;
        return *this;
    }
    ~HasPtr()
    {
        if(--*use == 0)
        {
            delete ps;
            delete use;
        }
    }
    void print()
    {
        cout << *ps << " 引用次数为:";
        cout << *use << endl;
    } //自己加的用于测试
private:
    string* ps;
    int i;
    size_t *use;
};
int main(int argc, char *argv[])
{
    HasPtr p1("cat"); //直接初始化,次数为1 
    p1.print();
    {
        HasPtr p2 = p1; //拷贝初始化,次数加1,变为2
        p1.print();     //p1,p2都可以,因为输出是一样的
        p2 = p1;  //赋值运算,左边引用次数加1,右边减1 ,次数仍然是2
        p1.print();
    }
    p1.print(); //p2析构,次数变为1
    return 0;
}
/*
cat 引用次数为:1
cat 引用次数为:2
cat 引用次数为:2
cat 引用次数为:1
*/

13.28

(a)

class TreeNode
{
public:
    //这里构造函数中left和right必须用nullptr,因为如果用new TreeNode(),就会一直调用调用调用...
    TreeNode():value(string()), count(new int(1)), left(nullptr), right(nullptr){}
    TreeNode(const TreeNode& bst):value(bst.value), count(bst.count), left(bst.left), right(bst.right){++*count;}
    TreeNode&operator=(const TreeNode& rhs)
    {
        ++*rhs.count;
        if(--*count == 0)
        {  //这里有的迷
            if(left)
            {
                delete left;    //析构原来的值
                left = nullptr; //这里有点迷,以前也没加过
            }
            if(right)
            {
                delete right;
                right = nullptr;
            }
            delete count;
            count = nullptr;
        }
        count = nullptr;
        value = rhs.value;
        left = rhs.left;
        right = rhs.right;
        count = rhs.count;
    }
    ~TreeNode()
    {
        if(--*count == 0)
        {
            if(left)
            {
                delete left;
                left = nullptr;
            }
            if(right)
            {
                delete right;
                right = nullptr;
            }
            delete count;
            count = nullptr;
        }
    }
private:
    string value;
    int *count; //题目这里没有* 。。。和上题一样,它作引用次数
    TreeNode *left;
    TreeNode *right;
};


(b)
这个就是简单的值传递类

class BinStrTree
{
public:
    BinStrTree():root(new TreeNode()){}
    BinStrTree(const BinStrTree& bst):root(new TreeNode(*bst.root)){}
    BinStrTree& operator=(const BinStrTree& rhs)
    {
        auto new_root = new TreeNode(*rhs.root); //处理自赋值
        delete root;     //删除旧的
        root = new_root; //赋新值
        return *this;
    }
    ~BinStrTree() { delete root; }
private:
    TreeNode *root;
};

13.29

因为加了 using std::swap;  所以
swap(lhs.ps, rhs.ps);  //采用标准库的swap(string*, string*)
swap(lhs.i, rhs.r);    //采用标准库的swap(int,int)

12.30

#include <iostream>
#include <string>
using namespace std;
class HasPtr {
    friend void swap(HasPtr &lhs, HasPtr &rhs);
public:
    HasPtr(const string& s = string()) : ps(new string(s)), i(0) {}
    HasPtr(const HasPtr& hp) : ps(new string(*hp.ps)), i(hp.i) {}
    HasPtr& operator=(HasPtr rhs)
    {
        swap(*this, rhs);
        return *this;
    }
    ~HasPtr() { delete ps; }
private:
    string* ps;
    int i;
};
void swap(HasPtr &lhs, HasPtr &rhs)
{
    using std::swap;
    swap(lhs.ps,rhs.ps);
    swap(lhs.i, rhs.i);
    cout << "运行swap" << endl;
}
int main(int argc, char *argv[])
{
    HasPtr p1("cat");
    cout << "--------" << endl;
    p1 = p1;
    cout << "--------" << endl;
    return 0;
}
/*
--------
运行swap
--------
*/

13.31

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class HasPtr {
    friend void swap(HasPtr &lhs, HasPtr &rhs);
    friend bool operator<(const HasPtr &lhs, const HasPtr &rhs);
public:
    explicit HasPtr(const string& s = string()) : ps(new string(s)), i(0) {}
    HasPtr(const HasPtr& hp) : ps(new string(*hp.ps)), i(hp.i) {}
    HasPtr& operator=(HasPtr rhs)
    {
        swap(*this, rhs);
        return *this;
    }
    ~HasPtr() { delete ps; }
    void print(){cout << *ps << endl;} //自己定义的,用于检验结果
private:
    string* ps;
    int i;
};
void swap(HasPtr &lhs, HasPtr &rhs)
{
    using std::swap;
    swap(lhs.ps,rhs.ps);
    swap(lhs.i, rhs.i);
    cout << "运行swap" << endl;
}
bool operator<(const HasPtr &lhs, const HasPtr &rhs)
{
    return *lhs.ps < *rhs.ps;
}
int main(int argc, char *argv[])
{
    vector<HasPtr> vec = {HasPtr("cat"), HasPtr("dog"), HasPtr("apple")};
    sort(vec.begin(),vec.end());
    for(auto i : vec)
        i.print();
    return 0;
}
/*
运行swap
运行swap
apple
cat
dog
*/

13.32

不会,因为类指针的HasPtr的拷贝赋值只是拷贝指针,不涉及动态内存申请释放

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值