Структуры
Структура в языке программирования Си представляет собой составной тип данных, который состоит из других компонентов. При этом в отличие от массива эти компоненты могут представлять различные типы данных.
Определение структуры
Для определения структуры применяется ключевое слово struct , а сам формат определения выглядит следующим образом:
Имя_структуры представляет произвольный идентификатор, к которому применяются те же правила, что и при наименовании переменных.
После имени структуры в фигурных скобках помещаются компоненты структуры — объекты, которые составляют структуру.
Следует отметить, что в отличие от функции при определении структуры после закрывающей фигурной скобки идет точка с запятой.
Например, определим простейшую структуру:
Здесь определена структура person , которая имеет два элемента: age (представляет тип int ) и name (представляет указатель на тип char ).
Все элементы структуры объявляются как обычные переменные. Но в отличие от переменных при определении элементов структуры для них не выделяется память, и их нельзя инициализировать. По сути мы просто определяем новый тип данных.
Использование структуры
После определения структуры мы можем ее использовать. Для начала мы можем определить объект структуры — по сути обычную переменную, которая будет представлять выше созданный тип:
Здесь определена переменная tom, которая представляет структуру person . И при каждом определении переменной типа структуры ей будет выделяться память, необходимая для хранения ее элементов.
Инициализация структуры
При определении переменной структуры ее можно сразу инициализировать, присвоив какое-нибудь значение. Инициализация структур аналогична инициализации массивов: в фигурных скобках передаются значения для элементов структуры. Есть два способа инициализации структуры.
По позиции : значения передаются элементам структуры в том порядке, в котором они следуют в структуре:
Так как в структуре person первым определено свойство age, которое представляет тип int — число, то в фигурных скобках вначале идет число, которое передается элементу age . Вторым идет элемент name , который представляет указатель на тип char или строку, соответственно вторым идет строка. И так далее для всех элементов структуры по порядку.
По имени : значения передаются элементам структуры по имени, независимо от порядка:
В этом случае перед именем элемента указывается точка, например, .name .
Обращение к элементам структуры
Также после создания переменной структуры можно обращаться к ее элементам — получать их значения или, наоборот, присваивать им новые значения. Для обращения к элементам структуры используется операция «точка»:
Теперь объединим все вместе в рамках программы:
Консольный вывод программы:
Можно инициализировать элементы структуры по отдельности:
Объединение определение структуры и ее переменных.
Мы можем одновременно совмещать определение типа структуры и ее переменных:
После определения структуры, но до точки с запятой мы можем указать переменные этой структуры. А затем присвоить их элементам значения.
Можно тут же инициализировать структуру:
Можно определить сразу несколько переменных:
При подобном определении мы можем даже не указывать имя структуры:
В этом случае компилятор все равно будет знать, что переменная tom представляет структуры с двумя элементами name и age. И соответственно мы также с этими переменными сможем работать. Другое дело, что мы не сможем задать новые переменные этой структуры в других местах программы.
typedef
Еще один способ определения структуры представляет ключевое слово typedef :
В конце определения структуры после закрывающей фигурной скобки идет ее обозначение — в данном случае person . В дальнейшем мы можем использовать это обозначение для создания переменной структуры. При этом в отличие от примеров выше здесь при определении переменной не надо использовать слово struct .
Директива define
Еще один способ определить структуру представляет применение препроцессорной директивы #define :
В данном случае директива define определяет константу PERSON, вместо которой при обработке исходного кода препроцессором будет вставляться код структуры struct
Копирование структур
Одну структуру можно присвавивать другой структуре того же типа. При копировании элементы структуры получают копии значений:
Здесь в переменную bob копируются данные из структуры tom . Далее мы для структуры bob меняется значение поля name . В итоге мы получим следующий консольный вывод:
Ввод с консоли данных для структуры
С элементами структуры можно производить все те же операции, что и с переменными тех же типов. Например, добавим ввод с консоли:
# Typedef
The typedef mechanism allows the creation of aliases for other types. It does not create new types. People often use typedef to improve the portability of code, to give aliases to structure or union types, or to create aliases for function (or function pointer) types.
In the C standard, typedef is classified as a ‘storage class’ for convenience; it occurs syntactically where storage classes such as static or extern could appear.
# Typedef for Structures and Unions
You can give alias names to a struct :
Compared to the traditional way of declaring structs, programmers wouldn’t need to have struct every time they declare an instance of that struct.
Note that the name Person (as opposed to struct Person ) is not defined until the final semicolon. Thus for linked lists and tree structures which need to contain a pointer to the same structure type, you must use either:
The use of a typedef for a union type is very similar.
A structure similar to this can be used to analyze the bytes that make up a float value.
# Simple Uses of Typedef
# For giving short names to a data type
This reduces the amount of typing needed if the type is used many times in the program.
# Improving portability
The attributes of data types vary across different architectures. For example, an int may be a 2-byte type in one implementation and an 4-byte type in another. Suppose a program needs to use a 4-byte type to run correctly.
In one implementation, let the size of int be 2 bytes and that of long be 4 bytes. In another, let the size of int be 4 bytes and that of long be 8 bytes. If the program is written using the second implementation,
For the program to run in the first implementation, all the int declarations will have to be changed to long .
To avoid this, one can use typedef
Then, only the typedef statement would need to be changed each time, instead of examining the whole program.
The <stdint.h> header and the related <inttypes.h> header define standard type names (using typedef ) for integers of various sizes, and these names are often the best choice in modern code that needs fixed size integers. For example, uint8_t is an unsigned 8-bit integer type; int64_t is a signed 64-bit integer type. The type uintptr_t is an unsigned integer type big enough to hold any pointer to object. These types are theoretically optional — but it is rare for them not to be available. There are variants like uint_least16_t (the smallest unsigned integer type with at least 16 bits) and int_fast32_t (the fastest signed integer type with at least 32 bits). Also, intmax_t and uintmax_t are the largest integer types supported by the implementation. These types are mandatory.
# To specify a usage or to improve readability
If a set of data has a particular purpose, one can use typedef to give it a meaningful name. Moreover, if the property of the data changes such that the base type must change, only the typedef statement would have to be changed, instead of examining the whole program.
# Typedef for Function Pointers
We can use typedef to simplify the usage of function pointers. Imagine we have some functions, all having the same signature, that use their argument to print out something in different ways:
Now we can use a typedef to create a named function pointer type called printer:
This creates a type, named printer_t for a pointer to a function that takes a single int argument and returns nothing, which matches the signature of the functions we have above. To use it we create a variable of the created type and assign it a pointer to one of the functions in question:
Then to call the function pointed to by the function pointer variable:
Thus the typedef allows a simpler syntax when dealing with function pointers. This becomes more apparent when function pointers are used in more complex situations, such as arguments to functions.
If you are using a function that takes a function pointer as a parameter without a function pointer type defined the function definition would be,
However, with the typedef it is:
Likewise functions can return function pointers and again, the use of a typedef can make the syntax simpler when doing so.
A classic example is the signal function from <signal.h> . The declaration for it (from the C standard) is:
That’s a function that takes two arguments — an int and a pointer to a function which takes an int as an argument and returns nothing — and which returns a pointer to function like its second argument.
If we defined a type SigCatcher as an alias for the pointer to function type:
then we could declare signal() using:
On the whole, this is easier to understand (even though the C standard did not elect to define a type to do the job). The signal function takes two arguments, an int and a SigCatcher , and it returns a SigCatcher — where a SigCatcher is a pointer to a function that takes an int argument and returns nothing.
Although using typedef names for pointer to function types makes life easier, it can also lead to confusion for others who will maintain your code later on, so use with caution and proper documentation. See also Function Pointers
# Syntax
- typedef existing_name alias_name;
# Remarks
Disadvantages of Typedef
typedef could lead to the pollution of namespace in large C programs.
Disadvantages of Typedef Structs
Also, typedef ‘d structs without a tag name are a major cause of needless imposition of ordering relationships among header files.
With such a definition, not using typedefs , it is possible for a compilation unit to include foo.h to get at the FOO_DEF definition. If it doesn’t attempt to dereference the bar member of the foo struct then there will be no need to include the bar.h file.
Typedef vs #define
#define is a C pre-processor directive which is also used to define the aliases for various data types similar to typedef but with the following differences:
How to use the typedef struct in C
Many candidates are rejected or down-leveled due to poor performance in their System Design Interview. Stand out in System Design Interviews and get hired in 2023 with this popular free course.
The C language contains the typedef keyword to allow users to provide alternative names for the primitive (e.g., int) and user-defined (e.g struct) data types.
Remember, this keyword adds a new name for some existing data type but does not create a new type.
Typedef struct c что это
Давайте попробуем разобраться, в чем разница между двумя определениями структур (далее перевод [1]):
[C++]
С точки зрения объявления переменных 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++. Конечно же, Вы можете найти и исправить ошибку, но это требует дополнительной дисциплины в программировании и усилий, которых можно было бы избежать.
Еще одно важное отличие: typedef-ы не могут иметь предварительное декларирование [4]. Таким образом, для опции typedef Вы должны сделать #include файла, содержащего определения typedef. Это означает, что всякий раз, когда какой-то код применит #include для Вашего заголовка .h, он также подключит и файл с typedef-ами — независимо от того, нужны ему эти определения или нет. Это может значительно увеличить время сборки больших проектов.
Без typedef в некоторых случаях Вы можете просто добавить предварительное декларирование struct Foo; в начале Вашего заголовочного .h файла, и в файле .cpp применять #include только для определения структуры.
Итак, в C++ все декларации struct/union/enum/class действуют точно так же как если бы они были неявно объявлены через typedef, пока имя не скрыто другой декларацией с таким же именем.
[C]
Таким образом, тонкие отличия есть только в 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. Это означает, что предварительное декларирование невозможно. Если Вы хотите применить предварительное декларирование, то нужно задать имя для пространства имен тегов.