Inizializzazione di tutti gli elementi di un array su un valore predefinito in C ++?


248

Note C ++: l'inizializzazione di array ha una bella lista sull'inizializzazione di array. Ho un

int array[100] = {-1};

aspettandosi che sia pieno con -1 ma non lo è, solo il primo valore è e gli altri sono 0 mescolati con valori casuali.

Il codice

int array[100] = {0};

funziona bene e imposta ogni elemento su 0.

Cosa mi manca qui ... Non è possibile inizializzarlo se il valore non è zero?

E 2: l'inizializzazione predefinita (come sopra) è più veloce del normale ciclo attraverso l'intero array e assegna un valore o fa la stessa cosa?


1
Il comportamento in C e C ++ è diverso. In C {0} è un caso speciale per un inizializzatore di struttura, tuttavia AFAIK non per le matrici. int array [100] = {0} dovrebbe essere uguale a array [100] = {[0] = 0}, che come effetto collaterale azzererà tutti gli altri elementi. Il compilatore CA NON dovrebbe comportarsi come descritto sopra, invece int array [100] = {- 1} dovrebbe impostare il primo elemento su -1 e il resto su 0 (senza rumore). In C se si dispone di un array struct x [100], l'utilizzo di = {0} come inizializzatore NON è valido. Puoi usare {{0}} che inizializzerà il primo elemento e azzererà tutti gli altri, nella maggior parte dei casi sarà la stessa cosa.
Fredrik Widlund,

1
@FredrikWidlund È lo stesso in entrambe le lingue. {0}non è un caso speciale per strutture o array. La regola è che gli elementi senza inizializzatore vengano inizializzati come se avessero 0un inizializzatore. Se sono presenti aggregati nidificati (ad es. struct x array[100]), Gli inizializzatori vengono applicati ai non aggregati in ordine di "riga maggiore"; le parentesi graffe possono facoltativamente essere omesse facendo questo. struct x array[100] = { 0 }è valido in C; e valido in C ++ purché il primo membro di struct Xaccetta 0come inizializzatore.
MM

1
{ 0 }non è speciale in C, ma è molto più difficile definire un tipo di dati che non può essere inizializzato con esso poiché non ci sono costruttori e quindi nessun modo per impedire che 0vengano convertiti e assegnati in modo implicito a qualcosa .
Leushenko,

3
Votato per riaprirsi perché l'altra domanda riguarda C. Esistono molti modi C ++ per inizializzare un array che non sono validi in C.
xskxzr

1
Votato anche per la riapertura - C e C ++ sono lingue diverse
Pete,

Risposte:


350

Usando la sintassi che hai usato,

int array[100] = {-1};

dice "imposta il primo elemento su -1e il resto su 0" poiché tutti gli elementi omessi sono impostati su0 .

In C ++, per impostarli tutti -1, puoi usare qualcosa come std::fill_n(da <algorithm>):

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

Nel C portatile, devi girare il tuo loop. Ci sono estensioni del compilatore o puoi dipendere dal comportamento definito dall'implementazione come scorciatoia se questo è accettabile.


14
Ciò ha anche risposto a una domanda indiretta su come riempire l'array con valori predefiniti "facilmente". Grazie.
Milano,

7
@chessofnerd: non precisamente, #include <algorithm>è l'intestazione giusta, <vector>può o meno includerla indirettamente, a seconda della tua implementazione.
Evan Teran,

2
Non è necessario ricorrere all'inizializzazione dell'array durante il runtime. Se è davvero necessario che l'inizializzazione avvenga in modo statico, è possibile utilizzare modelli variadici e sequenze variadiche per generare la sequenza desiderata di intse espanderla nell'inizializzatore dell'array.
void-pointer

2
@ontherocks, no non esiste un modo corretto di utilizzare una singola chiamata fill_nper riempire un intero array 2D. Devi passare attraverso una dimensione, riempiendo l'altra.
Evan Teran,

7
Questa è una risposta ad un'altra domanda. std::fill_nnon è inizializzazione.
Ben Voigt,

133

Esiste un'estensione per il compilatore gcc che consente la sintassi:

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

Ciò imposterebbe tutti gli elementi su -1.

Questo è noto come "inizializzatori designati" vedere qui per ulteriori informazioni.

Nota che questo non è implementato per il compilatore gcc c ++.


2
Eccezionale. Questa sintassi sembra funzionare anche in clang (quindi può essere utilizzata su iOS / Mac OS X).
JosephH,

31

La pagina a cui hai collegato ha già dato la risposta alla prima parte:

Se viene specificata una dimensione dell'array esplicita, ma viene specificato un elenco di inizializzazione più breve, gli elementi non specificati vengono impostati su zero.

Non esiste un modo integrato per inizializzare l'intero array su un valore diverso da zero.

Per quanto riguarda ciò che è più veloce, si applica la solita regola: "Il metodo che offre al compilatore la massima libertà è probabilmente più veloce".

int array[100] = {0};

dice semplicemente al compilatore "imposta questi 100 in zero", che il compilatore può ottimizzare liberamente.

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

è molto più specifico. Indica al compilatore di creare una variabile di iterazione i, gli dice l' ordine in cui gli elementi devono essere inizializzati e così via. Ovviamente, il compilatore probabilmente lo ottimizzerà, ma il punto è che qui stai specificando in modo eccessivo il problema, costringendo il compilatore a lavorare di più per ottenere lo stesso risultato.

Infine, se vuoi impostare l'array su un valore diverso da zero, dovresti (almeno in C ++) usare std::fill:

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

Ancora una volta, potresti fare lo stesso con un array, ma questo è più conciso e dà più libertà al compilatore. Stai solo dicendo che vuoi che l'intero array sia riempito con il valore 42. Non dici nulla sull'ordine in cui dovrebbe essere fatto, o qualsiasi altra cosa.


5
Buona risposta. Si noti che in C ++ (non in C) è possibile eseguire int array [100] = {}; e dai al compilatore la massima libertà :)
Johannes Schaub - litb

1
d'accordo, ottima risposta. Ma per un array di dimensioni fisse, userebbe std :: fill_n :-P.
Evan Teran,

12

C ++ 11 ha un'altra opzione (imperfetta):

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

oppurestd::fill(begin(a), end(a), -1)
doctorlai,

9

Con {} assegni gli elementi come sono dichiarati; il resto è inizializzato con 0.

Se non è = {}necessario initalizzare, il contenuto non è definito.


8

La pagina che hai collegato indica

Se viene specificata una dimensione dell'array esplicita, ma viene specificato un elenco di inizializzazione più breve, gli elementi non specificati vengono impostati su zero.

Problema di velocità: qualsiasi differenza sarebbe trascurabile per array così piccoli. Se si lavora con array di grandi dimensioni e la velocità è molto più importante della dimensione, è possibile disporre di un array const dei valori predefiniti (inizializzato in fase di compilazione) e quindi di memcpyquesti nell'array modificabile.


2
memcpy non è una buona idea, dal momento che sarebbe paragonabile alla semplice impostazione dei valori direttamente in base alla velocità.
Evan Teran,

1
Non vedo la necessità della copia e dell'array const: perché non creare l'array modificabile in primo luogo con i valori precompilati?
Johannes Schaub - litb

Grazie per la spiegazione della velocità e come farlo se la velocità è un problema con un array di grandi dimensioni (che è nel mio caso)
Milano,

L'elenco di inizializzatori viene eseguito in fase di compilazione e caricato in fase di esecuzione. Non c'è bisogno di andare a copiare le cose in giro.
Martin York,

@litb, @Evan: ad esempio gcc genera l'inizializzazione dinamica (molti movimenti) anche con le ottimizzazioni abilitate. Per array di grandi dimensioni e requisiti di prestazioni stretti, si desidera eseguire l'init al momento della compilazione. memcpy è probabilmente meglio ottimizzato per le copie di grandi dimensioni rispetto a molti semplici movimenti da soli.
laalto,

4

Un altro modo di inizializzare l'array su un valore comune sarebbe quello di generare effettivamente l'elenco di elementi in una serie di definisce:

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

L'inizializzazione di un array su un valore comune può essere facilmente eseguita:

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

Nota: DUPx introdotto per abilitare la sostituzione macro nei parametri a DUP


3

Nel caso di una matrice di elementi a byte singolo, è possibile utilizzare memset per impostare tutti gli elementi sullo stesso valore.

C'è un esempio qui .


3

Usando std::array, possiamo farlo in modo abbastanza semplice in C ++ 14. È possibile farlo solo in C ++ 11, ma leggermente più complicato.

La nostra interfaccia è una dimensione in fase di compilazione e un valore predefinito.

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

La terza funzione è principalmente per comodità, quindi l'utente non deve costruire un std::integral_constant<std::size_t, size> se stesso, in quanto si tratta di una costruzione piuttosto prolissa. Il vero lavoro è svolto da una delle prime due funzioni.

Il primo sovraccarico è piuttosto semplice: costruisce una std::arraydimensione 0. Non è necessario copiare, lo costruiamo e basta.

Il secondo sovraccarico è un po 'più complicato. Inoltra lungo il valore che ha ottenuto come sorgente e costruisce anche un'istanza di make_index_sequencee chiama semplicemente un'altra funzione di implementazione. Che aspetto ha quella funzione?

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

Questo costruisce la prima dimensione - 1 argomenti copiando il valore che abbiamo passato. Qui, usiamo gli indici dei pacchetti di parametri variadici proprio come qualcosa da espandere. Ci sono dimensioni - 1 voci in quel pacchetto (come abbiamo specificato nella costruzione di make_index_sequence), e hanno valori di 0, 1, 2, 3, ..., dimensioni - 2. Tuttavia, non ci importa dei valori ( quindi lo gettiamo nel vuoto, per mettere a tacere eventuali avvisi del compilatore). L'espansione del pacchetto di parametri espande il nostro codice in qualcosa del genere (assumendo dimensioni == 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) };

Utilizziamo queste parentesi per garantire che l'espansione del pacchetto variadico ...espanda ciò che vogliamo e anche per assicurarci di utilizzare l'operatore virgola. Senza le parentesi, sembrerebbe che stiamo passando un sacco di argomenti alla nostra inizializzazione di array, ma in realtà stiamo valutando l'indice, lanciandolo a vuoto, ignorando quel risultato vuoto e quindi restituendo valore, che viene copiato nella matrice .

L'argomento finale, quello che chiediamo std::forward, è un'ottimizzazione minore. Se qualcuno passa in una stringa std :: string temporanea e dice "crea un array di 5 di questi", vorremmo avere 4 copie e 1 mossa, anziché 5 copie. Le std::forwardassicura che facciamo questo.

Il codice completo, comprese le intestazioni e alcuni test unitari:

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

Il tuo non_copyabletipo è effettivamente copiabile tramite operator=.
Hertz,

Suppongo che non_copy_constructiblesarebbe un nome più preciso per l'oggetto. Tuttavia, non esiste alcuna assegnazione in qualsiasi parte di questo codice, quindi non ha importanza per questo esempio.
David Stone,

1

1) Quando si utilizza un inizializzatore, per una struttura o un array del genere, i valori non specificati sono essenzialmente predefiniti. Nel caso di un tipo primitivo come ints, ciò significa che verranno azzerati. Si noti che ciò si applica in modo ricorsivo: potresti avere una matrice di strutture contenenti array e se specifichi solo il primo campo della prima struttura, tutto il resto verrà inizializzato con zeri e costruttori predefiniti.

2) Il compilatore probabilmente genererà un codice di inizializzazione che è almeno buono come si potrebbe fare a mano. Tendo a preferire lasciare che il compilatore esegua l'inizializzazione per me, quando possibile.


1) L'inizializzazione predefinita dei POD non sta avvenendo qui. Utilizzando l'elenco, il compilatore genererà i valori in fase di compilazione e li inserirà in una sezione speciale dell'assembly che viene appena caricata come parte dell'inizializzazione del programma (come il codice). Quindi il costo è zero in fase di esecuzione.
Martin York,

1
Non vedo dove ha torto? int a [100] = {} è certamente inizializzato su tutti 0, indipendentemente da dove appare, e struct {int a; } b [100] = {}; è anche. "essenzialmente default built" => "valore costruito", tho. Ma questo non importa in caso di ints, PODS o tipi con i medici dichiarati dall'utente. Ciò che so è importante solo per i NON Pod senza utenti dichiarati dall'utente. Ma non darei un voto negativo (!) Per questo. comunque, +1 per renderlo di nuovo 0 :)
Johannes Schaub - litb

@Evan: ho qualificato la mia affermazione con "Quando usi un inizializzatore ..." Non mi riferivo a valori non inizializzati. @Martin: potrebbe funzionare con dati costanti, statici o globali. Ma non vedo come funzionerebbe con qualcosa del genere: int test () {int i [10] = {0}; int v = i [0]; i [0] = 5; ritorno v; } È meglio che il compilatore inizializzi i [] su zero ogni volta che chiami test ().
Boojum,

potrebbe posizionare i dati nel segmento dei dati statici e fare in modo che "i" si riferisca ad essi :)
Johannes Schaub - litb

Vero - tecnicamente, in questo caso potrebbe anche eludere "i" interamente e restituire semplicemente 0. Ma l'uso del segmento di dati statici per dati mutabili sarebbe pericoloso in ambienti multi-thread. Il punto che stavo cercando di chiarire in risposta a Martin era semplicemente che non è possibile eliminare completamente il costo dell'inizializzazione. Copia un blocco predefinito dal segmento di dati statici, certo, ma non è ancora gratuito.
Boojum,


0

Nel linguaggio di programmazione C ++ V4, Stroustrup consiglia di utilizzare vettori o valarrays su array incorporati. Con i valarrary, quando li crei, puoi iniziarli a un valore specifico come:

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

Per inizializzare un array di 7 membri con "7777777".

Questo è un modo C ++ per implementare la risposta usando una struttura di dati C ++ invece di una matrice "semplice vecchia C".

Sono passato all'utilizzo del valarray come tentativo nel mio codice per provare a usare gli ismi v di C ++. C'isms ....


Questo è il secondo peggior esempio di come usare un tipo che abbia mai visto ...
Steazy

-3

Dovrebbe essere una funzionalità standard ma per qualche motivo non è inclusa nello standard C né 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]);
    }
}

In Fortran potresti fare:

program main
    implicit none

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

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

ma non ha numeri senza segno ...

Perché C / C ++ non può semplicemente implementarlo. È davvero così difficile? È così sciocco doverlo scrivere manualmente per ottenere lo stesso risultato ...

#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 un array di 1.000,00 byte? Avrei bisogno di scrivere una sceneggiatura per scriverla per me, o ricorrere a hack con assembly / etc. Questo non ha senso.

È perfettamente portatile, non c'è motivo per non essere nella lingua.

Basta hackerarlo come:

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

Un modo per hackerarlo è tramite la preelaborazione ... (Il codice seguente non copre i casi limite, ma è scritto per dimostrare rapidamente cosa si potrebbe fare.)

#!/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);

stai stampando in un ciclo, perché non puoi assegnare in un ciclo?
Abhinav Gauniyal,

1
l'assegnazione all'interno di un ciclo comporta un sovraccarico di runtime; mentre l'hardcoding del buffer è gratuito perché il buffer è già incorporato nel binario, quindi non perde tempo a costruire l'array da zero ogni volta che il programma viene eseguito. hai ragione a dire che la stampa in un ciclo non è una buona idea nel complesso, tuttavia è meglio aggiungere all'interno del ciclo e quindi stampare una volta, poiché ogni chiamata printf richiede una chiamata di sistema, mentre non lo fa la concatenazione di stringhe utilizzando l'heap / stack dell'applicazione. Poiché le dimensioni di questo tipo di programma non sono un problema, è meglio costruire questo array in fase di compilazione, non in fase di esecuzione.
Dmitry

"L'assegnazione all'interno di un ciclo comporta un sovraccarico di runtime" - L'ottimizzatore viene fortemente sottovalutato.
Asu,

A seconda della dimensione dell'array, gcc e clang "codificheranno" o inganneranno il valore, e con array più grandi, direttamente solo memsetesso, anche con l'array "hardcoded".
Asu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.