Initialisation de tous les éléments d'un tableau à une valeur par défaut en C ++?


248

Notes C ++: l' initialisation des tableaux a une belle liste sur l'initialisation des tableaux. j'ai un

int array[100] = {-1};

s'attendant à ce qu'il soit plein de -1 mais ce n'est pas le cas, seule la première valeur est et les autres sont des 0 mélangés avec des valeurs aléatoires.

Le code

int array[100] = {0};

fonctionne très bien et met chaque élément à 0.

Qu'est-ce que je manque ici .. Ne peut-on pas l'initialiser si la valeur n'est pas nulle?

Et 2: l'initialisation par défaut (comme ci-dessus) est-elle plus rapide que la boucle habituelle dans tout le tableau et attribue-t-elle une valeur ou fait-elle la même chose?


1
Le comportement en C et C ++ est différent. En C {0} est un cas particulier pour un initialiseur struct, cependant AFAIK pas pour les tableaux. int array [100] = {0} devrait être le même que array [100] = {[0] = 0}, qui comme effet secondaire mettra à zéro tous les autres éléments. Le compilateur AC ne doit PAS se comporter comme vous le décrivez ci-dessus, mais int array [100] = {- 1} doit définir le premier élément sur -1 et le reste sur 0 (sans bruit). En C si vous avez un tableau struct x [100], utiliser = {0} comme initialiseur n'est PAS valide. Vous pouvez utiliser {{0}} qui initialisera le premier élément et mettra à zéro tous les autres, sera dans la plupart des cas la même chose.
Fredrik Widlund

1
@FredrikWidlund C'est la même chose dans les deux langues. {0}n'est pas un cas particulier pour les structures ni les tableaux. La règle est que les éléments sans initialiseur sont initialisés comme s'ils l'avaient été 0pour un initialiseur. S'il y a des agrégats imbriqués (par exemple struct x array[100]), les initialiseurs sont appliqués aux non-agrégats dans l'ordre "ligne principale"; les accolades peuvent éventuellement être omises en faisant cela. struct x array[100] = { 0 }est valide en C; et valide en C ++ tant que le premier membre de struct Xaccepte 0comme initialiseur.
MM

1
{ 0 }n'est pas spécial en C, mais il est beaucoup plus difficile de définir un type de données qui ne peut pas être initialisé avec lui car il n'y a pas de constructeurs et donc aucun moyen d'empêcher 0d'être implicitement converti et assigné à quelque chose .
Leushenko

3
Voté pour rouvrir car l'autre question concerne C. Il existe de nombreuses façons C ++ d'initialiser un tableau qui ne sont pas valides en C.
xskxzr

1
Également voté pour la réouverture - C et C ++ sont des langages différents
Pete

Réponses:


350

En utilisant la syntaxe que vous avez utilisée,

int array[100] = {-1};

dit "définir le premier élément sur -1et le reste sur 0" puisque tous les éléments omis sont définis sur 0.

En C ++, pour les définir tous sur -1, vous pouvez utiliser quelque chose comme std::fill_n(from <algorithm>):

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

Dans C portable, vous devez rouler votre propre boucle. Il existe des extensions de compilateur ou vous pouvez dépendre du comportement défini par l'implémentation comme raccourci si cela est acceptable.


14
Cela a également répondu à une question indirecte sur la façon de remplir le tableau avec des valeurs par défaut "facilement". Je vous remercie.
Milan le

7
@chessofnerd: pas précisément, #include <algorithm>est le bon en-tête, <vector>peut-être l'inclure indirectement, cela dépendrait de votre implémentation.
Evan Teran

2
Vous n'avez pas à recourir à l'initialisation de la baie lors de l'exécution. Si vous avez vraiment besoin que l'initialisation se produise statiquement, il est possible d'utiliser des modèles variadiques et des séquences variadiques pour générer la séquence de ints souhaitée et la développer dans l'initialiseur du tableau.
void-pointer

2
@ontherocks, non, il n'y a pas de moyen correct d'utiliser un seul appel fill_npour remplir un tableau 2D entier. Vous devez parcourir une dimension en remplissant l'autre.
Evan Teran

7
Ceci est une réponse à une autre question. std::fill_nn'est pas l'initialisation.
Ben Voigt

133

Il y a une extension au compilateur gcc qui permet la syntaxe:

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

Cela mettrait tous les éléments à -1.

C'est ce que l'on appelle les "initialiseurs désignés", voir ici pour plus d'informations.

Notez que cela n'est pas implémenté pour le compilateur gcc c ++.


2
Impressionnant. Cette syntaxe semble également fonctionner en clang (elle peut donc être utilisée sur iOS / Mac OS X).
JosephH

31

La page à laquelle vous avez lié a déjà donné la réponse à la première partie:

Si une taille de tableau explicite est spécifiée, mais qu'une liste d'initialisation plus courte est spécifiée, les éléments non spécifiés sont définis sur zéro.

Il n'y a aucun moyen intégré d'initialiser le tableau entier à une valeur non nulle.

Quant à ce qui est plus rapide, la règle habituelle s'applique: "La méthode qui donne le plus de liberté au compilateur est probablement plus rapide".

int array[100] = {0};

dit simplement au compilateur de "mettre ces 100 pouces à zéro", que le compilateur peut optimiser librement.

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

est beaucoup plus spécifique. Il indique au compilateur de créer une variable d'itération i, il lui indique l' ordre dans lequel les éléments doivent être initialisés, etc. Bien sûr, le compilateur est susceptible d'optimiser cela, mais le fait est qu'ici vous surpécifiez le problème, forçant le compilateur à travailler plus dur pour arriver au même résultat.

Enfin, si vous souhaitez définir le tableau sur une valeur non nulle, vous devez (au moins en C ++) utiliser std::fill:

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

Encore une fois, vous pouvez faire la même chose avec un tableau, mais c'est plus concis et donne plus de liberté au compilateur. Vous dites simplement que vous voulez que le tableau entier soit rempli avec la valeur 42. Vous ne dites rien dans quel ordre cela doit être fait, ou quoi que ce soit d'autre.


5
Bonne réponse. Notez qu'en C ++ (pas en C) vous pouvez faire int array [100] = {}; et donnez au compilateur le plus de liberté :)
Johannes Schaub - litb

1
d'accord, excellente réponse. Mais pour un tableau de taille fixe, il utiliserait std :: fill_n :-P.
Evan Teran

12

C ++ 11 a une autre option (imparfaite):

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

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

9

Avec {}, vous affectez les éléments tels qu'ils sont déclarés; le reste est initialisé avec 0.

S'il n'y a pas = {}à initialiser, le contenu n'est pas défini.


8

La page que vous avez liée aux états

Si une taille de tableau explicite est spécifiée, mais qu'une liste d'initialisation plus courte est spécifiée, les éléments non spécifiés sont définis sur zéro.

Problème de vitesse: toute différence serait négligeable pour des baies aussi petites. Si vous travaillez avec de grands tableaux et que la vitesse est beaucoup plus importante que la taille, vous pouvez avoir un tableau const des valeurs par défaut (initialisé au moment de la compilation), puis memcpyles placer dans le tableau modifiable.


2
le memcpy n'est pas une très bonne idée, car cela serait comparable à simplement définir les valeurs directement en termes de vitesse.
Evan Teran

1
Je ne vois pas la nécessité de la copie et du tableau const: Pourquoi ne pas créer le tableau modifiable en premier lieu avec les valeurs pré-remplies?
Johannes Schaub - litb

Merci pour l'explication de la vitesse et comment le faire si la vitesse est un problème avec une grande taille de tableau (ce qui est dans mon cas)
Milan

La liste d'initialisation est effectuée au moment de la compilation et chargée au moment de l'exécution. Pas besoin d'aller copier des choses.
Martin York

@litb, @Evan: Par exemple, gcc génère une initialisation dynamique (beaucoup de movs) même avec des optimisations activées. Pour les tableaux de grande taille et les exigences de performances strictes, vous souhaitez effectuer l'initialisation au moment de la compilation. memcpy est probablement mieux optimisé pour les grandes copies que beaucoup de movs simples seuls.
laalto

4

Une autre façon d'initialiser le tableau à une valeur commune serait de générer réellement la liste des éléments dans une série de définitions:

#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'initialisation d'un tableau à une valeur commune peut facilement être effectuée:

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

Remarque: DUPx introduit pour permettre la substitution de macro dans les paramètres de DUP


3

Dans le cas d'un tableau d'éléments à un octet, vous pouvez utiliser memset pour définir tous les éléments à la même valeur.

Il y a un exemple ici .


3

En utilisant std::array, nous pouvons le faire d'une manière assez simple en C ++ 14. Il est possible de le faire en C ++ 11 uniquement, mais légèrement plus compliqué.

Notre interface est une taille au moment de la compilation et une valeur par défaut.

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 troisième fonction est principalement pour la commodité, de sorte que l'utilisateur n'a pas à construire un std::integral_constant<std::size_t, size> même, car c'est une construction assez verbeuse. Le vrai travail est effectué par l'une des deux premières fonctions.

La première surcharge est assez simple: il construit un std::arrayde taille 0. Il n'y a pas de copie nécessaire, nous le construisons simplement.

La deuxième surcharge est un peu plus délicate. Il transmet la valeur obtenue en tant que source, et construit également une instance de make_index_sequenceet appelle simplement une autre fonction d'implémentation. À quoi ressemble cette fonction?

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

Cela construit les premiers arguments de taille - 1 en copiant la valeur que nous avons transmise. Ici, nous utilisons nos index de paquets de paramètres variadic comme quelque chose à développer. Il y a taille - 1 entrées dans ce pack (comme nous l'avons spécifié dans la construction de make_index_sequence), et elles ont des valeurs de 0, 1, 2, 3, ..., taille - 2. Cependant, nous ne nous soucions pas des valeurs ( nous l'avons donc annulé, pour désactiver tous les avertissements du compilateur). L'expansion du pack de paramètres étend notre code à quelque chose comme ceci (en supposant que la taille == 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) };

Nous utilisons ces parenthèses pour nous assurer que l'expansion du pack variadic ...élargit ce que nous voulons, et également pour nous assurer que nous utilisons l'opérateur virgule. Sans les parenthèses, il semblerait que nous passions un tas d'arguments à l'initialisation de notre tableau, mais en réalité, nous évaluons l'index, le convertissons en vide, ignorant ce résultat vide, puis renvoyant la valeur, qui est copiée dans le tableau .

Le dernier argument, celui que nous invoquons std::forward, est une optimisation mineure. Si quelqu'un passe dans une chaîne std :: temporaire et dit "faire un tableau de 5 d'entre eux", nous aimerions avoir 4 copies et 1 déplacement, au lieu de 5 copies. Le std::forwards'assure que nous le faisons.

Le code complet, y compris les en-têtes et certains tests unitaires:

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

Votre non_copyabletype est en fait copiable au moyen de operator=.
Hertz

Je suppose que ce non_copy_constructibleserait un nom plus précis pour l'objet. Cependant, il n'y a aucune affectation nulle part dans ce code, donc cela n'a pas d'importance pour cet exemple.
David Stone

1

1) Lorsque vous utilisez un initialiseur, pour une structure ou un tableau comme celui-ci, les valeurs non spécifiées sont essentiellement construites par défaut. Dans le cas d'un type primitif comme les entiers, cela signifie qu'ils seront mis à zéro. Notez que cela s'applique récursivement: vous pouvez avoir un tableau de structures contenant des tableaux et si vous spécifiez uniquement le premier champ de la première structure, tout le reste sera initialisé avec des zéros et des constructeurs par défaut.

2) Le compilateur générera probablement un code d'initialisation au moins aussi bon que vous pourriez le faire à la main. J'ai tendance à préférer laisser le compilateur faire l'initialisation pour moi, lorsque cela est possible.


1) L'initialisation par défaut des POD ne se produit pas ici. En utilisant la liste, le compilateur générera les valeurs au moment de la compilation et les placera dans une section spéciale de l'assemblage qui vient d'être chargée dans le cadre de l'initialisation du programme (comme le code). Le coût est donc nul à l'exécution.
Martin York

1
Je ne vois pas où il a tort? int a [100] = {} est certainement initialisé à tous les 0, sans tenir compte de l'endroit où il apparaît, et struct {int a; } b [100] = {}; est aussi. "essentiellement construit par défaut" => "valeur construite", tho. Mais cela n'a pas d'importance en cas d'entiers, de PODS ou de types avec des cteurs déclarés par l'utilisateur. Cela ne concerne que les NON-Pods sans cteurs déclarés par l'utilisateur, à ce que je sache. Mais je ne voterais pas (!) Pour cette raison. de toute façon, +1 pour que vous puissiez le faire à nouveau :) :)
Johannes Schaub - litb

@Evan: J'ai qualifié ma déclaration de "Quand vous utilisez un initialiseur ..." Je ne faisais pas référence à des valeurs non initialisées. @Martin: cela peut fonctionner pour des données constantes, statiques ou globales. Mais je ne vois pas comment cela fonctionnerait avec quelque chose comme: int test () {int i [10] = {0}; int v = i [0]; i [0] = 5; return v; } Le compilateur ferait mieux d'initialiser i [] en zéros à chaque fois que vous appelez test ().
Boojum

il pourrait placer des données dans le segment de données statiques et y faire référence "i" :)
Johannes Schaub - litb

Vrai - techniquement, dans ce cas, il pourrait également supprimer complètement "i" et retourner simplement 0. Mais l'utilisation du segment de données statiques pour des données mutables serait dangereuse dans des environnements multithreads. Le point que j'essayais de faire valoir en réponse à Martin était simplement que vous ne pouvez pas éliminer complètement le coût de l'initialisation. Copiez un morceau prédéfini à partir du segment de données statiques, bien sûr, mais ce n'est toujours pas gratuit.
Boojum


0

Dans le langage de programmation C ++ V4, Stroustrup recommande d'utiliser des vecteurs ou des valarrays sur des tableaux intégrés. Avec valarrary's, lorsque vous les créez, vous pouvez les initier à une valeur spécifique comme:

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

Pour initialiser un tableau de 7 membres de long avec "7777777".

Il s'agit d'une façon C ++ d'implémenter la réponse en utilisant une structure de données C ++ au lieu d'un tableau "plain old C".

Je suis passé à l'utilisation du valarray comme une tentative dans mon code pour essayer d'utiliser les ismes C ++ contre les c'ismes ....


Ceci est le deuxième pire exemple d'utilisation d'un type que j'ai jamais vu ...
Steazy

-3

Devrait être une fonctionnalité standard, mais pour une raison quelconque, elle n'est pas incluse dans C standard ni 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]);
    }
}

À Fortran, vous pourriez faire:

program main
    implicit none

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

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

mais il n'a pas de numéros non signés ...

Pourquoi C / C ++ ne peut-il pas simplement l'implémenter. Est-ce vraiment si difficile? C'est tellement idiot d'avoir à écrire cela manuellement pour obtenir le même résultat ...

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

Et si c'était un tableau de 1 000,00 octets? J'aurais besoin d'écrire un script pour l'écrire pour moi, ou de recourir à des hacks avec assembly / etc. Ça n'a pas de sens.

Il est parfaitement portable, il n'y a aucune raison qu'il ne soit pas dans la langue.

Il suffit de le pirater comme:

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

Une façon de le pirater est via le prétraitement ... (Le code ci-dessous ne couvre pas les cas marginaux, mais est écrit pour démontrer rapidement ce qui pourrait être fait.)

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

vous imprimez en boucle, pourquoi ne pouvez-vous pas attribuer en boucle?
Abhinav Gauniyal

1
l'affectation à l'intérieur d'une boucle entraîne une surcharge d'exécution; tandis que le codage en dur du tampon est gratuit car le tampon est déjà intégré dans le binaire, donc il ne perd pas de temps à construire le tableau à partir de zéro à chaque exécution du programme. vous avez raison de dire que l'impression dans une boucle n'est pas une bonne idée dans l'ensemble, il est préférable d'ajouter à l'intérieur de la boucle, puis d'imprimer une fois, car chaque appel printf nécessite un appel système, contrairement à la concaténation de chaînes à l'aide du tas / pile de l'application. Étant donné que la taille dans ce type de programme n'est pas un problème, il est préférable de construire ce tableau au moment de la compilation, pas au moment de l'exécution.
Dmitry

"affecter à l'intérieur d'une boucle entraîne une surcharge d'exécution" - Vous sous-estimez sérieusement l'optimiseur.
Asu

Selon la taille du tableau, gcc et clang "coderont en dur" ou tromperont la valeur dans, et avec des tableaux plus grands, directement juste memset, même avec le tableau "codé en dur".
Asu
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.