c++Primer读书笔记(第十二章 动态内存)

12.1 动态内存与智能指针

头文件

12.1.1 shared_ptr

c++Primer读书笔记(第十二章 动态内存)_第1张图片
c++Primer读书笔记(第十二章 动态内存)_第2张图片
make_shared函数

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。

auto p = make_shared<int>(42);
auto q(p);

使用了动态生存期的资源的类

使用动态内存出于以下三种原因:

  1. 程序不知道自己需要使用多少对象
  2. 程序不知道所需对象的准确类型
  3. 程序需要在多个对象间共享数据
class StrBlob{
public:
    typedef std::vector<std::string>::size_type size_type;
    //构造函数
    StrBlob();
    StrBlob(std::initializer_list<std::string>il);
    //
    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 pop_back();

    std::string& front();
    std::string& back();

private:
    std::shared_ptr<std::vector<std::string>>data;
    void check(size_type i,const std::string &msg) const
    {
        if(i>data->size())
            throw out_of_range(msg);
    }
};

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

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

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

12.1.2 直接管理内存

int *pi=new int;
int *pi=new int(1024);
auto p1=new auto(obj);

动态分配的const对象

const int *pci = new const int(1024);
//动态分配的const对象必须初始化

内存耗尽

int *p1=new int;//如果分配失败,new会抛出一个std::bad_alloc
int *p2=new (nothrow) int;//如果分配失败,new返回一个空指针

带参数的new成为定位new

delete必须和new一一对应

使用动态内存的管理非常容易出错

  1. 忘记delete内存(内存泄漏)
  2. 使用已经释放掉的对象。通过在释放内存后将指针置为空,可以检测出这种错误
  3. 同一块内存释放两次

delete之后重置指针值

delete指针之后指针仍然保存了动态内存的地址,成为空悬指针。
delete指针之后记得置为nullptr

shared_ptr和new结合使用

接收指针参数的构造函数为explicit,所以不能进行隐式转换,必须直接初始化形式来初始化一个智能指针

shared_ptr<int>p2(new int(42));//p2指向一个值为42的int
shared_ptr<int>p2=new int(42);//错误:必须使用直接初始化形式

c++Primer读书笔记(第十二章 动态内存)_第3张图片
c++Primer读书笔记(第十二章 动态内存)_第4张图片
不要混合使用普通指针和智能指针

普通指针指向的对象交由智能指针后,智能指针一旦将对象释放,那么普通指针便变为了空悬指针

也不要使用get初始化另一个智能指针或者为智能指针赋值

c++Primer读书笔记(第十二章 动态内存)_第5张图片

其他shared_ptr操作

c++Primer读书笔记(第十二章 动态内存)_第6张图片

12.1.4 智能指针和异常

如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要的时候将其释放

智能指针和哑类

没有自己的析构函数,使用shared_ptr管理

使用我们自己的释放操作

默认情况下,shared_ptr假定它们指向的是动态内存。因此当一个shared_ptr被销毁的时候,他默认地对它管理的指针进行delete操作。
有时候我们希望调用自己定义的函数来代替delete。

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

void f(destination &d)
{
	connection c=connect(&d);
	shared_ptr<connection>p(&c,end_connection);
	//一旦发生异常交由智能指针自己调用end_connection进行释放
}

智能指针的一些陷阱

  1. 不适用相同的内置指针初始化或者reset多个智能指针
  2. 不delete get()返回的指针
  3. 不适用get()初始化或者reset另一个指针指针,和1类似
  4. 如果你使用了get()返回的指针,当智能指针计数为0销毁后,你使用的指针就变为空悬指针了
  5. 如果你使用智能指针管理的资源不是new分配的内存,那么记得自己定义删除器

12.1.5 unique_ptr

初始化unique_ptr必须采用直接初始化形式
不支持普通的拷贝或赋值操作

unique_ptr<double>p1;
unique_ptr<int>p2(new int(42));

unique_ptr<string>p2(p1);
unique_ptr<string>p3;
p3=p2;

c++Primer读书笔记(第十二章 动态内存)_第7张图片

传递unique_ptr参数和返回unique_ptr

我们可以拷贝或赋值一个将要被销毁的unique_ptr

c++Primer读书笔记(第十二章 动态内存)_第8张图片
向unique_ptr传递删除器

12.1.6 weak_ptr

弱智能指针 讲一个weak_ptr绑到一个shared_ptr不会改变其引用计数

c++Primer读书笔记(第十二章 动态内存)_第9张图片

12.2动态数组

12.2.1 new和数组

int *p=new int[get_size()];
typedef int arrT[42];
int *p = new arrT;

初始化动态分配的对象的数组

int *pia=new int[10];//10个未初始化的int
int *pia2=new int[10]();//10个0
string *psa=new string[10];//10个空的string
string *pas2=new string[10]();//10个空的string

可以用花括号进行初始化列表,缺补默认值 多报异常

动态分配一个空数组是合法的

char arr[0];//不合法
char *cp = new char[0];//合法

释放动态数组

delete p;
delete [] pa;

智能指针和动态数组

unique_ptr<int[]>up(new int[10]);
up.release();//自动调用delete[]

管理动态数组的unique_ptr不能用.或者->,直接用下标访问

shared_ptr管理动态数组的话必须提供一个删除器,调用delete[]

shared_ptr<int>sp(new int[10],[](int *p){delete [] p;})
sp.reset();

12.2.2 allocator 427

c++Primer读书笔记(第十二章 动态内存)_第10张图片
c++Primer读书笔记(第十二章 动态内存)_第11张图片

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

需要完成的任务

  1. 当程序读取输入文件的时候,记住单词出现的行,需要逐行读取输入文件,并将每一行分解为独立的单词
  2. 输出:必须能提取每个单词关联的行号、行号必须按升序出现而且没有重复、他必须能打印给定行号中的文本

实现的工具

  1. 使用一个vector< string >来保存文件的一行,下标就是行号
  2. 使用一个istringstream来将每一行分解为单词
  3. 使用一个set保存每一个单词在输入文本中出现的行号,保证对于每一个单词同一个行号只会出现一次并且按升序排列(set是有序的无重复集合)
  4. 使用map存储每一个单词和对应的行号set集合,方便通过任意一个单词提取它的行数set集合

在类之间共享数据

  1. QueryResult类对象用来保存TextQuery输出的结果,那么如果不进行特殊处理,则需要进行拷贝set甚至是vector,而我们不希望进行拷贝操作。
  2. 所以我们可以返回指向TextQuery内部对象的迭代器或者指针,这样可以避免拷贝的出现
  3. 新的问题出现:如何直到指向的对象是否存活?如果已经提前销毁,那么QueryResult就会对一个已经不存在的对象进行操作
  4. 对于生存周期进行监视这一操作已经暗示了答案,我们可以使用shared_ptr作为对象,反应数据结构中这种共享关系
  • main.cpp
  1. ostream对象的用法
  2. !(std::cin>>s)遇到文件尾部返回false
  3. ifstream读入文件用法
#include "TextQuery.hpp"
#include "QueryResult.hpp"
#include 
#include 
using namespace std;

/*
 * @function make_plural 根据输入的个数返回相应的string
 *
 * @param ctr 需要判断的个数
 * @param word “word”
 * @param word “s”
 *
 * */

string make_plural(size_t ctr,const string &word,const string &ending)
{
    return (ctr > 1)? word+ending : word;
}

/*
 * @function print 打印输出的结果
 * 
 * @param os 输出流对象
 * @param qr 包含结果的QueryResult类对象
 * */


ostream &print(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 + 1 <<")"<< *(qr.file->begin() + num)<<endl;
    return os;
}

/*
 * @function runQueries 处理文件与用户的输入并输出结果
 *
 * @param infile 需要处理的文件
 * */

void runQueries(ifstream &infile)
{
    //infile是需要处理的文件
    TextQuery tq(infile);//保存文件并且建立查询set
    //与用户交互:提示用户输入要查询的单词,完成拆查询并打印结果
    while(true)
    {
        std::cout<<"enter word to look for , or q to quit:";
        std::string s;
        //遇到文件尾部或者用户输入q时循环中止
        if(!(std::cin>>s) || s == "q")break;
        print(std::cout,tq.query(s))<<endl;
    }
}


int main()
{
    ifstream test;
    test.open("./test.txt");
    runQueries(test);
    return 0;
}
  • 文本查询类程序TextQuery
    TextQuery.hpp
#ifndef TEXTQUERY_HPP
#define TEXTQUERY_HPP
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "QueryResult.hpp"
/*
 * @class TextQuery
 * @brief 处理输入的文本文件,得到结果放入QueryResult类对象中
 *
 * @param fiel 保存读入的文件
 * @param wn 保存单词和对应的行号set
 * 
 * @function query 接受string,返回结果
 * */
class QueryResult;//返回类型,需要先定义
class TextQuery
{
public:
    using line_no=std::vector<std::string>::size_type;//便于阅读,取别名
    TextQuery(std::ifstream&);//构造函数
    QueryResult query(const std::string&)const;//接受string,返回结果
private:
    std::shared_ptr<std::vector<std::string>>file;//保存读如的文件
    std::map<std::string,std::shared_ptr<std::set<line_no>>>wn;//保存单词和对应的行号set
};

#endif

TextQuery.cpo

  1. istringstream的使用
  2. map下标操作返回对应的元素
  3. 设置一个静态的set指针 ,用来给每次没有找到sought用作返回值
  4. 当map用来查找元素的时候使用find而不是下标,避免将不存在的关键字插入map中
#include "TextQuery.hpp"
#include 

TextQuery::TextQuery(std::ifstream &is):file(new std::vector<std::string>)
{
    std::string text;//暂存一行
    while(getline(is,text))//循环获取一行
    {
        file->push_back(text);//将一行加入file中
        int n=file->size()-1;//获取当前行的行号
        std::istringstream line(text);//创建istringstream对象,将text分解为单词
        std::string word;//暂存单词
        while(line>>word)//读取单词
        {
            auto &lines = wn[word];//map返回一个shared_ptr>
            if(!lines)//如果为空,说明单词第一次出现,创建一个新的set
                lines.reset(new std::set<line_no>);
            lines->insert(n);//将当前行号插入set
        }
    }
}


QueryResult TextQuery::query(const std::string &sought)const
{
    //如果没有找到sought,我们将返回一个指向此set的指针
    static std::shared_ptr<std::set<line_no>>nodata(new std::set<line_no>);
    
    //使用find而不是下标运算符来查找查找单词,避免将单词添加到wm中
    auto loc=wn.find(sought);
    if(loc == wn.end())
        return QueryResult(sought,nodata,file);
    else
        return QueryResult(sought,loc->second,file);
}
  • QueryResult
#ifndef QUERYRESULT_HPP
#define QUERYRESULT_HPP
#include 
#include 
#include 
#include 

/*
 * @class QueryResult 接受TextQuery类中query函数返回的结果
 *
 * @param sought 需要查询的单词
 * @param lines 单词出现的行号集合set
 * @param file 输入的文件
 * */


class QueryResult
{
    friend std::ostream& print(std::ostream &os, const QueryResult&qr);
    using line_no = std::vector<std::string>::size_type;
public:
     QueryResult(std::string s,std::shared_ptr<std::set<line_no>> p,std::shared_ptr<std::vector<std::string>>f):sought(s),lines(p),file(f){}
private:
    std::string sought;//查询单词
    std::shared_ptr<std::set<line_no>>lines;//出现的行号
    std::shared_ptr<std::vector<std::string>>file;//输入文件
};

#endif

你可能感兴趣的:(c++,primer,c++)