strings - print string array c++




Wie verwende ich Arrays in C++? (4)

5. Häufige Fallstricke bei der Verwendung von Arrays.

5.1 Fallstricke: Vertrauenswürdige Typ-unsichere Verknüpfung.

OK, Ihnen wurde gesagt, oder Sie haben selbst herausgefunden, dass Globals (Namespace-Scope-Variablen, auf die außerhalb der Übersetzungseinheit zugegriffen werden kann) Evil ™ sind. Aber wusstest du, wie wahrlich Evil ™ sie sind? Betrachten Sie das folgende Programm, bestehend aus zwei Dateien [main.cpp] und [numbers.cpp]:

// [main.cpp]
#include <iostream>

extern int* numbers;

int main()
{
    using namespace std;
    for( int i = 0;  i < 42;  ++i )
    {
        cout << (i > 0? ", " : "") << numbers[i];
    }
    cout << endl;
}

// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

In Windows 7 kompiliert und verknüpft dies sowohl mit MinGW g ++ 4.4.1 und Visual C ++ 10.0.

Da die Typen nicht übereinstimmen, stürzt das Programm ab, wenn Sie es ausführen.

In-the-formal Erklärung: Das Programm hat Undefined Behavior (UB), und anstatt es zu stürzen, kann es einfach hängen, oder vielleicht nichts tun, oder es kann bedrohliche E-Mails an die Präsidenten der USA, Russland, Indien senden, China und die Schweiz, und lass Nasal Daemons aus deiner Nase fliegen.

Erklärung in der Praxis: In main.cpp das Array als ein Zeiger behandelt, der sich an der gleichen Adresse wie das Array befindet. Für 32-Bit-Executable bedeutet dies, dass der erste int Wert im Array als Zeiger behandelt wird. Dh, in main.cpp enthält oder enthält die Zahlenvariable (int*)1 . Dies veranlaßt das Programm, auf den Speicher ganz unten im Adreßraum zuzugreifen, was üblicherweise reserviert ist und eine Trap-Veranlassung verursacht. Ergebnis: Sie bekommen einen Absturz.

Die Compiler sind vollständig berechtigt, diesen Fehler nicht zu diagnostizieren, weil C ++ 11 § 3.5 / 10 über die Anforderung kompatibler Typen für die Deklarationen sagt,

[N3290 §3.5 / 10]
Ein Verstoß gegen diese Regel bei der Typidentität erfordert keine Diagnose.

Derselbe Absatz beschreibt die Variation, die erlaubt ist:

... Deklarationen für ein Array-Objekt können Array-Typen angeben, die sich durch das Vorhandensein oder Fehlen einer großen Array-Grenze (8.3.4) unterscheiden.

Diese erlaubte Variation beinhaltet nicht die Deklaration eines Namens als ein Array in einer Übersetzungseinheit und als einen Zeiger in einer anderen Übersetzungseinheit.

5.2 Fallstricke: Vorzeitige Optimierung ( memset & friends).

Noch nicht geschrieben

5.3 Fallstricke: Verwenden des C-Idioms, um die Anzahl der Elemente zu erhalten.

Mit tiefer C Erfahrung ist es natürlich zu schreiben ...

#define N_ITEMS( array )   (sizeof( array )/sizeof( array[0] ))

Da ein array bei Bedarf zum Zeiger auf das erste Element abfällt, kann der Ausdruck sizeof(a)/sizeof(a[0]) auch als sizeof(a)/sizeof(*a) . Es bedeutet dasselbe, und egal, wie es geschrieben steht, es ist das C-Idiom, um die Zahlenelemente des Arrays zu finden.

Hauptfehler: Das C-Idiom ist nicht typsicher. Zum Beispiel der Code ...

#include <stdio.h>

#define N_ITEMS( array ) (sizeof( array )/sizeof( *array ))

void display( int const a[7] )
{
    int const   n = N_ITEMS( a );          // Oops.
    printf( "%d elements.\n", n );
}

int main()
{
    int const   moohaha[]   = {1, 2, 3, 4, 5, 6, 7};

    printf( "%d elements, calling display...\n", N_ITEMS( moohaha ) );
    display( moohaha );
}

übergibt einen Zeiger an N_ITEMS und führt daher höchstwahrscheinlich zu einem falschen Ergebnis. In Windows 7 als ausführbare 32-Bit-Datei kompiliert ...

7 Elemente, Display aufrufen ...
1 Elemente.

  1. Der Compiler schreibt int const a[7] in nur int const a[] .
  2. Der Compiler schreibt int const a[] nach int const* a .
  3. N_ITEMS wird daher mit einem Zeiger aufgerufen.
  4. Für eine ausführbare 32-Bit- sizeof(array) (Größe eines Zeigers) ist dann 4.
  5. sizeof(*array) entspricht sizeof(int) , was für eine ausführbare 32-Bit-Datei auch 4 ist.

Um diesen Fehler zur Laufzeit zu erkennen, können Sie ...

#include <assert.h>
#include <typeinfo>

#define N_ITEMS( array )       (                               \
    assert((                                                    \
        "N_ITEMS requires an actual array as argument",        \
        typeid( array ) != typeid( &*array )                    \
        )),                                                     \
    sizeof( array )/sizeof( *array )                            \
    )

7 Elemente, Display aufrufen ...
Assertion failed: ("N_ITEMS benötigt ein aktuelles Array als Argument", typeid (a)! = Typeid (& * a)), Datei runtime_detect ion.cpp, Zeile 16

Diese Anwendung hat die Runtime aufgefordert, sie auf ungewöhnliche Weise zu beenden.
Bitte kontaktieren Sie das Support-Team der Anwendung für weitere Informationen.

Die Laufzeitfehlererkennung ist besser als keine Erkennung, aber es verschwendet ein wenig Prozessorzeit und vielleicht viel mehr Programmierzeit. Besser mit der Erkennung zur Kompilierzeit! Wenn Sie Arrays lokaler Typen mit C ++ 98 nicht unterstützen möchten, können Sie Folgendes tun:

#include <stddef.h>

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

#define N_ITEMS( array )       n_items( array )

Kompiliert diese Definition in das erste vollständige Programm, mit g ++, habe ich ...

M: \ count> g ++ compile_time_detection.cpp
compile_time_detection.cpp: In der Funktion 'void display (const int *)':
compile_time_detection.cpp: 14: error: keine passende Funktion für den Aufruf von 'n_items (const int * &)'

M: \ Anzahl> _

Wie es funktioniert: Das Array wird als Verweis auf n_items und daher nicht zum Zeiger auf das erste Element, und die Funktion kann nur die Anzahl der vom Typ angegebenen Elemente zurückgeben.

Mit C ++ 11 können Sie dies auch für Arrays des lokalen Typs verwenden, und es ist das typsichere C ++ - Idiom zum Auffinden der Anzahl von Elementen eines Arrays.

5.4 C ++ 11 & C ++ 14 Fallstricke: Verwenden einer constexpr Array- constexpr .

Mit C ++ 11 und später ist es natürlich, aber wie Sie sehen werden gefährlich !, um die C ++ 03-Funktion zu ersetzen

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size n_items( Type (&)[n] ) { return n; }

mit

using Size = ptrdiff_t;

template< class Type, Size n >
constexpr auto n_items( Type (&)[n] ) -> Size { return n; }

Die wesentliche Änderung ist die Verwendung von constexpr , wodurch diese Funktion eine Kompilierzeitkonstante erzeugen kann.

Zum Beispiel kann im Gegensatz zur C ++ 03-Funktion eine solche Kompilierzeitkonstante verwendet werden, um ein Array derselben Größe wie ein anderes zu deklarieren:

// Example 1
void foo()
{
    int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4};
    constexpr Size n = n_items( x );
    int y[n] = {};
    // Using y here.
}

Aber bedenken Sie diesen Code mit der constexpr Version:

// Example 2
template< class Collection >
void foo( Collection const& c )
{
    constexpr int n = n_items( c );     // Not in C++14!
    // Use c here
}

auto main() -> int
{
    int x[42];
    foo( x );
}

Die Falle: -pedantic-errors Juli 2015 kompiliert das obige mit MinGW-64 5.1.0 mit -pedantic-errors , und testet mit den Online-Compilern auf gcc.godbolt.org/ , auch mit clang 3.0 und clang 3.2, aber nicht mit clang 3.3, 3.4.1, 3.5.0, 3.5.1, 3.6 (rc1) oder 3.7 (experimentell). Und wichtig für die Windows-Plattform, kompiliert es nicht mit Visual C ++ 2015. Der Grund ist eine C ++ 11 / C ++ 14-Anweisung über die Verwendung von Referenzen in constexpr Ausdrücken:

C ++ 11 C ++ 14 $ 5,19 / 2 neunter Gedankenstrich

Ein Bedingungsausdruck e ist ein Kernkonstantenausdruck, es sei denn, die Auswertung von e nach den Regeln der abstrakten Maschine (1.9) würde einen der folgenden Ausdrücke auswerten:

  • ein ID-Ausdruck , der sich auf eine Variable oder ein Datenelement des Referenztyps bezieht, es sei denn, der Verweis hat eine vorangehende Initialisierung und beides
    • es wird mit einem konstanten Ausdruck oder initialisiert
    • es ist ein nicht statisches Datenelement eines Objekts, dessen Lebensdauer mit der Auswertung von e begann;

Man kann immer ausführlicher schreiben

// Example 3  --  limited

using Size = ptrdiff_t;

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = std::extent< decltype( c ) >::value;
    // Use c here
}

... aber dies schlägt fehl, wenn Collection kein unformatiertes Array ist.

To deal with collections that can be non-arrays one needs the overloadability of an n_items function, but also, for compile time use one needs a compile time representation of the array size. And the classic C++03 solution, which works fine also in C++11 and C++14, is to let the function report its result not as a value but via its function result type . For example like this:

// Example 4 - OK (not ideal, but portable and safe)

#include <array>
#include <stddef.h>

using Size = ptrdiff_t;

template< Size n >
struct Size_carrier
{
    char sizer[n];
};

template< class Type, Size n >
auto static_n_items( Type (&)[n] )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

template< class Type, size_t n >        // size_t for g++
auto static_n_items( std::array<Type, n> const& )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

#define STATIC_N_ITEMS( c ) \
    static_cast<Size>( sizeof( static_n_items( c ).sizer ) )

template< class Collection >
void foo( Collection const& c )
{
    constexpr Size n = STATIC_N_ITEMS( c );
    // Use c here
    (void) c;
}

auto main() -> int
{
    int x[42];
    std::array<int, 43> y;
    foo( x );
    foo( y );
}

About the choice of return type for static_n_items : this code doesn't use std::integral_constant because with std::integral_constant the result is represented directly as a constexpr value, reintroducing the original problem. Instead of a Size_carrier class one can let the function directly return a reference to an array. However, not everybody is familiar with that syntax.

About the naming: part of this solution to the constexpr -invalid-due-to-reference problem is to make the choice of compile time constant explicit.

Hopefully the oops-there-was-a-reference-involved-in-your- constexpr issue will be fixed with C++17, but until then a macro like the STATIC_N_ITEMS above yields portability, eg to the clang and Visual C++ compilers, retaining type safety.

Related: macros do not respect scopes, so to avoid name collisions it can be a good idea to use a name prefix, eg MYLIB_STATIC_N_ITEMS .

C ++ erbte Arrays von C, wo sie praktisch überall verwendet werden. C ++ bietet Abstraktionen, die einfacher zu verwenden und weniger fehleranfällig sind ( std::vector<T> seit C ++ 98 und std::array<T, n> seit C++11 ), was Array nicht benötigt Es kommt so oft vor, wie es in C der Fall ist. Wenn Sie jedoch älteren Code lesen oder mit einer in C geschriebenen Bibliothek interagieren, sollten Sie genau wissen, wie Arrays funktionieren.

Diese FAQ ist in fünf Teile unterteilt:

  1. Arrays auf Textebene und zugreifende Elemente
  2. Array-Erstellung und Initialisierung
  3. Zuweisung und Parameterübergabe
  4. mehrdimensionale Arrays und Arrays von Zeigern
  5. häufige Fallstricke bei der Verwendung von Arrays

Wenn Sie der Meinung sind, dass in dieser FAQ etwas Wichtiges fehlt, schreiben Sie eine Antwort und verlinken Sie sie hier als zusätzlichen Teil.

Im folgenden Text bedeutet "Array" "C-Array", nicht die Klassenvorlage std::array . Grundlegende Kenntnisse der C-Deklaratorsyntax werden vorausgesetzt. Beachten Sie, dass die manuelle Verwendung von new und delete wie unten gezeigt, bei Ausnahmen extrem gefährlich ist, aber das ist das Thema einer anderen FAQ .

(Hinweis: Dies ist ein Eintrag in die C ++ - FAQ von Stack Overflow . Wenn Sie die Idee, eine FAQ in diesem Formular bereitzustellen, kritisieren möchten, dann wäre das Posting auf meta, mit dem all dies begonnen wurde , der richtige Ort dafür Diese Frage wird im C ++ - Chatraum überwacht, wo die FAQ-Idee von Anfang an begann, so dass Ihre Antwort sehr wahrscheinlich von denjenigen gelesen wird, die die Idee hatten.)


Array-Erstellung und Initialisierung

Wie bei jeder anderen Art von C ++ - Objekt können Arrays entweder direkt in benannten Variablen gespeichert werden (dann muss die Größe eine Kompilierzeitkonstante sein; C ++ unterstützt keine VLAs ), oder sie können anonym auf dem Heap gespeichert und indirekt über zugegriffen werden Zeiger (nur dann kann die Größe zur Laufzeit berechnet werden).

Automatische Arrays

Automatische Arrays (Arrays, die "auf dem Stack" leben) werden jedes Mal erstellt, wenn der Steuerungsfluss die Definition einer nicht statischen lokalen Array-Variablen durchläuft:

void foo()
{
    int automatic_array[8];
}

Die Initialisierung wird in aufsteigender Reihenfolge durchgeführt. Beachten Sie, dass die Anfangswerte vom Elementtyp T abhängen:

  • Wenn T ein POD (wie im obigen Beispiel int ), findet keine Initialisierung statt.
  • Andernfalls initialisiert der Standardkonstruktor von T alle Elemente.
  • Wenn T keinen verfügbaren Standardkonstruktor bereitstellt, kompiliert das Programm nicht.

Alternativ können die Anfangswerte explizit im Array-Initialisierer angegeben werden , eine durch Kommas getrennte Liste, die von geschweiften Klammern umgeben ist:

    int primes[8] = {2, 3, 5, 7, 11, 13, 17, 19};

Da in diesem Fall die Anzahl der Elemente im Array-Initialisierer gleich der Größe des Arrays ist, ist die manuelle Angabe der Größe redundant. Es kann automatisch vom Compiler abgeleitet werden:

    int primes[] = {2, 3, 5, 7, 11, 13, 17, 19};   // size 8 is deduced

Es ist auch möglich, die Größe anzugeben und einen kürzeren Array-Initialisierer bereitzustellen:

    int fibonacci[50] = {0, 1, 1};   // 47 trailing zeros are deduced

In diesem Fall werden die verbleibenden Elemente auf zero-initialized . Beachten Sie, dass C ++ einen leeren Array-Initialisierer zulässt (alle Elemente werden initialisiert), während C89 dies nicht tut (mindestens ein Wert ist erforderlich). Beachten Sie außerdem, dass Array-Initialisierer nur zum Initialisieren von Arrays verwendet werden können. Sie können später nicht in Zuweisungen verwendet werden.

Statische Arrays

Statische Arrays (Arrays, die "im Datensegment" leben) sind lokale Array-Variablen, die mit dem static Schlüsselwort definiert sind, und Array-Variablen im Namespace-Bereich ("globale Variablen"):

int global_static_array[8];

void foo()
{
    static int local_static_array[8];
}

(Beachten Sie, dass Variablen im Namespacebereich implizit statisch sind. Das Hinzufügen des static Schlüsselworts zu ihrer Definition hat eine völlig andere, nicht mehr empfohlene Bedeutung .)

Hier verhalten sich statische Arrays anders als automatische Arrays:

  • Statische Arrays ohne einen Array-Initialisierer werden vor jeder weiteren möglichen Initialisierung auf Null initialisiert.
  • Statische POD-Arrays werden genau einmal initialisiert, und die Anfangswerte werden normalerweise in die ausführbare Datei geschrieben. In diesem Fall entstehen zur Laufzeit keine Initialisierungskosten. Dies ist jedoch nicht immer die platzsparendste Lösung und wird vom Standard nicht benötigt.
  • Statische Nicht-POD-Arrays werden initialisiert, wenn der Steuerungsfluss das erste Mal ihre Definition durchläuft. Im Fall von lokalen statischen Arrays kann dies niemals passieren, wenn die Funktion nie aufgerufen wird.

(Keine der obigen Angaben ist für Arrays spezifisch. Diese Regeln gelten auch für andere Arten von statischen Objekten.)

Array-Datenelemente

Array-Datenelemente werden erstellt, wenn ihr eigenes Objekt erstellt wird. Unglücklicherweise bietet C ++ 03 keine Möglichkeit, Arrays in der Memberinitialisierungsliste zu initialisieren , daher muss die Initialisierung mit Zuweisungen gefälscht werden:

class Foo
{
    int primes[8];

public:

    Foo()
    {
        primes[0] = 2;
        primes[1] = 3;
        primes[2] = 5;
        // ...
    }
};

Alternativ können Sie im Konstruktorhauptteil ein automatisches Array definieren und die Elemente kopieren:

class Foo
{
    int primes[8];

public:

    Foo()
    {
        int local_array[] = {2, 3, 5, 7, 11, 13, 17, 19};
        std::copy(local_array + 0, local_array + 8, primes + 0);
    }
};

In C ++ 0x können Arrays dank einheitlicher Initialisierung in der Memberinitialisierungsliste initialisiert werden :

class Foo
{
    int primes[8];

public:

    Foo() : primes { 2, 3, 5, 7, 11, 13, 17, 19 }
    {
    }
};

Dies ist die einzige Lösung, die mit Elementtypen funktioniert, die keinen Standardkonstruktor haben.

Dynamische Arrays

Dynamische Arrays haben keine Namen, daher können sie nur über Zeiger auf sie zugreifen. Da sie keine Namen haben, werde ich sie ab sofort als "anonyme Arrays" bezeichnen.

In C werden anonyme Arrays über malloc und Freunde erstellt. In C ++ werden anonyme Arrays mit der new T[size] -Syntax erstellt, die einen Zeiger auf das erste Element eines anonymen Arrays zurückgibt:

std::size_t size = compute_size_at_runtime();
int* p = new int[size];

Die folgende ASCII-Grafik zeigt das Speicherlayout, wenn die Größe zur Laufzeit als 8 berechnet wird:

             +---+---+---+---+---+---+---+---+
(anonymous)  |   |   |   |   |   |   |   |   |
             +---+---+---+---+---+---+---+---+
               ^
               |
               |
             +-|-+
          p: | | |                               int*
             +---+

Offensichtlich benötigen anonyme Arrays aufgrund des zusätzlichen Zeigers, der separat gespeichert werden muss, mehr Speicher als benannte Arrays. (Es gibt auch zusätzliche Kosten für den kostenlosen Shop.)

Beachten Sie, dass hier kein Verfall von Array zu Pointer stattfindet. Obwohl die Auswertung von new int[size] tatsächlich ein Array von Ganzzahlen erzeugt, ist das Ergebnis des Ausdrucks new int[size] bereits ein Zeiger auf eine einzelne ganze Zahl (das erste Element), kein Array von Ganzzahlen oder ein Zeiger auf ein Array von Ganzzahlen unbekannter Größe. Dies wäre unmöglich, da das System vom statischen Typ Array-Größen als Kompilierzeitkonstanten benötigt. (Daher habe ich das anonyme Array nicht mit statischen Informationen auf dem Bild versehen.)

In Bezug auf Standardwerte für Elemente verhalten sich anonyme Arrays ähnlich wie automatische Arrays. Normalerweise werden anonyme POD-Arrays nicht initialisiert, aber es gibt eine spezielle Syntax , die eine Wert-Initialisierung auslöst:

int* p = new int[some_computed_size]();

(Beachten Sie das abschließende Klammerpaar direkt vor dem Semikolon.) C ++ 0x vereinfacht die Regeln erneut und ermöglicht das Angeben von Initialwerten für anonyme Arrays dank einheitlicher Initialisierung:

int* p = new int[8] { 2, 3, 5, 7, 11, 13, 17, 19 };

Wenn Sie mit einem anonymen Array fertig sind, müssen Sie es zurück an das System freigeben:

delete[] p;

Sie müssen jedes anonyme Array genau einmal freigeben und danach nie wieder anfassen. Wenn es überhaupt nicht freigegeben wird, führt dies zu einem Speicherverlust (oder allgemeiner, je nach Elementtyp, einem Ressourcenleck), und wenn versucht wird, es mehrmals freizugeben, führt dies zu undefiniertem Verhalten. Die Verwendung der Nicht-Array-Form delete (oder free ) anstelle von delete[] , um das Array freizugeben, ist ebenfalls undefiniert .


Zuordnung

Ohne besonderen Grund können Arrays nicht einander zugewiesen werden. Verwenden Sie stattdessen std::copy :

#include <algorithm>

// ...

int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int b[8];
std::copy(a + 0, a + 8, b);

Dies ist flexibler als das, was eine echte Array-Zuweisung bereitstellen könnte, da es möglich ist, Scheiben größerer Arrays in kleinere Arrays zu kopieren. std::copy ist normalerweise auf primitive Typen spezialisiert, um maximale Leistung zu erzielen. Es ist unwahrscheinlich, dass std::memcpy besser std::memcpy . Im Zweifelsfall messen.

Obwohl Sie Arrays nicht direkt zuweisen können, können Sie Strukturen und Klassen zuweisen, die Array-Elemente enthalten . Das liegt daran, dass Array-Elemente memberwise von dem Zuweisungsoperator kopiert werden, der vom Compiler standardmäßig bereitgestellt wird. Wenn Sie den Zuweisungsoperator für Ihre eigenen Struktur- oder Klassentypen manuell definieren, müssen Sie auf das manuelle Kopieren für die Array-Mitglieder zurückgreifen.

Parameterübergabe

Arrays können nicht nach Wert übergeben werden. Sie können sie entweder per Zeiger oder als Referenz übergeben.

Pass-by-Zeiger

Da Arrays selbst nicht als Wert übergeben werden können, wird normalerweise ein Zeiger auf das erste Element als Wert übergeben. Dies wird oft als "Zeiger übergeben" bezeichnet. Da die Größe des Arrays nicht über diesen Zeiger abrufbar ist, müssen Sie einen zweiten Parameter übergeben, der die Größe des Arrays angibt (die klassische C-Lösung) oder einen zweiten Zeiger nach dem letzten Element des Arrays (die C ++ - Iterator-Lösung). :

#include <numeric>
#include <cstddef>

int sum(const int* p, std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

int sum(const int* p, const int* q)
{
    return std::accumulate(p, q, 0);
}

Als syntaktische Alternative können Sie Parameter auch als T p[] deklarieren, und es bedeutet genau dasselbe wie T* p im Kontext von Parameterlisten :

int sum(const int p[], std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

Sie können sich den Compiler als Umschreiben von T p[] bis T *p im Kontext von Parameterlisten vorstellen . Diese spezielle Regel ist teilweise für die ganze Verwirrung über Arrays und Zeiger verantwortlich. In jedem anderen Kontext macht das Deklarieren von etwas als Array oder als Zeiger einen großen Unterschied.

Leider können Sie auch eine Größe in einem Array-Parameter angeben, der vom Compiler ignoriert wird. Das heißt, die folgenden drei Signaturen sind genau gleichwertig, wie durch die Compilerfehler angezeigt:

int sum(const int* p, std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[], std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[8], std::size_t n)   // the 8 has no meaning here

Durch Verweis gehen

Arrays können auch als Referenz übergeben werden:

int sum(const int (&a)[8])
{
    return std::accumulate(a + 0, a + 8, 0);
}

In diesem Fall ist die Array-Größe signifikant. Da das Schreiben einer Funktion, die nur Arrays mit genau 8 Elementen akzeptiert, wenig sinnvoll ist, schreiben Programmierer normalerweise solche Funktionen als Templates:

template <std::size_t n>
int sum(const int (&a)[n])
{
    return std::accumulate(a + 0, a + n, 0);
}

Beachten Sie, dass Sie eine solche Funktionsvorlage nur mit einem tatsächlichen Array von ganzen Zahlen und nicht mit einem Zeiger auf eine ganze Zahl aufrufen können. Die Größe des Arrays wird automatisch abgeleitet, und für jede Größe n wird eine andere Funktion aus der Vorlage instanziiert. Sie können auch sehr nützliche Funktionsschablonen schreiben, die sowohl vom Elementtyp als auch von der Größe abstrahieren.


Programmierer verwechseln häufig mehrdimensionale Arrays mit Arrays von Zeigern.

Mehrdimensionale Arrays

Die meisten Programmierer sind mit benannten mehrdimensionalen Arrays vertraut, aber viele wissen nicht, dass multidimensionale Arrays auch anonym erstellt werden können. Mehrdimensionale Arrays werden oft als "Arrays von Arrays" oder " echte multidimensionale Arrays" bezeichnet.

Benannte mehrdimensionale Arrays

Wenn Sie benannte mehrdimensionale Arrays verwenden, müssen alle Dimensionen zum Zeitpunkt der Kompilierung bekannt sein:

int H = read_int();
int W = read_int();

int connect_four[6][7];   // okay

int connect_four[H][7];   // ISO C++ forbids variable length array
int connect_four[6][W];   // ISO C++ forbids variable length array
int connect_four[H][W];   // ISO C++ forbids variable length array

So sieht ein benanntes mehrdimensionales Array im Speicher aus:

              +---+---+---+---+---+---+---+
connect_four: |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+

Beachten Sie, dass 2D-Raster wie oben nur nützliche Visualisierungen sind. Aus der Sicht von C ++ ist Speicher eine "flache" Sequenz von Bytes. Die Elemente eines mehrdimensionalen Arrays werden in Reihen-Haupt-Reihenfolge gespeichert. Das heißt, connect_four[0][6] und connect_four[1][0] sind Nachbarn im Speicher. Tatsächlich bezeichnen connect_four[0][7] und connect_four[1][0] dasselbe Element! Dies bedeutet, dass Sie mehrdimensionale Arrays verwenden und sie als große eindimensionale Arrays behandeln können:

int* p = &connect_four[0][0];
int* q = p + 42;
some_int_sequence_algorithm(p, q);

Anonyme mehrdimensionale Arrays

Bei anonymen mehrdimensionalen Arrays müssen alle Dimensionen außer der ersten zur Kompilierungszeit bekannt sein:

int (*p)[7] = new int[6][7];   // okay
int (*p)[7] = new int[H][7];   // okay

int (*p)[W] = new int[6][W];   // ISO C++ forbids variable length array
int (*p)[W] = new int[H][W];   // ISO C++ forbids variable length array

So sieht ein anonymes mehrdimensionales Array im Speicher aus:

              +---+---+---+---+---+---+---+
        +---> |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |
      +-|-+
   p: | | |
      +---+

Beachten Sie, dass das Array selbst immer noch als ein einzelner Block im Speicher reserviert ist.

Arrays von Zeigern

Sie können die Beschränkung der festen Breite überwinden, indem Sie eine andere Ebene der Indirektion einführen.

Benannte Arrays von Zeigern

Hier ist ein benanntes Array von fünf Zeigern, die mit anonymen Arrays unterschiedlicher Länge initialisiert werden:

int* triangle[5];
for (int i = 0; i < 5; ++i)
{
    triangle[i] = new int[5 - i];
}

// ...

for (int i = 0; i < 5; ++i)
{
    delete[] triangle[i];
}

Und so sieht es im Gedächtnis aus:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
          +---+---+---+---+---+

Da jede Zeile jetzt einzeln zugewiesen wird, funktioniert das Anzeigen von 2D-Arrays als 1D-Arrays nicht mehr.

Anonyme Arrays von Zeigern

Hier ist ein anonymes Array von 5 (oder einer anderen Anzahl von) Zeigern, die mit anonymen Arrays unterschiedlicher Länge initialisiert werden:

int n = calculate_five();   // or any other number
int** p = new int*[n];
for (int i = 0; i < n; ++i)
{
    p[i] = new int[n - i];
}

// ...

for (int i = 0; i < n; ++i)
{
    delete[] p[i];
}
delete[] p;   // note the extra delete[] !

Und so sieht es im Gedächtnis aus:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
          | | | | | | | | | | |
          +---+---+---+---+---+
            ^
            |
            |
          +-|-+
       p: | | |
          +---+

Umwandlungen

Der Verfall von Array zu Pointer erstreckt sich natürlich auf Arrays von Arrays und Arrays von Zeigern:

int array_of_arrays[6][7];
int (*pointer_to_array)[7] = array_of_arrays;

int* array_of_pointers[6];
int** pointer_to_pointer = array_of_pointers;

Es gibt jedoch keine implizite Umwandlung von T[h][w] nach T** . Wenn eine solche implizite Konvertierung vorhanden wäre, wäre das Ergebnis ein Zeiger auf das erste Element eines Arrays von h Zeigern auf T (die jeweils auf das erste Element einer Zeile im ursprünglichen 2D-Array zeigen), aber dieses Zeigerfeld existiert nicht irgendwo in der Erinnerung noch. Wenn Sie eine solche Konvertierung wünschen, müssen Sie das erforderliche Pointer-Array manuell erstellen und füllen:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = connect_four[i];
}

// ...

delete[] p;

Beachten Sie, dass dies eine Ansicht des ursprünglichen mehrdimensionalen Arrays generiert. Wenn Sie stattdessen eine Kopie benötigen, müssen Sie zusätzliche Arrays erstellen und die Daten selbst kopieren:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = new int[7];
    std::copy(connect_four[i], connect_four[i + 1], p[i]);
}

// ...

for (int i = 0; i < 6; ++i)
{
    delete[] p[i];
}
delete[] p;




c++-faq