Структуры

Зачем нужны структуры

Массив хранит значения одного типа, но у реальной записи поля разные: номер (целое), курс (целое), балл (дробное). Хранить их в трёх независимых массивах неудобно: легко перепутать индексы и потерять связь между полями.

Структура (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)выделен размер указателя, а не структуры
не инициализировали поля перед чтениемв полях «мусор»

Документация

Что запомнить

  1. struct объединяет поля разных типов в один объект.
  2. К полям переменной обращаются через ., к полям указателя — через ->.
  3. Структуры можно присваивать и передавать как единое целое.
  4. По значению передаётся копия; для изменения оригинала нужен указатель.
  5. Для выделения памяти используйте sizeof(Student), а не сумму полей.

Подтверждение

—