что такое typedef struct в си
Объявления Typedef
Объявление typedef — это объявление с typedef в качестве класса хранения. Декларатор становится новым типом. Объявления typedef можно использовать для создания более коротких или более понятных имен для типов, уже определенных в языке C или объявленных пользователем. Имена typedef позволяют инкапсулировать детали реализации, которые могут измениться.
Объявление typedef интерпретируется точно так же, как и объявление переменной или функции, но идентификатор становится синонимом типа, а не принимает тип, указанный в объявлении.
Синтаксис
declaration:
declaration-specifiers init-declarator-listopt;
declaration-specifiers:
storage-class-specifier declaration-specifiersopt
type-specifier declaration-specifiersopt
type-qualifier declaration-specifiersopt
storage-class-specifier:
typedef
type-specifier:
void
char
short
int
long
float
double
signed
unsigned
struct-or-union-specifier
enum-specifier
typedef-name
Имена Typedef используют то же пространство имен, что и обычные идентификаторы (дополнительные сведения см. в статье Пространства имен). Поэтому в программе может присутствовать имя typedef и идентификатор с тем же именем в локальной области. Пример:
При объявлении в локальной области идентификатора с тем же именем, что и имя typedef, или при объявлении члена структуры либо объединения в той же области или во внутренней области обязательно должен указываться спецификатор типа. Следующий пример иллюстрирует это ограничение:
Чтобы повторно использовать имя FlagType для идентификатора, члена структуры или члена объединения, необходимо указать тип:
поскольку FlagType воспринимается как часть типа, а не как заново объявляемый идентификатор. Это объявление недопустимо, как и
С помощью typedef можно объявить любой тип, включая типы указателей, функций и массивов. Имя typedef для типа указателя на структуру или объединение можно объявить до определения типа структуры или объединения, если только определение находится в той же области видимости, что и объявление.
Имена typedef можно использовать, чтобы сделать код более понятным. Все три следующих объявления signal задают один и тот же тип, причем в первом объявлении имена typedef не используются.
Примеры
В следующих примерах показаны объявления typedef:
В этом примере задан тип DRAWF для функции, не возвращающей никакого значения и принимающей два аргумента int. Это означает, например, что объявление
Структуры
Определение структур
Структура в языке программирования Си представляет собой производный тип данных, который объединяет в единое целое множество компонентов. При этом в отличие от массива эти компоненты могут представлять различные типы данных.
Имя_структуры представляет произвольный идентификатор, к которому применяются те же правила, что и при наименовании переменных.
Следует отметить, что в отличие от функции при определении структуры после закрывающей фигурной скобки идет точка с запятой.
Например, определим простейшую структуру:
Все элементы структуры объявляются как обычные переменные. Но в отличие от переменных при определении элементов структуры для них не выделяется память, и их нельзя инициализировать. По сути мы просто определяем новый тип данных.
Здесь определена переменная tom, которая представляет структуру person. И при каждом определении переменной типа структуры ей будет выделяться память, необходимая для хранения ее элементов.
При определении переменной структуры ее можно сразу инициализировать, присвоив какое-нибудь значение:
Теперь объединим все вместе в рамках программы:
Консольный вывод программы:
С элементами структуры можно производить все те же операции, что и с переменными тех же типов. Например, добавим ввод с консоли:
Консольный вывод программы:
Мы можем одновременно совмещать определение типа структуры и ее переменных:
После определения структуры, но до точки запятой мы можем через запятую перечислить набор переменных. А затем присвоить их элементам значения.
При подобном определении мы можем даже не указывать имя структуры:
В этом случае компилятор все равно будет знать, что переменные tom, bob и alice представляют структуры с двумя элементами name и age. И соответственно мы также с этими переменными сможем работать. Другое дело, что мы не сможем задать новые переменные этой структуры в других местах программы.
typedef
Еще один способ определения структуры представляет ключевое слово typedef :
Директива define
Еще один способ определить структуру представляет применение препроцессорной директивы #define :
В данном случае директива define определяет константу PERSON, вместо которой при обработке исходного кода препроцессором будет вставляться код структуры struct
Псевдонимы и определения типов (C++)
Объявление псевдонима можно использовать для объявления имени, которое будет использоваться в качестве синонима для ранее объявленного типа. (Этот механизм также называется псевдонимом типа). Этот механизм также можно использовать для создания шаблона псевдонима, который может быть особенно полезен для пользовательских распределительов.
Синтаксис
Примечания
identifier
Имя псевдонима.
type
Идентификатор типа, для которого создается псевдоним.
Псевдоним не вводит в программу новый тип и не может менять значение существующего имени типа.
Простейшая форма псевдонима эквивалентна typedef механизму из c++ 03:
Псевдонимы также работают с указателями на функции, но гораздо удобнее для чтения, чем эквивалентное определение типа:
Ограничением typedef механизма является то, что оно не работает с шаблонами. Напротив, синтаксис псевдонима типа в C ++11 позволяет создавать шаблоны псевдонимов:
Пример
Определения типов
Объявления typedef можно использовать для создания более коротких или более понятных имен для типов, уже определенных в языке или объявленных пользователем. Имена typedef позволяют инкапсулировать детали реализации, которые могут измениться.
При объявлении в локальной области идентификатора с тем же именем, что и имя typedef, или при объявлении члена структуры либо объединения в той же области или во внутренней области обязательно должен указываться спецификатор типа. Пример:
Чтобы повторно использовать имя FlagType для идентификатора, члена структуры или члена объединения, необходимо указать тип:
поскольку FlagType воспринимается как часть типа, а не как заново объявляемый идентификатор. Это объявление недопустимо, как и
С помощью typedef можно объявить любой тип, включая типы указателей, функций и массивов. Имя typedef для типа указателя на структуру или объединение можно объявить до определения типа структуры или объединения, если только определение находится в той же области видимости, что и объявление.
Примеры
Использование typedef объявлений состоит в том, чтобы сделать объявления более однородными и компактными. Пример:
Чтобы использовать typedef для указания фундаментальных и производных типов в одном объявлении, можно разделить деклараторы запятыми. Пример:
В следующем примере задан тип DRAWF для функции, не возвращающей никакого значения и принимающей два аргумента int.
После оператора выше typedef объявление
будет эквивалентно следующему:
typedef часто объединяется с struct для объявления и именования определяемых пользователем типов:
Повторное объявление определений типов
typedef Объявление можно использовать для повторного объявления того же имени для ссылки на один и тот же тип. Пример:
typedef Невозможно переопределить имя, которое ранее было объявлено как другой тип. Таким образом, если file2. H содержит
компилятор выдает ошибку из-за попытки повторного объявления имени CHAR как имени другого типа. Это правило распространяется также на конструкции, подобные следующим:
определения типов в C++ и C
Преимущество такого объявления заключает в том, что можно выполнять объявления
В C++ разница между typedef именами и реальными типами (объявленными с class struct union enum ключевыми словами,, и) более Разна. Хотя методика C объявления структуры без имени в typedef инструкции по-прежнему работает, она не предоставляет преимуществ для нотаций, как это делается в c.
Имя (синоним) не может использоваться после class struct union префикса, или.
Имя не может использоваться в качестве имени конструктора или деструктора в объявлении класса.
Таким образом, этот синтаксис не предоставляет механизм наследования, создания или удаления.
Структуры
Введение
Мир вокруг можно моделировать различными способами. Самым естественным из них является представление о нём, как о наборе объектов. У каждого объекта есть свои свойства. Например, для человека это возраст, пол, рост, вес и т.д. Для велосипеда – тип, размер колёс, вес, материал, изготовитель и пр. Для товара в магазине – идентификационный номер, название, группа, вес, цена, скидка и т.д.
У классов объектов набор этих свойств одинаковый: все собаки могут быть описаны, с той или иной точностью, одинаковым набором свойств, но значения этих свойств будут разные.
Все самолёты обладают набором общих свойств в пределах одного класса. Если же нам надо более точное описание, то можно выделить подклассы: самолёт амфибии, боевые истребители, пассажирские лайнеры – и в пределах уже этих классов описывать объекты. Например, нам необходимо хранить информацию о сотрудниках компании. Каждый сотрудник, в общем, обладает большим количеством разных свойств. Мы выберем только те, которые нас интересуют для решения прикладной задачи: пол, имя, фамилия, возраст, идентификационный номер. Для работы с таким объектом нам необходима конструкция, которая бы могла агрегировать различные типы данных под одним именем. Для этих целей в си используются структуры.
Объявление структуры
Синтаксис объявления структуры
Полями структуры могут быть любые объявленные типы, кроме самой структуры этого же типа, но можно хранить указатель на структуру этого типа:
В том случае, если несколько полей имеют один тип, то их можно перечислить через запятую:
После того, как мы объявили структуру, можно создавать переменную такого типа с использованием служебного слова 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. Структура комплексное число и функции для работы с ней.
Что такое typedef struct в си
Давайте попробуем разобраться, в чем разница между двумя определениями структур (далее перевод [1]):
С точки зрения объявления переменных Foo на языке C++ нет разницы, как определить Foo. После того, как Foo была определена либо через struct, либо через typedef struct, можно объявлять переменные Foo так:
Однако между struct и typedef struct различие есть, и оно тонкое. Дело в том, что struct определяет новый тип. В отличие от этого typedef struct никакого типа не определяет, он только создает ссылку (alias) с именем Foo (ни в коем случае не новый тип) на неименованный тип struct.
Спецификатор typedef. Имя, заданное с участием спецификатора typedef, становится специальным именем. В области действия этой декларации typedef-имя синтаксически эквивалентно ключевому слову и именам типа, связанного с идентификатором. Таким образом, typedef-name является синонимом другого типа. Так что typedef-имя НЕ СОЗДАЕТ НОВЫЙ ТИП, как это делается при декларации класса или enum.
Если декларация определяет неименованный класс (или перечисление enum), первое typedef-имя, заданное в декларации, типа класса (или типа enum) используется для обозначения типа класса (или типа enum) только для целей линковки. Пример:
Итак, typedef ВСЕГДА используется как контейнер/синоним для другого типа.
Если хотите, то можете представить себе, что C++ генерирует typedef для каждого имени тега:
К сожалению, это не точно соответствует действительности. Хотелось бы, чтобы все было так просто, но это не так. C++ не может генерировать такие typedef для struct, union или enum без введения несовместимости с языком C. Например, программа C декларирует и функцию, и структуру под одним и тем же именем status:
Само собой, это плохая практика, и нельзя никому советовать так делать, но это C, так сделать можно. В этой программе status (сам по себе) относится к функции; struct status относится к типу.
Если C++ автоматически генерирует typedef-ы для тегов, то когда Вы скомпилируете эту программу на как C++, компилятор сгенерирует код:
К сожалению, это имя будет конфликтовать с именем функции, и программа не скомпилируется. Вот почему C++ не может просто генерировать typedef для каждого тега.
В C++ действие тегов точно такое же, как и typedef-имен, за исключением того, что программа может объявить объект, функцию или энумератор с тем же именем и той же областью действия, как у тега. В этом случае объект, функция или энумератор скрывают имя тега. Программа может обратиться к имени тега только через использование ключевых слов class, struct, union или enum (какое из них подойдет) перед именем тега. Имя типа, состоящее их одного из этих ключевых слов, за которым идет тег, является конкретизированным спецификатором типа (elaborated-type-specifier [3]). Например, struct status и enum month как раз являются такими elaborated-type-specifier.
Таким образом, программа, которая содержит оба определения:
становится такой же, когда компилируется как C++. Только имя status относится к функции. Программа может сослаться на тип status только при использовании к типу только при помощи конкретизированным спецификатором типа, т. е. struct status.
К каким ошибкам это может привести в программе? Вот пример кода:
Здесь программа определяет класс foo с конструктором по умолчанию, и оператор конверсии преобразует объект foo в char const *. Выражение
в основном должно создать объект foo, и применить оператор конверсии. Следующий оператор вывода
должен отобразить класс foo, но этого не произойдет. Оператор отобразит функцию foo.
Этот неожиданный результат произошел потому, что программа подключила заголовок lib.h:
Этот заголовок определяет функцию, которая так же носит имя foo. Имя функции foo скрывает имя класса foo, так что обычное обращение к foo относится к функции, не к классу. Именно к классу можно обратиться только через elaborated-type-specifier:
Чтобы избежать подобного беспорядка в программе, нужно добавить следующий typedef для имени класса foo сразу перед или после определения класса:
Этот typedef приводит к конфликту между именем класса foo и именем функции foo (из библиотеки), и это вызовет ошибку при компиляции.
Применение typedef всегда требует от программиста дисциплины. Поскольку ошибки, подобные той что описана в листинге 1, встречаются довольно редко, то Вы вероятно никогда не сталкивались с подобной проблемой. Но если ошибка в Вашей программе может привести к серьезным последствиям, то Вы должны применить typedef независимо от малой вероятности возникновения ошибки.
Невозможно представить себе, зачем могло понадобиться скрыть имя класса именем функции или объекта, когда они находятся в одной области видимости. Так что скрывающие правила языка C были ошибкой, и они не должны быть расширены на классы в C++. Конечно же, Вы можете найти и исправить ошибку, но это требует дополнительной дисциплины в программировании и усилий, которых можно было бы избежать.
Итак, в C++ все декларации struct/union/enum/class действуют точно так же как если бы они были неявно объявлены через typedef, пока имя не скрыто другой декларацией с таким же именем.
Таким образом, тонкие отличия есть только в C++, и это пережиток от языка C, где применение struct и typedef struct имеет значение. На языке C есть два разных пространств имен типов: пространство имен тегов struct/union/enum, и пространство имен typedef. Если Вы просто напишете в программе:
то получите ошибку компиляции, потому что Foo определен только в пространстве имен тегов. Вы должны декларировать переменную x так:
В любой момент, когда Вы хотите обратиться к Foo, Вы всегда должны вызывать struct Foo. Это быстро раздражает, тогда Вы можете добавить typedef:
Теперь оба выражения, и struct Foo (в пространстве имен тегов) и просто Foo (в пространстве имен typedef), относятся к одному и тому же, и Вы можете свободно декларировать объекты с типом Foo без ключевого слова struct.
Является просто объединением декларации структуры и typedef.
И, наконец, конструкция
декларирует безымянную структуру, и создает для нее typedef. Так что для такой конструкции у Вас нет имени Foo в пространстве имен тегов, имя Foo есть только в пространстве имен typedef. Это означает, что предварительное декларирование невозможно. Если Вы хотите применить предварительное декларирование, то нужно задать имя для пространства имен тегов.