Л1. Від задачі до першої програми C++
Коротко про лекцію
Почнемо з задачі, яку легко порахувати вручну. Далі розберемо, як програма зберігає значення, як ці значення отримують імена та як із простих інструкцій складається перша повна програма.
Ця лекція починає програмування з нуля. Кожна нова конструкція з’являється після пояснення її ролі. Тому спочатку розберемо задачу й дані, а повну програму зберемо з уже знайомих частин.
Практичний сенс. Код легше читати, коли кожний його рядок пов’язаний із конкретною дією задачі.
1. Спочатку розуміємо задачу
1.1. Рахуємо один випадок вручну
Автомобіль має проїхати 120 км. Середня витрата становить 7.5 л на 100 км. Один літр пального коштує 58 грн.
Потрібно знайти вартість пального для цієї поїздки. Спочатку визначимо кількість витрачених літрів:
120 × 7.5 / 100 = 9 л
Потім обчислимо вартість:
9 × 58 = 522 грн
Отже для цих даних очікуємо 522 грн. Пізніше програма повинна отримати такий самий результат.
1.2. Записуємо дії по черзі
Комп’ютер виконує інструкції у визначеному порядку. Тому розв’язання треба поділити на окремі зрозумілі дії.
Для нашої задачі достатньо шести кроків:
- Взяти відстань
120 км. - Взяти витрату
7.5 л на 100 км. - Взяти ціну
58 грн за літр. - Обчислити кількість витраченого пального.
- Обчислити його вартість.
- Показати результат.
Таку впорядковану послідовність дій називають алгоритмом. Кожний крок має однозначно описувати одну потрібну дію.
1.3. Розрізняємо відомі та обчислені значення
Відстань, витрата й ціна відомі до початку розрахунку. Це вхідні дані. Кількість пального з’являється під час розрахунку, тому це проміжне значення.
Потрібна відповідь — вартість поїздки. Це результат задачі.
| Роль у задачі | Ім’я, яке використаємо в коді | Значення |
|---|---|---|
| відстань | distance_km |
120.0 |
| витрата | fuel_per_100_km |
7.5 |
| ціна літра | fuel_price |
58.0 |
| витрачене пальне | fuel_used |
9.0 |
| вартість | trip_cost |
522.0 |
У коді кожному потрібному значенню дамо змістовне ім’я. Наприклад, fuel_price нагадує, що число позначає ціну пального.
2. Як C++ зберігає дані
2.1. Значення, ім’я і тип
Програмі треба зберігати числа та звертатися до них під час обчислень. Для цього значення отримує ім’я. C++ також просить вказати тип даних.
Тип повідомляє, які значення ми плануємо зберігати. У цій лекції достатньо двох числових типів:
| Тип | Для чого використаємо | Приклади |
|---|---|---|
int |
цілі числа | 0, 4, 33 |
double |
числа з дробовою частиною | 7.5, 58.0, 120.0 |
Поки що важлива лише ця практична різниця. Перетворення типів, точність чисел і додаткові типи розберемо далі в курсі.
2.2. Цілі числа: int
Кількість квитків або копій зазвичай є цілим числом. Для такого значення використаємо тип int.
Перед кодом прочитайте рядок зліва направо: тип → ім’я → початкове значення.
int tickets{4};
У цьому записі:
| Частина | Значення |
|---|---|
int |
тип: зберігаємо ціле число |
tickets |
ім’я, через яке звертаємося до значення |
{4} |
початкове значення |
; |
кінець інструкції |
На початку курсу змінну зручно уявляти як іменоване місце, де програма зберігає значення. Тут змінна tickets зберігає ціле число 4.
2.3. Числа з дробовою частиною: double
Ціна або витрата можуть містити дробову частину. Для таких значень використаємо тип double.
double fuel_price{58.0};
double задає тип, fuel_price задає ім’я, а {58.0} задає перше значення. У C++ дробову частину записують через крапку.
Значення 58.0 математично дорівнює 58. Запис із .0 одразу показує, що ми працюємо з типом double.
2.4. Оголошення та ініціалізація
Рядок
int tickets{4};
одночасно створює ім’я tickets і задає йому перше значення. Введення нового імені з типом називають оголошенням. Надання початкового значення називають ініціалізацією.
Та сама схема працює для double:
double fuel_price{58.0};
Після цих інструкцій програма має два збережені значення: tickets = 4 і fuel_price = 58.0.
2.5. Як змінити значення
Звичайна змінна може отримати нове значення під час виконання програми. Запис = для вже створеної змінної означає присвоєння.
Спочатку обчислюється права частина. Після цього результат записується у змінну зліва.
Термін: коментар
Коментар пояснює код людині та не виконується як інструкція. У C++ запис // починає коментар до кінця поточного рядка.
int tickets{4}; // Спочатку tickets дорівнює 4.
tickets = 5; // Тепер tickets дорівнює 5.
Другий рядок не створює нову змінну. Він змінює значення вже створеної tickets.
2.6. Значення, яке змінювати не плануємо: const
У нашій задачі відстань 120 км є вхідним значенням одного розрахунку. Під час цього розрахунку змінювати її не потрібно.
Перед типом можна записати ключове слово const:
const double distance_km{120.0};
const означає, що після ініціалізації нове значення цій змінній присвоювати не можна. Такий запис допомагає явно зафіксувати задум.
Наприклад, цей рядок уже суперечив би const:
distance_km = 60.0; // Помилка: const-змінній не можна надати нове значення.
2.7. Записуємо знайому формулу
Тепер маємо всі поняття, потрібні для запису ручного розрахунку. У C++ множення позначають *, а ділення — /.
const double distance_km{120.0};
const double fuel_per_100_km{7.5};
const double fuel_price{58.0};
const double fuel_used{distance_km * fuel_per_100_km / 100.0};
const double trip_cost{fuel_used * fuel_price};
Перші три рядки зберігають вхідні дані. Четвертий рядок обчислює 9.0, а п’ятий отримує 522.0.
Імена дозволяють читати формулу через зміст задачі. fuel_used * fuel_price означає «кількість літрів помножити на ціну одного літра».
3. Як виглядає повна програма C++
До цього моменту ми працювали з окремими інструкціями. Тепер треба зібрати їх у повну програму.
Термін: компілятор
Компілятор читає код C++, перевіряє правила мови та готує програму до виконання. Діагностика компілятора допомагає знайти порушення цих правил.
3.1. Підключення виведення і точка початку
Для виведення використаємо готовий компонент стандартної бібліотеки C++. Стандартна бібліотека містить типи, функції та об’єкти, які постачаються разом із реалізацією мови.
Її основні імена згруповані в просторі імен std. Простір імен допомагає відрізняти однакові імена з різних частин програми.
Термін: простір імен std
Простір імен групує пов’язані імена та зменшує ризик конфліктів. Префікс std:: показує, що компонент належить стандартній бібліотеці C++.
Об’єкт стандартного виведення має ім’я cout, тому повний запис виглядає як std::cout. Щоб зробити потрібні оголошення доступними файлу, використаємо директиву препроцесора #include.
Директива препроцесора — службовий рядок, який обробляється до основної перевірки C++ коду компілятором. #include підключає оголошення з указаного заголовка. Заголовок <iostream> оголошує стандартні потокові засоби, зокрема std::cout.
#include <iostream>
На початковому етапі пишемо std:: явно. Такий запис допомагає одразу бачити походження бібліотечного компонента.
Виконання повної програми починається з main():
int main() {
// Тіло main() містить кроки, які програма виконує після запуску.
}
Запис int main() описує спеціальну функцію main. Функція — це іменований блок інструкцій; детально власні функції розберемо пізніше. int у записі main належить до опису функції, а не до оголошення змінної.
Фігурні дужки { і } позначають блок інструкцій. Інструкції всередині виконуються у записаному порядку.
3.2. Як показати текст і число
std::cout передає дані у стандартний вивід. Оператор — це позначення дії над значеннями. Тут оператор << додає наступну частину повідомлення до виведення.
std::cout << "Вартість поїздки: " << trip_cost << " грн\n";
Текст у подвійних лапках виводиться як написано. Ім’я trip_cost підставляє збережене числове значення. Послідовність \n переходить на новий рядок.
Крапка з комою ; завершує цю інструкцію.
3.3. Перша повна програма задачі
Тепер кожна основна частина коду вже знайома. Зберемо алгоритм у повну програму.
#include <iostream> // Підключає std::cout для виведення тексту і чисел.
int main() { // З main() починається виконання повної програми.
const double distance_km{120.0}; // Відстань: 120 км.
const double fuel_per_100_km{7.5}; // Витрата: 7.5 л на 100 км.
const double fuel_price{58.0}; // Ціна: 58 грн за літр.
const double fuel_used{distance_km * fuel_per_100_km / 100.0}; // 9 л.
const double trip_cost{fuel_used * fuel_price}; // 522 грн.
std::cout << "Вартість поїздки: " << trip_cost << " грн\n"; // Показує результат.
}
Після запуску очікуємо:
Вартість поїздки: 522 грн
Програма повторює ручний розрахунок. Спочатку вона зберігає вхідні дані, потім обчислює два нові значення й показує результат.
Збіг із 522 грн підтверджує цей конкретний випадок. Для впевненості пізніше перевіримо інші вхідні значення.
4. Як значення змінюються під час виконання
Ми вже бачили присвоєння tickets = 5. Тепер розглянемо кілька інструкцій, які залежать одна від одної.
4.1. Проходимо інструкції по черзі
#include <iostream>
int main() {
int points{5}; // Спочатку points дорівнює 5.
int bonus{points + 2}; // З поточного points обчислюємо bonus = 7.
points = bonus * 2; // Замінюємо points: тепер 14. bonus лишається 7.
std::cout << "бали = " << points << ", бонус = " << bonus << "\n";
}
Після першої інструкції points дорівнює 5. Друга інструкція використовує це значення й створює bonus = 7.
Третя інструкція змінює тільки points. Тому наприкінці маємо points = 14 і bonus = 7.
4.2. Записуємо значення після кожного кроку
| Після виконаної інструкції | points |
bonus |
|---|---|---|
int points{5}; |
5 | — |
int bonus{points + 2}; |
5 | 7 |
points = bonus * 2; |
14 | 7 |
Таблиця показує, як змінюються значення після кожної інструкції. Такий запис корисний, коли результат залежить від кількох послідовних кроків.
4.3. Називаємо два нові поняття
Поточні значення змінних утворюють стан програми. Після другої інструкції стан містить points = 5 і bonus = 7.
Покроковий запис стану після кожної інструкції називають трасуванням. Трасування допомагає знайти перший крок, після якого значення стало неправильним.
Тепер фраза «протрасувати фрагмент» має конкретний зміст: виконати інструкції по черзі та записати зміни значень.
Перед запуском
Для короткого фрагмента спочатку запишіть очікувані значення. Потім запустіть код і порівняйте результат зі своїм записом.
4.4. Порядок інструкцій змінює результат
Послідовність важлива навіть тоді, коли використовуються ті самі числа й операції.
int total{10};
total = total + 5; // 15
total = total * 2; // 30
Якщо поміняти дві останні інструкції місцями, отримаємо інший результат:
int total{10};
total = total * 2; // 20
total = total + 5; // 25
Під час читання коду стежте за формулами та порядком їх виконання.
5. Передбачаємо результат до запуску
Квиток коштує 85 грн, сервісний збір становить 20 грн, а покупець бере 4 квитки. Ручний розрахунок дає 360 грн.
#include <iostream>
int main() {
const int tickets{4}; // Кількість квитків.
const double price_per_ticket{85.0}; // Ціна одного квитка.
const double service_fee{20.0}; // Фіксований сервісний збір.
const double total{tickets * price_per_ticket + service_fee}; // 4 × 85 + 20 = 360.
std::cout << total << "\n"; // Очікуємо 360.
}
До запуску подумки змініть tickets з 4 на 5. Новий результат має дорівнювати 445.
Після цього змініть значення в коді та запустіть програму. Прогноз дає конкретне число для порівняння.
5.1. Кілька послідовних обчислень
Сервіс друку бере 3.5 грн за копію та 25 грн за підготовку. Для 12 копій отримаємо 42 грн, а потім 67 грн.
#include <iostream>
int main() {
const int copies{12}; // Кількість копій.
const double price_per_copy{3.5}; // Ціна однієї копії.
const double setup_fee{25.0}; // Фіксована підготовка замовлення.
const double subtotal{copies * price_per_copy}; // 12 × 3.5 = 42.
const double total{subtotal + setup_fee}; // 42 + 25 = 67.
std::cout << "Друк: " << subtotal << "\n";
std::cout << "Разом: " << total << "\n";
}
subtotal зберігає вартість копій. total додає фіксовану підготовку.
Якщо фактична відповідь неправильна, окремий subtotal допомагає перевірити перший етап обчислення.
6. Що робить компілятор
Ми вже назвали компілятор перед першою повною програмою. Тепер простежимо його роль під час пошуку помилок у коді.
Спрощено процес виглядає так:
код C++ → компілятор → програма → результат
Правила запису конструкцій мови називають синтаксисом. Якщо синтаксис порушений, компілятор зупиняється й показує діагностичне повідомлення.
Термін: синтаксис
Синтаксис — правила запису конструкцій мови. Компілятор перевіряє ці правила до виконання програми.
6.1. Пропущена крапка з комою
#include <iostream>
int main() {
const int items{3} // Помилка: після завершеної інструкції бракує крапки з комою.
std::cout << items << "\n";
}
Після {3} пропущено ;. Через це компілятор не може правильно відокремити одну інструкцію від наступної.
Під час виправлення спочатку прочитайте перше зрозуміле повідомлення. Потім перевірте названий рядок і попередній рядок.
6.2. Помилка в імені
C++ розрізняє імена символ за символом. quantity і quanity є різними іменами.
#include <iostream>
int main() {
const int quantity{3};
const double unit_price{42.5};
// Помилка: оголошено quantity, а нижче написано quanity.
const double subtotal{unit_price * quanity};
std::cout << subtotal << "\n";
}
У програмі створено ім’я quantity. У формулі написано quanity, якого програма не знає.
Ім’я, записане за правилами мови, також називають ідентифікатором. Для першого пошуку помилки достатньо порівняти два ідентифікатори посимвольно.
7. Коли код компілюється, а результат неправильний
Компілятор перевіряє правила C++. Він не знає, яку формулу вимагає задача.
Для 120 км і швидкості 60 км/год час руху дорівнює:
120 / 60 = 2 години
У наступній програмі навмисно використано іншу операцію.
#include <iostream>
int main() {
const double distance_km{120.0}; // Відстань: 120 км.
const double speed_kmh{60.0}; // Швидкість: 60 км/год.
const double time_hours{distance_km * speed_kmh}; // Помилка у формулі: тут потрібне ділення.
std::cout << time_hours << "\n"; // Програма надрукує 7200.
}
Множення дозволене правилами C++, тому програма компілюється. Але результат 7200 суперечить ручному значенню 2.
Таку ситуацію називають логічною помилкою. Програма виконується, але реалізує неправильне обчислення.
Для довшого фрагмента можна записати проміжні значення по кроках. Потім треба знайти перший крок, де фактичне значення відрізняється від очікуваного.
8. Робочий порядок для невеликої програми
Для перших задач використовуйте однакову послідовність:
- Прочитайте умову й назвіть потрібний результат.
- Визначте вхідні дані.
- Обчисліть один випадок вручну.
- Запишіть алгоритм короткими кроками.
- Виберіть імена та потрібні типи даних.
- Перенесіть кроки у C++.
- Перед запуском передбачте результат.
- Запустіть програму й порівняйте значення.
- За розбіжності знайдіть перший неправильний крок.
Після успішного запуску змініть одне вхідне значення. Наприклад, для 60 км за тих самих умов потрібно вдвічі менше пального. Вартість також повинна зменшитися вдвічі.
8.1. Помилки, які вже можна розпізнати
- Невдалий тип. Значення
3.5не варто зберігати якint, бо дробова частина має зміст для задачі. - Плутанина між ініціалізацією і присвоєнням.
int points{5};створює змінну, аpoints = 10;змінює вже наявне значення. - Невдале використання
const. Якщо значення треба змінювати далі, його не слід оголошувати якconst. - Очікування автоматичного перерахунку. Якщо
bonusуже обчислено зpoints, подальша змінаpointsсама по собі не змінитьbonus. - Довіра лише до компілятора. Успішна компіляція підтверджує коректність синтаксису, але не правильність формули задачі.
Ці випадки зручно перевіряти коротким ручним розрахунком і покроковим записом значень.
9. Інтерактивна самоперевірка лекції
Виконайте практичну самоперевірку. Після кожної відповіді доступні пояснення, підказка та розбір.