C++Primer:第十二章 动态内存 笔记

概述信息

1、静态内存用来保存

1)局部static对象

2)类static数据成员

3)定义在函数外的变量

2、栈内存保存

1)函数内非static对象


3、静态内存和栈内存中的对象由编译器自动创建和销毁。

1)栈对象:仅在其定义的程序块运行时才存在

2)static对象:使用之前分配,程序结束时销毁


4、补充一下C++的内存区域

1)代码区:存放程序代码

2)静态数据区:存放全局变量或对象、static局部变量或对象

全局变量或对象:程序开始运行时在该区分配
static局部变量或对象:在程序运行过程中第一次进入其作用域在该区分配。
该区的变量或对象直到程序运行结束才被释放。

3)局部数据区(栈):

存放auto局部变量或对象。在程序运行到其作用域时在栈区分配,但怎样分配在编译时已经确定。在离开其作用域时被释放

4)动态存储区(自由存储区、堆区)

存放运行过程中由new运算符动态创建的变量或对象。在编译时无法预订存储空间,按需分配。
需要使用delete运算符才能释放


1、动态内存与智能指针

1、new:分配空间,返回一个指向对象的指针。

2、delete:接受一个动态对象的指针,销毁该对象,释放内存。


3、为安全+容易使用动态内存,新标准库提供了两种智能指针:负责自动释放所指向的对象

1)shared_ptr:允许多个指针指向同一个对象。----这是通常指针的用法所需要的吧

2)unique_ptr:“独占”所指向对象。

3)还有一个弱引用:weak_ptr

都在memory头文件中。

1.1 shared_ptr类

1、智能指针也是模板,创建时需要说明指向的类型。

shared_ptr<string> p1;//指向string
shared_ptr<list<int>> p2;//指向int类型的list

2、默认初始化的智能指针中保存着一个空指针

3、用法与普通指针一样,*p返回的就是被指向的对象。

4、shared_ptr和unique_ptr支持的操作

1)都支持的

  • 空智能指针,指向类型为T的对象。

       shared_ptr<T> sp

       unique_ptr<T> up

  • p  ,  用作条件判断,如果p指向一个对象,则为true
  • *p,获得指向的对象
  • p.get()   返回p中保存的指针。
  • swap(p,q);p.swap(q):交换p和q中的指针
  • p->mem:等价于(*p).mem   ----大家都这么说,这个mem指的是什么?后面看能否找到

2)shared_ptr独有的

  • make_shared<T>(args),返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化该对象(调用构造函数)
  • shared_ptr<T> p(q):p是shared_ptr q的拷贝。会递增q中的计数器。q的指针必须能转换为T*
  • p = q:p和q都是shared_ptr,所保存的指针能互相转换。q的技术会递增,p的会递减。---这个和上面一个有什么区别。上面的p是新定义的么
  • p.unique():如果p.use_count()为1,则返回true,否则返回false。-----用来判断是否只有一个引用
  • p.use_count():与p共享对象的智能指针数量。可能很慢,主要用于调试----为啥会慢?后续要搞清楚

5、make_shared函数

1)最安全的分配和使用动态内存的方法是调用这个函数。

<>:用来指定各类型

(args):用来作为参数,与相应类型的构造函数匹配,完成构造过程。

shared_ptr<int> p3 = make_shared<int>(42);//指向值为42的int的shared_ptr
shared<string> p4 = make_shared<string>(8,'a');//指向值为aaaaaaaa的string
shared_ptr<int> p5 = make_shared<int>();//指向一个值初始化的int,值为0

第三行是 一个新花样,如果不传递任何参数,就是进行“值初始化“(专有叫法),后面很快有介绍。

2)通常可用auto来定义:

auto p6 = make_shared<vector<string>>();//p6指向一个动态分配的,空vector<string>

问题,这样只是相当于定位到vector首地址。后续vector通过其他方式进行扩展?那新地址空间从哪里分配,这个指针的目的地址还会变化?

6、shared_ptr的拷贝和赋值

1)进行拷贝和赋值时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同对象。

下面的图不一定对(不一定这样实现,依赖库的实现形式),但可以帮助理解。就是有一个use.count的计数器。如果有多个shared_ptr指向相同对象时,+1;不指向时,-1;如果为0时,对象被销毁。这个变量是由这些shared_ptr共享的,发生变化时,应该都能取得最新值。




当用一个shared_ptr去初始化另一个shared_ptr时,+1;

当作为参数传递给一个函数时,+1;

当作为函数返回值时,+1;

当给一个shared_ptr赋予一个新值,它不再指向原来的对象,所以计数器-1;

当shared_ptr计数器为0,会释放自己管理的对象


7、shared_ptr自动销毁所管理的对象

1)它通过调用析构函数完成销毁工作。

2)shared_ptr的析构函数会递减所指向的对象的引用计数。如果变为0,则销毁对象,释放内存。


8、如果将shared_ptr存放在一个容器中,而后不需要全部元素,要记得用erase删除不再需要的那些元素。


9、使用动态内存的三种原因

1)不知道要用多少对象(vector是一个列子)

2)不知道对象的准确类型

3)需要在多个对象间共享数据(不能放在对象里,不能因一个对象销毁,数据就丢了


10、对象间共享数据的例子--StrBlob(只记录几条有关键意义的信息)

1)实现集合类型,最简单是选择合适的标准库容器来管理元素,这里选vector

2)为了保证有对象销毁,数据也还在。将vector保存在动态内存中

3)为每个StrBlob对象,设置一个shared_ptr。这样就能记录有多少个StrBlob共享vector。

4)在StrBlob的构造函数,初始化共享的数据(记住make_shared是在动态内存(调用构造函数)申请空间)。

5)在对类进行操作时,一些做相同操作的动作,可以提取成函数放在private域里面。

6)对StrBlob对象进行拷贝、赋值时,默认做法是拷贝数据成员。所以会拷贝shared_ptr,从而指向相同对象。至于对象之前指向的,则递减计数器。


1.2、直接管理内存

1、分配释放动态内存:new、delete

2、new返回一个指向新对象的指针。

int *pi = new int;//pi指向一个动态分配的,未初始化的无名对象

3、默认初始化:默认情况下,动态分配的对象是默认初始化的内置类型或组合类型的对象值是未定义的而类类型对象是用默认构造函数进行初始化

那么组合类型为何是未定义的?参照这里的描述可以略有了解,但还需要后面熟悉后再探究一下

1)嵌套构造:类的组合中,当创建类的对象时,如果这个类具有内嵌对象成员,那么各个内嵌对象将首先被自动创建
      先内嵌对象构造,然后再是本对象进行构造。在内嵌对象的构造中,初始化的顺序是根据内嵌对象在类中的声明顺序,而不是类的初始化列表的顺序。

2)特殊情况:在组合类的构造中有特殊的情况,比如有些内嵌对象没有在构造函数的初始化列表中,

Clockx(const Clock&clock):c(clock){}//这个意思是内嵌对象在构造函数的初始化列表中
Clock c;

那么该内嵌对象的默认构造函数将会被执行。记住是默认构造函数,如果我们自己重新定义了构造函数的话,这个默认构造函数就不存在了,而默认会调用我们定义的构造函数,并且这个构造函数不能带参数。

3)需要用初始化列表:然而需要注意的是,有些数据成员的初始化必须在构造函数的初始化列表中进行。一类是没有默认构造函数的内嵌对象—–因为这类对象初始化时必须提供参数,另一类是引用类型的数据成员—–因为引用型变量必须在初始化时绑定引用的对象。引用型变量就是带&的对象声明。

另有一个地方也在强调:https://blog.csdn.net/forestround/article/details/52726984

 Line(Point pstart, Point pend):start(pstart), end(pend) // 组合类的构造函数对内前对象成员的初始化必须采用初始化列表形式  

所以说,组合类的初始化还很复杂。但疑问是:

1)如果需要采用列表初始化,但本对象初始化没有输入参数,是表现为编译报错,还是结果未定?

2)对于都可以用默认构造函数来初始化的类对象,是否结果就不是未定义的了?


4、直接初始化

使用传统的构造方式(圆括号),也可以使用列表初始化(花括号)

int *pi = new int(1024);//int初始化为1024,pi指向
string *ps = new string(10,'a');//字符串“aaaaaaaaaa”,ps指向
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9}//列表初始化,新vector,有10个值,pv指向
5、值初始化

上下这两个叫法有些拗口。

在类型名后面加一个空括号。主要对内置类型有作用。

string *ps1 = new string;//默认初始化为空string

string *ps2 = new string();//值初始化为空string,和上面一样

int *pi1 = new int;//*pi1值未定

int *pi2 = new int();//*pi2为0

说明:对于定义了自己的构造函数的类类型,要求值初始化是没有意义的,对象都会通过默认构造函数来初始化。

对于内置类型,差距大:值初始化的内置类型对象由良好的定义的值,默认初始化时未定义的

建议:对动态分配的对象进行初始化


6、动态分配的const对象

1)可以用new分配一个const对象

//分配并初始化一个const int

const int *pci = new const int[1024];

//分配并默认初始化一个const的空string。问题:一个空的srtring有什么意义。pcs应该是不能再次赋值的。那空stirng有何用?从下面的分析看,空就空了。这里只是示意。

const string * pcs = new const string;

这里需要澄清一下:const指针与指向const对象的指针

const指针:此指针指向的地址是不能够改变的,但是其指向的对象是可以被修改的

int b=12;  

int* const a=&b; 

这里的a是const指针。

继续:

   int c=2;  
   a=&c; 

就出错了。因为a不能再指向&c;


指向const对象的指针其指向的对象是const的,不能被修改,但是其本身并不是const的,可以修改其指向的地址。

const int* a;  
int c=2;  
a=&c;  //可以
*a=4;  //不行

7、内存耗尽

1)一旦内存耗尽,new会失败。默认情况下,会抛出类型为bad_alloc的异常

2)定位new表达式:可改变使用new的方式。允许向new传递额外参数。

int *p2 = new (nothrow) int;//如果分配失败,只返回空指针,不抛出异常。

不过目前看上去,还没感觉到有什么特别作用。

8、释放动态内存

1)delete,接受一个指针,指向要释放的对象。

2)delete的指针,必须是动态分配的内存,或者是一个空指针(释放空指针没错)。

3)释放一个非new出来的空间,或者将相同指针释放多次,行为未定义(也就是不合适,编译可能不报错,但实际有问题,需要消除)

int i, *pi1 = &i, *pi2 = nullptr;
delete *pd =new double(33), *pd2 = pd;
delete i; //错误,i不是指针
delete pi1;//未定义,它指向的是局部变量,不是动态申请内存。
delete pd;//正确,new出来的空间
delete pd2;//未定义,重复释放
delete pi2;//正确,释放空指针。

9、动态对象的生存期直到被释放为止。意思是得显式去释放,会存在遗漏。推荐只使用智能指针。

如果一定要用,建议在指针即将离开其作用域之前释放它关联的内存,更进一步可以设置为nullptr,更安全。

但保护有限。多个指针指向同一个对象,有些并不能及时被设置为nullptr,还会引用,会出问题。

1.3、shared_ptr和new结合使用

1、除了用make_shared库函数来初始化shared_ptr,还可以用new。

sharee_ptr<int> p2(new int(43));//p2指向一个值为43的int。
2、接受参数的智能指针构造函数时explicit(看到再理解)的,不支持隐式转换,必须使用 直接初始化形式
shared_ptr<int> p1 = new int(1024);//错误,不能将普通指针转换为智能指针。
shared_ptr<int? p2(new int(1024));//正确,用直接初始化形式。

3、同样,也不能再返回值上做隐式转换

shared_ptr<int> clone(int p)
{
    return net int(p);//错误
}

shared_ptr<int> clone(int p)
{
    return shared_ptr(new int(p));//正确
}

4、默认下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放它关联的对象。如果要指向其他类型,需要提供自己操作代替delete。(后面说)

5、shared_ptr的一些方法

shared_ptr<T> p(q)  : p管理内置指针q所指向的对象;q必须为new分配内存,且能够转换为T*类型(上面例子就是)

shared_ptr<T> p(u):p从unique_ptr那里接管对象所有权,把u置空(因为不唯一了)

shared_ptr<T> p(q,d): p管理内置指针q所指向的对象(注意,这里没强调是new分配内存),q必须能够转换为T*类型。p将使用可调用对象d(lambda函数)来代替delete。

shared_ptr<T> p(p2,d):p是shared_ptr p2的拷贝,唯一区别是p将用可调用对象来代替delete。

p.reset()   

p.reset(q)

p.reset(q,d):  如果p是唯一指向对象的shared_ptr,调用reset会释放此对象。如果带了可选的参数内置指针q,会另p指向q,否则会设p为空。(---前面这部分逻辑比较啰嗦。)d是用来代替delete。


6、不要混合使用普通指针和智能指针。

1)shared_ptr可以协调对象的析构,但仅限于自身的拷贝(也是shared_ptr)之间。这是为什么;推荐使用make_shared,而不是new。这样,在分配对象时就与shared_ptr绑定,避免无意中将同一个内存绑定到多个独立创建的shared_ptr上。

但目前来看,用new创建shared_ptr,采用直接初始化的形式,直接就关联了,好像不存在将对象绑定到多个shared_ptr上的机会?留个疑问。下面应该是解释这个。


先定义一个对shared_ptr操作的函数

void process(shared_ptr<int> ptr)
{
    //使用ptr
 }//ptr离开作用域,被销毁

process是采用传值方式,因此实参会被拷贝到ptr中。 拷贝一个shared_ptr会增加引用计数,所以,在process运行过程中,ptr的引用计数至少为2。当退出process时,ptr引用计数减1,局部ptr销毁。

其正确调用方式是:

   

shared_ptr<int> p(new int(33));//创建一个shared_ptr
process(p)//传递参数,引用计数为2,调用完毕后,引用计数为1
int i = *p;//因为引用计数为1,所以还可用,正确。
上面做法是正确的,创建指针,做参数传递,都没问题。

看看下面的:

int *x(new int(1024));//一个普通指针,问题,和int *想= new int(1024)有何区别?
process(x);//错误,不能自动转换类型
process(shared_ptr<int>(x));//合法。将*x转换为shared_ptr,在process内部,计数器变为1,退出process是,减为0,则释放了对象。
int j=*x;//到这里时,未定义了。

结论就是:当将一个shared_ptr绑定到一个普通指针时,就不应该再用内置指针来访问所指向内存了


7、不要使用get初始化另外一个智能指针或为智能指针赋值

1)智能指针有一个get函数返回一个内置指针,指向智能指针管理的对象。

2)所以,相当于会走捷径,窃取对象。不要通过这个方式主动delete对象,不然智能指针就错乱了。同时,也不要用get初始化、赋值智能指针。因为相互之间的引用计数同步无法完成。


8、其他shared_ptr操作

shared_ptr<int> p(new int44));//定义p为智能指针

p = new int(1024);//错误,不能直接赋值
p.reset(net int(1024));//可以。

reset通常与unique一起用。


if(!p.unique())//不是唯一用户,说明还有其他shared_ptr指向对象
   p.reset(new string(*p));//则复制一个拷贝

*p+=newVal;//改变对象的值

1.4、智能指针和异常

1、如果使用智能指针,即使程序块过早结束,也能保证内存在不需要的时候被释放

2、而直接管理的内存,可能没有机会去调用delete释放。

3、对于那些分配资源,但没有定义析构函数得类,会出现忘记释放的问题。用智能指针可有效解决这个问题。

void end_connection(connection *p) {disconnect(*p);}//定义一个队类进行操作的删除器
void f(destination &d,....)
{
   connection c = connect(&d);
  shared_ptr<connection> p(&c,end_connection);//这里用一个智能指针,指向新类定义,并传递一个删除器作为参数。可以确保,在异常退出时,会调用shared_ptr的删除器,从而解决这个问题。
}

上面是挺机智的一个用法。


4、注意智能指针的规范

1)不适用相同的内置指针初始化(或reset)多个智能指针(----计数器不同步了)

2)不delete get()返回的指针

3)不适用get()初始化或reset一个智能指针

4)如果使用get()返回指针,当对象对应的最后一个智能指针销毁后,指针就空悬了。

5)如果使用智能指针管理的资源不是new分配的内存,记住要有一个删除器(因为简单的delete不管用了)

1.5 unique_ptr

1、某个时刻只能有一个unique_ptr指向一个给定对象。unique_ptr被销毁时,所指向对象也被销毁。

2、unique_ptr初始化只有一种方式(没有make_shared),需要将其绑定到一个new返回的指针,必须采取直接初始化的形式。

unique_ptr<double> p1;
unique_ptr<int> p2(new int(23));

unique_ptr的独占性,赋值和拷贝都不允许。

unique_ptr<string> p1(new string("aa"));
unique_ptr<string> p2(p1);//错误
unique_ptr<string> P3;
P3=P2;//错误

3、unique_ptr的一些操作

unique_ptr<t> u1

unique_ptr<T,D> u2 :u2使用一个类型为D的可调用对象来释放它的指针

unique_ptr<T,D> u(d):用类型为D的对象d来代替delete

u = nullptr  :释放u指向的对象,将u置空。----注意,这里将对象释放了

u.release():u放弃对指针的控制权,返回指针,并将u置为空---注意,这里只是去了控制权,但没有释放。所以通过这个函数可以转移控制权

u.reset():释放u指向的对象---注意,没有置为空

u.reset(q):如果提供了内置的q,则u指向q;否则u为空

u.reset(nullptr):---如果释放对象,最好这样调用,即释放对象,又置为空


4、有了上述几个函数,虽然uniuque_ptr不能赋值或拷贝,但可以转移。


//p1释放控制权,并置为null,但返回了指针,用来构造p2,
unique_ptr<string> p2(p1.release());
unique_ptr<string> p3(new string("aa"));
//同样,p3释放,通过reset调用,让p2指p3
p2.reset(p3.release());

5、注意:调用release会切断unique_ptr与原来管理对象的联系。通常用release返回的指针去初始化/赋值另一个智能指针。

如果没有被赋值或保存,我们需要记得自己释放。

p2.release();//错误,p2指向的对象再找不到了。
auto p = p2.release();//被转交给p了。我们需要记得delete(p);

7、例外:可以拷贝或赋值一个即将要被销毁的unique_ptr。编译器自己判断。

看例子,下面两个都对。

unique_ptr<int> clone(int p)
{
    //临时创建,然后返回
    return unique_ptr<int> (new int(p));
}

unique_ptr<int> clone(int p)
{
    unique_ptr<int> ret(new int(p));
    return ret;//局部变量
}

8、关于unique_ptr的删除器,和shared_ptr的不一样。

1)在尖括号指定删除器类型

2)在创建或reset时,提供指定类型可调用对象。


void 发(destination &d)
{
    connection c  = connect(&d);//打开连接
    //当f退出(含异常),connection会被正确关闭
unique_ptr<connection,decltype(end_connection)*> p(&c,end_connnection);

这里用decltype(返回指定数据的类型),因为是一个函数指针,用了*。


1.6 weak_ptr

1、weak确实 要弱一些。指向一个shareed_ptr管理的对象,但不控制对象的生存周期(不改变引用计数)。

2、weak_ptr是为了配合shared_ptr而引入的一种智能指针,它更像是shared_ptr的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况.

3、相关操作

weak_ptr<T> w  :空weak_ptr指针,可以指向类型为T的对象

wak_ptr<T> w(sp):与shared_ptr sp指向相同的对象。T必须能转化为sp指向的类型

w=p:p可以是一个shared_ptr或weak_ptr,赋值后w与p共享对象

w.reset():将w置为空

w.use_count():与w共享对象的shared_ptr数量

w.expired():如果w.use_count为0,返回ture。否则返回false。

w.lock():如果expired为true,返回一个空shared_ptr,否则返回一个指向w对象的shared_ptr。--这个有点特别本质是w虽然指向对象,但不操作对象,需要获取真正的shared_ptr来操作。由于对象可能不存在,不能用weak_ptr直接访问对象,必须调用lock。


if(shared_ptr<int> np=wp.lock()){
    //在if中,np与wp共享对象
}

4、核查指针类(weak_ptr的一个示意)

1)为一个类定义一个伴随指针类。它保存一个weak_ptr,指向主类的数据成员。

2)通过对weak_ptr调用lock来判断。如果返回空指针,说明对象已经销毁;如果返回shared_ptr,则可用


2、动态数组(不推荐,推荐使用标准容器)

1、某些应用需要一次为很对多想分配内存。

2、C++提供了两种方法:new、allocator类。

3、通常不采用动态数组的方法,用vector(或其他标准容器)更简单高效安全

2.1new和数组

1、用new分配数组的例子

int *pi = new int[get_size()];//调用get_size()确定分配多少个int,指针指向第一个int。

也可以

typedef int arrT[100];//定义别名,arrT来代替int[100];
int *p = new arrt;

2、new T[]分配的内存叫“动态数组”,但实际上并不是一个数组类型的对象。而是一个数组元素类型的指针。

所以,一些数组的操作在这里不适用。比如:

begin、end、for。

3、初始化方法

int *p1 = new int[10]; //10个未初始化int
int *p2 = new int[10](); //10个初始化程0的int
string *psa = new string[10];
int *psa = new string[10]*psa = new string[10](); //10个空string

//列表初始化
int *p3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
//10个string,前4个指定初始化,余下的进行值初始化。
string *psa = new string[10]{"a","bb","ccc",string(3,'xx')};

4、如果初始化器数目小于元素数目,剩余元素将进行值初始化。如果初始化器大于元素数目,会失败,抛出异常。bac_array_length。

5、可以用空括号对元素进行值初始化,但不能再括号中给出初始化器(意思是只能是空括号。)。

6、动态分配一个空数组是合法的。返回一个非空指针。但没啥用。

char *cp = new int[0];//真确,但cp不能解引用(*cp不能用)

7、删除动态数组,要用[]

delete []p;

8、智能指针和动态数组

1)unique_ptr能配合在一起

unique_ptr(int []) up(new int[10]);//up指向一个包含10个未初始化int的的数组
up.release();//自动调用delete []销毁指针

up指向的是一个int数组而不是一个int,所以delete时,也是的调用delete[]。

9、当unique_ptr指向是一个数组时,可用操作并不多。不支持点、箭头的运算符。

unique_ptr<T []> u:u指向一个动态分配的数组,数组元素类型为T

unique_ptr<T[]> u(p):u指向内置指针p所指向的动态分配的数组。p必须能转化为类型T*。---相当于是赋值、初始化。

u[i]:返回u拥有数组位置i的对象。

10、shared_ptr不直接支持管理动态数组。如果希望使用shared_ptr管理动态数组,必须提供删除器。

shared_ptr<int> sp(new int[10],[](int *p){delete []p;}
sp.reset();//使用lambda释放数组。
如果未提供删除器,这段代码是未定义的


因为sharred_ptr未定下标运算,而且只能指针类型不支持指针算术运算。因此,为了访问数组中的元素,必须用get获取一个内置指针,然后用它来访问数组元素

2.2 allocator类

1、new把内存分配和对象构造组合在一起;delete把对象析构和内存释放组合在一起。不够灵活。

1)有些对象创建了,可能一直用不着。

2)每个元素被赋值2次:第一次是默认初始化,第二次是赋值。

3)没有默认构造函数的类不能动态分配数组。

2、这里要解决的是内存分配和对象构造分离的问题。

3、引入allocator类(标准库)

1)提供一种类型感知的内存分配方法,分配的内存时原始的、未构造的。

2)allocator是一个模板。

3)可用操作包括:

allocator<T> a  :定义一个名为a的allocator对象,可以为类型为T的对象分配内存。

a.allocate(n):分配一段原始的、未构造的内存,保存n个类型为T的对象。

a.deallocate(p,n):释放从T*指针p中地址开始的内存,这个内存保存了n个类型为T的对象。p是之前allocate返回的指针,n必须是p创建时要求的大小。在调用deallocate之前,必须对每个在这块内存中创建的对象调用destory

a.construct(p,args):p是一个类型为T*的指针,指向一块原始内存;arg被传递给类型为T的构造函数,在p指向内存中构造一个对象。

a.destory(p):对p指向对象执行析构函数。

4)

为定义一个allocator对象,必须指明这个allocator可以分配的对象类型
//----还得指定啊,不像C的malloc,不管怎么先弄一块内存。
allocator<string> alloc;//可以分配string的alloctor对象
auto const p = alloc.allocate(n);//分配n个未初始化的string

auto q=p;
alloc.construct(q++);  //*q为空字符串
alloc.construct(q++,10,'c');//*q为cccccccccc
alloc.construct(q++,"123");//*q为123

对未构造的对象进行使用,是错误的。

5)只能对真正构造了的元素进行destory

while(q!=p)
    alloc.destory(--q);//逐一释放。

一旦元素被销毁,可以重新使用这部分内存来保存其他string,也可以归还给系统(调用deallocate完成)

6)uninitialized_copy&uninitialized_fill。

1)是allocator类的两个伴随算法,用于在未初始化内存中创建对象。

uninitialized_copy(b,e,b2):

uninitialized_copy_n(b,n,b2):

uninitialized_fill(b,e,t):

uninitialized_fill_n(b,n,t):

感觉这几个比较偏门,用例子来看吧。



例子:有一个int的vector,希望将内容拷贝到动态内存中。

//分配比vi中元素占用空间大一倍的动态内存(自己要保证够大,能放得下)
auto p = alloc.allocate(vi.size()*2);

//拷贝vi中元素,q记录的应该是初始化到哪里了。
auto q = uninitialized_copy(vi.begin(),vi.end(),p);

//初始化剩余的元素为42
uninitialized_fill_n(q,vi.size(),42);



--------------------------

后面的例子,是对标准库的使用的一个综合例子。因前面的章节还没看,暂时不搞了。后面学完再单独一章来学。

到此,C++Primer的第12章学了一遍。也折腾了好几天了,断断续续。概念了解了不少,收获挺大。

这篇就暂时到此吧。

2018.5.12 0:02

深圳

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值