Структуры
Зачем нужны структуры
Массив хранит значения одного типа, но у реальной записи поля разные: номер (целое), курс (целое), балл (дробное). Хранить их в трёх независимых массивах неудобно: легко перепутать индексы и потерять связь между полями.
Структура (struct) — составной тип: несколько полей под одним именем.
Аналогия. Массив — коробка одинаковых яблок. Структура — анкета: в ней есть поля «номер», «курс», «балл», и все они относятся к одной записи.
Объявление структуры
struct Student
{
int id;
int course;
double score;
};
struct Student — имя типа. После закрывающей } обязательно ставится ; — это самая
частая опечатка. Структура описывает шаблон, а не готовую переменную.
struct Student s; /* одна переменная */
struct Student group[30]; /* массив структур */
Доступ к полям
Для переменной структуры используется операция .:
struct Student s;
s.id = 1;
s.course = 2;
s.score = 4.5;
printf("%d %d %.2f\n", s.id, s.course, s.score);
Поля можно заполнить при объявлении:
struct Student s = { 1, 2, 4.5 }; /* по порядку полей */
struct Student p = { .score = 4.7, .id = 2 }; /* по именам */
Если инициализировать не все поля, остальные обнуляются. При обычном объявлении без инициализатора в полях будет «мусор» — заполняйте их до чтения.
typedef: короткое имя типа
Чтобы не писать struct каждый раз, используют typedef:
typedef struct
{
int id;
int course;
double score;
} Student;
Student s;
Student group[30];
Можно и с тегом:
typedef struct Student
{
int id;
int course;
double score;
} Student;
struct Student a; /* тоже работает */
Student b; /* короче */
В лабораторной работе используйте один вариант и придерживайтесь его.
Массив структур
#define MAX_STUDENTS 100
Student group[MAX_STUDENTS];
int n;
if (scanf("%d", &n) != 1 || n < 0 || n > MAX_STUDENTS)
{
return 1;
}
for (int i = 0; i < n; i++)
{
if (scanf("%d %d %lf",
&group[i].id,
&group[i].course,
&group[i].score) != 3)
{
return 1;
}
}
group[i] — структура, поэтому к полям обращаются через точку. Массив структур можно
сортировать, меняя местами целые структуры:
Student tmp = group[i];
group[i] = group[j];
group[j] = tmp;
Присваивание структуры копирует все её поля. Если внутри структуры есть указатель, копируется только адрес, а не данные; такие структуры требуют отдельного освобождения.
Вложенные структуры
Полем структуры может быть другая структура:
typedef struct
{
int day;
int month;
int year;
} Date;
typedef struct
{
int id;
int course;
Date enrolled;
double score;
} Student;
Student s;
s.enrolled.day = 15;
s.enrolled.month = 9;
s.enrolled.year = 2026;
Обращение идёт по цепочке: s.enrolled.year.
Указатель на структуру и ->
Student s = { 1, 2, 4.5 };
Student *p = &s;
printf("%d\n", p->id); /* то же, что (*p).id */
p->score = 5.0; /* изменили саму структуру */
Запись p->score короче и читается лучше, чем (*p).score. Скобки в (*p).score
обязательны: *p.score компилятор поймёт как *(p.score), а у указателя нет поля score.
Структуры и функции
Структуру можно передать в функцию по значению — функция получит копию:
void print_student(Student s)
{
printf("%d %d %.2f\n", s.id, s.course, s.score);
}
Изменения копии не повлияют на оригинал. Если функция должна изменить структуру, передают указатель:
void add_bonus(Student *s, double bonus)
{
s->score += bonus;
}
add_bonus(&s, 0.5);
Если функция только читает, полезно поставить const:
void print_student(const Student *s)
{
printf("%d %d %.2f\n", s->id, s->course, s->score);
}
Для больших структур передача по указателю ещё и быстрее: не нужно копировать все поля.
sizeof и структуры
sizeof(Student) возвращает размер всей структуры в байтах. Он может быть больше суммы
размеров полей из-за выравнивания. Поэтому для выделения памяти используйте
sizeof(Student) или sizeof(*p), а не сумму вручную.
Частые ошибки
| Ошибка | Что не так |
|---|---|
забыли ; после } структуры | ошибка компиляции |
s.score без & при scanf | запись по «мусорному» адресу |
p.score для указателя | нужен p->score |
*p.score вместо (*p).score | неверный порядок операций |
| изменили поля структуры внутри функции | меняется копия, а не оригинал |
sizeof(Student *) вместо sizeof(Student) | выделен размер указателя, а не структуры |
| не инициализировали поля перед чтением | в полях «мусор» |
Документация
Что запомнить
structобъединяет поля разных типов в один объект.- К полям переменной обращаются через
., к полям указателя — через->. - Структуры можно присваивать и передавать как единое целое.
- По значению передаётся копия; для изменения оригинала нужен указатель.
- Для выделения памяти используйте
sizeof(Student), а не сумму полей.