Struct c что это
Типы struct, union и enum в Modern C++
Язык C++ сильно изменился за последние 10 лет. Изменились даже базовые типы: struct, union и enum. Сегодня мы кратко пройдёмся по всем изменениям от C++11 до C++17, заглянем в C++20 и в конце составим список правил хорошего стиля.
Зачем нужен тип struct
Тип struct — фундаментальный. Согласно C++ Code Guidelines, struct лучше использовать для хранения значений, не связанных инвариантом. Яркие примеры — RGBA-цвет, вектора из 2, 3, 4 элементов или информация о книге (название, количество страниц, автор, год издания и т.п.).
Он похож на class, но есть два мелких различия:
Согласно C++ Core Guidelines, struct хорошо применять для сокращения числа параметров функции. Этот приём рефакторинга известен как «parameter object».
Кроме того, структуры могут сделать код более лаконичным. Например, в 2D и 3D графике удобнее считать в 2-х и 3-х компонентных векторах, чем в числах. Ниже показан код, использующий библиотеку GLM (OpenGL Mathematics)
Эволюция struct
В C++11 появилась инициализация полей при объявлении.
Ранее для таких целей приходилось писать свой конструктор:
Вместе с инициализацией при объявлении пришла проблема: мы не можем использовать литерал структуры, если она использует инициализацию полей при объявлении:
В C++11 и C++14 это решалось вручную написанием конструктора с boilerplate кодом. В C++17 ничего дописывать не надо — стандарт явно разрешает агрегатную инициализацию для структур с инициализаторами полей.
В примере написаны конструкторы, необходимые только в C++11 и C++14:
В C++20 агрегатная инициализация обещает стать ещё лучше! Чтобы понять проблему, взгляните на пример ниже и назовите каждое из пяти инициализируемых полей. Не перепутан ли порядок инициализации? Что если кто-то в ходе рефакторинга поменяет местами поля в объявлении структуры?
В C11 появилась удобная возможность указать имена полей при инициализации структуры. Эту возможность обещают включить в C++20 под названием «назначенный инициализатор» («designated initializer»). Подробнее об этом в статье Дорога к С++20.
В C++17 появился structured binding, также известный как «декомпозиция при
объявлении». Этот механизм работает со структурами, с std::pair и std::tuple и дополняет агрегатную инициализацию.
В сочетании с классами STL эта фишка может сделать код элегантнее:
Зачем нужен тип union
Вообще-то в C++17 он не нужен в повседневном коде. C++ Core Guidelines предлагают строить код по принципу статической типобезопасности, что позволяет компилятору выдать ошибку при откровенно некорректной обработке данных. Используйте std::variant как безопасную замену union.
Если же вспоминать историю, union позволяет переиспользовать одну и ту же область памяти для хранения разных полей данных. Тип union часто используют в мультимедийных библиотеках. В них разыгрывается вторая фишка union: идентификаторы полей анонимного union попадают во внешнюю область видимости.
Эволюция union
В C++11 вы можете складывать в union типы данных, имеющие собственные конструкторы. Вы можете объявить свой констуктор union. Однако, наличие конструктора ещё не означает корректную инициализацию: в примере ниже поле типа std::string забито нулями и вполне может быть невалидным сразу после конструирования union (на деле это зависит от реализации STL).
В C++17 код мог бы выглядеть иначе, используя variant. Внутри variant использует небезопасные конструкции, которые мало чем отличаются от union, но этот опасный код скрыт внутри сверхнадёжной, хорошо отлаженной и протестированной STL.
Зачем нужен тип enum
Тип enum хорошо использовать везде, где есть состояния. Увы, многие программисты не видят состояний в логике программы и не догадываются применить enum.
Ниже пример кода, где вместо enum используют логически связанные булевы поля. Как думаете, будет ли класс работать корректно, если m_threadShutdown окажется равным true, а m_threadInitialized — false?
Другой пример — магические числа, без которых якобы никак. Пусть у вас есть галерея 4 слайдов, и программист решил захардкодить генерацию контента этих слайдов, чтобы не писать свой фреймворк для галерей слайдов. Появился такой код:
Даже если хардкод слайдов оправдан, ничто не может оправдать магические числа. Их легко заменить на enum, и это по крайней мере повысит читаемость.
Иногда enum используют как набор флагов. Это порождает не очень наглядный код:
Возможно, вам лучше использовать std::bitset :
Иногда программисты записывают константы в виде макросов. Такие макросы легко заменить на enum или constexpr.
Эволюция enum
Кроме того, для enum и scoped enum появилась возможность явно выбрать тип, используемый для представления перечисления в сгенерированном компилятором коде:
В некоторых новых языках, таких как Swift или Rust, тип enum по умолчанию является строгим в преобразованиях типов, а константы вложены в область видимости типа enum. Кроме того, поля enum могут нести дополнительные данные, как в примере ниже
Правила хорошего стиля
Подведём итоги в виде списка правил:
Из таких мелочей строится красота и лаконичность кода в телах функций. Лаконичные функции легко рецензировать на Code Review и легко сопровождать. Из них строятся хорошие классы, а затем и хорошие программные модули. В итоге программисты становятся счастливыми, на их лицах расцветают улыбки.
Структуры (struct) в C#
Объявление структуры
Для того, чтобы объявить переменную типа структуры в C# используется ключевое слово struct :
После ключевого слова struct следует имя структуры и далее, в фигурных скобках — элементы структуры (поля, методы и т.д.). Например, определим структуру, которая описывает точку в трехмерном пространстве:
Создание структуры
Как и в случае с классами, структуры в C# можно создавать с использованием ключевого слова new :
После того, как структура создана, её полям и свойствам можно присваивать значения (см. в предыдущем пункте).
Если структура содержит только публичные поля (не путать со свойствами) и методы, то можно не вызывать конструктор, а сразу назначить значение полей и после этого вызывать методы структуру. Например:
Начиная с версии C# 10 полям структуры можно присваивать значения по умолчанию, однако, в этом случае необходимо будет вызвать new(), чтобы создать экземпляр структуры. Например:
Если мы попытаемся вывести в консоль значения полей вот так:
то получим ошибку «Попытка доступа к неинициализированной переменной». Поэтому, необходимо получать доступ к переменной point с использованием оператора new:
Конструкторы структур
Здесь, опять же, структуры ничем не отличаются от классов C#. У любой структуры есть как минимум один конструктор (конструктор по умолчанию) без параметров. При этом, мы можем создавать свои конструкторы и точно также, как и с классами вызывать их по цепочке, например:
Создаем структуры ( struct )
Начиная с версии C# 10 мы можем также создать для структуры свой конструктор без параметров:
Инициализатор структур struct
Значения полей и свойств структуры, как в случае и с классами, можно задавать непосредственно при создании, используя следующую языковую конструкцию:
то есть вначале мы объявляем переменную, затем вызываем конструктор и затем в фигурных скобках указываем имена полей или свойств и их значения. Даже, если мы создадим и проинициализируем структуру вот так:
то значения полей будут теми, которые мы указываем в инициализаторе, т.е. 24, 45 и 22.
Копирование структур с изменением значений (оператор with)
Начиная с версии C# 10 мы можем копировать значения структур с изменениями, например:
Отличие структуры от класса в C#
Думаю, что после прочтения всего, что было выше, у любого начинающего программировать в C# человека возникнет резонный вопрос: если у структур в C# всё тоже самое, что и у классов. то зачем нам эти структуры нужны и, если всё-таки они нужны, то когда их использовать? Попробуем в кратце разобраться с этим вопросом вместе.
Структура — тип значений, класс — ссылочный тип
Если не вдаваться далеко в подробности работы программ, то основное отличие struct от class заключается в том, что структура храниться целиком в стеке, а объект класса храниться в куче, а ссылка на него — в стеке. В результате этого, доступ к данным структуре будет путь не намного, но быстрее, чем к классу. О том, что такое стек и куча мы ещё поговорим позднее.
Структуры не поддерживают наследование
В отличие от классов C#, наследование структур не поддерживается, то есть вот такой код приведет к ошибке:
Когда использовать структуры (struct), а когда классы (class) в C#
Конечно, вопрос о том, что лучше использовать зависит, в первую очередь, от того в контексте чего задается такой вопрос, но основная рекомендация от Microsoft может быть сформулирована следующим образом: структуры (struct) стоит использовать в том случае, если ваш объект содержит минимальное количество каких-либо логически связанных операций или не содержит их вообще.
Например, использование структур вполне оправдано в примерах выше — описание точки в трехмерном пространстве. Максимум логики, которую мы можем добавить в структуру — это переопределить операторы сложения, вычитания и равенства.
Если же мы пробуем описать с помощью своего типа данных, например, автомобиль, то тут уже логика может быть самая разветвленная: проверка наличия топлива в баке, технические характеристики, оценка состояния в зависимости от каких-либо внешних или внутренних факторов и т.д. Соответственно, в этом случае, более предпочтительным будет использование не структуры, а класса.
Итого
Как и классы в C#, структуры позволяют определить пользовательский тип данных в вашем проекте. При этом, на первый взгляд, структуры (struct) практически ни чем не отличаются от классов, однако различия есть: во-первых, структуры относятся к типам значений, во-вторых, структуры не поддерживают механизмов наследования. Использовать или не использовать структуры — решение самого разработчика, однако, рекомендуется использовать тип struct только в том случае, если ваш пользовательский тип данных содержит минимум бизнес-логики или не содержит её вовсе.
Работа со структурами в C#
Расположение
В большинстве случаев вы можете описать и использовать структуру без знания о том, как она реализована — особенно как расположены в памяти ее поля. Если вы должны создать структуру для использования ее другими приложениями, или сами должны использовать чужую структуру, то в этом случае вопросы памяти становятся важными. Как вы думаете, каков размер следующей структуры?
Разумный ответ — 8 байт, просто сумма размеров всех полей. Однако если вы попытаетесь узнать размер структуры:
Для работы вам понадобится ссылка на InteropServices:
Для ручного расположения полей в памяти используется атрибут StructLayout. Например:
Это заставляет компилятор располагать поля последовательно, в порядке объявления, что и делает по умолчанию. Другими значениями атрибута являются значение Auto, которое позволяет компилятору самому определять порядок размещения полей, и значение Explicit, которое позволяет программисту указать размер каждого поля. Тип Explicit часто используется для последовательного расположения без упаковки, но в большинстве случаев проще использовать параметр Pack. Он сообщает компилятору о том, сколько должно выделяться памяти и как должны быть выровнены данные. Например, если вы укажете Pack=1, тогда структура будет организована таким образом, что каждое поле будет находиться в границах одного байта и может быть считано побайтно — т.е. никакого упаковывания не требуется. Если вы измените объявление структуры:
… то обнаружите, что теперь структура занимает ровно 8 байт, что отвечает последовательному расположению полей в памяти без дополнительных «упаковывающих» байт. Именно таким образом нужно работать с большинством структур, объявленных в Windows API и C/C++. В большинстве случаев вам не придется использовать другие значения параметра Pack. Если вы установите Pack=2, тогда обнаружите, что структура станет занимать 10 байт, потому что будет добавлено по одному байту к каждому однобайтовому полю, чтобы данные могли читаться кусками по 2 байта. Если установить Pack=4, размер структуры увеличится до 12 байт, чтобы структура могла быть прочитана блоками по 4 байта. Дальше значение параметра перестанет учитываться, потому что размер Pack игнорируется, если он равен или превышает выравнивание, использующееся в данном процессоре, и составляющее 8 байт для архитектуры Intel. Расположение структуры в памяти при разных значения Pack показано на рисунке:
Стоит также упомянуть, что может изменить способ упаковки структуры, изменяя порядок полей в ней. Например, при изменении порядка полей на:
… структуре не понадобится упаковка, она и так займет ровно 8 байт.
Если быть точным
Если вам нужно точно указать, сколько памяти будет выделено для каждого поля, используйте тип расположения Explicit. Например:
Так вы получите 8-байтовую структуры без дополнительны выравнивающих байтов. В данном случае это эквивалентно использованию Pack=1. Однако использование Explicit позволяет вам полностью контролировать память. Например:
Эта структура займет 16 байт, вместе с дополнительными байтами после поля b. До версии C# 2.0, тип Explicit использовался в основном для указания буферов с фиксированными размерами при вызове сторонних функций. Вы не можете объявить массив фиксированной длины в структуре, потому что инициализация полей запрещена.
Этот код выдаст ошибку. Если вам нужен массив длиной 10 байт, вот один из способов:
Таким образом, вы оставляете 10 байт для массива. Тут существует ряд интересных нюансов. Первое, почему нужно использовать смещение в 8 байт? Причина в том, что вы не можете начать массив с нечетного адреса. Если вы воспользуетесь смещением в 7 байт, то увидите ошибку времени выполнения, сообщающую о том, что структура не может быть загружена из-за проблем с выравниванием. Это важно, потому что при использовании Explicit вы можете столкнуться с проблемами, если не будете понимать, что вы делаете. Второй момент связан с тем, что в конец структуры добавляются дополнительные байты, чтобы размер структуры был кратен 8 байтам. Компилятор все еще участвует в том, как структура будет размещена в памяти. Конечно, на практике, любая внешняя структура, которую вы попытаетесь конвертировать в структуру C#, должна быть корректна выровнена.
Наконец, стоит упомянуть, что вы не можете обратиться к 10-байтовому массиву, используя имя массива (например, buffer[1]), потому что C# думает, что массиву не назначено значение. Поэтому если вы не можете использовать массив и это вызывает проблему с выравниванием, гораздо лучше объявить структуру так:
Для доступа к массиву придется воспользоваться арифметикой на указателях, что является unsafe кодом. Чтобы под структуру было выделено фиксированное количество байт, используйте параметр Size в атрибуте StructLayout:
Сейчас в C# 2.0 массивы фиксированного размера разрешены, поэтому все вышеприведенные конструкции в общем-то необязательны. Стоит заметить, что массивы фиксированной длины используют тот же механизм: выделение фиксированного числа байт и указатели (что тоже является небезопасным). Если вам нужно использовать массивы для вызова функций из библиотек, возможно, лучшим способом будет явный маршалинг массивов, который считается «безопасным». Давайте рассмотрим все три упомянутых способа.
Вызовы API
В качестве примера структуры, которая требует выравнивания, мы можем использовать функцию EnumDisplayDevices, которая определена следующим образом:
Это довольно просто конвертируется в C#:
Структура DISPLAY_DEVICE определена так:
Понятно, что она содержит четыре символьных массива с фиксированной длиной. Используя тип выравнивания Explicit, перепишем структуру в C#:
Обратите внимание на использования параметра Size для указания места, необходимого для хранения поля DeviceKey. Теперь если использовать эту структуру при вызове функции:
… то все, к чему вы можете обратиться напрямую — это первые символы массивов. Например, DeviceString содержит первый символ строки информации об устройстве. Если вы хотите получить остальные символы из массива, нужно получить указатель на DeviceString и использовать арифметику на указателях, чтобы пройти по массиву.
При использовании C# 2.0 самым простым решением является использовать в структуре массивы:
Обратите внимание, что структура должна быть помечена модификатором unsafe. Теперь после API вызова мы можем получить данные из массивов без использования указателей. Впрочем, неявно они все-таки используются, и любой код, обращающийся к массивам, должен быть помечен как небезопасный.
Третий и последний метод заключается в кастомном маршалинге. Многие C# программисты не понимают, что суть маршалинга заключается не только в том, как данные о типах передаются в библиотечные вызовы, — это еще и активный процесс, который копирует и изменяет управляемые данные. Например, если вы захотите передать ссылку на типизированный массив, вы можете передать его по значению, и система сконвертирует его в массив фиксированной длины и обратно в управляемый массив без дополнительных действий с вашей стороны.
В этом случае все, что нам остается сделать, это добавить атрибут MarshalAs, указывающий типа и размер массивов:
В этом случае при вызове библиотечной функции поля передаются путем создания неуправляемых массивов нужной длины внутри копии структуры, которая и передается в вызов. Когда функция завершает свою работу, неуправляемые массива конвертируются в управляемые символьные массивы и ссылки на них присваиваются полям структуры. В результате, после вызовы функции вы обнаружите, что структура содержит массива нужного размера, заполненные данными.
В случае вызова функций сторонных библиотек, использование кастомного маршалинга является лучшим решением, поскольку при этом используется безопасный код. Хотя вызов сторонних функций с помощью p/Invoke и не является безопасным в общем смысле.
Сериализация структур
Теперь, после того как мы рассмотрели довольно сложные вопросы, связанные с размещением структур в памяти, самое время узнать, как получить все байты, составляющие структуру. Иными словами, как сериализовать структуру? Существует много способов сделать это, чаще всего используется метод Marshal.AllocHGlobal для выделения памяти в куче под неуправляемый массив. После этого все делается функциями, работающими с памятью, такими как StructToPtr или Copy. Пример:
Фактически, надобность в стольких действиях отсутствует, проще переместить байты структуры напрямую в байтовый массив без использования промежуточного буфера. Ключевым объектом в этом способе является GCHandle. Он возвратит хэндл Garbage Collector’а, и вы можете использовать метод AddrOfPinnedObject для получения стартового адреса структуры. Метод RawSerialize может быть переписан следующим образом:
Этот способ проще и быстрее. Вы можете использовать эти же методы для десериализации данных из байтового массива в структуру, однако более полезно будет рассмотреть решение проблемы чтения структуры из потока.
Чтение структур из потоков
Иногда возникает потребность зачитать структуру, возможно написанную на другом языке, в C# структуру. Например, вам нужно прочитать bitmap-файл, который начинается с заголовка файла, затем следует заголовок битмапа и затем собственно битовые данные. Структура заголовка файла выглядит так:
Функция, которая будет читать любой поток, и возвращать структуру, может быть написана без использования обобщений:
Для передачи данных здесь используется GCHandle. Новое в этом коде — использование параметра, указывающего на тип структуры. К сожалению, нельзя использовать этот тип для возвращаемого значения, поэтому после вызова функции необходимо преобразовать ее результат:
Если мы хотим избежать преобразования, тогда нужно использовать обобщенный метод:
Обратите внимание, что теперь мы должны преобразовать объект, возвращаемый методом PtrToStructure, в самом методе, а не в месте вызова, который теперь выглядит следующим образом:
Приятно наблюдать, насколько лучше выглядит использование обобщенного метода.
Ручной маршалинг
Маршалинг так хорошо работает в подавляющем количестве случаев, что можно вообще забыть о его существовании. Однако если вы сталкиваетесь с чем-то необычным, вы можете удивиться, что происходит, когда маршалинг перестает работать. Например, некоторым API вызовам нужно передавать указатель на указатель на структуру. Вы уже знаете, как передать указатель на структуру — это просто передача по ссылке — и поэтому вам может показаться, что передать указатель на указатель тоже просто. Однако все сложнее, чем вы ожидаете. Давайте посмотрим.
В функции AVIFileCreateStream два последних параметра передаются как указатели на IntPtr и структуру соответственно:
Для вызова это функции вы бы написали:
Основываясь на предыдущих примерах, кажется более легким изменить передачу указателя на структуру самим указателем. Казалось бы, что может быть неверного в следующем объявлении:
Однако если вы попытаетесь передать адрес закрепленной (pinned) структуры:
Причина этой ошибки заключается в том, что хотя вы и передаете указатель на адрес начала структуры, эта структура располагается в управляемой памяти, а неуправляемый код не может получить к ней доступ. Мы забываем о том, что стандартный маршалинг делает еще кое-какую работу при создании указателей. Перед тем, как создать указатели, для всех параметров, передаваемых по ссылке, создаются полные копии в неуправляемой памяти. После окончания вызова данные из неуправляемой памяти копируются обратно в управляемую.
Написать подобную функцию, которая делает работу маршалинга, нетрудно и очевидно полезно:
Тут просто возвращается IntPtr на область в куче, которая содержит копию данных. Единственный неприятный момент заключается в том, что нужно помнить об освобождении выделенной памяти:
Приведенный код работает в точности как стадартный маршалинг. Однако не забудьте, что lpstruct передается по значению как integer. Для того чтобы скопировать результат обратно в структуру, понадобится еще один метод:
Теперь, после того как мы реализовали ручной маршалинг указателя в структуру, нам нужно получить указатель на указатель на структуру. К счастью, нам не потребуется писать нового кода, потому что наша функция преобразования структуры в указатель может преобразовывать любой тип данных в неуправляемый указатель — включая и сам указатель.
В качестве примера возьмем функцию AVISaveOption, т.к. она принимает два указателя на указатель в качестве параметров:
Фактически параметр ppavi — это указатель на хэндл (который в свою очередь является указателем), а ppOptions — указатель на указатель на структуру. Для вызова этого метода нам понадобится структура:
Определение этой структуры можно посмотреть в документации по стандарту AVI. На следующем шаге нам нужно получить маршализованный указатель на структуру:
… а затем указатель на указатель:
… а за ним указатель на хэндл:
Теперь вызов функции:
… где остальные параметры не представляют интереса, сведения о них могут быть найдены в документации.
После вызова функции все, что остается сделать, это переправить данные из неуправляемого буфера в структуру:
Обратите внимание, нужно использовать указатель на саму структуру, а не указатель на указатель! Ну и в конце освобождаем память:
Все это может показаться сложным. Использование указателей на указатели не является простой вещью, именно поэтому C# требует, чтобы код, работающий с указателями был помечен как небезопасный (unsafe).
С другой стороны, общие принципы работы довольно просты. Когда вы передаете что-то по ссылке, это содержимое копируется в неуправляемую память, и адрес на новый участок памяти передается в вызов функции.
Обычно стандартный маршалинг берет всю работу на себя. Однако если вам нужно что-то сверх этого, вы можете управлять всем копированием вручную.
Структуры
Введение
Мир вокруг можно моделировать различными способами. Самым естественным из них является представление о нём, как о наборе объектов. У каждого объекта есть свои свойства. Например, для человека это возраст, пол, рост, вес и т.д. Для велосипеда – тип, размер колёс, вес, материал, изготовитель и пр. Для товара в магазине – идентификационный номер, название, группа, вес, цена, скидка и т.д.
У классов объектов набор этих свойств одинаковый: все собаки могут быть описаны, с той или иной точностью, одинаковым набором свойств, но значения этих свойств будут разные.
Все самолёты обладают набором общих свойств в пределах одного класса. Если же нам надо более точное описание, то можно выделить подклассы: самолёт амфибии, боевые истребители, пассажирские лайнеры – и в пределах уже этих классов описывать объекты. Например, нам необходимо хранить информацию о сотрудниках компании. Каждый сотрудник, в общем, обладает большим количеством разных свойств. Мы выберем только те, которые нас интересуют для решения прикладной задачи: пол, имя, фамилия, возраст, идентификационный номер. Для работы с таким объектом нам необходима конструкция, которая бы могла агрегировать различные типы данных под одним именем. Для этих целей в си используются структуры.
Объявление структуры
Синтаксис объявления структуры
Полями структуры могут быть любые объявленные типы, кроме самой структуры этого же типа, но можно хранить указатель на структуру этого типа:
В том случае, если несколько полей имеют один тип, то их можно перечислить через запятую:
После того, как мы объявили структуру, можно создавать переменную такого типа с использованием служебного слова struct. Доступ до полей структуры осуществляется с помощью операции точка:
Структура, объявленная в глобальном контексте, видна всем. Структура также может быть объявлена внутри функции:
Можно упростить пример: синтаксис языка позволяет создавать экземпляры структуры сразу же после определения:
Структура также может быть анонимной. Тогда мы не сможем использовать имя структуры в дальнейшем.
В этом примере мы создали переменную A. Она является структурой с двумя полями.
Начальная инициализация структур
Структуру можно инициализировать во время создания как массив. Поля в этом случае будут присваиваться по порядку.
Замечание: таким образом можно только иницализировать структуру. Присваивать значение всей структуре таким образом нельзя.
Современный стандарт си позволяет инициализировать поля структуры по имени. Для этого используется следующий синтакис:
Определение нового типа
Когда мы определяем новую структуру с помощью служебного слова struct, в пространстве имён структур (оно не имеет ничего общего с пространствами имён С++) создаётся новый идентификатор. Для доступа к нему необходимо использовать служебное слово struct. Можно определить новый тип с помощью служебного слова typedef. Тогда будет создан псевдоним для нашей структуры, видимый в глобальном контексте.
Теперь при работе с типом Point нет необходимости каждый раз писать слово struct. Два объявления можно объединить в одно
Замечание. Если мы создаём новый тип-структуру, полем которого является указатель на этот же тип, то его необходимо объявлять явно с использованием служебного слова struct
Указатели на структуру
Обратите внимание на удаление массива структур: при удалении экземпляра структуры он не удаляет своих полей самостоятельно, поэтому необходимо сначала удалять поля, после этого удалять сам массив.
При вызове функции jsonUser мы передаём указатель на экземпляр структуры, поэтому внутри функции доступ до полей осуществляется с помощью оператора стрелка.
Устройство структуры в памяти
Первая структура должна иметь размер 6 байт, вторая 8 байт, третья 7 байт, однако на 32-разрядной машине компилятор VC сделает их все три равными 8 байт. Стандарт гарантирует, что поля расположены друг за другом, но не гарантирует, что непрерывно.
Есть возможность изменить упаковку структур в памяти. Можно явно указать компилятору каким образом производить упаковку полей структуры, объединений или полей класса. Каким образом это делать, зависит от компилятора. Один из самых распространённых способов прагма pack()
У неё есть несколько разновидностей, рассмотрим только одну. pragma pack(n) указывает значение в байтах, используемое для упаковки. Если параметр компилятора не заданы для модуля значения по умолчанию n 8. Допустимыми значениями являются 1, 2, 4, 8 и 16. Выравнивание поля происходит по адресу, кратному n или сумме нескольких полей объекта, в зависимости от того, какая из этих величин меньше.
Использование #pragma pack не приветствуется: логика работы программы не должна зависить от внутреннего представления структуры (если, конечно, вы не занимаетесь системным программированием или ломаете чужие программы и сети).
Приведение типов
Стандартом поведение при приведении одной структуры к другой не определено. Это значит, что даже если структуры имеют одинаковые поля, то нельзя явно кастовать одну структуру до другой.
Этот пример работает, но это хак, которого необходимо избегать. Правильно писать так
Привести массив к структуре (или любому другому типу) по стандарту также невозможно (хотя в различных компиляторах есть для этого инструменты).
Но в си возможно всё.
Но запомните, что в данном случае поведение не определено.
Вложенные структуры
Структура сама может являться полем структуры. Пример: структура Model – модель автомобиля, имеет название, номер, год выпуска и поле Make, которое в свою очередь хранит номер марки и её название.
Вложенные структуры инициализируются как многомерные массивы. В предыдущем примере можно произвести начальную инициализацию следующим образом:
P.S. подобным образом инициализировать строки не стоит, здесь так сделано только для того, чтобы упростить код.
Указатели на поля структуры и на вложенные структуры
Указатели на поля структуры определяются также, как и обычные указатели. Указатели на вложенные структуры возможны только тогда, когда структура определена. Немного переделаем предыдущий пример: «деанонимизируем» вложенную безымянную структуру и возьмём указатели на поля структуры Model:
Как уже говорилось ранее, в си, даже если у двух структур совпадают поля, но структуры имеют разные имена, то их нельзя приводить к одному типу. Поэтому приходится избавляться от анонимных вложенных структур, если на них нужно взять указатель. Можно попытаться взять указатель типа char* на поле структуры, но нет гарантии, что поля будут расположены непрерывно.
Примеры
1. Стек, реализованный с помощью структуры «Узел», которая хранит значение (в нашем примере типа int) и указатель на следующий узел. Это неэффективная реализация, которая требует удаления и выделения памяти под узел при каждом вызове операции push и pop.
3. Структура Линия, состоит из двух структур точек. Для краткости реализуем только пару операций
Обратите внимание на операции создания и копирования линии. Обязательно нужно копировать содержимое, иначе при изменении или удалении объектов, которые мы получили в качестве аргументов, наша линия также изменится. Если структура содержит другие структуры в качестве полей, то необходимо проводить копирование содержимого всех полей. Глубокое копирование позволяет избежать неявных зависимостей.
4. Структура комплексное число и функции для работы с ней.