C++

Масиви

Одновимірні та двовимірні масиви в C++. Оголошення, ініціалізація, індексація. Обхід циклом. Алгоритми пошуку, сортування та накопичення. Рядки як масиви символів.

Проблема: зберегти 30 оцінок

Уявіть задачу: прочитати оцінки 30 студентів, знайти середнє, максимум і мінімум, вивести тих, хто вище середньої. З тим, що ви вже знаєте, рішення виглядало б жахливо:

int grade1, grade2, grade3; // ... grade30;
cin >> grade1 >> grade2 >> grade3; // ... >> grade30;
int sum = grade1 + grade2 + grade3; // + ... + grade30;

Тридцять окремих змінних, жодного способу обробити їх автоматично в циклі. А якщо студентів 300? 3000? Очевидно, що цей підхід нежиттєздатний.

Для таких ситуацій — зберігати набір однотипних значень і обробляти їх в циклі — існує фундаментальна структура даних: масив (array).

Масив — це іменована послідовність елементів одного типу, що зберігаються у суміжних (поруч розташованих) ділянках пам'яті та доступні через числовий індекс.

Ключові слова у цьому визначенні: одного типу (масив int містить лише int) та суміжних ділянках (всі елементи розміщені підряд у пам'яті — це дає миттєвий доступ за індексом).

Оголошення та ініціалізація

Синтаксис оголошення

тип ім'я[розмір];
int grades[30];      // Масив із 30 цілих чисел
double prices[10];   // Масив із 10 дійсних чисел
char letters[5];     // Масив із 5 символів
bool flags[8];       // Масив із 8 логічних значень

Після цього оголошення в пам'яті резервується безперервний блок: для int grades[30] — 30 × 4 байти = 120 байтів.

Якщо оголосити масив без ініціалізації (int grades[30];), всі його елементи містять сміттєві значення — випадкові числа, що залишилися в пам'яті від попередніх програм. Завжди ініціалізуйте масив перед використанням!

Ініціалізація при оголошенні

// Явна ініціалізація всіх елементів
int scores[5] = {95, 87, 72, 60, 88};

// Ініціалізація нулями — якщо значень менше, решта = 0
int data[10] = {1, 2, 3};   // {1, 2, 3, 0, 0, 0, 0, 0, 0, 0}

// Ініціалізація ВСІХ елементів нулями
int zeros[100] = {0};

// Розмір виводиться автоматично з кількості елементів
int primes[] = {2, 3, 5, 7, 11};  // Розмір = 5
Ініціалізація нулями = {0} — найпоширеніший і безпечний спосіб підготувати масив до роботи. Він гарантує, що жоден елемент не міститиме «сміття».

Константа для розміру масиву

Ніколи не пишіть розмір масиву «магічним числом» просто в коді. Замість цього використовуйте іменовану константу:

// ❌ Погано — розмір розкиданий по коду
int grades[30];
for (int i = 0; i < 30; i++)
{
    // ...
}

// ✅ Добре — одна константа управляє всім
const int STUDENT_COUNT = 30;
int grades[STUDENT_COUNT];

for (int i = 0; i < STUDENT_COUNT; i++)
{
    // ...
}

Якщо знадобиться змінити кількість студентів на 50 — у першому варіанті доведеться знайти та замінити кожне 30 у всьому файлі. У другому — змінити одне значення константи.

Доступ до елементів: індексація

Кожен елемент масиву має індекс (index) — порядковий номер, починаючи з нуля. Це одна з найважливіших особливостей C++ (і більшості мов програмування): перший елемент — [0], другий — [1], ..., останній — [N-1].

int scores[5] = {95, 87, 72, 60, 88};
//  індекси:      0   1   2   3   4
Loading diagram...
graph LR
    subgraph "scores[5]"
        A["[0]\n95"]
        B["[1]\n87"]
        C["[2]\n72"]
        D["[3]\n60"]
        E["[4]\n88"]
    end

    style A fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B fill:#64748b,stroke:#334155,color:#ffffff
    style C fill:#64748b,stroke:#334155,color:#ffffff
    style D fill:#64748b,stroke:#334155,color:#ffffff
    style E fill:#64748b,stroke:#334155,color:#ffffff
// Зчитати значення елемента
cout << scores[0];  // 95 (перший)
cout << scores[4];  // 88 (останній)

// Записати значення
scores[2] = 100;    // Змінюємо третій елемент

// Використання змінної як індексу
int i = 3;
cout << scores[i];  // 60
Вихід за межі масиву (out-of-bounds access) — одна з найнебезпечніших помилок у C++. Звернення до scores[5] або scores[-1]не дає помилки компіляції і навіть може не дати помилки під час виконання — натомість програма читає або пише у довільну ділянку пам'яті. Поведінка непередбачувана і часто катастрофічна. Завжди слідкуйте, щоб індекс знаходився в діапазоні 0 ... N-1.

Обхід масиву циклом

Масиви та цикли for — природне поєднання. Лічильник i стає індексом елемента:

PrintArray.cpp
#include <iostream>

using namespace std;

int main()
{
    const int SIZE = 5;
    int scores[SIZE] = {95, 87, 72, 60, 88};

    // Виводимо всі елементи
    for (int i = 0; i < SIZE; i++)
    {
        cout << "scores[" << i << "] = " << scores[i] << "\n";
    }

    return 0;
}

Результат:

scores[0] = 95
scores[1] = 87
scores[2] = 72
scores[3] = 60
scores[4] = 88

Зверніть на умову циклу: i < SIZE, а не i <= SIZE. При SIZE = 5 коректні індекси — 0, 1, 2, 3, 4. Значення 5 вже виходить за межі.

Введення масиву з клавіатури

const int SIZE = 5;
int values[SIZE];

cout << "Enter " << SIZE << " numbers:\n";

for (int i = 0; i < SIZE; i++)
{
    cout << "values[" << i << "]: ";
    cin >> values[i];
}

Range-based for (спрощений обхід)

У стандарті C++11 з'явився range-based for — спрощений синтаксис для обходу всіх елементів:

int scores[] = {95, 87, 72, 60, 88};

for (int score : scores)
{
    cout << score << " ";
}
// Виведе: 95 87 72 60 88

Читається як: «для кожного score у scores». Не потрібен лічильник та розмір. Але є обмеження: не можна дізнатися поточний індекс, і цикл завжди йде вперед. Якщо потрібен індекс або зміна елемента — використовуйте звичайний for.

У range-based for для зміни елементів масиву змінна має бути посиланням (int& score : scores). Без & ви отримуєте копію і оригінал не змінюється. Детальніше про посилання — у відповідному розділі.

Алгоритми на масивах

Сума та середнє

AverageScore.cpp
#include <iostream>

using namespace std;

int main()
{
    const int SIZE = 6;
    int grades[SIZE] = {78, 92, 55, 88, 67, 95};

    int sum = 0;

    for (int i = 0; i < SIZE; i++)
    {
        sum += grades[i];
    }

    double average = (double)sum / SIZE;

    cout << "Sum: "     << sum     << "\n";
    cout << "Average: " << average << "\n";

    return 0;
}
  • Рядок 10: Накопичувач sum ініціалізується нулем до циклу.
  • Рядок 17: Явне приведення (double)sum перед діленням — щоб отримати дійсний результат, а не цілочисельне ділення.

Пошук мінімуму та максимуму

const int SIZE = 6;
int data[SIZE] = {34, 17, 89, 42, 11, 73};

int maxVal = data[0];  // Ініціалізуємо ПЕРШИМ елементом масиву
int minVal = data[0];

int maxIndex = 0;
int minIndex = 0;

for (int i = 1; i < SIZE; i++)  // Починаємо з 1, бо [0] вже взяли
{
    if (data[i] > maxVal)
    {
        maxVal = data[i];
        maxIndex = i;
    }
    if (data[i] < minVal)
    {
        minVal = data[i];
        minIndex = i;
    }
}

cout << "Max: " << maxVal << " at index " << maxIndex << "\n";
cout << "Min: " << minVal << " at index " << minIndex << "\n";

Ключовий момент: maxVal і minVal ініціалізуються data[0] — першим реальним елементом масиву. Ніколи не ініціалізуйте їх 0 або -1: якщо всі елементи масиву від'ємні, тоді maxVal = 0 одразу стане хибним максимумом.

Перевіряємо кожен елемент по черзі в пошуках потрібного значення:

const int SIZE = 8;
int numbers[SIZE] = {14, 7, 33, 2, 56, 99, 12, 44};

int target;
cout << "Search for: ";
cin >> target;

int foundIndex = -1;  // -1 означає «не знайдено»

for (int i = 0; i < SIZE; i++)
{
    if (numbers[i] == target)
    {
        foundIndex = i;
        break;  // Знайшли — виходимо одразу
    }
}

if (foundIndex != -1)
{
    cout << target << " found at index " << foundIndex << "\n";
}
else
{
    cout << target << " not found.\n";
}

Значення foundIndex = -1 — класичний «сигнальний» маркер: жодний коректний індекс не може бути -1, тому це безпечний спосіб позначити «не знайдено».

Сортування бульбашкою (bubble sort)

Сортування (sorting) — упорядкування елементів масиву за зростанням або спаданням. Один з найпростіших алгоритмів — сортування бульбашкою (bubble sort): за кожен прохід порівнюємо сусідні елементи та міняємо їх місцями, якщо вони не в правильному порядку. Найбільший елемент «спливає» вгору, як бульбашка.

BubbleSort.cpp
#include <iostream>

using namespace std;

int main()
{
    const int SIZE = 6;
    int arr[SIZE] = {64, 34, 25, 12, 22, 11};

    // Зовнішній цикл: N-1 проходів
    for (int pass = 0; pass < SIZE - 1; pass++)
    {
        // Внутрішній цикл: порівнюємо сусідів
        for (int i = 0; i < SIZE - 1 - pass; i++)
        {
            if (arr[i] > arr[i + 1])
            {
                // Міняємо елементи місцями
                int temp = arr[i];
                arr[i] = arr[i + 1];
                arr[i + 1] = temp;
            }
        }
    }

    // Виводимо відсортований масив
    for (int i = 0; i < SIZE; i++)
    {
        cout << arr[i] << " ";
    }
    cout << "\n";

    return 0;
}

Розберемо механізм обміну (рядки 18–21):

int temp = arr[i];      // Зберігаємо arr[i] у тимчасову змінну
arr[i] = arr[i + 1];   // Перекладаємо arr[i+1] на місце arr[i]
arr[i + 1] = temp;     // Перекладаємо збережений arr[i] на місце arr[i+1]

Без temp перший рядок arr[i] = arr[i + 1] знищив би оригінальне значення arr[i] — і воно було б втрачене. Тимчасова змінна — обов'язковий посередник при обміні.

Трасування першого проходу (масив {64, 34, 25, 12, 22, 11}):

ПорівнянняДоПісля
arr[0] vs arr[1] (64 > 34){64, 34, ...}{34, 64, ...}
arr[1] vs arr[2] (64 > 25){34, 64, 25, ...}{34, 25, 64, ...}
arr[2] vs arr[3] (64 > 12){34, 25, 64, 12, ...}{34, 25, 12, 64, ...}
arr[3] vs arr[4] (64 > 22){34, 25, 12, 64, 22, ...}{34, 25, 12, 22, 64, ...}
arr[4] vs arr[5] (64 > 11){..., 64, 11}{..., 11, 64}64 на місці

Після першого проходу максимальний елемент 64 стоїть на останньому місці. Після другого проходу — 34 на передостанньому. І так далі.

Результат: 11 12 22 25 34 64

Двовимірні масиви

Одновимірний масив — це рядок. Двовимірний (2D) масив — це таблиця з рядками і стовпцями. Він природно моделює матриці, шахові дошки, карти рівнів у грі.

Оголошення та ініціалізація

тип ім'я[рядки][стовпці];

// Приклад: матриця 3×4 (3 рядки, 4 стовпці)
int matrix[3][4];

// Ініціалізація
int table[2][3] = {
    {1, 2, 3},   // Рядок 0
    {4, 5, 6}    // Рядок 1
};
Loading diagram...
graph TD
    subgraph "table[2][3]"
        subgraph "Рядок 0"
            A["[0][0]\n1"]
            B["[0][1]\n2"]
            C["[0][2]\n3"]
        end
        subgraph "Рядок 1"
            D["[1][0]\n4"]
            E["[1][1]\n5"]
            F["[1][2]\n6"]
        end
    end

    style A fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style B fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style C fill:#3b82f6,stroke:#1d4ed8,color:#ffffff
    style D fill:#64748b,stroke:#334155,color:#ffffff
    style E fill:#64748b,stroke:#334155,color:#ffffff
    style F fill:#64748b,stroke:#334155,color:#ffffff

Обхід двовимірного масиву

Для обходу потрібні два вкладені цикли: зовнішній — по рядках, внутрішній — по стовпцях.

const int ROWS = 3;
const int COLS = 4;

int matrix[ROWS][COLS] = {
    {1,  2,  3,  4},
    {5,  6,  7,  8},
    {9, 10, 11, 12}
};

// Вивід у вигляді таблиці
for (int row = 0; row < ROWS; row++)
{
    for (int col = 0; col < COLS; col++)
    {
        cout << matrix[row][col] << "\t";
    }
    cout << "\n";  // Перехід на новий рядок після кожного рядка матриці
}

Результат:

1    2    3    4
5    6    7    8
9    10   11   12

Практичний приклад: успішність матриці студентів

GradeMatrix.cpp
#include <iostream>

using namespace std;

int main()
{
    const int STUDENTS = 3;
    const int SUBJECTS = 4;

    int grades[STUDENTS][SUBJECTS] = {
        {85, 92, 78, 90},   // Студент 0
        {70, 65, 88, 75},   // Студент 1
        {95, 98, 100, 92}   // Студент 2
    };

    // Середня оцінка кожного студента
    for (int student = 0; student < STUDENTS; student++)
    {
        int sum = 0;

        for (int subject = 0; subject < SUBJECTS; subject++)
        {
            sum += grades[student][subject];
        }

        double average = (double)sum / SUBJECTS;
        cout << "Student " << student << " average: " << average << "\n";
    }

    return 0;
}
  • Рядки 10–14: Ініціалізація матриці: кожен рядок — один студент, кожен стовпець — одна дисципліна.
  • Рядки 17–27: Для кожного студента окремо накопичується сума оцінок по всіх предметах (внутрішній цикл), потім ділиться на кількість предметів.

Результат:

Student 0 average: 86.25
Student 1 average: 74.5
Student 2 average: 96.25

Масиви та рядки (char масиви)

У C++ рядок тексту — це масив символів типу char, що закінчується спеціальним символом '\0' (нуль-термінатор). Цей підхід успадкований з мови C.

// Оголошення рядка як масиву char
char name[20] = "Ivan";
//   Фактично: {'I', 'v', 'a', 'n', '\0', ?, ?, ...}
//   Нуль-термінатор '\0' показує кінець рядка
// Введення та виведення рядка
char city[50];
cout << "Enter city: ";
cin >> city;       // Зчитує одне слово (до пробілу)
cout << city;      // cout знає, де кінець — за '\0'
Тип string (з бібліотеки <string>) — більш зручний та безпечний спосіб роботи з текстом у C++. char-масиви — це низькорівневий підхід, важливий для розуміння внутрішньої механіки. У сучасному коді переважно використовується string. З ним ми детально познайомимося в окремому розділі.

Повний приклад: Журнал оцінок

Реалізуємо повну програму: введення оцінок студентів, підрахунок статистики та виведення тих, хто вище середнього.

GradeBook.cpp
#include <iostream>

using namespace std;

int main()
{
    const int SIZE = 5;
    int grades[SIZE];
    int sum = 0;

    // Введення оцінок
    cout << "Enter " << SIZE << " grades:\n";

    for (int i = 0; i < SIZE; i++)
    {
        cout << "Student " << (i + 1) << ": ";
        cin >> grades[i];
        sum += grades[i];  // Суму рахуємо відразу при введенні
    }

    // Обчислення середнього
    double average = (double)sum / SIZE;

    // Пошук max/min
    int maxGrade = grades[0];
    int minGrade = grades[0];

    for (int i = 1; i < SIZE; i++)
    {
        if (grades[i] > maxGrade) maxGrade = grades[i];
        if (grades[i] < minGrade) minGrade = grades[i];
    }

    // Вивід статистики
    cout << "\n--- Results ---\n";
    cout << "Average: " << average << "\n";
    cout << "Max:     " << maxGrade << "\n";
    cout << "Min:     " << minGrade << "\n";

    // Хто вище середнього?
    cout << "\nAbove average:\n";

    for (int i = 0; i < SIZE; i++)
    {
        if (grades[i] > average)
        {
            cout << "  Student " << (i + 1)
                 << ": " << grades[i] << "\n";
        }
    }

    return 0;
}

Важлива деталь — рядок 18: накопичуємо суму sum += grades[i] прямо під час введення. Це дозволяє обійтися одним циклом замість двох (введення + підрахунок суми). Після виходу з циклу і масив заповнений, і сума порахована.

Приклад роботи:

Enter 5 grades:
Student 1: 78
Student 2: 92
Student 3: 55
Student 4: 88
Student 5: 67

--- Results ---
Average: 76
Max:     92
Min:     55

Above average:
  Student 1: 78
  Student 2: 92
  Student 4: 88

Практичні завдання

Рівень 1 — Базовий

Рівень 2 — Логічний

Рівень 3 — Творчий

Підсумок

📌 Оголошення

тип ім'я[розмір]; — масив однотипних елементів. Розмір — через const константу. Ініціалізація = {0} гарантує відсутність сміттєвих значень.

📌 Індексація

Елементи — від [0] до [N-1]. Вихід за межі ([N] або [-1]) — невизначена поведінка без помилки компіляції.

📌 Обхід

Класичний for (int i = 0; i < SIZE; i++). Range-based for (int x : arr) — якщо індекс не потрібен.

📌 Алгоритми

Сума/середнє — накопичувач. Min/max — ініціалізація першим елементом. Пошук — foundIndex = -1. Обмін — через temp.

📌 2D масив

тип ім'я[рядки][стовпці]. Обхід — двома вкладеними циклами. Доступ: arr[row][col].

📌 Сортування

Bubble sort: N-1 проходів, порівняння сусідів, обмін через temp. Простий, але O(n²) — для навчання.
Copyright © 2026