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?
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 -1
kö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.
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.
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 i
sagt 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.
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.
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.
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.");
}
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ätten0
für einen Initialisierer. Bei verschachtelten Aggregaten (zstruct 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 vonstruct X
akzeptiert0
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 gibt0
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