Инициализация обычного массива с одним значением по умолчанию

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

int array[100] = {-1};

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

Код

int array[100] = {0};

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

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

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

14 ответов

Решение

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

int array[100] = {-1};

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

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

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

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

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

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

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

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

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

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

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

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

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

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. Вы ничего не говорите о том, в каком порядке это должно быть сделано, или о чем-то еще.

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

std::array<int, 100> a;
a.fill(-1);

С помощью 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, ..., size - 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.");
}

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

Если нет = {} чтобы инициализировать, содержание не определено.

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

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

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

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

#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

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

Здесь есть пример.

Самый простой способ — использовать и написать шаблон функции, который будет возвращать требуемое значение.std::arrayсо всеми его элементами, инициализированными переданным аргументом, как показано ниже.

Версия С++ 11

      template<std::size_t N> std::array<int, N> make_array(int val)
{
    std::array<int, N> tempArray{};
    for(int &elem:tempArray)
    {
        elem = val;
    }
    return tempArray;
}
int main()
{
    //---------------------V-------->number of elements  
    auto arr  = make_array<8>(5);
    //------------------------^---->value of element to be initialized with

    
    //lets confirm if all objects have the expected value 
    for(const auto &elem: arr)
    {
        std::cout << elem << std::endl; //prints all 5 
    }
    
}

Рабочая демонстрация


Версия С++17

С С++17 вы можете добавитьconstexprв шаблон функции, чтобы его можно было использовать в контексте constexpr:

      //-----------------------------------------vvvvvvvvv--->added constexpr
template<std::size_t N> std::array<int, N> constexpr make_array(int val)
{
    std::array<int, N> tempArray{};
    for(int &elem:tempArray)
    {
        elem = val;
    }
    return tempArray;
}
int main()
{
//--vvvvvvvvv------------------------------>constexpr added
    constexpr auto arr  = make_array<8>(5);

    for(const auto &elem: arr)
    {
        std::cout << elem << std::endl;
    }
    
}

Рабочая демонстрация

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

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

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

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

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

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

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

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

Должна быть стандартной функцией, но по какой-то причине она не включена ни в стандарт 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);
Другие вопросы по тегам