Ответ 1
struct MyStruct PassedStruct[]
является главным образом альтернативным синтаксисом для:
struct MyStruct * PassedStruct
Итак, вы получите доступ к исходной структуре и измените ее.
Только одна деталь для изменения, правильный вызов функции не
myFunction(StructArray[]);
а
myFunction(StructArray);
Теперь я попытаюсь объяснить, почему я использовал слово в основном в приведенном выше предложении:
Я дам некоторый намек на разницу между массивами и указателями, почему их не следует путать (даже если бы я не сказал, что они не связаны друг с другом, совсем наоборот) и проблема с передачей параметра MyStruct PassedStruct[]
синтаксис.
Это не для новичков, и стандартные эксперты С++ также должны избегать этого чтения (потому что я не хочу вступать в какую-либо войну ISO Standard_, поскольку я вхожу в зону поведения ISO undefined - aka запрещенная территория).
Начнем с массивов:
Представьте себе простую структуру:
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
. Если вы это сделаете, компилятор даст вам несколько буквенных слов. Ok, &a1
также является указателем, но то, что вы получаете при принятии адреса a1
, является указателем на весь массив. Это означает, что если вы добавите 1 к указателю этого типа, фактический адрес будет перемещаться по этапам из трех структур одновременно.
Фактический тип указателя такого типа (пусть его называют p2
) будет MyStruct (*p2)[3];
. Теперь вы можете написать p2 = &a1
. Если вы хотите получить доступ к первой структуре MyStruct
в начале блока памяти, на которую указывает p2
, вам придется писать такие вещи, как p2[0][0].a
или (*p2)[0].a
или (*(*p2)).a
или (*p2)->a
или p2[0]->a
,
Благодаря системе типа и арифметике указателя все они делают точно то же самое: выбор адреса, содержащегося в p2, используйте этот адрес как массив (известный постоянный адрес), как описано выше.
Теперь вы можете понять, почему указатели и массивы являются совершенно разными типами, которые не следует путать, как могут сказать некоторые. В простых словах указатели являются переменными, содержащими адрес, массивы - это постоянные адреса. Пожалуйста, не стреляйте в меня гуру С++, да, я знаю, что это не полная история, и что компиляторы содержат много другой информации, например, адрес, размер объекта point (адресуемого?).
Теперь вы можете удивиться, почему в контексте передачи параметров вы можете использовать пустые квадратные скобки, и это действительно означает указатель.? Без понятия. Кто-то, вероятно, думал, что это выглядит хорошо.
Кстати, по крайней мере с gcc, вы также можете поместить некоторое значение между скобками вместо того, чтобы держать их пустыми. Это не повлияет, вы все равно получите указатель, а не массив, а границы или проверка типов не будут выполнены. Я не проверял в стандарте ISO, должен быть сделан, и если это требуется стандартом или если это конкретное поведение.
Если вам нужна проверка типов для границ, просто используйте ссылку. Это может быть удивительно, но это область, где, если вы используете ссылку, фактический тип параметра изменяется от указателя на массив (а не от указателя к ссылке на указатель, как можно ожидать).
MyStruct StructArray[10];
- header:
void myFunction(struct MyStruct * PassedStruct)
- вызывающий абонент:
myFunction(StructArray)
- статус: работает, вы работаете с указателем в PassedStruct
- заголовок:
void myFunction(struct MyStruct PassedStruct[])
- вызывающий абонент:
myFunction(StructArray)
- статус: работает, вы работаете с указателем в PassedStruct
- header:
void myFunction(struct MyStruct (& PassedStruct)[10])
- вызывающий абонент:
myFunction(StructArray)
- статус: работает, вы работаете со ссылкой на массив размером 10
- header:
void myFunction(struct MyStruct (& PassedStruct)[11])
- вызывающий абонент:
myFunction(StructArray)
- статус: не компилируется, тип несоответствия массива между прототипом и фактическим параметром
- header:
void myFunction(struct MyStruct PassedStruct[10])
- вызывающий абонент:
myFunction(StructArray)
- status: works, PassedStruct - указатель, размер которого игнорируется.
- header:
void myFunction(struct MyStruct PassedStruct[11])
- вызывающий абонент:
myFunction(StructArray)
- status: works, PassedStruct - указатель, размер которого игнорируется.