如何在C++中使用new运算符初始化内存?

204

我刚开始学习 C++,想养成良好的编程习惯。如果我使用 new 运算符分配了一个 int 类型的数组,如何才能初始化所有元素为0而不必自己循环遍历数组?我应该使用 memset 吗?还有没有更“C++”方式来实现?


20
如果你想养成良好的C++习惯,那么避免直接使用数组,转而使用向量(vector)代替。向量会初始化所有的项目,无论它们的类型是什么,这样你就不需要记得调用delete[]操作符。 - brianegge
@brianegge:如果我需要将一个数组传递给外部的C函数,我能直接将它传递给向量吗? - dreamlax
12
你可以传递 &vector[0] - jamesdlin
当你将数组传递给C函数时,通常需要指定第一个元素的指针&vector[0],就像@jamesdlin所说的那样,并且数组的大小由vector.size()提供。 - Trebor Rude
相关(要求非数组类型):https://dev59.com/c2sz5IYBdhLWcg3w9ssQ - Aconcagua
10个回答

459

这是一个关于C++的鲜为人知的特性(如此少有人提到,可见度极低),但它实际上有用于对数组进行值初始化的特殊语法:

new int[10]();

请注意,您必须使用空括号 —— 不能使用 (0) 或任何其他形式(这就是为什么它只用于值初始化的原因)。
ISO C++03 5.3.4[expr.new]/15 明确允许这样做,该规范说明:
新表达式创建类型为 T 的对象并将其初始化如下:
...
如果新初始化程序采用 () 形式,则项目进行值初始化 (8.5);
而且不限制可允许此操作的类型,而 (expression-list) 形式则受到同一部分中进一步规则的明确限制,因此不允许数组类型。

2
虽然我同意这是鲜为人知的,但我不能(完全)同意这真的非常令人惊讶——它是在C++ 03中添加的,大多数人似乎几乎忽略了它(因为这是它所添加的少数新功能之一)。 - Jerry Coffin
2
@Jerry:我必须承认,我之前还不知道这个(可能是因为当我开始阅读标准时,已经是C++03了)。话虽如此,值得注意的是,我所知道的所有实现都支持这个(我猜这是因为它很容易实现)。 - Pavel Minaev
3
实现起来非常简单。就新颖程度而言,C++03中的所有“值初始化”都是新的。 - Jerry Coffin
52
在C++11中,您也可以使用统一初始化方式:new int[10] {}。您还可以提供要初始化的值:new int[10]{1, 2, 3} - bames53
1
请不要混淆默认初始化和值初始化:它们在标准中都有明确定义,并且是不同的初始化方式。 - Deduplicator

27

有多种方法可以分配基本类型的数组,这些方法都是正确的,但选择哪种方法取决于...

通过循环手动初始化所有元素

int* p = new int[10];
for (int i = 0; i < 10; i++)
    p[i] = 0;

使用来自<cstring>std::memset函数

int* p = new int[10];
std::memset(p, 0, sizeof *p * 10);

使用来自<algorithm>std::fill_n算法

int* p = new int[10];
std::fill_n(p, 10, 0);

使用 std::vector 容器
std::vector<int> v(10); // elements zero'ed

如果C++11可用,则使用初始化列表功能。
int a[] = { 1, 2, 3 }; // 3-element static size array
vector<int> v = { 1, 2, 3 }; // 3-element array but vector is resizeable in runtime

1
应该使用vector<int>,如果你添加p = new int10,那么你就有了一个完整的列表。 - karsten
@mloskot,在您使用“new”初始化数组的第一种情况下,如何进行引用传递?如果我使用int array[SIZE] ={1,2,3,4,5,6,7};表示法,则可以使用void rotateArray(int (& input)[SIZE], unsigned int k);作为我的函数声明,那么在使用第一种约定时会发生什么?有什么建议吗? - Anu
2
我担心使用std::memset的示例是错误的 - 您传递了10,它似乎期望字节数 - 请参见https://en.cppreference.com/w/cpp/string/byte/memset。(我认为这很好地展示了为什么应尽可能避免使用这种低级构造。) - Suma
@Suma 很好的发现!已经修复了。这似乎是一个十年前的 bug 的候选者 :-) 是的,我同意你的评论。 - mloskot

26

假设你真的需要一个数组而不是 std::vector, "C++方式"就是这样

#include <algorithm> 

int* array = new int[n]; // Assuming "n" is a pre-existing variable

std::fill_n(array, n, 0); 

请注意,在底层实际上仍然只是一个循环,将每个元素赋值为0(除非具有硬件级别支持的特殊体系结构,否则没有其他方法可行)。


我不介意循环是在函数下面实现的,我只是想知道是否需要自己实现这样的循环。谢谢你的提示。 - dreamlax
4
你可能会感到惊讶,我也是。在我的 STL(包括 GCC 和 Dinkumware),如果检测到 std::copy 被调用时使用的是内置类型,它实际上会转换为 memcpy。如果 std::fill_n 使用 memset,我也不会感到意外。 - Brian Neal
2
不要这样做。使用“值初始化”将所有成员设置为0。 - Martin York
int * array = new int[n]() - 8.8.8.8

21

初始化动态数组的可能方式。根据您的要求选择其中一种方式。

int* x = new int[5];          // gv gv gv gv gv (gv - garbage value)
int* x = new int[5]();        // 0  0  0  0  0 
int* x = new int[5]{};        // 0  0  0  0  0  (Modern C++)
int* x = new int[5]{1,2,3};   // 1  2  3  0  0  (Modern C++)

8

如果您要分配的内存是具有执行某些有用操作的构造函数的类,则运算符new将调用该构造函数并使对象初始化。

但是,如果您要分配一个POD或者没有初始化对象状态的构造函数,则不能使用运算符new在一次操作中分配内存并初始化该内存。但是,您有几个选项:

  1. Use a stack variable instead. You can allocate and default-initialize in one step, like this:

     int vals[100] = {0}; // first element is a matter of style
    
  2. use memset(). Note that if the object you are allocating is not a POD, memsetting it is a bad idea. One specific example is if you memset a class that has virtual functions, you will blow away the vtable and leave your object in an unusable state.

  3. Many operating systems have calls that do what you want - allocate on a heap and initialize the data to something. A Windows example would be VirtualAlloc().

  4. This is usually the best option. Avoid having to manage the memory yourself at all. You can use STL containers to do just about anything you would do with raw memory, including allocating and initializing all in one fell swoop:

     std::vector<int> myInts(100, 0); // creates a vector of 100 ints, all set to zero
    

7
是的,有这个功能:
std::vector<int> vec(SIZE, 0);

使用向量而不是动态分配数组。好处包括不必费心显式删除数组(当向量超出范围时会被删除),即使抛出异常,内存也会自动删除。
编辑:为了避免进一步的匆忙投票,这个答案并没有说vector总是正确的答案。但它肯定比“手动”确保删除一个数组更符合C++的方式。
现在有了C++11,还有std::array,它模拟了一个常量大小的数组(与能够增长的向量相对)。还有std::unique_ptr,它管理动态分配的数组(可以与其他答案中回答的初始化组合使用)。在我看来,任何一个都比手动处理指向数组的指针更符合C++的方式。

11
这并不真正回答所问的问题。 - John Knoeller
1
我应该总是使用std::vector而不是动态分配的数组吗?使用数组和向量有什么好处,反之亦然? - dreamlax
1
@John Knoller:OP 询问了 C++ 的实现方式,我认为 vector 是这个问题的 C++ 实现方式。当然,你说得对,在某些情况下仍可能需要使用普通数组,而不知道 OP 的具体情况,这也许是其中之一。但我猜测不会这样,因为 OP 似乎不知道 vector。 - villintehaspam
2
@villintehaspam:不,这不是C++的做法。这是Java的做法。无论上下文如何,到处粘贴“vector”被称为“在C++中编写Java代码”。 - AnT stands with Russia
1
@AndreyT:我同意总是使用向量而不是动态分配数组并不是一个好的说法。然而,我仍然会认为使用向量而不是动态分配数组是C++的做法,除非有特殊原因使用动态分配数组,否则应该绝对是首选。这不是编写Java代码,而是以最小的努力和更低的错误风险完成工作。 - villintehaspam
显示剩余9条评论

2

std::fill是其中一种方法。需要两个迭代器和一个值来填充该区域。我认为,这或者使用for循环才是更C++的方式。

对于将基本整数类型的数组设置为0,memset是可以的,尽管可能会引起注意。考虑使用calloc,但由于需要进行转换,因此在C++中使用有点不方便。

就我而言,我几乎总是使用循环。

(我不喜欢去猜测别人的意图,但事实上,所有条件相等时,使用std::vector比使用new[]更可取。)


2

你可以随时使用memset:

int myArray[10];
memset( myArray, 0, 10 * sizeof( int ));

@gbrandt - 虽然这可能是真的,但我不会说这是“C++的方式”。有更多“被接受”的做法。 - Smashery
1
这并不是真正的“C++方式”,但原始数组也不是。 - Pete Kirkham
1
@gbrandt:也就是说,在C或C++中它的效果并不好。它适用于大多数char或unsigned char类型的值。对于值为0的大多数类型,它也可以使用(至少在大多数实现中)。否则,它通常是无用的。 - Jerry Coffin
1
10 * sizeof( *myArray )10 * sizeof( int )更具有文档化和可变性。 - Kevin Reid
1
无论如何,OP拥有一个原始数组,而memset是将该数组清零的最快和最简单的方法。 - Gregor Brandt
显示剩余2条评论

0

对于C++,使用std::array<int/*类型*/, 10/*大小*/>代替C风格数组。这是C++11标准中提供的,并且是一个好的实践方法。在这里查看标准和示例。如果出于某些原因您想坚持使用旧的C风格数组,则有两种可能的方法:

  1. int *a = new int[5](); 这里将括号留空,否则会导致编译错误。这将初始化分配数组中的所有元素。如果您不使用括号,它仍将使用零初始化整数值,因为new将调用构造函数,在这种情况下是int()
  2. int *a = new int[5] {0, 0, 0}; 这在C++11标准中是允许的。在这里,您可以使用任何您想要的值来初始化数组元素。在这里,请确保您的初始化列表({}中的值)大小不应大于您的数组大小。初始化列表大小小于数组大小是可以的。数组中剩余的值将被初始化为0。

std::array<> 的大小必须在编译时知道。无法根据在运行时确定的大小声明 std::array<>,因此在使用 new[]std::vector<> 时,不能总是实用或可行地使用 std::array<> - dreamlax
@dreamlax 是的,我完全同意你的观点。我们可以使用 std::array<> 中的固定大小数组,其大小必须在编译时已知。 - Neeraj C. Usadadiya

-1
通常用于动态列表的项目,您可以使用 std::vector
一般情况下,我会使用 memset 或循环进行原始内存动态分配,具体取决于我预计代码区域在未来会有多大变化。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接