🌽动态数组
2022-5-30
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property

 
有时候希望在程序中可以一次性分配一个数组的内存。C++提供了两种方式来这样做:通过新的new ,或者通过模板类allocator来分离分配和初始化过程。一般来说 allocator 将得到更好的性能以及更灵活的内存管理。
然而,除非是希望直接管理动态数组,使用标准库中容器通常是更好的方式。使用容器将更加简单,更少的 bug 而且具有更好的性能。而且,可以使用默认定义的拷贝、赋值和析构函数。而动态分配数组的类则需要自己定义这些函数来进行相应的内存管理。
 

new 和数组

为了让new 分配一个对象数组,需要在类型名之后跟一对方括号,在其中指明要分配的对象的数目。
通过在类型后给出数组的长度,new创建出一个动态数组,并返回指向首元素的指针。
方括号中的值必须是一个整数,但不必是常量。
 
也可以用一个类型别名来动态分配数组,虽然没有用到方括号,但依然是数组形式的 new[]
当用 new 分配数组时,返回的并不是数组类型,而是指向首元素的指针。即使使用类型别名,返回的数据类型依然是元素的指针类型。这种情况下分配数组的事实表面上是不可见的,因为没有 [num],即使这样 new 返回的依然是元素的指针。
由于分配的内存不是数组类型,所以根本不能调用 beginend标准函数。这些函数需要知道数组的维度才能返回指向首元素和尾后元素的指针。由于同样的原因,不能对动态数组使用范围for
 

初始化动态分配的数组

通常由new分配的对象,不论是单个对象还是数组,都是默认初始化的。可以在分配数组后加上括号使其进行值初始化:
C++11允许使用括弧中的值对动态分配的数组进行列初始化:
如果给的值比数组的长度小,其余的元素将被值初始化。如果多于数组长度,则无法编译通过。值得注意的是不能在括号中填入初始值来初始化元素。虽然用空括号对数组中元素进行值初始化,但不能在括号中给出初始化器,这意味着不能用auto分配数组。
 
动态创建空数组是合法的,当用0作为数组长度而执行new操作时,返回的合法的非 0 指针。这个指针被保证不与任何其它new返回的指针值一样。这个指针类似于尾后指针,其可以与其它指针进行比较、可以加减 0、可以减去自己产生 0,但是不能用此指针进行解引用。
可以用任意表达式来确定要分配的对象的数目:
 

释放动态数组

与释放单个对象不一样的是,释放动态数组必须加上 [] 来指示当前释放的是数组:
 
即便是对于使用了类型别名的数组,其被释放时依然需要使用方括号。原因在于指针指向的首元素,而不是类型别名表面上的类型对象。
编译器并不会在我们释放数组内存时忘记给出方括号而提示我们,或者当释放单个对象时却添加了方括号。程序可能在执行过程中在没有任何警告的情况下行为异常
 
 

智能指针和动态数组

标准库提供了数组版本的 unique_ptr 来管理动态数组的内存。为了使用这个类,需要在模板参数中的对象类型中加上一对空方括号:
当 unique_ptr 指向数组时,不能调用箭头或点号进行成员访问。毕竟,它指向的是一个数组而不是单个对象。另一方面,可以使用此指针进行下标操作来访问数组中的元素。如:
以下是指向数组的 unique_ptr 特有的操作,除了不支持成员访问外,其它的操作与指向单个对象的 unique_ptr 是一样的:
notion image
与 unique_ptr 不同,shared_ptr 没有提供直接管理动态数组的支持,如果想要使用 shared_ptr 就必须得自己提供删除器:
 
 

allocator 类

限制new使用的原因是new既分配内存又要构建对象。相同的,delete既要析构又要释放内存。当动态创建对象时,这种方式是合适的。但当我们需要分配一大块内存时,通常会在之后需要的时候进行构建对象,在这种情况下最好是将分配和构建分离开来。以下演示new的局限性:
new表达式分配并初始化了nstring。但是,我们可能不需要nstring,少量string可能就足够了。这样,就可能创建了一些永远也用不到的对象。而且,对于那些确实要使用的对象,初始化之后立即赋予了它们新值。每个使用到的元素都被赋值了两次:第一次是在默认初始化时,随后是在赋值时。
更重要的是,那些没有默认构造函数的类就不能动态分配数组了。
 
 
memory 头文件中定义了 allocator 类,其可以将分配和构建分开。它提供类型识别的分配未构建的内存。
notion image
allocator是模板类,定义allocator时需要提供对象类型作为模板参数:
 
allocator分配的内存是未构建的。新标准中允许调用construct成员函数来在指定位置构建对象。
 
使用没有构建的对象是一种错误。必须在构建之后才能使用对象。当使用完毕后必须调用destroy进行析构,destroy函数以指向对象的指针为参数,调用其析构函数:
 
只能对已经构建的对象进行析构,如果对未构建过的对象进行析构结果将是未定义的。已经被析构的对象占用的内存,可以被用于别的对象,或者将其返回给系统。通过 deallocate 来释放整个内存:
传递给deallocate的指针一定不能是空指针,且必须指向由allocate分配内存所返回的指针,并且n必须与传递给allocate进行分配时一致
 
 
复制和填充未初始化的内存
标准库还为allocator 类定义了两个伴随算法,可以在未初始化内存中创建对象。以下这些函数将在目的地构建元素,而不是给它们赋值:
notion image
copy 不同的是以上 uninitialized_copy 是在目的地进行构建而非赋值,与 copy 一样,它也返回递增后的目的地迭代器。
假定有一个intvector,希望将其内容拷贝的动态内存中。分配一块比vector中元素所占空间大一倍的动态内存,然后将原vector中的元素拷贝到前一半空间,对后一半空间用一个给定值进行填充:
 
  • C++
  • 智能指针拷贝、赋值与析构
    目录