c++动态内存
一般程序中所用的对象在创建时分配,程序结束时销毁。但是动态分配的对象的生存期与它们在哪创建是无关的,只有当显示的被释放时,这些对象才会销毁。
目前为止用过静态内存和栈内存,静态内存用来保存局部static对象、类static数据成员以及定义在函数之外的变量。栈内存用来保存定义在函数内的非static对象。除了这两个之外,每个程序还拥有一个内存池,这部分内存被称为自由空间或堆。程序用对来存储动态分配的对象。
1.动态内存和智能指针
在c++中,动态内存的管理是通过一对运算符来完成的:new,在动态内存中为对象分配空间并返回一个指向该对象的指针。delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
智能指针类似常规指针,重要的区别是它负责自动释放所指向的对象。两种智能指针:shared_ptr允许多个指针指向同一个对象,unique_ptr独占所指向的对象。
(1)shared_ptr类
shared_ptr和unique_ptr都支持的操作:a.shared_ptr<T> sp; b.unique_ptr<T> up; c. p;(将p用做一个条件判断,若p指向一个对象,则为true) d. *p;解引用p,获得它指向的对象 e. p->mem;等价于(*p).mem f. p.get(); 返回p中保存的指针 g. swap(p,q); p.swap(q); 交换p和q中的指针
shared_ptr独有的操作:a.make_shared<T> (args)返回一个shared_ptr b.shared_ptr<T>p(q) p是shared_ptr q的拷贝 c.p=q 所保存的指针必须能相互转换。此操作会递减p的引用计数,递增q的引用计数,若p的引用计数变为0,则将其管理的原内存释放。 d. p.unique()是不是唯一的,若是,返回true e. p.use_count() 返回与p共享对象的智能指针数量。
make_shared函数:shared_ptr<int> p3=make_shared<int> (42);//指向一个值为42的int的shared_ptr
但是我们通常使用auto定义一个对象来保存make_shared的结果:auto p6=make_shared<vector<string>>。
每个shared_ptr都有一个关联的计数器,通常称其为引用计数。拷贝一个shared_ptr,计数器都会递增。当计数器变为0时,就会自动释放管理的对象以及所占用的内存。
程序使用动态内存的原因:a.程序不知道自己需要使用多少对象 b.程序不知道所需对象的准确类型 c.程序需要在多个对象间共享数据
每个vector都有自己的元素,拷贝一个vector时,原vector和副本vector中的元素是分离的。 我们可以定义一个模板blob,希望不同的blob对象的不同拷贝之间共享相同的元素。因此我们为每个blob设置一个shared_ptr来管理动态分配的vector。
(2)直接管理内存
new分配内存,delete释放内存。但是直接用容易出错,使用智能指针一般较好。
int *pi=new int;//pi指向一个动态分配、未初始化的无名对象
auto p1=new auto(obj);//p1指向一个与obj类型相同的对象,该对象用obj进行初始化。
当内存不够时,new就会抛出异常,可以使用类似 int *p2=new (nothrow) int;来定义,分配失败后返回空指针。
delete p;销毁给定的指针指向的对象
用delete释放一块非new分配的内存或者相同指针释放多次,其行为是未定义的。
内置指针是什么???
delete后,指针就变成了空悬指针,我们要释放掉它所指向的内存,这样就没机会使用该指针了。
(3)shared_ptr与new的结合使用
a.我们不能将一个内置指针类型隐式的转换为一个智能指针,必须使用直接初始化形式
是shared_ptr<int> p2(new int(1024))而不是shared_ptr<int> p2=new int(1024)
b.不要混合使用普通指针和智能指针
c.不要使用get初始化另一个只能指针或为智能指针赋值
d.对shared_ptr赋值时,使用reset函数。
(4)智能指针和异常
使用智能指针的局部变量的内存在发生异常时会被释放掉,若使用new、delete,则在两者之间发生异常时,内存不会被释放。
(5)unique_ptr
定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。使用直接初始化。不支持普通的拷贝和赋值操作。
unique_ptr操作:u.release()//放弃对指针的控制权。
(6)weak_ptr
weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weal_ptr绑定到一个shared_ptr不会改变引用计数。一旦最后一个指向对象被销毁,即使有weak_ptr指向对象,对象还是会被释放。
当创建weak_ptr时,要用一个shared_ptr来初始化。
auto p=make_shared<int>(42);
weak_ptr<int> wp(p);
由于不知道weak_ptr所指向的对象存不存在,我们需用lock,返回一个指向对象的shared_ptr,没有的话,返回空shared_ptr。
2.动态数组
new和delete只能分配和删除一个对象,但是某些应用需要一次为很多对象分配内存,当容器需要重新分配内存时,必须一次性为很多元素分配内存。为了支持这种需求,有两种一次分配一个对象数组的方法:一种是new表达式语法,初始化一个对象数组。另一种是allocator类,将分配与初始化分离。
a.new和数组
为了让new分配一个对象数组,我们要在类型名之后跟一对方括号如:
int *pia=new int[get_size()];//pia指向第一个int,方括号中的大小必须为整型,但不必是常量。
也可写成如下形式:typedef int arrT[42]; //arrT表示42个int的数组类型 int *p=new arrT;
分配一个数组会得到一个数组元素类型的指针,而不是一个数组类型的对象。动态数组并不是数组类型。
初始化动态分配对象的数组:int *pia2=new int[10]();//在后面加上()就能对数组中的元素进行值初始化。
int *pia3=new int[10]{0,1,2,3,4,5,6,7,8,9};//可以使用列表初始化。
我们用空括号进行值初始化,但是在括号中不能加入某一对象,也就意味着不能使用auto来初始化。
动态分配一个空数组是合法的:char arr[0];//非法 char *cp=new char[0];//cp不能解引用,定义的指针相当于尾后指针。
释放动态数组:delete [] pa;//pa必须指向一个动态分配的数组或为空。
智能指针和动态数组:标准库提供了一个可以管理new分配的数组的unique_ptr版本。unique_ptr<int []> up(new int[10]); up.release();//自动用delete []销毁其指针
当一个unique_ptr指向一个数组时,我们不能使用点和箭头成员运算符。我们可以通过下标运算符来访问数组中的元素。
指向数组的unique_ptr:unique_ptr<T[]> u; unique_ptr<T[]> u(p); u[i];
b.allocator类
先分配大块内存,在需要时才真正执行对象的创建操作。
allocator类定义在头文件memory中,类似vector,allocator是一个模板。
allocator<string> alloc;
auto const p=alloc.allocate(n);//这个allocator调用为n个string分配了内存。
allocator的算法:allocator<T> a; a.allocate(n); a.deallocate(p,n); //释放内存 a.construct(p,args); a.destory(p);
allocator分配未构造的内存,为了使用allocate返回的内存,我们必须使用construct构造对象。autoq=p; alloc.construct(q++,"hi");//*q为hi
allocator算法:uninitialized_copy(b,e,b2) uninitialized_copy(b,n,b2) uninitialized_fill(b,e,t) uninitialized_fill_n(b,n,t)
3.使用标准库:文本查询程序
本节作为标准库相关内容学习的总结
读入一个文件,寻找单词element。
需求:a.程序需要逐行读取输入文件,并将每一行分解为独立的单词。
b.当程序生成输出时,必须能提取每个单词所关联的行号,行号必须按升序出现且无重复,必须能打印给定行号中的文本。
利用标准库实现需求:a.使用一个vector<string>来保存整个输入文件的一份拷贝,每个元素对应一行。
b.使用一个istringstream来将每行分解为单词
c.使用set来保存每个单词在输入文本中出现的行号。保证了行号按升序保存。
d.使用一个map来将每个单词与他出现的行号set关联起来。
数据结构:查询返回所有这些内容最简单的方法就是定义一个类,命名为QueryResult,该类还应该有一个print函数。定义一个保存输入文件的类,将这个类命名为TextQuery
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;
}}
class QueryResult;
class TextQuery{
public:
using line_no=std::vector<std::string>::size_type;
TextQuery(std::Query);
QueryResult query(const std::string&) const;
private:
std::shared_ptr<std::vector<std::string>> file;
std::map<std::string,std::shared_ptr<std::set<line_no>>> wm;
}
TextQuery构造函数
TextQuery::TextQuery(ifstream &is):file(new vector<string>)
{string text;
while(getline(is,next)){
file->pushback(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{
friend std::ostream& print(std::ostream&,const QueryResult);
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;
};
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);
}
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;
}