C++ primer学习笔记——第十二章 动态内存

本文探讨了智能指针(shared_ptr、unique_ptr、weak_ptr)在C++中的使用,详细介绍了它们如何管理动态内存,包括对象生命周期、异常安全性和资源释放。同时,文章深入分析了动态数组的分配与释放,以及标准库allocator类的作用。
摘要由CSDN通过智能技术生成

全局对象在程序启动时分配,在程序结束时销毁;

局部自动对象在定义所在的程序块时被创建,在离开块时被销毁;

局部static对象在第一次使用前分配,在程序结束时销毁

动态分配的对象的生存期与它们在哪里创建是无关的,只有当显式地释放时,这些对象才会销毁

 

静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。——在使用前分配,在程序结束时销毁

栈内存用来保存定义在函数之内的非static对象。——仅在其定义的程序块运行时才存在

(或自由空间)用来存储动态分配的对象——即那些在程序运行时分配的对象

一、动态内存与智能指针

通过new在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象进行初始化。

delete接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

动态内存的使用很容器出问题,因此新标准库提供了两种智能指针类型来管理动态对象。智能指针的行为类似于常规指针,重要的区别是它负责自动释放所指向的对象。

shared_ptr允许多个指针指向同一对象;unique_ptr则独占多指向的对象;另外还有一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。这三种类型都定义在memory头文件中

类似于vector,智能指针也是模板,当我们创建一个智能指针时,必须提供额外的信息——指针可以指向的类型。

shared_ptr和unique_ptr都支持的操作
shared_ptr<T> sp空智能指针,可以指向类型为T的对象
unique_ptr<T> up
p将p用作一个条件判断
*p解引用p,获得它指向的对象
p->mem等价于(*p).mem
p.get()返回p中保存的指针
swap(p,q)交换p和q中的指针
p.swap(q)
shared_ptr独有的操作
make_shared<T>(args)返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象
shared_ptr<T>p(q)p是shared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换为T*
p=q此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放
p.unique()若p.use_count为1,返回true;否则返回false
p.use_count()返回与p共享对象的智能指针数量;可能很慢,主要用于调试

make_shared函数

此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr:

 //指向一个值为42的int的shared_ptr
shared_ptr<int> p3=make_shared<int>(42); 
//p4指向一个值为“9999999999”的string
shared_ptr<string> p4=make_shared<string>(10,'9');
//p5指向一个值初始化的int,即,值为0
shared_ptr<int> p5=make_shared<int>();

使用auto定义一个对象来保存make_shared的结果比较简单:

//p6指向一个动态分配的空vector<vector<string>>
auto p6=make_shared<vector<string>>();

shared_ptr的拷贝和赋值

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:

auto p=make_shared<int>(42);
auto q(p);       //p和q指向相同对象,此对象有两个引用者

每个shared_ptr都有一个关联的计数器,称为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增(初始化、函数从餐、函数返回值)。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁时,计数器就会递减。

一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象:

auto r=make_shared<int>(42);
r=q;   //给r赋值,令它指向另一个地址
       //递增q指向的对象的引用计数
       //递减r原来指向的对象的引用计数
       //r原来指向的对象已没有引用者,会自动释放

shared_ptr自动销毁所管理的对象

析构函数负责完成销毁工作,一般用来释放对象所分配的资源。类似于构造函数,每一个类都有一个析构函数

shared_ptr的析构函数会递减它所指向的对象的计数引用,如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

shared_ptr还会自动释放相关联的内存

当动态对象不再使用时,shared_ptr类会自动释放动态内存,这一特性使得动态内存的使用变得非常容易。(根据计数引用是否为0)

对于一块内存,shared_ptr类保证只要有任何shared_ptr对象引用它,它就不会被释放掉。

如果将shared_ptr存放于一个容器中,而后不再需要全部元素,而只能使用其中一部分,要记得用erase删除不再需要的那些元素

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

程序使用动态内存出于以下三种原因之一:

1、程序不知道自己需要使用多少对象(典型例子:容器)

2、程序不知道所需对象的准确类型

3、程序需要在多个对象间共享数据

到目前为止,我们使用的类中,分配的资源都与对应对象生存期一致。当我们拷贝一个vector时,原vector和副本vector中的元素是相互分离的:

vector<string> v1;
{//新作用域
    vector<string> v2={"a","an","the"};
    v1=v2; //从v2拷贝元素到v1
} //v2被销毁,其中的元素也被销毁
//v1依然有三个元素

一般而言,如果两个对象共享底层的数据,当某个对象被销毁时,我们不能单方面的销毁底层数据:

Blob<string> b1;
{//新作用域
    Blob<string> b2={"a","an","the"};
    b1=b2; //从b2拷贝元素到b1
} //b2被销毁,但b2中的元素不能销毁
//b1指向最初由b2创建的元素

使用动态内存的一个常见原因就是允许多个对象共享相同的状态

定义StrBlob类

为了实现数据共享,我们为每个strBlob设置一个shared_ptr来管理动态分配的vector。


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; }
	void pop_back();
	//元素访问
	std::string &front();
	std::string &back();

private:
	std::shared_ptr<std::vector<std::string>> data;
//如果data[i]不合法,抛出一个异常
	void check(size_type i, const std::string &msg) const;
};

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

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();
}

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

StrBlob构造函数

默认构造函数分配一个空vector;接受一个initializer_list的构造函数将其参数传递给对应的vector构造函数,此构造函数通过拷贝列表中的值来初始化vector的元素: 

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

StrBlob的拷贝、赋值和销毁

StrBlob使用默认版本的拷贝、赋值和销毁成员函数来对此类型的对象进行这些操作。默认情况下,这些操作拷贝、赋值和销毁类的数据成员。 

2、直接管理内存

我们使用运算符new分配内存,delete释放new分配的内存。相对于智能指针,使用这两个运算符管理内存非常容易出错。而且,自己直接管理内存的类与使用智能指针的类不同,他们不能依赖类拷贝、赋值和销毁操作的任何默认定义。

使用new动态分配和初始化对象

在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针;

new表达式在自由空间构造一个对象,并返回指向该对象的指针

int *p=new int;   //pi指向一个动态分配的、未初始化的无名对象

默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值是未定义的,而类类型对象将默认构造函数进行初始化:

string *ps=new string;   //初始化为空的string
int *pi=new int;         //pi指向一个未初始化的int

我们可以使用直接初始化来初始化一个动态分配的对象:

int *pi=new int(1024);
string *ps=new string(10,'9');
vector<int> *pv=new vector<int> {0,1,2,3,4,5,6,7,8,9};

也可以对动态分配的对象进行值初始化,只需要在类型名之后跟一对空括号即可:

string *ps1=new string;    //默认初始化为空string
string *ps=new string();   //值初始化为空string
int *pi1=new int;          //默认初始化;*pi1的值未定义
int *pi2=new int();        //值初始化为0,*pi2为0

对于内置类型,注意值初始化和默认初始化的区别:

1.对于定义了自己的构造函数的类类型(如string),要求值初始化是没有意义的:不管采用什么形式,对象都会通过默认构造函数来初始化;

2.对于内置类型,值初始化的内置类型对象有着良好定义的值,而默认初始化的对象的值则是未定义的。

3.对于类中那些依赖于编译器合成的默认构造函数的内置类型对象,如果它们未在类内初始化,它们的值也是未定义的

如果我们提供了一个括号包围的初始化器,就可以使用auto,此时初始化器可以推断我们想要分配的对象的类型,只有当括号中仅有单一初始化器才能使用auto:

auto p1=new auto(obj);
auto p2=new auto{a,b,c};   //错误:括号中只能有单个初始化器

动态分配的const对象

动态分配的const对象必须进行初始化。对于定义了默认构造函数的类类型,其const动态对象可以隐式初始化,而其他类型的对象就必须显式初始化:

const int *pci=new const int(1024);   //显式初始化
const string *pcs=new const string;   //默认初始化

内存耗尽

默认情况下,如果new不能分配所要求的内存空间,会抛出一个类型为bad_alloc的异常。我们也可以改变使用new的方式来阻止它抛出异常。bad_alloc和nothrow都定义在头文件new中:

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

释放动态内存

delete表达式接受一个指针,指向我们想要释放的对象:

delete p;

指针值和delete

传递给delete的指针必须指向动态分配的内存,或者是一个空指针。释放一块并非new分配的内存,或者将相同的指针值释放多次,其行为是未定义的

虽然一个const对象的值不能被改变,但它本身是可以被销毁的:

动态对象的生存期直到被释放时为止

当使用动态内存时,必须记得释放内存:

void use_factory(T arg)
{ 
    Foo *p=factory(arg);
    //使用p
    delete p; //现在记得释放内存,我们已经不需要它了
}


Foo *use_factory(T arg)
{
   Foo *p=factory(arg);
   //使用p
   return p;   //调用者必须释放内存
}

使用new和delete管理动态内存存在的三个常见问题:

1.忘记delete内存——内存泄露问题

2. 使用已经释放掉的对象。

3.同一块内存释放两次。

坚持只使用智能指针,就可以避免所有这些问题

3、shared_ptr和new结合使用

我们可以用new返回的指针来初始化智能指针。因为接受指针参数的智能指针构造函数是explicit的,因此我们不能将一个内置指针隐式转换成一个智能指针,必须使用直接初始化形式

shared_ptr<int> p1=new int(1024);    //错误:必须使用直接初始化形式
shared_ptr<int> p2(new int(1024));   //正确:使用了直接初始化形式

同理,一个返回shared_ptr的函数不能在其返回语句中隐式转换成一个普通指针:

shared_ptr<int> clone(int p){
    return new int(p);  //错误:隐式转换为shared_ptr<int>
}

shared_ptr<int> clone(int p){
    return shared_ptr<int>(new int(p));  //正确:显式地用int*创建shared_ptr<int>
}

默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放它所关联的对象。

定义和改变shared_ptr的其他方法
shared_ptr<T> p(q)p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换成T*类型
shared_ptr<T> p(u)p从unique_ptr u那里接管了对象的所有权;将u置为空
shared_ptr<T> p(q,d)p接管了内置指针q所指向的对象的所有权。p将使用可调用对象d来代替delete
shared_ptr<T> p(p2,d)p是shared_ptr p2的拷贝,唯一的区别是p将调用可调用对象d来代替delete
p.reset()若p是唯一指向其对象的shared_ptr,reset会释放此对象;若传递了可选的参数内置指针q,会令p指向q,否则会将p置空。若还传递了参数d,将会调用d而不是delete来释放q。
p.reset(q)
p.reset(q,d)

不要混合使用普通指针和智能指针

当将一个shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr。一旦这么做了,我们就不应该再使用内置指针来访问shared_ptr所指向的内存了。

int *x(new int(24));  //危险:x是一个普通指针,而不是一个智能指针
process(x);           //错误:不能将int*转换成一个shared_ptr<int>
process(shared_ptr<int>(x));   //临时shared_ptr,合法的,但内存会被释放,引用计数变为0
int j=*x;   //未定义的:x是一个空悬指针

使用一个内置指针来访问一个智能指针所负责的对象是很危险的,因为我们无法知道对象何时会被销毁。内置指针很可能成为空悬指针

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

get返回一个内置指针,指向智能指针所管理的对象。get用来将指针的访问权限传递给代码,你只有在确定代码不会delete指针的情况下,才能使用get。特别是,永远不要用get初始化另一个智能指针或者为另一个智能指针赋值。

4、智能指针和异常

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

void f()
{
    shared_ptr<int> sp(new int(42));  //分配一个新对象
    //抛出一个异常,却未被捕获
}//在函数结束时shared_ptr自动释放内存

而在下面的例子中,内存不会被释放:

void f(){
    int *ip=new int(42);
    //抛出异常,且未被捕获
    delete ip;
} //内存永远不会被释放了

智能指针与哑类,使用自己的释放操作

某些类没有定义析构函数,此时我们可以使用shared_ptr来保证该类生成的对象的内存被正确释放,首先定义一个函数(删除器)来代替得delete:

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

void f(destination &d/*其他参数*/){
    connection c=connect(&d);
    shared_ptr<connection> p(&c,end_connection);
    //使用链接
    //当f退出时(即使是由于异常而退出),connection会被正确关闭
}

智能指针陷阱:

为了正确使用智能指针,我们必须坚持一些基本规范:

  • 不能使用相同的内置指针初始化(或reset)多个智能指针
  • 不delete get()返回的指针
  • 不使用get()初始化或reset另一个智能指针
  • 如果你get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
  • 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器

5、unique_ptr

在某个时刻,只能有一个unique_ptr指向一个给定对象,unique_ptr被销毁时,它所指向的对象也被销毁。

与shared_ptr不同,没有类似make_shared的标准函数返回一个unique_ptr。因此,当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。类似于shared_ptr,初始化unique_ptr必须采用直接初始化方式:

unique_ptr<double> p1;    //可以指向一个double的unique_ptr
unique_ptr<int> p2(new int(42));     //p2指向一个值为42的int

由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作。不过有一个列外:我们可以拷贝或赋值一个将要被销毁的unique_ptr。最常见的例子是从函数返回一个unique_ptr。

unique_ptr操作
unique_ptr<T> u1 
unique_ptr<T,D> u2 
unique_ptr<T,D> u(d)空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u=nullptr 
u.release()返回u当前保存的指针并将其置为空
u.reset()释放u指向的指针
u.reset(q)如果提供了内置指针q,令u指向这个对象,原来的内存被释放
u.reset(nullptr)将u置空

虽然我们不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique_ptr:

//将所有权从p1转移给p2
unique_ptr<string> p2(p1.release());  //release将p1置为空
unique_ptr<string> p3(new string("Trex"));  
//将所有权从p3转移给p2
p2.reset(p3.release());   //reset释放了p2原来指向的内存

如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放:

p2.release();        //错误:p2不会释放内存,而且我们丢失了指针
auto p=p2.release();  //正确:但我们必须记得delete(p)

向unique_ptr传递删除器

重载一个unique_ptr中的删除器会影响到unique_ptr类型以及如何构造(或reset)该类型的对象。我们必须在尖括号中unique_ptr指向类型之后提供删除器类型。在创建或reset一个这种unique_ptr类型的对象时,必须提供一个指定类型的可调用对象(删除器):

void f(destination &d/*其他参数*/)
{
    connection c=connect(&d);  //打开连接
    unique_ptr<connection,decltype(end_connection)*>
               p(&c,end_connection);
    //使用连接
    //当f退出时(即使是由于异常而退出),connection会被正确关闭
}

6、weak_ptr

weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。讲一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。

weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少. 

weak_ptr
weak_ptr<T>  w 
weak_ptr<T> w(sp) 
w=p 
w.reset() 
w.use_count 
w.expired() 
w.lock() 

当创建一个weak_ptr时,要用一个shared_ptr来初始化它:

auto p=make_shared<int>(42);
weak_ptr<int> wp(p);   

由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否存在。

if(shared_ptr<int> np=wp.lock()){//如果np不为空则条件成立
     //在if中,np与p共享对象

}

 

二、动态数组

大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更为简单、更不容易出现内存管理错误并且可能有更好的性能。

使用容器的类可以使用默认版本的拷贝、赋值和析构操作。分配动态数组的类则必须定义自己版本的操作,在拷贝、复制以及销毁对象时管理所关联的内存。

3、new和数组

使用new分配一个动态数组,必须在类型名之后跟一对方括号,在其中指明要分配的对象的数目。方括号中的大小必须是整数,但不必是常量。分配一个数组会返回指向第一个对象的指针:

int *p=new int[42]; //p指向第一个int

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

不加括号——默认初始化

大小之后加一对空括号——值初始化

大小之后跟一个花括号列表——初始化器初始化

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

虽然我们不能创建一个大小为0的静态数组对象,但当n等于0时,调用new[n]是合法的:

char arr[0];            //错误:不能定义长度为0的数组
char *cp=new char[0];   //正确:但cp不能解引用

当我们用new分配一个大小为0的数组时,new返回一个合法的非空指针。但此指针不能解引用——毕竟它不指向任何元素。

释放动态数组

使用特殊的delete来释放动态数组——在指针前加上一个空方括号对(方括号必须加上):

delete []pa;

智能指针和动态数组

标准库提供了一个可以管理new分配的数组的unique_ptr版本。使用unique_ptr管理动态数组时,我们必须在对象类型后面跟一对方括号:

unique_ptr<int[]> up(new int[10]);
up.release();   //自动用delete[]销毁其指针

另外一方面,当一个unique_tr指向一个数组时,我们可以使用下标运算符来访问数组中的元素:

for(size_t i=0;i!=10;++i)
    up[i]=i;  //为每个元素赋予一个新值
指向数组的unique_ptr

指向数组的unique_ptr不支持成员访问运算符(点和箭头运算符)

其他unique_ptr操作不变

unique_ptr<T[]> u 
unique_ptr<T[]> u(p)p为内置指针
u[i] 

与unique_ptr不同,shared_ptr不直接支持管理动态数组。如果我们希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器:

shared_ptr<int> sp(new int[10],[](int *p){delete [] p;});
sp.reset();   //使用我们提供的lambda释放数组,它使用delete[]

shared_ptr未定义下标运算符,而且智能指针类型不支持指针算数运算。因此,为了访问数组中的元素,必须用get获取一个内置指针,然后用它来访问数组元素:

for(size_t i=0;i!=10;++i)
    *(sp.get()+i)=i;  //使用get获取一个内置指针

2、allocator类

标准库allocator类定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。

标准库allocator类及其算法
allocator<T> a 
a.allocate(n) 
a.deallocate(p,n) 
a.construct(p,args) 
a.destroy(p) 
allocator<string> alloc;          //可以分配string的allocator对象
auto const p=alloc.allocate(n);   //分配n个未初始化的string

auto q=p;
alloc.construct(q++);          //*q为空字符串
alloc.construct(q++,10,'c');   //*q为cccccccccc
alloc.construct(q++,"hi");     //*q为hi

cout<<*p<<endl;    //正确:使用string的输出运算符
cout<<*q<<endl;   //灾难:q指向未构造的内存

当我们用完对象后,必须对每个构造的元素调用destory来摧毁它们。我们只能对真正构造了的元素进行destory操作

一旦元素被销毁后,我们就可以重新使用这部分内存保存其他string,也可以将其归还给系统。释放内存通过调用deallocate来完成。

拷贝和填充未初始化内存的算法

allocator还有两个伴随算法,可以在未初始化内存中创建对象。它们都定义在头文件memory中。

allocator算法
这些函数在给定目的位置创建元素,而不是由系统分配内存给它们
uninitialized_copy(b,e,b2) 
uninitialized_copy_n(b,n,b2) 
uninitialized_fill(b,e,t) 
uninitialized_fill_n(b,n,t) 

uninitialized_copy会返回(递增后的)目的位置迭代器:

vector<int> vi(10);  //空间为10的vector
//分配比vi空间大一倍的动态内存
auto p=alloc.allocate(vi.size()*2);   
//通过拷贝vi中的元素来构造从p开始的元素
auto q=uninitialized_copy(vi.begin(),vi.end(),p);
//将剩余元素初始化为42
uninitialized_fiil_n(q,vi.size(),42);

 

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

#include<iostream>
#include<map>
#include<vector>
#include<set>
#include<new>
#include<fstream>
#include<sstream>
#include<memory>

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<std::string>> file;
	map<string, shared_ptr<set<line_no>>> wm;
};

TextQuery::TextQuery(ifstream &is) :file(new vector<string>)
{
	string text;
	while (getline(is, text))
	{
		file->push_back(text);
		int n = file->size() - 1;
		istringstream line(text);
		string word;
		while (line >> word)
		{
			auto &lines = wm[word];
			if (!lines)
				lines.reset(new set<line_no>);
			lines->insert(n);
		}
	}
}

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

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

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

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

void runQueries(ifstream &infile)
{
	TextQuery tq(infile);
	while (true)
	{
		cout << "enter word to look for,or q to quit:";
		string s;
		if (!(cin >> s) || s == "q") break;
		print(cout, tq.query(s)) << endl;
	}
}

int main()
{
	ifstream in("text.txt");
	if (!in)
	{
		cout << "文件不存在!" << endl;
		return -1;
	}

	runQueries(in);

	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值