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