【问题标题】:Pass an array by value按值传递数组
【发布时间】:2017-06-22 05:49:35
【问题描述】:

一般我们通过数组名传递数组时,就是按地址调用。这意味着如果我们更改 main() 之外的任何数组值,它将反映在 main() 中。

那么,如果我想将数组作为函数的参数传递并在main() 中调用它,这样该函数的任何更改都不会反映在main() 中,我该怎么办?

例如:

void Foo(int arr[])   //takes an integer array `arr` as argument
{
    // do something with `arr
}

int main()
{
    int abc[]={5,1,2,9};
    //do something to pass `abc` inside `Foo` so that changes inside `Foo` doesn't change the value of `abc` array.
}

现在我想将abc 数组按值传递给Foo

【问题讨论】:

  • 你可以将它包装在一个结构中,但这有点小技巧。为什么不在Foo 中使用memcpy 代替?
  • "//takes an integer array arr as argument" 不,实际上它需要一个int *。在定义函数参数的上下文中,T t[] 等价于T * t

标签: c arrays function pass-by-value


【解决方案1】:

可以通过将数组包装在struct 中来做到这一点。您可以包含一个用于数组大小的字段,这样您就不需要显式传递此参数。这种方法的优点是避免了以后必须释放的额外内存分配。

C 已经通过值将参数传递给函数,但数组标识符在大多数表达式中衰减为指针,尤其是在函数调用中。然而structs 不会衰减为指针,而是按值传递给函数,这意味着原始结构及其所有内容的副本在函数范围内是可见的。如果struct 包含一个数组,它也会被复制。请注意,如果struct 包含指向动态数组的int 的指针,那么当struct 传递给函数时,指针会被复制,但复制和原来的指针。这种方法依赖于包含实际数组的struct

还要注意struct 不能包含类型不完整的成员,因此不能包含 VLA。在这里,我将全局常量 MAX_ARR 定义为 100,以提供一些空间来处理具有相同 struct 类型的不同大小的数组。

您还可以从函数返回struct。我已经包含了一个示例,它修改了传递给函数的Array struct,并返回修改后的struct 以分配给调用函数中的不同Array struct。这导致调用者可以访问原始数组和转换后的数组。

#include <stdio.h>

#define MAX_ARR  100

struct Array {
    size_t size;
    int array[MAX_ARR];
};

void print_array(struct Array local_arr);
void func(struct Array local_arr);
struct Array triple(struct Array local_arr);

int main(void)
{
    struct Array data = {
        .size = 10,
        .array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
    };
    struct Array transformed_data;

    func(data);
    transformed_data = triple(data);

    printf("Original\n");
    print_array(data);

    printf("Transformed\n");
    print_array(transformed_data);

    return 0;
}

void print_array(struct Array local_arr)
{
    for (size_t i = 0; i < local_arr.size; i++) {
        printf("%5d", local_arr.array[i]);
    }
    putchar('\n');
}

void func(struct Array local_arr)
{
    for (size_t i = 0; i < local_arr.size; i++) {
        local_arr.array[i] *= 2;
    }
    printf("Modified\n");
    print_array(local_arr);
}

struct Array triple(struct Array local_arr)
{
    for (size_t i = 0; i < local_arr.size; i++) {
        local_arr.array[i] *= 3;
    }
    return local_arr;
}

程序输出:

Modified
    2    4    6    8   10   12   14   16   18   20
Original
    1    2    3    4    5    6    7    8    9   10
Transformed
    3    6    9   12   15   18   21   24   27   30

【讨论】:

  • 嗯,我认为这是我迄今为止最好的答案,没有使用临时变量或类似的东西。感谢您的方法。
【解决方案2】:

一般来说,你不能。

调用者可以做这样的事情;

int main()
{
    int abc[]={5,1,2,9};

    {
         int temp[sizeof (abc)/sizeof (*abc)];
         memcpy(temp, abc, sizeof(abc));
         Foo(temp);
    }
}

请记住,Foo() 不会收到有关传递的数组中元素数量的任何信息。

如果你想让Foo()做类似的事情,那么调用者就不需要了,需要将元素的数量作为单独的参数传递。

void Foo(int arr[], size_t size)    /*  C99 or later */
{
       int temp[size];   //   VLA
       memcpy(temp, arr, size*sizeof(int));
         /* whatever */
}

或(在 C99 之前)。

void Foo(int arr[], size_t size)    /*  Before C99 */
{
       int *temp = malloc(size * sizeof (int));
       memcpy(temp, arr, size*sizeof(int));
         /* whatever */
       free(temp);
}

为了避免内存泄漏,有必要确保在第二种情况下,函数在调用free(temp)之前不会返回。

在上述Foo() 的两个版本中,可能需要额外的错误检查(例如,检测空指针或零大小被传递,malloc() 成功等)。

【讨论】:

  • 喜欢 C99/pre-C99 警告 :-) 上帝帮助任何需要 malloc 堆栈数组的人 :-)
  • 它不是分配堆栈数组——它动态分配内存来保存数组的副本。甚至不要让我开始不需要支持 C11 中的 VLA 的实现。
  • 哦,是的,我说话仓促,意思是 mallocing,因为你没有动态堆栈数组。
  • 在任何情况下都应该传递大小,否则 Foo 甚至不知道它必须处理多少数据——即使它不必复制它们。此外,在您的变体中,它应该采用 const int arr[],因为它不应该更改调用者可见的数据。
  • 还有其他方法来计算长度,例如存在标记值(如strcmp() 等函数中使用的那样。在const 上,我同意,但保持示例一致与 OP。
【解决方案3】:

(我不确定 Ryan 为什么不自愿提供他自己的答案,但我同意以下应该可行:)

#include <stdlib.h> // In header
#include <string.h>

int Foo(size_t size, int arr[])
{
   // guard against bad arguments
   if (arr == NULL || size <= 0)
   {
       return -1;
   }

   // create local array, since size is variable, allocate dynamically
   int* arr_2 = NULL;
   if ((arr_2 = malloc(n * sizeof(*arr_2)) == NULL)
   {
       return -1; // malloc allocation error
   }
   // otherwise if the size is constant:
   // int arr_2[SOME_CONSTANT_SIZE];

   // copy from arr to arr_2
   memcpy(arr_2, arr, n * sizeof(*arr_2));

   // some computation

   free(arr_2);

   return 0;
}

请记住,一旦我们离开Foo 的范围,arr_2 将不再存在。此外,对于非原始数组元素,您需要做更多的复制工作。

【讨论】:

  • 将 return 放在同一行很俗气(不一致,大多数代码商店都不允许这样做)。运算符周围的空白。显式检查 malloc() 是否针对 NULL,而不是使用 !。 NOT 运算符在技术上有效(因为我们知道 0/非零),但 含义 是它正在检查 boolean 值,但您实际上是在检查空指针。最好是一致地编写代码,让一切尽可能地阐明你的意图。而特殊的格式只会导致巴比伦之塔。每个人都有一个怪癖。找到一个可靠的 C 风格标准并坚持下去。从长远来看会更好
  • 您还缺少函数参数之间的逗号吗?非常简洁一致的风格的另一件事是它更容易和更快地发现错误。
  • @clearlight 出于本示例的目的,我将更改我所拥有的,但您是否想到我一直使用单行参数保护返回语句(并且仅用于此目的?)我不'不知道你是否认为取消引用是需要空格的东西,但我认为如果我写 * ptr 而不是 *ptr 会更难阅读。
  • @SurajJain 我通常使用四个空格。这在 StackOverflow 上不正常吗?
  • @synchronizer 这很正常,请查看编辑历史记录,您的包含未包含在代码中,因此我对其进行了编辑以使其包含在代码中。此外,我还必须添加其他内容,否则,编辑会少于 6 个字符,不会被接受。请参阅编辑历史记录。
猜你喜欢
  • 1970-01-01
  • 2015-08-03
  • 1970-01-01
  • 1970-01-01
  • 2012-05-06
  • 2020-05-23
  • 2014-09-30
  • 2015-01-06
相关资源
最近更新 更多