Inicialização de todos os elementos de uma matriz para um valor padrão em C ++?

248

Notas sobre C ++: A inicialização de matrizes possui uma boa lista sobre a inicialização de matrizes. eu tenho um

int array[100] = {-1};

esperando que esteja cheio de -1, mas não, apenas o primeiro valor é e o restante é 0 misturado com valores aleatórios.

O código

int array[100] = {0};

funciona bem e define cada elemento como 0.

O que estou perdendo aqui .. Não é possível inicializá-lo se o valor não for zero?

E 2: a inicialização padrão (como acima) é mais rápida que o loop usual em toda a matriz e atribui um valor ou faz a mesma coisa?

Milão
fonte
1
O comportamento em C e C ++ é diferente. Em C {0} é um caso especial para um inicializador de estrutura, no entanto, o AFAIK não para matrizes. int array [100] = {0} deve ser o mesmo que array [100] = {[0] = 0}, que como efeito colateral zerará todos os outros elementos. O compilador CA NÃO deve se comportar como descrito acima, em vez disso, a matriz int [100] = {- 1} deve definir o primeiro elemento como -1 e o restante como 0 (sem ruído). Em C, se você tiver uma matriz struct x [100], usar = {0} como inicializador NÃO é válido. Você pode usar {{0}}, que inicializará o primeiro elemento e zerará todos os outros; na maioria dos casos, será a mesma coisa.
Fredrik Widlund
1
@FredrikWidlund É o mesmo nos dois idiomas. {0}não é um caso especial para estruturas nem matrizes. A regra é que elementos sem inicializador são inicializados como se tivessem 0um inicializador. Se houver agregados aninhados (por exemplo struct x array[100]), os inicializadores serão aplicados aos não agregados na ordem "maior de linha"; chaves pode opcionalmente ser omitido fazendo isso. struct x array[100] = { 0 }é válido em C; e válido em C ++, desde que o primeiro membro do struct Xaceite 0como inicializador.
MM
1
{ 0 }não é especial em C, mas é muito mais difícil definir um tipo de dados que não pode ser inicializado com ele, pois não há construtores e, portanto, não há maneira de deixar 0de ser implicitamente convertido e atribuído a algo .
precisa saber é o seguinte
3
Votado para reabrir, porque a outra pergunta é sobre C. Há muitas maneiras C ++ para inicializar uma matriz que não são válidas em C.
xskxzr
1
Também votou para re-aberta - C e C ++ são diferentes idiomas
Pete

Respostas:

350

Usando a sintaxe que você usou,

int array[100] = {-1};

diz "defina o primeiro elemento como -1e o restante como 0", pois todos os elementos omitidos estão definidos como 0.

No C ++, para configurá-los todos -1, você pode usar algo como std::fill_n(de <algorithm>):

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

No C portátil, você precisa rolar seu próprio loop. Existem extensões do compilador ou você pode depender do comportamento definido pela implementação como um atalho, se isso for aceitável.

Evan Teran
fonte
14
Isso também respondeu a uma pergunta indireta sobre como preencher a matriz com valores padrão "facilmente". Obrigado.
Milão
7
@chessofnerd: não exatamente, #include <algorithm>é o cabeçalho certo, <vector>pode ou não incluir-o indiretamente, isso dependeria da sua implementação.
Evan Teran
2
Você não precisa recorrer à inicialização da matriz durante o tempo de execução. Se você realmente precisa que a inicialização ocorra estaticamente, é possível usar modelos e seqüências variadas para gerar a sequência desejada de se intexpandi-la para o inicializador da matriz.
vazio-pointer
2
@ontherocks, não, não há maneira correta de usar uma única chamada fill_npara preencher uma matriz 2D inteira. Você precisa percorrer uma dimensão enquanto preenche a outra.
precisa
7
Esta é uma resposta para outra pergunta. std::fill_nnão é inicialização.
Ben Voigt
133

Há uma extensão para o compilador gcc que permite a sintaxe:

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

Isso definiria todos os elementos para -1.

Isso é conhecido como "Inicializadores Designados", consulte aqui para obter mais informações.

Observe que isso não foi implementado para o compilador gcc c ++.

Callum
fonte
2
Impressionante. Essa sintaxe também parece funcionar em clang (portanto, pode ser usada no iOS / Mac OS X).
precisa saber é
31

A página à qual você vinculou já deu a resposta para a primeira parte:

Se um tamanho explícito de matriz for especificado, mas uma lista de inicialização mais curta for especificada, os elementos não especificados serão definidos como zero.

Não há uma maneira interna de inicializar toda a matriz para um valor diferente de zero.

Quanto ao que é mais rápido, aplica-se a regra usual: "O método que dá ao compilador a maior liberdade provavelmente é mais rápido".

int array[100] = {0};

simplesmente diz ao compilador "defina essas 100 polegadas para zero", que o compilador pode otimizar livremente.

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

é muito mais específico. Diz ao compilador para criar uma variável de iteração i, informa a ordem em que os elementos devem ser inicializados e assim por diante. Obviamente, é provável que o compilador otimize isso, mas o ponto é que aqui você está superespecificando o problema, forçando o compilador a trabalhar mais para obter o mesmo resultado.

Finalmente, se você deseja definir a matriz para um valor diferente de zero, você deve (pelo menos em C ++) usar std::fill:

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

Novamente, você pode fazer o mesmo com uma matriz, mas isso é mais conciso e oferece mais liberdade ao compilador. Você está apenas dizendo que deseja que toda a matriz seja preenchida com o valor 42. Você não diz nada sobre a ordem em que deve ser feita ou qualquer outra coisa.

jalf
fonte
5
Boa resposta. Observe que em C ++ (não em C) você pode executar int array [100] = {}; e dar o compilador mais liberdade :)
Johannes Schaub - litb
1
concordou, excelente resposta. Mas para uma matriz de tamanho fixo, ela usaria std :: fill_n :-P.
Evan Teran
12

O C ++ 11 tem outra opção (imperfeita):

std::array<int, 100> a;
a.fill(-1);
Timmmm
fonte
oustd::fill(begin(a), end(a), -1)
doctorlai 5/08/19
9

Com {} você atribui os elementos como eles são declarados; o restante é inicializado com 0.

Se não houver = {}para inicializar, o conteúdo é indefinido.

0x6adb015
fonte
8

A página que você vinculou

Se um tamanho explícito de matriz for especificado, mas uma lista de inicialização mais curta for especificada, os elementos não especificados serão definidos como zero.

Problema de velocidade: quaisquer diferenças seriam insignificantes para matrizes tão pequenas. Se você trabalha com matrizes grandes e a velocidade é muito mais importante que o tamanho, pode ter uma matriz const dos valores padrão (inicializados em tempo de compilação) e depois memcpypara a matriz modificável.

Laalto
fonte
2
o memcpy não é uma ideia muito boa, pois seria comparável a apenas definir os valores diretamente com velocidade.
Evan Teran
1
Não vejo a necessidade da cópia e da matriz const: por que não criar a matriz modificável em primeiro lugar com os valores pré-preenchidos?
Johannes Schaub - litb 30/06/09
Obrigado pela explicação velocidade e como fazê-lo, se a velocidade é um problema com um grande tamanho da matriz (que é no meu caso)
Milan
A lista do inicializador é feita no tempo de compilação e carregada no tempo de execução. Não há necessidade de copiar as coisas.
Martin Iorque
@litb, @Evan: Por exemplo, o gcc gera inicialização dinâmica (muitos movimentos), mesmo com as otimizações ativadas. Para matrizes grandes e requisitos de desempenho restritos, você deseja executar o init em tempo de compilação. O memcpy provavelmente é melhor otimizado para cópias grandes do que muitos movimentos simples.
30510 laalto
4

Outra maneira de inicializar a matriz com um valor comum seria realmente gerar a lista de elementos em uma série de define:

#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 )

A inicialização de uma matriz para um valor comum pode ser feita facilmente:

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

Nota: DUPx introduzido para ativar a substituição de macro nos parâmetros do DUP

Steen
fonte
3

No caso de uma matriz de elementos de byte único, você pode usar o memset para definir todos os elementos com o mesmo valor.

Há um exemplo aqui .

Steve Melnikoff
fonte
3

Usando std::array, podemos fazer isso de uma maneira bastante direta no C ++ 14. É possível fazer apenas no C ++ 11, mas um pouco mais complicado.

Nossa interface é um tamanho em tempo de compilação e um valor padrão.

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));
}

A terceira função é principalmente por conveniência, de modo que o usuário não precisa construir a std::integral_constant<std::size_t, size>si próprio, pois essa é uma construção bastante prolífica. O trabalho real é realizado por uma das duas primeiras funções.

A primeira sobrecarga é bem direta: ela cria um std::arraytamanho 0. Não há cópia necessária, apenas a construímos.

A segunda sobrecarga é um pouco mais complicada. Ele encaminha o valor obtido como fonte e também constrói uma instância make_index_sequencee apenas chama alguma outra função de implementação. Como é essa função?

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

Isso constrói os argumentos do primeiro tamanho - 1, copiando o valor que passamos. Aqui, usamos nossos índices variados do pacote de parâmetros como algo para expandir. Existem entradas size - 1 nesse pacote (conforme especificado na construção de make_index_sequence) e elas têm valores de 0, 1, 2, 3, ..., tamanho - 2. No entanto, não nos importamos com os valores ( então nós o lançamos para anular, para silenciar qualquer aviso do compilador). A expansão do pacote de parâmetros expande nosso código para algo assim (assumindo tamanho == 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) };

Usamos esses parênteses para garantir que a expansão variada do pacote ...expanda o que queremos e também para garantir que estamos usando o operador vírgula. Sem os parênteses, parece que estamos passando um monte de argumentos para a inicialização de nossa matriz, mas, na verdade, estamos avaliando o índice, lançando-o para nulo, ignorando esse resultado nulo e retornando o valor, que é copiado na matriz .

O argumento final, o que chamamos std::forward, é uma otimização menor. Se alguém passar uma std :: string temporária e disser "faça uma matriz de 5 delas", gostaríamos de ter 4 cópias e 1 movimentação, em vez de 5 cópias. O std::forwardgarante que fazemos isso.

O código completo, incluindo cabeçalhos e alguns testes de unidade:

#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.");
}
David Stone
fonte
Seu non_copyabletipo é realmente copiável por meio de operator=.
Hertz
Suponho non_copy_constructibleque seria um nome mais preciso para o objeto. No entanto, não há atribuição em nenhum lugar deste código, portanto, não importa para este exemplo.
David Stone
1

1) Quando você usa um inicializador, para uma estrutura ou uma matriz como essa, os valores não especificados são essencialmente construídos por padrão. No caso de um tipo primitivo como ints, isso significa que eles serão zerados. Observe que isso se aplica recursivamente: você pode ter uma matriz de estruturas contendo matrizes e, se especificar apenas o primeiro campo da primeira estrutura, todo o resto será inicializado com zeros e construtores padrão.

2) O compilador provavelmente irá gerar um código inicializador que seja pelo menos tão bom quanto você poderia fazer manualmente. Costumo preferir deixar o compilador fazer a inicialização para mim, quando possível.

Boojum
fonte
1) A inicialização padrão dos PODs não está acontecendo aqui. Usando a lista, o compilador gerará os valores no momento da compilação e os colocará em uma seção especial do assembly que é carregada apenas como parte da inicialização do programa (como o código). Portanto, o custo é zero no tempo de execução.
Martin Iorque
1
Não vejo onde ele está errado? int a [100] = {} certamente é inicializado com todos os 0, independentemente de onde aparece, e struct {int a; } b [100] = {}; é também. "essencialmente construído por padrão" => "valor construído", tho. Mas isso não importa no caso de ints, PODS ou tipos com códigos declarados pelo usuário. Só importa para os NÃO-Pods sem o usuário declarado ctors, pelo que sei. Mas eu não colocaria um voto negativo (!) Por causa disso. de qualquer maneira, um para você, é o 0 novamente :)
Johannes Schaub - litb
@Evan: qualifiquei minha declaração com "Quando você usa um inicializador ..." Eu não estava me referindo a valores não inicializados. @ Martin: Isso pode funcionar para dados constantes, estáticos ou globais. Mas não vejo como isso funcionaria com algo como: int test () {int i [10] = {0}; int v = i [0]; i [0] = 5; retornar v; } É melhor o compilador inicializar i [] em zeros toda vez que você chama test ().
Boojum 30/06/09
poderia colocar dados para o segmento de dados estático, e fazer "i" se referem a ele :)
Johannes Schaub - litb
Verdade - tecnicamente, nesse caso, ele também pode eliminar "i" inteiramente e retornar apenas 0. Mas o uso do segmento de dados estáticos para dados mutáveis ​​seria perigoso em ambientes com vários threads. O ponto que eu estava tentando explicar em resposta a Martin era simplesmente que você não pode eliminar completamente o custo da inicialização. Copie um pedaço pré-fabricado do segmento de dados estáticos, com certeza, mas ainda não é gratuito.
Boojum
0

Na linguagem de programação C ++ V4, o Stroustrup recomenda o uso de vetores ou valarrays sobre matrizes internas. Com os valarrarys, quando você os cria, você pode iniciá-los com um valor específico, como:

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

Para inicializar uma matriz com 7 membros, com "7777777".

Essa é uma maneira C ++ de implementar a resposta usando uma estrutura de dados C ++ em vez de uma matriz "C simples e antiga".

Eu mudei para usar o valarray como uma tentativa no meu código para tentar usar C ++ 'isms v. C'isms ....

Astara
fonte
Esta é a segunda pior exemplo de como usar um tipo que eu já vi ...
Steazy
-3

Deve ser um recurso padrão, mas por algum motivo não está incluído no padrão C nem 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]);
    }
}

No Fortran, você poderia fazer:

program main
    implicit none

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

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

mas não possui números não assinados ...

Por que o C / C ++ não pode simplesmente implementá-lo. Isso é realmente tão difícil? É tão tolo ter que escrever isso manualmente para obter o mesmo resultado ...

#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]);
    }
}

E se fosse uma matriz de 1.000,00 bytes? Eu precisaria escrever um script para escrever para mim ou recorrer a hacks com assembly / etc. Isso não faz sentido.

É perfeitamente portátil, não há razão para não estar no idioma.

Basta invadir como:

#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;
}

Uma maneira de invadir isso é através do pré-processamento ... (O código abaixo não cobre casos extremos, mas é escrito para demonstrar rapidamente o que poderia ser feito.)

#!/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);
Dmitry
fonte
você está imprimindo em um loop, por que não pode atribuir em um loop?
Abhinav Gauniyal 13/01
1
atribuir dentro de um loop incorre em sobrecarga de tempo de execução; enquanto a codificação do buffer é livre, porque o buffer já está incorporado no binário, para que não perca tempo construindo a matriz do zero toda vez que o programa é executado. você está certo de que imprimir em um loop não é uma boa idéia, no geral, é melhor anexar dentro do loop e depois imprimir uma vez, pois cada chamada printf exige uma chamada do sistema, enquanto a concatenação de strings usando a pilha / pilha do aplicativo não. Como o tamanho desse tipo de programa é uma não emissão, é melhor construir essa matriz em tempo de compilação, não em tempo de execução.
Dmitry
"atribuir dentro de um loop incorre em sobrecarga de tempo de execução" - Você subestima severamente o otimizador.
Asu
Dependendo do tamanho da matriz, gcc e clang irão "codificar" ou enganar o valor, e com matrizes maiores, diretamente apenas memsetisso, mesmo com a matriz "codificada".
Asu