Warum ist die Dimension eines Arrays Teil seines Typs?

14

Beim Lesen des C ++ Primer-Buches stieß ich auf die folgende Aussage: "Die Anzahl der Elemente in einem Array ist Teil des Array-Typs." Also wollte ich das mit folgendem Code herausfinden:

#include<iostream>

int main()
{
    char Array1[]{'H', 'e', 'l', 'p'};
    char Array2[]{'P', 'l', 'e', 'a', 's', 'e'};

    std::cout<<typeid(Array1).name()<<std::endl;        //prints  A4_c
    std::cout<<typeid(Array2).name()<<std::endl;        //prints  A6_c

    return 0;
}

Und interessanterweise zeigte das Ergebnis von typeid auf den beiden Arrays, dass sie irgendwie unterschiedlich sind.

  • Was ist hinter den Kulissen los?
  • Warum müssen Arrays einen Typ haben, der seine Größe enthält? Liegt es nur daran, dass sich seine Größe nicht ändern sollte?
  • Wie wirkt sich dies auf den Vergleich von Arrays aus?

Ich möchte nur in der Lage sein, das Konzept tief zu verstehen.

Tintenfisch
quelle
3
Es ist nicht unbedingt erforderlich ,
Größeninformationen
Jedes Tutorial zu Arrays erklärt (1). Ich bin mir nicht sicher, was Sie mit (3) meinen, da es keine integrierte Möglichkeit gibt, Arrays zu vergleichen.
HolyBlackCat

Antworten:

20

Was ist hinter den Kulissen los?

Ein nicht dynamisch zugeordneter Container ist per Definition ein Container fester Größe mit homogenen Elementen. Eine Anordnung von NElementen des Typs Twird im Speicher als eine zusammenhängende Sequenz von angelegten NObjekten vom Typ T.


Warum müssen Arrays einen Typ haben, der seine Größe enthält?

Ich glaube nicht, dass es "notwendig" ist, dass der Typ eines Arrays seine Größe enthält - tatsächlich können Sie einen Zeiger verwenden, um auf eine zusammenhängende Folge von TObjekten zu verweisen . Ein solcher Zeiger würde Größeninformationen über das Array verlieren.

Es ist jedoch eine nützliche Sache zu haben. Es verbessert die Typensicherheit und codiert nützliche Informationen zur Kompilierungszeit, die auf verschiedene Arten verwendet werden können. Beispielsweise können Sie Verweise auf Arrays verwenden , um Arrays unterschiedlicher Größe zu überladen

void foo(int(&array)[4]) { /* ... */ }
void foo(int(&array)[8]) { /* ... */ }

oder um die Größe eines Arrays als konstanten Ausdruck herauszufinden

template <typename T, std::size_t N>
constexpr auto sizeOf(const T(&array)[N]) { return N; }

Wie wirkt sich dies auf den Vergleich von Arrays aus?

Wirklich nicht.

Sie können Arrays im C-Stil nicht auf die gleiche Weise vergleichen, wie Sie zwei Zahlen (z int. B. Objekte) vergleichen würden . Sie müssten eine Art lexikografischen Vergleich schreiben und entscheiden, was dies für Sammlungen unterschiedlicher Größe bedeutet. std::vector<T>vorausgesetzt, dass und die gleiche Logik auf Arrays angewendet werden kann.


Bonus: C ++ 11 und höher bietet std::arrayeinen Wrapper um ein Array im C-Stil mit einer containerähnlichen Oberfläche. Es sollte Arrays im C-Stil vorgezogen werden, da es mit anderen Containern (z. B. std::vector<T>) konsistenter ist und auch sofort lexikografische Vergleiche unterstützt .

Vittorio Romeo
quelle
2
"Sie müssten eine Art lexikografischen Vergleich schreiben und entscheiden, was dies für Sammlungen unterschiedlicher Größe bedeutet." Sie können einfach verwenden std::equal(via std::beginund std::enddie für Arrays definiert sind). In diesem Fall sind Arrays unterschiedlicher Größe ungleich.
Stu
3
Es kann erwähnenswert sein, dass bereichsbasierte Schleifen, deren Bereich ein Array ist, die Größe des Arrays zur Kompilierungszeit lesen müssen - es ist etwas subtiler, da (zum Glück!) In diesem Beispiel der Typ des Arrays nie ausgeschrieben wird. Aber es scheint viel mehr zu sein als eine Überladung aufgrund der Größe.
Milo Brandt
8

Die Menge an Speicherplatz, die einem Objekt beim Erstellen zugewiesen wird, hängt vollständig von seinem Typ ab. Bei der Zuordnung handelt es sich nicht um Zuweisungen von newoder malloc, sondern um den zugewiesenen Speicherplatz, damit Sie Ihren Konstruktor ausführen und Ihr Objekt initialisieren können.

Wenn Sie eine Struktur definiert haben als (zum Beispiel)

struct A { char a, b; }; //sizeof(A) == 2, ie an A needs 2 bytes of space

Wenn Sie dann das Objekt konstruieren:

A a{'a', 'b'};

Sie können sich den Prozess des Konstruierens des Objekts als einen Prozess vorstellen:

  • Weisen Sie 2 Byte Speicherplatz zu (auf dem Stapel, aber wo spielt dies für dieses Beispiel keine Rolle)
  • Führen Sie den Konstruktor des Objekts aus (in diesem Fall kopieren Sie 'a'und 'b'zum Objekt)

Es ist wichtig zu beachten, dass die benötigten 2 Bytes Speicherplatz vollständig vom Typ des Objekts abhängen. Die Argumente für die Funktion spielen keine Rolle. Für ein Array ist der Prozess also der gleiche, außer dass jetzt der benötigte Speicherplatz von der Anzahl der Elemente im Array abhängt.

char a[] = {'a'}; //need space for 1 element
char b[] = {'a', 'b', 'c', 'd', 'e'}; //need space for 5 elements

Die Arten von aund bmüssen die Tatsache widerspiegeln, dass agenügend Platz für 1 bZeichen und genügend Platz für 5 Zeichen benötigt wird. Dies bedeutet, dass sich die Größe dieser Arrays nicht plötzlich ändern kann. Sobald ein Array mit 5 Elementen erstellt wurde, handelt es sich immer um ein Array mit 5 Elementen. Um "Array" -ähnliche Objekte zu haben, bei denen die Größe variieren kann, benötigen Sie eine dynamische Speicherzuweisung, die Ihr Buch irgendwann abdecken sollte.

SirGuy
quelle
0

Dies hat einen internen Grund für die Laufzeitbibliothek. Wenn Sie zum Beispiel die folgenden Aussagen berücksichtigen:

unsigned int i;
unsigned int *iPtr;
unsigned int *iPtrArr[2];
unsigned int **iPtrHandle;

Dann wird klar, was das Problem ist: Zum Beispiel muss sich die Adressierung von unsigned int *mit der sizeof operatoroder Adressierung von befassen unsigned int.

Es gibt eine detailliertere Erklärung für den Rest dessen, was Sie hier sehen, aber es ist größtenteils eine Zusammenfassung dessen, was in der C-Programmiersprache, 2. Ausgabe von Kernighan und Ritchie über das Programm behandelt wurde, das den Klartext des deklarierten Typs druckt Zeichenfolge.

CR Ward
quelle