Jak zwrócić wiele wartości z funkcji w języku C?

87

Jeśli mam funkcję, która generuje wynik inti wynik string, w jaki sposób mogę zwrócić je obie z funkcji?

O ile wiem, mogę zwrócić tylko jedną rzecz, określoną przez typ poprzedzający nazwę funkcji.

Tony Stark
źródło
6
By stringzrobić to znaczy „Używam C ++ i jest to std::stringklasa” lub „Używam C i jest to char *wskaźnik lub char[]tablicą.”
Chris Lutz,
cóż, w moim konkretnym przypadku były to dwie liczby int: jedna dla „wyniku” porównania, a druga dla „indeksu” miejsca, w którym znaleziono ten maksymalny wynik. Chciałem użyć tutaj przykładu ciągu tylko dla bardziej ogólnego przypadku
Tony Stark
Przekaż ciąg przez odniesienie i zwróć int. Najszybsza droga. Żadne struktury nie są wymagane.
Stefan Steiger
1
Czy funkcja, która zwraca 2 wyniki, nie robi więcej niż jednej rzeczy? Co by powiedział wujek Bob?
Duncan

Odpowiedzi:

123

Nie wiem, jakie stringjest twoje , ale założę, że zarządza własną pamięcią.

Masz dwa rozwiązania:

1: Zwróć, structktóry zawiera wszystkie potrzebne typy.

struct Tuple {
    int a;
    string b;
};

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

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

2: Użyj wskaźników, aby przekazać wartości.

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);
}

Który z nich wybierzesz, zależy w dużej mierze od osobistych preferencji co do tego, jaką semantykę lubisz bardziej.

Travis Gockel
źródło
7
Myślę, że zależy to bardziej od tego, jak powiązane są wartości zwracane. Jeśli int jest kodem błędu, a ciąg jest wynikiem, nie należy ich łączyć w strukturę. To po prostu głupie. W takim przypadku zwróciłbym int i przekazał ciąg jako a char *i a size_tdla długości, chyba że jest to absolutnie niezbędne, aby funkcja przydzieliła własny ciąg i / lub zwróciła NULL.
Chris Lutz,
@Chris Całkowicie się z tobą zgadzam, ale nie mam pojęcia o semantyce użycia zmiennych, których potrzebuje.
Travis Gockel
Słuszna uwaga, Chris. Kolejną rzeczą, na którą warto zwrócić uwagę, jest wartość a odniesienie. Jeśli się nie mylę, zwrócenie struktury, jak pokazano w przykładzie, oznacza, że ​​po zwrocie zostanie wykonana kopia, czy to prawda? (Jestem trochę chwiejny na C) Podczas gdy druga metoda wykorzystuje przekazywanie przez odwołanie, a zatem nie wymaga przydzielania większej ilości pamięci. Oczywiście struktura może zostać zwrócona przez wskaźnik i mieć tę samą korzyść, prawda? (upewniając się, że przydzielono pamięć właściwie i to wszystko oczywiście)
RTHarston,
@BobVicktor: C w ogóle nie ma semantyki referencji (to jest wyłączne dla C ++), więc wszystko jest wartością. Rozwiązanie z dwoma wskaźnikami (# 2) polega na przekazaniu kopii wskaźników do funkcji, która getPairnastępnie wyłuskuje . W zależności od tego, co robisz (OP nigdy nie wyjaśnił, czy jest to faktycznie pytanie w C), alokacja może być problemem, ale zwykle nie jest w C ++ land (optymalizacja wartości zwracanej zapisuje to wszystko), aw C land, dane kopie zwykle są jawne (przez strncpylub cokolwiek).
Travis Gockel,
@TravisGockel Dzięki za korektę. Miałem na myśli fakt, że używane są wskaźniki, więc nie jest to kopiowanie wartości, tylko dzielenie się tym, co zostało już przydzielone. Ale masz rację, mówiąc, że nie jest to właściwie nazywane przekazywaniem odniesienia w C. I dziękuję również za inne wspaniałe samorodki wiedzy. Uwielbiam uczyć się tych małych rzeczy o językach. :)
RTHarston
10

Option 1: Zadeklaruj strukturę z int i stringiem i zwróć zmienną strukturalną.

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

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

Option 2: Możesz przekazać jeden z dwóch za pomocą wskaźnika i wprowadzić zmiany do rzeczywistego parametru przez wskaźnik, a drugi jak zwykle zwrócić:

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

lub

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

Option 3: Podobnie jak w przypadku opcji 2. Możesz przekazać zarówno wskaźnik, jak i nic nie zwracać z funkcji:

void fun(char **param1,int *param2) {
 strcpy(*param1,"....");
 *param2 = /* some calculated value */
}
codaddict
źródło
Odnośnie opcji 2, należy również podać długość łańcucha. int fun(char *param, size_t len)
Chris Lutz,
To zależy od tego, co robi funkcja. Jeśli wiemy, jakiego rodzaju wynik funkcja umieszcza w tablicy znaków, możemy po prostu przydzielić na nią wystarczającą ilość miejsca i przekazać ją do funkcji. Nie ma potrzeby przekraczania długości. Coś podobnego do tego, którego używamy strcpyna przykład.
codaddict
7

Ponieważ jednym z typów wyników jest łańcuch (i używasz C, a nie C ++), zalecam przekazywanie wskaźników jako parametrów wyjściowych. Posługiwać się:

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

i nazwij to tak:

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

Ogólnie rzecz biorąc, preferuj alokację w funkcji wywołującej , a nie w samej funkcji, abyś mógł być jak najbardziej otwarty na różne strategie alokacji.

Jesse Beder
źródło
6

Dwa różne podejścia:

  1. Przekaż zwracane wartości przez wskaźnik i modyfikuj je wewnątrz funkcji. Deklarujesz swoją funkcję jako void, ale zwraca ona wartości przekazane jako wskaźniki.
  2. Zdefiniuj strukturę, która agreguje wartości zwracane.

Myślę, że # 1 jest trochę bardziej oczywiste, jeśli chodzi o to, co się dzieje, chociaż może to być nudne, jeśli masz zbyt wiele wartości zwracanych. W takim przypadku opcja nr 2 działa całkiem dobrze, chociaż tworzenie wyspecjalizowanych struktur do tego celu wiąże się z pewnym obciążeniem umysłowym.

James Thompson
źródło
1
C nie ma odniesień ;-), chociaż skoro plakat używany string, może być bezpiecznie założyć, C ++ ...
Travis Gockel
Całkowicie o tym zapomniałem! Zmodyfikowałem odpowiedź, aby użyć wskaźników, ale najwyraźniej zbyt długo byłem w krainie C ++. :)
James Thompson
6

Utwórz strukturę i ustaw dwie wartości wewnątrz i zwróć zmienną strukturalną.

struct result {
    int a;
    char *string;
}

Musisz przeznaczyć miejsce na program char *w swoim programie.

zs2020
źródło
3

Użyj wskaźników jako parametrów funkcji. Następnie użyj ich do zwrócenia wielu wartości.

Bloodlee
źródło
2

Przekazując parametry przez odniesienie do funkcji.

Przykłady:

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

Również przy użyciu zmiennych globalnych, ale nie jest to zalecane.

Przykład:

int a=0;

void main(void)
{
    //Anything you want to code.
}
Badr
źródło
4
void main(void)OH JAK SIĘ SPALA!
Chris Lutz,
co masz na myśli? @ Chris Lutz
Badr,
6
mainma zwrócić kod statusu. Pod * nix częściej deklaruje się to jako int main(int argc, char *argv[]), uważam, że Windows ma podobne konwencje.
Duncan
2

Jedną z metod jest użycie makr. Umieść to w pliku nagłówkowymmultitype.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__)

Umożliwia to zwrócenie do czterech zmiennych z funkcji i przypisanie ich do maksymalnie czterech zmiennych. Jako przykład możesz ich użyć w następujący sposób:

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;
}

Oto, co drukuje:

(55, 3.9, 24.15)

Rozwiązanie może nie być tak przenośne, ponieważ wymaga C99 lub nowszej wersji dla makr wariadycznych i deklaracji zmiennych dla instrukcji. Ale myślę, że opublikowanie tutaj było wystarczająco interesujące. Inną kwestią jest to, że kompilator nie ostrzeże Cię, jeśli przypiszesz im niewłaściwe wartości, więc musisz być ostrożny.

Dodatkowe przykłady i wersja kodu oparta na stosie przy użyciu unii są dostępne w moim repozytorium github .

Klas. S
źródło
1
Większym problemem z przenośnością jest niestandardowa funkcja__typeof__
MM
Zamiast typeof prawdopodobnie możesz użyć sizeof. Uzyskaj rozmiar zwracanych wartości i przydziel odpowiednio dużą tablicę, aby przechowywać je wszystkie. Następnie możesz go zwrócić.
Klas. S