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


248

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

int array[100] = {-1};

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

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 sie dasselbe?


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

1
@FredrikWidlund Es ist in beiden Sprachen gleich. {0}ist kein Sonderfall für Strukturen oder Arrays. Die Regel ist, dass Elemente ohne Initialisierer wie 0für einen Initialisierer initialisiert werden. Wenn verschachtelte Aggregate vorhanden sind (z. B. struct x array[100]), werden Initialisierer in der Reihenfolge "Zeilen-Haupt" auf die Nicht-Aggregate angewendet. Klammern können dabei optional weggelassen werden. struct x array[100] = { 0 }ist gültig in C; und gültig in C ++, solange das erste Mitglied von als Initialisierer struct Xakzeptiert 0.
MM

1
{ 0 }ist in C nicht besonders, aber es ist viel schwieriger, einen Datentyp zu definieren, der damit nicht initialisiert werden kann, da es keine Konstruktoren gibt und daher nicht verhindert werden kann 0, implizit konvertiert und etwas zugewiesen zu werden .
Leushenko

3
Zum erneuten
Öffnen

1
Auch für die Wiedereröffnung gestimmt - C und C ++ sind verschiedene Sprachen
Pete

Antworten:


350

Verwenden Sie die von Ihnen verwendete Syntax.

int array[100] = {-1};

sagt "setze das erste Element auf -1und den Rest auf 0", da alle ausgelassenen Elemente auf gesetzt sind 0.

In C ++ können Sie zum Festlegen aller -1Optionen Folgendes verwenden : std::fill_n(from <algorithm>):

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

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


14
Dies beantwortete auch eine indirekte Frage, wie das Array "leicht" mit Standardwerten gefüllt werden kann. Danke dir.
Mailand

7
@chessofnerd: Nicht genau, #include <algorithm>ist der richtige Header, <vector>kann ihn indirekt enthalten oder nicht, was von Ihrer Implementierung abhängen würde.
Evan Teran

2
Sie müssen nicht zur Laufzeit auf die Initialisierung des Arrays zurückgreifen. Wenn die Initialisierung wirklich statisch erfolgen muss, können Sie mithilfe variabler Vorlagen und variabler Sequenzen die gewünschte Sequenz von ints generieren und in den Initialisierer des Arrays erweitern.
Leerzeiger

2
@ontherocks, nein, es gibt keine korrekte Möglichkeit, einen einzelnen Aufruf fill_nzum Füllen eines gesamten 2D-Arrays zu verwenden. Sie müssen eine Dimension durchlaufen, während Sie die andere ausfüllen.
Evan Teran

7
Dies ist eine Antwort auf eine andere Frage. std::fill_nist keine Initialisierung.
Ben Voigt

133

Es gibt eine Erweiterung des gcc-Compilers, die die Syntax zulässt:

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

Dies würde alle Elemente auf -1 setzen.

Dies ist bekannt als „Designated Initializers“ sieht hier für weitere Informationen.

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


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

31

Die Seite, auf die Sie verlinkt haben, gab bereits die Antwort auf den ersten Teil:

Wenn eine explizite Arraygröße angegeben wird, aber eine kürzere Initilierungsliste angegeben wird, 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 größte Freiheit gibt, ist wahrscheinlich schneller."

int array[100] = {0};

sagt dem Compiler einfach "setze diese 100 Ints auf Null", 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, gibt die Reihenfolge an, in der die Elemente initialisiert werden sollen, und so weiter. Natürlich wird der Compiler dies wahrscheinlich optimieren, aber der Punkt ist, dass Sie hier das Problem überbestimmen und den Compiler dazu zwingen, härter zu arbeiten, um zum gleichen Ergebnis zu gelangen.

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

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

Auch hier können Sie dasselbe mit einem Array tun, dies ist jedoch präziser und gibt dem Compiler mehr Freiheit. Sie sagen nur, dass Sie möchten, dass das gesamte Array mit dem Wert 42 gefüllt wird. Sie sagen nichts darüber, in welcher Reihenfolge es ausgeführt werden soll oder sonst etwas.


5
Gute Antwort. Beachten Sie, dass Sie in C ++ (nicht in C) int array [100] = {} ausführen können. und gib dem Compiler die meiste Freiheit :)
Johannes Schaub - litb

1
stimmte zu, ausgezeichnete Antwort. Für ein Array mit fester Größe würde jedoch std :: fill_n :-P verwendet.
Evan Teran

12

C ++ 11 hat eine andere (unvollständige) Option:

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

oderstd::fill(begin(a), end(a), -1)
Doctorlai

9

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

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


8

Die Seite, die Sie verlinkt haben, gibt an

Wenn eine explizite Arraygröße angegeben wird, aber eine kürzere Initilierungsliste angegeben wird, werden die nicht angegebenen Elemente auf Null gesetzt.

Geschwindigkeitsproblem: Bei so kleinen Arrays wären Unterschiede vernachlässigbar. Wenn Sie mit großen Arrays arbeiten und die Geschwindigkeit viel wichtiger als die Größe ist, können Sie ein const-Array mit den Standardwerten (zur Kompilierungszeit initialisiert) und diese dann memcpyin das modifizierbare Array einfügen.


2
Das Memcpy ist keine sehr gute Idee, da dies mit dem direkten Einstellen der Werte in Bezug auf die Geschwindigkeit vergleichbar wäre.
Evan Teran

1
Ich sehe keine Notwendigkeit für die Kopie und das const-Array: Warum nicht zuerst das modifizierbare Array mit den vorgefüllten Werten erstellen?
Johannes Schaub - litb

Vielen Dank für die Erklärung der Geschwindigkeit und wie es geht, wenn die Geschwindigkeit ein Problem mit einem großen Array ist (was in meinem Fall der Fall ist)
Mailand

Die Initialisierungsliste wird zur Kompilierungszeit erstellt und zur Laufzeit geladen. Sie müssen keine Dinge kopieren.
Martin York

@litb, @Evan: Zum Beispiel generiert gcc auch bei aktivierten Optimierungen eine dynamische Initialisierung (viele Movs). Bei großen Arrays und engen Leistungsanforderungen möchten Sie den Init zur Kompilierungszeit ausführen. memcpy ist wahrscheinlich besser für große Kopien optimiert als viele einfache Moves allein.
Laalto

4

Eine andere Möglichkeit, das Array auf einen gemeinsamen Wert zu initialisieren, besteht darin, die Liste der Elemente in einer Reihe von Definitionen zu generieren:

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

Das Initialisieren eines Arrays auf einen gemeinsamen Wert ist einfach:

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

Hinweis: DUPx wurde eingeführt, um die Makrosubstitution in Parametern für DUP zu ermöglichen


3

Für den Fall eines Arrays von Einzelbyte-Elementen können Sie memset verwenden, um alle Elemente auf denselben Wert zu setzen.

Es ist ein Beispiel hier .


3

Mit std::arraykönnen wir dies in C ++ 14 auf ziemlich einfache Weise tun. Dies ist nur in C ++ 11 möglich, jedoch etwas komplizierter.

Unsere Schnittstelle hat eine Größe zur Kompilierungszeit und einen 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, so dass der Benutzer kein std::integral_constant<std::size_t, size>Selbst konstruieren muss, da dies eine ziemlich wortreiche Konstruktion ist. Die eigentliche Arbeit erledigt eine der ersten beiden Funktionen.

Die erste Überladung ist ziemlich einfach: Sie erstellt eine std::arrayGröße 0. Es ist kein Kopieren erforderlich, wir erstellen sie einfach.

Die zweite Überlastung ist etwas kniffliger. Es leitet den Wert weiter, den es als Quelle erhalten hat, und erstellt auch eine Instanz vonmake_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 übergebene Wert kopiert wird. Hier verwenden wir unsere variablen Parameterpaketindizes nur als Erweiterung. Es gibt Einträge der Größe 1 in diesem Paket (wie wir in der Konstruktion von angegeben habenmake_index_sequence ) und sie haben Werte von 0, 1, 2, 3, ..., Größe - 2. Die Werte sind uns jedoch egal ( Also haben wir es für ungültig erklärt, um alle Compiler-Warnungen zum Schweigen zu bringen. Die Erweiterung des Parameterpakets erweitert unseren Code auf etwa Folgendes (vorausgesetzt, 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 Erweiterung des Variadic Packs das ...erweitert, was wir wollen, und um sicherzustellen, dass wir den Komma-Operator verwenden. Ohne die Klammern würde es so aussehen, als würden wir eine Reihe von Argumenten an unsere Array-Initialisierung übergeben, aber tatsächlich bewerten wir den Index, 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, auf das wir zurückgreifen std::forward, ist eine geringfügige Optimierung. Wenn jemand einen temporären std :: string übergibt und sagt "mache ein Array von 5 davon", möchten wir 4 Kopien und 1 Zug anstelle von 5 Kopien haben. Dasstd::forward stellt sicher, dass wir dies tun.

Der vollständige Code, einschließlich Header und einiger Komponententests:

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

Ihr non_copyableTyp kann tatsächlich mit kopiert werden operator=.
Hertz

Ich nehme an, es non_copy_constructiblewäre ein genauerer Name für das Objekt. Da in diesem Code jedoch keine Zuordnung vorhanden ist, spielt dies für dieses Beispiel keine Rolle.
David Stone

1

1) Wenn Sie einen Initialisierer für eine Struktur oder ein Array wie dieses verwenden, werden die nicht angegebenen Werte im Wesentlichen standardmäßig erstellt. Bei einem primitiven Typ wie Ints bedeutet dies, dass sie auf Null gesetzt werden. Beachten Sie, dass dies rekursiv gilt: Sie können ein Array von Strukturen haben, die Arrays enthalten. Wenn Sie nur das erste Feld der ersten Struktur angeben, wird der Rest mit Nullen und Standardkonstruktoren initialisiert.

2) Der Compiler generiert wahrscheinlich Initialisierungscode, der mindestens so gut ist, wie Sie es von Hand tun könnten. Ich ziehe es vor, den Compiler nach Möglichkeit die Initialisierung für mich durchführen zu lassen.


1) Die Standardinitialisierung von PODs findet hier nicht statt. Mithilfe der Liste generiert der Compiler die Werte zur Kompilierungszeit und platziert sie in einem speziellen Abschnitt der Baugruppe, der gerade als Teil der Programminitialisierung geladen wird (wie der Code). Die Kosten sind also zur Laufzeit gleich Null.
Martin York

1
Ich sehe nicht, wo er falsch liegt? int a [100] = {} wird sicherlich auf alle 0 initialisiert, unabhängig davon, wo es erscheint, und struct {int a; } b [100] = {}; ist auch. "im wesentlichen standardmäßig konstruiert" => "Wert konstruiert", tho. Dies spielt jedoch keine Rolle bei Ints, PODS oder Typen mit vom Benutzer deklarierten Ctors. Es ist nur wichtig für NON-Pods ohne vom Benutzer deklarierte Ctors, soweit ich weiß. Aber ich würde deswegen keine (!) Stimme abgeben. sowieso +1 für dich, um es wieder 0 zu machen :)
Johannes Schaub - litb

@Evan: Ich habe meine Aussage mit "Wenn Sie einen Initialisierer verwenden ..." qualifiziert. Ich habe mich nicht auf nicht initialisierte Werte bezogen. @Martin: Das funktioniert möglicherweise für konstante, statische oder globale Daten. Aber ich sehe nicht, wie das mit so etwas funktionieren würde: int test () {int i [10] = {0}; int v = i [0]; i [0] = 5; return v; } Der Compiler sollte i [] bei jedem Aufruf von test () auf Null setzen.
Boojum

es könnte Daten in das statische Datensegment
einfügen

Richtig - technisch gesehen könnte es in diesem Fall auch "i" vollständig entfernen und nur 0 zurückgeben. Die Verwendung des statischen Datensegments für veränderbare Daten wäre jedoch in Umgebungen mit mehreren Threads gefährlich. Der Punkt, den ich als Antwort auf Martin ansprechen wollte, war einfach, dass Sie die Kosten für die Initialisierung nicht vollständig eliminieren können. Kopieren Sie einen vorgefertigten Block aus dem statischen Datensegment, aber er ist immer noch nicht kostenlos.
Boojum


0

In der C ++ - Programmiersprache V4 empfiehlt Stroustrup die Verwendung von Vektoren oder Valarrays gegenüber integrierten Arrays. Wenn Sie Valarrarys erstellen, können Sie sie beim Erstellen auf einen bestimmten Wert wie folgt einleiten:

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

So initialisieren Sie ein 7 Mitglieder langes Array mit "7777777".

Dies ist eine C ++ - Methode zum Implementieren der Antwort mithilfe einer C ++ - Datenstruktur anstelle eines "einfachen alten C" -Arrays.

Ich habe den Valarray als Versuch in meinem Code verwendet, C ++ 'isms v. C'isms zu verwenden ....


Dies ist das zweitschlechteste Beispiel für die Verwendung eines Typs, den ich je gesehen habe ...
Steazy

-3

Sollte eine Standardfunktion sein, ist aber aus irgendeinem Grund weder in Standard C noch in C ++ enthalten ...

#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 können Sie Folgendes tun:

program main
    implicit none

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

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

aber es hat keine vorzeichenlosen Nummern ...

Warum kann C / C ++ es nicht einfach implementieren? Ist es wirklich so schwer? Es ist so dumm, dies manuell schreiben zu müssen, um das gleiche Ergebnis zu erzielen ...

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

Was wäre, wenn es ein Array von 1.000,00 Bytes wäre? Ich müsste ein Skript schreiben, um es für mich zu schreiben, oder auf Hacks mit Assembly / etc. Zurückgreifen. Das ist schwachsinn.

Es ist perfekt portabel, es gibt keinen Grund dafür, nicht in der Sprache zu sein.

Hacken Sie es einfach wie folgt:

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

Eine Möglichkeit, es zu hacken, ist die Vorverarbeitung ... (Der folgende Code deckt keine Randfälle ab, sondern wurde geschrieben, um schnell zu demonstrieren, was getan werden kann.)

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

Sie drucken in einer Schleife. Warum können Sie nicht in einer Schleife zuweisen?
Abhinav Gauniyal

1
Das Zuweisen innerhalb einer Schleife verursacht Laufzeit-Overhead. Während die Hardcodierung des Puffers kostenlos ist, da der Puffer bereits in die Binärdatei eingebettet ist, wird keine Zeit damit verschwendet, das Array bei jeder Programmausführung von Grund auf neu zu erstellen. Sie haben Recht, dass das Drucken in einer Schleife insgesamt keine gute Idee ist. Es ist jedoch besser, innerhalb der Schleife anzuhängen und dann einmal zu drucken, da jeder printf-Aufruf einen Systemaufruf erfordert, während die Verkettung von Zeichenfolgen mit dem Heap / Stack der Anwendung dies nicht tut. Da die Größe in dieser Art von Programm keine Ausgabe ist, ist es am besten, dieses Array zur Kompilierungszeit und nicht zur Laufzeit zu erstellen.
Dmitry

"Das Zuweisen innerhalb einer Schleife verursacht Laufzeit-Overhead" - Sie unterschätzen den Optimierer stark.
Asu

Abhängig von der Größe des Arrays werden gcc und clang den Wert "fest codieren" oder betrügen, und bei größeren Arrays direkt nur memset, selbst mit dem "fest codierten" Array.
Asu
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.