Deklarieren einer C-Funktion, um ein Array zurückzugeben

Lesezeit: 7 Minuten

Jaskas Benutzeravatar
Jaska

Wie kann ich eine Funktion erstellen, die ein Array zurückgibt? Ich habe das versucht

const int WIDTH=11;
const int HEIGHT=11;

int main() {
  char A[WIDTH][HEIGHT];
  A=rand_grid(WIDTH,HEIGHT);
  return 0;
}

// Initializes a random board.
char[][] rand_grid(int i, int k) {
  char* A[i][k];
  for(j=0;j<i;++j) {
    for(l=0;l<k;++l) {
      A[j][l]=ran(10);
    }
  }
  return A;
}

// Returns a random number from the set {0,...,9}.
int ran(int i) {
  srand((unsigned int) time(0));
  return(rand()%10);
}

  • Rufen Sie srand() nicht mehr als einmal in Ihrem Programm auf: Das verlangsamt Ihr Programm unnötig und vor allem verringert es die Zufälligkeit der rand()-Funktion.

    – pmg

    21. September 2009 um 8:57 Uhr

Einige Dinge, auf die hingewiesen werden sollte.

Zunächst einmal können Sie kein Array-Objekt zuweisen, wie Sie es hier tun:

char A[WIDTH][HEIGHT];  
A=rand_grid(WIDTH,HEIGHT);

Objekte vom Typ Array sind nicht änderbar.

Zweitens können Funktionen in C keine Array-Typen zurückgeben. Sie können zurückkehren Zeiger zu Arrays, obwohl:

char (*foo(int width))[HEIGHT]
{
  /**
   * dynamically allocate memory for a widthxHEIGHT array of char
   */
  char (*newArr)[HEIGHT] = malloc(sizeof *newArr * width);
  /**
   * initialize array contents here
   */
  return newArr;
}

Die Syntax ist etwas verwirrend; es liest sich wie

       foo                                   -- foo
       foo(int width)                        -- is a function
                                             -- taking an int parameter
      *foo(int width)                        -- returning a pointer
     (*foo(int width))[HEIGHT]               -- to a HEIGHT-element array
char (*foo(int width))[HEIGHT]               -- of char

Für C89 muss HEIGHT im obigen Snippet ein konstanter integraler Ausdruck zur Kompilierzeit sein (entweder ein Makro, ein numerisches Literal oder ein arithmetischer Ausdruck, der aus Makros und/oder numerischen Literalen besteht). Ich bin mir nicht sicher, ob das auch für C99 gilt.

Basierend auf dem von Ihnen geposteten Snippet möchten Sie ein Array nehmen, das Sie bereits zugewiesen haben, und seinen Inhalt initialisieren. Denken Sie daran, dass in den meisten Kontexten ein Ausdruck eines Array-Typs implizit in einen Zeiger auf den Basistyp konvertiert wird. IOW, wenn Sie ein N-Element-Array von T an eine Funktion übergeben, erhält die Funktion tatsächlich einen Zeiger auf T:

void foo (T *p) {...}
...
T arr[N];
foo(arr);

Für 2-D-Arrays ist es etwas hässlicher:

void foo (T (*p)[M]) {...}
...
T arr[N][M];
foo(arr);

Dies hängt auch davon ab, dass M zur Kompilierzeit bekannt ist, was die Nützlichkeit der Funktion einschränkt. Was Sie möchten, ist eine Funktion, die mit einem 2-D-Array beliebiger Größe umgehen kann. Der beste Weg, den ich kenne, um dies zu erreichen, besteht darin, anstatt einen Zeiger auf das Array zu übergeben, die Adresse des ersten Elements im Array zu übergeben[1]und übergeben Sie die Anzahl der Zeilen und Spalten als separate Parameter:

void foo(T *base, size_t rows, size_t cols) {...}
...
T arr[N][M];
foo (&arr[0][0], N, M);

Ihre rand_grid-Funktion würde also etwa so aussehen:

void rand_grid(char *base, size_t rows, size_t cols)
{
  size_t i, j;
  for (i = 0; i < rows; i++)
  {
    for (j = 0; j < cols; j++)
    {
      /**
       * Since base is a simple char *, we must index it
       * as though it points to a 1-d array.  This works if
       * base points to the first element of a 2-d array,
       * since multi-dimensional arrays are contiguous.  
       */
      base[i*cols+j] = initial_value();
    }
  }
}

int main(void)
{
  char A[WIDTH][HEIGHT];
  rand_grid(&A[0][0], WIDTH, HEIGHT);
  ...
}

  1. Obwohl die Ausdrücke &A[0][0] und A denselben Wert (die Basisadresse von A) liefern, sind die Typen der beiden Ausdrücke unterschiedlich. Der erste Ausdruck ergibt einen einfachen Zeiger auf char (char *), während die zweite zu einem Zeiger auf ein 2-D-Array von char (char (*)[HEIGHT]).

  • Diese Antwort ist erstaunlich lehrreich. Vielen Dank!

    – JohannesK

    21. Juni 2013 um 1:57 Uhr

  • Müsste eine C++-Funktion, die ein Array zurückgibt, einen ähnlichen Ansatz verwenden? (etwas außerhalb des Rahmens der Frage, die ich kenne, aber nicht anders genug, um eine neue zu stellen)

    – Ajay

    5. August 2014 um 18:11 Uhr

Michael Krelin - Benutzeravatar des Hackers
Michael Krelin – Hacker

Du kannst nicht. Sie können entweder den Zeiger auf das Array als Parameter übergeben und ihn von der Funktion ändern lassen, oder die Funktion selbst kann Daten zuweisen und den Zeiger zurückgeben.

in Ihrem Fall

void rand_grid(char A[WIDTH][HEIGHT]) {
    A[0][0] = 'A'; // or whatever you intend to do
}

main() {
    char A[WIDTH][HEIGHT];
    rand_grid(A);
}

Bearbeiten: Wie Café darauf hinwies, kann man das eigentlich zurückgeben struct mit einem Array darin, aber natürlich würde das kein C-Programmierer mit klarem Verstand tun.

  • Sie können das Array tatsächlich nach Wert zurückgeben, indem Sie es in a umschließen structaber es ist eine sehr schlechte Idee.

    – Café

    21. September 2009 um 8:41 Uhr

  • Vielen Dank. Ich habe mich gefragt, wie ich das folgende Problem lösen kann: Ich brauche ein Gitter, das mit zufälligen Ganzzahlen gefüllt ist. Ich möchte, dass die Initialisierung eines Arrays nicht auf der Hauptfunktion liegt, da dies im Rest des Programms nützlich wäre. Wie kann ich Zeiger verwenden, um das Problem zu lösen?

    – Jaska

    21. September 2009 um 8:47 Uhr

  • Jaska, Sie sollten diese Unterfrage als separate Frage stellen, dh als neue Frage zum Stapelüberlauf.

    – Frank Grimm

    21. September 2009 um 8:51 Uhr

  • Vorausgesetzt, dass die Array-Dimensionen zur Kompilierzeit festgelegt werden, können Sie das Code-Snippet aus meiner Antwort verwenden. Es ist effektiv eine Zeigerübergabe.

    – Michael Krelin – Hacker

    21. September 2009 um 8:54 Uhr

  • Warum ist es eine schlechte Idee, ein Array innerhalb einer Struktur zurückzugeben? Das Packen kleiner Arrays in Strukturen ist nicht ungewöhnlich. Es ist wahrscheinlich schneller und sicherlich einfacher, als Speicher zu mallocieren und einen Zeiger darauf zurückzugeben.

    – Johannes Schaub – litb

    23. September 2009 um 17:38 Uhr

Benutzeravatar von unwind
entspannen

Sie können niemals einen Stapel zurückgeben (“auto“) Variable von etwas anderem als einem primitiven (Wert-)Typ, und structs von solchen. Bei anderen Typen müssen Sie den Speicher mithilfe von aus dem Heap zuweisen malloc()oder packen Sie das Array (mit fester Größe) in a struct.

Wenn Sie ein Array mit fester Größe verwenden, können Sie es als modellieren struct und verwenden Sie struct-return:

#define WIDTH  11
#define HEIGHT 11

typedef struct {
  unsigned char cell[WIDTH * HEIGHT];
} Board;

Board board_new(void)
{
  Board b;
  size_t i;

  for(i = 0; i < sizeof b.cell / sizeof *b.cell; i++)
    b.cell[i] = rand() & 255;
  return b;
}

Dies ist in Ordnung und sollte nicht teurer sein als die Alternative, einen expliziten Zeiger zu verwenden:

void board_init(Board *b);

Da der erstere Fall von struct-return (vom Compiler) in den letzteren umgeschrieben werden kann. Das nennt man Renditeoptimierung.

  • Das ist einfach nicht wahr – Sie können zurückkehren struct Typen, die entschieden nicht-primitiv sind. Es ist jedoch keine großartige Idee.

    – Café

    21. September 2009 um 8:38 Uhr

  • Aber Strukturtypen werden vom Stack in eine (versteckte) lokale Variable in der Nähe des Aufrufers kopiert. Es könnte funktionieren, wenn das Array mit fester Größe definiert wurde, damit der Compiler die Größe der Rückgabestruktur/des Arrays kennt. Aber das tut es nicht. Es ist ein dynamisches Array …

    – Wim ten Brink

    21. September 2009 um 8:53 Uhr

  • malloc() ist nicht die einzige Möglichkeit, Speicher zuzuweisen. Sie können eine statische Variable innerhalb einer Funktion haben

    – qrdl

    21. September 2009 um 8:56 Uhr

  • Alex, das Array muss keine dynamische Größe haben.

    – Michael Krelin – Hacker

    21. September 2009 um 9:00 Uhr

  • Entspannen Sie sich, ich lehne Sie für die Verwendung des Wortes “am besten” ab, wenn es mehr als unangemessen ist 😉 “Das Beste wäre wahrscheinlich, es als Struktur zu modellieren”. Ich werde meine Ablehnung rückgängig machen, wenn Sie Ihren Wortlaut ändern;-)

    – Michael Krelin – Hacker

    21. September 2009 um 9:12 Uhr

Wenn Sie das wirklich wollen, können Sie versuchen, das Array A statisch zu machen, auf diese Weise wird der Speicher für A nicht durch den Funktionsumfang bestimmt und Sie können das Array tatsächlich zurückgeben (in Form eines Zeigers natürlich).

Dies ist jedoch kein guter Weg, um das zu erreichen, was Sie erreichen möchten. Stattdessen übergeben Sie das Array an die Funktion rand_grid . Dafür ist die Pass-by-Adresse gedacht.

Alle Methoden, die mir bekannt sind, um ein Array von einer Funktion zurückzugeben, haben Schwächen und Stärken.

Das Einschließen in eine Struktur vermeidet den Aufwand für das Zuweisen und Freigeben von Speicher und vermeidet das Erinnern an die Freigabe. Sie haben diese Probleme bei jeder Lösung, die malloc, calloc und realloc verwendet. Andererseits erfordert das Umschließen einer Struktur die Kenntnis der maximal möglichen Größe des Arrays und verschwendet bei großen Arrays deutlich Speicher und Ausführungszeit (z. B. das Laden einer Datei in den Speicher und das Weitergeben des Dateiinhalts von Funktion zu Funktion). durch Kopieren).

1408070cookie-checkDeklarieren einer C-Funktion, um ein Array zurückzugeben

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy