【问题标题】:How to return multiple types from a function in C?如何从C中的函数返回多种类型?
【发布时间】:2017-08-03 14:31:06
【问题描述】:

我在 C 语言中有一个计算数组平均值的函数。在同一个循环中,我正在创建一个 t 值数组。我当前的函数返回平均值。如何修改它以返回 t 数组?

/* function returning the mean of an array */
double getMean(int arr[], int size) {
   int i;
   printf("\n");
   float mean;
   double sum = 0;
   float t[size];/* this is static allocation */
    for (i = 0; i < size; ++i) {
        sum += arr[i];
        t[i] = 10.5*(i) / (128.0 - 1.0);
        //printf("%f\n",t[i]);
   }
   mean = sum/size;
   return mean;
}

想法: 我需要在函数中定义一个结构吗?这适用于类型标量和类型数组吗?有没有更清洁的方法?

【问题讨论】:

  • 嗯,我的意思是 C 标准的含义。 自动存储持续时间 意味着变量在执行范围内的确切时间存在。
  • 创建一个包含两个字段的结构,一个数组和一个双精度。了解函数内部声明的任何内容在完成后都不存在,这对您来说也很重要。您需要在函数之前分配它并发送到函数(结构)。
  • 不相关,但不要在没有充分理由的情况下使用float,因为它的精度非常有限。始终使用double
  • @TonyTannous 或返回一个已分配对象,调用者在完成后必须将其发送给free()
  • @Sjoseph 不要让反对票阻止您提出问题 :) 这只是表明问题格式错误的一种方式,请阅读“如何提问部分”以了解未来的问题。下面的答案非常简单而优雅。

标签: c function struct return-value


【解决方案1】:

在 C 函数中只能返回 1 个对象。因此,如果您无法选择,则必须创建一个结构来返回您的 2 个值,例如:

typedef struct X{
     double mean;
     double *newArray;
} X;

但是,在您的情况下,您还需要使用malloc 动态分配t,否则返回的数组将在堆栈中丢失。

另一种方法是让调用者分配新数组,并将其作为指针传递给您,这样您仍将仅返回平均值,并用您的计算值填充给定数组。

【讨论】:

    【解决方案2】:

    这种情况最常见的方法是让调用者为您要返回的值提供存储空间。为此,您可以将 t 设为您的函数的另一个参数:

    double getMean(double *t, const int *arr, size_t size) {
       double sum = 0;
       for (size_t i = 0; i < size; ++i) {
            sum += arr[i];
            t[i] = 10.5*(i) / (128.0 - 1.0);
       }
       return sum/size;
    }
    

    这个 sn-p 在其他一些方面也有所改进:

    • 不要使用float,尤其是当您打算返回double 时。 float 的精度很差
    • 使用size_t 表示对象大小。虽然int 经常有效,但size_t 保证可以容纳任何可能的对象大小,并且是安全的选择
    • 不要在计算某些东西的函数中混合输出(只是一种风格建议)
    • 声明变量接近它们首先使用的位置(另一种风格建议)
    • 这有点固执己见,但我更改了您的签名以明确表明该函数是传递指向数组的指针,而不是数组。在 C 中传递数组是不可能的,因此数组类型的参数无论如何都会自动调整为相应的指针类型。
    • 由于您不打算修改arr 指向的内容,因此请通过添加const 使其明确。例如,如果您不小心尝试修改此数组,这有助于编译器捕获错误。

    您可以调用此代码,例如像这样:

    int numbers[] = {1, 2, 3, 4, 5};
    double foo[5];
    
    double mean = getMean(foo, numbers, 5);
    

    代替幻数 5,你可以写例如sizeof numbers / sizeof *numbers.


    另一种方法是在函数内部使用malloc() 动态分配数组,但这需要调用者稍后再分配free()。哪种方法更合适取决于您的程序的其余部分。

    【讨论】:

    • 只是为了添加它不会混淆 OP,任何通过引用函数发送的内容,并在其中进行修改,然后从外部可见更改。因此,您可能只需将存储发送到函数而不需要结构。不要忘记在函数中分配数组。
    • @TonyTannous 调用者应该提供输出数组。
    • 是的,如果尺寸发生变化,我只是喜欢在里面做......但也是一个简单的解决方案。 +1。
    • @TonyTannous 我想说让调用者提供内存是 惯用的 C 方式。优点是任何对象都可以工作,自动的、静态的、已分配的……但当然有时您更愿意在函数内部分配数组并让调用者 free() 它。
    • @FelixPalmen,当我从主函数调用这个函数时,我应该将什么传递给 double *t 的函数?我可能错过了这一点,但是这个方法会更新主函数内部的 t 数组还是只能在函数 getMean 的范围内访问?
    【解决方案3】:

    遵循@FelixPalmen 的建议可能是最好的选择。但是,如果存在可以预期的最大数组大小,也可以将数组包装在 struct 中,而无需动态分配。这允许代码创建新的structs 而无需释放。

    mean_array 结构可以在get_mean() 函数中创建,分配正确的值,然后返回给调用函数。调用函数只需要提供一个mean_array结构就可以接收返回值。

    #include <stdio.h>
    #include <assert.h>
    
    #define MAX_ARR  100
    
    struct mean_array {
        double mean;
        double array[MAX_ARR];
        size_t num_elems;
    };
    
    struct mean_array get_mean(int arr[], size_t arr_sz);
    
    int main(void)
    {
        int my_arr[] = { 1, 2, 3, 4, 5 };
    
        struct mean_array result = get_mean(my_arr, sizeof my_arr / sizeof *my_arr);
    
        printf("mean: %f\n", result.mean);
        for (size_t i = 0; i < result.num_elems; i++) {
            printf("%8.5f", result.array[i]);
        }
        putchar('\n');
    
        return 0;
    }
    
    struct mean_array get_mean(int arr[], size_t arr_sz)
    {
        assert(arr_sz <= MAX_ARR);
        struct mean_array res = { .num_elems = arr_sz };
        double sum = 0;
    
        for (size_t i = 0; i < arr_sz; i++) {
            sum += arr[i];
            res.array[i] = 10.5 * i / (128.0 - 1.0);
        }
        res.mean = sum / arr_sz;
    
        return res;
    }
    

    程序输出:

    mean: 3.000000
    0.00000 0.08268 0.16535 0.24803 0.33071
    

    回答 OP 在 cmets 中提出的几个问题:

    size_t 是用于数组索引的正确类型,因为它保证能够保存任何数组索引。您通常可以使用int 来代替;但是要小心这一点,因为访问,甚至形成指向数组第一个元素之前的位置的指针会导致未定义的行为。一般来说,数组索引应该是非负的。此外,在某些实现中,size_t 可能是比int 更宽的类型; size_t 保证保存任何数组索引,但 int 没有这样的保证。

    关于此处使用的for 循环语法,例如for (size_t i = 0; i &lt; sz; i++) {}:此处i 是用循环范围 声明的。也就是说,i 的生命周期在循环体退出时结束。自 C99 以来,这一直是可能的。最好的做法是尽可能限制变量范围。我默认这样做,因此我必须主动选择以使循环变量在循环体之外可用。

    如果循环范围的变量或size_t 类型导致编译错误,我怀疑您可能是在 C89 模式下编译。这两个特性都是在 C99 中引入的。如果您使用 gcc,旧版本(例如,我相信 gcc 4.x)默认为 C89。您可以使用gcc -std=c99gcc -std=c11 进行编译以使用更新的语言标准。我建议至少启用警告:gcc -std=c99 -Wall -Wextra 以在编译时发现许多问题。如果您在 Windows 中工作,您可能也会遇到类似的困难。据我了解,MSVC 符合 C89,但对后来的 C 语言标准的支持有限。

    【讨论】:

    • 事情开始变得有意义了!我在 for 循环中做什么之前的 size_t 是什么?我注意到你和菲利克斯使用了这个。我必须删除它才能让代码正确执行
    • @Sjoseph 我在回答中解释了size_t。包括stddef.hstdlib.h 以使用它。
    • @FelixPalmen-- size_t 也在stdio.h 中声明,因此包含该头文件就足够了。
    • @Sjoseph-- size_t 是用于数组索引的正确类型,因为它保证能够保存任何数组索引。您通常可以改用int,而有时int 似乎是正确的选择。请注意,在循环中,例如 for (size_t i = 0; i &lt; sz; i++) {},这里的 i 是用 循环范围 声明的。也就是说,i 的生命周期在循环体退出时结束。自 C99 以来,这一直是可能的。最好的做法是尽可能限制变量范围。我默认这样做,因此我必须主动选择以使循环变量在循环体之外可用。
    • @Sjoseph-- 如果循环范围的变量导致编译错误,我怀疑您可能是在 C89 模式下编译。如果您使用 gcc,旧版本(例如 gcc 4.x,我相信)默认为 C89。您可以使用-std=c99-std=c11 进行编译以使用更新的语言标准。如果您在 Windows 中工作,您可能也会遇到类似的困难。 Felix 比我更熟悉在 Windows 系统上使用 C 语言工作的来龙去脉。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-15
    • 2021-11-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多