Глиф Учебники

Урок 9. Структуры

Курс «C» · урок 9 из 10 · 3 минуты чтения

Часто несколько значений описывают одну вещь: у точки — две координаты, у ученика — имя, класс и средний балл. Хранить их в отдельных переменных или параллельных массивах неудобно. Структура объединяет разные значения в одну переменную. Её части называются полями.

Объявление структуры

C
#include <stdio.h>

struct point {
    int x;
    int y;
};

int main(void) {
    struct point a = {3, 4};
    struct point b = {.x = -1, .y = 2};
    printf("a = (%d, %d)\n", a.x, a.y);
    b.x = 10;
    printf("b = (%d, %d)\n", b.x, b.y);

    struct point c = a;
    c.x = 0;
    printf("a.x = %d, c.x = %d\n", a.x, c.x);
    return 0;
}
Вывод
a = (3, 4)
b = (10, 2)
a.x = 3, c.x = 0

В отличие от массивов, структуры можно присваивать целиком: c = a копирует все поля, даже массивы внутри структуры. После копирования c и a — независимые переменные. А вот сравнить структуры через == нельзя — только по полям.

тип — только описание struct point { int x; int y; }; переменные этого типа a x 3 y 4 {3, 4} b x было -1 10 y 2 {.x = -1, .y = 2} b.x = 10; c x было 3 0 y 4 c = a; c.x = 0; a.x осталось 3
Описание struct point само памяти не занимает — это шаблон с полями x и y; у каждой переменной a, b, c свои поля, а c = a копирует их значения, поэтому c.x = 0 не меняет a.x.

typedef — короткое имя

Писать каждый раз struct point длинно. typedef даёт типу короткое имя:

C
typedef struct {
    char name[30];
    int grade;
    double score;
} Student;

Теперь переменная объявляется просто: Student s;. Такая запись встречается в программах на C очень часто.

Массив структур

Массив структур — как таблица: элемент — строка, поля — столбцы. Пример: даны ученики (имя, класс, балл), нужно найти лучшего.

C
#include <stdio.h>

typedef struct {
    char name[30];
    int grade;
    double score;
} Student;

int main(void) {
    Student s[100];
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%29s %d %lf", s[i].name, &s[i].grade, &s[i].score);
    }

    int best = 0;
    for (int i = 1; i < n; i++) {
        if (s[i].score > s[best].score) {
            best = i;
        }
    }
    printf("Лучший: %s, %d класс, балл %.1f\n", s[best].name, s[best].grade, s[best].score);
    return 0;
}
Ввод
3
Ivan 10 8.5
Olga 11 9.2
Petr 10 7.9
Вывод
Лучший: Olga, 11 класс, балл 9.2

Обратите внимание на scanf: у поля-строки s[i].name нет & (это массив), у числовых полей — есть. Запомнить лучше номер лучшего ученика (best), а не копировать структуру при каждом сравнении.

Student s[100]; заполнено n = 3 name grade score s[0] Ivan 10 8.5 s[1] Olga 11 9.2 s[2] Petr 10 7.9 best = 1 строка — один Student, столбец — его поле s[best].score = 9.2
Массив структур s похож на таблицу: каждый элемент — строка с полями name, grade и score; наибольший балл 9.2 у Olga, элемента с номером 1, поэтому best = 1.

Отсортировать такой массив, например по баллу, помогает функция qsort — пример в справке Как отсортировать массив.

Структуры и функции

Структуру можно передать в функцию и вернуть из функции. Передаётся она, как обычная переменная, копией:

C
#include <stdio.h>

typedef struct {
    double x, y;
} Vec;

Vec add(Vec a, Vec b) {
    Vec r = {a.x + b.x, a.y + b.y};
    return r;
}

void scale(Vec *v, double k) {
    v->x *= k;
    v->y *= k;
}

int main(void) {
    Vec a = {1.5, 2};
    Vec b = {0.5, -1};
    Vec c = add(a, b);
    printf("c = (%.1f, %.1f)\n", c.x, c.y);
    scale(&c, 3);
    printf("c = (%.1f, %.1f)\n", c.x, c.y);
    return 0;
}
Вывод
c = (2.0, 1.0)
c = (6.0, 3.0)
main c x было 2.0 6.0 y было 1.0 3.0 scale(&c, 3); вывод: c = (6.0, 3.0) void scale(Vec *v, double k) v адрес c k 3 v->x *= k; c.x = 6.0 v->y *= k; c.y = 3.0 v->x — то же, что (*v).x
scale(&c, 3) получает адрес структуры c в указатель v; v->x и v->y — поля самой c в main, поэтому умножение на k = 3 превращает (2.0, 1.0) в (6.0, 3.0).

Большие структуры (с длинными массивами внутри) часто передают указателем, даже если менять их не нужно: копировать указатель быстрее, чем всю структуру. Чтобы показать, что функция структуру не меняет, пишут const Vec *v.

Вложенные структуры и размер

Поле структуры может быть другой структурой:

C
typedef struct {
    int day, month, year;
} Date;

typedef struct {
    char title[50];
    Date deadline;
} Task;

Обращение — цепочкой точек: t.deadline.year, а через указатель — p->deadline.year.

Размер структуры (sizeof) бывает больше суммы размеров полей: компилятор добавляет между полями пустые байты, чтобы числа лежали по удобным для процессора адресам. Поэтому размер структуры всегда узнают через sizeof, а не считают вручную.

Частые ошибки

Коротко

Проверьте себя

1. Как обратиться к полю age через указатель p на структуру?
2. Что выведет программа?
C
#include <stdio.h>

typedef struct {
    int v;
} Box;

void set(Box b) {
    b.v = 99;
}

int main(void) {
    Box b = {1};
    set(b);
    printf("%d\n", b.v);
    return 0;
}
3. Что делает присваивание c = a для двух структур одного типа?
4. Что обязательно после закрывающей скобки в struct point { int x; int y; }?