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;

}

 

posted on 2016-03-14 18:11  sccy  阅读(250)  评论(0编辑  收藏  举报