【问题标题】:Passing an array by reference in C?在C中通过引用传递数组?
【发布时间】:2010-11-09 13:38:09
【问题描述】:

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

举个例子:

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

【问题讨论】:

    标签: c arrays pass-by-reference


    【解决方案1】:

    在 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;
    

    【讨论】:

    • 使用 memset(array, 0, sizeof array);而不是 for() 循环里面的重置:)
    • @rodi: 这是一个有趣的观点,考虑到你引入了一个错误 :) 我已经更新了代码以使用 memset,因为它应该被使用:memset(array,0,size * sizeof *array) -- sizeof(array) in这种情况是指针的大小,而不是指向的数据。
    • 最好不要类型转换malloc的返回值。见here
    • @Aka:你说的很对,自从我 9 年前写这篇文章以来我就知道了 :) [我已经编辑删除了演员表,感谢您提出这个问题]
    • 当数组类型被限制为 int 时,为什么要 sizeof(*array)?为什么不只是 sizeof(int)?
    【解决方案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;
    }
    

    【讨论】:

      【解决方案3】:

      在这里扩展一些答案...

      在 C 中,当数组标识符出现在上下文中而不是作为 & 或 sizeof 的操作数时,标识符的类型会从“T 的 N 元素数组”隐式转换为“指向 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 是指向 T 的 N 元素数组的指针(与 T *p[N] 相反,其中 p 是指向 T 的指针的 N​​ 元素数组)。所以你可以传递一个指向数组的指针而不是指向第一个元素的指针:

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

      这种方法的缺点是数组大小是固定的,因为指向 T 的 10 元素数组的指针与指向 T 的 20 元素数组的指针是不同的类型。

      第三种方法是将数组包装在结构中:

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

      这种方法的优点是你不会乱用指针。缺点是数组大小是固定的(同样,T 的 10 元素数组与 T 的 20 元素数组是不同的类型)。

      【讨论】:

      • 包装器公式的另一个缺点是wrapper 是由 传递的。 80 字节(-ish),SomeMethod() 内部的赋值对main 中声明的wrapper 没有影响。看起来像一个错误。
      【解决方案4】:

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

      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;

      【讨论】:

        【解决方案5】:

        还要注意,如果您在方法中创建数组,则不能返回它。如果你返回一个指向它的指针,当函数返回时它就会从堆栈中删除。 您必须将内存分配到堆上并返回指向它的指针。 例如。

        //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)
        }
        

        【讨论】:

          【解决方案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 中可用的引用调用。

          【讨论】:

            【解决方案7】:

            C 语言不支持任何类型的引用传递。最接近的等价物是传递一个指向该类型的指针。

            以下是两种语言的人为示例

            C++ 风格的 API

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

            最接近的 C 等效项

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

            【讨论】:

            • 我知道,但我发现使用相同方法传递数组时出现问题:s
            • @Yassir,你能举个例子吗?
            • 此示例演示了通过引用传递原始类型(使用按值传递的指针),但没有演示通过引用传递结构数组,如下面的帖子中正确演示的那样。
            • 我的意思是在 dribeas 的帖子中正确展示了! =)
            • @Ralph,你认为它值得 -1 吗? OP 问了一个没有有效答案的问题,我提供了一个简单的“最接近的功能是......”答案。也许不是 +1 答案,但肯定不是 -1
            猜你喜欢
            • 2013-11-07
            • 1970-01-01
            • 1970-01-01
            • 2020-07-30
            • 2012-04-17
            • 2019-06-29
            • 2015-07-08
            相关资源
            最近更新 更多