Wie gebe ich mehrere Werte von einer Funktion in C zurück?

86

Wenn ich eine Funktion habe, die ein Ergebnis intund ein Ergebnis erzeugt string, wie kann ich beide von einer Funktion zurückgeben?

Soweit ich das beurteilen kann, kann ich nur eines zurückgeben, wie durch den Typ vor dem Funktionsnamen bestimmt.

Tony Stark
quelle
6
Mit stringmeinen Sie "Ich verwende C ++ und dies ist die std::stringKlasse" oder "Ich verwende C und dies ist ein char *Zeiger oder char[]Array".
Chris Lutz
Nun, in meinem speziellen Fall waren es zwei Ints: eine für die 'Punktzahl' dessen, was ich verglichen habe, und eine für den 'Index', wo diese maximale Punktzahl gefunden wurde. Ich wollte hier ein String-Beispiel nur für den allgemeineren Fall verwenden
Tony Stark
Übergeben Sie den String als Referenz und geben Sie den int zurück. Schnellste Weg. Keine Strukturen erforderlich.
Stefan Steiger
1
Ist eine Funktion, die 2 Ergebnisse zurückgibt, nicht mehr als eine Sache? Was würde Onkel Bob sagen?
Duncan

Antworten:

122

Ich weiß nicht, was du stringbist, aber ich gehe davon aus, dass es sein eigenes Gedächtnis verwaltet.

Sie haben zwei Lösungen:

1: Geben Sie a zurück, structdas alle benötigten Typen enthält.

struct Tuple {
    int a;
    string b;
};

struct Tuple getPair() {
    Tuple r = { 1, getString() };
    return r;
}

void foo() {
    struct Tuple t = getPair();
}

2: Verwenden Sie Zeiger, um Werte auszugeben.

void getPair(int* a, string* b) {
    // Check that these are not pointing to NULL
    assert(a);
    assert(b);
    *a = 1;
    *b = getString();
}

void foo() {
    int a, b;
    getPair(&a, &b);
}

Welche Sie wählen, hängt weitgehend von Ihren persönlichen Vorlieben ab, welche Semantik Ihnen besser gefällt.

Travis Gockel
quelle
7
Ich denke, es hängt mehr davon ab, wie verwandt die Rückgabewerte sind. Wenn das int ein Fehlercode ist und die Zeichenfolge ein Ergebnis ist, sollten diese nicht in einer Struktur zusammengefasst werden. Das ist einfach albern. In diesem Fall würde ich das int zurückgeben und die Zeichenfolge als a char *und a size_tfür die Länge übergeben, es sei denn, es ist absolut wichtig, dass die Funktion ihre eigene Zeichenfolge zuweist und / oder zurückgibt NULL.
Chris Lutz
@ Chris Ich stimme Ihnen voll und ganz zu, aber ich habe keine Ahnung von der Verwendungssemantik der Variablen, die er benötigt.
Travis Gockel
Guter Punkt Chris. Eine andere Sache, auf die ich hinweisen sollte, ist Wert gegen Referenz. Wenn ich mich nicht irre, die Struktur zurückzugeben, wie im Beispiel gezeigt, bedeutet dies, dass bei der Rückgabe eine Kopie erstellt wird. Ist das richtig? (Ich bin ein bisschen wackelig bei C) Während die andere Methode Pass-by-Reference verwendet und daher keine Zuweisung von mehr Speicher erfordert. Natürlich könnte die Struktur über einen Zeiger zurückgegeben werden und denselben Vorteil haben, oder? (
Stellen Sie
@ BobVicktor: C hat überhaupt keine Referenzsemantik (das ist exklusiv für C ++), daher ist alles ein Wert. Die Zwei-Zeiger-Lösung (Nr. 2) übergibt Kopien der Zeiger an eine Funktion, die getPairdann dereferenziert . Abhängig davon, was Sie tun (OP hat nie geklärt, ob dies tatsächlich eine C-Frage ist), kann die Zuordnung ein Problem sein, ist jedoch normalerweise nicht in C ++ - Land (Rückgabewertoptimierung speichert all dies) und in C-Land Daten Kopien erfolgen normalerweise explizit (über strncpyoder was auch immer).
Travis Gockel
@TravisGockel Danke für die Korrektur. Ich bezog mich auf die Tatsache, dass Zeiger verwendet werden, so dass die Werte nicht kopiert werden, sondern nur geteilt wird, was bereits zugewiesen wurde. Aber Sie haben Recht, wenn Sie sagen, dass dies in C nicht als Pass-by-Reference bezeichnet wird. Und danke auch für die anderen großartigen Wissensnuggets. Ich liebe es, diese kleinen Dinge über Sprachen zu lernen. :)
RTHarston
10

Option 1: Deklarieren Sie eine Struktur mit einem int und einem String und geben Sie eine Strukturvariable zurück.

struct foo {    
 int bar1;
 char bar2[MAX];
};

struct foo fun() {
 struct foo fooObj;
 ...
 return fooObj;
}

Option 2: Sie können einen der beiden per Zeiger übergeben und über den Zeiger Änderungen am Aktualparameter vornehmen und den anderen wie gewohnt zurückgeben:

int fun(char **param) {
 int bar;
 ...
 strcpy(*param,"....");
 return bar;
}

oder

 char* fun(int *param) {
 char *str = /* malloc suitably.*/
 ...
 strcpy(str,"....");
 *param = /* some value */
 return str;
}

Option 3: Ähnlich wie bei Option 2. Sie können beide per Zeiger übergeben und nichts von der Funktion zurückgeben:

void fun(char **param1,int *param2) {
 strcpy(*param1,"....");
 *param2 = /* some calculated value */
}
Codaddict
quelle
In Bezug auf Option 2 sollten Sie auch die Länge der Zeichenfolge übergeben. int fun(char *param, size_t len)
Chris Lutz
Das hängt davon ab, was die Funktion tut. Wenn wir wissen, welche Art von Ergebnis die Funktion in das char-Array einfügt, können wir einfach genug Speicherplatz dafür zuweisen und an die Funktion übergeben. Keine Notwendigkeit, die Länge zu übergeben. Ähnliches wie strcpyzum Beispiel.
Codaddict
7

Da einer Ihrer Ergebnistypen eine Zeichenfolge ist (und Sie C und nicht C ++ verwenden), empfehle ich, Zeiger als Ausgabeparameter zu übergeben. Verwenden:

void foo(int *a, char *s, int size);

und nenne es so:

int a;
char *s = (char *)malloc(100); /* I never know how much to allocate :) */
foo(&a, s, 100);

Ziehen Sie die Zuweisung im Allgemeinen lieber in der aufrufenden Funktion vor, nicht innerhalb der Funktion selbst, damit Sie für verschiedene Zuordnungsstrategien so offen wie möglich sind.

Jesse Beder
quelle
6

Zwei verschiedene Ansätze:

  1. Übergeben Sie Ihre Rückgabewerte per Zeiger und ändern Sie sie innerhalb der Funktion. Sie deklarieren Ihre Funktion als ungültig, sie wird jedoch über die als Zeiger übergebenen Werte zurückgegeben.
  2. Definieren Sie eine Struktur, die Ihre Rückgabewerte aggregiert.

Ich denke, dass # 1 etwas offensichtlicher ist, was los ist, obwohl es langweilig werden kann, wenn Sie zu viele Rückgabewerte haben. In diesem Fall funktioniert Option 2 ziemlich gut, obwohl die Erstellung spezieller Strukturen für diesen Zweck einen gewissen mentalen Aufwand mit sich bringt.

James Thompson
quelle
1
C hat keine Referenzen ;-), obwohl es seit dem verwendeten Poster stringsicher ist, C ++ anzunehmen ...
Travis Gockel
Völlig vergessen! Ich habe meine Antwort geändert, um Zeiger zu verwenden, aber ich war eindeutig zu lange im C ++ - Land. :)
James Thompson
6

Erstellen Sie eine Struktur, legen Sie zwei Werte fest und geben Sie die Strukturvariable zurück.

struct result {
    int a;
    char *string;
}

Sie müssen Platz für die char *in Ihrem Programm zuweisen .

zs2020
quelle
3

Verwenden Sie Zeiger als Funktionsparameter. Verwenden Sie sie dann, um mehrere Werte zurückzugeben.

Bloodlee
quelle
2

Durch Übergabe von Parametern unter Bezugnahme auf die Funktion.

Beispiele:

 void incInt(int *y)
 {
     (*y)++;  // Increase the value of 'x', in main, by one.
 }

Auch durch die Verwendung globaler Variablen wird dies jedoch nicht empfohlen.

Beispiel:

int a=0;

void main(void)
{
    //Anything you want to code.
}
Badr
quelle
4
void main(void)OH WIE ES BRENNT!
Chris Lutz
Was meinst du? @ Chris Lutz
Badr
6
mainsoll einen Statuscode zurückgeben. Unter * nix ist es üblicher, dies als zu deklarieren int main(int argc, char *argv[]). Ich glaube, Windows hat ähnliche Konventionen.
Duncan
2

Ein Ansatz ist die Verwendung von Makros. Platzieren Sie dies in einer Header-Dateimultitype.h

#include <stdlib.h>

/* ============================= HELPER MACROS ============================= */

/* __typeof__(V) abbreviation */

#define TOF(V) __typeof__(V)

/* Expand variables list to list of typeof and variable names */

#define TO3(_0,_1,_2,_3) TOF(_0) v0; TOF(_1) v1; TOF(_2) v2; TOF(_3) v3;
#define TO2(_0,_1,_2)    TOF(_0) v0; TOF(_1) v1; TOF(_2) v2;
#define TO1(_0,_1)       TOF(_0) v0; TOF(_1) v1;
#define TO0(_0)          TOF(_0) v0;

#define TO_(_0,_1,_2,_3,TO_MACRO,...) TO_MACRO

#define TO(...) TO_(__VA_ARGS__,TO3,TO2,TO1,TO0)(__VA_ARGS__)

/* Assign to multitype */

#define MTA3(_0,_1,_2,_3) _0 = mtr.v0; _1 = mtr.v1; _2 = mtr.v2; _3 = mtr.v3;
#define MTA2(_0,_1,_2)    _0 = mtr.v0; _1 = mtr.v1; _2 = mtr.v2;
#define MTA1(_0,_1)       _0 = mtr.v0; _1 = mtr.v1;
#define MTA0(_0)          _0 = mtr.v0;

#define MTA_(_0,_1,_2,_3,MTA_MACRO,...) MTA_MACRO

#define MTA(...) MTA_(__VA_ARGS__,MTA3,MTA2,MTA1,MTA0)(__VA_ARGS__)

/* Return multitype if multiple arguments, return normally if only one */

#define MTR1(...) {                                                           \
    typedef struct mtr_s {                                                    \
      TO(__VA_ARGS__)                                                         \
    } mtr_t;                                                                  \
    mtr_t *mtr = malloc(sizeof(mtr_t));                                       \
    *mtr = (mtr_t){__VA_ARGS__};                                              \
    return mtr;                                                               \
  }

#define MTR0(_0) return(_0)

#define MTR_(_0,_1,_2,_3,MTR_MACRO,...) MTR_MACRO

/* ============================== API MACROS =============================== */

/* Declare return type before function */

typedef void* multitype;

#define multitype(...) multitype

/* Assign return values to variables */

#define let(...)                                                              \
  for(int mti = 0; !mti;)                                                     \
    for(multitype mt; mti < 2; mti++)                                         \
      if(mti) {                                                               \
        typedef struct mtr_s {                                                \
          TO(__VA_ARGS__)                                                     \
        } mtr_t;                                                              \
        mtr_t mtr = *(mtr_t*)mt;                                              \
        MTA(__VA_ARGS__)                                                      \
        free(mt);                                                             \
      } else                                                                  \
        mt

/* Return */

#define RETURN(...) MTR_(__VA_ARGS__,MTR1,MTR1,MTR1,MTR0)(__VA_ARGS__)

Auf diese Weise können bis zu vier Variablen von einer Funktion zurückgegeben und bis zu vier Variablen zugewiesen werden. Als Beispiel können Sie sie folgendermaßen verwenden:

multitype (int,float,double) fun() {
    int a = 55;
    float b = 3.9;
    double c = 24.15;

    RETURN (a,b,c);
}

int main(int argc, char *argv[]) {
    int x;
    float y;
    double z;

    let (x,y,z) = fun();

    printf("(%d, %f, %g\n)", x, y, z);

    return 0;
}

Folgendes wird gedruckt:

(55, 3.9, 24.15)

Die Lösung ist möglicherweise nicht so portabel, da für variadische Makros und for-Anweisungsvariablendeklarationen C99 oder höher erforderlich ist. Aber ich denke, es war interessant genug, hier zu posten. Ein weiteres Problem ist, dass der Compiler Sie nicht warnt, wenn Sie ihnen die falschen Werte zuweisen. Sie müssen also vorsichtig sein.

Weitere Beispiele und eine stapelbasierte Version des Codes mit Gewerkschaften finden Sie in meinem Github-Repository .

Klas. S.
quelle
Ein größeres Portabilitätsproblem ist die nicht standardmäßige Funktion__typeof__
MM
Anstelle von typeof könnten Sie wahrscheinlich sizeof verwenden. Ermitteln Sie die Größe der Rückgabewerte und weisen Sie ein Array zu, das groß genug ist, um alle zu speichern. Dann können Sie es zurückgeben.
Klas. S