441 Stimmen

Übergabe eines 2D-Arrays an eine C++-Funktion

Ich habe eine Funktion, die ich als Parameter ein 2D-Array mit variabler Größe annehmen möchte.

Bislang habe ich dies:

void myFunction(double** myArray){
     myArray[x][y] = 5;
     etc...
}

Und ich habe ein Array an anderer Stelle in meinem Code deklariert:

double anArray[10][10];

Allerdings ist der Aufruf myFunction(anArray) gibt eine Fehlermeldung aus.

Ich möchte das Array nicht kopieren, wenn ich es eingebe. Alle Änderungen, die in myFunction sollte den Zustand von anArray . Wenn ich es richtig verstehe, möchte ich als Argument nur einen Zeiger auf ein 2D-Array übergeben. Die Funktion muss auch Arrays unterschiedlicher Größe akzeptieren. Also zum Beispiel, [10][10] y [5][5] . Wie kann ich das tun?

531voto

shengy Punkte 8919

Es gibt drei Möglichkeiten, ein 2D-Array an eine Funktion zu übergeben:

  1. Der Parameter ist ein 2D-Array

    int array[10][10];
    void passFunc(int a[][10])
    {
        // ...
    }
    passFunc(array);
  2. Der Parameter ist ein Array mit Zeigern

    int *array[10];
    for(int i = 0; i < 10; i++)
        array[i] = new int[10];
    void passFunc(int *a[10]) //Array containing pointers
    {
        // ...
    }
    passFunc(array);
  3. Der Parameter ist ein Zeiger auf einen Zeiger

    int **array;
    array = new int *[10];
    for(int i = 0; i <10; i++)
        array[i] = new int[10];
    void passFunc(int **a)
    {
        // ...
    }
    passFunc(array);

247voto

legends2k Punkte 29253

Feste Größe

1. Übergabe durch Verweis

template <size_t rows, size_t cols>
void process_2d_array_template(int (&array)[rows][cols])
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < cols; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

In C++ ist es wahrscheinlich am sichersten, das Array per Referenz zu übergeben, ohne die Dimensionsinformationen zu verlieren, da man sich keine Sorgen machen muss, dass der Aufrufer eine falsche Dimension übergibt (Compilerflags bei Nichtübereinstimmung). Dies ist jedoch bei dynamischen (freestore) Arrays nicht möglich; es funktioniert bei automatischen ( in der Regel stapelfähig ) nur Arrays, d.h. die Dimensionalität sollte zur Kompilierzeit bekannt sein.

2. Passieren durch Zeiger

void process_2d_array_pointer(int (*array)[5][10])
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < 5; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < 10; ++j)
            std::cout << (*array)[i][j] << '\t';
        std::cout << std::endl;
    }    
}

Das C-Äquivalent der vorherigen Methode ist die Übergabe des Arrays per Zeiger. Dies ist nicht zu verwechseln mit der Übergabe durch den zerfallenen Zeigertyp des Arrays (3) Dies ist die gängige und beliebte Methode, die zwar weniger sicher als diese ist, aber dafür flexibler. Wie (1) Verwenden Sie diese Methode, wenn alle Dimensionen des Arrays festgelegt und zur Kompilierungszeit bekannt sind. Beachten Sie, dass beim Aufruf der Funktion die Adresse des Arrays übergeben werden sollte process_2d_array_pointer(&a) und nicht die Adresse des ersten Elements durch Zerfall process_2d_array_pointer(a) .

Variable Größe

Diese werden von C geerbt, sind aber weniger sicher, da der Compiler keine Möglichkeit hat, zu prüfen und zu garantieren, dass der Aufrufer die erforderlichen Abmessungen übergibt. Die Funktion verlässt sich nur darauf, was der Aufrufer als Dimension(en) angibt. Diese sind flexibler als die oben genannten, da ihnen immer Arrays unterschiedlicher Länge übergeben werden können.

Es ist zu bedenken, dass es in C nicht möglich ist, ein Array direkt an eine Funktion zu übergeben [während sie in C++ als Referenz übergeben werden können (1) ]; (2) wird ein Zeiger auf das Array und nicht das Array selbst übergeben. Die Übergabe eines Arrays im Ist-Zustand wird zu einer Zeiger-Kopier-Operation, die erleichtert wird durch die Eigenschaft von Arrays, in einen Zeiger zu zerfallen .

3. Übergeben Sie mit (value) einen Zeiger auf den zerfallenen Typ

// int array[][10] is just fancy notation for the same thing
void process_2d_array(int (*array)[10], size_t rows)
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < 10; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

Obwohl int array[][10] erlaubt ist, würde ich es nicht gegenüber der obigen Syntax empfehlen, da die obige Syntax deutlich macht, dass der Bezeichner array ist ein einzelner Zeiger auf ein Array von 10 Ganzzahlen, während diese Syntax siehe als wäre es ein 2D-Array, aber es ist derselbe Zeiger auf ein Array mit 10 ganzen Zahlen. Hier kennen wir die Anzahl der Elemente in einer einzelnen Zeile (d.h. die Spaltengröße, hier 10), aber die Anzahl der Zeilen ist unbekannt und muss daher als Argument übergeben werden. In diesem Fall gibt es eine gewisse Sicherheit, da der Compiler erkennen kann, wenn ein Zeiger auf ein Array mit einer zweiten Dimension ungleich 10 übergeben wird. Die erste Dimension ist der variierende Teil und kann weggelassen werden. Siehe hier für die Begründung warum nur die erste Dimension weggelassen werden darf.

4. Übergabe durch Zeiger an einen Zeiger

// int *array[10] is just fancy notation for the same thing
void process_pointer_2_pointer(int **array, size_t rows, size_t cols)
{
    std::cout << __func__ << std::endl;
    for (size_t i = 0; i < rows; ++i)
    {
        std::cout << i << ": ";
        for (size_t j = 0; j < cols; ++j)
            std::cout << array[i][j] << '\t';
        std::cout << std::endl;
    }
}

Auch hier gibt es eine alternative Syntax von int *array[10] was dasselbe ist wie int **array . In dieser Syntax ist die [10] wird ignoriert, da er in einen Zeiger zerfällt und dadurch zu int **array . Vielleicht ist es nur ein Hinweis für den Aufrufer, dass das übergebene Array mindestens 10 Spalten haben sollte, selbst dann ist die Zeilenzahl erforderlich. In jedem Fall erkennt der Compiler keine Längen-/Größenverstöße (er prüft nur, ob der übergebene Typ ein Zeiger auf einen Zeiger ist), so dass es hier sinnvoll ist, sowohl die Zeilen- als auch die Spaltenanzahl als Parameter zu verlangen.

Anmerkung: (4) ist die am wenigsten sichere Option da sie kaum eine Typenprüfung hat und am unbequemsten ist. Man kann dieser Funktion nicht rechtmäßig ein 2D-Array übergeben; C-FAQ verurteilt die übliche Umgehung durch int x[5][10]; process_pointer_2_pointer((int**)&x[0][0], 5, 10); da es kann möglicherweise zu undefiniertem Verhalten führen aufgrund der Abflachung des Arrays. Der richtige Weg, ein Array in dieser Methode zu übergeben, bringt uns zu dem unbequemen Teil, d.h. wir brauchen ein zusätzliches (Surrogat-)Array von Zeigern, bei dem jedes Element auf die entsprechende Zeile des eigentlichen, zu übergebenden Arrays zeigt; dieses Surrogat wird dann an die Funktion übergeben (siehe unten); all dies, um dieselbe Aufgabe zu erledigen wie die obigen Methoden, die sicherer, sauberer und vielleicht schneller sind.

Hier ist ein Treiberprogramm zum Testen der oben genannten Funktionen:

#include <iostream>

// copy above functions here

int main()
{
    int a[5][10] = { { } };
    process_2d_array_template(a);
    process_2d_array_pointer(&a);    // <-- notice the unusual usage of addressof (&) operator on an array
    process_2d_array(a, 5);
    // works since a's first dimension decays into a pointer thereby becoming int (*)[10]

    int *b[5];  // surrogate
    for (size_t i = 0; i < 5; ++i)
    {
        b[i] = a[i];
    }
    // another popular way to define b: here the 2D arrays dims may be non-const, runtime var
    // int **b = new int*[5];
    // for (size_t i = 0; i < 5; ++i) b[i] = new int[10];
    process_pointer_2_pointer(b, 5, 10);
    // process_2d_array(b, 5);
    // doesn't work since b's first dimension decays into a pointer thereby becoming int**
}

33voto

LemonPi Punkte 848

Überrascht, dass dies noch niemand erwähnt hat, aber Sie können einfach Vorlage auf alles 2D Unterstützung [][] Semantik.

template <typename TwoD>
void myFunction(TwoD& myArray){
     myArray[x][y] = 5;
     etc...
}

// call with
double anArray[10][10];
myFunction(anArray);

Es funktioniert mit jeder 2D-"array-ähnlichen" Datenstruktur, wie z.B. std::vector<std::vector<T>> oder einen benutzerdefinierten Typ, um die Wiederverwendung von Code zu maximieren.

22voto

Benjamin Lindley Punkte 99114

Sie können eine Funktionsvorlage wie folgt erstellen:

template<int R, int C>
void myFunction(double (&myArray)[R][C])
{
    myArray[x][y] = 5;
    etc...
}

Dann haben Sie beide Dimensionsgrößen über R und C. Für jede Array-Größe wird eine andere Funktion erstellt. Wenn Ihre Funktion also groß ist und Sie sie mit einer Vielzahl verschiedener Array-Größen aufrufen, kann dies kostspielig sein. Sie könnten es aber als Wrapper über eine Funktion wie diese verwenden:

void myFunction(double * arr, int R, int C)
{
    arr[x * C + y] = 5;
    etc...
}

Es behandelt das Array als eindimensional und verwendet Arithmetik, um die Offsets der Indizes herauszufinden. In diesem Fall würden Sie die Vorlage wie folgt definieren:

template<int C, int R>
void myFunction(double (&myArray)[R][C])
{
    myFunction(*myArray, R, C);
}

13voto

Sergey Kalinichenko Punkte 694383

anArray[10][10] ist kein Zeiger auf einen Zeiger, sondern ein zusammenhängender Speicherbereich, der für die Speicherung von 100 Werten des Typs double geeignet ist und den der Compiler zu adressieren weiß, weil Sie die Abmessungen angegeben haben. Sie müssen ihn als Array an eine Funktion übergeben. Sie können die Größe der anfänglichen Dimension weglassen, wie folgt:

void f(double p[][10]) {
}

Dadurch können Sie jedoch keine Arrays mit einer anderen letzten Dimension als zehn übergeben.

Die beste Lösung in C++ ist die Verwendung von std::vector<std::vector<double> > : Es ist fast genauso effizient und wesentlich bequemer.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X