在C语言中如何通过引用传递数组?

77

我如何在C中通过引用传递结构体数组?

以下是一个例子:

struct Coordinate {
   int X;
   int Y;
};
SomeMethod(Coordinate *Coordinates[]){
   //Do Something with the array
}
int main(){ 
   Coordinate Coordinates[10];
   SomeMethod(&Coordinates);
}
7个回答

153

在C语言中,数组传递的是第一个元素的指针。它们是唯一一个不会真正按值传递的元素(指针是按值传递的,但数组并未复制)。这使得被调用的函数能够修改其内容。

void reset( int *array, int size) {
   memset(array,0,size * sizeof(*array));
}
int main()
{
   int array[10];
   reset( array, 10 ); // sets all elements to 0
}

如果你想改变数组本身(元素数量等),你无法使用栈或全局数组,只能使用在堆中动态分配的内存。在这种情况下,如果你想改变指针,你必须传递一个指向它的指针。

void resize( int **p, int size ) {
   free( *p );
   *p = malloc( size * sizeof(int) );
}
int main() {
   int *p = malloc( 10 * sizeof(int) );
   resize( &p, 20 );
}

在问题编辑中,你特别询问了如何传递结构体数组。你有两个解决方案:声明一个typedef或明确说明你正在传递结构体:

struct Coordinate {
   int x;
   int y;
};
void f( struct Coordinate coordinates[], int size );
typedef struct Coordinate Coordinate;  // generate a type alias 'Coordinate' that is equivalent to struct Coordinate
void g( Coordinate coordinates[], int size ); // uses typedef'ed Coordinate

你可以在声明类型时使用 typedef(这是 C 语言中常见的用法):

typedef struct Coordinate {
   int x;
   int y;
} Coordinate;

2
使用memset( array, 0, sizeof array);来代替reset函数中的for()循环 :) - M4rk
5
@rodi:考虑到你引入了一个错误,这是一个有趣的观点。我已经更新了代码,使用memset作为它应该使用的方式:memset(array,0,size * sizeof *array)-- 这种情况下 sizeof(array) 是指针的大小,而不是所指向的数据大小。 - David Rodríguez - dribeas
1
最好的做法是不要对malloc的返回值进行强制类型转换。参见这里 - Aka
2
@Aka:你说得完全正确,我已经在9年前写下这篇文章时学到了这一点 :) [我已经编辑掉了强制转换,感谢你提出这个问题] - David Rodríguez - dribeas
当数组类型被限制为int时,为什么要使用sizeof(*array)?为什么不只使用sizeof(int)? - Elias Hasle
完美的答案。对于像我这样的一些n00b,可能会尝试在char数组上重复第一个代码片段,并修改函数内部指针的非空值,应该知道,就像我现在意识到的那样,那个代码片段不会按原样工作。需要使用“size-1”来保留空间以进行空终止。 - Duck Dodgers

12

稍微解释一下这里的一些答案...

在C语言中,当一个数组标识符出现在除 & 或 sizeof 之外的上下文中时,标识符的类型会隐式转换为“T类型的指针”,并且它的值会隐式设置为数组中第一个元素的地址(与数组本身的地址相同)。这就是为什么当你只将数组标识符作为参数传递给函数时,函数接收到的是基本类型的指针,而不是数组。由于你无法仅通过查看第一个元素的指针来确定数组的大小,因此你必须将大小作为单独的参数传递。

struct Coordinate { int x; int y; };
void SomeMethod(struct Coordinate *coordinates, size_t numCoordinates)
{
    ...
    coordinates[i].x = ...;
    coordinates[i].y = ...; 
    ...
}
int main (void)
{
    struct Coordinate coordinates[10];
    ...
    SomeMethod (coordinates, sizeof coordinates / sizeof *coordinates);
    ...
}

有几种传递数组给函数的替代方法。

有一种指向 T 类型数组的指针,与指向 T 的指针不同。你可以这样声明这样的指针:

T (*p)[N];

在这种情况下,p 是一个指向 N 个元素的 T 数组的指针(与 T *p[N] 相反,其中 p 是指针数组,包含 N 个指向 T 的指针)。因此,您可以传递指向数组的指针,而不是指向第一个元素的指针:
struct Coordinate { int x; int y };

void SomeMethod(struct Coordinate (*coordinates)[10])
{
    ...
    (*coordinates)[i].x = ...;
    (*coordinates)[i].y = ...;
    ...
}

int main(void)
{
    struct Coordinate coordinates[10];
    ...
    SomeMethod(&coordinates);
    ...
}

这种方法的缺点是数组大小固定,因为指向包含10个T元素的数组的指针与指向包含20个T元素的数组的指针是不同类型的。第三种方法是将数组封装在结构体中:
struct Coordinate { int x; int y; };
struct CoordinateWrapper { struct Coordinate coordinates[10]; };
void SomeMethod(struct CoordinateWrapper wrapper)
{
    ...
    wrapper.coordinates[i].x = ...;
    wrapper.coordinates[i].y = ...;
    ...
}
int main(void)
{
    struct CoordinateWrapper wrapper;
    ...
    SomeMethod(wrapper);
    ...
}

这种方法的优点是你不需要操作指针。缺点是数组大小是固定的(例如,一个包含10个元素的T类型数组和一个包含20个元素的T类型数组是不同的类型)。

你的包装器实现的另一个缺点是 wrapper 是按值传递的。大约 80 字节,而 SomeMethod() 中的赋值对于在 main 中声明的 wrapper 没有影响。看起来像是一个 bug。 - bobbogo

9

C语言不支持任何类型的按引用传递。最接近的等效方法是传递该类型的指针。

以下是两种语言中的一个人为示例:

C++风格API

void UpdateValue(int& i) {
  i = 42;
}

最接近的C语言等效代码

void UpdateValue(int *i) {
  *i = 42;
}

我知道,但是我发现用同样的方法传递数组有问题 :s - Hannoun Yassir
3
Sure, 你好!这是一个例子:@Yassir,你能举个例子吗? - JaredPar
我是指在dribeas的帖子中得到了适当的演示!=) - Ralph Caraveo
@Ralph,你认为这值得一个-1吗?OP提出了一个没有有效答案的问题,我提供了一个简单的“最接近的功能是…”的答案。也许不是+1的答案,但肯定不是-1。 - JaredPar
@Ralph,当然,dribeas也没有通过引用传递它(正如你在阅读他的回答时所看到的,他说他通过值传递指针)。顺便说一下,函数的行为也是一样的。它们就像通过指向它们的指针传递的数组。 - Johannes Schaub - litb
显示剩余2条评论

8

请注意,如果您在方法内创建数组,则无法返回它。如果您返回指向它的指针,在函数返回时,它将从堆栈中移除。 您必须在堆上分配内存并返回指向该内存的指针。 例如:

//this is bad
char* getname()
{
  char name[100];
  return name;
}

//this is better
char* getname()
{
  char *name = malloc(100);
  return name;
  //remember to free(name)
}

7

默认情况下,数组是通过引用传递的。实际上是传递了指向第一个元素的指针的值。因此,接收到它的函数或方法可以修改数组中的值。

void SomeMethod(Coordinate Coordinates[]){Coordinates[0].x++;};
int main(){
  Coordinate tenCoordinates[10];
  tenCoordinates[0].x=0;
  SomeMethod(tenCoordinates[]);
  SomeMethod(&tenCoordinates[0]);
  if(0==tenCoordinates[0].x - 2;){
    exit(0);
  }
  exit(-1);
}

这两个调用是等价的,退出值应该为0;

6
在纯C中,您可以在API中使用指针/大小组合。
void doSomething(MyStruct* mystruct, size_t numElements)
{
    for (size_t i = 0; i < numElements; ++i)
    {
        MyStruct current = mystruct[i];
        handleElement(current);
    }
}

在C语言中,使用指针是最接近以引用方式调用的方法。


2

大家好,这是一个简单的测试程序,展示了如何使用new或malloc来分配和传递数组。只需复制,粘贴并运行即可。玩得开心!

struct Coordinate
{
    int x,y;
};

void resize( int **p, int size )
{
   free( *p );
   *p = (int*) malloc( size * sizeof(int) );
}

void resizeCoord( struct Coordinate **p, int size )
{
   free( *p );
   *p = (Coordinate*) malloc( size * sizeof(Coordinate) );
}

void resizeCoordWithNew( struct Coordinate **p, int size )
{
   delete [] *p;
   *p = (struct Coordinate*) new struct Coordinate[size];
}

void SomeMethod(Coordinate Coordinates[])
{
    Coordinates[0].x++;
    Coordinates[0].y = 6;
}

void SomeOtherMethod(Coordinate Coordinates[], int size)
{
    for (int i=0; i<size; i++)
    {
        Coordinates[i].x = i;
        Coordinates[i].y = i*2;
    }
}

int main()
{
    //static array
    Coordinate tenCoordinates[10];
    tenCoordinates[0].x=0;
    SomeMethod(tenCoordinates);
    SomeMethod(&(tenCoordinates[0]));
    if(tenCoordinates[0].x - 2  == 0)
    {
        printf("test1 coord change successful\n");
    }
    else
    {
        printf("test1 coord change unsuccessful\n");
    }


   //dynamic int
   int *p = (int*) malloc( 10 * sizeof(int) );
   resize( &p, 20 );

   //dynamic struct with malloc
   int myresize = 20;
   int initSize = 10;
   struct Coordinate *pcoord = (struct Coordinate*) malloc (initSize * sizeof(struct Coordinate));
   resizeCoord(&pcoord, myresize); 
   SomeOtherMethod(pcoord, myresize);
   bool pass = true;
   for (int i=0; i<myresize; i++)
   {
       if (! ((pcoord[i].x == i) && (pcoord[i].y == i*2)))
       {        
           printf("Error dynamic Coord struct [%d] failed with (%d,%d)\n",i,pcoord[i].x,pcoord[i].y);
           pass = false;
       }
   }
   if (pass)
   {
       printf("test2 coords for dynamic struct allocated with malloc worked correctly\n");
   }


   //dynamic struct with new
   myresize = 20;
   initSize = 10;
   struct Coordinate *pcoord2 = (struct Coordinate*) new struct Coordinate[initSize];
   resizeCoordWithNew(&pcoord2, myresize); 
   SomeOtherMethod(pcoord2, myresize);
   pass = true;
   for (int i=0; i<myresize; i++)
   {
       if (! ((pcoord2[i].x == i) && (pcoord2[i].y == i*2)))
       {        
           printf("Error dynamic Coord struct [%d] failed with (%d,%d)\n",i,pcoord2[i].x,pcoord2[i].y);
           pass = false;
       }
   }
   if (pass)
   {
       printf("test3 coords for dynamic struct with new worked correctly\n");
   }


   return 0;
}

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