
- •Загальні положення
- •1.1 Мета роботи
- •1.2 Організація самостійної роботи студентів
- •1.3 Варіанти індивідуальних завдань
- •1.3 Контрольні питання та завдання
- •Мета роботи
- •Організація самостійної роботи студентів
- •Варіанти індивідуальних завдань
- •2.3 Контрольні питання та завдання
- •3.1 Мета роботи
- •Організація самостійної роботи студентів
- •Варіанти індивідуальних завдань
- •3.4 Контрольні питання та завдання
- •Мета роботи
- •Організація самостійної роботи студентів
- •4.3 Варіанти індивідуальних завдань
- •4.4 Контрольні питання та завдання
- •Мета роботи
- •Організація самостійної роботи студентів
- •Найбільш важливими з найпростіших структур даних є стек та черга. Ці структури зустрічаються в програмуванні на кожному кроці, у найрізноманітніших ситуаціях.
- •Варіанти індивідуальних завдань
- •5.4 Контрольні питання та завдання
- •6.1 Мета роботи
- •6.2 Організація самостійної роботи студентів
- •6.3 Варіанти індивідуальних завдань
- •6.4 Контрольні питання та завдання
- •7.1 Мета роботи
- •7.2 Організація самостійної роботи студентів
- •7.3 Варіанти індивідуальних завдань
- •7.4 Контрольні питання та завдання
- •8.1 Мета роботи
- •8.2 Організація самостійної роботи студентів
- •8.3 Варіанти індивідуальних завдань
- •8.4 Контрольні питання та завдання
- •9.1 Мета роботи
- •9.2 Організація самостійної роботи студентів
- •9.3 Варіанти індивідуальних завдань
- •9.4 Контрольні питання та завдання
- •10 Використання хеш-функцій
- •Мета роботи
- •10.2 Організація самостійної роботи студентів
- •10.3 Варіанти індивідуальних завдань
- •10.4 Контрольні питання та завдання
- •Перелік посилань
- •Додаток а інструкція з техніки безпеки при виконанні лабораторних робіт
- •61166, Харків, просп. Леніна, 14
1.3 Варіанти індивідуальних завдань
Для масиву 7
7 знайти максимальний елемент масиву та виконати сортування головної діагоналі за зменшенням значень її елементів.
Для масиву 7 7 знайти мінімальний елемент масиву та виконати сортування головної діагоналі за збільшенням значень її елементів.
Для матриці 6 6 визначити чи є вона магічним квадратом, тобто такою, у якої сума елементів у всіх строках та стовбцях є однаковими.
Для масиву 7 7 знайти строки з максимальним і мінімальним елементом та змінити їх місцями.
Для масиву 6 6 знайти стовбчики з мінімальним і максимальним елементом. Виконати сортування цих стовпчиків за збільшенням значень їх елементів.
Для масиву 7 7 знайти стовбчики з мінімальним і максимальним елементом масиву та замінити їх місцями.
Для матриці 7 7 визначити, чи є вона симетричною відповідно до головної діагоналі.
Для матриці 6 6 знайти стовбчики з мінімальним і максимальним елементом матриці та замінити їх місцями.
Для матриці 7 7 визначити координати всіх її седлових точок. Елемент матриці є седловою точкою, якщо він є найменшим у строчці та одночасно – найбільшим у стовпчику.
Для матриці 7 7 визначити координати всіх її седлових точок. Елемент матриці є седловою точкою, якщо він є найбільшим у строчці та одночасно – найменшим у стовбчику.
1.3 Контрольні питання та завдання
З яких частин складається Сі – програма?
Що таке заголовочні файли та файли реалізації? Їх призначення.
Назвіть основні типи змінних мови Сі.
Які типи змінних входять до базових?
Надайте характеристику цілочисельних типів даних: int, char, short, long.
Надайте характеристику дійсних типів даних: float, double.
Надайте характеристику типу даних void.
Надайте характеристику наступним конструкціям даних: масив, вказівник, структура.
Що таке вирази у мові Сі?
Назвіть арифметичні операції, які використовуються у мові Сі?
Що таке операції збільшення та зменшення? Їх форми.
Що таке операції „збільшення на”, „зменшення на”? Їх різновид.
Які логічні операції використовуються у мові Сі?
Що таке операції порівняння?
Які побітові логічні операції використовуються у мові Сі?
Які операції зміщення використовуються у мові Сі?
Надайте характеристику керуючим конструкціям мови Сі: if-else, if-else if, while, for.
Назвіть основні можливості інструментального засобу Borland C++ Builder.
З яких частин складаються такі програмні документи як специфікація та текст програми.
ВИВЧЕННЯ МОЖЛИВОСТЕЙ МОВИ С++ ПРИ РОБОТІ З ФУНКЦІЯМИ
Мета роботи
Вивчення можливостей мови С++ при роботі з функціями. Отримання практичних навичок по створенню, трансляції та налагодження програм, які використовують функції, у середовищі Borland C++ Builder.
Організація самостійної роботи студентів
Під час підготовки до виконання лабораторної роботи необхідно вивчити індивідуальне завдання (п. 2.3), виконати розробку алгоритму вирішення задачі та підготовити текст програми щодо реалізації розробленого алгоритму, підготовити відповідні розділи звіту та вивчити відповідний теоретичний матеріал, який викладено у лекціях: „Керуючі конструкції мови С++”, „Представлення програми у вигляді функції”, та у наступних підручниках і навчальних посібниках [1, 2, 4, 5, 7, 8].
Завжди перед використанням або реалізацією функції необхідно описати її прототип. Прототип функції включає інформацію про ім'я функції, типі значення, що повертається, кількості та типах її аргументів. Приклад:
int gcd(int x, int y);
Тут описано прототип функції gcd, що повертає ціле значення із двома цілими аргументами. Імена аргументів x та y тут є лише коментаріями, тому що не несуть ніякої інформації для компілятора. Їх можна опускати, наприклад, опис
int gcd(int, int);
є цілком припустимим.
Описи прототипів функцій взагалі виносяться в заголовні файли. Для коротких програм, які містяться в одному файлі, опис прототипів розташовують на початку програми. У мові Сі функціям передаються значення фактичних параметрів. При виклику функції значення параметрів копіюються в апаратний стек. Варто чітко розуміти, що зміна формальних параметрів у тілі функції не призводить до зміни змінних програми, переданих функції при її виклику – адже функція працює не із самими цими змінними, а з копіями їхніх значень! Розглянемо наступний приклад фрагменту програми:
void f(int x); // Опис прототипу функції
int main() {
. . .
int x = 5;
f(x);
// Значення x як і раніше дорівнює 5
. . .
}
void f(int x) {
. . .
x = 0; // Зміна формального параметра
. . . // не приводить до зміни фактичного
// параметра в зухвалій програмі
}
Тут функція main викликає функцію f, якій передається значення змінної x, рівне п'яти. Незважаючи на те, що в тілі функції f формальному параметру x присвоюється значення 0, значення змінної x у функції main не міняється.
Якщо необхідно, щоб функція могла змінити значення змінних програми, треба передавати їй покажчики на ці змінні. Тоді функція може записати будь-яку інформацію з переданих адрес. У Сі в такий спосіб реалізуються вихідні та вхідно-вихідні параметри функцій.
У традиційних мовах програмування, таких як Сі, Фортран, Паскаль, існують три види пам'яті: статична, стекова та динамічна. Звичайно, з фізичної точки зору, ніяких різних видів пам'яті немає: оперативна пам'ять – це масив байтів, кожний байт має адреса, починаючи з нуля. Коли говориться про види пам'яті, маються на увазі способи організації роботи з нею, включаючи виділення та звільнення пам'яті, а також методи доступу.
Статична пам'ять виділяється ще до початку роботи програми, на стадії компіляції та складання. Статичні змінні мають фіксовану адресу, відому до запуску програми та не змінну в процесі її роботи. Статичні змінні створюються та ініціалізуються до входу у функцію main, з якої починається виконання програми.
Існує два типи статичних змінних:
глобальні змінні – це змінні, визначені поза функціями, в описі яких відсутнє слово static. Описи глобальних змінних, що включають слово extern, виносяться в заголовні файли ( h-файли). Слово extern означає, що змінна описується, але не створюється в даній частині програми. Визначення глобальних змінних, тобто опис без слова extern, міститься у файлах реалізації (c-файли або cpp-файли). Приклад: глобальна змінна maxind описується двічі:
в h-файлі за допомогою рядка:
extern int maxind;
цей опис повідомляє про наявність такий змінної, але не створює цю змінну!
в cpp-файлі за допомогою рядка:
int maxind = 1000;
цей опис створює змінну maxind та встановлює її початкове значення 1000. Помітимо, що стандарт мови не вимагає обов'язкового присвоєння початкових значень глобальним змінним, але це краще робити завжди, інакше в змінній буде знаходитися непередбачене значення (сміття, як говорять програмісти). Ініціалізація всіх глобальних змінних при їхньому визначенні – це правило гарного стилю. Глобальні змінні називаються так тому, що вони доступні в будь-якій частині програми, у всіх її файлах. Тому імена глобальних змінних повинні бути досить довгими, щоб уникнути випадкового збігу імен двох різних змінних. Наприклад, імена x або n для глобальної змінної не підходять;
статичні змінні – це змінні, в описі яких присутнє слово static. Як правило, статичні змінні описуються поза функціями. Такі статичні змінні у всьому подібні глобальними, з одним виключенням: область видимості статичної змінної обмежена одним файлом, усередині якого вона визначена – і, більше того, її можна використовувати тільки після її опису, тобто нижче по тексту. Із цієї причини описи статичних змінних виносяться на початок файлу. На відміну від глобальних змінних, статичні змінні ніколи не описуються в h-файлах (модифікатори extern і static конфліктують між собою).Порада: використовуйте статичні змінні, якщо потрібно, щоб вони були доступні тільки для функцій, описаних усередині того самого файлу. По можливості не застосовуйте в таких ситуаціях глобальні змінні, це дозволить уникнути конфліктів імен при реалізації великих проектів, що складаються із сотень файлів.
Статичну змінну можна описати й усередині функції, хоча звичайно так ніхто не робить. Змінна розміщається не в стеку, а в статичній пам'яті, тобто її не можна використовувати при рекурсії, а її значення зберігається між різними входами у функцію. Область видимості такий змінної обмежена тілом функції, у якій вона визначена. В іншому вона подібна статичній або глобальній змінній. Помітимо, що ключове слово static у мові Сі використовується для двох різних цілей:
як вказівка типу пам'яті: змінна розташовується в статичній пам'яті, а не в стеку;
як спосіб обмежити область видимості змінної рамками одного файлу (у випадку опису змінної поза функцією).
Слово static може бути присутнім і в заголовку функції. При цьому воно використовується тільки для того, щоб обмежити область видимості ім'я функції рамками одного файлу. Приклад:
static int gcd(int x, int y); // Прототип ф-ции
. . .
static int gcd(int x, int y) { // Реалізація
. . .
}
Порада: використовуйте модифікатор static у заголовку функції, якщо відомо, що функція буде викликатися лише усередині одного файлу. Слово static повинне бути присутнім як в описі прототипу функції, так і в заголовку функції при її реалізації.
Локальні, або стекові, змінні – це змінні, описані усередині функції. Пам'ять для таких змінних виділяється в апаратному стеку. Пам'ять виділяється в момент входу у функцію або блок і звільняється в момент виходу з функції або блоку. При цьому захоплення та звільнення пам'яті відбуваються практично миттєво, тому що комп'ютер тільки змінює регістр, що містить адресу вершини стека.
Локальні змінні можна використовувати при рекурсії, оскільки при повторному вході у функцію в стеку створюється новий набір локальних змінних, а попередній набір не руйнується. По цій же причині локальні змінні безпечні при використанні ниток у паралельному програмуванні. Програмісти називають таку властивість функції реентерабельністю, від англ. re-enter able – можливість повторного входу. Це дуже важлива якість із погляду надійності та безпеки програми! Програма, що працює зі статичними змінними, цією властивості не має, тому для захисту статичних змінних доводиться використовувати механізми синхронізації, а логіка програми різко ускладнюється. Завжди варто уникати використання глобальних та статичних змінних, якщо можна обійтися локальними.
Недоліки локальних змінних є продовженням їхніх достоїнств. Локальні змінні створюються при входженні до функції, а зникають після виходу з неї, тому їх не можна використовувати як дані, що поділяються між декількома функціями. До того ж, розмір апаратного стека обмежений, стек може переповнитися (наприклад, при глибокій рекурсії), що призведе до катастрофічного завершення програми. Тому локальні змінні не повинні мати великого розміру. Зокрема, не можна використовувати великі масиви як локальні змінні.
Крім статичної та стекової пам'яті, існує ще практично необмежений ресурс пам'яті, що називається динамічна, або купа (heap). Програма може захоплювати ділянки динамічної пам'яті потрібного розміру. Після використання раніше захоплена ділянка динамічної пам'яті її необхідно звільнити. Під динамічну пам'ять приділяється простір віртуальної пам'яті процесу між статичною пам'яттю та стеком. Стек розташовується в старших адресах віртуальної пам'яті та росте убік зменшення адрес. Програма та константні дані розміщаються в молодших адресах, вище розташовуються статичні змінні. Простір вище статичних змінних та нижче стека займає динамічна пам'ять.
Структура динамічної пам'яті автоматично підтримується виконуючою системою мови C++. Динамічна пам'ять складається із захоплених і вільних сегментів, кожному з яких передує опис сегмента. При виконанні запиту на захоплення пам'яті виконуюча система робить пошук вільного сегмента достатнього розміру та захоплює в ньому відрізок необхідної довжини. При звільненні сегмента пам'яті він позначається як вільний, при необхідності декілька вільних сегментів, що ідуть підряд, поєднуються.
У мові С++ для захоплення та звільнення динамічної пам'яті застосовуються оператори new та delete. Вони є частиною мови C++.
Нехай T – деякий тип мови C++, p – покажчик на об'єкт типу T. Тоді для захоплення пам'яті розміром в один елемент типу T використовується оператор new:
T *p;
p = new T;
Наприклад, для захоплення восьми байтів під дійсне число типу double використовується фрагмент
double *p;
p = new double;
Оператор new зручний тим, що можна присвоїти початкове значення об'єкту, створеному в динамічній пам'яті (тобто виконати ініціалізацію об'єкта). Для цього початкове значення записується в круглих дужках після імені типу після слова new. Наприклад, у наведеному нижче рядку захоплюється пам'ять під дійсне число, якому присвоюється початкове значення 1.5:
double *p = new double(1.5);
Цей фрагмент еквівалентний фрагменту:
double *p = new double;
*p = 1.5;
За допомогою оператора new можна захоплювати пам'ять під масив елементів заданого типу. Для цього у квадратних дужках вказується довжина захоплюваного масиву, що може представлятися будь-яким цілочисельним виразом. Наприклад, у наступному фрагменті в динамічній пам'яті створюється фрагмент для зберігання дійсної матриці розміру m*n:
double *a;
int m = 100, n = 101;
a = new double[m * n];
Таку форму оператора new іноді називають векторною.
Оператор delete звільняє пам'ять, захоплену раніше за допомогою оператора new, наприклад,
double *p = new double(1.5); //Захоплення та ініціалізація
. . .
delete p; // Звільнення пам'яті
Якщо пам'ять під масив була захоплена за допомогою векторної форми оператора new, то для її звільнення необхідно використовувати векторну форму оператора delete, у якій після слова delete записуються порожні квадратні дужки:
double *a = new double[100]; // Захоплюємо масив
. . .
delete[] a; // Звільняємо масив
Для масивів, що складаються з елементів базових типів Сі, при звільненні пам'яті можна використовувати звичайну форму оператора delete. Єдина відмінність векторної форми: при звільненні масиву елементів класу, у якому визначений деструктор, тобто завершальна дія перед знищенням об'єкта, цей деструктор викликається для кожного елемента знищуваного масиву. Оскільки для базових типів деструктори не визначені, векторна та звичайна форми оператора delete для них еквівалентні.
Приємна особливість оператора delete полягає в тому, що при звільненні нульового покажчика нічого не відбувається. Наприклад, як видно, наступний фрагмент програми цілком коректний:
double *a = 0; // Нульовий покажчик
bool b;
. . .
if (b) {
a = new double[1000];
. . .
}
. . .
delete[] a;
Тут у покажчик a спочатку записується нульова адреса. Потім, якщо справедлива деяка умова, захоплюється пам'ять під масив. Таким чином, при виконанні оператора delete покажчик a містить або нульове значення, або адресу масиву. У першому випадку оператор delete нічого не робить, у другому звільняє пам'ять, зайняту масивом. Така технологія застосовується практично всіма програмістами на C++: завжди ініціалізувати покажчики на динамічну пам'ять нульовими значеннями і не мати ніяких проблем при звільненні пам'яті.