第十二章(动态内存)
1).动态分配对象的生存期和它们在哪里创建是没有关系的,只有显式地被释放时,这些对象才会被释放。
2).为了解决动态对象能够被正确释放的问题,设置了智能指针。当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。
3).静态内存。
- 局部
static
变量。 - 类的
static
数据成员 - 全局变量
4).栈内存。保存局部变量。
5).静态内存和栈内存,由编译器自动创建和销毁。
6).程序的自由空间,或者堆。程序可以用堆,存储动态分配(程序运行时分配)的对象(它的创建和销毁由程序代码显式表示)。
/1.动态内存和指针
1).动态内存管理的运算符。
new
,在动态内存中为对象分配一个空间并返回一个指向该对象的指针。delete
,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
2).问题。
- 忘记释放,内存泄漏。
- 过早释放,使用非法的引用或者指针。s
3).解决。两个智能指针,和一个伴随类。
- 智能指针会自动释放所指向的对象。(这是与普通指针的差别)
shares_ptr
,允许多个指针同时指向一个对象。unique_ptr
,独占一个对象。- 伴随类。
weak_ptr
,它是一个弱引用。指向的是shared_ptr
所管理的对象。 - 这三个类型都定义在头文件
memory
中。
//1.shared_ptr类
1).智能指针实现的机制。依靠智能指针类里面的引用计数器成员。
- 可以认为每一个智能指针对象都有一个引用计数成员,指向同一个对象的智能指针的引用计数值一样。
- 当一个被引用对象的引用计数为0时,它的空间就会被自动释放。
引用计数何时增减。
- 当进行拷贝时,引用计数增加。例如,将它作为实参传递给形参时,进行了拷贝;当函数返回时,进行拷贝。
- 当一个局部
ptr
被销毁;或者一个ptr
被赋予新值时;引用计数器减少。
关于引用计数器。
- 引用计数器的具体实现由标准库决定。
- 可能时计数器,也可能是其他的数据结构。
2).介绍智能指针。
- 它是一个模板类。它的定义以及初始化方式如下,
{
shard_ptr<string> p1;//指向string对象的空指针
shared_ptr<list<int>> p2;//指向int类型的list的空指针。
}
- 使用
make_shared
函数构造智能指针并可以选择进行初始化。如果没有初始化,那么即使进行**值初始化。**这是分配和使用动态内存最安全的方法。该函数定义头文件memory
中。(使用时需要类型,类似于模板;与emplace
的类似,可以用参数来构造对象。)
{
auto p1 = make_shared<int>(42);//p指向一个值为42的int类型。
shared_ptr<string> p2 = make_shared<string>(10,'2');//p2指向内容为10个2的string类型。
shared_ptr<int> p3 = make_shared<int>();//p3指向一个值初始化为0的int类型
}
- 使用列表初始化。
{
initializer_list<string> li = {};
make_shared<vector<string>>(li);
}
- 智能指针是普通指针的加强版。
4).智能指针支持的操作。
操作名称 | 相关介绍 |
---|---|
shared_ptr和unique——ptr 都支持的操作。 | |
shared_ptr<T> sp; | 空智能指针。 |
unique<T> up; | |
p | 将p作为一个条件。非0返回true |
*p | 解引用 |
p->mem | 等价于(*p).mem |
p.get() | 返回与p指向同一个对象的内置指针。 |
swap(p,q) | 交换指针的值 |
q.swap§ |
5).shared_ptr
独有的操作。
操作名称 | 相关描述 |
---|---|
make_ptr<T>(agrs) | 返回一个指向动态分配的类型为T的对象的shared——ptr 。该对象用args 进行初始化。 |
shared_ptr<T> p(q) | 进行拷贝初始化。q 中的指针必须可以转换为T* ,p 是shared_ptr 类型的指针。 |
p = q | p,q都是shared_ptr ,并且它们所指向的类型必须可以进行相互转换。 |
p.use_count() | 返回的是与p共享对象的智能指针的数量,可能很慢,主要用于调式。 |
p.unique() | 如果,p.use_count()的数量为1,返回的是true ,否则返回的是false |
6).析构函数。
- 析构函数,是用来完成销毁工作的。
- 每一个类都有一个析构函数。它控制对象销毁时进行什么操作。一般用来释放对象所分配的资源。例如,释放内存。
- 当引用计数为0时,智能指针的析构函数就会销毁对象,释放它所占用的内存。
7).注意。以下情况引用计数不是为0。
{
void ues_factory(T args)
{
shared_ptr<F> p = make_ptr<F>(args);
return p;//由于进行了拷贝,引用计数递增了
//虽然p离开了局部作用域会被销毁。
//但是内存不会被释放。
}
}
8).当我们在一个容器中保存一些智能指针时,如果我们不需要这些指针了。只需要将他们erase
就可以进行删除。实际中这一点进行被遗忘。
9).为什么使用动态生存期的资源。
- 程序不知道自己需要使用多少的对象。例如,
vector
,我们随着用户输入才知道,需要多少的空间。容器的动态内存机制。 - 程序不知道所需对象的准确类型。(
template<typename T>
) - 程序需要在多个对象中共享数据。例如,一个
vector
拷贝给另一个vector
,虽然它们的内容是一样的,但是,它们不是共享的。一个vector
的生命期到了,它里面的元素也就释放了。我们要实现的是,当一个对象进行拷贝时,它所指向的底层数据是以被引用的方式被获取的,而不是底层数据的拷贝。
练习,
- 12.2,
const
版本的成员函数,返回值和this
类型都需要是const
。
//2.直接管理内存
1).使用new关键字创建指针。
- 可以选择是否值初始化。注意对于类类型,如果其内置类型数据成员没有类初始值,那么使用默认构造函数,内置类型还是未定义的。
- 对于类类型的是否值初始化都是调用它的默认构造函数。
- 可以选择进行显式地初始化。
- 注意,
new
关键字分配的空间是没有命名的,返回的是指针指向该对象。
{
//进行默认初始化
int *p = new int;
string *q = new string;
//进行值初始化
int *p = new int();
string *q = new string();
//对于类,不论是进行默认初始化还是值初始化都是调用默认构造函数,
// 对于内置类型,则由很大差异,前者是未定义,后者int则是0
//显式初始化。(进版本只能使用())
int *p = new int(12);
int *p(new int(42));
string *q = new string("hello world!");
string *q = new string(10,'a');
//在新版本中,还支持使用{}进行列表初始化
vector<int> *p = new vector<int>{1,2,3,4,5,6};
}
2).使用auto
关键字进行自动识别。
{
auto p = new auto(obj);//例如obj是一个int类型等。
//只能有一个初始化器。
auto q = new auto{a,b,c};//错误
}
3).申请对于const
对象的指针。
const
指针的释放方式是一样的。delete p;
即可。
{
const string *p = new const string;
delete p;
}
const
对象必须进行初始化。可以是显式地,也可以是隐式地(例如,定义了默认构造函数的类类型。)
{
const int *p = new const int(12);
const string *q = new const string(" ");
const string *q = new const string;//隐式地进行初始化
}
4).关于定位new
与内存耗尽不能分配空间。
- 可以对定位
new
表达式传入额外参数, bad_alloc
和nothrow
都是定义在头文件new
中的。
{
int *p = new int;//如果分配失败就抛出一个std::bad_alloc的错误。
int *p = new(nothrow) int;//如果分配失败就返回一个空指针。
}
5).delete
表达式
delete
执行两个操作,销毁给定指向的对象,然后释放内存。- 不可以对同一个空间进行多次释放。结果将会是未定义的。
- 只能对
new
关键字分配的内存或者空指针进行delete
。否则结果是未定义的。 - 编译器对于多次释放或者释放的是一个局部的变量无法判断,编译是通过的。
{
int i,*p = &i,*q = nullptr;
double *s = new double(33),*t = s;
delete i;//错误,不是指针
delete p;//未定义,不是new产生的
delete q;//正确,释放空指针
delete s;//正确
delete t;//错误,重复释放一个空间。
}
- 没有
delete
,即使指针被销毁了,它所指向的空间还是没有被释放。 - 释放后的指针,还是指向原来的位置,但是此时它就是一个空悬指针。会造成非法的访问。应该将他赋值为
nullptr
。 - 但是对于多个指针指向同一个对象的情况,上述方法只能修改个别指针。
练习,
- 通过指针转换判断,是否成功分配内存。
{
bool b()
{
int *p = new(nothrow) int;
return p;
}
}
//3.shared_ptr和new的结合使用
1).shared_ptr
支持的操作。
操作名称 | 相关描述 |
---|---|
shared_ptr<T> p(q); | q是一个内置指针,p管理内置指针的空间。q必须是由new 分配的,并且可以转换为T* |
shared_ptr<T> p(u); | p从unique_ptr 中接管对象,将u置为空。也就是unique_ptr 和shared_ptr 之间的转换。 |
shared<T> p(q,d); | 同第一个操作,但是这里是使用可调用对象d来代替delete 。 |
shared_ptr<T> p(p2,d) | p2是shared_ptr p2 类型的拷贝,区别就是使用d来代替delete |
p.reset() | 如果p是唯一指向其对象的shared_ptr ,reset 会释放这个对象。并将p置为空。 |
p.reset(q) | 此时指向的是内置指针q |
p.reset(q,d) | 此时用d代替delete |
2).用内置指针初始化shared_ptr
。
- 注意接受内置指针参数的构造函数时
explicit
的。所以我们不可以对它进行拷贝初始化。因为不能进行隐式地转换。 - 对智能指针进行初始化,赋值的内置指针必须是指向动态内存的,因为智能指针就是默认使用
delete
的。 - 如果非要绑定其他类型的内置指针,我们需要自定义操作重载
delete
。
{
shared_ptr<int> p = new int(12);//错误,对于内置类型对shared_ptr的初始化,只能是直接初始化,不能是拷贝。
// 因为智能指针将会对该内置指针指向的空间进行接管。
shared_ptr<int> p(new int(12));
//使用reset,用内置指针对智能指针赋值
p.reset(new int(1024));
// 使用显式地转换也可以。
p = shared_ptr<int>(new int(12));
}
3).试图在函数返回值中使用内置指针对智能指针进行拷贝的错误。编译器不会执行从内置指针到智能指针的隐式转换。
{
shared_ptr<int> F(int q)
{
return new int(q);//错误,试图进行拷贝。
return shared_ptr<int>(new int(q));//正确。
}
}
4).试图在传参时,将一个内置指针传递给智能指针导致指针内存的释放。
- 将一个内置指针的所有权交给智能指针时很危险的。你不知道什么时候他会释放内存。
{
int *p = new int(12);
void F(shared_ptr<int> q)
{
return;
}
F(p);//非法不能进行隐式转换。
F(shared_ptr<int>(p));
int i = *p;//错误,智能指针离开局部作用域时将内存释放。p此时就是一个空悬指针。
}
5).对一个内置指针绑定多个智能指针的错误。使用了get
函数。
- 永远不要使用
get
返回的内置指针对一个智能指针进行赋值。除非你可以保证他不会delete
。
{
shared_ptr<in> p(new int(12));
int *q = p.get();
{
shared_ptr<int>(q);
}
int i = *p;//错误内存已经被块作用域的智能指针释放了。
}
6).reset
的使用。
{
//配合unique成员,对单一分内容进行拷贝
// 实现单一处理
if (!p.unique())
p.reset(new string(*p));//内容不变
//多新的拷贝进行处理。
*p += " ";
}
7).归根结底就是,
- 智能指针只有在拷贝时才会增加引用计数。多次独立创建智能指针的错误就是因为每一个独立的智能指针的引用计数都是独立的。
练习,
- 12.10和12.11
{
shared_ptr<int> p(new int(12));
f(shared_ptr(p));//正确,可以
f(shared_ptr(p.get());//错误。
}
- 12.12
f(new int(12));//这是错误的,这也是需要隐式转换。
- 12.13,
get
返回的指针是可以delete
,但是会导致,shared_ptr
变为空悬指针。
//4.智能指针与异常
1).在一个函数f中,如果程序异常结束了,且在函数体里面没有捕获到这一个异常。那么,
- 局部变量智能指针会在退出函数体时正确地释放内存。
- 而由于
delete
,在异常抛出点之后,函数体退出时,它还没有运行到。使用内置类型的指针(局部变量)被销毁了。但是它的内存永远不会被释放。
2).虽然大多数的类有析构函数,来清理随想使用的资源。但是有一些是没有定义良好的析构函数的。**如果它有析构,就像内置类型一样,根本不需要我们进行释放。**例如,c和c++都使用的网络库。这种情况下,我们需要主动地去释放这些空间。但是,
- 要么被遗忘。
- 要么因为异常,没法释放。
3).解决,使用智能指针。但需要自定义函数(删除器)重载delete
。因为他没有delete
操作,不是new
产生的动态内存。(shared_ptr
默认是使用delete
,默认管理的是动态内存。)
{
void end_connection(connnection *p)
{
disconnection(*p);
}
//创建一个智能指针。
destination d;
connection c = connect(&d);
//c传入的是指针。!!
shared_ptr<connection> p(&c,end_connection);
}
练习,
- 12.15,使用
lambda
表达式作为重载函数。
//5.unique_ptr
1).除了与shared_ptr
一样的操作,还支持以下操作。
操作名称 | 相关描述 |
---|---|
unique_ptr<T> u | 空的智能指针。使用delete 来释放空间 |
unique_ptr<T,D> u | u会调用D代替delete |
unique_ptr<T,D>u(d) | 空的指针,hi用类型为D的d来重载delete |
u = nullptr | 释放u指向的空间,并置为空指针 |
u.release() | u放弃控制权,并返回一个内置指针。u置为空指针。没有释放空间 |
u.reset() | 释放空间,并且u置为空。,如果u为空则无需释放。 |
u.reset(q) | 内置指针q。u释放所指向的空间,并且指向新的q。 |
u.reset(nullptr) | 效果同u.reset() |
2).release
只是放弃控制权,没有释放内存。
{
p.release();//错误,内存泄漏
auto q = p.release();//正确
// 但是不要漏了,delete q;
}
3).对unique_ptr
的定义以及初始化,赋值。
- 可以认为它的引用计数只能为1;
unique_ptr
之间只能交换控制权,不可以相互赋值,初始化。- 不支持隐式转换。
{
unique<int> u = new int(1);//错误
unique<int> u(new int(1));
unique<int> q(u);//错误
unique<int> p;
p = u;//错误。
}
4).在函数的返回值是可以拷贝unique_ptr
的。编译器会知道要返回的对象将要被销毁。
- 但是还是不支持隐式地转换。切忌混淆。
{
unique_ptr<int> f(int p)
{
unique_ptr<int> ret(new int(p));
return ret;
//或者也可以这样。
return unique_ptr<int>(new int(p));
}
}
5).**自定义操作版本的unique_ptr
和shared_ptr
是不一样的。与算法是一样的。
- 重载
delete
操作,使得unique_ptr
的类型发生变化。
{
void f(destination d)
{
connection c = connect(&d);
unique_ptr<connect,decltype(end_connection)*> u(&c,end_connection);
}
}
6).移交控制权。
- 注意对于一个非
const
的unique_ptr
才可以进行移交控制权。
{
unique_ptr<string> p2(p1.release());//p2获获得p1的控制权,p1被置为空指针。
unique_ptr<string> p3(new string("test"));
p2.reset(p3.release());//p2所指向的空间,也就是p1原来的空间被释放了
//p3的控制权被转移到p2,p3被置为空指针。
}
7).关于auto_ptr
- 它是早期版本的一个类,有部分
unique_ptr
的特点。 - 不可以在容器中保存,也不能作为函数的返回值。
- 他仍是标准库的一部分。但是我们不会使用它。
练习,
- 12.16,当你试图拷贝或者赋值一个
unique_ptr
时,编译器给出的错误,并不一定是好理解的。 - 12.17,对一个
unique_ptr
使用一个普通的指针进行构造,合法。但是行为是未定义。
//6.weak_ptr
1).支持的操作。
操作名称 | 相关描述 |
---|---|
weak_ptr<T> w | 空指针 |
weak_ptr<T> w(sp) | 与shared_ptr 指向相同的对像的weak_ptr 。T必须可以转换为sp的类型。 |
w = p; | p可以是一个weak_ptr 或者是一个shared_ptr ,赋值后共享对象。 |
w.reset() | 将w置为空指针 |
w.use_count() | 与w共享的shared_ptr 的数量。 |
w.expired() | 如果w.use_count() 为0返回true ,反之返回的是false |
w.lock() | 如果w.expired() 返回true ,返回一个空的shared_ptr ,反之返回一个w的对象的shared_ptr |
2).定义以及初始化。
- 它不会控制对象的生存期。
- 只能用
shared_ptr
初始化weak_ptr
- 创建时必须进行初始化。
{
auto p = make_shared<int>(42);
weak_ptr<int> q(p);
}
3).由于是弱引用,它的引用不计入引用计数中。因此它可能是无效的。使用时需要进行判断。使用local
{
if (shared_ptr q = wp.local())
{
//保证q是有效的
}
}
/2.动态数组
1).解决一次为多个对象分配、释放内存的问题。
2).虽然可以操作,但是在新版本中,使用标准库容器,有很多优势。
- 不需要自己定义拷贝,赋值,析构
- 不用担心内存管理,简单高效
- 有更好的性能。
//1.new和数组
1).使用new
,创建一个动态的数组。
- 形式为
new int[];
- 注意虽然我们申请的是数组,但是
new
返回的并不是一个数组,而是一个指向数组首元素的指针。因此不可以使用begin
或者end
函数(因为begin
和end
是基于数组的维度实现的。),也自然地不可以使用范围for
循环。 - 与内置数组不一样,
[]
不要求是一个常量表达式
{
int *p = new int[get_size()];
}
- 使用类型别名。
{
typedef int arr[12];
int *p = new arr;
//编译器编译时为
int *p = new int[12];
}
- 如果没有
()
将会执行默认初始化。如果有()
将会执行值初始化。**由于是数组,我们不可以在()
中有初始化器,**因此它们不可以使用auto
来通过编译器自动识别类型。
{
int *p = new int[12]();
//对于string,效果是一样的。
string *p = new string[12];
string *p = new string[12]();
}
- 对于传入初始化器的,使用
{}
进行列表初始化。
- 如果数量不足,剩下的元素进行值初始化。
- 如果数量超过。new表达式是错误的,不会分配内存,会抛出一个
bad_array_new_length
的异常。这个异常和bad_alloc
都定义在头文件new
中。
{
int *p = new int[12]{1,2,3,4};
string *p = new string[12]{"the","a",string(12,'1')};
}
2).动态分配一个大小为零的动态数组是合法的。
- 返回的是一个尾后指针。
- 但是不可以对这个指针进行解引用的操作。
- 可以对这个指针进行算术运算。(加上或者减去数,两个指针相减。)
{
size_t n = get_size();
int *p = new int[n]();
for (int *q = p;q != p+n;++q)
//n可以为0
}
3).释放动态数组。
- 形式
delete []p;
- 对于一个对象,加了
[]
;或者对于一个数组没有[]
,它们的行为都是没有定义的。但是编译器不会报错。 - 注意,编译器的释放顺序是,逆序,即最后一个元素先被释放,然后是到数第二个。
{
delete p;//p指向一个动态分配的对象或者为空。
delete []p;//p必须指向一个动态分配的数组或者为空。
}
4).使用智能指针管理动态数组。
- 对于,
unique_ptr
,改变了它的类型。unique_ptr<int[]>
- 关于使用
release
;书上的例子是否有误。
{
unique_ptr<int[]> up(new int[12]);
//up.release();
//自动使用delete[]p销毁其指针。
}
unique_ptr<int[]>
类型改变,是因为它销毁内存空间时调用的是delete[]
5).unique_ptr
管理动态数组时支持的操作。
- 指向数组的
unique_ptr
不支持访问运算符号。.以及->
。因为不是单个对象,是数组。 - 其他的操作一样的(包括之前介绍的)。
操作名称 | 相关描述 |
---|---|
unique_ptr<T[]> p | |
unique_ptr<T[]> p(q) | q指向的是一个动态数组。类型为T |
u[i] | 支持下标运算。u必须指向一个数组 |
6).使用shared_ptr
管理动态数组。
- 需要自己重载
delete
运算为delete[]
{
shared_ptr<int> p = (new int[12],[](int *p){delete []p};);
//这里使用了lambda表达式删除器。
sp.reset();
}
shared_ptr
不支持下标运算,不支持算术运算。
{
for (size_t n = 0;n != 10;++n)
*(sp.get()+n) = n;
//使用内置数组解决这个问题。
}
- 智能指针不支持算术运算。
//2.allocator类
1).与new
和delete
的比较。
- 它与
new
以及delete
的不同在于,它将内存分配的对象创建分开。
- 避免了创建一些我们永远都不会用到的对象。
- 避免了在创建时进行初始化,我们需要使用时再一次赋值的消耗。
- 避免没有默认初始化的类不能使用动态内存。
- 但是注意
allocate
这样做,也要有一定的开销。
2).allocator支持的操作。
- 注意它也是一个类模板,定义在头文件
memory
。 - 我们只能对真正构造了对象的内存进行
destroy
。 - 试图使用没有构造对象内存的错误。
{
cout << *p << endl;//错误,没有分配内存。
}
操作名称 | 相关描述 |
---|---|
allocator<T> a | 定义一个可以申请类型为T的内存空间的allocator 对象。 |
a.allocate(n) | 为类型T的申请一个原始的没有构造的内存。可以保存n个对象。返回的是指向这段连续空间的首元素。 |
a.deallocate(p,n) | 释放从p开始的n个内存空间。p必须是由allocate 的到的指针。n必须是p创建时的大小。在调用它之前必须先调用,destroy 清除创建的对象。 |
a.construct(p,args) | p必须时一个T*类型的指针,指向一块原始的内存。 |
{
allocator<string> alloc;//可以分配string内存的对象
auto p = alloc.allocate(n);//申请n个空间。
auto q = p;//方便后面destroy
alloc.construct(q++);//构造一个空的字符串
alloc.construct(q++,10,'c');
alloc.construct(q++,"hi");
//destroy
--q;//指向第一个元素。
while (q != p)
{
alloc.destroy(--q);
}
alloc.destroy(q);
//可以用于构造别的对象。
//释放内存。
alloc.deallocate(p,n);
}
3).拷贝以及填充未构造对象的算法。
算法 | 相关描述 |
---|---|
uninitialized_copy(b,e,d) | 输入范围拷贝到未构造的原始内存。d开始的内存应该足够大。 |
uninitialized_copy(b,n,b2) | 输入范围未b开始的n个元素。输入范围必须是未构造的。因为它执行的是构造,不是拷贝,与copy不一样 ;并且返回的是指向下一个没有构造的元素的指针。这一点与copy 相似。 |
uninitialized_fill(b,e,val) | b,e为未构造的内存空间。 |
uninitialized_fill_n(b,n,val) | b开始的空间必须足够大。至少要有n个空间。 |
{
auto p = alloc.allocate(v.size()*2);
auto q = uninitialized_copy(v.begin(),v.end(),p);
uninitialized_fill_n(q,v.size(),42);
}
/3.使用标准库:文本查询程序
//1.文本查询程序设计
1).思路设计。
- 利用
vector<string>
存储文本中的每一行。 - 利用
istringstream
对输入的每一行进行分解。 - 利用
set
存储每一个单词出现的所有行号。 - 利用
map
将每一个单词和它的set
相互关联起来。 - 使用共享数据。
shared_ptr
(设计两个类)
- 避免了数据的拷贝。
set
,和vector
- 如果仅仅是通过迭代器或者指针,容易导致对象被销毁的非法访问。
2).编写使用这个类的程序,观察类是否具有我们预想的功能。
{
void runQuires(ifsstream &infile)
{
TextQuery tq(infile);//保存文件并且,建立查询的map
//建立用户交互。
while (true)
{
cout << "enter the word to look for,or q to quit:";
string s;
//如果到了文件末尾或者用户输入了'q',就结束
if (!(cin >> s) || s == 'q') break;
//打印查询的结果
print(cout,tq.query(s))<< endl;
}
}
}
//2.文本查询程序类的定义
1).编写TextQuery
类。
{
class QueryResult;//先声明再使用作为成员函数query的返回值
class TextQuery{
public:
using line_no = std::vector<string>::size_type;
TextQuery(std::ifstream&);//接受文本输入内容。
QueryResult query(const std::string&)const;
//query只是查询功能。令他时const版本。
private:
std::shared_ptr<std::vector<std::string>> file;//建立一个共享的指针。
std::map<string,std::shared_ptr<set<line_no>>> wm;//建立映射关系。原文中这个是错误的。
};
}
2).构造一个TextQuery
对象。(要求构造处file
和wm
数据成员。)
{
TextQuery::TextQuery(std::ifstream &is) : file(new vector<string>)
//这里初始化一个shared_ptr指向空的vector
{
string text;
//每一次读一行
while (getline(is,tect))
{
file->push_back(text);//将这一行存入vector中
int n = file->size() - 1;//当前的行号。
//对于每一个单词都有该行号。
string word;
istringstream line(text);
while (line >> word)
{
auto &lines = wm[word];//存入,返回的时一个value_type,这里是指针的引用。如果word原来是不存在的,那么返回的是一个空指针。
if (!lines)//如果是第一次出现这个单词。
{
lines.reset(new set<ilne_no>);//分配一个新的set
lines->insert(n);//将该行号插入set
}
}
}
}
}
3).
{
class QueryResult{
friend std::ostream& print(std::ostream&,const string&);
public:
QueryResult(std::string s,
std::shared_ptr<std::vector<std::string>> f,
std::shared_ptr<std::set> p) :
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;//输入的文件。
}
}
4).
{
QueryResult TextQuery::query(const string &sought) const
{
//设置成static,即便是没有找到也存在。
static shared_ptr<set<line_no>> nodata(new set<line_no>);
auto loc = wm.find(sought);//返回一个pair迭代器
if (loc == wm.end())
return QueryResult(sought,nodata,file);//未找到。
else
return QueryResult(sought,loc->second,file);
}
}
5).
{
//不必再一次声明友元。
ostream &print(ostream &os, const QueryResult &qr)
{
os << qr.sought << " occurs " << qr.lines->size() << " "
<< (qr.lines->size() > 1 ? "times" : "time") << endl;
for (auto num : *(qr.lines))//对set中的每一个行号
//当lines所指向的set为空时,循环一次也不执行。
{
//由于下标从零开始
os << "\t(line " << num+1 << ") "
<< *((*(qr.file)).begin() + num) <<
endl;
}
return os;
}
}