Как мне вернуть несколько значений из функции в C?

87

Если у меня есть функция, которая производит результат intи результат string, как мне вернуть их оба из функции?

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

Тони Старк
источник
6
Под stringвы имеете в виду «Я использую C ++ и это std::stringкласс» или «Я использую C , и это char *указатель или char[]массив.»
Крис Лутц,
ну, в моем конкретном случае это было два целых числа: одно для «балла» того, что я сравнивал, и одно для «индекса» того, где был найден этот максимальный балл. Я хотел использовать здесь пример строки только для более общего случая
Тони Старк
Передайте строку по ссылке и верните int. Самый быстрый способ. Никаких структур не требуется.
Стефан Штайгер,
1
Разве функция, возвращающая два результата, не выполняет больше одной задачи? Что бы сказал дядя Боб?
Дункан

Ответы:

123

Я не знаю, что у вас stringтакое, но предполагаю, что он управляет собственной памятью.

У вас есть два решения:

1: вернуть, 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 Я полностью согласен с вами, но я понятия не имею о семантике использования переменных, которые ему нужны.
Трэвис Гокель,
Хороший момент, Крис. Еще одна вещь, на которую, я думаю, стоит обратить внимание, - это значение и ссылка. Если я не ошибаюсь, возвращение структуры, как показано в примере, означает, что копия будет сделана после возврата, это правильно? (Я немного не уверен в C) В то время как другой метод использует передачу по ссылке и, следовательно, не требует выделения дополнительной памяти. Конечно, структура может быть возвращена с помощью указателя и имеет те же преимущества, верно? (убедитесь, что память правильно распределяется и все такое, конечно)
RTHarston
@BobVicktor: C вообще не имеет ссылочной семантики (которая является исключительной для C ++), поэтому все является значением. Решение с двумя указателями (№2) передает копии указателей в функцию, которая getPairзатем разыменовывается . В зависимости от того, что вы делаете (OP никогда не уточнял, действительно ли это вопрос C), выделение может быть проблемой, но обычно этого нет в области C ++ (оптимизация возвращаемого значения сохраняет все это), а в области C данные копии обычно происходят явно (через strncpyили что-то еще).
Трэвис
@TravisGockel Спасибо за исправление. Я имел в виду тот факт, что используются указатели, поэтому они не копируют значения, а просто делятся тем, что уже было выделено. Но вы правы, говоря, что это неправильно называть передачей по ссылке в C. И спасибо за другие великие самородки знаний. Я люблю изучать эти мелочи о языках. :)
RTHarston
10

Option 1: Объявить структуру с целым числом и строкой и вернуть структурную переменную.

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 */
}
codaddict
источник
Что касается варианта 2, вы также должны передать длину строки. int fun(char *param, size_t len)
Крис Лутц,
Теперь это зависит от того, что делает функция. Если мы знаем, какой результат функция помещает в массив 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 *в вашей программе.

zs2020
источник
3

Используйте указатели в качестве параметров функции. Затем используйте их для возврата нескольких значений.

Bloodlee
источник
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
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)

Решение может быть не таким переносимым, потому что оно требует C99 или более поздней версии для вариативных макросов и объявлений переменных for-statement. Но я думаю, что было достаточно интересно написать здесь. Другая проблема заключается в том, что компилятор не предупредит вас, если вы присвоите им неправильные значения, поэтому вы должны быть осторожны.

Дополнительные примеры и стековая версия кода с использованием объединений доступны в моем репозитории на github .

Клас. S
источник
1
Более серьезная проблема переносимости - нестандартная функция__typeof__
MM
Вместо typeof вы, вероятно, могли бы использовать sizeof. Получите размер возвращаемых значений и выделите массив, достаточно большой, чтобы хранить их все. Тогда вы можете вернуть его.
Клас. S