美文网首页
【记4】动态数组的两种分配方式

【记4】动态数组的两种分配方式

作者: CozyMoonquake | 来源:发表于2019-06-27 11:25 被阅读0次

在C++这个文集中太久没更新了,今天刚好在看C++的基础知识,就顺便来写一下关于allocator类的学习记录。

前言

我们都知道,new/delete一次分配/释放一个对象,但某些应用可能需要一次为很多对象分配内存。比如,vector和string都是在连续内存中保存它们的元素,因此当容器需要重新分配内存时,必须一次性为很多元素分配内存。
为支持这种需求,C++语言和标准库提供了两种一次分配一个对象数组的方法。一种是C++语言定义了另一种new,可以分配并初始化一个对象数组;另一种是标准库中包含了一个名为allocator的类,允许我们将分配和初始化分离,使用allocator通常会提供更好的性能和更灵活的内存管理能力。
不过大多数应用都没有直接访问动态数组的需求,所以一般不采用动态数组的方法,使用vector(或其他标准库容器)更简单高效安全。

1. new和数组

1. 用new分配数组
int *p=new int[42];     //p指向第一个int

也可以

typedef int arr[42];    //类型别名,arr表示int[42]
int *p=new arr;
2. 分配一个数组会得到一个元素类型的指针

通常称new T[] 分配的内存为“动态数组”,但其实当用new分配一个数组时,并未得到一个数组类型的对象,而是得到一个数组元素类型的指针。因此不能对动态数组调用begin或end,也不能用范围for处理(所谓的)动态数组中的元素。
要记住:我们所说的动态数组并不是数组类型!

3. 初始化动态分配对象的数组
int *p1=new int[10];        //10个未初始化的int
int *p2=new int[10]();      //10个值初始化为0的int
string *p3=new string[10];          //10个空string
string *p4=new string[10]();        //同上

int *p5=new int[10]{1,2,3,4,5,6,7,8,9,10};              //列表初始化
int *p6=new string[10]{"a","bb","ca",string(3,'d')};    //前4个指定初始化,剩余的值初始化

如果初始化器数目大于元素数目,new表达式失败,不会分配任何内存,new会抛出bad_array_new_length异常。另外,可以用空括号对数组元素进行值初始化,但不能在括号中给出初始化器,所以不能用auto分配数组。

4. 动态分配一个空数组是合法的
size_t n=get_size();    //get_size返回需要的元素数目
int *p=new int[n];       //分配数组保存元素
for(int *t=p; t!=p+n; ++t)
   /*处理数组*/

如果get_size返回0,代码仍能正常工作,不过for循环不会被执行。虽然不能创建一个大小为0的静态数组对象,但当n为0时,调用new[n]是合法的:

int arr[0];              //错误!不能定义长度为0的数组
int *p=new int[0];        //正确,但p不能解引用,毕竟它不指向任何元素
6. 释放动态数组
delete p1;      //p1必须指向一个动态分配的对象或为空
delete []p2;    //p2必须指向一个动态分配的数组或为空,数组中元素按逆序销毁

2. allocator类

1. new/delete的使用不够灵活,可能导致不必要的浪费

new将内存分配和对象构造组合在一起,delete将对象析构和内存释放组合在一起。当分配一大块内存时,通常计划在这块内存上按需构造对象,因此希望将内存分配和对象构造分离。

int *const p=new int[n];      //构造n个空string
int i;
int *q=p;                     //q指向第一个string
while(cin>>i&&q!=p+n)
    *q++=i;                   //赋予*q新值
const size_t size=q-p;        //计算读取了多少个string
/*数组处理*/
delete []p;

new分配并初始化了n个string,但我们可能不需要n个string,这样我们就可能创建了一些永远也用不到的对象。而且,每个使用到的元素都被赋值了两次,一次是默认初始化,另一次是在赋值时。更重要的是,那些没有默认构造函数的类就不能动态分配数组了。

2. allocator类

标准库allocator类定义在头文件memory中,将内存分配和对象构造分离。allocator类提供一种类型感知的内存分配方法,其分配的内存是原始的、未构造的。类似vector,allocator也是一个模板,支持的操作如下:
(1) allocator<T> a:定义一个名为a的allocator对象,可以为类型为T的对象分配内存。
(2) a.allocate(n):分配一段原始的、未构造的内存,保存n个类型为T的对象。
(3) a.deallocate(p,n):释放从T*指针p中地址开始的内存,这个内存保存了n个类型为T的对象;p必须是之前allocate返回的指针,且n必须是p创建时要求的大小。在调用deallocate之前,必须对每个在这块内存中创建的对象调用destory。
(4) a.construct(p,args):p指向一块原始内存;args被传递给类型为T的构造函数,在p指向内存中构造一个对象。
(5) a.destory(p):对p指向对象执行析构函数。

3. allocator分配未构造的内存

allocator分配的内存是未构造的,我们可以按需在此内存中构造对象。

allocator<string> alloc;            //可以分配string的allocator对象
auto const p=alloc.allocate(n);     //分配n个未初始化的string

auto q=p;                            //q指向最后构造的元素之后的位置
alloc.construct(q++);                //*q为空字符串
alloc.construct(q++,10,'c');         //*q为cccccccccc
alloc.construct(q++,"hi");           //*q为hi

使用未构造的内存是错误的:

cout<<*p<<endl;              //正确:使用string的输出运算符
cout<<*q<<endl;              //灾难:q指向未构造的内存!

为使用allocate返回的内存,必须用construct构造对象。当用完对象后,必须对每个构造的元素调用destroy销毁它们。函数destroy接受一个指针,对指向的对象执行析构函数(只能对真正构造了的元素进行destroy操作)。

while(q!=p)
    alloc.destroy(--q);       //释放真正构造的string

一旦元素被销毁,就可以重新使用这部分内存来保存其它string,也可将其归还给系统,释放内存通过调用deallocate来完成:

alloc.deallocate(p,n);
4. 拷贝和填充未初始化内存的算法

标准库还为allocator类定义了两个伴随算法,定义在头文件memory中,可以在未初始化内存中创建对象:
(1) uninitialized_copy(b,e,b2):从迭代器b到e的范围内拷贝元素到迭代器b2指定的未构造的原始内存中,b2指向的内存必须足够大,能容纳输入序列中元素的拷贝。
(2) uninitialized_copy_n(b,n,b2):从迭代器b指向的元素的开始,拷贝n个元素到b2开始的内存中。
(3) uninitialized_fill(b,e,t):在迭代器b到e的原始内存范围内创建对象,对象的值均为t的拷贝。
(4) uninitialized_fill_n(b,n,t):从迭代器b指向的内存地址开始创建n个对象,b必须指向足够大的未构造的原始内存,能容纳给定数量的对象。
还是举个例子比较容易理解,假定有一个int的vector为vi,希望将其内容拷贝到动态内存中,操作如下:

auto p=alloc.allocate(vi.size()*2);                  //分配比vi中元素所占用空间大一倍的动态内存
auto q=uninitialized_copy(vi.begin(),vi.end(),p);     //通过拷贝vi中的元素来构造从p开始的元素
uninitialized_fill_n(p,vi.size(),42);                  //将剩余元素初始化为42

类似copy,一次uninitialized_copy调用会返回一个指针,指向最后一个构造的元素之后的位置。

动态内存先写到这里,算是对allocator类有了一个基本的了解了吧。

相关文章

  • 【记4】动态数组的两种分配方式

    在C++这个文集中太久没更新了,今天刚好在看C++的基础知识,就顺便来写一下关于allocator类的学习记录。 ...

  • 数组

    一、一维数组 数组的初始化 方式一:动态初始化(分配空间和赋值分开写) 1、声明 int arr[]; int[]...

  • 一维数组的创建及使用

    创建一维数组 1.声明,在用new运算符进行内存分配 有两种方式 例题 为数组分配内容 语法格式如下 例题 2.声...

  • 1. 线性表_顺序表

    1. 定义顺序表 通过分配内存的方式,可以分为两种顺序表 (1)静态分配 (2)动态分配 2. 增加,删除,按值查...

  • MyOwnWorld003

    数组的创建只有两种方式: 要么是静态初始化:数组类型[] 数组名 = {元素1,元素2}; 要么是动态初始化:...

  • 动态分配内存

    动态分配内存初始化的方式 初始化列表(list initialization)对于数组、类、结构体都是试用的。

  • 2017.9.14

    对象数组,指针,内存访问方式通过变量名地址访问,地址运算符,&、指针变量 概念,动态存储分配

  • iOS知识复习笔记(19)---数据结构和算法1

    数组和链表的区别 数组静态分配内存,链表动态分配内存 数组内存中连续,链表不连续 数组元素在栈区,链表在堆区 数组...

  • uniapp 添加两种或以上动态class(:class)

    如上图两种或以上个动态class用数组的方式添加,中间用逗号隔开。

  • Java中的数组

    数组是由同一类型对象护着基本数据组成。 数组是对象,先定义数组名字,用动态的方式分配数组空间,再初始化的。 每个数...

网友评论

      本文标题:【记4】动态数组的两种分配方式

      本文链接:https://www.haomeiwen.com/subject/hspccctx.html