Передача массива по ссылке в C? - PullRequest
63 голосов
/ 10 июля 2009

Как я могу передать массив структур по ссылке в C?

Как пример:

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

Ответы [ 7 ]

124 голосов
/ 10 июля 2009

В 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;
11 голосов
/ 10 июля 2009

Чтобы немного расширить некоторые ответы здесь ...

В C, когда идентификатор массива появляется в контексте, отличном от операнда для & или sizeof, тип идентификатора неявно преобразуется из «N-элементного массива T» в «указатель на 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).

8 голосов
/ 10 июля 2009

Язык C не поддерживает передачу по ссылке любого типа. Ближайшим эквивалентом является передача указателя на тип.

Вот надуманный пример на обоих языках

API в стиле C ++

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

Ближайший эквивалент C

void UpdateValue(int *i) {
  *i = 42;
}
7 голосов
/ 10 июля 2009

также помните, что если вы создаете массив в методе, вы не можете его вернуть. Если вы вернете указатель на него, он будет удален из стека при возврате функции. Вы должны выделить память на кучу и вернуть указатель на это. например.

//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 голосов
/ 10 июля 2009

Массивы по умолчанию передаются по ссылке. На самом деле значение указателя на первый элемент передается. Поэтому функция или метод, получающие это, могут изменять значения в массиве.

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 голосов
/ 10 июля 2009

В простом 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 голосов
/ 08 декабря 2011

Привет, ребята, вот простая тестовая программа, которая показывает, как распределять и передавать массив, используя 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;
}
...