Initialisierung aller Elemente eines Arrays auf einen Standardwert in C++?

Lesezeit: 11 Minuten

Initialisierung aller Elemente eines Arrays auf einen Standardwert in C
Mailand

C++-Hinweise: Array-Initialisierung hat eine schöne Liste über die Initialisierung von Arrays. Ich habe ein

int array[100] = {-1};

Ich erwarte, dass es voll mit -1 ist, aber das ist es nicht, nur der erste Wert ist und der Rest sind 0, gemischt mit zufälligen Werten.

Der Code

int array[100] = {0};

funktioniert einwandfrei und setzt jedes Element auf 0.

Was fehlt mir hier. Kann man es nicht initialisieren, wenn der Wert nicht Null ist?

Und 2: Ist die Standardinitialisierung (wie oben) schneller als die übliche Schleife durch das gesamte Array und weist einen Wert zu oder macht es dasselbe?

  • Das Verhalten in C und C++ ist unterschiedlich. In C ist {0} ein Sonderfall für einen Struct-Initialisierer, jedoch AFAIK nicht für Arrays. int-Array[100]={0} sollte dasselbe wie Array sein[100]={[0]=0}, was als Nebeneffekt alle anderen Elemente auf Null setzt. Der AC-Compiler sollte sich NICHT wie oben beschrieben verhalten, sondern int array[100]={-1} sollte das erste Element auf -1 setzen und den Rest auf 0 (ohne Rauschen). In C, wenn Sie ein struct x-Array haben[100], die Verwendung von ={0} als Initialisierer ist NICHT gültig. Sie können {{0}} verwenden, wodurch das erste Element initialisiert und alle anderen auf Null gesetzt werden. In den meisten Fällen ist dies dasselbe.

    – Fredrik Widlund

    26. Februar 2015 um 11:08 Uhr


  • @FredrikWidlund Es ist in beiden Sprachen gleich. {0} ist kein Sonderfall für Strukturen oder Arrays. Die Regel ist, dass Elemente ohne Initialisierer so initialisiert werden, als ob sie es getan hätten 0 für einen Initialisierer. Bei verschachtelten Aggregaten (z struct x array[100]) dann werden Initialisierer auf die Nicht-Aggregate in “row-major”-Reihenfolge angewendet; Klammern können dabei optional weggelassen werden. struct x array[100] = { 0 } ist in C gültig; und gültig in C++, solange das erste Mitglied von struct X akzeptiert 0 als Initialisierer.

    – MM

    3. Dezember 2015 um 5:04 Uhr

  • { 0 } ist in C nichts Besonderes, aber es ist viel schwieriger, einen Datentyp zu definieren, der damit nicht initialisiert werden kann, da es keine Konstruktoren und daher keine Möglichkeit zum Stoppen gibt 0 nicht implizit konvertiert und zugewiesen werden etwas.

    – Leuschenko

    3. April 2016 um 11:32 Uhr

  • Für die Wiedereröffnung gestimmt, weil sich die andere Frage auf C bezieht. Es gibt viele C++-Möglichkeiten, ein Array zu initialisieren, die in C nicht gültig sind.

    – xskxzr

    15. Juni 2018 um 14:36 ​​Uhr

  • Auch für die Wiedereröffnung gestimmt – C und C++ sind unterschiedliche Sprachen

    – Peter

    21. Juni 2019 um 9:52 Uhr

1647097812 374 Initialisierung aller Elemente eines Arrays auf einen Standardwert in C
Evan Teran

Verwenden Sie die von Ihnen verwendete Syntax,

int array[100] = {-1};

sagt “Setzen Sie das erste Element auf -1 und der Rest zu 0“, da alle ausgelassenen Elemente auf gesetzt werden 0.

In C++, um sie alle zu setzen -1können Sie so etwas wie verwenden std::fill_n (von <algorithm>):

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

In Portable C müssen Sie Ihre eigene Schleife rollen. Es gibt Compiler-Erweiterungen oder Sie können sich auf das implementierungsdefinierte Verhalten als Abkürzung verlassen, wenn dies akzeptabel ist.

  • Das beantwortete auch eine indirekte Frage, wie man das Array “einfach” mit Standardwerten füllen kann. Danke.

    – Mailand

    30. Juni 2009 um 20:23 Uhr

  • @chessofnerd: nicht genau, #include <algorithm> ist die richtige Überschrift, <vector> kann es indirekt enthalten oder nicht, das hängt von Ihrer Implementierung ab.

    – Evan Teran

    23. Mai 2013 um 19:37 Uhr

  • Sie müssen das Array während der Laufzeit nicht initialisieren. Wenn die Initialisierung wirklich statisch erfolgen muss, können Sie variadische Vorlagen und variadische Sequenzen verwenden, um die gewünschte Sequenz zu generieren ints und erweitern Sie es in den Initialisierer des Arrays.

    – Void-Zeiger

    2. Juni 2013 um 22:12 Uhr


  • @ontherocks, nein, es gibt keinen korrekten Weg, einen einzelnen Aufruf zu verwenden fill_num ein ganzes 2D-Array zu füllen. Sie müssen eine Dimension durchlaufen, während Sie die andere ausfüllen.

    – Evan Teran

    3. Oktober 2013 um 14:38 Uhr

  • Dies ist eine Antwort auf eine andere Frage. std::fill_n ist keine Initialisierung.

    – Ben Voigt

    30. Juni 2014 um 23:19 Uhr

Es gibt eine Erweiterung für den gcc-Compiler, die die folgende Syntax ermöglicht:

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

Dies würde alle Elemente auf -1 setzen.

Dies wird als “Designated Initializers” bezeichnet, siehe Hier Für weitere Informationen.

Beachten Sie, dass dies nicht für den c++-Compiler gcc implementiert ist.

  • Fantastisch. Diese Syntax scheint auch in Clang zu funktionieren (kann also unter iOS/Mac OS X verwendet werden).

    – JosefH

    7. Januar 2014 um 15:03 Uhr

Die Seite, auf die Sie verlinkt haben, hat bereits den ersten Teil beantwortet:

Wenn eine explizite Array-Größe angegeben ist, aber eine kürzere Initialisierungsliste angegeben ist, werden die nicht angegebenen Elemente auf Null gesetzt.

Es gibt keine integrierte Möglichkeit, das gesamte Array auf einen Wert ungleich Null zu initialisieren.

Was schneller ist, gilt die übliche Regel: “Die Methode, die dem Compiler die meisten Freiheiten gibt, ist wahrscheinlich schneller”.

int array[100] = {0};

teilt dem Compiler einfach mit, “diese 100 Ints auf Null zu setzen”, was der Compiler frei optimieren kann.

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

ist viel spezifischer. Es weist den Compiler an, eine Iterationsvariable zu erstellen isagt es ihm Befehl in dem die Elemente initialisiert werden sollen, und so weiter. Natürlich wird der Compiler das wahrscheinlich wegoptimieren, aber der Punkt ist, dass Sie hier das Problem überspezifizieren und den Compiler zwingen, härter zu arbeiten, um zum gleichen Ergebnis zu gelangen.

Wenn Sie schließlich das Array auf einen Wert ungleich Null setzen möchten, sollten Sie (zumindest in C++) verwenden std::fill:

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

Auch hier könnten Sie dasselbe mit einem Array machen, aber das ist prägnanter und gibt dem Compiler mehr Freiheit. Sie sagen nur, dass das gesamte Array mit dem Wert 42 gefüllt werden soll. Sie sagen nichts darüber, in welcher Reihenfolge es getan werden soll, oder irgendetwas anderes.

  • Gute Antwort. Beachten Sie, dass Sie in C++ (nicht in C) ein Int-Array ausführen können[100] = {}; und gib dem Compiler die meiste Freiheit 🙂

    – Johannes Schaub – litb

    30. Juni 2009 um 20:25 Uhr

  • einverstanden, ausgezeichnete Antwort. Aber für ein Array mit fester Größe würde es std::fill_n 😛 verwenden.

    – Evan Teran

    30. Juni 2009 um 20:28 Uhr

C++11 hat eine andere (unvollkommene) Option:

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

Mit {} weisen Sie die Elemente so zu, wie sie deklariert sind; der Rest wird mit 0 initialisiert.

Wenn es keine gibt = {} zu initialisieren, der Inhalt ist undefiniert.

Initialisierung aller Elemente eines Arrays auf einen Standardwert in C
laalto

Auf der von Ihnen verlinkten Seite heißt es

Wenn eine explizite Array-Größe angegeben ist, aber eine kürzere Initialisierungsliste angegeben ist, werden die nicht angegebenen Elemente auf Null gesetzt.

Geschwindigkeitsproblem: Alle Unterschiede wären für so kleine Arrays vernachlässigbar. Wenn Sie mit großen Arrays arbeiten und die Geschwindigkeit viel wichtiger ist als die Größe, können Sie ein konstantes Array mit den Standardwerten (zur Kompilierzeit initialisiert) und dann memcpy sie in das modifizierbare Array.

Initialisierung aller Elemente eines Arrays auf einen Standardwert in C
David Stein

Verwenden std::array, können wir dies auf ziemlich einfache Weise in C++14 tun. Dies ist nur in C++11 möglich, aber etwas komplizierter.

Unsere Schnittstelle ist eine Kompilierzeitgröße und ein Standardwert.

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

Die dritte Funktion dient hauptsächlich der Bequemlichkeit, sodass der Benutzer a nicht konstruieren muss std::integral_constant<std::size_t, size> selbst, da das eine ziemlich wortreiche Konstruktion ist. Die eigentliche Arbeit wird von einer der ersten beiden Funktionen erledigt.

Die erste Überladung ist ziemlich einfach: Sie konstruiert a std::array der Größe 0. Es ist kein Kopieren notwendig, wir konstruieren einfach.

Die zweite Überladung ist etwas kniffliger. Es leitet den Wert weiter, den es als Quelle erhalten hat, und es erstellt auch eine Instanz von make_index_sequence und ruft nur eine andere Implementierungsfunktion auf. Wie sieht diese Funktion aus?

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

Dadurch werden die ersten Argumente der Größe 1 erstellt, indem der von uns übergebene Wert kopiert wird. Hier verwenden wir unsere variadischen Parameterpaketindizes nur als etwas zum Erweitern. Es gibt Größe – 1 Einträge in diesem Paket (wie wir bei der Konstruktion von angegeben haben make_index_sequence), und sie haben Werte von 0, 1, 2, 3, …, Größe – 2. Die Werte sind uns jedoch egal (also wandeln wir sie in void um, um alle Compiler-Warnungen stumm zu schalten). Die Parameterpaketerweiterung erweitert unseren Code in etwa so (unter der Annahme einer Größe == 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) };

Wir verwenden diese Klammern, um sicherzustellen, dass die Variadic-Paketerweiterung ... erweitert, was wir wollen, und auch um sicherzustellen, dass wir den Kommaoperator verwenden. Ohne die Klammern würde es so aussehen, als würden wir eine Reihe von Argumenten an unsere Array-Initialisierung übergeben, aber in Wirklichkeit werten wir den Index aus, wandeln ihn in void um, ignorieren dieses void-Ergebnis und geben dann den Wert zurück, der in das Array kopiert wird .

Das letzte Argument, das wir nennen std::forward on, ist eine kleine Optimierung. Wenn jemand einen temporären std::string übergibt und sagt “erstelle ein Array aus 5 davon”, hätten wir gerne 4 Kopien und 1 Zug statt 5 Kopien. Die std::forward stellt sicher, dass wir dies tun.

Der vollständige Code, einschließlich Header und einige Unit-Tests:

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

  • Dein non_copyable Typ ist tatsächlich kopierbar durch operator=.

    – Hertz

    20. Februar 2016 um 19:23 Uhr


  • @Hertz: Die explizite Standardeinstellung des Bewegungskonstruktors führt dazu, dass die Kopier- und Bewegungszuweisungsoperatoren implizit gelöscht werden. Für diesen Testfall spielt es aber keine Rolle, da es keine Zuweisungen gibt.

    – David Stein

    14. Juni 2021 um 17:31 Uhr

994120cookie-checkInitialisierung aller Elemente eines Arrays auf einen Standardwert in C++?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy