Урок 9. Структуры
Часто несколько значений описывают одну вещь: у точки — две координаты, у ученика — имя, класс и средний балл. Хранить их в отдельных переменных или параллельных массивах неудобно. Структура объединяет разные значения в одну переменную. Её части называются полями.
Объявление структуры
#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 = 0struct point { … };описывает новый тип — сама по себе эта запись переменных не создаёт. После закрывающей фигурной скобки обязательна точка с запятой.struct point a = {3, 4};— переменнаяaэтого типа, значения полей по порядку.{.x = -1, .y = 2}— значения по именам полей (так можно с C99); поля, которые не названы, станут нулями.a.x— обращение к полю через точку.
В отличие от массивов, структуры можно присваивать целиком: c = a копирует все поля, даже массивы внутри структуры. После копирования c и a — независимые переменные. А вот сравнить структуры через == нельзя — только по полям.
typedef — короткое имя
Писать каждый раз struct point длинно. typedef даёт типу короткое имя:
typedef struct {
char name[30];
int grade;
double score;
} Student;Теперь переменная объявляется просто: Student s;. Такая запись встречается в программах на 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), а не копировать структуру при каждом сравнении.
Отсортировать такой массив, например по баллу, помогает функция qsort — пример в справке Как отсортировать массив.
Структуры и функции
Структуру можно передать в функцию и вернуть из функции. Передаётся она, как обычная переменная, копией:
#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)addполучает копииaиbи возвращает новую структуру.scaleдолжна изменить структуру вызывающего — поэтому получает указательVec *v.v->x— поле структуры, на которую указываетv. Это короткая запись для(*v).x. Скобки там нужны: без них*v.xпоняли бы как*(v.x).
Большие структуры (с длинными массивами внутри) часто передают указателем, даже если менять их не нужно: копировать указатель быстрее, чем всю структуру. Чтобы показать, что функция структуру не меняет, пишут const Vec *v.
Вложенные структуры и размер
Поле структуры может быть другой структурой:
typedef struct {
int day, month, year;
} Date;
typedef struct {
char title[50];
Date deadline;
} Task;Обращение — цепочкой точек: t.deadline.year, а через указатель — p->deadline.year.
Размер структуры (sizeof) бывает больше суммы размеров полей: компилятор добавляет между полями пустые байты, чтобы числа лежали по удобным для процессора адресам. Поэтому размер структуры всегда узнают через sizeof, а не считают вручную.
Частые ошибки
- Нет
;после}в описании структуры. Компилятор выдаёт непонятную ошибку на следующей строке — часто в строке сint main:expected ';', identifier or '(' before 'int'. - Точка вместо стрелки и наоборот. Через переменную —
s.x, через указатель —p->x. gcc подсказывает: наp.xу указателя он пишет'p' is a pointer; did you mean to use '->'?. - Структура изменилась только внутри функции. Функция получила копию; чтобы изменить оригинал, передайте указатель.
Коротко
struct имя { поля };описывает тип, точка с запятой в конце обязательна.typedef struct { … } Имя;— короткое имя типа.- Поле переменной —
s.x, поле по указателю —p->x(то же, что(*p).x). - Структуры присваиваются целиком (
c = a), но не сравниваются через==. - В функцию структура передаётся копией; чтобы изменить её — указателем.