Typedef struct c что это
Typedef struct c что это
Давайте попробуем разобраться, в чем разница между двумя определениями структур (далее перевод [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. Это означает, что предварительное декларирование невозможно. Если Вы хотите применить предварительное декларирование, то нужно задать имя для пространства имен тегов.
Сложные типы данных в Си
Структура — это объединение нескольких объектов, возможно, различного типа под одним именем, которое является типом структуры. В качестве объектов могут выступать переменные, массивы, указатели и другие структуры.
Структуры позволяют трактовать группу связанных между собой объектов не как множество отдельных элементов, а как единое целое. Структура представляет собой сложный тип данных, составленный из простых типов.
Общая форма объявления структуры:
После закрывающей фигурной скобки > в объявлении структуры обязательно ставится точка с запятой.
Пример объявления структуры
В указанном примере структура date занимает в памяти 12 байт. Кроме того, указатель *month при инициализации будет началом текстовой строки с названием месяца, размещенной в памяти.
При объявлении структур, их разрешается вкладывать одну в другую.
Инициализация полей структуры
Инициализация полей структуры может осуществляться двумя способами:
В первом способе инициализация осуществляется по следующей форме:
Имя элемента структуры является составным. Для обращения к элементу структуры нужно указать имя структуры и имя самого элемента. Они разделяются точкой:
Второй способ инициализации объектов языка Си с использованием функций ввода-вывода.
Объединения
Объединениями называют сложный тип данных, позволяющий размещать в одном и том же месте оперативной памяти данные различных типов.
Размер оперативной памяти, требуемый для хранения объединений, определяется размером памяти, необходимым для размещения данных того типа, который требует максимального количества байт.
Когда используется элемент меньшей длины, чем наиболее длинный элемент объединения, то этот элемент использует только часть отведенной памяти. Все элементы объединения хранятся в одной и той же области памяти, начиная с одного адреса.
Общая форма объявления объединения
Объединения применяются для следующих целей:
Например, удобно использовать объединения, когда необходимо вещественное число типа float представить в виде совокупности байтов
Результат выполнения:
Пример Поменять местами два младших байта во введенном числе
Результат выполнения
Битовые поля
Используя структуры, можно упаковать целочисленные компоненты еще более плотно, чем это было сделано с использованием массива.
Набор разрядов целого числа можно разбить на битовые поля, каждое из которых выделяется для определенной переменной. При работе с битовыми полями количество битов, выделяемое для хранения каждого поля отделяется от имени двоеточием
При работе с битовыми полями нужно внимательно следить за тем, чтобы значение переменной не потребовало памяти больше, чем под неё выделено.
Пример Разработать программу, осуществляющую упаковку даты в формат
Результат выполнения
Массивы структур
Работа с массивами структур аналогична работе со статическими массивами других типов данных.
Пример Библиотека из 3 книг
Результат выполнения
Указатели на структуры
Доступ к элементам структуры или объединения можно осуществить с помощью указателей. Для этого необходимо инициализировать указатель адресом структуры или объединения.
Для организации работы с массивом можно использовать указатель. При этом обращение к полям структуры через указатель будет выглядеть как:
указатель — указатель на структуру или объединение;
поле — поле структуры или объединения;
Динамическое выделение памяти для структур
Пример Библиотека из 3 книг
Комментариев к записи: 34
# if ndef department_h
#define department_h
#include
struct Office
<
char title[50];
struct <
int amount; // количество сотрудников
char lastname[50]; // фамилия начальника
> inc;
struct <
int year;
int month;
> date;
#include //подключаем основную библиотеку
#include
#include //подключаем библиотеку математики
#include //подключаем библиотеку языков
#include
#include «department.h»
#define N 0
int small( struct Office* a, int n, int year, int month)
<
int i = 0, index = 0, min = 0;
while ((a[i].date.year > year) || (a[i].date.month > month) && (a[i].date.year == year)) //сравниваем дату создания с датой, введенной нами
<
i++;
>
index = i;
min = a[i].inc.amount;
for (i; i //находим самый маленький отдел и сравниваем дату с другими датами создания отдела
<
if ((a[i].inc.amount return index;
>
int main()
<
setlocale(LC_ALL, «Rus» ); //включение локализации
setlocale(LC_NUMERIC, «Eng» ); //использование «.» в дробных значениях
int n = 0, year = 0, month = 0;
n = sizeof (A) / sizeof (A[0]); //размер (в байтах) всего массива, то есть сумма всех элементов/ размер (в байтах) одной структуры (50)
for ( int i = 0; i # else
struct Office A[19];
create(A, &n);
for ( int i = 0; i #endif
СОДЕРЖАНИЕ
Синтаксис
Синтаксис объявления typedef:
Имя нового псевдонима типа следует тому же синтаксису, что и объявление любого другого идентификатора C, поэтому в более подробной форме:
typedef идентификатор определения типа
Примеры
Использование документации
Объявление typedef может использоваться в качестве документации, указывая значение переменной в контексте программирования, например, оно может включать выражение единицы измерения или количества. Общие объявления,
может быть выражено объявлением типов, зависящих от контекста:
Упрощение типов
Объявление typedef устраняет необходимость указания struct в C. Например, объявление
Объявление структуры и typedef также можно объединить в один оператор:
Или это можно использовать следующим образом:
Здесь и C, и C ++ нуждаются в struct ключевом слове в определении параметра.
Указатели
Typedef может использоваться для определения нового типа указателя.
Использование typedef для определения нового типа указателя иногда может привести к путанице. Например:
Структуры и указатели структур
Используя typedef, приведенный выше код можно переписать следующим образом:
Указатели на функции
Предыдущий код можно переписать со спецификациями typedef:
Объявление функции выше является загадочным, поскольку оно не ясно показывает, что функция принимает в качестве аргументов или тип, который она возвращает. Начинающий программист может даже предположить, что функция принимает единственный int аргумент и ничего не возвращает, но на самом деле ей также нужен указатель на функцию и она возвращает другой указатель на функцию. Более чисто можно написать:
Массивы
Typedef также можно использовать для упрощения определения типов массивов. Например,
Приведение типов
funcptr используется в левой части для объявления переменной и используется в правой части для приведения значения. Таким образом, typedef может использоваться программистами, которые не хотят выяснять, как преобразовать синтаксис определения в синтаксис приведения типов.
Без typedef, как правило, невозможно взаимозаменяемо использовать синтаксис определения и синтаксис приведения. Например:
Использование в C ++
В C ++ имена типов могут быть сложными, а typedef предоставляет механизм для присвоения типу простого имени.
Использовать с шаблонами
C ++ 03 не предоставляет шаблонных определений типов. Например, чтобы stringpair представлять std::pair для каждого типа T один не может использовать:
Другие языки
В SystemVerilog typedef ведет себя точно так же, как в C и C ++.
В этом примере синоним типа определен PairOfInts как целочисленный тип.
В Seed7 определение постоянного типа используется для введения синонима типа:
В Swift typealias ключевое слово используется для создания typedef:
C # содержит функцию, аналогичную typedef или using синтаксису C ++.
В D ключевое слово alias позволяет создавать синонимы типа или частичного типа.
Проблемы использования
Керниган и Ричи указали две причины использования typedef. Во-первых, он предоставляет средства, позволяющие сделать программу более переносимой или более простой в обслуживании. Вместо того, чтобы изменять тип при каждом появлении в исходных файлах программы, нужно изменить только один оператор typedef. size_t и ptrdiff_t в являются такими именами typedef. Во-вторых, определение типа может облегчить понимание сложного определения или объявления.
Динамическая типизация C
Преамбула
Эта статья была написана и опубликована мной на своем сайте более десяти лет назад, сам сайт с тех пор канул в лету, а я так и не начал писать что-то более вразумительное в плане статей. Все ниже описанное является результатом исследования C как языка двадцатилетним парнем, а, следовательно, не претендует на звание учебного пособия, несмотря на стиль изложения. Тем не менее, я искренне надеюсь, что она побудит молодых разработчиков погрузиться в эксперименты с C также, как когда-то делал это я.
Предупреждение
Эта короткая статья, окажется абсолютно бесполезной для опытных программистов C/C++, но кому-то из начинающих, возможно, позволит сэкономить время. Хочу подчеркнуть, что в большинстве хороших книг по C/C++ данная тема рассмотрена в достаточной степени.
Динамическая и статическая типизация
Здесь мы поговорим про язык C, хотя все, что описано ниже, применимо и к C++.
Магия указателя пустоты
Вывод:
Первый пример не нес никакой полезной нагрузки. Попробуем ее поискать во втором примере:
Вывод:
Здесь мы создали, можно сказать, универсальную функцию для округления как целых чисел (которым оно не требуется, конечно), так и для чисел двойной точности. Следует понимать, что функция может выполнять и что-то более полезное, в зависимости от типа аргумента.
Альтернативная реализация функции lilround() :
Предположим, что у нас две или более структур ( struct ), которые содержат различный набор полей. Но так уж получилось, что нужно передать их одной и той же функции. Почему так вышло рассуждать не будем.
То все сработает корректно. Но если написать так:
То программа соберется, но во время работы выдаст неверный вариант, так как, в зависимости от того — к какой структуре приведем указатель, в случае обращения программа попытается считать первый байт из double value или, вообще, неизвестно откуда.
А вот и пример использования такого подхода:
Примечание: директивы компилятора #pragma pack(push, 1) и #pragma pack(pop) необходимо помещать до и после каждой специфической структуры, соответственно. Данная директива используется для выравнивания структуры в памяти, что обеспечит корректность метода. Однако не стоит также забывать о порядке полей.
В теле функции аргумент приводится к структуре iStruct и проверяется значение поля type. Дальше уже аргумент приводится к другому типу структуры, если нужно.
Исходя из кода: для совершения операции необходимо записать (*(int *)var) и уже к данной записи применить требуемый оператор.
Подобие интерфейсов в C
Вернемся к структурам. Если структура «засылается» далеко и глубоко в код, возможно даже чужой, то имеет смысл передать вместе с ней и методы, которые будут обрабатывать ее значения. Для этого создадим дополнительную структуру, которая заменит поле type :
Опишем реализации указанных выше функций для разных типов структур, а также — функции инициализации. Результат ниже:
Вывод:
Примечание: директивами компилятора следует обрамлять только те структуры, которые необходимо использовать в качестве аргумента для void-указателя.
Заключение
В последнем примере можно заметить сходство с ООП, что, в общем-то, правда. Здесь мы создаем структуру, инициализируем ее, задаем ее ключевым полям значения и вызываем функцию округления, которая, кстати говоря, крайне упростилась, хотя мы сюда же добавили вывод типа аргумента. На этом все. И помните, что применять подобные конструкции нужно размумно, ведь, в подавляющем большинстве задач их наличие не требуется.
UPD.: Спасибо модераторам хабра за указания на опечатки и досадные ошибки исходной версии текста.
Урок №60. Псевдонимы типов: typedef и type alias
Обновл. 13 Сен 2021 |
На этом уроке мы рассмотрим псевдонимы типов typedef и type alias в языке C++.
typedef
Ключевое слово typedef позволяет программисту создать псевдоним для любого типа данных и использовать его вместо фактического имени типа. Чтобы объявить typedef (использовать псевдоним типа) — используйте ключевое слово typedef вместе с типом данных, для которого создается псевдоним, а затем, собственно, сам псевдоним. Например:
typedef не определяет новый тип данных. Это просто псевдоним (другое имя) для уже существующего типа. Его можно использовать везде, где используется обычный тип.
Даже если следующее не имеет смысла, оно все равно разрешено в языке C++:
typedef и читабельность кода
typedef используется в улучшении документации и разборчивости кода. Имена таких типов, как char, int, long, double и bool хороши для описания того, какой тип возвращает функция, но чаще всего мы хотим знать, с какой целью возвращается значение. Например, рассмотрим следующую функцию:
Мы видим, что возвращаемым значением является целое число, но что оно означает? Количество пропущенных вопросов? Идентификационный номер учащегося? Код ошибки? Сам int ни о чем нам не говорит. Исправим ситуацию:
С использованием возвращаемого типа testScore_t становится очевидным, что функция возвращает тип, значением которого является результат теста.
typedef и поддержка кода
typedef также позволяет изменить базовый тип объекта без внесения изменений в большое количество кода. Например, если вы использовали тип short для хранения идентификационного номера учащегося, но потом решили, что лучше использовать тип long, то вам придется прошерстить кучу кода для замены short на long. И, вероятно, было бы трудно определить, какой из типов short используется для хранения идентификационных номеров, а какой — для других целей.
typedef и кроссплатформенность
Еще одним большим преимуществом typedef является возможность скрывать специфические для определенных платформ (операционных систем) детали. На некоторых платформах тип int занимает 2 байта, на других — 4 байта. Таким образом, использование типа int для хранения более 2 байтов информации может быть потенциально опасным при написании кроссплатформенного кода.
Поскольку char, short, int и long не указывают свой размер, то для кроссплатформенных программ довольно часто используется typedef для определения псевдонимов, которые включают размер типа данных в битах. Например, int8_t — это 8-битный signed int, int16_t — это 16-битный signed int, а int32_t — это 32-битный signed int.
typedef и упрощение сложного
Хотя мы до сих пор рассматривали только простые типы данных, в языке C++ вы можете увидеть и следующие переменные/функции: