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();//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();
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 的int型shared_ptr
shared_ptr<string>sps2 = make_shared<string>("C++");//指向一个值为C++的string类型的shared_ptr
shared_ptr<string>sps3 = make_shared<string>(, '');//指向一个值为"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>();//p指向一个值为42的shared_ptr,该对象只有p一个引用者
auto q(p);//将p拷贝给q,此时p和q指向相同的对象
auto r = make_shared<int>();//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
//定义一个管理string的类,名为StrBlob,实现一个集合类型的最简单的方法是使用某个标准库容器来管理元素,这样可以
//借助标准库类型来管理元素所使用的内存空间。本例中使用vector,但是不能再一个对象内直接保存vector,因为一个对象
//成员在对象销毁时也会被销毁。为保证vector中元素继续存在,将vector保存在动态内存中,为实现数据共享,为StrBlob
//设置一个shared_ptr来管理动态分配发vector #ifndef STRBLOB_H
#define STRBLOB_H
#include <vector>
#include <string>
#include <memory>
#include <initializer_list>
#include <stdexcept>
//using namespace std;//必须加上此句,编译才没错误,但是,该加的地方都加了
class StrBlob {
public:
typedef std::vector<std::string>::size_type size_type;
StrBlob();//声明一个默认构造函数
StrBlob(std::initializer_list<std::string> il);//声明一个接受一个initiazer_list参数的构造函数
size_type size()const { return data->size(); }//定义一个常函数,返回data指向的vector的size
bool empty() { return data->empty(); }
//bool empty()const { return data->empty(); }//此处const和非const的区别
//添加或删除元素
void push_back(const std::string& str) { data->push_back(str); }
void pop_back();
//元素访问
std::string& front();//声明访问首尾元素
std::string& back();
const std::string& front()const;
const std::string& back()const; private:
std::shared_ptr<std::vector<std::string>> data;//定义一个指向vector<string>的shared_ptr,动态管理vector
//声明异常函数 如果访问的元素不存在,抛出异常
void check(size_type i, const std::string& msg)const; }; //函数定义
//StrBlob::StrBlob() : data(std::make_shared<vector<string>>()){}
//StrBlob::StrBlob(initializer_list<string>il):data(make_shared<vector<string>>(il)){}
//void StrBlob::check(size_type i, const std::string& msg)const
//{
// if (i >= data->size())
// throw out_of_range(msg);
//}
//上面注释掉的与下面的不同就是std::作用域,必须还得加上编译才通过
StrBlob::StrBlob() : data(std::make_shared<std::vector<std::string>>()) {}
StrBlob::StrBlob(std::initializer_list<std::string>il) : data(std::make_shared<std::vector<std::string>>(il)) {}
void StrBlob::check(size_type i, const std::string& msg)const
{
if (i >= data->size())
throw std::out_of_range(msg);
} std::string& StrBlob::front()
{
check(, "front on empty StrBlob");
return data->front();
}
//const 版本的front
const std::string& StrBlob::front()const
{
check(, "front on empty StrBlob");
return data->front();
}
std::string& StrBlob::back()
{
check(, "bak on empty StrBlob");
return data->back();
} const std::string& StrBlob::back()const
{
check(, "back on empty StrBlob");
return data->back();
}
void StrBlob::pop_back()
{
check(, "pop_back on empty StrBlob");
data->pop_back();
}
#endif
//StrBlob.cpp
#include <iostream>
#include "StrBlob.h"
1 int main()
{
StrBlob b1;
{
StrBlob b2 = { "a","an","the" };
b1 = b2;
b2.push_back("on");
cout << b2.size() << endl;
}
cout << b1.size() << endl;
cout << b1.front() << " " << b1.back() << endl;
const StrBlob b3 = b1;
cout << b3.size() << endl;
cout << b3.front() << " " << b3.back() << endl;
system("pause");
return ;
}
12.6 12.7
#include <iostream>
#include <vector>
#include <new>
#include <memory> using namespace std; //vector<int> new_vector()
vector<int> *new_vector()
{
return new(nothrow) vector<int>;//nothrow 是一种抛出异常的方式,如果内存分配失败,不再抛出bad_salloc
//返回一个空指针
} void save_in_vector(vector<int> *pv)
{
int iv;
while (cin >> iv)
pv->push_back(iv);
}
void out_vector(vector<int> *pv)
{
//for (auto &v : pv)
for(auto &v : *pv)
cout << v << " ";
cout << endl;
}
/*****************************--12.7--******************************/
shared_ptr<vector<int>> new_spvector()
{
return make_shared<vector<int>>();
} void save_in_spvector(shared_ptr<vector<int>> spv)
{
int ipv;
while (cin >> ipv)
spv->push_back(ipv);
} void out_spvector(shared_ptr<vector<int>>spv)
{
for (auto &i : *spv)
cout << i << " ";
cout << endl;
}
int main()
{
/***************************12.6****************************/
vector<int> *pv = new_vector();//调用new_vector函数,new 一段内存空间分配给vector<int>
if (!pv)
{
cout << "分配失败!" << endl;
return -;
}
//save_in_vector(*pv);
save_in_vector(pv);
out_vector(pv);
delete pv;//使用完,释放内存空间
pv = nullptr;//将空指针赋值给pv
/***************************12.7****************************/
auto spv = new_spvector();
save_in_spvector(spv);
out_spvector(spv);//不需要程序员手动释放内存,有效避免内存泄漏
system("pause");
return ;
}
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());//使用new返回的一个值为42的int直接初始化shared_ptr,使其指向
//值为42的int
shared_ptr<int>pi3 = new int();//错误:隐式的用一个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());//
//发生了异常,也没能捕获异常
}//程序退出时shared_ptr会自动释放内存 void f()
{
int* ip = new int();
//during function process 抛出一个异常,没能捕获
delete ip;//退出之前显示释放内存,其实这段内存没能释放,因为new和delete之间发生了异常,
//没能捕获异常,程序提前退出
}
#include <iostream>
#include <memory> using namespace std; struct destination{};
struct connection{}; connection connect(destination* pd)//打开连接
{
cout << "打开连接" << endl;
return connection();
} void disconnect(connection c)//关闭连接
{
cout << "关闭连接" << endl;
} void f(destination &d/*其他参数*/)//使用连接
{
cout << "普通管理连接" << endl;
connection c = connect(&d);
//没有调用disconnect 关闭连接
}
void end_connection(connection *p)
{
disconnect(*p);
} //使用shared_ptr void f1(destination &d/*其他参数*/)
{
connection c = connect(&d);
shared_ptr<connection>p(&c, end_connection);
//使用连接
//当f退出时,connection会被正确关闭
}
int main()
{
destination d;
f(d);
f1(d);
system("pause");
return ;
} //当退出f1时 p的值 p shared_ptr {...} [1 strong ref] [custom deleter] std::shared_ptr<connection>
//
//shanred_ptr 传递一个纸箱删除器的参数,shared_ptr<connection>p(&c, end_connection);调用discinnect,关闭连接
//对shared_ptr中保存的指针进行释放。
/*************12.15*******************/
//将shared_ptr<connection>p(&c, end_connection);改为
//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());//必须直接初始化,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>();
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
//StrBlob是一个管理string的类
#pragma once
#include <memory>
#include <string>
#include <vector>
#include <initializer_list>
#include <stdexcept> class StrBlobPtr;
class StrBlob {
friend class StrBlobPtr;
public:
typedef std::vector<std::string>::size_type size_type;
StrBlob();
StrBlob(std::initializer_list<std::string> il);
size_type size() { return data->size(); }
bool empty() { return data->empty(); }
void push_back( std::string& str) { return data->push_back(str); }
void push_back(const std::string& str) { return data->push_back(str); }//上面两句是因为最初定义的是非const
//版本的,在写测试程序时,使用了const的了,所以又加了一个const版本
void pop_back();
std::string& front();
std::string& backed();
const std::string& front()const;
const std::string& back()const;
//定义begin()和end()操作,返回一个指向自己的StrBlobPtr
StrBlobPtr begin();
StrBlobPtr end();
private:
std::shared_ptr<std::vector<std::string>> data;
void check(size_type , const std::string& )const; };
inline StrBlob::StrBlob():data(std::make_shared<std::vector<std::string>>()){}
inline StrBlob::StrBlob(std::initializer_list<std::string>il)
:data(std::make_shared<std::vector<std::string>>(il)){}
inline void StrBlob::check(size_type i, const std::string& msg)const
{
if (i >= data->size())
throw std::out_of_range(msg);
} inline void StrBlob:: pop_back()
{
check(, "pop on empty StrBlob1");
data->pop_back(); }
inline std::string& StrBlob::front()
{
check(, "front on empty StrBlob!");
return data->front();
} inline const std::string& StrBlob::front()const
{
check(, "front on empty StrBlob!");
return data->front();
} inline std::string& StrBlob::backed()
{
check(, "back on empty StrBlob");
return data->back();
} inline const std::string& StrBlob::back()const
{
check(, "back on empty StrBlob");
return data->back();
} class StrBlobPtr { public:
StrBlobPtr():curr(){} //默认构造函数,生成一个空的StrBlobPtr,将curr显示初始化为0,jwptr隐式初始化为空vector
StrBlobPtr(StrBlob &a, size_t sz = ):wptr(a.data),curr(sz){}
//StrBlob 的一个引用,一个索引值,初始化wptr,令其指向StrBlob对象的shared_ptr中的vector,并将curr初始化为szd 值.
std::string& deref()const;//声明“重载”解引用操作符的函数
StrBlobPtr& incr();
StrBlobPtr& decr();
private:
std::weak_ptr<std::vector<std::string>> wptr;//保存一个weak_ptr,指向StrBlob的data成员
std::size_t curr;//在数组中的当前位置
std::shared_ptr<std::vector<std::string>>check(std::size_t , const std::string&)const;
friend bool equal( StrBlobPtr&, StrBlobPtr&); }; //定义check()函数,与StrBlob中check()不同,它还要检查指向的vector是否存在
inline std::shared_ptr<std::vector<std::string>>
StrBlobPtr::check(std::size_t i, const std::string& msg)const
{
auto ret = wptr.lock();//lock返回一个指向共享对象的shared_ptr,z只要此shared_ptr存在,它指向的底层
//对象就一直存在
if (!ret)
throw std::runtime_error("unbound StrBlobPtr");
if (i >= ret->size())
throw std::out_of_range(msg);
return ret;//f返回指向vector的shared_ptr }
inline std::string& StrBlobPtr::deref()const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];//p 是一个shared_ptr,指向strBlobPtr指向的vector, *p就是解引用p
}
//前缀递增,返回递增后的对象的引用
inline StrBlobPtr& StrBlobPtr::incr()
{
//如果curr已经指向容器的尾后位置,就不能递增它
check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
//递减前缀
inline StrBlobPtr& StrBlobPtr::decr()
{
--curr;//递减当前位置
check(-, "decrement past the beginning of StrBlobPtr");
return *this;
}
inline StrBlobPtr StrBlob::begin() { return StrBlobPtr(*this); }
inline StrBlobPtr StrBlob::end()
{
auto ret = StrBlobPtr(*this, data->size());
return ret;
}
//StrBlobPtr的比较操作
inline bool equal(StrBlobPtr& lhs, StrBlobPtr& rhs)
{
auto left = lhs.wptr.lock();//获取lhs指向的底层vector
auto rht = rhs.wptr.lock();
//若两个底层vector相同
if (left == rht)
return (!left || lhs.curr == rhs.curr);
else
return false; } inline bool nequal( StrBlobPtr& lhs, StrBlobPtr& rhs)
{
return !equal(lhs, rhs);
}
//主程序
#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 ;
}
12.20
//主程序
#include <iostream>
#include"my_StrBlobPtr.h"
#include <fstream> using namespace std; int main(int argc, char*argv[])
{
//cout << "test";
ifstream infile(argv[]);
if (!infile)
{
cerr << "can not open the file" << endl;
return -;
} StrBlob b;
string word;
while (getline(infile, word))
b.push_back(word);
for (auto iter = b.begin(); nequal(iter, b.end()); iter.incr())
cout << iter.deref() << " ";
cout << endl;
system("pause");
return ;
} /************12.22******************/
//1.为StrBlobPtr定义能接受const StrBlob& 参数的构造函数
StrBlobPtr(const StrBlob& a,size_t =):wptr(a.data),curr(){}
//2.为strBlob定义能操作const对象是begin()和end()
//StrBlobPtr begin() const;
//StrBlobPtr end() const;
12.27
后面12,30
12.28
此题的用意是使用面向过程的方法,以便与面向对象的方法做一个对比
//相对于使用类管理数据这种面向对象的程序设计,本题要求不使用类实现同样的功能,是一种面向过程的程序设计,与面向
//对象有不同,file 和保存单词和关联行号的set的map都不需要shared_ptr来管理了。直接定义为vector,set 和map jiu 可以 #include <iostream>
#include <map>
#include <set>
#include <vector>
#include <string>
#include <fstream>
#include <sstream> using namespace std;
//由于不使用类,要实现数据共享,将vector和map定义为全局变量,也不需要定义成shared_ptr了
vector<string> file;//保存读取的文件的每行
//vector<string>::size_type lineNo;//行号,即vector的下标
using lineNo = vector<string>::size_type;//将lineNo定义为类型,
map<string&, set<lineNo>> word_line; string transform( string& str)
{
string ret_str;
for (auto iter = str.begin(); iter != str.end(); ++iter)
{
if (!ispunct(*iter))
ret_str = tolower(*iter);
}
return ret_str;
} void file_process(ifstream& in)
{
string text;
while (getline(in, text))
{
file.push_back(text);//将读到的每一行保存到vector中
int n = file.size() - ;//获取当前行号
istringstream line(text);//取出行中的每一个单词
string word;
while (line >> word)
word_line[transform(word)].insert(n); }
} ostream& query_print(string& word, ostream& os)
{
auto it = word_line.find(word);//使用find查找word是否在map中,find返回一个迭代器,指向第一个关键字为word
if (it == word_line.end())//的迭代器,若不存在则返回尾后迭代器
cout << word << " is not in the file" << endl;
else
{
auto line = it->second;//存在,需统计出现的行,second的成员是一个set,保存的是每次出现的行号
os << word << " appears " << line.size() << " times " << endl;//set的元素的个数就是word出现的次数
//打印出word出现的每一行和行号
for (auto ret : line)
os << " the row of " << ret + << ": \t" << *(file.begin() + ret) << endl; }
return os;
} void runQuery(ifstream& infile)
{
//infile 用来读取用户指定的磁盘中的文件
file_process(infile);//读入文本并存入map
while (true)
{
cout << "please enter the word you want to find" << endl;
string str;
if (!(cin >> str) || str == "q")
break;
query_print(str,cout);
} }
int main(int argc, char* argv[])
//int main()
{
//ifstream infile(argv[1]);
/*string test;
cin >> test;
ifstream infile(test);*/
ifstream infile(argv[]);
if (!infile)
{
cerr << "can not open the file" << endl;
} runQuery(infile); system("pause");
return ;
}
12.30
用了一个哈利波特的小说做的测试,我用的编译器,终端输入文件名后,执行有点慢,等了一会才提示输入要查询的单词.我输入了一个高频单词,执行过程让我对这个方向的热情和兴趣再增,我觉得还是不要收影响,扎扎实实的把基础搞好,我始终认为,地基稳,楼才盖的高。
//主程序
#include<iostream>
#include "QueryResult.h"
#include "TextQuery.h" using namespace std; void runquery(ifstream& infile)//infile 就是要读取的文件
{
TextQuery tq(infile);//调用TextQuery的构造函数,将处理过的文件保存到map中
//与用户交互,由用户输入要查询的单词
while (true)
{
cout << "please enter the word you want to search" << endl;
string word;
if (!(cin >> word) || word == "q")//若遇到文件结束符或用户输入q时退出
break;
print(cout, tq.query(word));
}
} int main(int argc, char*argv[])
{
//cout << "test" << endl;
if (argc < )
cerr << " there is no file,check it please" << endl;
ifstream infile(argv[]);
if (!infile)
cerr << " can not open the file" << endl;
else
runquery(infile); system("pause");
return ;
}
//TextQuery.h
#pragma once
#include <vector>
#include <fstream>
#include <map>
#include <set>
#include <memory>
#include <string> class QueryResult;
class TextQuery {
public:
using lineNo = std::vector<std::string>::size_type;//set需要保存单词是行号,即vector的下标
//将vector的下标定义为一个类型
TextQuery(std::ifstream&);//构造函数接受读入的文件,按行保存到vector中,每行的每个单词映射到map中
QueryResult query(const std::string&)const;//接收一个要查询的单词,并返回查询结果
private:
std::map<std::string, std::shared_ptr<std::set<lineNo>>> wm;//保存单词和行号
std::shared_ptr<std::vector<std::string>>file;//保存输入文件每行的vector
};
//TextQuery.cpp
#include "TextQuery.h"
#include <memory>
#include <vector>
#include <map>
#include <set>
#include <sstream> using namespace std; TextQuery::TextQuery(ifstream& in) :file(new vector<string>)
{
string text;
while (getline(in, text))//使用getline()获取读入文件的每一行,放入vector
{
//file.push_back(text);
file->push_back(text);//file是动态分配的,由shared_ptr指向的对象,使用->解引用操作符
int n = file->size() - ;//每存入一行,保存当前行号
istringstream line(text);//定义读取行中的单词的流line
string word;
while (line >> word)
{
auto &lines = wm[word];//map的下标运算符,若word在map中,返回关键字为word的值(行号),若不在,将
//word插入map 中,并初始化word关联的值,此时lines指针为空,需要创建一个
//新的set,将该行号插入set中,使用shared_ptr的reset,指向该set
if (!lines)
lines.reset(new set<lineNo>);
lines->insert(n);//无论word是否在map中,即无论是否是新闯将的set都将当前行号插入set中
}
} }
//make_plural.h
#pragma once
#include <string> std::string make_plural(int cnt, const std::string& str, const std::string& ending)
{
return (cnt > ? str + ending : str);
}
//QueryResult.h
#pragma once
#include <vector>
#include <fstream>
#include <map>
#include <set>
#include <memory>
#include <string> class QueryResult {
using lineNo = std::vector<std::string>::size_type;
friend std::ostream& print(std::ostream&, const QueryResult&);
public:
QueryResult(const std::string& str,
std::shared_ptr<std::vector<std::string>> f,
std::shared_ptr<std::set<lineNo>> line):
being_word(str), file(f), lines(line){}
private:
std::string being_word;
std::shared_ptr<std::vector<std::string>> file;
std::shared_ptr<std::set<lineNo>>lines; };
//QueryResult.cpp
#include "QueryResult.h"
#include <memory>
#include "TextQuery.h"
#include "make_plural.h"
#include <iostream> using namespace std;
//query
//若果找到,返回一个QueryResult(),包含要查询的单词,对应的file文件行,行号
//QueryResult 的成员定义顺序为being_word,line(查找的word的行号),file(该行内容)
//以及QueryResult()的构造函数的定义,这样更符合阅读习惯
QueryResult TextQuery::query(const string& being_word)const
{
//如果没有找到单词的行号,则没有对应set,此时可以定义一个static的set,shared_ptr指向该set
//没有找到单词时返回该set 的一个拷贝(传值)
static shared_ptr<set<lineNo>> nodata;
auto loc_it = wm.find(being_word);//使用find,而不是下标操作,返回的一个迭代器,指向第一个key为being_word的set
if (loc_it != wm.end())
return QueryResult(being_word, file, loc_it->second);
else
return QueryResult(being_word, file, nodata); } //string make_plural(int cnt, string& str, const string& ending)
//{
// return (cnt > 1 ? str + ending : str);
//}
//输出查询结果
ostream& print(ostream& os, const QueryResult& qr)
{
os << qr.being_word << " occurs " << qr.lines->size() << " " <<
make_plural(qr.lines->size(), "time", "s") << endl;
//打印单词出现的行以及所在行的内容
for (auto num : *qr.lines)
os << "\t(line" << num + << ")" << *(qr.file->begin() + num) << endl;
return os; }
CH12 动态内存的更多相关文章
- 【转】Linux C动态内存泄漏追踪方法
原文:http://www.cnblogs.com/san-fu-su/p/5737984.html C里面没有垃圾回收机制,有时候你申请了动态内存却忘记释放,这就尴尬了(你的程序扮演了强盗角色,有借 ...
- C++指针和动态内存分配
指针和动态内存分配 数组与指针 数组 数组名是一个指针常量. 数组名传递数据时,传递的是地址. 数组作为函数参数时不指定第一维大小. 对象数组 A a[2] = {A(1,2)}; 执行时先调用有参数 ...
- SQLite剖析之动态内存分配
SQLite通过动态内存分配来获取各种对象(例如数据库连接和SQL预处理语句)所需内存.建立数据库文件的内存Cache.保存查询结果. 1.特性 SQLite内核和它的内存分配子系统提供以下特性 ...
- C和指针 第十一章 动态内存分配
声明数组时,必须指定数组长度,才可以编译,但是如果需要在运行时,指定数组的长度的话,那么就需要动态的分配内存. C函数库stdlib.h提供了两个函数,malloc和free,分别用于执行动态内存分配 ...
- 解决Ubuntu Server 12.04 在Hyper-v 2012 R2中不能使用动态内存的问题
前言 全新Hyper-v 2012 R2终于开始支持在Linux的VPS中使用动态内存,可以大大优化服务器的资源分配,小弟我兴奋不已,于是抽空时间赶紧升级到 2012 R2,好好整理一番内存分配,不过 ...
- 动态内存分配导致Javascript性能的问题
内存分配对性能的影响是很大的,分配内存本身需要时间,垃圾回收器回收内存也需要时间,所以应该尽量避免在堆里分配内存.不过直到最近优化HoLa cantk时,我才深刻的体会到内存分配对性能的影响,其中有一 ...
- C++动态内存管理之shared_ptr、unique_ptr
C++中的动态内存管理是通过new和delete两个操作符来完成的.new操作符,为对象分配内存并调用对象所属类的构造函数,返回一个指向该对象的指针.delete调用时,销毁对象,并释放对象所在的内存 ...
- 【C++】动态内存与智能指针
C++常见的内存分配方式有三种: 从静态存储区分配,这里主要是存储局部static对象,类的static成员以及定义在函数之外的变量: 从栈内存分配,这里主要是存储函数内的非static对象: 从堆内 ...
- C++动态内存分配
C++动态内存分配1.堆内存分配 :C/C++定义了4个内存区间:代码区,全局变量与静态变量区,局部变量区即栈区,动态存储区,即堆(heap)区或自由存储区(free store). 堆的概念:通常定 ...
随机推荐
- java 协程
协程是比线程更轻量级的程序处理单元,也可以说是运行在线程上的线程,由自己控制 1.适用于被阻塞的,且需要大量并发的场景. 2.不适用于,大量计算的多线程,遇到此种情况,更好实用线程去解决. 虽然Jav ...
- Kubernetes的pod控制器及ReplicaSet控制器类型的pod的定义
为什么需要Pod Kubernetes项目之所以这么做的原因: 因为Kubernetes是谷歌公司基于Borg项目做出来的,谷歌工程师发现,他们部署的应用往往存在这进程与进程组的关系.具体说呢,就是这 ...
- mysql 默认信息
泰基MYSQL默认信息 登录名1-------默认用户 名字:root 密码:123 登录名2-------APP对应的数据库 名字:hotekey 密码:8888
- 洛谷P1060开心的金明(滚动数组优化)
题目描述 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间.更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过NNN元钱就行”. ...
- 7-10 多项式A除以B (25分)(多项式除法)
7-10 多项式A除以B (25分) 这仍然是一道关于A/B的题,只不过A和B都换成了多项式.你需要计算两个多项式相除的商Q和余R,其中R的阶数必须小于B的阶数. 输入格式: 输入分两行,每行给出 ...
- Java基础知识笔记第七章:内部类和异常类
内部类 /* *Java支持在一个类中定义另一个类,这样的类称为内部类,而包含内部类的类称为内部类的外嵌类 */ 重要关系: /* *1.内部类的外嵌类在内部类中仍然有效,内部类的方法也可以外嵌类的方 ...
- nginx防盗链处理模块referer和secure_link模块
使用场景:某网站听过URI引用你的页面:当用户在网站点击url时:http头部会通过referer头部,将该网站当前页面的url带上,告诉服务本次请求是由这个页面发起的 思路:通过referer模块, ...
- jdk动态代理和cglib动态代理底层实现原理超详细解析(jdk动态代理篇)
代理模式是一种很常见的模式,本文主要分析jdk动态代理的过程 1.举例 public class ProxyFactory implements InvocationHandler { private ...
- FFmpeg——命令笔记
1. 获取 dshow设备列表 ffmpeg -list_devices true -f dshow -i dummy 2. 通过UDP流推ts文件: ffmpeg.exe -re -i zhen.t ...
- redhat 7.6 安装 inotify-tools 文件监控工具 搭配rsync
1.解压inotify-tools tar -zxvpf inotify-tools-3.14.tar.gz 2.cd 到解压的目录 3../configure 编译,然后失败,提示checking ...