如何在C语言中从函数中返回多个值?

108

如果我有一个函数可以返回int类型的结果和string类型的结果,我该如何从函数中同时返回它们?

据我所知,一个函数只能返回一种类型的值,这由函数名前面的类型决定。


7
“string”一词是否表示“我正在使用C++,这是std::string类”,还是表示“我正在使用C语言,这是一个char *指针或char[]数组”? - Chris Lutz
嗯,在我的特定情况下,它们是两个整数:一个用于比较的“分数”,另一个用于找到最大分数的“索引”。我想在这里使用一个字符串示例,以便更一般化。 - Tony Stark
将字符串按引用传递并返回整数。最快的方法。不需要结构体。 - Stefan Steiger
1
一个返回两个结果的函数难道不是在做超过一件事吗?Uncle Bob会怎么说呢? - Duncan
8个回答

141
我不知道你的字符串是什么,但我假设它管理自己的内存。
你有两个解决方案:
1:返回一个包含所需类型的结构体。
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是错误代码,而string是结果,那么它们不应该放在一个结构体中,这很荒谬。在这种情况下,我会返回int,并将字符串作为'char *'和'size_t'传递,除非对函数分配自己的字符串和/或返回'NULL'绝对至关重要。 - Chris Lutz
@Chris 我完全同意你的观点,但是我不知道他需要的变量使用语义。 - Travis Gockel
Chris说得很好。我认为值与引用也值得一提。如果我没记错的话,按照示例返回结构体意味着在返回时会进行复制,这是正确的吗?(我对C语言有点不太确定)而另一种方法使用传递引用,因此不需要分配更多的内存。当然,结构体也可以通过指针返回,并分享同样的好处,对吧?(当然要确保适当地分配内存等等) - RTHarston
@BobVicktor:C 语言没有引用的语义 (这种语义是 C++ 特有的),所以一切都是值。解决方案(#2)中,通过将指针的副本传递到函数中,getPair 再次进行了_取消引用_操作。根据您要做什么(OP 从未明确说明这是否真的是一个 C 问题),分配可能是一个问题,但在 C++ 领域通常不是问题(返回值优化可以节省所有这些),而在 C 领域中,数据复制通常会显式地进行(通过 strncpy 或其他方式)。 - Travis Gockel
@TravisGockel 感谢您的纠正。我指的是使用指针,因此它不会复制值,只是共享已经分配的内容。但是您说得对,在C语言中这并不能被称为按引用传递。还有感谢您分享其他优秀的知识点。我喜欢学习关于编程语言的这些小细节。 :) - RTHarston
显示剩余2条评论

11

选项1:声明一个包含整数和字符串的结构体并返回一个结构体变量。

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

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

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

选项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

8
创建一个结构体,并设置其中两个值,然后返回该结构体变量。
struct result {
    int a;
    char *string;
}

在你的程序中,你必须为char *分配空间。


8

由于你的结果类型之一是字符串(并且你使用的是 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. 通过指针传递返回值,并在函数内部进行修改。你将函数声明为void,但是它通过作为指针传入的值进行返回。
  2. 定义一个结构体来聚合你的返回值。
我认为#1更明显一些,尽管如果你有太多的返回值,这可能变得乏味。在这种情况下,选项#2相当不错,尽管在为此目的制作专门的结构时需要一些额外的思考。

1
C语言没有引用 ;-), 不过因为发帖者使用了 string ,所以可以安全地假设是C++... - Travis Gockel
完全忘记了!我修改了我的答案以使用指针,但显然我已经在C++领域呆得太久了。 :) - James Thompson

4

一种方法是使用宏。将以下内容放入头文件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 语句变量声明。但我认为它足够有趣了,可以在这里发布。另一个问题是,如果你给它们错误的值,编译器不会发出警告,所以你必须小心。

我的 github 代码库 中提供了其他示例和基于堆栈的版本。


2
一个更大的可移植性问题是非标准特性__typeof__ - M.M
你可以考虑使用sizeof代替typeof。获取返回值的大小并分配一个足够大的数组来存储它们。然后,你就可以将它返回。 - Klas. S

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

5
void main(void) 真让人抓狂! - Chris Lutz
你是什么意思?@ Chris Lutz - Badr
6
main 应该返回一个状态码。在 *nix 中,通常将其声明为 int main(int argc, char *argv[]),我相信 Windows 也有类似的约定。 - Duncan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接