CH12 动态内存
动态分配的对象的生命期与它们在哪里创建的五官,只有显示地释放时,这些对象才被销毁
静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量,栈内存用来保存定义在函数内的非static对象,分配在静态内存或栈内存中的对象由编译器自动创建和销毁,static对象在使用前分配,程序结束时销毁,栈对象,定义在程序块运行时才存在。
动态内存即自由空间或堆,程序用来存储动态分配的对象。
动态内存与智能指针
C++中是通过new和delete这对运算符来管理动态内存的。new:在动态内存中为对象分配空间并返回一个指向该对象的指针,delete:接受一个动态对象的指针,销毁该对象并释放其占用的内存
int *pi = new int;//pi指向一个动态分配没有初始化的int对象 string *ps = new string;//ps指向一个动态分配的string,并初始化为空string
默认情况下,动态分配的对象执行默认初始化,即:内置类型或组合类型的对象的值是未定义的,类类型的对象使用默认构造函数进行初始化
也可以使用直接初始化的方式初始化一个动态分配的对象
int *pi = new int(1024);//pi指向的对象的值为1024 string *ps = new string("Hello");//ps指向的对象的值为"hello" cout << *pi << " " << *ps << endl;
也可以对动态分配的对象进行值初始化
int *pi1 = new int();//默认初始化为0 string *ps1 = new string();//默认初始化为空串 cout << *pi1 << " " << *ps1 << endl;
C++11提供了一个括号包围的初始化器,可以使用auto获得要分配的对象的类型,但是,括号内只能有一个初始化器才可以
auto pi2 = new auto(pi1);//p2指向一个与pi1类型相同的对象 cout << *pi2;//没有初始化,所以pi2的值是未定义的,输出的是内存中的某一个地址的值
动态分配的const对象
动态分配的const对象必须进行初始化(像其他任何const对象一样),对于定义了默认构造函数的类型,其const动态对象可以隐式初始化,而其他类型的对象必须显示初始化,分配的对象是const的,所以new返回的指针是一个指向const的指针
const int *pci = new const int(32); const string *pcs = new const string(); const string *pcs1 = new const string("C++"); cout << *pci << *pcs <<" "<<*pcs1<<endl;
释放动态内存
如果只分配而不释放动态内存,内存会被耗尽,再使用new分配就会失败,抛出一个类型为bad_alloc的异常。,可以改变new的方式阻止抛出异常
int *pi3 = new int;//分配失败,new抛出std::bad_alloc int *pi4 = new (nothrow) int;//如果 分配失败,new返回一个空指针
只是new而不释放,不只会是内存耗尽,也会造成内存泄漏,所以最正确的是使用后释放delete
delete p;//p必须指向一个动态分配的对象或一个空指针
释放上述分配的内存
对于一个有内置指针管理的动态对象,直到显示释放前都是存在的,同样,若是调用返回指向动态内存的指针的函数,必须记得释放内存
delete pi; delete ps; delete pi1; delete ps1; delete pi2; delete pci;//const对象虽然不能被改变,但可以被销毁 delete pcs; delete pcs1;
foo* factory(T arg) { //function return new foo(arg); } void call_factory(arg) { foo *p = factory(arg);//使用p delete p;//使用完,一定要释放 }
使用new和delete管理动态内存存在的三个常见问题
1.忘记delete内存,造成内存泄漏,并且这种错误查找非常困难
2.使用已经释放掉的对象
3.同一块内存释放两次
所以要想避免这些问题,可以使用智能指针,智能指针会自动释放内存
智能指针
C++11标准提供了两种智能指针类型管理动态对象:shared_ptr(允许多个指针指向同一个对象),unique_ptr(“独占”指向的对象),和一个伴随类指针weak_ptr,是一种弱引用,指向shared_ptr所管理的对象,这三种类型都定义在头文件memory中
shared_ptr类
智能指针也是模板,因此当创建一个智能指针时必须指明指针可以指向的类型
shared_ptr<string> sps1;//可以指向string shared_ptr<vector<int>> spv1;//可以指向vector<int> if (sps1&&ps1->empty()) *sps1 = "C++ primer";
上述指针执行默认初始化,默认初始化智能指针保存一个空指针,解引用一个智能指针返回它指向的对象。通过调用标准库函数make_shared来分配和使用动态内存是最安全和常用的方法,此函数在动态内存中分配一个对象并初始化该对象,返回指向此对象的shared_ptr。
shared_ptr<int> spi1 = make_shared<int>(1024);//指向一个值为1024 的int型shared_ptr shared_ptr<string>sps2 = make_shared<string>("C++");//指向一个值为C++的string类型的shared_ptr shared_ptr<string>sps3 = make_shared<string>(6, '6');//指向一个值为"666666"的string的shared_ptr
类似顺序容器的emplace成员,make_shared用其参数构造给定类型的对象,例如上面后两个例子调用make_shared<string>传递的参数分别于string的两个构造函数匹配,调用make_shared<int>传递的参数必须能用来初始化int。如果不传递任何参数,进行值初始化。
当然,简单的可以使用auto来推断要获取对象的类型
auto spiv = make_shared<vector<int>>();//spiv指向一个动态分配的vector<int>
shared_ptr 的拷贝和赋值
当进行拷贝或赋值操作时,每个shared_ptr会记录其它指向相同对象的shared_ptr的数量,即每个shared_ptr有一个关联的计数器,称为引用计数,拷贝一个shared_ptr或将shared-ptr作为参数传递给一个函数或作为函数返回值时,引用计数会递增,当将一个新值赋给shared_ptr时,该shared_ptr指向的对象的shared_ptr引用计数会递减(shared_ptr的析构函数会自动递减计数),当计数为0时,即指向一个对象的最后一个shared_otr被销毁,shared_ptr类会通过该类的析构函数自动销毁对象。
auto p = make_shared<int>(42);//p指向一个值为42的shared_ptr,该对象只有p一个引用者 auto q(p);//将p拷贝给q,此时p和q指向相同的对象 auto r = make_shared<int>(64);//r指向一个值为64的shared_ptr,并且该对象只有r 一个引用者 r = q;//将q指向的对象拷贝给r,r现在指向一个值为42的shared_ptr,递增q的引用计数,递减r的引用计数 //r原来指向的对象 已经没有引用者了,自动释放
class Foo { }; template <typename T> shared_ptr<Foo>factory(T arg) { //function return shared_ptr<Foo>(arg);//factory 返回一个shared_ptr,它分配的对象会在使用完成后自动被释放 }
将返回的shared_ptr保存在一个局部变量中,一旦离开作用域,该局部变量也会被销毁
template<typename T> void use_factory(T arg) { shared_ptr<Foo> p = factory(arg); //function //使用p }//p离开了作用域,它指向的对象被自动销毁
但是还有其他shared_ptr指向该内存,就不会被释放
template<typename T> shared_pre<Foo> useFactory(T arg) { shared_ptr<Foo> p = factory(arg); //function //使用p return p;//返回p时,引用计数会递增 }//p离开了作用域,但不会销毁p指向的对象,也不会释放该对象关联的内存
使用了动态生存期的资源的类
使用动态内存的一个常见原因是允许多个对象共享相同的状态
template<typename Blob> Blob<string> b1; void f() { Blob<string>b2 = { "C++","Primer" }; b1 = b2;//b1和b2共享相同是元素 }//b2离开作用域,被销毁了,但是b2中的元素不能被销毁,b1仍指向b1原来指向的元素
本章一个使用shared_ptr的一个完整的例子是定义StrBlob类
定义一个管理string的类StrBlob,实现一个新的集合类型的最简单的方法就是使用标准库容器来管理元素,为了实现数据共享,采用动态内存,为每个StrBlob设置一个shared_ptr来管理动态分配的容器,这里使用vector
该类有一个默认的构造函数和一个接受initializaer_list<string>的构造函数,提供访问元素的操作等,并且,用户试图访问不存在的元素时,抛出异常。具体实现见习题12.2
12.1
b2倍销毁了,b1包含4个元素
12.2
//StrBlob.h
1 #pragma once 2 //定义一个管理string的类,名为StrBlob,实现一个集合类型的最简单的方法是使用某个标准库容器来管理元素,这样可以 3 //借助标准库类型来管理元素所使用的内存空间。本例中使用vector,但是不能再一个对象内直接保存vector,因为一个对象 4 //成员在对象销毁时也会被销毁。为保证vector中元素继续存在,将vector保存在动态内存中,为实现数据共享,为StrBlob 5 //设置一个shared_ptr来管理动态分配发vector 6 7 8 #ifndef STRBLOB_H 9 #define STRBLOB_H 10 #include <vector> 11 #include <string> 12 #include <memory> 13 #include <initializer_list> 14 #include <stdexcept> 15 //using namespace std;//必须加上此句,编译才没错误,但是,该加的地方都加了 16 class StrBlob { 17 public: 18 typedef std::vector<std::string>::size_type size_type; 19 StrBlob();//声明一个默认构造函数 20 StrBlob(std::initializer_list<std::string> il);//声明一个接受一个initiazer_list参数的构造函数 21 size_type size()const { return data->size(); }//定义一个常函数,返回data指向的vector的size 22 bool empty() { return data->empty(); } 23 //bool empty()const { return data->empty(); }//此处const和非const的区别 24 //添加或删除元素 25 void push_back(const std::string& str) { data->push_back(str); } 26 void pop_back(); 27 //元素访问 28 std::string& front();//声明访问首尾元素 29 std::string& back(); 30 const std::string& front()const; 31 const std::string& back()const; 32 33 34 private: 35 std::shared_ptr<std::vector<std::string>> data;//定义一个指向vector<string>的shared_ptr,动态管理vector 36 //声明异常函数 如果访问的元素不存在,抛出异常 37 void check(size_type i, const std::string& msg)const; 38 39 }; 40 41 //函数定义 42 //StrBlob::StrBlob() : data(std::make_shared<vector<string>>()){} 43 //StrBlob::StrBlob(initializer_list<string>il):data(make_shared<vector<string>>(il)){} 44 //void StrBlob::check(size_type i, const std::string& msg)const 45 //{ 46 // if (i >= data->size()) 47 // throw out_of_range(msg); 48 //} 49 //上面注释掉的与下面的不同就是std::作用域,必须还得加上编译才通过 50 StrBlob::StrBlob() : data(std::make_shared<std::vector<std::string>>()) {} 51 StrBlob::StrBlob(std::initializer_list<std::string>il) : data(std::make_shared<std::vector<std::string>>(il)) {} 52 void StrBlob::check(size_type i, const std::string& msg)const 53 { 54 if (i >= data->size()) 55 throw std::out_of_range(msg); 56 } 57 58 std::string& StrBlob::front() 59 { 60 check(0, "front on empty StrBlob"); 61 return data->front(); 62 } 63 //const 版本的front 64 const std::string& StrBlob::front()const 65 { 66 check(0, "front on empty StrBlob"); 67 return data->front(); 68 } 69 std::string& StrBlob::back() 70 { 71 check(0, "bak on empty StrBlob"); 72 return data->back(); 73 } 74 75 const std::string& StrBlob::back()const 76 { 77 check(0, "back on empty StrBlob"); 78 return data->back(); 79 } 80 void StrBlob::pop_back() 81 { 82 check(0, "pop_back on empty StrBlob"); 83 data->pop_back(); 84 } 85 #endif
//StrBlob.cpp
#include <iostream>
#include "StrBlob.h"
1 int main() 2 { 3 StrBlob b1; 4 { 5 StrBlob b2 = { "a","an","the" }; 6 b1 = b2; 7 b2.push_back("on"); 8 cout << b2.size() << endl; 9 } 10 cout << b1.size() << endl; 11 cout << b1.front() << " " << b1.back() << endl; 12 const StrBlob b3 = b1; 13 cout << b3.size() << endl; 14 cout << b3.front() << " " << b3.back() << endl; 15 system("pause"); 16 return 0; 17 }
12.6 12.7
1 #include <iostream> 2 #include <vector> 3 #include <new> 4 #include <memory> 5 6 using namespace std; 7 8 //vector<int> new_vector() 9 vector<int> *new_vector() 10 { 11 return new(nothrow) vector<int>;//nothrow 是一种抛出异常的方式,如果内存分配失败,不再抛出bad_salloc 12 //返回一个空指针 13 } 14 15 void save_in_vector(vector<int> *pv) 16 { 17 int iv; 18 while (cin >> iv) 19 pv->push_back(iv); 20 } 21 void out_vector(vector<int> *pv) 22 { 23 //for (auto &v : pv) 24 for(auto &v : *pv) 25 cout << v << " "; 26 cout << endl; 27 } 28 /*****************************--12.7--******************************/ 29 shared_ptr<vector<int>> new_spvector() 30 { 31 return make_shared<vector<int>>(); 32 } 33 34 void save_in_spvector(shared_ptr<vector<int>> spv) 35 { 36 int ipv; 37 while (cin >> ipv) 38 spv->push_back(ipv); 39 } 40 41 void out_spvector(shared_ptr<vector<int>>spv) 42 { 43 for (auto &i : *spv) 44 cout << i << " "; 45 cout << endl; 46 } 47 int main() 48 { 49 /***************************12.6****************************/ 50 vector<int> *pv = new_vector();//调用new_vector函数,new 一段内存空间分配给vector<int> 51 if (!pv) 52 { 53 cout << "分配失败!" << endl; 54 return -1; 55 } 56 //save_in_vector(*pv); 57 save_in_vector(pv); 58 out_vector(pv); 59 delete pv;//使用完,释放内存空间 60 pv = nullptr;//将空指针赋值给pv 61 /***************************12.7****************************/ 62 auto spv = new_spvector(); 63 save_in_spvector(spv); 64 out_spvector(spv);//不需要程序员手动释放内存,有效避免内存泄漏 65 system("pause"); 66 return 0; 67 }
12,8
程序片段返回的值bool值,所以推测程序的本意是通过返回的指针结果判断内存分配是否成功,如果分配成功返回一个指针值,如果分配失败返回一个空指针,可转换为0,但是对于此段程序,若分配失败返回一个std::bad_alloc,抛出异常,若没有捕获异常的程序则直接导致程序崩溃,得不到预期结果,所以要想得到预期结果可以捕获异常或改变使用new的方式:
int* p = new(nothrow) int;
shared_ptr和new结合使用
如果程序员没有初始化智能指针,就会被默认初始化一个空指针,可以使用new返回的指针初始化智能指针,但是接受指针参数的指针构造函数第explicit的,因此,不能讲一个内置指针隐式转换为一个智能指针,必须使用直接初始化初始化智能指针
shared_ptr<int> pi1;//shared_ptr 的pi1指向一个int shared_ptr<int>pi2(new int(1024));//使用new返回的一个值为42的int直接初始化shared_ptr,使其指向 //值为42的int shared_ptr<int>pi3 = new int(1024);//错误:隐式的用一个new返回的int*创建一个shared_ptr,不能进行内置指针到 //智能指针的的隐式转换
同样,一个返回shared_ptr的函数不能再起返回语句中隐式转换一个普通指针,必须将shared_ptr显示绑定到一个想要返回的指针
//错误 shared_ptr<int> clone(int p) { return new int(p);//错误:将普通指针隐式转换为shared_ptr } //正确的方式 shared_ptr<int> clone(int p) { return shared_ptr<int>(new int(p));// }
不要混合使用普通指针和智能指针
void process(shared_ptr<int> ptr) { //function //使用ptr }//ptr离开作用域,被销毁,但此函数参数是值传递,实参拷贝给ptr ,递增ptr的引用计数,程序结束时,ptr被销毁,但ptr //指向的内存并没有被释放 //所以应该传递给ptr一个shared_ptr,
不要使用get初始化另一个智能指针或为智能指针赋值
使用get返回的指针的代码不能delete此指针
12.10
调用 正确。利用p创建一个临时的shared_ptr富裕process的参数ptr,p和ptr都指向相同的int对象那个,该对象的引用计数值为2,process执行完毕后,ptr被销毁,引用计数减1,只有p指向它
12.111
调用错误.p.get()获得的是一个普通指针,指向p所共享的int 对象,利用此指针创建一个shared_ptr,而不是利用p创建一个shared_ptr,没有形成动态的对象共享。编译器会认为p和ptr是使用两个地址,创建的两个不相干的shared_ptr,而非共享同一个动态对象。这样两者的引用计数均为1,process执行完毕后,ptr的引用计数减为0,所管理的内存地址呗释放,p成为一个管理shared_ptr的空悬指针
12.2
a)合法
b)合法
c)不合法。不能将普通int* 转换为shared_ptr
d)合法。但是会造成程序错误。p是一个指向一个int的对象的普通指针,被用来创建一个临时的shared_ptr,传递给process的参数ptr,引用计数为1,当process执行完毕,ptr被销毁,引用计数为0 ,int对象也被销毁,p变为空悬指针。
智能指针和异常
智能指针可以确保因程序出现异常而过早退出时正确的释放资源,而直接管理内存是不会自动释放的,如果使用内置指针管理内训,且在new之后delete之前发生了异常,则内存不会被释放。
void f() { shared_ptr<int> sp(new int(1024));// //发生了异常,也没能捕获异常 }//程序退出时shared_ptr会自动释放内存 void f() { int* ip = new int(1024); //during function process 抛出一个异常,没能捕获 delete ip;//退出之前显示释放内存,其实这段内存没能释放,因为new和delete之间发生了异常, //没能捕获异常,程序提前退出 }
1 #include <iostream> 2 #include <memory> 3 4 using namespace std; 5 6 struct destination{}; 7 struct connection{}; 8 9 connection connect(destination* pd)//打开连接 10 { 11 cout << "打开连接" << endl; 12 return connection(); 13 } 14 15 void disconnect(connection c)//关闭连接 16 { 17 cout << "关闭连接" << endl; 18 } 19 20 void f(destination &d/*其他参数*/)//使用连接 21 { 22 cout << "普通管理连接" << endl; 23 connection c = connect(&d); 24 //没有调用disconnect 关闭连接 25 } 26 void end_connection(connection *p) 27 { 28 disconnect(*p); 29 } 30 31 //使用shared_ptr 32 33 void f1(destination &d/*其他参数*/) 34 { 35 connection c = connect(&d); 36 shared_ptr<connection>p(&c, end_connection); 37 //使用连接 38 //当f退出时,connection会被正确关闭 39 } 40 int main() 41 { 42 destination d; 43 f(d); 44 f1(d); 45 system("pause"); 46 return 0; 47 } 48 49 //当退出f1时 p的值 p shared_ptr {...} [1 strong ref] [custom deleter] std::shared_ptr<connection> 50 // 51 //shanred_ptr 传递一个纸箱删除器的参数,shared_ptr<connection>p(&c, end_connection);调用discinnect,关闭连接 52 //对shared_ptr中保存的指针进行释放。 53 /*************12.15*******************/ 54 //将shared_ptr<connection>p(&c, end_connection);改为 55 //shared_ptr<connection>p(&c,[](connection* p){disconnect(*p);});
unique_ptr
某个时刻一个unique_ptr只能指向一个给定对象,当unique_ptr被销毁时,它所指向的对象也被销毁。
/unique_ptr //基本操作 unique_ptr<int>pi1;//指向一个int的unique_ptr unique_ptr<int>pi2(new int(42));//必须直接初始化,pi2指向一个值为42 的int //不支持拷贝和赋值 unique_ptr<string>ps1(new string("unique_ptr")); unique_ptr<string>ps2(ps1);//错误:不支持拷贝 unique_ptr<string>ps3; ps3 = ps1;//错误:不支持赋值
但是可以使用release或reset将指针所有权从一个(非const)unique_ptr转移给另一个
unique_ptr<string>ps2(ps1.release());//release释放ps1指向的对象并将ps1置空,转给ps2 unique_ptr<string>ps4(new string("unique_ptr reset")); ps2.reset(ps4);//重新定位了ps2的指向对象,ps2不再指向原来的"unique_ptr",而是指向"unique_ptr reset"
12.17
与12.12相似
weak_ptr 是一种不控制所指向对象生存期的只能指针,指向一个shared_ptr管理的对象,并且不会改变shared_ptr关联的引用计数,
auto ps = make_shared<int>(42); weak_ptr<int>wp(ps);//用shared_ptr初始化weak_ptr,wp弱共享ps,但不会改变ps的引用计数 if(shared_ptr<int> np = wp.lock())//不能直接使用weak_ptr直接访问对象,必须使用lock() { //if中,np 与p共享对象 }
12.19
1 //StrBlob是一个管理string的类 2 #pragma once 3 #include <memory> 4 #include <string> 5 #include <vector> 6 #include <initializer_list> 7 #include <stdexcept> 8 9 class StrBlobPtr; 10 class StrBlob { 11 friend class StrBlobPtr; 12 public: 13 typedef std::vector<std::string>::size_type size_type; 14 StrBlob(); 15 StrBlob(std::initializer_list<std::string> il); 16 size_type size() { return data->size(); } 17 bool empty() { return data->empty(); } 18 void push_back( std::string& str) { return data->push_back(str); } 19 void push_back(const std::string& str) { return data->push_back(str); }//上面两句是因为最初定义的是非const 20 //版本的,在写测试程序时,使用了const的了,所以又加了一个const版本 21 void pop_back(); 22 std::string& front(); 23 std::string& backed(); 24 const std::string& front()const; 25 const std::string& back()const; 26 //定义begin()和end()操作,返回一个指向自己的StrBlobPtr 27 StrBlobPtr begin(); 28 StrBlobPtr end(); 29 private: 30 std::shared_ptr<std::vector<std::string>> data; 31 void check(size_type , const std::string& )const; 32 33 }; 34 inline StrBlob::StrBlob():data(std::make_shared<std::vector<std::string>>()){} 35 inline StrBlob::StrBlob(std::initializer_list<std::string>il) 36 :data(std::make_shared<std::vector<std::string>>(il)){} 37 inline void StrBlob::check(size_type i, const std::string& msg)const 38 { 39 if (i >= data->size()) 40 throw std::out_of_range(msg); 41 } 42 43 inline void StrBlob:: pop_back() 44 { 45 check(0, "pop on empty StrBlob1"); 46 data->pop_back(); 47 48 } 49 inline std::string& StrBlob::front() 50 { 51 check(0, "front on empty StrBlob!"); 52 return data->front(); 53 } 54 55 inline const std::string& StrBlob::front()const 56 { 57 check(0, "front on empty StrBlob!"); 58 return data->front(); 59 } 60 61 inline std::string& StrBlob::backed() 62 { 63 check(0, "back on empty StrBlob"); 64 return data->back(); 65 } 66 67 inline const std::string& StrBlob::back()const 68 { 69 check(0, "back on empty StrBlob"); 70 return data->back(); 71 } 72 73 74 75 76 class StrBlobPtr { 77 78 public: 79 StrBlobPtr():curr(0){} //默认构造函数,生成一个空的StrBlobPtr,将curr显示初始化为0,jwptr隐式初始化为空vector 80 StrBlobPtr(StrBlob &a, size_t sz = 0):wptr(a.data),curr(sz){} 81 //StrBlob 的一个引用,一个索引值,初始化wptr,令其指向StrBlob对象的shared_ptr中的vector,并将curr初始化为szd 值. 82 std::string& deref()const;//声明“重载”解引用操作符的函数 83 StrBlobPtr& incr(); 84 StrBlobPtr& decr(); 85 private: 86 std::weak_ptr<std::vector<std::string>> wptr;//保存一个weak_ptr,指向StrBlob的data成员 87 std::size_t curr;//在数组中的当前位置 88 std::shared_ptr<std::vector<std::string>>check(std::size_t , const std::string&)const; 89 friend bool equal( StrBlobPtr&, StrBlobPtr&); 90 91 92 }; 93 94 //定义check()函数,与StrBlob中check()不同,它还要检查指向的vector是否存在 95 inline std::shared_ptr<std::vector<std::string>> 96 StrBlobPtr::check(std::size_t i, const std::string& msg)const 97 { 98 auto ret = wptr.lock();//lock返回一个指向共享对象的shared_ptr,z只要此shared_ptr存在,它指向的底层 99 //对象就一直存在 100 if (!ret) 101 throw std::runtime_error("unbound StrBlobPtr"); 102 if (i >= ret->size()) 103 throw std::out_of_range(msg); 104 return ret;//f返回指向vector的shared_ptr 105 106 } 107 inline std::string& StrBlobPtr::deref()const 108 { 109 auto p = check(curr, "dereference past end"); 110 return (*p)[curr];//p 是一个shared_ptr,指向strBlobPtr指向的vector, *p就是解引用p 111 } 112 //前缀递增,返回递增后的对象的引用 113 inline StrBlobPtr& StrBlobPtr::incr() 114 { 115 //如果curr已经指向容器的尾后位置,就不能递增它 116 check(curr, "increment past end of StrBlobPtr"); 117 ++curr; 118 return *this; 119 } 120 //递减前缀 121 inline StrBlobPtr& StrBlobPtr::decr() 122 { 123 --curr;//递减当前位置 124 check(-1, "decrement past the beginning of StrBlobPtr"); 125 return *this; 126 } 127 inline StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); } 128 inline StrBlobPtr StrBlob::end() 129 { 130 auto ret = StrBlobPtr(*this, data->size()); 131 return ret; 132 } 133 //StrBlobPtr的比较操作 134 inline bool equal(StrBlobPtr& lhs, StrBlobPtr& rhs) 135 { 136 auto left = lhs.wptr.lock();//获取lhs指向的底层vector 137 auto rht = rhs.wptr.lock(); 138 //若两个底层vector相同 139 if (left == rht) 140 return (!left || lhs.curr == rhs.curr); 141 else 142 return false; 143 144 } 145 146 inline bool nequal( StrBlobPtr& lhs, StrBlobPtr& rhs) 147 { 148 return !equal(lhs, rhs); 149 }
//主程序 #include<iostream> #include"my_StrBlobPtr.h" using namespace std; int main() { StrBlob b1; { StrBlob b2 = { "a","an","the" }; b1 = b2; string str("on"); b2.push_back(str); cout << b2.size() << endl; cout << b2.front()<<" "<<b2.back() << endl; } cout << b1.size() << endl; cout <<b1.front()<<" "<< b1.back() << endl; StrBlob b3 = b1; cout << b3.front() << " " << b3.back() << endl; //for (auto iter = b1.begin(); iter != b1.end(); iter.incr() //StrBlobPtr 没有定义!=号,若想使用,还需定义StrBlobPtr的"!=" for(auto iter = b1.begin();nequal(iter,b1.end());iter.incr()) { cout<<iter.deref()<<" "; cout<<endl; } system("pause"); return 0; }
12.20
1 //主程序 2 #include <iostream> 3 #include"my_StrBlobPtr.h" 4 #include <fstream> 5 6 using namespace std; 7 8 int main(int argc, char*argv[]) 9 { 10 //cout << "test"; 11 ifstream infile(argv[1]); 12 if (!infile) 13 { 14 cerr << "can not open the file" << endl; 15 return -1; 16 } 17 18 StrBlob b; 19 string word; 20 while (getline(infile, word)) 21 b.push_back(word); 22 for (auto iter = b.begin(); nequal(iter, b.end()); iter.incr()) 23 cout << iter.deref() << " "; 24 cout << endl; 25 system("pause"); 26 return 0; 27 } 28 29 /************12.22******************/ 30 //1.为StrBlobPtr定义能接受const StrBlob& 参数的构造函数 31 StrBlobPtr(const StrBlob& a,size_t =0):wptr(a.data),curr(0){} 32 //2.为strBlob定义能操作const对象是begin()和end() 33 //StrBlobPtr begin() const; 34 //StrBlobPtr end() const;
12.27
后面12,30
12.28
此题的用意是使用面向过程的方法,以便与面向对象的方法做一个对比
1 //相对于使用类管理数据这种面向对象的程序设计,本题要求不使用类实现同样的功能,是一种面向过程的程序设计,与面向 2 //对象有不同,file 和保存单词和关联行号的set的map都不需要shared_ptr来管理了。直接定义为vector,set 和map jiu 可以 3 4 #include <iostream> 5 #include <map> 6 #include <set> 7 #include <vector> 8 #include <string> 9 #include <fstream> 10 #include <sstream> 11 12 using namespace std; 13 //由于不使用类,要实现数据共享,将vector和map定义为全局变量,也不需要定义成shared_ptr了 14 vector<string> file;//保存读取的文件的每行 15 //vector<string>::size_type lineNo;//行号,即vector的下标 16 using lineNo = vector<string>::size_type;//将lineNo定义为类型, 17 map<string&, set<lineNo>> word_line; 18 19 string transform( string& str) 20 { 21 string ret_str; 22 for (auto iter = str.begin(); iter != str.end(); ++iter) 23 { 24 if (!ispunct(*iter)) 25 ret_str = tolower(*iter); 26 } 27 return ret_str; 28 } 29 30 void file_process(ifstream& in) 31 { 32 string text; 33 while (getline(in, text)) 34 { 35 file.push_back(text);//将读到的每一行保存到vector中 36 int n = file.size() - 1;//获取当前行号 37 istringstream line(text);//取出行中的每一个单词 38 string word; 39 while (line >> word) 40 word_line[transform(word)].insert(n); 41 42 } 43 } 44 45 ostream& query_print(string& word, ostream& os) 46 { 47 auto it = word_line.find(word);//使用find查找word是否在map中,find返回一个迭代器,指向第一个关键字为word 48 if (it == word_line.end())//的迭代器,若不存在则返回尾后迭代器 49 cout << word << " is not in the file" << endl; 50 else 51 { 52 auto line = it->second;//存在,需统计出现的行,second的成员是一个set,保存的是每次出现的行号 53 os << word << " appears " << line.size() << " times " << endl;//set的元素的个数就是word出现的次数 54 //打印出word出现的每一行和行号 55 for (auto ret : line) 56 os << " the row of " << ret + 1 << ": \t" << *(file.begin() + ret) << endl; 57 58 59 } 60 return os; 61 } 62 63 void runQuery(ifstream& infile) 64 { 65 //infile 用来读取用户指定的磁盘中的文件 66 file_process(infile);//读入文本并存入map 67 while (true) 68 { 69 cout << "please enter the word you want to find" << endl; 70 string str; 71 if (!(cin >> str) || str == "q") 72 break; 73 query_print(str,cout); 74 } 75 76 } 77 int main(int argc, char* argv[]) 78 //int main() 79 { 80 //ifstream infile(argv[1]); 81 /*string test; 82 cin >> test; 83 ifstream infile(test);*/ 84 ifstream infile(argv[1]); 85 if (!infile) 86 { 87 cerr << "can not open the file" << endl; 88 } 89 90 runQuery(infile); 91 92 system("pause"); 93 return 0; 94 }
12.30
用了一个哈利波特的小说做的测试,我用的编译器,终端输入文件名后,执行有点慢,等了一会才提示输入要查询的单词.我输入了一个高频单词,执行过程让我对这个方向的热情和兴趣再增,我觉得还是不要收影响,扎扎实实的把基础搞好,我始终认为,地基稳,楼才盖的高。
1 //主程序 2 #include<iostream> 3 #include "QueryResult.h" 4 #include "TextQuery.h" 5 6 using namespace std; 7 8 void runquery(ifstream& infile)//infile 就是要读取的文件 9 { 10 TextQuery tq(infile);//调用TextQuery的构造函数,将处理过的文件保存到map中 11 //与用户交互,由用户输入要查询的单词 12 while (true) 13 { 14 cout << "please enter the word you want to search" << endl; 15 string word; 16 if (!(cin >> word) || word == "q")//若遇到文件结束符或用户输入q时退出 17 break; 18 print(cout, tq.query(word)); 19 } 20 } 21 22 int main(int argc, char*argv[]) 23 { 24 //cout << "test" << endl; 25 if (argc < 2) 26 cerr << " there is no file,check it please" << endl; 27 ifstream infile(argv[1]); 28 if (!infile) 29 cerr << " can not open the file" << endl; 30 else 31 runquery(infile); 32 33 system("pause"); 34 return 0; 35 }
1 //TextQuery.h 2 #pragma once 3 #include <vector> 4 #include <fstream> 5 #include <map> 6 #include <set> 7 #include <memory> 8 #include <string> 9 10 class QueryResult; 11 class TextQuery { 12 public: 13 using lineNo = std::vector<std::string>::size_type;//set需要保存单词是行号,即vector的下标 14 //将vector的下标定义为一个类型 15 TextQuery(std::ifstream&);//构造函数接受读入的文件,按行保存到vector中,每行的每个单词映射到map中 16 QueryResult query(const std::string&)const;//接收一个要查询的单词,并返回查询结果 17 private: 18 std::map<std::string, std::shared_ptr<std::set<lineNo>>> wm;//保存单词和行号 19 std::shared_ptr<std::vector<std::string>>file;//保存输入文件每行的vector 20 };
1 //TextQuery.cpp 2 #include "TextQuery.h" 3 #include <memory> 4 #include <vector> 5 #include <map> 6 #include <set> 7 #include <sstream> 8 9 using namespace std; 10 11 TextQuery::TextQuery(ifstream& in) :file(new vector<string>) 12 { 13 string text; 14 while (getline(in, text))//使用getline()获取读入文件的每一行,放入vector 15 { 16 //file.push_back(text); 17 file->push_back(text);//file是动态分配的,由shared_ptr指向的对象,使用->解引用操作符 18 int n = file->size() - 1;//每存入一行,保存当前行号 19 istringstream line(text);//定义读取行中的单词的流line 20 string word; 21 while (line >> word) 22 { 23 auto &lines = wm[word];//map的下标运算符,若word在map中,返回关键字为word的值(行号),若不在,将 24 //word插入map 中,并初始化word关联的值,此时lines指针为空,需要创建一个 25 //新的set,将该行号插入set中,使用shared_ptr的reset,指向该set 26 if (!lines) 27 lines.reset(new set<lineNo>); 28 lines->insert(n);//无论word是否在map中,即无论是否是新闯将的set都将当前行号插入set中 29 } 30 } 31 32 }
1 //make_plural.h 2 #pragma once 3 #include <string> 4 5 6 std::string make_plural(int cnt, const std::string& str, const std::string& ending) 7 { 8 return (cnt > 1 ? str + ending : str); 9 }
1 //QueryResult.h 2 #pragma once 3 #include <vector> 4 #include <fstream> 5 #include <map> 6 #include <set> 7 #include <memory> 8 #include <string> 9 10 11 12 class QueryResult { 13 using lineNo = std::vector<std::string>::size_type; 14 friend std::ostream& print(std::ostream&, const QueryResult&); 15 public: 16 QueryResult(const std::string& str, 17 std::shared_ptr<std::vector<std::string>> f, 18 std::shared_ptr<std::set<lineNo>> line): 19 being_word(str), file(f), lines(line){} 20 private: 21 std::string being_word; 22 std::shared_ptr<std::vector<std::string>> file; 23 std::shared_ptr<std::set<lineNo>>lines; 24 25 };
1 //QueryResult.cpp 2 #include "QueryResult.h" 3 #include <memory> 4 #include "TextQuery.h" 5 #include "make_plural.h" 6 #include <iostream> 7 8 using namespace std; 9 //query 10 //若果找到,返回一个QueryResult(),包含要查询的单词,对应的file文件行,行号 11 //QueryResult 的成员定义顺序为being_word,line(查找的word的行号),file(该行内容) 12 //以及QueryResult()的构造函数的定义,这样更符合阅读习惯 13 QueryResult TextQuery::query(const string& being_word)const 14 { 15 //如果没有找到单词的行号,则没有对应set,此时可以定义一个static的set,shared_ptr指向该set 16 //没有找到单词时返回该set 的一个拷贝(传值) 17 static shared_ptr<set<lineNo>> nodata; 18 auto loc_it = wm.find(being_word);//使用find,而不是下标操作,返回的一个迭代器,指向第一个key为being_word的set 19 if (loc_it != wm.end()) 20 return QueryResult(being_word, file, loc_it->second); 21 else 22 return QueryResult(being_word, file, nodata); 23 24 } 25 26 //string make_plural(int cnt, string& str, const string& ending) 27 //{ 28 // return (cnt > 1 ? str + ending : str); 29 //} 30 //输出查询结果 31 ostream& print(ostream& os, const QueryResult& qr) 32 { 33 os << qr.being_word << " occurs " << qr.lines->size() << " " << 34 make_plural(qr.lines->size(), "time", "s") << endl; 35 //打印单词出现的行以及所在行的内容 36 for (auto num : *qr.lines) 37 os << "\t(line" << num + 1 << ")" << *(qr.file->begin() + num) << endl; 38 return os; 39 40 }