Передача массива структур в функцию c ++ - PullRequest
16 голосов
/ 01 сентября 2010

Извините за вопрос noob, я немного запутался.
Если у меня есть массив структур в main, которые я хочу передать функции:

struct MyStruct{
    int a;
    int b;
    char c;
    mayarray[5];
};  
MyStruct StructArray[10]; 

myFunction(StructArray[])

Перейдите к этомуфункция:

void myFunction(struct MyStruct PassedStruct[])
{
    PassedStruct[0].a = 1;
    PassedStruct[0].b = 2;
    // ... etc
}  

Мой вопрос: вызовет ли функция, подобная этой, изменение данных в StructArray?Мне это нужно.Будет ли это позвонить по ссылке?Я немного запутался.Как бы я изменил его так, чтобы при передаче массива структур в функцию функция изменяла массив StructArray?Кстати, я использую визуальную студию.
Спасибо.

Ответы [ 5 ]

24 голосов
/ 01 сентября 2010
struct MyStruct PassedStruct[]

это в основном альтернативный синтаксис для:

struct MyStruct * PassedStruct

Так что да, вы получите доступ и измените исходную структуру.

Только одна деталь, которую нужно изменить, правильный вызов функции не

myFunction(StructArray[]);

но:

myFunction(StructArray);

Теперь я попытаюсь объяснить, почему я использовал слово в основном в приведенном выше предложении:

Я дам некоторую подсказку о разнице между массивами и указателями, почему вы не должны путать их (даже если бы я не сказал, что они не связаны, а совсем наоборот), и проблема с передачей параметра MyStruct PassedStruct[] выше синтаксис.

Это не для начинающих, и эксперты по стандарту C ++ также должны избегать чтения этого (потому что я не хочу вступать в некоторые - ISO Standard_ war, когда я вхожу в ISO неопределенное поведение территория - также запрещенная территория ).

Начнем с массивов:

Представьте простую структуру:

struct MyStruct{
    int a;
    int b;
    char c;
};  

MyStruct a1[3]; - это объявление массива, элементы которого имеют вышеуказанный тип структуры. Самое важное, что делает компилятор при определении массива, - это выделить для него место. В нашем примере это зарезервированное пространство для 3 структур. Это зарезервированное пространство может находиться в стеке или из ресурсов глобальной памяти, в зависимости от того, где находится оператор объявления.

Вы также можете инициализировать структуру при объявлении ее следующим образом:

struct MyStruct a1[3] = {{1, 2}, {3, 4}, {5, 6}};

Обратите внимание, что в этом примере я не инициализировал поле c, а просто a и b. Это разрешено Я мог бы также использовать синтаксис указателя, если мой компилятор поддерживает его, как в:

struct MyStruct a1[3] = {{a:1, b:2}, {a:3, b:4}, {a:5, b:6}};

Теперь есть другой синтаксис для определения массива с использованием пустых квадратных бэкэтов, как в:

struct MyStruct a2[] = {{1, 2}, {3, 4}, {5, 6}};

Дело в том, что a2 - это совершенно нормальный массив, такой же как a1. Размер массива не является неявным, он передается через инициализатор: у меня есть три инициализатора, поэтому я получаю массив из трех структур.

С этим синтаксисом я мог бы определить неинициализированный массив известного размера. Для неинициализированного массива размером 3 у меня будет:

struct MyStruct a2[] = {{},{},{}};

Пространство выделено точно так же, как и в предыдущем синтаксисе, указатель здесь не используется.

Давайте введем один указатель:

MyStruct * p1;

Это простой указатель на структуру типа MyStruct. Я могу получить доступ к полям через обычный синтаксис указателя p1->a или (*p1).a. Существует также синтаксис с использованием массива для выполнения действий, описанных выше p1[0].a. Все так же, как и выше. Вам просто нужно помнить, что p1 [0] является сокращением для (*(p1+0)).

Также запомните правило арифметики с указателями: добавление 1 к указателю означает добавление sizeof указанного объекта к базовому адресу памяти (что вы получаете, когда используете параметр формата% p printf). Арифметика указателя позволяет получить доступ к последовательным идентичным структурам. Это означает, что вы можете получить доступ к структурам по индексу с помощью p1[0], p1[2] и т. Д.

Границы не проверяются. То, на что указывает память, является обязанностью программиста. Да, я знаю, что ISO говорит по-другому, но это то, что делают все компиляторы, которые я когда-либо пробовал, поэтому, если вы знаете, что это не так, скажите, пожалуйста.

Чтобы сделать что-нибудь полезное с p1, вы должны указать на некоторую структуру типа MyStruct. Если у вас есть массив таких структур, таких как a1, вы можете просто сделать p1=a1 и p1 будет указывать на начало массива. Другими словами, вы могли бы также сделать p1=&a1[0]. Естественно иметь простой доступный синтаксис, поскольку именно для этого предназначена арифметика указателей: доступ к массивам похожих объектов.

Прелесть этого соглашения в том, что оно позволяет полностью унифицировать синтаксис доступа к указателю и массиву. Разница видна только компилятору:

  • когда он видит p1[0], он знает, что должен извлечь содержимое переменной с именем p1 и что он будет содержать адрес некоторой структуры памяти.

  • когда он видит a1[0], он знает, a1 - это некоторая константа, которую следует понимать как адрес (а не что-то для извлечения из памяти).

Нокак только адрес из p1 или a1 станет доступным, процедура будет идентичной.

Распространенной ошибкой является запись p1 = &a1.Если вы сделаете это, компилятор выдаст вам четыре буквенных слова.Хорошо, &a1 также является указателем, но то, что вы получаете, беря адрес a1, это указатель на весь массив.Это означает, что если вы добавите 1 к указателю этого типа, фактический адрес будет перемещаться с шагом в 3 структуры одновременно.

Фактический тип указателя такого типа (назовем его p2) будет MyStruct (*p2)[3];.Теперь вы можете написать p2 = &a1.Если вы хотите получить доступ к первой структуре MyStruct в начале блока памяти, на который указывает p2, вам придется написать что-то вроде p2[0][0].a или (*p2)[0].a или (*(*p2)).a или (*p2)->a или p2[0]->a,

Благодаря арифметике системы типов и указателей все они выполняют одно и то же: извлекают адрес, содержащийся в p2, используют этот адрес в качестве массива (известный постоянный адрес), как описано выше.

Теперь вы можете понять, почему указатели и массивы являются совершенно разными типами, которые не следует путать , как некоторые могут сказать.Проще говоря, указатели - это переменные, содержащие адрес, а массивы - это постоянные адреса.Пожалуйста, не стреляйте в меня, гуру C ++, да, я знаю, что это не полная история, и компиляторы хранят много другой информации наряду с адресом, размером указанного (адресуемого?) Объекта, например.

Теперь вы можете задаться вопросомпочему в контексте передачи параметров вы можете использовать пустые квадратные скобки, и это действительно означает указатель .?Без понятия.Кто-то, вероятно, думал, что это выглядело хорошо.

Кстати, по крайней мере, с помощью gcc вы также можете поместить некоторые значения в скобки вместо того, чтобы оставлять их пустыми.Это не будет иметь значения, вы все равно получите указатель, а не массив, а границы или проверка типов не будут выполнены.Я не проверял в стандарте ISO, должно быть сделано, и если это требуется стандартом или если это определенное поведение.

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

MyStruct StructArray[10]; 
  • заголовок: void myFunction(struct MyStruct * PassedStruct)
  • абонент: myFunction(StructArray)
  • статус: работает, вы работаете с указателем в PassedStruct
  • заголовок: void myFunction(struct MyStruct PassedStruct[])
  • вызывающий абонент: myFunction(StructArray)
  • статус: работает, вы работаете с указателем в PassedStruct
  • заголовок: void myFunction(struct MyStruct (& PassedStruct)[10])
  • вызывающий: myFunction(StructArray)
  • статус: работает, вы работаете со ссылкой на массив размером 10
  • заголовок: void myFunction(struct MyStruct (& PassedStruct)[11])
  • вызывающая сторона: myFunction(StructArray)
  • состояние: не компилируется, несоответствие типа массива между прототипом и фактическим параметром
  • заголовок: void myFunction(struct MyStruct PassedStruct[10])
  • вызывающая сторона: myFunction(StructArray)
  • статус: работает, PassedStruct - указатель, предоставленный размер игнорируется
  • заголовок: void myFunction(struct MyStruct PassedStruct[11])
  • вызывающий: myFunction(StructArray)
  • статус: работает, PassedStruct - указатель, предоставленный размер игнорируется
8 голосов
/ 01 сентября 2010

Хотя массивы и указатели являются концептуально разными вещами, воды очень сильно загрязнены функциональными параметрами. Вы не можете напрямую передать массив в функцию; Вы можете только передать указатель. В результате язык «услужливо» преобразует такой прототип, как этот:

void foo (char arr[])

в это:

void foo (char *arr)

И когда вы вызываете функцию, вы не передаете ей полный массив, вы передаете указатель на первый элемент. В результате внутри функции foo у нее будет указатель на массив original , а присвоение элементам arr также изменит массив в вызывающей программе.

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

1 голос
/ 01 сентября 2010

Вместо этого вы можете использовать std :: vector.Массивы очень похожи на конструкции C, в C ++ есть классы-обертки для предотвращения именно такой неоднозначности

1 голос
/ 01 сентября 2010

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

Итак, когда внутри вашей функции вы используете [] для доступа к элементам массива, вы на самом деле выполняете арифметику только с указателем, чтобы получить элементы массива ORIGINAL.

Итак, да, вы модифицируете исходный массив. И этот ответ в значительной степени не зависит от того, какой компилятор вы используете (хотя, IMHO, полезно указывать компилятор в вопросе, как вы это сделали)

1 голос
/ 01 сентября 2010

Я считаю, что x [] означает то же самое, что и x *, что означает «указатель на тип». Поскольку это так, вы будете изменять передаваемый объект (вам нужно будет вызывать его с помощью оператора & или 'address of'), и вы можете рассматривать его как ссылку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...