
Структура — это общее имя для набора переменных. Если переменная — это тарелка, то структура — это поднос с тарелками. Или менажница.
struct Point
{
int x;
int y;
};
Переменные одного типа можно объявлять вместе. Давайте создадим один экземпляр (назовём его point), а затем заполним поля x и y (используя оператор точка .).
struct Point
{
int x, y;
};
int main()
{
struct Point point;
point.x = 0;
point.y = 0;
}
Как и массивы, структуры можно инициализировать при объявлении.
struct Point point = {0, 0};
А начиная со стандарта C99, можно инициализировать с указанием полей (чего нет в C++).
struct Point point = {.x = 0, .y = 0};
Структуры могут быть вложенными.
struct Date
{
int day;
int month;
int year;
};
struct Person
{
char name[64];
struct Date birthday;
};
Можно записать иначе.
struct Person
{
char name[64];
struct Date // Имя можно не писать.
{
int day;
int month;
int year;
} birthday;
};
Обращение к полям структуры в структуре будет длиннее, но всё ещё более-менее понятным.
int main()
{
struct Person person;
strcpy(person.name, "Gavin");
person.birthday.day = 0;
person.birthday.month = 0;
person.birthday.year = 0;
}
Структура также может быть объявлена внутри функции, поэкспериментируйте самостоятельно.
Структура может быть сперва объявлена, а лишь затем определена.
struct Point;
...
struct Point
{
int x;
int y;
};
struct Person person;
struct Person
{
...
} person;
struct Person* person = malloc(sizeof(struct Person));
struct Person
{
...
} *person;
В последнем случае также нужно вручную выделять память.
Если мы работаем с экземпляром структуры по указателю, то оператор точка . заменяется на ->.
Почему постоянно приходится писать слово struct? В языке C (но не в языке C++) структуры находятся в отдельном пространстве имён (которые не имеют ничего общего с пространствами имён языка C++). Чтобы постоянно не писать struct, можно единожды определить псевдоним одним из способов.
typedef struct Person
{
...
};
typedef struct Person Person;
Структура может быть анонимной, то есть не иметь имени. Тогда создавать экземпляры такой структуры можно только при определении смой структуры.
struct {
...
} person;
Можно создавать массивы структур, структуры могут содержать массивы и так далее.
При передаче по значению экземпляр структуры будет скопирован в функцию, и все манипуляции будут производиться с локальной копией.
void foo(struct Point point)
{
point.x = 0;
point.y = 0;
}
При передаче по указателю (если он не константный) будут изменяться поля самой переданной структуры, а не локальной копии.
void foo(struct Point* point)
{
point->x = 0;
point->y = 0;
}
32-битная машина быстрее всего работает с адресами, кратными 4. Взгляните на такую структуру.
struct Bar
{
char a, b; // aaaaaaaa bbbbbbbb -------- --------
int c; // cccccccc cccccccc cccccccc cccccccc
};
Третье поле, занимающее 4 байта, размещено отдельно, а перед ним добавлено пустое место. Это называется выравниванием. Оно предназначено для ускорения операций доступа. Поэтому не стоит удивляться, что суммарный размер структуры — 8 байтов.
printf("%zu", sizeof(struct Bar)); // 8
Можно воспользоваться средствами компилятора для тонкой настройки выравнивания.
Несколько логических значений можно упаковать в одном байте (например, для передачи на интерфейсы некоторых устройств). Битовое поле — это особый вид структуры.
struct Params
{
unsigned param1: 1; // Длина поля 1.
unsigned param2: 2;
unsigned param3: 1;
};
Битовые поля должны иметь тип int или unsigned. Зато можно смешивать битовые поля и любые типы. Битовые поля длиной 1 должны иметь тип unsigned (из-за бита знака). Нельзя получить адрес переменной битового поля. Переменные битового поля не могут размещаться в массиве. И снова встаёт проблема порядка битов.
Объединения позволяют хранить переменные разных типов в одном и том же месте памяти. Объединения объявляются как структуры, но пусть вас это не обольщает.
union IntOrChar
{
int i;
char ch;
};
Экземпляры объединений создаются так же, как у структур.
Если int в памяти занимает 4 байта, а char — 1 байт, то всё объединение будет иметь размер 4 байта.
В следующем примере создаётся объединение из типов int и float. Значение с плавающей запятой записывается в объединение, а считывается как целое. Приведения типа не происходит, зато происходит интерпретация битов так, будто сперва это биты числа с плавающей запятой, а потом — биты целого числа.
typedef union IntOrFloat
{
int i;
float f;
};
IntOrFloat intOrFloat;
intOrFloat.f = 3.1415f;
printf("%d", intOrFloat.i); // 1078529622