← До переліку лабораторних робіт

Лабораторна робота №8. Покажчики та посилання. Покажчики на функції

Варіант 19 · Виконав: Одарчук Олексій, КНУ імені Тараса Шевченка, ФІТ, група ІПЗ-11 · C++17, g++

1. Мета роботи

2. Умова задачі

Розробити програму, команди меню якої передбачають такі дії (таблиця 8.1, варіант 19):

Обов'язкові вимоги до виконання

Невиконання пунктів 1–5 неприпустимо і призводить до штрафних санкцій у 50% зниження балів:

  1. програма повинна мати меню команд, виконання яких здійснювати через покажчики на функції;
  2. пам'ять під масив слід виділяти та звільняти динамічно з використанням покажчиків;
  3. передачу масивів у функції слід здійснювати через покажчики;
  4. функції мають повертати масиви через покажчики;
  5. доступ до елементів масивів здійснювати через покажчики.

Обмеження умови: забороняється використовувати STL, методи класів колекцій (сортування, пошуку та інші).

3. Аналіз задачі та теоретичне обґрунтування

Мова реалізації

Роботу виконано мовою C++. Причина — тема роботи: робота називається «Покажчики та посилання», її мета — «опанувати технологію застосування посилальних типів даних», а теоретичні відомості містять окремий підрозділ про посилання з синтаксисом <тип> & <ідентифікатор>. Три контрольні питання з одинадцяти стосуються саме посилань: «Дати поняття посилальних типів даних», «Як оголосити покажчик на певний тип та посилання на змінну», «Як ініціалізувати покажчик та посилання».

Посилання — конструкція, якої в мові C не існує. Символ & у C має лише одне значення — унарна операція взяття адреси (int *b = &a;), і вона є в обох мовах. Оголошення ж посилання int &b = a;, де b стає альтернативним іменем змінної a, компілятор мови C відхиляє як синтаксичну помилку. Тому виконати цю роботу мовою C означало б не показати половину її теми.

Як виконано кожну з п'яти обов'язкових вимог

Вимога Реалізація Де в коді
1. Меню через покажчики на функції Команди зібрано в масив структур MenuItem, кожна містить поле MenuAction action — покажчик на функцію типу void (*)(). Потрібний пункт знаходиться зсувом покажчика на початок таблиці, і команда викликається через покажчик: (g_menu + choice - 1)->action(). Операторів switch чи ланцюжків if для вибору команди в програмі немає взагалі. typedef void (*MenuAction)(void);, структура MenuItem, масив g_menu[], виклик у main()
2. Динамічна пам'ять Масиви створюються оператором new int[n] і звільняються оператором delete[]. Кожен масив адресується покажчиком; попередній масив звільняється перед створенням нового, а перед виходом з програми звільняється поточний. createArray(), copyArray(), cmdCreate(), кінець main()
3. Передача масивів через покажчики Усі функції обробки приймають масив параметром типу int * або const int *. Модифікатор const застосовано там, де функція не змінює вихідний масив — це документує намір і дозволяє компілятору виявити випадкову зміну. printArray(), squareNegatives(), sortDescending(), sumOfEven(), copyArray()
4. Повернення масивів через покажчики Функції createArray(), copyArray(), squareNegatives(), sortDescending() повертають int * — покажчик на новий масив у динамічній пам'яті. заголовки відповідних функцій
5. Доступ до елементів через покажчики Звертання до елементів виконується виключно розіменуванням покажчика з адресною арифметикою: *(a + i), а подекуди — рухомим покажчиком у циклі for (int *p = a; p < a + n; ++p). Запису виду a[i] у програмі немає — зокрема й для таблиці команд меню. усі функції обробки

Застосування посилань поряд з покажчиками

Умова прямо вимагає передавати й повертати масиви виключно через покажчики (пункти 3–5), і ця вимога виконана без винятків. Проте функції обробки повертають додатково ще й скалярні значення — кількість замінених елементів, кількість обмінів, кількість парних чисел. Для них використано параметри-посилання виду int &changed.

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

Параметр-покажчик Параметр-посилання
Оголошення int *changed int &changed
У виклику f(..., &changed) — потрібна операція взяття адреси f(..., changed) — просто ім'я змінної
У тілі функції ++(*changed) — потрібне розіменування ++changed — як зі звичайною змінною
Може бути нульовим так — викличний код має передати дійсну адресу ні, завжди зв'язане зі змінною
Де в програмі readInt(), усі функції роботи з масивами squareNegatives(), sortDescending(), sumOfEven()

Параметр-покажчик використано у функції readInt(), тож обидва способи повернення значення наявні в одній програмі, і їх можна порівняти безпосередньо.

Чому кожне перетворення утворює новий масив

Умова вимагає «вивести на екран усі перетворення вхідного масиву». Якби перетворення виконувались на місці, вихідний масив був би знищений уже після першої операції, і показати послідовність стало б неможливо. Тому функції squareNegatives() та sortDescending() не змінюють вхідний масив, а будують новий і повертають покажчик на нього. Викличний код звільняє тимчасовий масив після виведення — саме тут і виникає ризик витоку пам'яті, тому кожному new[] відповідає рівно один delete[].

Алгоритм упорядкування

Бібліотечні методи сортування заборонені умовою, тому використано сортування вибором: на кожному кроці серед елементів, що лишилися невпорядкованими, знаходиться найбільший і переставляється на початок невпорядкованої частини. Трудомісткість — O(n²) порівнянь, але не більше n−1 обмінів, що для цієї задачі цілком прийнятно.

Визначення парності від'ємних чисел

Парність перевіряється умовою *p % 2 == 0, а не *p % 2 == 1. Це принципово: у мові C++ операція остачі для від'ємного діленого дає від'ємний результат, тобто (−3) % 2 дорівнює −1, а не 1. Перевірка на рівність нулю однаково правильна для чисел обох знаків.

Обмеження діапазону введення

Діапазон значень елементів обмежено 30 000 за модулем. Причина: квадрат від'ємного числа обчислюється в типі int і має вміститися в його межі, а 30 000² = 9·10⁸ менше за межу 2,15·10⁹, тоді як, наприклад, 100 000² = 10¹⁰ дав би переповнення. Межу задано однією сталою MAX_ABS_VALUE, яка застосовується до обох способів створення масиву — і до введення з клавіатури, і до генерації псевдовипадкових чисел. Саме однаковість межі для обох шляхів гарантує, що переповнення неможливе за жодних вхідних даних: значення поза межами відхиляється ще на введенні.

Рекомендована література (зі списку методичних вказівок): Ковалюк Т.В. Алгоритмізація та програмування. – Львів.: «Магнолія 2006», 2024. – 400 с.; Deitel P., Deitel H. C++ How to Program. Pearson Education, Inc. Hoboken, New Jersey. 2017. – 3015 p.

4. Блок-схема алгоритму

Рисунок 1 — Головний цикл: диспетчеризація команд через покажчики на функції
Рисунок 1 — Головний цикл: диспетчеризація команд через покажчики на функції
Рисунок 2 — Заміна від'ємних чисел їх квадратами (новий масив повертається через покажчик)
Рисунок 2 — Заміна від'ємних чисел їх квадратами (новий масив повертається через покажчик)
Рисунок 3 — Упорядкування за спаданням методом вибору
Рисунок 3 — Упорядкування за спаданням методом вибору
Рисунок 4 — Обчислення суми парних чисел
Рисунок 4 — Обчислення суми парних чисел

Схеми побудовано з текстів програм за допомогою rombik (rombik.app) відповідно до ДСТУ 19.701-90 (ISO 5807).

5. Текст програми

task1.cpp

Завантажити файл task1.cpp

/*==============================================================================
  Лабораторна робота №8. Варіант 19.
  Тема: покажчики та посилання. Покажчики на функції.

  Умова (таблиця 8.1, варіант 19): розробити програму, команди меню якої
  передбачають такі дії:
    - створити одновимірний масив додатних та від'ємних чисел, задавши їх
      кількість з клавіатури;
    - вивести створений масив на екран;
    - виконати такі операції:
        замінити від'ємні числа їх квадратами;
        упорядкувати масив за спаданням;
        визначити суму парних чисел;
    - вивести на екран усі перетворення вхідного масиву.

  ОБОВ'ЯЗКОВІ ВИМОГИ ДО ВИКОНАННЯ (невиконання — штраф 50% балів):
    1. Програма повинна мати меню команд, виконання яких здійснювати через
       покажчики на функції.
    2. Пам'ять під масив слід виділяти та звільняти динамічно з використанням
       покажчиків.
    3. Передачу масивів у функції слід здійснювати через покажчики.
    4. Функції мають повертати масиви через покажчики.
    5. Доступ до елементів масивів здійснювати через покажчики.

  Як виконано кожну вимогу:
    1. Команди меню зібрано в масив структур MenuItem, кожна з яких містить
       поле-покажчик на функцію. Команда викликається через покажчик:
       (g_menu + choice - 1)->action() — жодного switch чи ланцюжка if для
       вибору команди немає.
    2. Масиви створюються оператором new[] і звільняються оператором
       delete[]; кожен масив адресується покажчиком.
    3. Усі функції обробки приймають масив параметром типу int * / const int *.
    4. Функції createArray(), copyArray(), squareNegatives() і sortDescending()
       повертають покажчик на новий масив.
    5. Звертання до елементів виконується через розіменування покажчика
       з адресною арифметикою: *(a + i) або рухомий покажчик *p, а не a[i].

  ПОСИЛАННЯ. Тема роботи охоплює обидва посилальні типи — покажчики і
  посилання. Масиви передаються та повертаються лише через покажчики, як
  вимагають пункти 3-5. Скалярні значення, які функція повертає додатково
  до основного результату (лічильники замінених елементів, обмінів, парних
  чисел), передаються через параметри-посилання виду int &. Це відповідає
  теоретичним відомостям до роботи: «щоб повертати з функцій більше одного
  значення, слід передати функції аргументи-посилання або аргументи-
  покажчики». Параметр-покажчик для скалярного значення має функція
  readInt(), тож в одній програмі є обидва способи:
      - параметр-покажчик: у виклику потрібна операція & взяття адреси,
        у тілі функції — розіменування *; покажчик може бути нульовим;
      - параметр-посилання: у виклику передається просто ім'я змінної,
        у тілі функції з ним працюють як зі звичайною змінною; посилання
        завжди зв'язане зі своєю змінною.

  ОБМЕЖЕННЯ УМОВИ: забороняється використовувати STL, методи класів колекцій
  (сортування, пошуку та інші).

  Виконав: Одарчук Олексій, КНУ імені Тараса Шевченка, ФІТ, група ІПЗ-11.

  Компілятор: g++ -std=c++17
==============================================================================*/

#include <cstdio>
#include <cstdlib>
#include <ctime>

/* Найбільше за модулем значення елемента масиву. Обмеження потрібне, щоб
   квадрат елемента вміщався в тип int: 30000^2 = 9*10^8, тоді як межа int
   близько 2,1*10^9. Застосовується однаково до введення з клавіатури та до
   генерації псевдовипадкових чисел. */
const int MAX_ABS_VALUE = 30000;

/*==============================================================================
  Функції введення
==============================================================================*/

/*------------------------------------------------------------------------------
  readInt — прочитати ціле число із заданого діапазону з контролем введення.

  Параметри:
      prompt [вхідний]  — текст запрошення;
      value  [вихідний] — ПОКАЖЧИК на змінну, куди записується число;
      low, high [вхідні] — межі припустимого діапазону.
  Повертає : true — число прочитано; false — вхідні дані вичерпано.
------------------------------------------------------------------------------*/
bool readInt(const char *prompt, int *value, int low, int high)
{
    for (;;) {
        printf("%s", prompt);
        const int scanned = scanf("%d", value);

        if (scanned == EOF)
            return false;
        if (scanned == 1 && *value >= low && *value <= high)
            return true;

        if (scanned != 1) {
            int c;
            /* очищення буфера */
            while ((c = getchar()) != '\n' && c != EOF) {
            }
        }
        printf("Помилка: потрібне ціле число від %d до %d.\n", low, high);
    }
}

/*==============================================================================
  Функції обробки масивів.
  Усі приймають масив через покажчик і повертають новий масив через покажчик.
==============================================================================*/

/*------------------------------------------------------------------------------
  createArray — створити масив у динамічній пам'яті та заповнити його.

  Параметри:
      n      [вхідний] — кількість елементів;
      byHand [вхідний] — true: введення з клавіатури; false: генерація;
      low, high [вхідні] — межі діапазону для генерації.

  Повертає: покажчик на створений масив або nullptr, якщо введення перервано.
            Пам'ять виділяється оператором new[]; звільняти її має викличний код.

  Локальні змінні:
      array — покажчик на початок виділеної ділянки пам'яті;
      p     — рухомий покажчик для заповнення елементів.
------------------------------------------------------------------------------*/
int *createArray(int n, bool byHand, int low, int high)
{
    int *const array = new int[n];

    if (byHand) {
        printf("Уведіть цілі числа від %d до %d (усього %d):\n", -MAX_ABS_VALUE,
               MAX_ABS_VALUE, n);

        for (int *p = array; p < array + n; ++p) {
            char prompt[40];
            snprintf(prompt, sizeof prompt, "  a[%ld] = ", (long)(p - array));

            if (!readInt(prompt, p, -MAX_ABS_VALUE, MAX_ABS_VALUE)) {
                delete[] array; /* звільнити пам'ять при перериванні */
                return nullptr;
            }
        }
    } else {
        const int range = high - low + 1;

        /* Масштабування rand() на діапазон замість rand() % range: стандарт гарантує
           лише RAND_MAX >= 32767, і для ширшого діапазону остача не охопила б
           усіх значень. */
        for (int *p = array; p < array + n; ++p)
            *p = low + (int)((double)rand() / ((double)RAND_MAX + 1.0) * range);
    }

    return array;
}

/*------------------------------------------------------------------------------
  copyArray — створити копію масиву в новій ділянці динамічної пам'яті.

  Параметри:
      source [вхідний] — покажчик на вихідний масив;
      n      [вхідний] — кількість елементів.
  Повертає : покажчик на новий масив-копію.
------------------------------------------------------------------------------*/
int *copyArray(const int *source, int n)
{
    int *const result = new int[n];

    for (int i = 0; i < n; ++i)
        *(result + i) = *(source + i);

    return result;
}

/*------------------------------------------------------------------------------
  printArray — вивести масив на екран через покажчик.

  Параметри:
      title [вхідний] — заголовок;
      a     [вхідний] — покажчик на масив;
      n     [вхідний] — кількість елементів.
------------------------------------------------------------------------------*/
void printArray(const char *title, const int *a, int n)
{
    printf("%s", title);

    for (const int *p = a; p < a + n; ++p)
        printf(" %d", *p);

    printf("\n");
}

/*------------------------------------------------------------------------------
  squareNegatives — побудувати новий масив, у якому від'ємні числа замінено
                    їх квадратами.

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

  Параметри:
      source  [вхідний]  — покажчик на вихідний масив;
      n       [вхідний]  — кількість елементів;
      changed [вихідний] — ПОСИЛАННЯ на лічильник замінених елементів.

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

  Квадрат обчислюється в типі int, тому має вміщатися в його межі. Для цього
  значення елементів обмежено за модулем числом 30000 під час створення
  масиву: 30000^2 = 9*10^8 менше за межу int (близько 2,1*10^9).
------------------------------------------------------------------------------*/
int *squareNegatives(const int *source, int n, int &changed)
{
    int *result;

    result = new int[n];
    changed = 0;

    for (int i = 0; i < n; ++i) {
        const int value = *(source + i);

        if (value < 0) {
            *(result + i) = value * value;
            ++changed;
        } else {
            *(result + i) = value;
        }
    }

    return result;
}

/*------------------------------------------------------------------------------
  sortDescending — побудувати новий масив, упорядкований за спаданням.

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

  Параметри:
      source [вхідний]  — покажчик на вихідний масив;
      n      [вхідний]  — кількість елементів;
      swaps  [вихідний] — ПОСИЛАННЯ на лічильник обмінів.
  Повертає : покажчик на новий упорядкований масив.

  Локальні змінні:
      result  — покажчик на новий масив;
      maxPos  — індекс найбільшого елемента невпорядкованої частини;
      temp    — проміжне значення для обміну.
------------------------------------------------------------------------------*/
int *sortDescending(const int *source, int n, int &swaps)
{
    int *result;

    result = copyArray(source, n);
    swaps = 0;

    for (int i = 0; i < n - 1; ++i) {
        int maxPos = i;

        for (int j = i + 1; j < n; ++j)
            if (*(result + j) > *(result + maxPos))
                maxPos = j;

        if (maxPos != i) {
            const int temp = *(result + i);
            *(result + i) = *(result + maxPos);
            *(result + maxPos) = temp;
            ++swaps;
        }
    }

    return result;
}

/*------------------------------------------------------------------------------
  sumOfEven — сума парних чисел масиву.

  Парність визначається за остачею від ділення на 2. Для від'ємних чисел
  операція % у мові C++ може дати остачу -1, тому перевіряється рівність
  остачі нулю, а не одиниці — цей запис однаково правильний для обох знаків.

  Параметри:
      a     [вхідний]  — покажчик на масив;
      n     [вхідний]  — кількість елементів;
      count [вихідний] — ПОСИЛАННЯ на лічильник парних чисел.
  Повертає : сума парних чисел.

  Функція повертає ДВА значення: суму — як результат, кількість — через
  параметр-посилання. Це і є те застосування посилальних типів, про яке
  йдеться в теоретичних відомостях: «щоб повертати з функцій більше одного
  значення, слід передати функції аргументи-посилання або аргументи-покажчики».
------------------------------------------------------------------------------*/
long long sumOfEven(const int *a, int n, int &count)
{
    long long sum = 0;
    count = 0;

    for (const int *p = a; p < a + n; ++p)
        if (*p % 2 == 0) {
            sum += *p;
            ++count;
        }

    return sum;
}

/*==============================================================================
  Стан програми та команди меню.

  Кожна команда має однаковий заголовок, що дозволяє зберігати покажчики на
  них в одному масиві та викликати за індексом — це і є диспетчеризація
  через покажчики на функції.
==============================================================================*/

/* Поточний масив та його довжина. Оголошені глобально, щоб усі команди меню
   мали однаковий заголовок і могли зберігатися в спільному масиві покажчиків
   на функції. */
int *g_array = nullptr;
int g_size = 0;

/*------------------------------------------------------------------------------
  cmdCreate — команда меню: створити масив.
  Параметри: немає. Змінює глобальний стан g_array, g_size.
------------------------------------------------------------------------------*/
void cmdCreate(void)
{
    int n = 0;
    if (!readInt("Уведіть кількість елементів масиву (1..1000): ", &n, 1, 1000))
        return;

    printf("Спосіб створення:\n"
           "  1 - введення з клавіатури\n"
           "  2 - генерація псевдовипадкових чисел\n");

    int choice = 0;
    if (!readInt("Оберіть спосіб (1..2): ", &choice, 1, 2))
        return;

    int low = -100, high = 100;
    if (choice == 2) {
        if (!readInt("Уведіть нижню межу (від'ємну, не менше -30000): ", &low,
                     -MAX_ABS_VALUE, 0))
            return;
        if (!readInt("Уведіть верхню межу (додатну, не більше 30000): ", &high, 1,
                     MAX_ABS_VALUE))
            return;
        srand((unsigned)time(nullptr));
    }

    int *const created = createArray(n, choice == 1, low, high);
    if (created == nullptr)
        return;

    delete[] g_array; /* звільнити попередній масив */
    g_array = created;
    g_size = n;

    printf("\nМасив створено.\n");
    printArray("Вхідний масив:", g_array, g_size);
}

/*------------------------------------------------------------------------------
  cmdPrint — команда меню: вивести поточний масив.
------------------------------------------------------------------------------*/
void cmdPrint(void)
{
    if (g_array == nullptr) {
        printf("Масив ще не створено. Скористайтеся командою 1.\n");
        return;
    }
    printArray("Вхідний масив:", g_array, g_size);
}

/*------------------------------------------------------------------------------
  cmdSquareNegatives — команда меню: замінити від'ємні числа їх квадратами.
------------------------------------------------------------------------------*/
void cmdSquareNegatives(void)
{
    if (g_array == nullptr) {
        printf("Масив ще не створено. Скористайтеся командою 1.\n");
        return;
    }

    int changed = 0;
    int *const result = squareNegatives(g_array, g_size, changed);

    printArray("Вхідний масив:                   ", g_array, g_size);
    printArray("Від'ємні замінено їх квадратами: ", result, g_size);
    printf("Замінено елементів: %d\n", changed);

    delete[] result; /* тимчасовий масив більше не потрібен */
}

/*------------------------------------------------------------------------------
  cmdSortDescending — команда меню: упорядкувати масив за спаданням.
------------------------------------------------------------------------------*/
void cmdSortDescending(void)
{
    if (g_array == nullptr) {
        printf("Масив ще не створено. Скористайтеся командою 1.\n");
        return;
    }

    int swaps = 0;
    int *const result = sortDescending(g_array, g_size, swaps);

    printArray("Вхідний масив:            ", g_array, g_size);
    printArray("Упорядковано за спаданням:", result, g_size);
    printf("Виконано обмінів: %d\n", swaps);

    delete[] result;
}

/*------------------------------------------------------------------------------
  cmdSumOfEven — команда меню: визначити суму парних чисел.
------------------------------------------------------------------------------*/
void cmdSumOfEven(void)
{
    if (g_array == nullptr) {
        printf("Масив ще не створено. Скористайтеся командою 1.\n");
        return;
    }

    int count = 0;
    const long long sum = sumOfEven(g_array, g_size, count);

    printArray("Вхідний масив:", g_array, g_size);
    printf("Парних чисел: %d\n", count);
    printf("Сума парних чисел: %lld\n", sum);

    if (count == 0)
        printf("Парних чисел у масиві немає, тому сума дорівнює нулю.\n");
}

/*------------------------------------------------------------------------------
  cmdAll — команда меню: виконати всі перетворення послідовно й вивести їх.
           Відповідає вимозі умови «вивести на екран усі перетворення
           вхідного масиву».
------------------------------------------------------------------------------*/
void cmdAll(void)
{
    if (g_array == nullptr) {
        printf("Масив ще не створено. Скористайтеся командою 1.\n");
        return;
    }

    int changed = 0, swaps = 0, count = 0;

    int *const squared = squareNegatives(g_array, g_size, changed);
    int *const sorted = sortDescending(squared, g_size, swaps);
    const long long sum = sumOfEven(sorted, g_size, count);

    printf("\nУсі перетворення вхідного масиву\n");
    printArray("  1. Вхідний масив:                       ", g_array, g_size);
    printArray("  2. Від'ємні замінено їх квадратами:     ", squared, g_size);
    printArray("  3. Упорядковано за спаданням:           ", sorted, g_size);
    printf("  4. Сума парних чисел упорядкованого масиву: %lld "
           "(парних чисел: %d)\n",
           sum, count);
    printf("\n  Замінено від'ємних: %d, обмінів під час сортування: %d\n", changed,
           swaps);

    delete[] sorted;
    delete[] squared;
}

/*------------------------------------------------------------------------------
  cmdExit — команда меню: завершення роботи. Тіло порожнє: саме завершення
            виконує головний цикл, а команда потрібна для однорідності меню.
------------------------------------------------------------------------------*/
void cmdExit(void)
{
}

/*==============================================================================
  Опис меню: назва пункту та ПОКАЖЧИК НА ФУНКЦІЮ, що його виконує.
==============================================================================*/

/* Тип «покажчик на функцію без параметрів, яка не повертає значення». */
typedef void (*MenuAction)(void);

/* Пункт меню: текст і покажчик на функцію-обробник. */
typedef struct {
    const char *title;
    MenuAction action;
} MenuItem;

/* Таблиця команд. Потрібний пункт знаходиться зсувом покажчика на початок
   таблиці, без операторів switch чи ланцюжків if. */
const MenuItem g_menu[] = {
    {"створити масив", cmdCreate},
    {"вивести масив", cmdPrint},
    {"замінити від'ємні числа їх квадратами", cmdSquareNegatives},
    {"упорядкувати масив за спаданням", cmdSortDescending},
    {"визначити суму парних чисел", cmdSumOfEven},
    {"виконати та вивести всі перетворення", cmdAll},
    {"вихід", cmdExit}};

const int MENU_SIZE = (int)(sizeof g_menu / sizeof g_menu[0]);

/*------------------------------------------------------------------------------
  Головна функція. Відображає меню та викликає обрану команду через
  покажчик на функцію.

  Локальні змінні:
      choice — номер обраного пункту меню.
------------------------------------------------------------------------------*/
int main(void)
{
    printf("Лабораторна робота №8 (варіант 19)\n");
    printf("Виконав: студент групи ІПЗ-11 Одарчук Олексій\n");
    printf("Обробка одновимірного масиву\n");

    for (;;) {
        printf("\n============================================================\n");
        printf("Меню команд:\n");

        for (int i = 0; i < MENU_SIZE; ++i)
            printf("  %d - %s\n", i + 1, (g_menu + i)->title);

        int choice = 0;
        char prompt[40];
        snprintf(prompt, sizeof prompt, "Оберіть команду (1..%d): ", MENU_SIZE);
        if (!readInt(prompt, &choice, 1, MENU_SIZE)) {
            printf("\nВхідні дані вичерпано. Завершення роботи.\n");
            break;
        }

        printf("\n");

        /* Виклик команди через покажчик на функцію. */
        (g_menu + choice - 1)->action();

        if ((g_menu + choice - 1)->action == cmdExit) {
            printf("Завершення роботи.\n");
            break;
        }
    }

    /* Звільнення динамічної пам'яті перед виходом. */
    delete[] g_array;
    g_array = nullptr;

    return 0;
}

6. Результати виконання роботи

Компіляція: make (g++ -std=c++17, прапорці -Wall -Wextra -pedantic -O2). Попереджень компілятора немає.

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

Екранна копія 1 (частина 1 з 2) — повний сценарій роботи з меню: створення масиву, окремі операції та всі перетворення
Екранна копія 1 (частина 1 з 2) — повний сценарій роботи з меню: створення масиву, окремі операції та всі перетворення
Екранна копія 1 (частина 2 з 2) — повний сценарій роботи з меню: створення масиву, окремі операції та всі перетворення
Екранна копія 1 (частина 2 з 2) — повний сценарій роботи з меню: створення масиву, окремі операції та всі перетворення

Повний протокол виконання (task1.txt) — повний протокол, 422 рядків

7. Аналіз достовірності результатів

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

Перевірка розрахунків на калькуляторі

Нижче наведено екранні копії обчислень у калькуляторі Wolfram|Alpha; поруч із кожною — результат програми.

Калькулятор: 36+8+4+2 — сума парних чисел після перетворень
Калькулятор: 36+8+4+2 — сума парних чисел після перетворень

Калькулятор: 50; програма: 50. Значення збігаються.

Перевірка перетворень ручним розрахунком

Вхідний масив: 5, −3, 8, −6, 7, 2, −1, 4.

Крок Ручний розрахунок Результат програми
Заміна від'ємних квадратами (−3)² = 9, (−6)² = 36, (−1)² = 1 → 5, 9, 8, 36, 7, 2, 1, 4; замінено 3 елементи 5 9 8 36 7 2 1 4, замінено 3
Упорядкування за спаданням 36, 9, 8, 7, 5, 4, 2, 1 36 9 8 7 5 4 2 1
Сума парних чисел парні: 36, 8, 4, 2 → 36 + 8 + 4 + 2 = 50, парних чисел 4 50, парних чисел 4

Усі три перетворення збігаються з ручним розрахунком.

Окремо слід розрізняти два значення суми парних чисел, які виводить програма. Команда «Визначити суму парних чисел» працює з вхідним масивом: парні там 8, −6, 2, 4, і сума дорівнює 8. Ланцюжок «Виконати та вивести всі перетворення» обчислює суму для перетвореного масиву, де від'ємні вже замінено квадратами: парні 36, 8, 4, 2, сума 50. Обидва значення перевірено вручну; різниця не є розбіжністю, а наслідком того, що суму беруть на різних кроках ланцюжка перетворень.

Перевірка граничних випадків

Випадок Очікувана поведінка Результат програми
Масив без парних чисел (1, 3, −5, 7) Сума дорівнює 0, потрібне окреме повідомлення Сума парних чисел: 0, виведено «Парних чисел у масиві немає, тому сума дорівнює нулю»
Виклик операції до створення масиву Повідомлення замість звертання за нульовим покажчиком «Масив ще не створено. Скористайтеся командою 1.»
Вибір неіснуючого пункту меню Повторний запит без аварійного завершення «Помилка: потрібне ціле число від 1 до 7»
Повторне створення масиву Попередній масив звільняється, витоку немає підтверджено перевіркою пам'яті (нижче)

Перевірка межі значень елементів

Введене значення Очікувана поведінка Результат програми
−30 000 (найменше допустиме) приймається; квадрат 900 000 000 вміщується в int приймається, квадрат 900000000
30 000 (найбільше допустиме) приймається приймається
−100 000 (поза межами) відхиляється на введенні, бо 100 000² = 10¹⁰ не вміщується в int «Помилка: потрібне ціле число від −30000 до 30000»

Граничні значення перевірено й засобом діагностики UndefinedBehaviorSanitizer, який фіксує знакове переповнення цілих чисел: для обох граничних значень діагностика не виявила жодної помилки.

Перевірка коректності роботи з динамічною пам'яттю

Оскільки вся робота лабораторної побудована на динамічній пам'яті та покажчиках, коректність роботи з пам'яттю перевірено інструментально. Програму зібрано з увімкненими засобами діагностики AddressSanitizer та UndefinedBehaviorSanitizer:

g++ -std=c++17 -Wall -Wextra -g -fsanitize=address,undefined -o task1 task1.cpp
ASAN_OPTIONS=detect_leaks=1 ./task1

На повному сценарії (створення масиву, всі перетворення, повторні операції, вихід) діагностика не виявила жодної помилки: немає витоків пам'яті, немає звертань за межі виділених ділянок, немає повторного звільнення та невизначеної поведінки. Це підтверджує, що кожному new[] відповідає рівно один delete[], а адресна арифметика не виходить за межі масивів.

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

8. Висновки