按值传递数组

12
通常情况下,当我们通过数组名传递数组时,它是按地址调用的。这意味着如果我们在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


6
你可以将其封装在结构体中,但这有点像一个hack。为什么不在Foo函数内部使用memcpy呢? - Ry-
2
"//takes an integer array arr as argument" 其实不是,它接受一个 int *。在定义函数参数的上下文中,T t[]T * t 两者是等价的。 - alk
3个回答

15

通过将数组包装在 struct 中,可以实现此操作。您可以包含一个字段来表示数组的大小,以便无需显式传递此参数。这种方法具有避免后续必须释放的额外内存分配的优点。

C 将参数按值传递给函数,但大多数表达式中的数组标识符会衰减为指针,在函数调用中尤其如此。然而,struct 不会衰减为指针,并且作为值传递给函数,这意味着原始结构及其所有内容的副本在函数范围内可见。如果 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

我认为这是目前为止最好的答案,没有使用临时变量或类似的东西。感谢您的方法。 - Fahim Al Mahmud Ashik

5
通常情况下,你不能这样做。
调用者可以像这样处理:
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他们的堆栈数组的人 :-) - clearlight
2
它不是为堆栈数组分配内存 - 它是动态分配内存来保存数组的副本。别让我开始谈论C11中实现不需要支持变长数组(VLAs)。 - Peter
哦,是的,我说话匆忙了,我的意思是因为你没有动态堆栈数组,所以需要使用malloc。 - clearlight
无论如何,您都应该传递大小,否则Foo甚至不知道它需要处理多少数据 - 即使它不必复制它们。此外,在您的变量中,它应该采用“const int arr []”,因为它不应更改调用者可见的数据。 - Ruslan
还有其他计算长度的方法,例如存在哨兵值(如在strcmp()等函数中使用)。关于const,我同意,但保持示例与OP一致。 - Peter

0

我不确定为什么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/非零),但暗示它正在检查一个布尔值,但实际上你正在检查一个NULL指针。最好始终编写一致的代码,并让所有内容尽可能地澄清您的意图。独特的格式只会导致混乱。每个人都有自己的怪癖。找到一个可靠的C风格标准并坚持下去,从长远来看更好。 - clearlight
你的函数参数中是否缺少逗号?非常整洁一致的风格还有一个好处就是更容易更快地发现错误。 - clearlight
1
@clearlight 为了举例说明,我会更改我所拥有的内容,但你是否想到过我一直使用单行参数保护返回语句(仅出于此目的?)我不知道你是否认为解引用需要空格,但如果我写成* ptr而不是*ptr,那么阅读起来会更困难。 - synchronizer
@SurajJain 我通常使用四个空格。在StackOverflow上不是这样吗? - synchronizer
@synchronizer 这是正常的,可以查看编辑历史记录,你的 include 没有被包含在代码中,所以我编辑了它,使其出现在代码中。此外,我还不得不添加其他内容,否则编辑内容将少于6个字符,无法被接受。请查看编辑历史记录。 - Suraj Jain

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