Как инициализировать память новым оператором в C ++?


176

Я только начинаю изучать C ++ и хочу приобрести хорошие привычки. Если я только что выделил массив типа intс newоператором, как я могу инициализировать их все в 0, не просматривая их все самостоятельно? Должен ли я просто использовать memset? Есть ли «С ++» способ сделать это?


19
Если вы хотите развить хорошую привычку C ++, избегайте прямого использования массивов и используйте vector. Vector инициализирует все элементы независимо от типа, и вам не нужно забывать вызывать оператор delete [].
brianegge

@brianegge: Что, если мне нужно передать массив во внешнюю функцию C, я могу просто дать ему вектор?
сонлак

12
Вы можете пройти &vector[0].
Джеймсдлин

Конечно, когда вы передаете массивы в функции C, вам, как правило, нужно указывать указатель на первый элемент & vector [0], как сказал @jamesdlin, и размер массива, предоставляемый в этом случае vector.size ().
Требор Руд

Связанный (запрашивает не-массив типов): stackoverflow.com/questions/7546620/…
Aconcagua

Ответы:


392

Это удивительно малоизвестная особенность C ++ (о чем свидетельствует тот факт, что никто еще не дал это в качестве ответа), но на самом деле он имеет специальный синтаксис для инициализации значения массива:

new int[10]();

Обратите внимание, что вы должны использовать пустые скобки - вы не можете, например, использовать (0)или что-либо еще (именно поэтому это полезно только для инициализации значения).

Это явно разрешено ISO C ++ 03 5.3.4 [expr.new] / 15, который гласит:

Выражение new, создающее объект типа, Tинициализирует этот объект следующим образом:

...

  • Если новый инициализатор имеет форму (), элемент инициализируется значением (8.5);

и не ограничивает типы, для которых это разрешено, тогда как (expression-list)форма явно ограничена дополнительными правилами в том же разделе, так что она не допускает типы массивов.


1
Хотя я согласен с тем, что это малоизвестно, я не могу (полностью) согласиться с тем, что это действительно очень удивительно - он был добавлен в C ++ 03, который большинство людей, похоже, почти проигнорировали (поскольку это была одна из немногих новых вещей это добавило).
Джерри Коффин

2
@ Джерри: Я должен признать, что я еще не знал (вероятно, потому что, когда я начал читать стандарт, это был уже C ++ 03). Тем не менее, замечательно, что все известные мне реализации поддерживают это (я думаю, это потому, что это так тривиально реализовать).
Павел Минаев

2
Да, это довольно просто реализовать. Насколько новые, вся «инициализация значения» была новой в C ++ 03.
Джерри Коффин

34
В C ++ 11 можно использовать равномерный initializtion , а также: new int[10] {}. Вы также можете new int[10] {1,2,3}
указать

Пожалуйста, не путайте default-initialized с value-initialized: они оба четко определены в стандарте и представляют собой разные инициализации.
Дедупликатор

25

Предполагая, что вы действительно хотите массив, а не 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.
Брайан Нил

2
Нет. Используйте 'Value-Initialization', чтобы установить для всех членов значение 0.
Martin York,

24

Существует множество методов для выделения массива внутреннего типа, и все эти методы являются правильными, хотя какой из них выбрать, зависит ...

Ручная инициализация всех элементов в цикле

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

Используя std::memsetфункцию из<cstring>

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

Используя std::fill_nалгоритм из<algorithm>

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

Использование std::vectorконтейнера

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

Если доступен C ++ 0x , используйте функции списка инициализатора

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
должен быть вектором <int> Если вы добавили p = new int [10] (), у вас был полный список.
Карстен

@mloskot, в первом случае, когда вы инициализировали массив с помощью «new», как будет происходить передача по ссылке? Если бы я использовал int array[SIZE] ={1,2,3,4,5,6,7};нотацию, я мог void rotateArray(int (& input)[SIZE], unsigned int k);бы использовать объявление моей функции, что было бы при использовании первого соглашения? любое предложение?
Anu

1
Боюсь, что пример с std::memsetнеправильным - вы пропустили 10, похоже, ожидаемое количество байтов - см. En.cppreference.com/w/cpp/string/byte/memset . (Я думаю, это прекрасно показывает, почему следует избегать такой низкоуровневой конструкции, когда это возможно.)
Suma

@ Сума Отличный улов! Исправлена. Похоже, это кандидат на десятилетнюю ошибку :-) Да, я согласен с вашим комментарием.
Млоскот

7

Если память, которую вы выделяете, является классом с конструктором, который делает что-то полезное, оператор new вызовет этот конструктор и оставит ваш объект инициализированным.

Но если вы выделяете POD или что-то, что не имеет конструктора, который инициализирует состояние объекта, то вы не можете выделить память и инициализировать эту память с помощью оператора new за одну операцию. Однако у вас есть несколько вариантов:

1) Вместо этого используйте переменную стека. Вы можете выделить и инициализировать по умолчанию за один шаг, например так:

int vals[100] = {0};  // first element is a matter of style

2) использовать memset(). Обратите внимание, что если объект, который вы размещаете, не является POD , memsetting это плохая идея. Один конкретный пример: если вы устанавливаете класс с виртуальными функциями, вы уничтожите виртуальную таблицу и оставите свой объект в непригодном для использования состоянии.

3) Многие операционные системы имеют вызовы, которые делают то, что вы хотите - выделить в куче и инициализировать данные для чего-то. Пример Windows будетVirtualAlloc()

4) Обычно это лучший вариант. Избегайте необходимости самостоятельно управлять памятью. Вы можете использовать контейнеры STL для выполнения практически всего, что вы делаете с необработанной памятью, включая выделение и инициализацию всего одним махом:

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

6

Да, есть:

std::vector<int> vec(SIZE, 0);

Используйте вектор вместо динамически распределяемого массива. Преимущества включают в себя отсутствие необходимости явного удаления массива (он удаляется, когда вектор выходит из области видимости), а также то, что память автоматически удаляется, даже если возникает исключение.

Изменить: чтобы избежать дальнейших негативных отзывов от людей, которые не удосужились прочитать комментарии ниже, я должен пояснить, что этот ответ не говорит о том, что вектор всегда является правильным ответом. Но это, безусловно, более подход C ++, чем «ручная» проверка удаления массива.

Теперь с C ++ 11 есть также std :: array, который моделирует массив постоянного размера (против вектора, который может расти). Существует также std :: unique_ptr, который управляет динамически размещаемым массивом (который можно комбинировать с инициализацией, как и в других ответах на этот вопрос). Любой из них является более C ++ способом, чем ручная обработка указателя на массив, IMHO.


11
это действительно не отвечает на вопрос, который был задан.
Джон Ноллер

1
Должен ли я всегда использовать std::vectorвместо динамически распределяемых массивов? Каковы преимущества использования массива над вектором, и наоборот?
Dreamlax

1
@John Knoller: ОП спросил, как это сделать на С ++, я бы сказал, что вектор - это способ на С ++. Конечно, вы правы, что могут быть ситуации, которые по-прежнему требуют простой массив, и, не зная ситуации OP, это может быть одна из них. Я бы сказал, что нет, так как кажется вероятным, что ОП не знает о векторах.
villintehaspam

1
@villintehaspam: Хотя это решение не отвечает на мой вопрос, это путь, по которому я собираюсь идти. Тайлер МакГенри отвечает на мой вопрос более прямо и должен особенно помочь людям, которые по какой-либо причине не могут его использовать std::vector.
Dreamlax

2
@villintehaspam: Нет, это не C ++ способ сделать это. Это Java-способ сделать это. Придерживаться vectorвезде независимо от контекста называется «Написание кода Java на C ++».
2010 года

2

std::fillэто один из способов. Принимает два итератора и значение для заполнения области. Это или цикл for (я полагаю) будет более подходящим для C ++.

Специально для установки массива примитивных целочисленных типов в 0 memsetхорошо, хотя это может вызвать удивление. Учтите также calloc, что использовать C ++ из-за приведения немного неудобно.

Со своей стороны, я почти всегда использую цикл.

(Я не люблю пересматривать намерения людей, но это правда, что при std::vectorпрочих равных условиях предпочтительнее использовать new[].)


1

Вы всегда можете использовать memset:

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

Я понимаю, что могу использовать memset, но я не был уверен, что это был способ C ++ для решения этой проблемы.
Dreamlax

1
На самом деле это не «путь C ++», но и необработанные массивы.
Пит Киркхам

1
@gbrandt: То есть он не очень хорошо работает на C или C ++. Это работает для большинства значений типа char или unsigned char. Это работает для большинства типов, значение равно 0 (по крайней мере, в большинстве реализаций). Иначе это вообще бесполезно.
Джерри Гроб

1
10 * sizeof( *myArray )более документирован и защищен от изменений, чем 10 * sizeof( int ).
Кевин Рид

1
В любом случае, OP имеет необработанный массив, а memset - самый быстрый и простой способ обнуления этого массива.
Грегор Брандт

-1

Обычно для динамических списков элементов вы используете std::vector.

Обычно я использую memset или цикл для динамического выделения памяти, в зависимости от того, какой переменной я ожидаю эту область кода в будущем.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.