Перейти до змісту

Структури в мові C

Що таке структура?

Структура (struct) — це спосіб об'єднати кілька змінних різних типів в одну сутність.

Аналогія

Уяви анкету студента: ім'я, вік, середній бал. Це три різні типи даних, але вони належать одній людині. Структура дозволяє зберігати їх разом.

Навіщо потрібні структури?

Можна обійтися без структур, наприклад ми вже вміємо групувати дані у масиви, і тут ми могли б використати три окремі масиви:

// Bad: data is scattered across different entities
char names[100][50];
int ages[100];
float grades[100];

// Why bad: For example, to get student #5 data:
std::cout <<
    names[5] << ", " <<
    ages[5] << " years, grade: " <<
    grades[5] << std::endl;

Структури дозволяють нам логічно згрупувати дані ніби в одну змінну. Нижче приклад використання структури, звернення до пам'яті та читання даних

// Example of single student:
Student john;
//...
std::cout <<
    john.name << ", " <<
    john.age << " years, grade: " <<
    john.grade << std::endl;

// Even array of students:
Student students[100];
//...
// To print all data about students:
for (int i = 0; i < 100; i++)
{
    std::cout <<
        students[i].name << ", " <<
        students[i].age << " years, grade: " <<
        students[i].grade << std::endl;
}
Ми поки що пропустили заповнення структури для спрощення прикладу, але зверніть увагу, що такий код читати легше. При ітеруванні по масиву оператор індексації students[i] поверне одного студента. Для звернення до його полів даних відбувається через оператор ., це спеціальний оператор в С, який повертає поле даних структури (як звичайну змінну відповідного типу)

Оголошення структури

Оголошуючи структуру, ми повідомляємо компілятору ніби новий тип даних. Для прикладу, що мав місце вище оголошення має бути приблизно таким:

struct Student {
    char name[50];
    int age;
    float grade;
};

Після цього, коли компілятор побачить визначення нової змінної:

//... somewhere in your code:
Student john;
//...
він (компілятор) виділить пам'яті достатньо для збереження всіх полів структури, відповідно до того ми оголосили структуру раніше
flowchart LR
    subgraph Student["struct Student"]
        direction LR
        A["char name[50]"]
        B["int age"]
        C["float grade"]
    end

Не забудь крапку з комою!

Після закриваючої фігурної дужки структури обов'язково ставиться ;

Створення змінної-структури

Спосіб 1: Окремо від оголошення

struct Student {
    char name[50];
    int age;
    float grade;
};

int main() {
    struct Student student1;  // Created a variable of type struct Student
    return 0;
}

Спосіб 2: Відразу при оголошенні

struct Student {
    char name[50];
    int age;
    float grade;
} student1, student2;  // Created two variables immediately

Спосіб 3: З typedef (рекомендовано!)

typedef struct {
    char name[50];
    int age;
    float grade;
} Student;  // Now you can write just Student instead of struct Student

int main() {
    Student student1;  // Shorter and more convenient!
    return 0;
}

Рекомендація

Завжди використовуй typedef — це робить код чистішим і схожим на інші мови програмування.

Доступ до полів структури

Використовуй оператор крапка . для доступу до полів:

#include <iostream>
#include <cstring>

typedef struct {
    char name[50];
    int age;
    float grade;
} Student;

int main() {
    Student s;

    // Writing data
    strcpy(s.name, "Ivan");  // For strings we use strcpy
    s.age = 16;
    s.grade = 10.5;

    // Reading data
    std::cout << "Student: " << s.name << std::endl;
    std::cout << "Age: " << s.age << std::endl;
    std::cout << "Grade: " << s.grade << std::endl;

    return 0;
}

Вивід:

Student: Ivan
Age: 16
Grade: 10.5

Ініціалізація структури

При оголошенні

Student s1 = {"Maria", 17, 11.2};

// Or with named fields (C99+):
Student s2 = {
    .name = "Petro",
    .age = 16,
    .grade = 9.8
};

Копіювання структур

Структури можна копіювати простим присвоєнням:

Student s1 = {"Ivan", 16, 10.5};
Student s2 = s1;  // Copied all fields!

std::cout << s2.name << std::endl;  // Output: Ivan

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

#include <iostream>

typedef struct {
    char name[50];
    int age;
    float grade;
} Student;

int main() {
    Student class_10a[3] = {
        {"Ivan", 16, 10.5},
        {"Maria", 15, 11.8},
        {"Petro", 16, 9.2}
    };

    // Print all students
    for (int i = 0; i < 3; i++) {
        std::cout <<
            class_10a[i].name << ": " <<
            class_10a[i].grade << std::endl;
    }

    return 0;
}

Вивід:

Ivan: 10.5
Maria: 11.8
Petro: 9.2

Вказівники на структури

Оператор стрілка ->

Коли маємо вказівник на структуру, використовуємо -> замість .:

#include <iostream>

typedef struct {
    char name[50];
    int age;
} Student;

int main() {
    Student s = {"Ivan", 16};
    Student *ptr = &s;  // Pointer to structure

    // Two equivalent ways:
    std::cout << (*ptr).name << std::endl;  // Method 1: dereference + dot
    std::cout << ptr->name << std::endl;    // Method 2: arrow (more convenient!)

    return 0;
}
flowchart LR
    subgraph Stack["Memory"]
        ptr["ptr<br/>(pointer)"] --> s
        subgraph s["s (Student)"]
            name["name: Ivan"]
            age["age: 16"]
        end
    end

Правило

  • Змінна структури → використовуй .
  • Вказівник на структуру → використовуй ->

Структури як параметри функцій

Передача за значенням (копіюється)

void printStudent(Student s) {
    std::cout << s.name << ", " << s.age << " years" << std::endl;
}

int main() {
    Student ivan = {"Ivan", 16};
    printStudent(ivan);  // A COPY is passed
    return 0;
}

Увага

При передачі за значенням копіюється вся структура. Для великих структур це повільно!

Передача за вказівником (ефективно)

void printStudent(Student *s) {
    std::cout << s->name << ", " << s->age << " years" << std::endl;
}

void birthday(Student *s) {
    s->age++;  // Modifying the original!
}

int main() {
    Student ivan = {"Ivan", 16};

    printStudent(&ivan);  // Passing address
    birthday(&ivan);      // Incrementing age
    printStudent(&ivan);  // Now 17 years

    return 0;
}

Вивід:

Ivan, 16 years
Ivan, 17 years

Вкладені структури

Структура може містити іншу структуру:

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

typedef struct {
    char name[50];
    Date birthday;  // Nested structure
} Student;

int main() {
    Student s = {
        "Ivan",
        {15, 3, 2008}  // day, month, year
    };

    std::cout <<
        s.name << " was born on " <<
        s.birthday.day << "." <<
        s.birthday.month << "." <<
        s.birthday.year << std::endl;

    return 0;
}
flowchart LR
    subgraph Student
        direction LR
        A["name: Ivan"]
        subgraph Date["birthday (Date)"]
            direction LR
            B["day: 15"]
            C["month: 3"]
            D["year: 2008"]
        end
    end

Розмір структури в пам'яті

Розмір структури можна дізнатися через sizeof:

typedef struct {
    char c;    // 1 byte
    int i;     // 4 bytes
    double d;  // 8 bytes
} Example;

int main() {
    std::cout << "Size of char: " << sizeof(char) << std::endl;
    std::cout << "Size of int: " << sizeof(int) << std::endl;
    std::cout << "Size of double: " << sizeof(double) << std::endl;
    std::cout << "Size of struct: " << sizeof(Example) << std::endl;
    return 0;
}

Цікаво

1 + 4 + 8 = 13 байтів, але sizeof(Example) може показати 16 або 24 байти!

Це через вирівнювання пам'яті (padding) — компілятор додає порожні байти для оптимізації.

Практичний приклад: Точка та відстань

#include <iostream>
#include <cmath>

typedef struct {
    double x;
    double y;
} Point;

double distance(Point a, Point b) {
    double dx = b.x - a.x;
    double dy = b.y - a.y;
    return sqrt(dx * dx + dy * dy);
}

int main() {
    Point p1 = {0.0, 0.0};
    Point p2 = {3.0, 4.0};

    std::cout << "Distance: " << distance(p1, p2) << std::endl;  // 5.00

    return 0;
}

Формула відстані між точками:

\[ d = \sqrt{(x_2 - x_1)^2 + (y_2 - y_1)^2} \]

Задачі для практики

Задача 1: Прямокутник

Створи структуру Rectangle з полями width і height. Напиши функції area() і perimeter().

Розв'язок
typedef struct {
    double width;
    double height;
} Rectangle;

double area(Rectangle r) {
    return r.width * r.height;
}

double perimeter(Rectangle r) {
    return 2 * (r.width + r.height);
}

Задача 2: Комплексні числа

Створи структуру Complex для комплексних чисел (реальна та уявна частини). Напиши функцію додавання двох комплексних чисел.

\[ (a + bi) + (c + di) = (a + c) + (b + d)i \]
Розв'язок
typedef struct {
    double real;
    double imag;
} Complex;

Complex add(Complex a, Complex b) {
    Complex result;
    result.real = a.real + b.real;
    result.imag = a.imag + b.imag;
    return result;
}

Задача 3: Список студентів

Створи масив з 5 студентів, заповни їх даними і знайди студента з найвищим балом.

Підказка

Використай цикл for і змінну для зберігання індексу найкращого студента.


Далі: Алгоритми