C++Primer 第十二章 动态内存

12.1 动态内存和智能指针

12.1.1 shared_ptr类

12.1 在此代码的结尾,b1 和 b2 各包含多少个元素?

StrBlob b1;
{
	StrBlob b2 = {"a", "an", "the"};
	b1 = b2;
	b2.push_back("about");
}
// b1包含4个元素;
// b2被销毁。

12.2 编写你自己的StrBlob 类,包含const 版本的 front 和 back。

#ifndef STRBLOB_H
#define STRBLOB_H

#include <memory>
#include <vector>
#include <string>
#include <initializer_list>

using namespace std;

class StrBlob
{
public:
    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> il);

    size_type size() const {return data -> size();}
    bool empty() const {return data -> empty();}
    void push_back(const string &t) {data -> push_back(t);}

    void pop_back();
    string &front();
    string &back();

    string &front() const;
    string &back() const;

private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string &msg) const;
};

StrBlob::StrBlob() : data(make_shared<vector<string>>()){ }
StrBlob::StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)) {}

void StrBlob::check(size_type i, const string &msg) const
{
    if (i >= data->size())
        throw out_of_range(msg);
}

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

string &StrBlob::front()
{
    check(0, "front on empty StrBlob");
    return data->front();
}

string &StrBlob::back()
{
    check(0, "back on empty StrBlob");
    return data->back();
}

string &StrBlob::front() const
{
    check(0, "front on empty StrBlob");
    return data->front();
}

string &StrBlob::back() const
{
    check(0, "back on empty StrBlob");
    return data->back();
}

#endif

12.3 StrBlob 需要const 版本的push_back 和 pop_back吗?如需要,添加进去。否则,解释为什么不需要。

不需要,添加进去虽然编译器不会报错,但是这样不符合类使用者的使用习惯。

12.4 在我们的 check 函数中,没有检查 i 是否大于0。为什么可以忽略这个检查?

可以忽略,本身i就是大于0的。

12.5 我们未编写接受一个 initializer_list explicit 参数的构造函数。讨论这个设计策略的优点和缺点。

使用explicit之后
优点:我们可以清楚地知道使用的是哪种类型;
缺点:不易使用,需要显示地初始化。

12.1.2 直接内存管理

12.6 编写函数,返回一个动态分配的 int 的vector。将此vector 传递给另一个函数,这个函数读取标准输入,将读入的值保存在 vector 元素中。再将vector传递给另一个函数,打印读入的值。记得在恰当的时刻delete vector。

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

using namespace std;

vector<int> *loc_vec()
{
    return new vector<int>();
}

void read(istream &is, vector<int> *vec)
{
    int i = 0;
    while (is >> i)
        vec->push_back(i);    
}

void print(ostream &os, const vector<int> *vec)
{
    for(auto i : *vec)
        os << i << " ";
    os << endl;
}

int main()
{
    vector<int> *vp = loc_vec();
    read(cin, vp);
    print(cout, vp);
    delete vp;

    return 0;
}

12.7 重做上一题,这次使用 shared_ptr 而不是内置指针。

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

using namespace std;

shared_ptr<vector<int>> loc_vec()
{
    return make_shared<vector<int>>();
}

void read(istream &is, shared_ptr<vector<int>> vec)
{
    int i = 0;
    while (is >> i)
        vec->push_back(i);    
}

void print(ostream &os, shared_ptr<vector<int>> vec)
{
    for(auto i : *vec)
        os << i << " ";
    os << endl;
}

int main()
{
    auto vp = loc_vec();
    read(cin, vp);
    print(cout, vp);

    return 0;
}

12.8 下面的函数是否有错误?如果有,解释错误原因。

bool b() {
	int* p = new int;
	// ...
	return p;
}

返回p是一个指针,而不是一个bool

12.9 解释下面代码执行的结果。

int *q = new int(42), *r = new int(100);
r = q;// r=q后r所指的内存没有释放,应该先delete r,再r=q;
auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;// 第二段代码内存会自动释放。

12.1.3 shared_ptr和new结合使用

12.10 下面的代码调用了第413页中定义的process 函数,解释此调用是否正确。如果不正确,应如何修改?

shared_ptr<int> p(new int(42));
process(shared_ptr<int>(p));
// 正确

12.11 如果我们像下面这样调用 process,会发生什么?

process(shared_ptr<int>(p.get()));
// 离开process时,p指向的内存会被释放,再使用p指针时会出现错误。

12.12 p 和 sp 的定义如下,对于接下来的对 process 的每个调用,如果合法,解释它做了什么,如果不合法,解释错误原因:

auto p = new int();
auto sp = make_shared<int>();
(a) process(sp);
(b) process(new int());
(c) process(p);
(d) process(shared_ptr<int>(p));
// (a)合法,将智能指针赋值给process;
// (b)不合法,shared_ptr初始化内置指针时需要使用直接初始化的形式;
// (c)不合法,shared_ptr初始化内置指针时需要使用直接初始化的形式;
// (d)合法。

12.13 如果执行下面的代码,会发生什么?

auto sp = make_shared<int>();
auto p = sp.get();
delete p;

12.1.4 智能指针和异常

12.14 编写你自己版本的用 shared_ptr 管理 connection 的函数。
12.15 重写第一题的程序,用 lambda 代替end_connection 函数。

#include <iostream>
#include <string>
#include <memory>
#include <algorithm>

using namespace std;

struct destination{};
struct connection{};
connection connect(destination *){};
void disconnect(connection){};

// void end_connection(connection *p) 
// {
//     disconnect(*p);
// }

void f(destination &d)
{
    connection c = connect(&d);
    // shared_ptr<connection> p(&c, end_connection);
    shared_ptr<connection> p(&c, [](connection *p){disconnect(*p);});
}

int main()
{

    return 0;
}

12.1.5 unique_ptr

12.16 如果你试图拷贝或赋值 unique_ptr,编译器并不总是能给出易于理解的错误信息。编写包含这种错误的程序,观察编译器如何诊断这种错误。

#include <iostream>
#include <memory>

using namespace std;

int main()
{
    unique_ptr<int> p(new int(10));
    // unique_ptr<int> p1 = p;
    // unique_ptr<int> p2(p);
    unique_ptr<int> p3(p.release());

    cout << *p3 << endl;

    return 0;
}

12.17 下面的 unique_ptr 声明中,哪些是合法的,哪些可能导致后续的程序错误?解释每个错误的问题在哪里。

int ix = 1024, *pi = &ix, *pi2 = new int(2048);
typedef unique_ptr<int> IntP;
(a) IntP p0(ix);
(b) IntP p1(pi);
(c) IntP p2(pi2);
(d) IntP p3(&ix);
(e) IntP p4(new int(2048));
(f) IntP p5(p2.get());

(a)非法,初始化错误;
(b)编译时合法,运行时会报错,因为pi不是new出来的,销毁时使用默认的delete会出错;
(c)编译时合法,但是运行时会导致空悬指针,unique_ptr释放空间时,使用pi2指针会出错;
(d)编译时合法,运行时会报错,因为指针不是new出来的,销毁时使用默认的delete会出错;
(e)合法;
(f)编译时合法,但是会导致两次delete或者一个delete后另一个变为空悬指针。

12.18 shared_ptr 为什么没有 release 成员?

多个shared_ptr可以指向同一个对象,直接赋值即可,无需release成员。

12.1.6 weak_ptr

12.19 定义你自己版本的 StrBlobPtr,更新 StrBlob 类,加入恰当的 friend 声明以及 begin 和 end 成员。

#ifndef STRBLOB_H
#define STRBLOB_H

#include <memory>
#include <vector>
#include <string>
#include <initializer_list>

using namespace std;

class StrBlobPtr;

class StrBlob
{
public:
    friend class StrBlobPtr;

    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> il);

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void push_back(const string &t) { data->push_back(t); }

    void pop_back();
    string &front();
    string &back();

    string &front() const;
    string &back() const;
    StrBlobPtr begin();
    StrBlobPtr end();

private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string &msg) const;
};

// 对于访问一个不存在元素的尝试,StrBlobPtr抛出一个异常
class StrBlobPtr
{
public:
    StrBlobPtr() : curr(0) {}
    StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
    string &deref() const; // 解引用
    StrBlobPtr &incr();    // 前置递增

private:
    shared_ptr<vector<string>> check(size_t, const string &) const;
    weak_ptr<vector<string>> wptr;
    size_t curr;
};

StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}

void StrBlob::check(size_type i, const string &msg) const
{
    if (i >= data->size())
        throw out_of_range(msg);
}

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

string &StrBlob::front()
{
    check(0, "front on empty StrBlob");
    return data->front();
}

string &StrBlob::back()
{
    check(0, "back on empty StrBlob");
    return data->back();
}

string &StrBlob::front() const
{
    check(0, "front on empty StrBlob");
    return data->front();
}

string &StrBlob::back() const
{
    check(0, "back on empty StrBlob");
    return data->back();
}

shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
    auto ret = wptr.lock();
    if (!ret)
        throw runtime_error("unbound StrBlobStr");
    if (i >= ret->size())
        throw out_of_range(msg);
    return ret;
}

string &StrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];
}

StrBlobPtr &StrBlobPtr::incr()
{
    auto p = check(curr, "increment past end of StrBlobPtr");
    ++curr;
    return *this;
}

StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }
StrBlobPtr StrBlob::end()
{
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

#endif

12.20 编写程序,逐行读入一个输入文件,将内容存入一个 StrBlob 中,用一个 StrBlobPtr 打印出 StrBlob 中的每个元素。

#include <iostream>
#include <string>
#include <fstream>
#include "StrBlob.h"

using namespace std;

int main()
{
    ifstream in("file3");
    StrBlob b1;
    string s;

    while (getline(in, s))
        b1.push_back(s);

	for(StrBlobPtr beg = b1.begin(), ed = b1.end(); beg != ed; beg.incr())
		std::cout << beg.deref() << std::endl;

    return 0;
}

12.21 也可以这样编写 StrBlobPtr 的 deref 成员:

std::string& deref() const {
	return (*check(curr, "dereference past end"))[curr];
}

你认为哪个版本更好?为什么?

之前的版本更好,更容易读懂。

12.22 为了能让 StrBlobPtr 使用 const StrBlob,你觉得应该如何修改?定义一个名为ConstStrBlobPtr 的类,使其能够指向 const StrBlob。

#ifndef STRBLOB_H
#define STRBLOB_H

#include <memory>
#include <vector>
#include <string>
#include <initializer_list>

using namespace std;

class StrBlobPtr;

class StrBlob
{
public:
    friend class StrBlobPtr;

    typedef vector<string>::size_type size_type;
    StrBlob();
    StrBlob(initializer_list<string> il);

    size_type size() const { return data->size(); }
    bool empty() const { return data->empty(); }
    void push_back(const string &t) { data->push_back(t); }

    void pop_back();
    string &front();
    string &back();

    string &front() const;
    string &back() const;
    StrBlobPtr begin();
    StrBlobPtr end();

private:
    shared_ptr<vector<string>> data;
    void check(size_type i, const string &msg) const;
};

// 对于访问一个不存在元素的尝试,StrBlobPtr抛出一个异常
class StrBlobPtr
{
public:
    StrBlobPtr() : curr(0) {}
    StrBlobPtr(const StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) {}
    string &deref() const; // 解引用
    StrBlobPtr &incr();    // 前置递增

    bool operator!=(const StrBlobPtr& p) { return p.curr != curr; }

private:
    shared_ptr<vector<string>> check(size_t, const string &) const;
    weak_ptr<vector<string>> wptr;
    size_t curr;
};

StrBlob::StrBlob() : data(make_shared<vector<string>>()) {}
StrBlob::StrBlob(initializer_list<string> il) : data(make_shared<vector<string>>(il)) {}

void StrBlob::check(size_type i, const string &msg) const
{
    if (i >= data->size())
        throw out_of_range(msg);
}

void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}

string &StrBlob::front()
{
    check(0, "front on empty StrBlob");
    return data->front();
}

string &StrBlob::back()
{
    check(0, "back on empty StrBlob");
    return data->back();
}

string &StrBlob::front() const
{
    check(0, "front on empty StrBlob");
    return data->front();
}

string &StrBlob::back() const
{
    check(0, "back on empty StrBlob");
    return data->back();
}

shared_ptr<vector<string>> StrBlobPtr::check(size_t i, const string &msg) const
{
    auto ret = wptr.lock();
    if (!ret)
        throw runtime_error("unbound StrBlobStr");
    if (i >= ret->size())
        throw out_of_range(msg);
    return ret;
}

string& StrBlobPtr::deref() const
{
    auto p = check(curr, "dereference past end");
    return (*p)[curr];
}

StrBlobPtr& StrBlobPtr::incr()
{
    auto p = check(curr, "increment past end of StrBlobPtr");
    ++curr;
    return *this;
}

StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }
StrBlobPtr StrBlob::end()
{
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

#endif

12.2 动态数组

12.2.1 new和数组

12.23 编写一个程序,连接两个字符串字面常量,将结果保存在一个动态分配的char数组中。重写这个程序,连接两个标准库string对象。

#include <iostream>
#include <string>
#include <cstring>

using namespace std;

int main()
{
    // const char* a = "aaa";
    // const char* b = "bbb";
    // char* cp = new char[strlen(a)+strlen(b)];

    // strcat(cp, a);
    // strcat(cp, b);

    // cout << string(cp) << endl;

    // delete [] cp;

    string s1, s2;
    cin >> s1 >> s2;
    string* cp = new string;

    *cp = s1 + s2;

    cout << *cp << endl;

    delete cp;

    return 0;
}

12.24 编写一个程序,从标准输入读取一个字符串,存入一个动态分配的字符数组中。描述你的程序如何处理变长输入。测试你的程序,输入一个超出你分配的数组长度的字符串。

#include <iostream>
using namespace std;

int main()
{
    string s;
    cin >> s;
    size_t len = s.size();
    char* cp = new char[len];

    for (size_t i = 0; i < len; ++i)
    {
        cp[i] = s[i];
        cout << cp[i] << endl;
    }

    delete [] cp;

    return 0;
}

12.25 给定下面的new表达式,你应该如何释放pa?

int *pa = new int[10];
delete [] pa;

12.2.2 allocator类

12.26 用 allocator 重写第427页中的程序。

#include <iostream>
#include <string>
#include <memory>

using namespace std;

int main()
{
    allocator<string> alloc;
    string s;
    int n; cin >> n;
    auto const p = alloc.allocate(n);
    auto q = p;

    cout << "请输入字符: " << endl;
    while (cin >> s && q != p+n)
        alloc.construct(q++, s);

    while (q != p)
    {
        cout << *(--q) << endl;
        alloc.destroy(q);
    }

    alloc.deallocate(p, n);

    return 0;
}

12.3 使用标准库:文本查询程序

12.3.1 文本查询程序设计

12.27 TextQuery 和 QueryResult 类只使用了我们已经介绍过的语言和标准库特性。不要提前看后续章节内容,只用已经学到的知识对这两个类编写你自己的版本。
12.28 编写程序实现文本查询,不要定义类来管理数据。你的程序应该接受一个文件,并与用户交互来查询单词。使用vector、map 和 set 容器来保存来自文件的数据并生成查询结果。

#include <string>
#include <vector>
#include <map>
#include <fstream>
#include <sstream>
#include <set>
#include <memory>
#include <iostream>
#include <algorithm>
#include <iterator>

using namespace std;
class QueryResult;

class TextQuery
{
public:
	using line_no = vector<string>::size_type;
	TextQuery(ifstream&);
	QueryResult query(const string&) const;
private:
	shared_ptr<vector<string>> file;
	map<string, shared_ptr<set<line_no>>> wm;
};

class QueryResult
{
    friend std::ostream& print(std::ostream&, const QueryResult&);
public:
    QueryResult(string s, shared_ptr<set<TextQuery::line_no>> p, shared_ptr<vector<string>> f) : sought(s), lines(p), file(f) {}
private:
    string sought;
    shared_ptr<set<TextQuery::line_no>> lines;
    shared_ptr<vector<string>> file;
};

TextQuery::TextQuery(ifstream& ifs) : file(new vector<string>)
{
    string text;
    while (getline(ifs, text))
    {
        file->push_back(text);
        int n = file->size();
        istringstream line(text);
        string word;
        while (line >> word)
        {
            auto &lines = wm[word];
            if (!lines)
                lines.reset(new set<line_no>);
            lines->insert(n);
        }
    }
    // for (auto &m : wm)
    // {
    //     cout << m.first << " : ";
    //     for (auto l : *m.second)
    //         cout << l << " ";
    //     cout << endl;
    // }       
}

QueryResult TextQuery::query(const string& sought) const
{
    shared_ptr<set<TextQuery::line_no>> nodate(new set<TextQuery::line_no>);
    auto loc = wm.find(sought);
    if (loc == wm.end())
        return QueryResult(sought, nodate, file);
    else
        return QueryResult(sought, loc->second, file);
}

std::ostream &print(std::ostream &os, const QueryResult &qr)
{
	os << qr.sought << " occurs " << qr.lines->size() << " " /*<< make_plural(qr.lines->size(), "time", "s")*/ << std::endl;
	for(auto num : *qr.lines)
		os << "\t(line " << num << ") " << std::endl;
	return os;
}

int main()
{
    ifstream in("file1");
    TextQuery tq(in);
    while (true)
    {
       cout << "enter word to look for, or q to quite: ";
       string s; 
       if (!(cin >> s) || s == "q") break;
       print(cout, tq.query(s));
    }

    return 0;
}

12.29 我们曾经用do while 循环来编写管理用户交互的循环。用do while 重写本节程序,解释你倾向于哪个版本,为什么?

    do
    {
       cout << "enter word to look for, or q to quite: ";
       string s; 
       if (!(cin >> s) || s == "q") break;
       print(cout, tq.query(s));
    }while (true);  

while 版本更加简洁明了

12.3.2 文本查询程序类的定义

12.30 定义你自己版本的 TextQuery 和 QueryResult 类,并执行12.3.1节中的runQueries 函数。

答案同12.28;

12.31 如果用vector 代替 set 保存行号,会有什么差别?哪个方法更好?为什么?

如果用 vector 则会有单词重复的情况出现。而这里保存的是行号,不需要重复元素,所以 set 更好。

12.32 重写 TextQuery 和 QueryResult类,用StrBlob 代替 vector 保存输入文件。

TextQuery 和 QueryResult 类中的 file 成员,改为 指向 StrBlob 的智能指针。在访问 StrBlob 时,要使用 StrBlobPtr。

12.33 在第15章中我们将扩展查询系统,在 QueryResult 类中将会需要一些额外的成员。添加名为 begin 和 end 的成员,返回一个迭代器,指向一个给定查询返回的行号的 set 中的位置。再添加一个名为 get_file 的成员,返回一个 shared_ptr,指向 QueryResult 对象中的文件。

    using iter = set<TextQuery::line_no>::iterator;
    iter begin() const {return lines->begin();}
    iter end() const {return lines->end();}
    shared_ptr<vector<string>> get_file() {return make_shared<vector<string>>(file);}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值