Инициализация всех элементов массива в одно значение по умолчанию в C ++?

248

Примечания C ++: у Инициализации массива есть хороший список по инициализации массивов. у меня есть

int array[100] = {-1};

ожидая, что он будет заполнен -1, но его нет, только первое значение равно нулю, а остальные 0 смешаны со случайными значениями.

Код

int array[100] = {0};

работает просто отлично и устанавливает каждый элемент на 0.

Чего мне здесь не хватает .. Разве нельзя инициализировать его, если значение не равно нулю?

И 2: Происходит ли инициализация по умолчанию (как указано выше) быстрее, чем обычный цикл по всему массиву и присваивается значение, или она делает то же самое?

Милан
источник
1
Поведение в C и C ++ отличается. В C {0} это особый случай для инициализатора структуры, однако AFAIK не для массивов. int array [100] = {0} должен быть таким же, как array [100] = {[0] = 0}, что в качестве побочного эффекта обнулит все остальные элементы. Компилятор переменного тока НЕ ​​должен вести себя так, как вы описали выше, вместо этого int array [100] = {- 1} должна установить первый элемент на -1, а остальные на 0 (без шума). В C, если у вас есть массив struct x [100], использование = {0} в качестве инициализатора НЕ допустимо. Вы можете использовать {{0}}, который инициализирует первый элемент и обнуляет все остальные, в большинстве случаев будет то же самое.
Фредрик Видлунд
1
@FredrikWidlund Это одинаково на обоих языках. {0}это не особый случай для структур и массивов. Правило состоит в том, что элементы без инициализатора инициализируются так, как если бы они были 0для инициализатора. Если есть вложенные агрегаты (например struct x array[100]), то инициализаторы применяются к неагрегатам в порядке "основной строки"; При этом фигурные скобки могут быть опущены. struct x array[100] = { 0 }действует в C; и действует в C ++ до тех пор, пока первый член struct Xпринимает в 0качестве инициализатора.
MM
1
{ 0 }не является особенным в C, но намного сложнее определить тип данных, который не может быть инициализирован с ним, так как нет конструкторов и, следовательно, нет способа остановить 0неявное преобразование и присвоение чему-либо .
Леушенко
3
Принято решение о повторном открытии, потому что другой вопрос касается C. Существует множество способов инициализации массива в C ++, которые недопустимы в C.
xskxzr
1
Также проголосовали за повторное открытие - C и C ++ - это разные языки
Пит

Ответы:

350

Используя синтаксис, который вы использовали,

int array[100] = {-1};

говорит "установить первый элемент в, -1а остальные в 0", поскольку все пропущенные элементы установлены в 0.

В C ++, чтобы установить их все -1, вы можете использовать что-то вроде std::fill_n(из <algorithm>):

std::fill_n(array, 100, -1);

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

Эван Теран
источник
14
Это также ответило на косвенный вопрос о том, как «легко» заполнить массив значениями по умолчанию. Спасибо.
Милан
7
@chessofnerd: не совсем, #include <algorithm>правильный заголовок, <vector>может включать или не включать его косвенно, что будет зависеть от вашей реализации.
Эван Теран
2
Вам не нужно прибегать к инициализации массива во время выполнения. Если вам действительно нужно, чтобы инициализация происходила статически, можно использовать шаблоны с переменными числами и последовательности с переменными числами, чтобы сгенерировать требуемую последовательность ints и развернуть ее в инициализаторе массива.
указатель на пустоту
2
@ontherocks, нет, нет правильного способа использовать один вызов fill_nдля заполнения всего 2D-массива. Вы должны пройти через одно измерение, заполняя другое.
Эван Теран
7
Это ответ на какой-то другой вопрос. std::fill_nэто не инициализация.
Бен Фойгт
133

У компилятора gcc есть расширение, которое допускает синтаксис:

int array[100] = { [0 ... 99] = -1 };

Это установит все элементы в -1.

Это известно как «Назначенные инициализаторы», смотрите здесь для получения дополнительной информации.

Обратите внимание, что это не реализовано для компилятора gcc c ++.

Каллум
источник
2
Потрясающие. Этот синтаксис также, кажется, работает в Clang (поэтому может использоваться на iOS / Mac OS X).
JosephH
31

Страница, на которую вы ссылаетесь, уже дала ответ на первую часть:

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

Не существует встроенного способа инициализации всего массива каким-либо ненулевым значением.

Что касается того, что быстрее, то применяется обычное правило: «Метод, который дает компилятору наибольшую свободу, вероятно, быстрее».

int array[100] = {0};

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

for (int i = 0; i < 100; ++i){
  array[i] = 0;
}

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

Наконец, если вы хотите установить для массива ненулевое значение, вы должны (по крайней мере, в C ++) использовать std::fill:

std::fill(array, array+100, 42); // sets every value in the array to 42

Опять же, вы можете сделать то же самое с массивом, но это более кратко и дает компилятору больше свободы. Вы просто говорите, что хотите, чтобы весь массив был заполнен значением 42. Вы ничего не говорите о том, в каком порядке это должно быть сделано, или о чем-то еще.

jalf
источник
5
Хороший ответ. Обратите внимание, что в C ++ (не в C) вы можете сделать int array [100] = {}; и дать компилятору наибольшую свободу :)
Йоханнес Шауб - Lit
1
согласился, отличный ответ. Но для массива фиксированного размера он использовал бы std :: fill_n :-P.
Эван Теран
12

В C ++ 11 есть еще одна (несовершенная) опция:

std::array<int, 100> a;
a.fill(-1);
Timmmm
источник
илиstd::fill(begin(a), end(a), -1)
doctorlai
9

С помощью {} вы назначаете элементы так, как они объявлены; остальное инициализируется 0.

Если нет = {}инициализации, содержимое не определено.

0x6adb015
источник
8

На странице, на которую вы ссылались, указано

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

Проблема скорости: любые различия будут незначительными для таких маленьких массивов. Если вы работаете с большими массивами, а скорость гораздо важнее размера, вы можете получить массив const со значениями по умолчанию (инициализированными во время компиляции), а затем memcpyперевести их в модифицируемый массив.

laalto
источник
2
memcpy не очень хорошая идея, так как это было бы сравнимо с простой установкой значений непосредственно по скорости.
Эван Теран
1
Я не вижу необходимости в копировании и константном массиве: почему бы не создать изменяемый массив в первую очередь с предварительно заполненными значениями?
Йоханнес Шауб -
Спасибо за объяснение скорости и как это сделать, если скорость является проблемой с большим размером массива (что в моем случае)
Милан
Список инициализаторов создается во время компиляции и загружается во время выполнения. Не нужно копировать вещи вокруг.
Мартин Йорк
@litb, @Evan: например, gcc генерирует динамическую инициализацию (много movs) даже с включенной оптимизацией. Для больших массивов и жестких требований к производительности вы хотите выполнить инициализацию во время компиляции. memcpy, вероятно, лучше оптимизирован для больших копий, чем множество простых однотипных файлов.
Лаалто
4

Другим способом инициализации массива с общим значением будет создание списка элементов в последовательности определений:

#define DUP1( X ) ( X )
#define DUP2( X ) DUP1( X ), ( X )
#define DUP3( X ) DUP2( X ), ( X )
#define DUP4( X ) DUP3( X ), ( X )
#define DUP5( X ) DUP4( X ), ( X )
.
.
#define DUP100( X ) DUP99( X ), ( X )

#define DUPx( X, N ) DUP##N( X )
#define DUP( X, N ) DUPx( X, N )

Инициализация массива к общему значению может быть легко выполнена:

#define LIST_MAX 6
static unsigned char List[ LIST_MAX ]= { DUP( 123, LIST_MAX ) };

Примечание: DUPx введен для включения подстановки макросов в параметрах в DUP

Steen
источник
3

В случае массива однобайтовых элементов вы можете использовать memset, чтобы установить для всех элементов одинаковое значение.

Там пример здесь .

Стив Мельникофф
источник
3

Используя std::array, мы можем сделать это довольно простым способом в C ++ 14. Это можно сделать только в C ++ 11, но немного сложнее.

Наш интерфейс имеет размер во время компиляции и значение по умолчанию.

template<typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
    return std::array<std::decay_t<T>, 0>{};
}

template<std::size_t size, typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
    return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
}


template<std::size_t size, typename T>
constexpr auto make_array_n(T && value) {
    return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
}

Третья функция в основном для удобства, так что пользователю не нужно создавать std::integral_constant<std::size_t, size>себя, так как это довольно многословная конструкция. Настоящая работа выполняется одной из первых двух функций.

Первая перегрузка довольно проста: она создает std::arrayразмер 0. Нет необходимости в копировании, мы просто создаем его.

Вторая перегрузка немного сложнее. Он продвигается вдоль значения, полученного в качестве источника, а также создает экземпляр make_index_sequenceи просто вызывает некоторую другую функцию реализации. Как выглядит эта функция?

namespace detail {

template<std::size_t size, typename T, std::size_t... indexes>
constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
    // Use the comma operator to expand the variadic pack
    // Move the last element in if possible. Order of evaluation is well-defined
    // for aggregate initialization, so there is no risk of copy-after-move
    return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
}

}   // namespace detail

Это создает первый аргумент размера - 1 путем копирования значения, которое мы передали. Здесь мы используем наши индексы пакета переменных параметров просто для расширения. В этом пакете есть записи размера - 1 (как мы указали в конструкции make_index_sequence), и они имеют значения 0, 1, 2, 3, ..., размер - 2. Однако нам нет дела до значений ( поэтому мы приводим его к void, чтобы заставить замолчать любые предупреждения компилятора). Расширение пакета параметров расширяет наш код до чего-то вроде этого (предполагая размер == 4):

return std::array<std::decay_t<T>, 4>{ (static_cast<void>(0), value), (static_cast<void>(1), value), (static_cast<void>(2), value), std::forward<T>(value) };

Мы используем эти скобки, чтобы гарантировать, что расширение пакета variadic ...расширяет то, что мы хотим, а также чтобы убедиться, что мы используем оператор запятой. Без скобок было бы похоже, что мы передаем кучу аргументов в нашу инициализацию массива, но на самом деле мы оцениваем индекс, приводим его к void, игнорируем тот результат void, а затем возвращаем значение, которое копируется в массив ,

Последний аргумент, который мы призываем std::forward, - это небольшая оптимизация. Если кто-то передает временную строку std :: string и говорит «создайте массив из 5 из них», мы бы хотели иметь 4 копии и 1 перемещение вместо 5 копий. В std::forwardгарантирует , что мы делаем это.

Полный код, включая заголовки и некоторые модульные тесты:

#include <array>
#include <type_traits>
#include <utility>

namespace detail {

template<std::size_t size, typename T, std::size_t... indexes>
constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
    // Use the comma operator to expand the variadic pack
    // Move the last element in if possible. Order of evaluation is well-defined
    // for aggregate initialization, so there is no risk of copy-after-move
    return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
}

}   // namespace detail

template<typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
    return std::array<std::decay_t<T>, 0>{};
}

template<std::size_t size, typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
    return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
}

template<std::size_t size, typename T>
constexpr auto make_array_n(T && value) {
    return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
}



struct non_copyable {
    constexpr non_copyable() = default;
    constexpr non_copyable(non_copyable const &) = delete;
    constexpr non_copyable(non_copyable &&) = default;
};

int main() {
    constexpr auto array_n = make_array_n<6>(5);
    static_assert(std::is_same<std::decay_t<decltype(array_n)>::value_type, int>::value, "Incorrect type from make_array_n.");
    static_assert(array_n.size() == 6, "Incorrect size from make_array_n.");
    static_assert(array_n[3] == 5, "Incorrect values from make_array_n.");

    constexpr auto array_non_copyable = make_array_n<1>(non_copyable{});
    static_assert(array_non_copyable.size() == 1, "Incorrect array size of 1 for move-only types.");

    constexpr auto array_empty = make_array_n<0>(2);
    static_assert(array_empty.empty(), "Incorrect array size for empty array.");

    constexpr auto array_non_copyable_empty = make_array_n<0>(non_copyable{});
    static_assert(array_non_copyable_empty.empty(), "Incorrect array size for empty array of move-only.");
}
Дэвид Стоун
источник
Ваш non_copyableтип на самом деле копируется с помощью operator=.
Герц
Я полагаю, non_copy_constructibleбыло бы более точное имя для объекта. Однако нигде в этом коде нет присваивания, поэтому для этого примера это не имеет значения.
Дэвид Стоун
1

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

2) Компилятор, вероятно, сгенерирует код инициализатора, который по крайней мере так же хорош, как вы могли бы сделать вручную. Я предпочитаю, чтобы компилятор делал инициализацию для меня, когда это возможно.

буджума
источник
1) Инициализация по умолчанию POD здесь не происходит. Используя список, компилятор генерирует значения во время компиляции и помещает их в специальный раздел сборки, который просто загружается как часть инициализации программы (например, код). Таким образом, стоимость равна нулю во время выполнения.
Мартин Йорк,
1
Я не понимаю, где он не прав? int a [100] = {} определенно инициализируется для всех 0, независимо от того, где он появляется, и struct {int a; } b [100] = {}; это слишком. "по умолчанию построено" => "значение построено", хотя. Но это не имеет значения в случае int, PODS или типов с объявленными пользователем ctors. Это имеет значение только для NON-Pods без объявленных пользователем ctors, насколько я знаю. Но я бы не стал (!) Голосовать за это. в любом случае, +1 для вас, чтобы сделать его снова 0 :)
Йоханнес Шауб - Litb
@Evan: я уточнил свое утверждение как «Когда вы используете инициализатор ...», я не имел в виду неинициализированные значения. @Martin: Это может работать для постоянных, статических или глобальных данных. Но я не понимаю, как это будет работать с чем-то вроде: int test () {int i [10] = {0}; int v = i [0]; я [0] = 5; возврат v; } Компилятору лучше инициализировать i [] нулями каждый раз, когда вы вызываете test ().
Boojum
он может поместить данные в сегмент статических данных и сделать так, чтобы «i»
ссылалось
Верно - технически, в этом случае он также может полностью исключить «i» и просто вернуть 0. Но использование статического сегмента данных для изменяемых данных было бы опасно в многопоточных средах. В ответ на вопрос Мартина я пытался просто сказать, что нельзя полностью исключить затраты на инициализацию. Конечно, скопируйте готовый кусок из сегмента статических данных, но он все еще не бесплатен.
Boojum
0

В языке программирования C ++ V4 Страуструп рекомендует использовать векторы или переменные над встроенными массивами. С valarrary, когда вы создаете их, вы можете инициировать их к определенному значению, например:

valarray <int>seven7s=(7777777,7);

Инициализировать массив длиной 7 элементов с "7777777".

Это способ реализации ответа в C ++ с использованием структуры данных C ++ вместо «простого старого массива C».

Я переключился на использование valarray как попытку в своем коде попытаться использовать C ++ 'isms v. C'isms ....

Астара
источник
Это второй худший пример того, как использовать тип, который я когда-либо видел ...
Steazy
-3

Должна быть стандартной функцией, но по какой-то причине она не включена ни в стандарт C, ни в C ++ ...

#include <stdio.h>

 __asm__
 (
"    .global _arr;      "
"    .section .data;    "
"_arr: .fill 100, 1, 2; "
 );

extern char arr[];

int main() 
{
    int i;

    for(i = 0; i < 100; ++i) {
        printf("arr[%u] = %u.\n", i, arr[i]);
    }
}

В Фортране вы можете сделать:

program main
    implicit none

    byte a(100)
    data a /100*2/
    integer i

    do i = 0, 100
        print *, a(i)
    end do
end

но у него нет чисел без знака ...

Почему C / C ++ не может просто реализовать это. Это действительно так сложно? Глупо писать это вручную, чтобы достичь того же результата ...

#include <stdio.h>
#include <stdint.h>

/* did I count it correctly? I'm not quite sure. */
uint8_t arr = {
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
    2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
};    

int main() 
{
    int i;

    for(i = 0; i < 100; ++i) {
        printf("arr[%u] = %u.\n", i, arr[i]);
    }
}

Что если это был массив из 1000,00 байтов? Мне нужно написать скрипт, чтобы написать его для меня, или прибегнуть к хакам со сборкой / и т.д. Это нонсенс.

Это совершенно портативно, нет причин не быть на языке.

Просто взломайте это как:

#include <stdio.h>
#include <stdint.h>

/* a byte array of 100 twos declared at compile time. */
uint8_t twos[] = {100:2};

int main()
{
    uint_fast32_t i;
    for (i = 0; i < 100; ++i) {
        printf("twos[%u] = %u.\n", i, twos[i]);
    }

    return 0;
}

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

#!/usr/bin/perl
use warnings;
use strict;

open my $inf, "<main.c";
open my $ouf, ">out.c";

my @lines = <$inf>;

foreach my $line (@lines) {
    if ($line =~ m/({(\d+):(\d+)})/) {
        printf ("$1, $2, $3");        
        my $lnew = "{" . "$3, "x($2 - 1) . $3 . "}";
        $line =~ s/{(\d+:\d+)}/$lnew/;
        printf $ouf $line;
    } else {
        printf $ouf $line;
    }
}

close($ouf);
close($inf);
Дмитрий
источник
Вы печатаете в цикле, почему вы не можете назначить в цикле?
Абхинав Гауниял
1
присваивание внутри цикла приводит к накладным расходам во время выполнения; тогда как жесткое кодирование буфера является свободным, потому что буфер уже встроен в двоичный файл, поэтому он не тратит время на создание массива с нуля при каждом запуске программы. вы правы в том, что печать в цикле не является хорошей идеей в целом, однако лучше добавить в цикл и затем выполнить печать один раз, поскольку каждый вызов printf требует системного вызова, тогда как конкатенация строк с использованием кучи / стека приложения этого не делает. Поскольку размер в такого рода программах не является проблемой, лучше создавать этот массив во время компиляции, а не во время выполнения.
Дмитрий
«назначение внутри цикла приводит к накладным расходам во время выполнения» - вы сильно недооцениваете оптимизатор.
Асу
В зависимости от размера массива, gcc и clang будут «жестко кодировать» или обманывать значение, а с помощью больших массивов - напрямую memset, даже с «жестко закодированным» массивом.
Асу