Як повернути декілька значень із функції в C?


87

Якщо у мене є функція, яка видає результат intі результат string, як повернути їх обох із функції?

Наскільки я можу сказати, я можу повернути лише одне, що визначається типом, що передує імені функції.


6
Під stringцим ви маєте на увазі "я використовую С ++ і це std::stringклас" або "я використовую С, а це char *вказівник або char[]масив".
Кріс Лутц,

ну, у моєму конкретному випадку це були два інти: один для «балу» того, що я порівнював, та інший для «індексу», де був знайдений цей максимальний бал. я хотів використати приклад рядка тут лише для більш загального випадку
Тоні Старк

Передайте рядок за посиланням і поверніть int. Найшвидший спосіб. Ніяких конструкцій не потрібно.
Стефан Штайгер

1
Хіба функція, яка повертає 2 результати, не робить більше, ніж одне? Що б сказав дядько Боб?
Дункан

Відповіді:


123

Я не знаю, що ти stringтакий, але я збираюся припустити, що він керує власною пам'яттю.

У вас є два рішення:

1: Поверніть a, structякий містить усі потрібні типи.

struct Tuple {
    int a;
    string b;
};

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

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

2: Використовуйте покажчики для передачі значень.

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

Який із них ви вирішите використовувати, багато в чому залежить від особистих уподобань щодо будь-якої семантики, яка вам подобається більше.


7
Я думаю, це більше залежить від того, наскільки пов'язані значення повернення. Якщо int - це код помилки, а рядок - результат, тоді їх не слід складати у структуру. Це просто безглуздо. У такому випадку я повернув би int і передав рядок як a char *та a size_tдля довжини, якщо функція не має абсолютно важливого значення для виділення власного рядка та / або повернення NULL.
Chris Lutz,

@Chris Я повністю з вами згідний, але я не маю уявлення про семантику використання потрібних йому змінних.
Тревіс Гоккель,

Гарний момент Кріс. Ще одне, на що, на мою думку, варто звернути увагу - це значення проти посилання. Якщо я не помиляюся, повертаючи структуру, як показано в прикладі, означає, що копія буде зроблена після повернення, це правильно? (Я трохи хиткий на C) Хоча інший метод використовує передачу-посилання і, отже, не вимагає виділення більше пам'яті. Звичайно, структуру можна повернути через покажчик і поділитися тією ж перевагою, чи не так? (переконавшись, що пам’яті розподілено належним чином, і все це, звичайно)
RTHarston,

@BobVicktor: C взагалі не має посилальної семантики (тобто ексклюзивно для C ++), тому все є цінністю. Рішення двох покажчиків (№2) - це передача копій покажчиків у функцію, яка getPairпотім здійснює розмежування . Залежно від того, що ви робите (OP ніколи не уточнював, чи це насправді питання C), розподіл може викликати занепокоєння, але, як правило, це не в C ++ land (оптимізація поверненого значення все це економить) і в C land, дані копії, як правило, трапляються явно (через strncpyчи інше).
Тревіс

@TravisGockel Дякую за виправлення. Я мав на увазі той факт, що використовуються вказівники, тож це не копіювання значень, а лише обмін тим, що вже було виділено. Але ви маєте рацію, стверджуючи, що це неправильно називається прохідним посиланням на C. І також дякую за інші великі самородки знань. Я люблю вивчати ці дрібниці про мови. :)
RTHarston

10

Option 1: Оголосіть структуру за допомогою int та string та поверніть змінну struct.

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

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

Option 2: Ви можете передати один із двох через покажчик і внести зміни до фактичного параметра через покажчик, а другий повернути як зазвичай:

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

або

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

Option 3: Подібно до варіанту 2. Ви можете передавати як через покажчик, так і нічого не повертати з функції:

void fun(char **param1,int *param2) {
 strcpy(*param1,"....");
 *param2 = /* some calculated value */
}

Щодо варіанту 2, ви також повинні передати довжину рядка. int fun(char *param, size_t len)
Chris Lutz,

Тепер це залежить від того, що робить функція. Якщо ми знаємо, який результат функція вбиває в масив char, ми могли б просто виділити для нього достатньо місця і передати його функції. Не потрібно передавати довжину. Щось схоже на те, як ми використовуємо, strcpyнаприклад.
codaddict

7

Оскільки одним із типів результатів є рядок (і ви використовуєте C, а не C ++), я рекомендую передавати покажчики як вихідні параметри. Використання:

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

і назвіть це так:

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

Загалом, віддайте перевагу розподілу у функції, що викликає , а не всередині самої функції, щоб ви могли бути максимально відкритими для різних стратегій розподілу.


6

Два різних підходи:

  1. Передайте свої повернені значення за покажчиком і змініть їх усередині функції. Ви оголошуєте свою функцію недійсною, але вона повертається через значення, передані як покажчики.
  2. Визначте структуру, яка агрегує ваші повернені значення.

Я думаю, що №1 трохи очевидніше про те, що відбувається, хоча це може стати нудним, якщо у вас занадто багато повернутих значень. У цьому випадку варіант №2 працює досить добре, хоча для створення спеціалізованих конструкцій для цієї мети задіяні певні розумові витрати.


1
C не має посилань ;-), хоча з моменту використання плаката string, можливо, можна вважати C ++ ...
Тревіс Гоккель,

Зовсім забув про це! Я змінив свою відповідь на використання покажчиків, але я, очевидно, перебуваю у C ++ занадто довго. :)
Джеймс Томпсон,

6

Створіть структуру та встановіть два значення всередині та поверніть змінну структури.

struct result {
    int a;
    char *string;
}

Ви повинні виділити місце для програми char *у вашій програмі.


3

Використовуйте покажчики як параметри функції. Потім використовуйте їх для повернення кратного значення.


2

Передаючи параметри за посиланням на функцію.

Приклади:

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

Також за допомогою глобальних змінних, але це не рекомендується.

Приклад:

int a=0;

void main(void)
{
    //Anything you want to code.
}

4
void main(void)О, ЯК ЦЕ ЗГАРЕ!
Chris Lutz,

Що ви маєте на увазі? @ Chris Lutz
Badr

6
mainповинен повертати код стану. Під * nix звичайніше оголошувати це як int main(int argc, char *argv[]), я вважаю, Windows має подібні домовленості.
Дункан

2

Один із підходів - використання макросів. Помістіть це у файл заголовкаmultitype.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__)

Це дає можливість повернути до чотирьох змінних із функції та призначити їх до чотирьох змінних. Як приклад, ви можете використовувати їх так:

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

Ось що друкується:

(55, 3.9, 24.15)

Рішення може бути не таким портативним, оскільки для варіативних макросів та оголошень змінних for-statement потрібен C99 або новішої версії. Але я думаю, що це було досить цікаво розміщувати тут. Інша проблема полягає в тому, що компілятор не попередить вас, якщо ви призначите їм неправильні значення, тому вам слід бути обережними.

Додаткові приклади та версія коду на основі стеку за допомогою профспілок доступні в моєму сховищі github .


1
Більшою проблемою переносимості є нестандартна функція__typeof__
MM

Замість typeof ви, мабуть, можете використовувати sizeof. Отримайте розмір повернутих значень і виділіть масив, достатньо великий для їх зберігання. Тоді ви можете його повернути.
Клас. S
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.