You are on page 1of 162

МIНIСТЕРСТВО ОСВIТИ I НАУКИ УКРАЇНИ

НАЦIОНАЛЬНИЙ ТЕХНIЧНИЙ УНIВЕРСИТЕТ УКРАЇНИ


«КИЇВСЬКИЙ ПОЛIТЕХНIЧНИЙ IНСТИТУТ
iменi Iгоря Сiкорського»

ОСНОВИ АЛГОРИТМIЗАЦIЇ ТА ПРОГРАМУВАННЯ


Частина 1. Основи алгоритмiзацiї

Курс лекцiй

Навчальний посiбник

Рекомендовано Методичною радою КПI iм. Iгоря Сiкорського


як навчальний посiбник для здобувачiв ступеня бакалавра
за освiтньою програмою «Системи керування лiтальними апаратами та комплексами»
спецiальностi 173 «Авiонiка»

Укладач М.М.Чепiлко

Електронне мережне навчальне видання

Київ
КПI iм. Iгоря Сiкорського
2022
Рецензент: Смiрнов С.А., канд.фiз.-мат.наук, доцент, навчально - науковий фiзико - те-
хнiчний iнститут

Вiдповiдальний редактор: Пономаренко С.О., кандидат технiчних наук, доцент

Гриф надано Методичною радою КПI iм. Iгоря Сiкорського


(Протокол № 6 вiд 24.06.2022 р.)
за поданням Вченої ради навчально - наукового iнституту аерокосмiчних технологiй
(Протокол № 5/22 вiд 31.05.2022 р.)

Видання навчального посiбника ”Основи алгоритмiзацiї та програмування. Частина 1.


Основи алгоритмiзацiї. Курс лекцiй” призначене для самостiйної роботи студнтiв над те-
оретичним матерiалом стосовно положень та методiв алгоритмiзацiї i функцiонального
(процедурного) програмування на мовi С++, ознайомлення з методами побудови фра-
гментiв програмних додаткiв.
Розглянуто засоби програмування як базових алгоритмiв, так i обробки структурованих
типiв даних, роботу з покажчиками, засоби динамiчного керування пам’яттю тощо. Значну
увагу придiлено опрацюванню динамiчних структур даних.

Реєстр. №21/22-601. Обсяг 7.4 авт.арк.

Нацiональний технiчний унiверситет України


«Київський полiтехнiчний iнститут iменi Iгоря Сiкорського»
проспект Перемоги, 37, м. Київ, 03056
https://kpi.ua
Свiдоцтво про внесення до Державного реєстру видавцiв, виготовлювачiв
i розповсюджувачiв видавничої продукцiї ДК №5354 вiд 25.05.2017 р.

c КПI iм. Iгоря Сiкорського, 2022


3

Змiст

Вступ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1 Алгоритми та їх властивостi . . . . . . . . . . . . . . . . . . . . 8
1.1 Алгоритми та правила їх графiчного запису . . . . . . . 8
1.2 Програмне забезпечення для реалiзацiї алгоритмiв . . . . 15
1.3 Питання та завдання для самоконтролю знань . . . . . . 17
2 Основи мови програмування С/С++ . . . . . . . . . . . . . . . . 18
2.1 Алгоритмiзацiя структури для програми у мовi С/С++ . 18
2.2 Введення i виведення у консолi . . . . . . . . . . . . . . . 20
2.3 Змiннi у мовi програмування C++ . . . . . . . . . . . . . 23
2.4 Формалiзм функцiй у мовi програмування С/С++ . . . . 26
2.5 Питання та завдання для самоконтролю знань . . . . . . 27
3 Типи данних та арифметичнi операцiї у мовi програмування
С/С++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
3.1 Основнi типи данных у мовi програмування С/С++ та
їх призначення . . . . . . . . . . . . . . . . . . . . . . . . 27
3.2 Статична типiзацiя i перетворення типiв . . . . . . . . . 32
3.3 Константи . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.4 Арифметичнi операцiї та привласнення . . . . . . . . . . 36
3.5 Посилання . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.6 Простори iмен i using . . . . . . . . . . . . . . . . . . . . 42
3.7 Питання та завдання для самоконтролю знань . . . . . . 43
4 Оператори управлiння у мовi програмування С/С++ . . . . . . 44
4.1 Умовнi вирази та операцiї вiдношення . . . . . . . . . . . 44
4.2 Логiчнi операцiї . . . . . . . . . . . . . . . . . . . . . . . . 45
4.3 Побiтовi операцiї . . . . . . . . . . . . . . . . . . . . . . . 46
4.4 Порозряднi операцiї . . . . . . . . . . . . . . . . . . . . . 47
4

4.5 Умовнi конструкцiї . . . . . . . . . . . . . . . . . . . . . . 48


4.6 Тернарний оператор . . . . . . . . . . . . . . . . . . . . . 52
4.7 Питання та завдання для самоконтролю знань . . . . . . 53
5 Цикли. Оператори циклу у мовi програмування С/С++ . . . . . 54
5.1 Цикл while . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.2 Цикл for . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
5.3 Цикл do . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
5.4 Оператори continue i break . . . . . . . . . . . . . . . . . 58
5.5 Питання та завдання для самоконтролю знань . . . . . . 59
6 Масиви у мовi програмування С/С++ . . . . . . . . . . . . . . . 60
6.1 Визначення та перебiр одновимiрних масивiв . . . . . . . 60
6.2 Багатовимiрнi масиви . . . . . . . . . . . . . . . . . . . . 63
6.3 Рядки як масиви . . . . . . . . . . . . . . . . . . . . . . . 64
6.4 Читання i введення рядка з консолi . . . . . . . . . . . . 68
6.5 Отримання i змiна символiв рядка . . . . . . . . . . . . . 69
6.6 Символьнi масиви . . . . . . . . . . . . . . . . . . . . . . 69
6.7 Питання та завдання для самоконтролю знань . . . . . . 70
7 Функцiї та об’єкти у мовi програмування С/C++ . . . . . . . . 70
7.1 Визначення i об’ява функцiй . . . . . . . . . . . . . . . . 70
7.2 Параметри та аргументи функцiї . . . . . . . . . . . . . . 73
7.3 Аргументи за замовчуванням . . . . . . . . . . . . . . . . 75
7.4 Передача аргументiв по значенню i по посиланню . . . . 76
7.5 Константнi параметри та посилання . . . . . . . . . . . . 79
7.6 Оператор return i повернення результату . . . . . . . . . 81
7.7 Рекурсивнi функцiї . . . . . . . . . . . . . . . . . . . . . . 83
7.8 Об’єкти. Класифiкацiя об’єктiв та їх застосування . . . . 85
7.9 Питання та завдання для самоконтролю знань . . . . . . 90
8 Покажчики у мовi програмування С/C++ . . . . . . . . . . . . 91
8.1 Покажчики . . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.2 Операцiї з покажчиками . . . . . . . . . . . . . . . . . . . 93
8.3 Арифметика покажчикiв . . . . . . . . . . . . . . . . . . 98
5

8.4 Константи i покажчики . . . . . . . . . . . . . . . . . . . 104


8.5 Питання та завдання для самоконтролю знань . . . . . . 106
9 Використання покажчикiв у функцiях мови програмування C++106
9.1 Покажчики у параметрах функцiї . . . . . . . . . . . . . 106
9.2 Покажчики на функцiї . . . . . . . . . . . . . . . . . . . . 109
9.3 Покажчики на функцiї як параметри . . . . . . . . . . . 113
9.4 Покажчик на функцiю як значення, що повертається . . 116
9.5 Питання та завдання для самоконтролю знань . . . . . . 120
10 Покажчики на масиви. Динамiчнi об’єкти та масиви у мовi про-
грамування С/C++ . . . . . . . . . . . . . . . . . . . . . . . . . 120
10.1 Покажчики на масиви . . . . . . . . . . . . . . . . . . . . 120
10.2 Покажчики на масиви у параметрах функцiї . . . . . . . 124
10.3 Передача маркера кiнця масиву . . . . . . . . . . . . . . 126
10.4 Передача багатовимiрного масиву . . . . . . . . . . . . . 129
10.5 Динамiчнi об’єкти . . . . . . . . . . . . . . . . . . . . . . 131
10.6 Видiлення та звiльнення пам’ятi . . . . . . . . . . . . . . 132
10.7 Динамiчнi масиви . . . . . . . . . . . . . . . . . . . . . . 134
10.8 Питання та завдання для самоконтролю знань . . . . . . 137
11 Багатофайловi програми у мовi програмування С/C++ . . . . . 138
11.1 Мiжфайлова взаємодiя . . . . . . . . . . . . . . . . . . . . 138
11.2 Заголовочнi файли . . . . . . . . . . . . . . . . . . . . . . 140
11.3 Бiблiотеки функцiй . . . . . . . . . . . . . . . . . . . . . . 143
11.4 Питання та завдання для самоконтролю знань . . . . . . 147
12 Потоки i система введення-виведення у мовi програмування
С/C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
12.1 Базовi типи для роботи з потоками . . . . . . . . . . . . . 147
12.2 Файловi потоки. Вiдкриття i закриття файлiв . . . . . . 151
12.3 Читання i запис текстових файлiв . . . . . . . . . . . . . 154
12.4 Перевизначення операторiв введення i виведення . . . . . 155
12.5 Читання i запис бiнарного файлу . . . . . . . . . . . . . . 158
12.6 Питання та завдання для самоконтролю знань . . . . . . 160
Рекомедована лiтература . . . . . . . . . . . . . . . . . . . . . . . . . 161
6

Вступ

Огляд алгоритмiчних мов програмування

Традицiйна технологiя програмування складалася у умовах, коли основни-


ми споживачами програм були науковi установи, обчислювальнi ресурси були
обмеженi, а проблеми супроводу по сутi невiдомi. Основними критерiями яко-
стi програми вважалися її ефективнiсть i компактнiсть. Згодом складнiсть
програм зросла настiльки, що на їх розробку йшли роки працi великого ко-
лективу, а у результатi системи з’являлися з запiзненням i мiстили тисячi
помилок.
Криза у програмуваннi призвела до необхiдностi створення нового способу
створення програм, який знижував б загальнi витрати на протязi всього ци-
клу програми, - вiд задуму до впровадження у експлуатацiї. Така техноло-
гiя з’явилася у другiй половинi пройшлого столiття i була названа мовою
структурного програмуванням С/С++. У її основi лежить поєднання теорiї
програмування та особистого досвiду висококвалiфiкованих програмiстiв, а
також сучасних вимог до програм i промислового характеру їх виробництва.
Мова структурного програмування С/С++ - це технологiя створення про-
грам, що дозволяє шляхом дотримання певних правил зменшити час розроб-
ки та кiлькiсть помилок, а також полегшити можливiсть модифiкацiї про-
грами. Структурний пiдхiд охоплює всi стадiї розробки проекту: специфiка-
цiю, проектування, власне програмування i тестування. Особливо важливо
дотримуватися певної дисциплiни при програмуваннi на C++. Ця мова має
настiльки велику гнучкiсть i широкi можливостi, що, якщо не поставити се-
бе у жорсткi рамки з самого початку, програма швидко перетвориться на
величезного некерованого монстра, що не пiддається налагодженнi.
Iдеї структурного програмування отримали свiй подальший розвиток у
об’єктно-орiєнтованому програмуваннi (ООП) - технологiї, що дозволяє до-
сягти простоти структури та керованостi дуже великих програмних систем.
Слiд також зважати на те, що мови програмування С/С++ суттєво вплину-
ли на ровиток таких алгоритмiчнмх мов програмування, як Java, JavaScript,
PHP. Тому при вивченнi основ алгоритмiв та програмування ми виберем мову
С/С++.
Теоретичнi питання застосування мови програмування С/С++ та супутнi
питання розглядаються пiд час лекцiй з навчальної дисциплiни ”Основи ал-
горитмiзацiї та програмування” та рекомендованiй лiтературi [1]-[10].
7

Апаратурнi ресурси, операцiйнi системи та програмнi засоби що


використовуються при програмуваннi на мовi C++

В навчальному процесi з програмування особливих вимог до конфiгурацiї


комп’ютера немає, досить 4GB оперативної пам’ятi та декiлькох десяткiв GB
на жорсткому диску. Що до операцiйної системи (ОС), то пiдiйде як Microsoft
Winduws так i Linux. Ми у своїй роботi спинимося на ОС Microsoft Winduws,
як найбiльш поширенiй у навчльних закладах. У якостi програмних засобiв
при вивченнi такої мови програмування як С/С++ завзвичай вибирають Mi-
crosoft Visual Studio Community з бiблiотекою QT, Embarcadero C++
Builder Community Edition або Borland C++ Builder Enterprise v6.0
[5], [6] якi придатнi для розробки програмних пакетiв у середовищi C++.
Як показє досвiд, на першому етапi освоєння технологiй програмування на
мовi С/С++ найбiльш пiдхоящим є програмний пакет Borland C++ Builder
Enterprise v6.0. Вiн є добре вiдпрацьований, найбiльш гнучкий i швидкий, iн-
туїтивно зрозумiлий, займає у декiлька раз менше ресурсiв компю’тера нiж
Microsoft Visual Studio Community чи Embarcadero C++ Builder. З огляду на
цi обставини при вивченнi мови програмування високого рiвня С/С++ ми
будемо використовувати саме Borland C++ Builder Enterprise v6.0. Отримав-
ши досвiд програмування у середовищi Borland C++ Builder Enterprise v6.0
можна буде без проблем перенести свої проекти без додаткового редвгуван-
ня до бiльш сучасного середовища Embarcadero C++ Builder, яке має дещо
розширенi можливостi, але, разом з тим, є бiльш ресурсоєним.
Середовище С++ Builder поєднує засоби мови програмування С/С++ та ком-
понентно - орiєнтований пiдхiд до створення програм. Поєднання простоти
освоєння вiзуального середовища та пiдтримки широкого спектра техноло-
гiй робить С++ Builder унiверсальним iнструментом створення програмних
проектiв якого завгодно рiвня складностi. Навiть початковi програмiсти змо-
жуть швидко створювати програмнi проекти у С++ Builder, якi матимуть
професiйний вiконний iнтерфейс найрiзноманiтнiшої спрямованостi, вiд су-
то обчислювальних та логiчних, до графiчних та мультимедiйних. Поряд з
цим, значну увагу буде придiлено створюванню консольних програм, оскiль-
ки їхнiй програмний код є унiверсальним i майже не вiдрiзнюється вiд про-
грам iнших засобiв розроблення програм мовою С/С++.
В якостi компiлятора консольних програм можна використати один iз най-
бiльш популярних на сьогодi компiляторiв — g++, який доступний для рi-
зних операцiйних систем.
8

1. Алгоритми та їх властивостi

1.1. Алгоритми та правила їх графiчного запису

Для програмування будь - яких обчисленнь на комп’ютерi використовується


поняття алгоритму – як послiдовностi дiй, необхiдних машинi для досягнення
результату. Сама ж комп’ютерна програма являє собою записаний набiр ко-
манд, якi задають алгоритм у формi, зрозумiлiй для машини. Для того, щоб
навчити комп’ютер щось робити, потрiбно попередньо скласти алгоритм.
Найбiльш загальним, простим та неформальним є: алгоритм – це набiр iн-
струкцiй, що описує, як деяке завдання може бути виконане.
Алгоритм (algorithm) це

• Скiнченна послiдовнiсть точно визначених дiй або операцiй, спрямова-


них на досягнення поставленої мети.
Iншими словами, алгоритм – система формальних правил, що визначає
змiст i порядок дiй над вхiдними даними i промiжними результатами,
необхiдними для отримання кiнцевого результату при розв’язуваннi за-
дачi.
• Будь-яка коректно визначена обчислювальна процедура, на вхiд (input)
якої подається деяка величина або набiр величин, i результатом викона-
ння якої є вихiдна (output) величина або набiр значень.
При розв’язуваннi будь-якої задачi та побудовi алгоритму її розв’язку
звичайно беруть до уваги наявнiсть деяких вхiдних даних i мають уяв-
лення про результат, що необхiдно отримати.

Можна навести загальнi риси алгоритму:

• Дискретнiсть iнформацiї. Кожний алгоритм має справу з даними:


вхiдними, промiжними, вихiдними. Цi данi представляються у виглядi
скiнченних слiв деякого алфавiту.
• Дискретнiсть роботи алгоритму. Алгоритм виконується по кроках
та при цьому на кожному кроцi виконується тiльки одна операцiя.
• Детермiнованiсть алгоритму. Система величин, якi отримуються у
кожний (не початковий) момент часу, однозначно визначається системою
величини, якi були отриманi у попереднi моменти часу.
• Елементарнiсть крокiв алгоритму. Закон отримання наступної си-
стеми величин з попередньої повинен бути простим та локальним.
9

• Виконуванiсть операцiй. У алгоритмi не має бути не виконуваних


операцiй. Наприклад, неможна у програмi призначити значення змiннiй
”нескiнченнiсть”, така операцiя була би не виконуваною. Кожна операцiя
опрацьовує певну дiлянку у словi, яке обробляється.
• Скiнченнiсть алгоритму. Опис алгоритму повинен бути скiнченним.
• Спрямованiсть алгоритму. Якщо спосiб отримання наступної вели-
чини з деякої заданої величини не дає результату, то має бути вказано,
що треба вважати результатом алгоритму.
• Масовiсть алгоритму. Початкова система величин може обиратись з
деякої потенцiйно нескiнченної множини. Тобто, можливiсть виконання
алгоритмiв для рiшення цiлого класу конкретних задач, що вiдповiдають
загальнiй постановцi задачi.

Iснує чотири способи написання алгоритмiв:

• вербальний (словесний);
• алгебраїчний (за допомогою лiтерно-цифрових позначень виконуваних
дiй);
• графiчний;
• з допомогою алгоритмiчних мов програмування.

Словесна форма запису алгоритмiв використовується у рiзних iнструкцiях,


призначених для виконання їх людиною.
Алгебраїчна форма найчастiше використовується у теоретичних дослiджен-
нях фундаментальних властивостей алгоритмiв.
Графiчна форма вiдповiдно до державних стандартiв на оформлення доку-
ментацiї прийнята як основна для опису алгоритмiв.
Алгоритм записаний за допомогою алгоритмiчної мови програмування нази-
вається програмою. Алгоритм у такiй формi може бути введений у комп’ютер
i пiсля вiдповiдного оброблення виконаний з метою отримання шуканого ре-
зультату.
Блок-схеми (графiчний спосiб подання алгоритмiв) – найбiльш зручний спо-
сiб вiзуального представлення алгоритмiв. Для того, щоб не заплутатися у
численних подробицях, є сенс складати блок-схему алгоритму у декiлька iте-
рацiй: почати з найбiльш загального i поступово його уточнювати.
Блок-схема – наочне графiчне зображення алгоритму, коли окремi його дiї
(етапи) зображуються за допомогою рiзних геометричних фiгур (блокiв), а
10

зв’язки мiж етапами указуються за допомогою стрiлок, що сполучають цi


фiгури.
Блок-схеми вiдображають кроки (Див. Рис. 1.1-1.8), якi повиннi виконуватися
комп’ютером, i послiдовнiсть їх виконання.
Розмiри символiв розраховуються вiдповiдно до наступних правил:

• Менший геометричний розмiр символу слiд обирати з ряду 10, 15, 20, . . .
мм (тобто a = 10, 15, 20, . . . мм;
• Спiввiдношення бiльшого та меншого розмiрiв має становити 1.5 (тобто
b = 1.5 a).
• Початок i кiнець алгоритму зображуються за допомогою овалiв (див.
рис. 1.1). Усерединi овалу записується ”початок” або ”кiнець”.
• Уведення початкових даних i виведення результатiв зображуються пара-
лелограмом (див. рис. 1.2). Усерединi нього пишеться слово ”уведення”
або ”виведення” i перераховуються змiннi, що пiдлягають введенню або
виведенню.
• Виконання операцiй зображується за допомогою прямокутникiв (див.
рис. 1.3) у яких записано вираз/операцiю. Для кожної окремої операцiї
використовується окремий блок.
• Ранiше створенi i окремо описанi функцiї та пiдпрограми зображуються
у виглядi прямокутника з бiчними лiнiями (див. рис. 1.4). Усерединi та-
кого ”подвiйного” прямокутника указуються iм’я функцiї (пiдпрограми),
параметри, при яких вона повинна бути виконана.
• Блок вибору, що визначає шлях, по якому пiдуть цi дiї (наприклад, об-
числення) далi, залежно вiд результату аналiзу даних, зображується у
виглядi ромбу (див. рис. 1.5). Сама умова записується усерединi ромба.
Якщо умова, що перевiряється, виконується, тобто має значення ”iсти-
на”, то наступним виконується етап по стрiлцi ”так”. Якщо умова не ви-
конується (”хибнiсть”), то здiйснюється перехiд по стрiлцi ”нi”. Стрiлки
повиннi бути пiдписанi.
• Шестикутник (див. рис. 1.6) використовують для змiни параметра змiн-
ної, яка керує виконанням циклiчного алгоритму, також зазначаються
умови завершення циклу.
• Стрiлками зображуються можливi шляхи алгоритму, а малими п’яти-
кутниками (див. рис. 1.7) – розриви цих шляхiв потоку з переходом на
наступну сторiнку.
11

• Коментарi використовуються у тих випадках, коли пояснення не помi-


щається усерединi блока (див. рис. 1.8).

Рис. 1.1 — Блок початок-кiнець. Вхiд Рис. 1.2 — Блок вводу - виводу даних. Вве-
- вихiд з програми, початок - кiнець дення даних з клавiатури або виведення на
функцiї екран результату

Рис. 1.3 — Обчислювальний блок. Об- Рис. 1.4 — Визначений процес. Виконан-
числення та послiдовнiсть обчислень ня пiдпрограми (функцiї)

Рис. 1.5 — Логiчний блок (блок умови).


Рис. 1.6 — Цикл. Початок циклу
Перевiрка умови

Рис. 1.7 — Перехiд. З’єднання мiж дво-


Рис. 1.8 — Коментар. Пояснення
ма сторiнками
12

Iснують такi правила графiчного запису алгоритмiв:

• блоки алгоритмiв з’єднуються лiнiями потокiв iнформацiї;


• лiнiї потокiв не повиннi перетинатися;
• будь-який алгоритм може мати лише один блок початку i один блок
кiнця.

Будь-який, навiть найскладнiший, алгоритм може бути поданий у виглядi


комбiнацiй кiлькох елементарних фрагментiв, якi називають базовими стру-
ктурами. До них належать:

• послiдовне проходження (див. рис. 1.9);


• розгалуження ”якщо-то” (див. рис. 1.10);
• розгалуження ”якщо-то-iнакше” (див. рис. 1.11);
• обирання варiанта за ключем (див. рис. 1.12);
• цикл з параметром (див. рис. 1.13);
• цикл з передумовою (див. рис. 1.14);
• цикл з пiсляумовою (див. рис. 1.15);
• використання коментарю (див. рис. 1.16)

Рис. 1.9 — Послiдовне проходження Рис. 1.10 — Розгалуження ”якщо - то”


13

Рис. 1.11 — Розгалуження ”якщо-то-iнакше”

Рис. 1.12 — Обирання варiанта за ключем


14

Рис. 1.13 — Цикл з пара- Рис. 1.14 — Цикл з передумо-


метром вою

Рис. 1.15 — Цикл з пiсляумовою


15

Рис. 1.16 — Використання коментаря

1.2. Програмне забезпечення для реалiзацiї алгоритмiв

Всi можливостi комп’ютера у питаннi реалiзацiї алгоритмiв забезпечуються


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

• управлiння роботою комп’ютера;


• розподiлу його ресурсiв;
• пiдтримки дiалогу з користувачем – операцiйнi системи;
• автоматизацiї процесу розробки та вiдлагодження програм;
• перекладу мов високого рiвня програмування на коди комп’ютера;
• архiвацiя файлiв тощо – утилiти;
• забезпечення роботи периферiйних пристроїв – драйвери.

Iнструментальне забезпечення слугує для розробки рiзних пакетiв програм,


що застосовуються у рiзних областях знання. У групу iнструментальних про-
грам входять: транслятори з рiзних алгоритмiчних мов, якi переводять текст
16

програми на машинну мову; налагоджувачi з допомогою яких знаходять i


виправляють помилки, якi були допущенi при написаннi програми; iнтегро-
ванi середовища розробки, якi об’єднують вказанi вище компоненти у єдину,
зручну для розробки систему.
Прикладне програмне забезпечення подiляють на прикладне програмне за-
безпечення загального призначення, до якого вiдносяться тi програми, якi
широко використовуються рiзними категорiями користувачiв (текстовi реда-
ктори, електроннi таблицi, системи управлiння базами даних, графiчнi ре-
дактори) та прикладне програмне забезпечення спецiального призначення,
до якого вiдносяться програми, якi мають специфiчне призначення (засоби
розробки програм, статистичнi обчислення, мережевi застосунки тощо).
У категорiю засобiв розробки програм вiдносять всi програми, що викори-
стовуються для розробки нових програм. Спектр засобiв пiдготовки програм
мiстить редактори вихiдних текстiв (зазвичай забезпечують пiдсвiтку (видi-
лення деяких елементiв тексту, що мають значення для користувача: дужки,
службовi слова та iн.) i деяку поверхневу перевiрку синтаксису конструкцiй
що вводяться), транслятори (дозволяють запускати програми), налагоджу-
вачi (призначенi для пошуку помилок у програмах) i у деяких випадках ще
тести (профайлери), що дозволяють, наприклад, визначити найбiльш повiль-
ний або вимогливий до ресурсiв блок програми.
Для подання алгоритму у виглядi, зрозумiлому комп’ютеру, служать мови
програмування. Спочатку завжди розробляється алгоритм дiй, а потiм вiн
записується однiєю з таких мов. У пiдсумку виходить текст програми – пов-
ний, закiнчений i детальний опис алгоритму мовою програмування. Потiм
цей текст програми спецiальними службовими додатками, якi називаються
трансляторами, або переводиться у машинний код, або виконується.
Мови програмування – штучнi мови. Вiд природних вони вiдрiзняються обме-
женою кiлькiстю ”слiв”, значення яких зрозумiло транслятору, i дуже строги-
ми правилами запису команд-операторiв. Сукупнiсть подiбних вимог формує
синтаксис мови програмування, а сенс кожної команди та iнших констру-
кцiй мови – його семантику. Отже, програма, з якою працює процесор, являє
собою послiдовнiсть чисел, яку називають машинним кодом.
Асемблер – це програма, яка перетворює код, написаний мовою асемблера,
остаточно у машинний код. Але часто i саму мову називають скорочено ”а-
семблером”.
Нинi бiльшiсть програм пишеться на високорiвневих алгоритмiчних мовах
програмування. Для виконання комп’ютером така програма має бути пере-
творена на машинний код або хоча б переписана низькорiвневою мовою (а
далi вже асемблер забезпечить її розумiння машиною). Цей процес перетво-
17

рення називається трансляцiєю, а програми, якi його виконують – транслято-


рами. Iснує два типи трансляторiв, що перетворюють вихiдний код програм
у машиннi команди: iнтерпретатори та компiлятори.
Iнтерпретатор зчитує вихiдний код програми по однiй iнструкцiї i, у найпро-
стiшому випадку, одразу намагається їх ”перекладати” та виконувати. Для
пiдвищення швидкодiї бiльшiсть сучасних iнтерпретаторiв насправдi працює
за змiшаною схемою, спочатку транслюючи вихiдний код програми у деяку
промiжну форму – так званий байт-код. Вiн є кодом нижчого рiвня i ближчий
до асемблеру, але машинно - незалежний – тому виконується не безпосере-
дньо комп’ютером, а деякою вiртуальною машиною, яка входить до складу
iнтерпретатора.
Компiлятор повнiстю перетворює вихiдний код програми у машинний код,
який операцiйна система може виконати самостiйно. Це дозволяє виконувати
скомпiльованi програми навiть на тих комп’ютерах, на яких немає компiля-
тора. Проте, скомпiльована програма прив’язується до операцiйної системи i
набору команд процесора, тому не завжди може бути перенесена i виконана
на комп’ютерi з iншою операцiйною системою.
Програмування – досить складний процес, i цiлком природно, коли програ-
мiст припускається помилки. Так повелося, що програмнi помилки називають
”багами” (вiд англ. bug – жучок). Процес виявлення i усунення помилок у
англомовнiй лiтературi прийнято позначати термiном debugging. Процес по-
шуку помилок у програмi називається тестуванням (testing), процес усунення
помилок – налагодженням (debugging).

1.3. Питання та завдання для самоконтролю знань

• Якi операцiйнi системи та програмнi засоби використовуються при про-


грамуваннi на мовi C++?
• Що таке алгоритм i яка його роль у програмуваннi комп’ютерних дода-
ткiв?
• Перерахуйте загальнi риси алгоритму
• Перерахуйте загальнi способи написання алгоритмiв
• Що таке блок-схема алгоритму? Назвiть основнi блоки, якi використо-
вуються при написаннi алгоритму.
• Якi базовi структури використовуються при написаннi алгоритму?
• Якi функцiї виконує системне програмне забезпечення?
• Яке призначення має iнструментальне забезпечення?
18

• Яке призначення має прикладне програмне забезпечення?


• У чому полягає рiзниця мiж iнтерпретатором i компiлятором?

2. Основи мови програмування С/С++

2.1. Алгоритмiзацiя структури для програми у мовi С/С++

Iнструкцiї

Програма на С/С++ складається з набору iнструкцiй. Кожна iнструкцiя


(statement) виконує певну дiю. У кiнцi iнструкцiї у мовi C++ ставиться кра-
пка з комою (;). Даний знак указує компiлятору на завершення iнструкцiї.
Наприклад:
s t d : : cout << " H e l l o World ! " ;

Даний рядок виводить на консоль рядок ”Hello world!”, є iнструкцiєю i тому


завершується крапкою з комою.
Набiр iнструкцiй може представляти блок коду. Блок коду полягає у фiгурнi
дужки, а iнструкцiї помiщаються мiж вiдкриваючою i закриваючою фiгур-
ними дужками:
{
s t d : : cout << " H e l l o World ! " ;
s t d : : cout << "Bye World ! " ;
}

У цьому блоцi коду двi iнструкцiї, якi виводять на консоль певний рядок.

Функцiя main

Кожна програма на мовi С/С++ повинна мати як мiнiмум одну функцiю -


функцiю main(). Саме з цiєї функцiї починається виконання додатку. Її iм’я
main фiксовано i для всiх програм на СI завжди однаково.
Функцiя також є блоком коду, тому її тiло обрамляється фiгурними дужками,
мiж якими визначається набiр iнструкцiй.
Зокрема, при створеннi першої програми використовувалася наступна фун-
кцiя main:
#i n c l u d e <i o s t r e a m > // пiдключаємо заголовний файл i o s t r e a m
i n t main ( ) // визначаємо функцiю main
{ // початок функцiї
19

s t d : : cout << " H e l l o World ! " ; // виводимо на консоль


r e t u r n 0 ; // виходимо з функцiї
} // кiнець функцiї
Визначення функии main починається з типу, що повертається. Функцiя
main у будь-якому випадку повинна повертати число. Тому її визначення
починається з ключового слова int.
Далi йде назва функцiї, тобто main. Пiсля назви у дужках йде список па-
раметрiв. У даному випадку функцiя main не приймає нiяких параметрiв,
тому пiсля назви вказанi порожнi дужки. Проте є iншi варiанти визначен-
ня функцiї main, якi у яких використовуються параметри. Зокрема, нерiдко
може зустрiчатися наступне визначення функцiї main, що використовує па-
раметри:
main ( i n t argc , char ∗ argv [ ] )
{

}
I пiсля списку параметрiв йде блок коду, який i мiстить у виглядi iнструкцiй
власне тi дiї, виконуванi функцiєю main.

Директиви препроцесора

У прикладi вище на консоль виводиться рядок, але щоб використовувати ви-


воду на консоль, необхiдно на початку файлу з початковим кодом пiдключати
бiблiотеку iostream за допомогою директиви include.
Директива include є директивою препроцесора. Кожна директива препроцесо-
ра розмiщується на одному рядку. I на вiдмiну вiд звичайних iнструкцiй мови
C++, якi завершуються крапкою з комою ; Ознакою завершення препроце-
сорної директиви є перехiд на новий рядок. Крiм того, директива повинна
починатися iз знаку грат #. Безпосередньо директива "include"визначає, якi
файли i бiблiотеки треба пiдключити у даному мiсцi у код програми.

Коментарi

Початковий код може мiстити коментарi. Коментарi дозволяють зрозумiти


змив програми, що роблять тi або iншi її частини. При компiляцiї коментарi
iгноруються i не надають нiякого вплив на роботу додатку i на його розмiр.
У мовi C++ є два типи коментарiв: однорядковий i багаторядковий. Одно-
рядковий коментар розмiщується на одному рядку пiсля подвiйного слеша
//:
20

#i n c l u d e <i o s t r e a m > // пiдключаємо б i б л i о т е к у i o s t r e a m


main ( ) // визначаємо функцiю main
{ // початок функцiї
s t d : : cout << " H e l l o World ! " ; // виводимо на консоль
r e t u r n 0 ; // виходимо з функцiї
} // кiнець функцiї

Багаторядковий коментар полягає мiж символами /* текст коментаря */. Вiн


може розмiщуватися на декiлькох рядках. Наприклад:
#i n c l u d e <i o s t r e a m >
/∗
Визначення функцiї Main
Виводить на консоль рядок H e l l o World !
∗/ main ( )
{
s t d : : cout << " H e l l o World ! " ; // виведення рядка на консоль
return 0;
}

2.2. Введення i виведення у консолi

За умовчанням язик C++ не мiстить вбудованих засобiв для введення з кон-


солi i виведення на консоль, цi засоби надаються бiблiотекою iostream. У нiй
визначено два типи: istream i ostream. istream представляє потiк введення, а
ostream - потiк виведення.
Вобще сам темин "потiк"у даному випадку представляє послiдовнiсть сим-
волiв, яка записується на пристрiй уведення-виведення або прочитується з
нього. I у даному випадку пiд пристроєм уведення-виведення розглядається
консоль.
Для запису або виведення символiв на консоль застосовується об’єкт cout,
який представляє тип ostream. А для читання з консолi використовується
об’єкт cin
Для використовування цих об’єктiв у початок початкового файлу необхiдно
пiдключити бiблiотеку iostream:
#i n c l u d e <i o s t r e a m >
21

Виведення на консоль

Для виведення на консоль застосовується оператор «. Цей оператор одержує


два операнди. Лiвий операнд представляє об’єкт типу ostream, у даному ви-
падку об’єкт cout. А правий операнд - значення, яке треба вивести на консоль.
Оскiльки оператор « повертає лiвий операнд - cout, то за допомогою ланцюж-
ка операторiв ми можемо передати на консоль декiлька значень. Наприклад,
визначимо найпростiшу програму виведення на консоль:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
i n t age = 3 3 ;
double weight = 8 1 . 2 3 ;
s t d : : cout << "Name : " << "Tom" << "\n " ;
s t d : : cout << "Age : " << age << s t d : : e n d l ;
s t d : : cout << " Weight : " << weight << s t d : : e n d l ;
return 0;
}

Консольне виведення програми:


Name : Tom
Age : 33
Weight : 8 1 . 2 3

Оператору « передаються рiзнi значення - рядки, значення змiнних, якi ви-


водяться на консоль.
Рядки можуть мiстити управляючi послiдовностi, якi iнтерпретуються пев-
ним чином. Наприклад, послiдовнiсть ”\n” iнтерпретується як переклад на
новий рядок. З iнших управляючих послiдовностей також нерiдко вживає-
ться ”\t”, яка iнтерпретується як табуляцiя.
Також ланцюжок операторiв « можна завершувати значенням std::endl, яке
викликає переклад на новий рядок i скидання буфера. При виведеннi у потiк
данi спочатку помiщаються у буфер. I скидання буфера гарантує, що всi пе-
реданi для виведення на консоль данi немедлено будуть виведенi на консоль.

Введення з консолi

Для прочитування з консолi даних застосовується оператор введення », який


приймає два операнди. Лiвий операнд представляє об’єкт типа istream (в да-
22

ному випадку об’єкт cin), з якого проводиться прочитування, а правий опе-


ранд - об’єкт, у який прочитуються данi.
Наприклад, рахуємо данi з консолi:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
i n t age ;
double weight ;
s t d : : cout << " Input age : " ;
s t d : : c i n >> age ;
s t d : : cout << " Input weight : " ;
s t d : : c i n >> weight ;
s t d : : cout << "Your age : " << age <<
"\ t your weight : " << weight << s t d : : e n d l ;
return 0;
}
Тут пiсля запрошень до введення програма чекає введення значень для змiн-
них age i weight.
Приклад роботи програми:
Input age : 32
Input weight : 6 7 . 4 5
Your age : 32 your weight : 6 7 . 4 5
Варто вiдзначити, що так оператор введення у першому випадку додаватиме
данi у цiлочисельну змiнну age, то вiн чекає введення числа. У випадку iз
змiнною weight оператор введення чекає дробове число, причому роздiльни-
ком цiлої i дробової частини повинна бути крапка. Тому ми не можемо ввести
будь-якi значення, наприклад, рядки. У цьому випадку програма може вида-
ти некоректний результат.
Оператор введення » повертає лiвий операнд - об’єкт cin, тому ми можемо по
ланцюжку прочитувати данi у рiзнi змiннi:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
i n t age ;
double weight ;
s t d : : cout << " Input age : " ;
s t d : : c i n >> age >> weight ;
23

s t d : : cout << "Your age : " << age <<


"\ t your weight : " << weight << s t d : : e n d l ;
return 0;
}

Приклад роботи програми:


Input age : 32 6 7 . 4 5
Your age : 32 your weight : 6 7 . 4 5

Пiсля введення одного iз значень треба буде ввести пропуск i потiм вводити
наступне значення.

2.3. Змiннi у мовi програмування C++

Як i у багатьох мавах програмування, у C++ для зберiгання даних викори-


стовуються змiннi. Змiнна має тип, iм’я i значення. Тип визначає, яку iнфор-
мацiю може берегти змiнна.
Перед використовуванням будь-яку змiнну треба визначити. Синтаксис ви-
значення змiнної виглядає таким чином:
тип_змiнної iм ’ я_змiнної ;

Найпростiше визначення змiнної:


i n t age ;

Тут визначена змiнна age, яка має тип int. Оскiльки визначення змiнної є
iнструкцiєю, то пiсля нього ставиться крапка з комою.
Також варто враховувати, що C++ - регiстрозалежна мова, а це значить, що
регiстр символiв має велике значення. Тобто у наступному кодi визначатиму-
ться двi рiзнi змiннi:
i n t age ;
i n t Age ;

Тому змiнна Age не представлятиме те ж саме, що i змiнна age.


Крiм того, як iм’я змiнної не можна використовувати ключовi слова мови
C++, наприклад, for або if. Але таких слiв не так багато: alignas, ali-
gnof, asm, auto, bool, break, case, catch, char, char16_t, char32_t,
class, const, constexpr, const_cast, continue, decltype, default, delete,
do, double, dynamic_cast, else, enum, explicit, export, extern, false,
float, for, friend, goto, if, inline, int, long, mutubale, namespace,
new, noexcept, nullptr, operator, private, protected, public, register,
reinterpret_cast, return, short, signed, sizeof, static, static_assert,
24

static_cast, struct, switch, template, this, thread_local, throw, true,


try, typedef, typeid, typename, union, unsigned, using, virtual, void,
volatile, wchar_t, while.
Також не можна оголосити бiльше однiєї змiнної з одним i тим же iм’ям,
наприклад:
i n t age ;
i n t age ;

Подiбне визначення викличе помилку на етапi компiляцiї.


I у довершеие слiд сказати, що змiнним варто давати осмисленi iмена, якi
говоритимуть про їх призначення.

Iнiцiалiзацiя

Пiсля визначення змiнної можна привласнити деяке значення:


i n t age ;
age = 2 0 ;

Наприклад, визначимо у прогамме змiнну i виведемо її значення на консоль:


#i n c l u d e <i o s t r e a m >
i n t main ( )
{
i n t age ;
age = 2 8 ;
s t d : : cout <<"Age = " << age ;
return 0;
}

За допомогою послiдовностi операторiв « можна вивести декiлька значень на


консоль.
Пiсля компiляцiї i запуску скомпiльованої програми на консоль буде виведено
число 28.
Проте також можна вiдразу при визначеннi змiнної дати їй деяке початко-
ве значення. Даний прийом називається iнiцiалiзацiєю, тобто привласнення
змiнної початкового значення:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
i n t age = 2 8 ;
s t d : : cout <<"Age = " << age ;
25

return 0;
}

Iнiцiалiзацiя за умовчанням

Якщо змiнну не iнiцiалiзувати, то вiдбувається її iнiцiалiзацiя за умовчанням,


тобто змiнна набуває деяке значення за умовчанням, яке залежить вiд мiсця,
де ця змiнна визначена.
Якщо змiнна, яка представляє вбудований тип (наприклад, тип int), визна-
чена усерединi функцiї, то вона набуває невизначене значення. Якщо змiнна
вбудованого типу визначена поза функцiєю, то вона набуває те значення за
умовчанням, яке вiдповiдає її типу. Для числових типiв це число 0. Напри-
клад:
#i n c l u d e <i o s t r e a m >
int x ;
i n t main ( )
{
int у ;
s t d : : cout <<"X = " << x << "\n " ;
s t d : : cout <<"Y = " << у ;
return 0;
}

Змiнна x визначена поза функцiєю, i тому вона набуде значення за умовчан-


ням - число 0.
Набагато складнiше справа йде iз змiнною у, яка визначена усерединi функцiї
main - її значення буде невизначеним, i багато що залежатиме вiд компiля-
тора, що використовується. Зокрема, виведення програми, скомпiльованої за
допомогою компiлятора g++, може виглядати таким чином:
x= 0
y= 4200475
А у Visual Studio вiдсутнiсть значення змiнної у викличе помилку.
Але у будь-якому випадку перед використовуванням змiнної краще явним чи-
ном призначати нею певне значення, а не покладатися на значення за умов-
чанням.

Змiна значення

Ключовою особливiстю змiнних є те, що ми можемо змiнювати їх значення:


26

#i n c l u d e <i o s t r e a m >
i n t main ( )
{
int x = 6;
x = 8;
x = 10;
s t d : : cout <<"X = " << x ; // X = 10
return 0;
}

2.4. Формалiзм функцiй у мовi програмування С/С++

Функцiя визначає дiї, якi виконує програма. Функцiї дозволяють видiлити


набiр iнструкцiй i додати йому iм’я. А потiм багатократно по привласненому
iменi викликати у рiзних частинах програми. По сутi функцiя - це iменований
блок коду.
Формальне визначення функцiї виглядає таким чином:
тип iм ’ я_функцiї ( параметри )
{
iнструкцiї
}

Перший рядок представляє заголовок функцiї. Спочатку указується тип фун-


кцiї, що повертається. Якщо функцiя не повертає нiякого значення, то вико-
ристовується тип void.
Потiм йде iм’я функцiї, яке представляє довiльний iдентифiкатор. До iмену-
юче функцiї застосовуються тi ж правила, що i до iменуюче змiнних.
Пiсля iменi функцiї у дужках йде перелiк параметрiв. Функцiя може не мати
параметрiв, у цьому випадку указуються порожнi дужки.
Пiсля заголовка функцiї у фiгурних дужках йде тiло функцiї, яке мiстить
виконуванi iнструкцiї.
Для повернення результату функцiя застосовує оператор return. Якщо фун-
кцiя має як тип, що повертається, будь-який тип, окрiм void, то вона повинна
обов’язково за допомогою оператора return повертати яке-небудь значення.
Наприклад, визначення функцiї main, яка повинна бути у будь-якiй програмi
на мовi C++ i з якою починається виконання програми:
main ( )
{
return 0;
27

}
Типом функцiї, що повертається, є тип int, тому функцiя повинна використо-
вувати оператор return i повертати яке-небудь значення, яке вiдповiдає типу
int. Значення, що повертається, ставиться пiсля оператора return.
Але якщо функцiя має тип void, то їй не треба нiчого повертати.

2.5. Питання та завдання для самоконтролю знань

• Що таке iнструкця у мовi програмування С/С++ i як фона оформлює-


ться?
• Яку родь вiдiграє функцiя main у мовi програмування С/С++?
• Що таке директиви препроцесора та коментарi?
• Що таке консоль i як вона використовується для введення i виведення
даних?
• Що таке змiннi i як вони оформлюються у мовi програмування C++?
• Що таке iнiцiалiзацiя i якi типи iнiцiалiзацi змiнних використовується у
мовi програмування C++?
• Яку структуру має функцiя у мовi програмування C++?
• За допомогою якого оператора повертається значення функцiї?

3. Типи данних та арифметичнi операцiї у мовi програ-


мування С/С++

3.1. Основнi типи данных у мовi програмування С/С++ та їх при-


значення

Кожна змiнна має певний тип. I цей тип визначає, якi значення може мати
змiнна, якi операцiї з нею можна проводити i скiльки байт у пам’ятi вона
займатиме. В мовi C++ визначенi наступнi базовi типи даних:

• bool: логiчний тип. Може приймати одну з двох значень true (iстина) i
false (брехня). Розмiр займаної пам’ятi для цього типу точно не визна-
чений.
• char: представляє один символ в кодуваннi ASCII. Займає в пам’ятi 1
байт (8 бiт). Може берегти будь-яке значення з дiапазону вiд -128 до 127,
або вiд 0 до 255
28

• signed char: представляє один символ. Займає в пам’ятi 1 байт (8 бiт).


Може берегти будь-кого значення з дiапазону вiд -128 до 127
• unsigned char: представляє один символ. Займає в пам’ятi 1 байт (8
бiт). Може берегти будь-кого значення з дiапазону вiд 0 до 255
• wchar_t: представляє розширений символ. На Windows займає в пам’я-
тi 2 байти (16 бiт), на Linux - 4 байти (32 бiти). Може берегти будь-кого
значення з дiапазону вiд 0 до 65 535 (при 2 байтах), або вiд 0 до 4 294
967 295 (для 4 байт)
• char16_t: представляє один символ в кодуваннi Unicode. Займає в па-
м’ятi 2 байти (16 бiт). Може берегти будь-кого значення з дiапазону вiд
0 до 65 535
• char32_t: представляє один символ в кодуваннi Unicode. Займає в па-
м’ятi 4 байти (32 бiти). Може берегти будь-кого значення з дiапазону вiд
0 до 4 294 967 295
• short: представляє цiле число в дiапазонi вiд -32768 до 32767. Займає в
пам’ятi 2 байти (16 бiт). Даний тип також має синонiми short int, signed
short int, signed short.
• unsigned short: представляє цiле число в дiапазонi вiд 0 до 65535. За-
ймає в пам’ятi 2 байти (16 бiт).
Даний тип також має синонiм unsigned short int.
• int: представляє цiле число. Залежно вiд архiтектури процесора може за-
ймати 2 байти (16 бiт) або 4 байти (32 бiти). Дiапазон граничних значень
вiдповiдно також може варiюватися вiд -32768 до 32767 (при 2 байтах)
або вiд -2 147 483 648 до 2 147 483 647 (при 4 байтах). Але у будь-якому
випадку розмiр повинен бути бiльше або рiвно розмiру типу short i менше
або рiвно розмiру типу long
Даний тип має синонiми signed int i signed.
• unsigned int: представляє позитивне цiле число. Залежно вiд архiте-
ктури процесора може займати 2 байти (16 бiт) або 4 байти (32 бiти), i
через це дiапазон граничних значень може мiнятися: вiд 0 до 65535 (для
2 байт), або вiд 0 до 4 294 967 295 (для 4 байт).
Як синонiм цього типу може використовуватися unsigned
• long: представляє цiле число в дiапазонi вiд -2 147 483 648 до 2 147 483
647. Займає в пам’ятi 4 байти (32 бiти).
У даного типу також є синонiми long int, signed long int i signed long
29

• unsigned long: представляє цiле число в дiапазонi вiд 0 до 4 294 967


295. Займає в пам’ятi 4 байти (32 бiти).
Має синонiм unsigned long int.
• long long: представляє цiле число в дiапазонi вiд -9 223 372 036 854 775
808 до +9 223 372 036 854 775 807. Займає в пам’ятi, як правило, 8 байт
(64 бiти).
Має синонiми long long int, signed long long int i signed long long.
• unsigned long long: представляє цiле число в дiапазонi вiд 0 до 18 446
744 073 709 551 615. Займає в пам’ятi, як правило, 8 байт (64 бiти).
Має синонiм unsigned, long long, int.
• float: представляє дiйсне число ординарної точностi з плаваючою кра-
пкою в дiапазонi +/ − 3.4E − 38 до 3.4E + 38. В пам’ятi займає 4 байти
(32 бiти)
• double: представляє дiйсне число подвiйної точностi з плаваючою кра-
пкою в дiапазонi +/ − 1.7E − 308 до 1.7E + 308. В пам’ятi займає 8 байт
(64 бiти)
• long double: представляє дiйсне число подвiйної точностi з плаваючою
крапкою не менше 8 байт (64 бiт). Залежно вiд розмiру займаної пам’ятi
може вiдрiзнятися дiапазон допустимих значень.
• void: тип без значення

Таким чином, всi типи даних за винятком void можуть бути роздiленi на
три групи: символьнi (char, wchar_t, char16_t, char32_t), цiлочисельнi
(short, int, long, long long) i типи чисел з плаваючою крапкою (float,
double, long double).

Символьнi типи

Для представлення символiв в додатку використовуються типи char,


wchar_t, char16_t i char32_t.
Визначимо декiлька змiнних:
char c = ’ d ’ ;
wchar_t d = ’ c ’ ;

Змiнна типу char як значення приймає один символ в одинарних лапках:


char c =’d’. Також можна привласнити число з вказаного вище в списку
30

дiапазону: char c = 120. В цьому випадку значенням змiнної c буде той


символ, який має код 120 в таблицi символiв ASCII.
Варто враховувати, що для виводу на консоль символiв wchar_t слiд вико-
ристовувати не std::cout, а потiк std::wcout:
#i n c l u d e <i o s t r e a m >
main ( )
{
char а = ’H ’ ;
wchar_t b = ’ e ’ ;
s t d : : wcout << а << b << ’\ n ’ ;
return 0;
}
При цьому потiк std::wcout може працювати як з char, так i з wchar_t.
А потiк std::cout для змiнної wchar_t замiсть символу виводитиме його
числовий код.
У стандартi С/С++11 були доданi типи char16_t i char32_t, якi орiєнто-
ванi на використовування Unicode. Проте на рiвнi ОС поки не реалiзованi
потоки для роботи з цими типами. Тому якщо буде потрiбно вивести на кон-
соль значення змiнних цих типiв, то необхiдно перетворити змiннi до типiв
char або wchar_t:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
char а = ’H ’ ;
wchar_t b = ’ e ’ ;
char16_t з = ’ l ’ ;
char32_t d = ’ o ’ ;
s t d : : cout << а << ( char ) b << ( char ) з << ( char ) d << "\n " ;
return 0;
}
У даному випадку при виводi перед змiнними указується операцiя приведення
до типу char - (char), завдяки чому значення змiнних b, c i d перетворяться
в тип char i можуть бути виведенi на консоль за допомогою потоку std::cout.

Цiлочисельнi типи

Цiлочисельнi типи представленi наступними типами: short, unsigned short,


int, unsigned int, long, unsigned long, long long i unsigned long long:
s h o r t а = −10;
31

u n s i g n e d s h o r t b= 1 0 ;
i n t c = −30;
unsigned i n t d = 60;
l o n g e = −170;
unsigned long f = 45;
long long g = 89;

Типи чисел з плаваючою точкою

Типи чисел з плаваючою крапкою иили дробовi числа представленi такими


типами як float, double i long double:
f l o a t а = −10.45;
double b = 0 . 0 0 1 0 5 ;
l o n g double c = 3 0 . 8 9 0 0 4 5 ;

Розмiри типiв даних

У вище приведеному списку для кожного типу вказаний розмiр, який вiн за-
ймає в пам’ятi. Проте варто вiдзначити, що граничнi розмiри для типiв роз-
робники компiляторiв можуть вибирати самостiйно, виходячи з апаратних
можливостей комп’ютера. Стандарт встановлює лише мiнiмальнi значення,
якi повиннi бути. Наприклад, для типiв int i short мiнiмальне значення -
16 бiт, для типу long - 32 бiти, для типу long double. При цьому розмiр
типу long повинен бути не менше розмiру типу int, а розмiр типу int - не
менше розмiру типу short, а розмiр типу long double повинен бути бiльше
double. Наприклад, компiлятор g++ пiд Windows для long double викори-
стовує 12 байт, а компiлятор, вбудований в Visual Studio i також працюючий
пiд Windows, для long double використовує 8 байт. Тобто навiть в рамках
однiєї платформи рiзнi компiлятори можуть по рiзному пiдходити до розмiрiв
деяких типiв даних. Але в цiлому використовуються тi розмiри, якi вказанi
вище при описi типiв даних.
Проте бувають ситуацiї, коли необхiдно точно знати розмiр певного типу.
I для цього в С/С++ є оператор sizeof(), який повертає розмiр пам’ятi в
байтах, яку займає змiнна:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
l o n g double number = 2 ;
s t d : : cout << " s i z e o f ( number)=" << s i z e o f ( number ) ;
32

return 0;
}

Консольний вивiд при компiляцiї в g++:


( s i z e o f ( number ) = 12

При цьому при визначеннi змiнних важливо розумiти, що значення змiнної


не повинне виходити за тi межi, якi обкресленi для її типу. Наприклад:
u n s i g n e d s h o r t number = −65535;

Компiлятор G++ при компiляцiї програми з цим рядком видасть помилку


про те, що значення -65535 не входить в дiапазон допустимих значень для
типу unsigned short i буде усiчено.
У Visual Studio компiляцiя може пройти без помилок, проте при цьому змiн-
на number набуде значення 2 - результат перетворення числа -65535 до типу
unsigned short. Тобто знову ж таки результат буде не зовсiм той, який очi-
кується. Значення змiнної - це всього лише набiр бiтiв в пам’ятi, якi iнтерпре-
туються вiдповiдно до певного типу. I для рiзних типiв один i той же набiр
бiтiв може iнтерпретуватися по рiзному. Тому важливо враховувати дiапазо-
ни значень для того або iншого типу при привласненнi змiнної значення.

Специфiкатор auto

Iнодi важко визначити тип виразу. I згiдно останнiм стандартам можна на-
дати компiлятору самому виводити тип об’єкту. I для цього застосовується
специфiкатор auto. При цьому якщо ми визначаємо змiнну iз специфiкатором
auto, ця змiнна повинна обов’язково iнiцiалiзувати яким-небудь значенням:
auto number = 5 ;

На пiдставi привласненого значення компiлятор виведе тип змiнної. Неiнiцi-


алiзованi змiннi iз специфiкатором auto не допускаються:
auto number ;

3.2. Статична типiзацiя i перетворення типiв

С++ є мовою програмування, що статично типiзується. Тобто якщо ми ви-


значили для змiнної якийсь тип даних, то в подальшому ми цей тип змiнити
не зможемо. Вiдповiдно змiнна може набути значення тiльки того типа, якого
вона представляє. Проте нерiдко виникає необхiднiсть привласнити змiннiй
значення якихось iнших типiв. I в цьому випадку застосовуються перетворе-
ння типiв.
33

Ряд перетворень компiлятор може проводити неявно, тобто автоматично. На-


приклад:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
i n t code = ’ g ’ ;
char l e t t e r = 1 0 3 ;
s t d : : cout << l e t t e r << " i n ASCII i s " << code << "\n " ;
return 0;
}

У даному випадку числовiй змiннiй типу int привласнюється символ ’g’. Цей
символ автоматично перетворюватиметься в число. По факту змiнна одер-
жить числовий код цього символу в таблицi ASCII.
Змiнної letter, навпаки, привласнюється число, хоча ця змiнна представляє
тип char. Це число перетворюватиметься в символ, i у результатi змiнна
letter берегтиме символ, чий код в таблицi ASCII рiвний 103, тобто символ
’g’.
Результатом цiєї програми буде наступний консольний вивiд:
i n ASCII i s 103

Як виконуються перетворення:
— Змiннiй типу bool привласнюється значення iншого типу. В цьому випадку
змiнна одержує false, якщо значення рiвне 0. У всiй рештi випадкiв змiнна
одержує true.
bool а = 1; // t r u e
bool b = 0; // f a l s e
bool c = ’g ’ ; // t r u e
bool d = 3.4; // t r u e

— Числовiй або символьнiй змiннiй привласнюється значення типу bool. В


цьому випадку змiнна одержує 1, якщо значень рiвне true, або одержує 0,
якщо привласнюване значення рiвне false.
int c = true ; // 1
double d = f a l s e ; // 0

— Цiлочисельнiй змiннiй привласнюється дробове число. В цьому випадку


дробова частина пiсля коми вiдкидається.
int а = 3.4; // 3
int b = 3.6; // 3
34

— Змiнною, яка представляє тип з плаваючою крапкою, привласнюється цiле


число. В цьому випадку якщо цiле число мiстить бiльше бiтiв, нiж може
вмiщати тип змiнної, то частина iнформацiї усiкається.
f l o a t а = 35005; // 35005
double b = 3 5 0 0 5 0 0 0 0 0 0 3 3 ; // 3 . 5 0 0 5 e+012
— Змiннiй беззнакового типу (unsigned) привласнюється значення не з його
дiапазону. В цьому випадку результатом буде залишок вiд розподiлу по мо-
дулю. Наприклад, тип unsigned char може берегти значення вiд 0 до 255.
Якщо привласнити йому значення зовнi цього дiапазону, то компiлятор при-
власнить йому залишок вiд розподiлу по модулю 256 (оскiльки тип unsigned
char може берегти 256 значень). Так, при привласненнi значення -1 змiнна
типу unsigned char одержить
u n s i g n e d char а = −5; // 251
u n s i g n e d s h o r t b = −3500; // 62036 ( )
u n s i g n e d i n t c = −50000000; // 4244967296
— Змiннiй знакового типу (signed) привласнюється значення не з його дiапа-
зону. В цьому випадку результат не визначений. Програма може працювати
нормально, видаючи адекватний результат, а може працювати некоректно.

Небезпечнi i безпечнi перетворення

Тi перетворення, при яких не вiдбувається втрата iнформацiї, є безпечни-


ми. Як правило, це перетворення вiд типу з меншою розряднiстю до типу з
бiльшою розряднiстю. Зокрема, це наступнi ланцюжки перетворень:
b o o l −> char −> s h o r t −> i n t −> double −> l o n g double
b o o l −> char −> s h o r t −> i n t −> l o n g −> l o n g l o n g
u n s i g n e d char −> u n s i g n e d s h o r t −> u n s i g n e d i n t
−> u n s i g n e d l o n g
f l o a t −> double −> l o n g double
Приклади безпечних перетворень:
short а = ’g ’ ; // перетворення з char в s h o r t
int b = 10;
double c = b ; // перетворення з i n t в double
double d = 3 . 4 ;
double e = d ; // перетворення з f l o a t в double
double f = 3 5 ; // перетворення з i n t в double
Але також є небезпечнi перетворення. При подiбних перетвореннях ми по-
тенцiйно можемо втратити точнiсть даних. Як правило, це перетворення вiд
35

типу з бiльшою розряднiстю до типу з меншою розряднiстю.


char l e t t e r = 2 9 5 ;
: : cout << l e t t e r ;

У даному випадку змiнної letter привласнюється значення, яке виходить за


межi дiапазону допустимих значень для типу char, тобто бiльше 255.
В подiбних прикладах багато що залежить вiд компiлятора. У рядi випадкiв
компiлятори при компiляцiї видають попередження, проте програма може бу-
ти успiшно скомпiльована. В iнших випадках компiлятори не видають нiяко-
го попередження. Власне в цьому i полягає небезпека, що програма успiшно
компiлюється, але проте iснує ризик втрати точностi даних. I, як правило,
в подiбних випадках при компiляцiї привласнюване значення усiкається до
допустимого. Наприклад, в прикладi вище число 295 буде скорочено до 39.
Тобто наступнi змiннi мiститимуть одне i те ж значення:
char l e t t e r 1 = 2 9 5 ;
char l e t t e r 2 = 3 9 ;

3.3. Константи

Вiдмiтною особливiстю змiнних є те, що ми можемо багато разiв протягом


роботи програми змiнювати їх значення:
int x = 7;
x = 9;
x = 5;

Але окрiм змiнних в мовi програмування C++ можна визначати константи.


Їх значення встановлюється один раз i згодом ми його не можемо змiнити.
Константа визначається практично також, як i змiнна за тим виключенням,
що на початку визначення константи йде ключове слово const. Наприклад:
const int x = 22;
s t d : : cout << x ;

Якщо ж ми схочемо пiсля визначення константи привласнити їй деяке зна-


чення, то компiлятор не зможе скомпiлювати програму i виведе помилку:
const int x = 22;
x = 78;

Тобто такий код не працюватиме. I оскiльки не можна змiнити значення кон-


станти, то її завжди необхiдно iнiцiалiзувати, якщо ми хочемо, щоб вона мала
деяке значення.
36

Якщо константа не iнiцiалiзує, то компiлятор також виведе помилку i не


зможе скомпiлювати програму, як в наступному випадку:
const int x ;

Як значення константам можна передавати як звичайнi лiтерали, так i дина-


мiчно вычысляемые значення, наприклад, значення змiнних або iнших кон-
стант:
int а = 10;
const int b = 7;
const int d = b;
const int x = а ;

Звичайно як константи визначаються такi значення, якi повиннi залишатися


постiйними протягом роботи всiєї програми i не можуть бути змiненi. Напри-
клад, якщо програми виконує математичнi операцiї з використанням числа
PI, то було б оптимально визначити данi значення як константу, оскiльки
воно все одно у принципi незмiнне:
const f l o a t pi = 3 . 1 4 ;

3.4. Арифметичнi операцiї та привласнення

Арифметичнi операцiї проводяться над числами. Значення, якi беруть участь


в операцiї, називаються операндами. В мовi програмування C++ арифмети-
чнi операцiї бiнарними (проводяться над двома операндами) i унарними (ви-
конуються над одним операндом). До бiнарних операцiй вiдносять наступнi:
Арифметична операця + Операцiя складання повертає суму двох чисел:
int а = 10;
int b = 7;
int c = а + b; // 17
int d = 4 + b; // 11

Арифметична операця - Операцiя вiднiмання повертає рiзницю двох чи-


сел:
int а = 10;
int b = 7;
int c = а − b ; // 3
int d = 41 − b ; // 34

Арифметична операця * Операцiя множення повертає твiр двох чисел:


int а = 10;
37

int b = 7;
int c = а ∗ b; // 70
int d = b ∗ 5; // 35

Арифметична операця / Операцiя дiлення повертає частку двох чисел:


int а = 20;
int b = 5;
int c = а / b; // 4
int d = 22.5 / 4 . 5 ; // 5

При дiленнi варто бути уважним, оскiльки якщо в операцiї беруть участь
два цiлi числа, то результат дiлення округлятиметься до цiлого числа, навiть
якщо результат привласнюється змiнної float або double:
double k = 10 / 4 ; // 2
s t d : : cout << k ;

Щоб результат представляв число з плаваючою крапкою, один з операндiв


також повинен представляти число з плаваючою крапкою:
double = 1 0 . 0 / 4 ; // 2 . 5
s t d : : cout << k ;

Арифметична операця % Операцiя отримання залишку вiд цiлочисель-


ного дiлення:
int а = 33;
int b = 5;
int c = а % b; // 3
int d = 22 % 4 ; // 2 (22 − 4∗5 = 2)

Також є двi унарнi арифметичнi операцiї, якi проводяться над одним числом:
++ (iнкремент) i – (декремент). Кожна з операцiй має два рiзновиди: пре-
фiксна i постфiксна:
Префiксний iнкремент Збiльшує значення змiнної на одиницю i одержа-
ний результат використовується як значення виразу ++x
int а = 8;
int b = ++a ;
std : : cout << а << "\n " ; // 9
std : : cout << b << "\n " ; // 9

Постфiксний iнкремент Збiльшує значення змiнної на одиницю, але зна-


ченням виразу x++ буде те, яке було до збiльшення на одиницю
intа = 8;
i n t b = a++;
38

s t d : : cout << а << "\n " ; // 9


s t d : : cout << b << "\n " ; // 8

Префiксний декремент Зменшує значення змiнної на одиницю, i набуте


значення використовується як значення виразу –x
int а = 8;
int b = −−а ;
std : : cout << а << "\n " ; // 7
std : : cout << b << "\n " ; // 7

Постфiксний декремент Зменшує значення змiнної на одиницю, але зна-


ченням виразу x– буде те, яке було до зменшення на одиницю
int а = 8;
int b = а−−;
std : : cout << а << "\n " ; // 7
std : : cout << b << "\n " ; // 8

Арифметичнi операцiї обчислюються злiва направо. Однi операцiї мають


бiльший прiоритет нiж iншi i тому виконуються спочатку. Операцiї в порядку
зменшення прiоритету:
++ ( iнкремент ) ,
−− ( декремент )
∗ ( множення )
/ ( дiлення )
% ( залишок в i д дiлення )
+ ( складання ) ,
− ( вiднiмання )

Прiоритет операцiй слiд враховувати при виконаннi набору арифметичних


виразiв:
int а = 8;
int b = 7;
int c = а + 5 ∗ ++b ; // 48
std : : cout << c ;

Хоча операцiї виконуються злiва направо, але спочатку виконуватиметься


операцiя iнкремента ++b, яка збiльшить значення змiнної b i поверне його
як результат, оскiльки ця операцiя має бiльший прiоритет. Потiм виконується
множення 5 * ++b, i лише в останню чергу виконується складання а + 5
* ++b
Дужки дозволяють перевизначити порядок обчислень. Наприклад:
39

int а = 8;
int b = 7;
int c = ( а + 5) ∗ ++b ; // 104
std : : cout << c ;

Не дивлячись на те, що операцiя складання має менший прiоритет, але спо-


чатку виконуватиметься саме складання, а не множення, оскiльки операцiя
складання укладена в дужки.

Операцiї привласнення

Операцiї привласнення дозволяють привласнити деяке значення. Цi операцiї


виконуються над двома операндами, причому лiвий операнд може представ-
ляти iменований вираз, що тiльки модифiкується, наприклад, змiнну.
Базова операцiя привласнення = дозволяє привласнити значення правого
операнда лiвому операнду:
int x ;
x = 2

Тобто в даному випадку змiнна x (лiвий операнд) матиме значення 2 (правий


операнд).
Варто вiдзначити, що тип значення правого операнда не завжди може спiв-
падати з типом лiвого операнда. В цьому випадку компiлятор намагається
перетворити значення правого операнда до типу лiвого операнда.
При цьому операцiї привласнення мають правостороннiй порядок, тобто ви-
конуються справа налiво. I, таким чином, можна виконувати множинне при-
власнення:
int a , b , c ;
a = b = c = 34;

Тут спочатку обчислюється значення виразу c = 34. Значення правого опе-


ранда - 34 привласнюється лiвому операнду з. Далi обчислюється вираз b = c:
значення правого операнда c (34) привласнюється лiвому операнду b. I в кiнцi
обчислюється вираз а = b: значення правого операнда b (34) привласнюється
лiвому операнду а.
Крiм того, слiд зазначити, що операцiї привласнення мають якнайменший
прiоритет в порiвняннi з iншими типами операцiй, тому виконуються в остан-
ню чергу:
int x ;
x = 3 + 5;
40

Вiдповiдно до прiоритету операцiй спочатку виконується вираз 3 + 5, i тiльки


потiм його значення привласнюється змiнної x.
Вся решта операцiй привласнення є поєднанням простої операцiї привласне-
ння з iншими операцiями:

• +=: привласнення пiсля складання. Привласнює лiвому операнду суму


лiвого i правого операндiв: А += B еквiвалентно А = А + B
• -=: привласнення пiсля вiднiмання. Привласнює лiвому операнду рiзни-
цю лiвого i правого операндiв: А -= B еквiвалентно А = А - B
• *=: привласнення пiсля множення. Привласнює лiвому операнду твiр
лiвого i правого операндiв: А *= B еквiвалентно А = А * B
• /=: привласнення пiсля розподiлу. Привласнює лiвому операнду прива-
тне лiвого i правого операндiв: А /= B еквiвалентно А = А / B
• %=: привласнення пiсля розподiлу по модулю. Привласнює лiвому опе-
ранду залишок вiд цiлочисельного розподiлу лiвого операнда на правий:
А
• «=: привласнення пiсля зсуву розрядiв влiво. Привласнює лiвому опе-
ранду результат зсуву його бiтового уявлення влiво на певну кiлькiсть
розрядiв, рiвну значенню правого операнда: А «= B еквiвалентно А = А
«B
• »=: привласнення пiсля зсуву розрядiв управо. Привласнює лiвому опе-
ранду результат зсуву його бiтового уявлення управо на певну кiлькiсть
розрядiв, рiвну значенню правого операнда: А »= B еквiвалентно А = А
»B
• &=: привласнення пiсля порозрядної кон’юнкцiї. Привласнює лiвому
операнду результат порозрядної кон’юнкцiї його бiтового уявлення з бi-
товим представленням правого операнда: А &= B еквiвалентно А = А
&B
• |=: привласнення пiсля порозрядної диз’юнкцiї. Привласнює лiвому опе-
ранду результат порозрядної диз’юнкцiї його бiтового уявлення з бiтовим
представленням правого операнда: А |= B еквiвалентно А = А | B
• =:
ˆ привласнення пiсля операцiї виключає АБО. Привласнює лiвому опе-
ранду результат операцiї виключає АБО його бiтового уявлення з бiто-
вим представленням правого операнда: А =
ˆ B еквiвалентно А = А B̂

Приклади операцiй:
41

int a = 5;
a += 1 0 ; // 15
a −= 3 ; // 12
a ∗= 2 ; // 24
a /= 6 ; // 4
a <<= 4 ; // 64
a >>= 2 ; // 16

3.5. Посилання

Посилання (reference) представляє спосiб манiпулювати яким-небудь об’є-


ктом. Фактично посилання - це альтернативне iм’я для об’єкту. Для визна-
чення посилання застосовується знак амперсанда &:
i n t number = 5 ;
i n t &refNumber = number ;

У даному випадку визначено посилання refNumber, яке посилається на об’єкт


number. При цьому у визначеннi посилання використовується той же тип,
який представляє об’єкт, на який посилання посилається, тобто в даному
випадку int.
При цьому не можна просто визначити посилання:
i n t &refNumber ;

Вона обов’язково повинна указувати на який-небудь об’єкт.


Також не можна привласнити посиланню лiтеральне значення, наприклад,
число:
i n t &refNumber = 1 0 ;

Пiсля встановлення посилання ми можемо через неї манiпулювати самим об’-


єктом, на який вона посилається:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
i n t number = 5 ;
i n t &refNumber = number ;
s t d : : cout << refNumber << s t d : : e n d l ; // 5
refNumber = 2 0 ;
s t d : : cout << number << s t d : : e n d l ; // 20
42

return 0;
}
Змiни по посиланню неминуче позначаться i на тому об’єктi, на який поси-
лається посилання.
Можна визначати не тiльки посилання на змiннi, але i посилання на констан-
ти. Але при цьому посилання саме повинне бути константним:
c o n s t i n t number = 5 ;
c o n s t i n t &refNumber = number ;
s t d : : cout << refNumber << s t d : : e n d l ; // 5
// refNumber = 2 0 ; изменять значение по ссылке нельзя
Iнiцiалiзувати неконстантне посилання константним об’єктом ми не можемо:
c o n s t i n t number = 5 ;
i n t &refNumber = number ; // ошибка
Також константне посилання може указувати i на звичайну змiнну, тiльки
значення по такому посиланню ми не зможемо змiнити:
i n t number = 5 ;
c o n s t i n t &refNumber = number ;
s t d : : cout << refNumber << s t d : : e n d l ; // 5
// refNumber = 2 0 ; изменять значение
// по ссылке на константу нельзя
// но мы можем изменить саму переменную
number = 2 0 ;
s t d : : cout << refNumber << s t d : : e n d l ; // 20
У даному випадку не дивлячись на те, що ми не можемо напряму змiнити
значення по константному посиланню, проте ми можемо змiнити сам об’єкт,
що приведе природно до змiни константного посилання.

3.6. Простори iмен i using

При читаннi i записi в попереднiх темах використовувалися об’єкти std::cout


i std::cin вiдповiдно. Причому вони використовувалися з префiксом std::. Цей
префiкс указує, що об’єкти cout, cin, endl визначенi в пространствен iмен std.
А сама подвiйна двокрапка :: представляє оператор областi видимостi (scope
operator), який дозволяє вказати, в якому просторi iмен визначений об’єкт. I
без префiкса цi об’єкти за умовчанням ми використовувати не можемо.
Проте подiбний запис може показатися дещо громiздкою. I в цьому випадку
можна використовувати оператор using, який дозволяє ввести в програму
об’єкти з рiзних просторiв iмен.
43

Використовування оператора using має следующй формат:


u s i n g простiр_iмен : : объект
Наприклад, хай у нас є наступна програма:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
i n t age ;
s t d : : cout << " Input age : " ;
s t d : : c i n >> age ;
s t d : : cout << "Your age : "
<< age << s t d : : e n d l ;
return 0;
}
Тут використовуються вiдразу три об’єкти з програнства iмен std: cout, cin i
endl. Перепишемо програму з використанням using:
#i n c l u d e <i o s t r e a m >
using std : : cin ;
u s i n g s t d : : cout ;
using std : : endl ;

i n t main ( )
{
i n t age ;
cout << " Input age : " ;
c i n >> age ;
cout << "Your age : "
<< age << e n d l ;
return 0;
}
Для кожного об’єкту з простору std визначається свiй вираз using. При цьому
програма працюватиме також як i ранiше.

3.7. Питання та завдання для самоконтролю знань

• Який змiст має термiн: типи данных мови програмування?


• Назвiть основнi типи даних мови програмування С/С++.
• Якi властивостi мають символьнi типи даних?
44

• Якi властивостi мають цiлочисельнi типи даних та типи чисел з плава-


ючою точкою?
• Якi розмiри мають основнi типи даних? Якi властивостi у функцiї si-
zeof()?
• Коли доцiльно використовувати специфiкатор auto?
• Що таке статична типiзацiя i в яких випадках використовується пере-
творення типiв?
• В яких випадках використовується константи мови програмування
С/С++?
• Якi арифметичнi операцiї використовується константи в мовi програму-
вання С/С++?
• Для чого використовується посилання (reference) i який знак застосову-
ється для визначення посилання?
• Що таке простори iмен i як використовується iнструкцiя using?

4. Оператори управлiння у мовi програмування С/С++

4.1. Умовнi вирази та операцiї вiдношення

Умовнi вирази є деякою умовою i повертають значення типу bool, тобто зна-
чення true (якщо умова iстинна), або значення false (якщо умова помилкова).
До умовних виразiв вiдносяться операцiї вiдношення i логiчнi операцiї.
У мовi програмування C++ є наступнi операцiї вiдношення:
— == Операцiя ”рiвно”. Повертає true, якщо обидва операнди рiвнi, i false,
якщо вони не рiвнi:
int a = 10;
int b = 4;
b o o l c = a == b ; // f a l s e
b o o l d = a == 1 0 ; // t r u e

— > Операцiя ”бiльш нiж”. Повертає true, якщо перший операнд бiльше дру-
гого, i false, якщо перший операнд менше другого:
int a = 10;
int b = 4;
bool c = a > b ; // t r u e
45

— < Операцiя ”менше нiж”. Повертає true, якщо перший операнд менше дру-
гого, i false, якщо перший операнд бiльше другого:
b o o l c = 10 < 4 ; // f a l s e

— <= Операцiя ”менше або рiвно”. Повертає true, якщо перший операнд мен-
ше або рiвно другому, i false, якщо перший операнд бiльше другого:
b o o l c = 10 <= 4 ; // f a l s e
b o o l d = 10 <= 1 4 ; // t r u e

— >= Операцiя ”бiльше або рiвно”. Повертає true, якщо перший операнд
бiльше або рiвно другому, i false, якщо перший операнд менше другого:
b o o l c = 10 >= 4 ; // t r u e
b o o l d = 10 >= 1 4 ; // f a l s e

— != Операцiя не ”рiвно”. Повертає true, якщо перший операнд не рiвний


другому, i false, якщо обидва операнди рiвнi:
b o o l c = 10 != 4 ; // t r u e
b o o l d = 4 != 4 ; // f a l s e

Звичайно операцiї вiдношення застосовуються в умовних конструкцiях типу


if...else, якi будуть розглянутi далi.

4.2. Логiчнi операцiї

Логiчнi операцiї звичайно об’єднують декiлька операцiй вiдношення. До ло-


гiчних операцiй вiдносять наступнi:
— ! (операцiя заперечення) Унарна операцiя, яка повертає true, якщо
операнд рiвний false. Якщо операнд рiвний true, операцiя повертає false.
bool a = true ;
bool b = ! a ; // f a l s e
b o o l c = !10 <5; // t r u e

— && (кон’юнкцiя, логiчне множення) Повертає true, якщо обидва опе-


ранди не рiвнi false. Повертає false, якщо хоча б один операнд рiвний false.
bool a = true ;
bool b = false ;
bool c = a && b ; // f a l s e
bool d = a && t r u e ; // t r u e

— || (диз’юнкцiя, логiчне складання) Повертає true, якщо хоча б один


операнд рiвний true. Повертає false, якщо обидва операнди рiвнi false.
46

bool a = true ;
bool b = false ;
bool c = a || b; // t r u e
bool d = b || false ; // f a l s e

Використовуємо одночасно декiлька логiчних операцiй i операцiй порiвняння:


#i n c l u d e <i o s t r e a m >
i n t main ( )
{
b o o l а = −2 > 5 ; // f a l s e
bool b = 0 < 7; // t r u e
b o o l c = 10 > 5 && 7 < 1 1 ; // t r u e
b o o l d = а && b | | c ; // t r u e
s t d : : cout << а << "\n " ; // 0
s t d : : cout << b << "\n " ; // 1
s t d : : cout << c << "\n " ; // 1
s t d : : cout << d << "\n " ; // 1
return 0;
}

Cлiд звернути увагу, що на консоль виводиться не безпосередньо true або


false, а їх числовi еквiваленти - 1 для true i 0 для false.
I також варто враховувати, що операцiї вiдношення мають бiльший прiоритет,
нiж логiчнi операцiї. Тому у виразi 10 > 5 && 7 < 11 спочатку виконуватиму-
ться пiдвирази - операцiї вiдношення 10 > 5 i 7 < 11, а потiм власне операцiя
логiчного множення.

4.3. Побiтовi операцiї

Побiтовi операцiї виконуються над окремими розрядами або бiтами чисел.


Данi операцiї проводяться тiльки над цiлими числами.
Операцiї зсуву. Кожне цiле число в пам’ятi представлено у виглядi певної
кiлькостi розрядiв. I операцiї зсуву дозволяють зсунути бiтове представлення
числа на декiлька розрядiв управо або влiво. Операцiї зсуву застосовуються
тiльки до цiлочисельних операндiв. Є двi операцiї:
— « Зсовує бiтове представлення числа, представленого першим операндом,
влiво на певну кiлькiсть розрядiв, яка задається другим операндом.
— » Зсовує бiтове представлення числа управо на певну кiлькiсть розрядiв.
Вживання операцiй:
i n t a = 2 << 2 ; // 10 на два разрядов влево = 1000 − 8
47

i n t b = 16 >> 3 ; // 10000 на три разряда вправо = 10 − 2

Число 2 в двiйковому уявленнi 10. Якщо зсунути число 10 на два розряди


влiво, то вийде 1000, що в десятковiй системi рiвне число 8.
Число 16 в двiйковому уявленнi 10000. Якщо зсунути число 10 на три розряди
управо (три останнi розряди вiдкидаються), то вийде 10, що в десятковiй
системi представляє число 2.

4.4. Порозряднi операцiї

Порозряднi операцiї також проводяться тiльки над вiдповiдними розрядами


цiлочисельних операндiв:

• &: порозрядна кон’юнкцiя (операцiя I або порозрядне множення). По-


вертає 1, якщо обидва з вiдповiдних розрядiв обох чисел рiвнi 1
• |: порозрядна диз’юнкцiя (операцiя АБО або порозрядне складання). По-
вертає 1, якщо хоча б один з вiдповiдних розрядiв обох чисел рiвний 1
• :̂ що порозрядне виключає АБО. Повертає 1, якщо тiльки один з вiдпо-
вiдних розрядiв обох чисел рiвний 1
• : порозрядне заперечення або iнверсiя. Iнвертує всi розряди операнда.
Якщо розряд рiвний 1, то вiн стає рiвний 0, а якщо вiн рiвний 0, то вiн
набуває значення 1.

Вживання операцiй:
int a = 5 | 2; // 101 | 010 = 111 − 7
int b = 6 & 2; // 110 & 010 = 10 − 2
int c = 5 ^ 2; // 101 ^ 010 = 111 − 7
int d = ~9; // −10

Наприклад, вираз 5 | 2 рiвно 7. Число 5 в двiйковому записi рiвно 101, а число


2 - 10 або 010. Складемо вiдповiднi розряди обох чисел. При складаннi якщо
хоча б один розряд рiвний 1, то сума обох розрядiв рiвна 1. Тому одержуємо:
1 0 1
0 1 0
1 1 1

У результатi одержуємо число 111, що в десятковому записi представляє чи-


сло 7.
48

Вiзьмемо iнший вираз 6 & 2. Число 6 в двiйковому записi рiвно 110, а число
2 - 10 або 010. Помножимо вiдповiднi розряди обох чисел. Твiр обох розря-
дiв рiвний 1, якщо обидва цi розряди рiвнi 1. Iнакше твiр рiвний 0. Тому
одержуємо:
1 1 0
0 1 0
0 1 0

Одержуємо число 010, що в десятковiй системi рiвно 2.

4.5. Умовнi конструкцiї

Умовнi конструкцiї направляють хiд програми по одному з можливих шляхiв


залежно вiд умови.

Конструкцiя if

Конструкцiя if перевiряє iстиннiсть умови, i якщо воно iстинне, виконує блок


iнструкцiй. Цей оператор має наступну скорочену форму:
i f ( умова )
{
iнструкцiї ;
}

Як умова використовуватися умовний вираз, який повертає true або false.


Якщо умова повертає true, то виконуються подальшi iнструкцiї, якi входять
в блок if. Якщо умова повертає false, то подальшi iнструкцiї не виконуються.
Блок iнструкцiй полягає у фiгурнi дужки.
Наприклад:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
int x = 60;

i f ( x > 50)
{
s t d : : cout << "x i s g r e a t e r than 50 \n " ;
}
49

i f ( x < 30)
{
s t d : : cout << "x i s l e s s than 30 \n " ;
}

s t d : : cout << "End o f Program" << "\n " ;


return 0;
}

Тут визначено двi умовнi конструкцiї if. Вони перевiрять бiльше або мен-
ше значення змiнної x, нiж певне значення. Як iнструкцiя в обох випадках
виконується виведення деякого рядка на консоль.
У першому випадку x > 50 умова iстинна, оскiльки значення змiнної x дiйсне
бiльше 50, тому ця умова поверне true, i, отже, будуть виконаються iнструкцiї,
якi входять в блок if.
У другому випадку операцiя вiдношення x < 30 поверне false, оскiльки умова
помилкова, тому подальший блок iнструкцiй виконуватися не буде. У резуль-
татi при запуску програми виведення консолi виглядатиме таким чином:
x g r e a t e r than 50
End o f Program

Також ми можемо використовувати повну форму конструкцiї if, яка включає


оператор else:
i f ( вираз_условия )
iнструкцiя_1
else
iнструкцiя_2

Пiсля оператора else ми можемо визначити набiр iнструкцiй, якi виконую-


ться, якщо умова в операторi if повертає false. Тобто якщо умова iстинна,
виконуються iнструкцiї пiсля оператора if, а якщо цей вираз помилковий, то
виконуються iнструкцiї пiсля оператора else.
int x = 50;
i f ( x > 60)
s t d : : cout << "x i s g r e a t e r than 60 \n " ;
else
s t d : : cout << "x i s l e s s or e q u a l 60 \n " ;

У даному випадку умова x > 60 помилково, тобто повертає false, тому вико-
нуватиметься блок else. I у результатi на консоль буде виведений рядок ”x is
less or equal 60 n”.
50

Проте нерiдко треба обробити не два можливi альтернативнi варiанти, а наба-


гато бiльше. Наприклад, у випадку вище можна налiчити три умови: змiнна
x може бути бiльше 60, менше 60 i рiвна 60. Для перевiрки альтернативних
умов ми можемо вводити вирази else if:
int x = 60;

i f ( x > 60)
{
s t d : : cout << "x i s g r e a t e r than 60 \n " ;
}
e l s e i f ( x < 60)
{
s t d : : cout << "x i s l e s s than 60 \n " ;
}
else
{
s t d : : cout << "x i s e q u a l 60 \n " ;
}

Тобто в даному випадку ми одержуємо три вiтки розвитку подiй в програмi.


Якщо в блоцi if або else або else-if необхiдно виконати тiльки одну iнструкцiю,
то фiгурнi дужки можна опустити:
int x = 60;

i f ( x > 60)
s t d : : cout << "x i s g r e a t e r than 60 \n " ;
e l s e i f ( x < 60)
s t d : : cout << "x i s l e s s than 60 \n " ;
else
s t d : : cout << "x i s e q u a l 60 \n " ;

Конструкцiя switch

Iншу форму органiзацiї галуження програм представляє конструкцiя swi-


tch...case. Вона має наступну форму:
s w i t c h ( вираз )
{
c a s e константа_1 : i н с т р у к ц i ї _ 1 ;
c a s e константа_2 : i н с т р у к ц i ї _ 2 ;
51

default : iнструкцiї ;
}

Пiсля ключового слова switch в дужках йде порiвнюваний вираз. Значення


цього виразу послiдовно порiвнюється iз значеннями пiсля оператора сase. I
якщо збiг буде знайдений, то виконуватиметься певний блок сase.
У кiнцi конструкцiї switch може стояти блок default. Вiн необов’язковий i ви-
конується в тому випадку, якщо значення пiсля switch не вiдповiдає жодному
з операторiв case. Наприклад:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
int x = 2;

switch (x)
{
case 1:
s t d : : cout << "x = 1" << "\n " ;
break ;
case 2:
s t d : : cout << "x = 2" << "\n " ;
break ;
case 3:
s t d : : cout << "x = 3" << "\n " ;
break ;
default :
s t d : : cout << "x i s u n d e f i n e d " << "\n " ;
break ;
}
return 0;
}

Щоб уникнути виконання подальших блокiв case/default, в кiнцi кожного


блоку ставиться оператор break. Тобто в даному випадку виконуватиметься
оператор
case 2:
s t d : : cout << "x = 2" << "\n " ;
break ;

Пiсля виконання оператора break вiдбудеться вихiд з конструкцiї switch..case,


i решта операторiв case буде проiгнорована. Тому на консоль буде виведений
52

наступний рядок
x = 2
Варто вiдзначити важливiсть використовування оператора break. Якщо ми
його не вкажемо в блоцi case, то пiсля цього блоку виконання перейде до
наступного блоку case. Наприклад, приберемо з попереднього прикладу всi
оператори break:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
int x = 2;

switch (x)
{
case 1:
std : : cout << "x = 1" << "\n " ;
case 2:
std : : cout << "x = 2" << "\n " ;
case 3:
std : : cout << "x = 3" << "\n " ;
default :
std : : cout << "x i s u n d e f i n e d " << "\n " ;
}
return 0;
}
У цьому випадку знову ж таки виконуватиметься оператор case 2:, оскiльки
змiнна x=2. Проте оскiльки цей блок case не завершується оператором break,
то пiсля його завершення виконуватиметься набiр iнструкцiй пiсля case 3:
навiть не дивлячись на те, що змiнна x як i ранiше рiвна 2. У результатi ми
одержимо наступний консольний вивiд:
x = 2
x = 3
x i s undefined

4.6. Тернарний оператор

Тернарний оператор ?: дозволяє скоротити визначення найпростiших умов-


них конструкцiй if i має наступну форму:
[ перший операнд − умова ] ? [ другий операнд ] : [ т р е т i й операнд ]
53

Оператор використовує вiдразу три операнди. Залежно вiд умови тернарный


оператор повертає другий або третiй операнд: якщо умова рiвна true (тоб-
то iстинно), то повертається другий операнд; якщо умова рiвна false (тобто
помилково), то третiй. Наприклад:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
s e t l o c a l e (LC_ALL, " " ) ;
int x = 5;
int y = 3;
char s i g n ;
s t d : : cout << " Введiть знак о п е р а ц i ї : " ;
s t d : : c i n >> s i g n ;
i n t r e s u l t = s i g n ==’+’?x + y : x − y ;
s t d : : cout << " Результат : " << r e s u l t << "\n " ;
return 0;
}

У даному випадку проводиться введення знаку операцiї. Тут результатом


тернарной операцiї є змiнна result. I якщо змiнна sign мiстить знак "+ то
result буде рiвне другому операнду - (x+y). Iнакше result буде рiвне третьому
операнду.

4.7. Питання та завдання для самоконтролю знань

• Що таке умовнi вирази i до якого типу даних вони вiдносяться?


• Який зв’язок мають логiчнi операцiї та операцiй вiдношення?
• Назвiть логiчнi операцiї, що застосовуються в мовi програмування
С/С++.
• Якi операцiї мають бiльший прiорiтет: операцiї вiдношення чи логiчнi
операцiї?
• Який змiст мають побiтовi та порозряднi операцiї?
• Як програмуються основнi умовнi конструкцiї в мовi програмування
С/С++? Навести приклади.
• Що таке тернарний оператор i якi правила його використання?
54

5. Цикли. Оператори циклу у мовi програмування


С/С++

Для виконання деяких дiй множина разiв залежно вiд певної умови викори-
стовуються цикли. В мовi C++ є наступнi види циклiв:

• for
• while
• do...while

5.1. Цикл while

Цикл while виконує деякий код, поки його умова iстинна, тобто повертає true.
Вiн має наступне формальне визначення:
w h i l e ( умова )
{
// виконання коду
}

Пiсля ключового слова while в дужках йде умовний вираз, який повертає true
або false. Потiм у фiгурних дужках йде набiр iнструкцiй, якi складають тiло
циклу. I поки умова повертає true, виконуватимуться iнструкцiї в тiлi циклу.
Наприклад, виведемо квадрати чисел вiд 1 до 9:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
int i = 1;
w h i l e ( i < 10)
{
s t d : : cout << i << " ∗ " << i
<< " = " << i ∗ i << s t d : : e n d l ;
i ++;
}

return 0;
}
55

Тут поки умова i < 10 iстинно, виконуватиметься цикл while, в якому виводи-
ться на консоль квадрат числа i инкрементируется змiнна i. В якийсь момент
змiнна i збiльшиться до 10, умова i < 10 поверне false, i цикл завершиться.
Консольне виведення програми:
1 ∗ 1 = 1
2 ∗ 2 = 4
3 ∗ 3 = 9
4 ∗ 4 = 16
5 ∗ 5 = 25
6 ∗ 6 = 36
7 ∗ 7 = 49
8 ∗ 8 = 64
9 ∗ 9 = 81

Кожний окремий прохiд циклу називається iтерацiєю. Тобто в прикладi вище


було 9 iтерацiй.
Якщо цикл мiстить одну iнструкцiю, то фiгурнi дужки можна опустити:
int i = 0;
w h i l e(++ i < 10)
s t d : : cout << i << " ∗ " << i
<< " = " << i ∗ i << s t d : : e n d l ;

5.2. Цикл for

Цикл for має наступне формальне визначення:


f o r ( вираз_1 ; вираз_2 ; вираз_3 )
{
// тело цикла
}

вираз_1 виконується один раз при початку виконання циклу i представляє


установку початкових умов, як правило, це iнiцiалiзацiя лiчильникiв - спецi-
альних змiнних, якi використовуються для контролю за циклом.
вираз_2 представляє умову, при дотриманнi якої виконується цикл. Як пра-
вило, як умова використовується операцiя порiвняння, i якщо вона повертає
ненульове значення (тобто умова iстинна), то виконується тiло циклу, а потiм
обчислюється вираз_3.
вираз_3 задає змiну параметрiв циклу, нерiдко тут вiдбувається збiльшення
лiчильникiв циклу на одиницю.
56

Наприклад, перепишемо програму по виведенню квадратiв чисел за допомо-


гою циклу for:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
f o r ( i n t i =1; i < 1 0 ; i ++)
{
s t d : : cout << i << " ∗ " << i
<< " = " << i ∗ i << s t d : : e n d l ;
}

return 0;
}
Перша частина оголошення циклу - int i = 1 - створює i iнiцiалiзував лi-
чильник i. Фактично це те ж саме, що i оголошення i iнiцiалiзацiя змiнної.
Лiчильник необов’язково повинен представляти тип int. Це може бути i iн-
ший числовий тип, наприклад, float. I перед виконанням циклу його значення
буде рiвне 1.
Друга частина - умова, при якiй виконуватиметься цикл. В даному випадку
цикл виконуватиметься, поки змiнна i не стане рiвна 10.
I третя частина - прирiст лiчильника на одиницю. Знову ж таки нам необо-
в’язково збiльшувати на одиницю. Можна зменшувати: i–. Можна змiнювати
на iнше значення: i+=2.
У результатi блок циклу спрацює 9 разiв, поки змiнна i не стане рiвна 10. I
кожного разу це значення збiльшуватиметься на 1. I по сутi ми одержимо той
же самий результат, що i у випадку з циклом while:
1 ∗ 1 = 1
2 ∗ 2 = 4
3 ∗ 3 = 9
4 ∗ 4 = 16
5 ∗ 5 = 25
6 ∗ 6 = 36
7 ∗ 7 = 49
8 ∗ 8 = 64
9 ∗ 9 = 81
Необов’язково указувати всi три вирази у визначеннi циклу, ми можемо одне
або навiть все з них опустити:
int i = 1;
57

for (; i < 10;)


{
s t d : : cout << i << " ∗ " << i
<< " = " << i ∗ i << s t d : : e n d l ;
i ++;
}

Формально визначення циклу залишилося тим же, тiльки тепер перше i третє
вирази у визначеннi циклу вiдсутнi: for (; i < 10;). Змiнна-лiчильник визначена
i iнiцiалiзувала зовнi циклу, а її прирiст вiдбувається в самому циклi.
Можна визначати вкладенi цикли. Наприклад, виведемо таблицю множення:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
f o r ( i n t i =1; i < 1 0 ; i ++)
{
f o r ( i n t j = 1 ; j < 1 0 ; j ++)
{
s t d : : cout << i ∗ j << "\ t " ;
}
s t d : : cout << s t d : : e n d l ;
}

return 0;
}

Таблиця множення на мовi C++

5.3. Цикл do

У циклi do спочатку виконується код циклу, а потiм вiдбувається перевiрка


умови в iнструкцiї while. I поки ця умова iстинна, тобто не рiвна 0, то цикл
повторюється. Формальне визначення циклу:
do
{
iнструкцiї
}
w h i l e ( умова ) ;

Наприклад:
58

#i n c l u d e <i o s t r e a m >

i n t main ( )
{
int i = 6;
do
{
s t d : : cout << i << s t d : : e n d l ;
i −−;
}
w h i l e ( i >0);

return 0;
}

Тут код циклу спрацює 6 разiв, поки i не стане рiвним нулю.


Але важливо вiдзначити, що цикл do гарантує хоча б однократне виконання
дiй, навiть якщо умова в iнструкцiї while не буде iстинна. Тобто ми можемо
написати:
i n t i = −1;
do
{
s t d : : cout << i << s t d : : e n d l ;
i −−;
}
w h i l e ( i >0);
}

Хоча у нас змiнна i менше 0, цикл все одно один раз виконається.

5.4. Оператори continue i break

Iнодi виникає необхiднiсть вийти з циклу до його завершення. В цьому ви-


падку можна скористатися оператором break. Наприклад:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
int i = 1;
for ( ; ; )
{
59

s t d : : cout << i << " ∗ " << i


<< " = " << i ∗ i << s t d : : e n d l ;
i ++;
i f ( i > 9) break ;
}
return 0;
}

Тут коли значення змiнної i досягне 10, здiйснюється вихiд з циклу за допо-
могою оператора break.
На вiдмiну вiд оператора break, оператор continue проводить перехiд до насту-
пної iтерацiї. Наприклад, нам треба порахувати суму тiльки непарних чисел
з деякого дiапазону:
#i n c l u d e <i o s t r e a m > main ( )
{
int result = 0;
f o r ( i n t i =0; i <10; i ++)
{
i f ( i % 2 == 0) c o n t i n u e ;
r e s u l t +=i ;
}
s t d : : cout << " r e s u l t = " << r e s u l t
<< s t d : : e n d l ; // 25
return 0;
}

Щоб взнати, чи парне число, ми одержуємо залишок вiд цiлочисельного роз-


подiлу на 2, i якщо вiн рiвний 0, то за допомогою оператора continue перехо-
димо до наступної iтерацiї циклу. А якщо число непарне, то складаємо його
з рештою непарних чисел.

5.5. Питання та завдання для самоконтролю знань

• Що таке цикли i для чого вони використовуються в програмуваннi?


• Якi види циклiв застосовуються у мовi програмування C++?
• Яке формальне визначення має оператор циклу while? Навести приклад
його використання.
• Яке формальне визначення має оператор циклу for? Навести приклад
його використання.
60

• Яке формальне визначення має оператор циклу do? Навести приклад


його використання.
• Яку роль вiдiграють оператори continue i break при програмуваннi ци-
клiв? Навести приклад їх використання.

6. Масиви у мовi програмування С/С++

6.1. Визначення та перебiр одновимiрних масивiв

Масив представляє набiр однотипних даних. Формальне визначення масиву


виглядає таким чином:
тип_змiнної назва_масиву [ довжина масиву ]

Пiсля типу змiнної йде назва масиву, а потiм в квадратних дужках його роз-
мiр. Наприклад, визначимо масив з 4 чисел:
i n t numbers [ 4 ] ;

Даний масив має чотири числа, але всi цi числа мають невизначене значення.
Проте ми можемо виконати iнiцiалiзацiю i привласнити цим числам деякi
початковi значення через фiгурнi дужки:
i n t numbers [ 4 ] = { 1 , 2 , 3 , 4 } ;

Значення у фiгурних дужках ще називають iнiцiалiзацiями. Якщо iнiцiалi-


зацiй менше нiж елементiв в масивi, то iнiцiалiзацiї використовуються для
перших елементiв. Якщо в iнiцiалiзацiй бiльше, нiж елементiв в масивi, то
при компiляцiї виникне помилка:
i n t numbers [ 4 ] = {1 , 2 , 3 , 4 , 5 , 6 } ;

Тут масив має розмiр 4, проте йому передається 6 значень.


Якщо розмiр масиву не вказаний явно, то вiн виводиться з кiлькостi iнiцiалi-
зацiй:
i n t numbers [ ] = {1 , 2 , 3 , 4 , 5 , 6 } ;

У даному випадку в масивi є 6 елементiв.


Свої особливостi має iнiцiалiзацiя символьних масивiв. Ми можемо передати
символьному масиву як набiр iнiцiалiзацiй, так i рядок:
char s1 [ ] = { ’ h ’ , ’ e ’ , ’ l ’ , ’ l ’ , ’ o ’ } ;
char s2 [ ] = " world " ;
61

Причому в другому випадку масив s2 матиме не 5 елементiв, а 6, оскiльки


при iнiцiалiзацiї рядком в символьний масив автоматично додається нульовий
символ ’\0’.
При цьому не допускається привласнення одному масиву iншого масиву:
i n t nums1 [ ] = { 1 , 2 , 3 , 4 , 5 } ;
i n t nums2 [ ] = nums1 ; // помилка
nums2 = nums1 ; // помилка

Пiсля визначення масиву ми можемо звернутися до його окремих елемен-


тiв по iндексу. Iндекси починаються з нуля, тому для звернення до першого
елементу необхiдно використовувати iндекс 0. Звернувшися до елементу по
iндексу, ми можемо набути його значення, або змiнити його:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
i n t numbers [ 4 ] = { 1 , 2 , 3 , 4 } ;
i n t first_number = numbers [ 0 ] ;
s t d : : cout << first_number << s t d : : e n d l ; // 1
numbers [ 0 ] = 3 4 ; // изменяем элемент
s t d : : cout << numbers [ 0 ] << s t d : : e n d l ; // 34

return 0;
}

Число елементiв масиву також можна визначати через константу:


const int n = 4;
i n t numbers [ n ] = { 1 , 2 , 3 , 4 } ;

Використовуючи цикли, можна пробiгтися по всьому масиву i через iндекси


звернутися до його елементiв:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
i n t numbers [ 4 ] = { 1 , 2 , 3 , 4 } ;
i n t s i z e = s i z e o f ( numbers )/ s i z e o f ( numbers [ 0 ] ) ;
f o r ( i n t i =0; i < s i z e ; i ++)
s t d : : cout << numbers [ i ] << s t d : : e n d l ;

return 0;
62

}
Щоб пройтися по масиву в циклi, спочатку треба знайти довжину масиву.
Для знаходження довжини застосовується оператор sizeof. По сутi довжина
масиву рiвна сукупнiй довжинi його елементiв. Всi елементи представляють
один i той же тип i займають один i той же розмiр в пам’ятi. Тому за допо-
могою виразу sizeof(numbers) знаходимо довжину всього масиву в байтах, а
за допомогою виразу sizeof(numbers[0]) - довжину одного елементу в байтах.
Роздiливши два значення, можна одержати кiлькiсть елементiв в масивi. А
далi за допомогою циклу for перебираємо всi елементи, поки лiчильник i не
стане рiвним довжинi масиву. У результатi на консоль будуть виведенi всi
елементи масиву:
1
2
3
4
Але також є i ще одна форма циклу for, яка призначена спецiально для роботи
з колекцiями, у тому числi i з масивами. Ця форма має наступне формальне
визначення:
f o r ( тип змiнна : колекцiя )
{
iнструкцiї ;
}
Використовуємо цю форму для перебору масиву:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
i n t numbers [ 4 ] = { 1 , 2 , 3 , 4 } ;
f o r ( i n t number : numbers )
s t d : : cout << number << s t d : : e n d l ;

return 0;
}
При переборi масиву кожний перебираний елемент помiщатиметься в змiнну
number, значення якої в циклi виводиться на консоль.
Якщо нам невiдомий тип об’єктiв в масивi, то ми можемо використовувати
специфiкатор auto для визначення типу:
f o r ( auto number : numbers )
63

s t d : : cout << number << s t d : : e n d l ;

6.2. Багатовимiрнi масиви

Окрiм одновимiрних масивiв в C++ є багатовимiрнi. Елементи таких масивiв


самi у свою чергу є масивами, в яких також елементи можуть бути масивами.
Наприклад, визначимо двомiрний масив чисел:
i n t numbers [ 3 ] [ 2 ] ;

Такий масив складається з трьох елементiв, при цьому кожний елемент пред-
ставляє масив з двох елементiв. Iнiцiалiзували подiбний масив:
i n t numbers [ 3 ] [ 2 ] = { {1 , 2} , {4 , 5} , {7 , 8} } ;

Вкладенi фiгурнi дужки обкреслюють елементи для кожного пiдмасиву. Та-


кий масив ще можна представити у виглядi таблицi:
1 2
4 5
7 8

Також при iнiцiалiзацiї можна опускати фiгурнi дужки:


i n t numbers [ 3 ] [ 2 ] = { 1 , 2 , 4 , 5 , 7 , 8 } ;

Можлива також iнiцiалiзацiя не всiх елементiв, а тiльки деяких:


i n t numbers [ 3 ] [ 2 ] = { {1 , 2} , {} , {7} } ;

I щоб звернутися до елементiв вкладеного масиву, буде потрiбно два iндекси:


i n t numbers [ 3 ] [ 2 ] = { {1 , 2} , {3 , 4} , {5 , 6} } ;
s t d : : cout << numbers [ 1 ] [ 0 ] << s t d : : e n d l ; // 3
numbers [ 1 ] [ 0 ] = 1 2 ; // изменение элемента
s t d : : cout << numbers [ 1 ] [ 0 ] << s t d : : e n d l ; // 12

Переберемо двовимiрний масив:


#i n c l u d e <i o s t r e a m >

i n t main ( )
{
c o n s t i n t rows = 3 , columns = 2 ;
i n t numbers [ rows ] [ columns ] = { {1 , 2} , {3 , 4} , {5 , 6} } ;
f o r ( i n t i =0; i < rows ; i ++)
{
f o r ( i n t j =0; j < columns ; j ++)
64

{
s t d : : cout << numbers [ i ] [ j ] << "\ t " ;
}
s t d : : cout << s t d : : e n d l ;
}
return 0;
}

Також для перебору елементiв багатовимiрного масиву можна використову-


вати iншу форму циклу for:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
c o n s t i n t rows = 3 , columns = 2 ;
i n t numbers [ rows ] [ columns ] = { {1 , 2} , {3 , 4} , {5 , 6} } ;

f o r ( auto &subnumbers : numbers )


{
f o r ( i n t number : subnumbers )
{
s t d : : cout << number << "\ t " ;
}
s t d : : cout << s t d : : e n d l ;
}

return 0;
}

Для перебору масивiв, якi входять в масив, застосовуються посилання. Тобто


в зовнiшньому циклi for(auto &subnumbers : numbers) &subnumbers представ-
ляє посилання на пiдмасив в масивi. У внутрiшньому циклi for(int number :
subnumbers) з кожного пiдмасиву в subnumbers одержуємо окремi його еле-
менти в змiнну number i виводимо її значення на консоль.

6.3. Рядки як масиви

Для зберiгання рядкiв в C++ застосовується тип string. Для використову-


вання цього типу його необхiдно пiдключити в код за допомогою директиви
include:
#i n c l u d e <s t r i n g >
65

#i n c l u d e <i o s t r e a m >

i n t main ( )
{
s t d : : s t r i n g h e l l o = " H e l l o World ! " ;
s t d : : cout << h e l l o << "\n " ;
return 0;
}
Тип string визначений в стандартнiй бiблiотецi i при його використовуваннi
треба указувати простiр iмен std.
Або можна використовувати вираз using, щоб не указувати префiкс std:
using std : : s t r i n g ;
У даному випадку значення змiнної hello, яка представляє тип string, виво-
диться на консоль.
При компiляцiї через g++ може бути потрiбно вказати прапор -static. Тобто
якщо код визначений у файл hello.cpp, то команда на компiляцiю для g++
може виглядати таким чином:
g++ h e l l o . cpp −o h e l l o −s t a t i c
Для iнiцiалiзацiї рядкiв можна використовувати рiзнi способи:
#i n c l u d e <s t r i n g >
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
std : : s t r i n g s1 ; // пустая строка
std : : s t r i n g s2 = " h e l l o " ; // h e l l o
std : : s t r i n g s3 (" welcome " ) ; // welcome
std : : s t r i n g s4 ( 5 , ’ h ’ ) ; // hhhhh
std : : s t r i n g s5 = s2 ; // h e l l o

s t d : : cout << s1 << "\n " ;


s t d : : cout << s2 << "\n " ;
s t d : : cout << s3 << "\n " ;
s t d : : cout << s4 << "\n " ;
s t d : : cout << s5 << "\n " ;
return 0;
}
Консольне виведення даної програми:
66

hello
welcome
hhhhh
hello
Якщо при визначеннi змiнної типу string ми не привласнюємо їй нiякого зна-
чення, то за умовчанням дана змiнна мiстить порожнiй рядок:
s t d : : s t r i n g s1 ;
Також можна iнiцiалiзувати змiнну рядковим лiтералом, який полягає в по-
двiйнi лапки:
s t d : : s t r i n g s2 = " h e l l o " ;
Як альтернатива можна передавати рядок в дужках пiсля визначення змiн-
ної:
s t d : : s t r i n g s3 (" welcome " ) ;
Якщо необхiдно, щоб рядок мiстив визначену кiлькiсть визначених символiв,
то можна вказати в дужках кiлькiсть символiв i сам символ:
s t d : : s t r i n g s4 ( 5 , ’ h ’ ) ;
I також можна передати змiннiй копiю iншого рядка:
s t d : : s t r i n g s5 = s2 ;

Конкатенацiя рядкiв

Над рядками можна виконувати ряд операцiй. Зокрема, можна об’єднувати


рядки за допомогою стандартної операцiї складання:
#i n c l u d e <i o s t r e a m >
u s i n g s t d : : cout ;
using std : : endl ;
using std : : s t r i n g ;

i n t main ( )
{
s t r i n g s1 = " h e l l o " ;
s t r i n g s2 = " world " ;
s t r i n g s3 = s1 + " " + s2 ; // h e l l o world
cout << s3 << e n d l ;
return 0;
}
67

Порiвняння рядкiв

До рядкiв можна застосовувати операцiї порiвняння. Оператор == повертає


true, якщо всi символи обох рядкiв рiвнi.
s t d : : s t r i n g s1 = " h e l l o " ;
s t d : : s t r i n g s2 = " world " ;

b o o l r e s u l t = s1 == s2 ; // f a l s e
r e s u l t = s1 == " H e l l o " ; // f a l s e
r e s u l t = s1 == " h e l l o " ; // t r u e

При цьому символи повиннi спiвпадати у тому числi по регiстру.


Операцiя != повертає true, якщо два рядки не спiвпадають.
s t d : : s t r i n g s1 = " h e l l o " ;
s t d : : s t r i n g s2 = " world " ;

b o o l r e s u l t = s1 != s2 ; // t r u e
r e s u l t = s1 != " H e l l o " ; // t r u e
r e s u l t = s1 != " h e l l o " ; // f a l s e

Решта базових операцiй порiвняння < <= > >= порiвнюють рядки залежно
вiд регiстра i алфавiтного порядку символiв. Наприклад, рядок "b"умовно
бiльший рядка "a оскiльки символ "b"за абеткою йде пiсля символу "а". А
рядок "a"бiльше рядка "A". Якщо першi символи рядка рiвнi, то порiвнюю-
ться подальшi символи:
s t d : : s t r i n g s1 = " Aport " ;
s t d : : s t r i n g s2 = " A p r i c o t " ;
b o o l r e s u l t = s1 > s2 ; // f a l s e

У даному випадку умова s1 > s2 помилкове, тобто s2 бiльш нiж s1, оскiльки
при рiвностi перших двох символiв ("Ар") третiй символ другого рядка ("o")
стоїть в алфавiтi до третього символу другого рядка ("p"), тобто "o"менше
нiж "p".

Розмiр рядка

За допомогою методу size() можна взнати розмiр рядка, тобто iз скiлькох


символiв вiн складається:
s t d : : s t r i n g s1 = " h e l l o " ;
s t d : : cout << s1 . s i z e ( ) << s t d : : e n d l ; // 5
68

Якщо рядок порожнiй, то вiн мiстить 0 символiв. В цьому випадку ми можемо


застосувати метод empty() - вiн повертає true, якщо рядок порожнiй:
s t d : : s t r i n g s1 = " " ;
i f ( s1 . empty ( ) )
s t d : : cout << " S t r i n g i s empty" << s t d : : e n d l ;

6.4. Читання i введення рядка з консолi

Читання рядка з консолi

Для прочитування введеного рядка з консолi можна використовувати об’єкт


std::cin:
#i n c l u d e <i o s t r e a m >
#i n c l u d e <s t r i n g >

i n t main ( )
{
s t d : : s t r i n g name ;
s t d : : cout << " Input your name : " ;
s t d : : c i n >> name ;
s t d : : cout << "Your name : " << name << s t d : : e n d l ;
return 0;
}

Консольне введення

Input your name : Tom


Your name : Tom

Проте якщо при даному способi введення рядок мiститиме пiдрядки, роздi-
ленi пропуском, то std::cin використовуватиме тiльки перший пiдрядок:
Input your name : Tom Smith
Your name : Tom

Щоб рахувати весь рядок, застосовується метод getline():


#i n c l u d e <i o s t r e a m >
#i n c l u d e <s t r i n g >

i n t main ( )
{
69

s t d : : s t r i n g name ;
s t d : : cout << " Input your name : " ;
g e t l i n e ( s t d : : cin , name ) ;
s t d : : cout << "Your name : " << name << s t d : : e n d l ;
return 0;
}

Метод getline приймає два об’єкти - std::cin i змiнну, в яку треба занести
рядок.
Консольне виведення:
Input your name : Tom Smith
Your name : Tom Smith

6.5. Отримання i змiна символiв рядка

Подiбно масиву ми можемо звертатися за допомогою iндексiв до окремих


символiв рядка, одержувати i змiнювати їх:
std : : s t r i n g h e l l o = " Hello ";
char c = h e l l o [ 1 ] ; // e
h e l l o [ 0 ] = ’M’ ;
s t d : : cout << h e l l o << s t d : : e n d l ; // Mello

6.6. Символьнi масиви

Масив символiв, останнiй елемент якого представляє нульовий символ ’\0’,


може використовуватися як рядок:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
char l e t t e r s [ ] = { ’ h ’ , ’ e ’ , ’ l ’ , ’ l ’ , ’ o ’ , ’ \ 0 ’ } ;
s t d : : cout << l e t t e r s << s t d : : e n d l ;

return 0;
}

Даний код виведе на консоль рядок "hello". Подiбне визначення масиву ряд-
кiв буде також еквiвалентне наступному:
char l e t t e r s [ ] = " h e l l o " ;
70

Проте подiбне використовування масиву рядкiв успадковувано вiд мови СI,


а при написаннi програм на С/С++ при роботi з рядками слiд вiддавати
перевагу вбудованому типу string, а не масиву символiв.

6.7. Питання та завдання для самоконтролю знань

• Що таке масив даних? Яке формальне визначення мають одновимiрнi


масиви даних?
• Як виконується iнiцiалiзацiя одновимiрних масивiв? Якi особливостi ма-
ють числовi та символьнi одновимiрнi масиви?
• Якi способи перебору одновимiрного масиву передбаченi в мовi програ-
мування С/С++?
• Яке формальне визначення мають багатовимiрнi масиви даних?
• Як типи iнiцiалiзацiї використовуються у випадку багатовимiрних маси-
вiв?
• Який зв’язок має мiсце мiж таким типом даних як рядок (string) i маси-
вами?
• Що таке конкатенацiя рядкiв?
• Якi особливостi має операцiя порiвняння рядкiв?
• Як визначається розмiр рядка?
• Яким чином можна отримаи або змiнити певнi символи рядка?

7. Функцiї та об’єкти у мовi програмування С/C++

7.1. Визначення i об’ява функцiй

Визначення функцiї

Функцiя визначає дiї, якi виконує програма. Функцiї дозволяють видiлити


набiр iнструкцiй i додати йому iм’я. А потiм багатократно по привласненому
iменi викликати у рiзних частинах програми. По сутi функцiя - це iменований
блок коду.
Формальне визначення функцiї виглядає таким чином:
71

тип iм ’ я_функцiї ( параметри )


{
iнструкцiї
}

Перший рядок представляє заголовок функцiї. Спочатку указується тип фун-


кцiї, що повертається. Якщо функцiя не повертає нiякого значення, то вико-
ристовується тип void.
Потiм йде iм’я функцiї, яке представляє довiльний iдентифiкатор. До iмену-
юче функцiї застосовуються тi ж правила, що i до iменуюче змiнних.
Пiсля iменi функцiї в дужках йде перелiк параметрiв. Функцiя може не мати
параметрiв, в цьому випадку указуються порожнi дужки.
Пiсля заголовка функцiї у фiгурних дужках йде тiло функцiї, яке мiстить
виконуванi iнструкцiї.
Для повернення результату функцiя застосовує оператор return. Якщо фун-
кцiя має як тип, що повертається, будь-який тип, окрiм void, то вона повинна
обов’язково за допомогою оператора return повертати яке-небудь значення.
Наприклад, визначення функцiї main, яка повинна бути в будь-якiй програмi
на язицi C++ i з якою починається її виконання:
main ( )
{
return 0;
}

Типом функцiї, що повертається, є тип int, тому функцiя повинна використо-


вувати оператор return i повертати яке-небудь значення, яке вiдповiдає типу
int. Значення, що повертається, ставиться пiсля оператора return.
Але якщо функцiя має тип void, то їй не треба нiчого повертати. Наприклад,
ми могли б визначити наступну функцiю:
void h e l l o ( )
{
s t d : : cout << " h e l l o \n " ;
}

Виконання функцiї

Для виконання функцiї її необхiдно викликати. Виклик функцiї здiйснюється


у формi:
iм ’ я_функцiї ( аргументи ) ;
72

Пiсля iменi функцiї указуються дужки, в яких перераховуються аргументи


- значення для параметрiв функцiї. Наприклад, визначимо i виконаємо най-
простiшу функцiю:
#i n c l u d e <i o s t r e a m >
hello ()
{
s t d : : cout << " h e l l o \n " ;
}
main ( )
{
hello ();
hello ();
return 0;
}
Тут визначена функцiя hello, яка викликається у функцiї main двiчi. В цьому
i полягає перевага функцiй: ми можемо винести деякi загальнi дiї в окрему
функцiю i потiм викликати багатократно в рiзних мiсцях програми. У ре-
зультатi програма двiчi виведе рядок "hello".

Оголошення функцiї

При використовуваннi функцiй варто враховувати, що компiлятор повинен


знати про функцiю до її виклику. Тому виклик функцiї повинен вiдбуватися
пiсля її визначення, як у випадку вище. В деяких мовах це не має значен-
ня, але в язицi C++ це грає велику роль. I якщо, наприклад, ми спочатку
викличемо, а потiм визначимо функцiю, то ми одержимо помилку на етапi
компiляцiї, як в наступному випадку:
#i n c l u d e <i o s t r e a m >
main ( )
{
hello ();
hello ();
return 0;
}
hello ()
{
s t d : : cout << " h e l l o \n " ;
}
У цьому випадку перед викликом функцiї треба її додатково оголосити. Ого-
лошення функцiї ще називають прототипом. Формальне оголошення вигля-
73

дає таким чином:


тип iм ’ я_функцiї ( параметри ) ;

Фактично це заголовок функцiї. Тобто для функцiї hello оголошення вигля-


датиме таким чином:
void h e l l o ( ) ;

Використовуємо оголошення функцiї:


#i n c l u d e <i o s t r e a m >
hello ();
main ( )
{
hello ();
hello ();
return 0;
}
hello ()
{
s t d : : cout << " h e l l o \n " ;
}

У даному випадку не дивлячись на те, що визначення функцiї йде пiсля її ви-


клику, але оскiльки функцiя вже оголошена до її виклику, то компiлятор вже
знатиме про функцiю hello, i жодних проблем в роботi програми не виникне.

7.2. Параметри та аргументи функцiї

Через параметри у функцiю можна передати различеые значення. Параметри


перераховуються в дужках пiсля iменi функцiї мають наступне визначення:
тип назва_параметру

Наприклад, визначимо програму обмiнного пункту:


#i n c l u d e <i o s t r e a m >
exchange ( double , double ) ;
main ( )
{
double r a t e = 5 8 ;
double sum = 5 0 0 0 ;
exchange ( r a t e , sum ) ;
exchange ( 6 0 , 5 0 0 0 ) ;
return 0;
74

}
exchange ( double c u r r a t e , double sum )
{
double r e s u l t = sum / c u r r a t e ;
s t d : : cout << " Rate : " << c u r r a t e << "\tSum : " << sum
<< "\ t R e s u l t : " << r e s u l t << s t d : : e n d l ;
}

При запуску програми ми одержимо наступний консольний вивiд:


Rate : 58 Sum : 5000 Result : 86.2069
Rate : 60 Sum : 5000 Result : 83.3333

Функцiя exchange приймає два параметри типу double, якi називаються


currate (поточний курс) i sum (сума, яку треба обмiняти).
При виклику функцiї exchange для цих параметрiв необхiдно передати значе-
ння. Значення, передаванi параметрам функцiї при її виклику, називаються
аргументами. В даному випадку як аргументи передаються звичайнi числа.
При цьому аргументи передаються по позицiї, тобто перший аргумент пере-
дається перому параметру, другий аргумент - другому параметру i так далi.
При цьому аргументи повиннi вiдповiдати параметрам по типу або допускати
неявно перетворення в тип параметра.
Розглянемо ще один приклад з функцiями:
#i n c l u d e <i o s t r e a m >
#i n c l u d e <s t r i n g >
square ( int ) ; display ( std : : string , int ) ;
main ( )
{
d i s p l a y ("Tom" , 3 3 ) ;
square ( 4 . 5 6 ) ;

return 0;
} square ( int x)
{
s t d : : cout << " Square " << x << " i s e q u a l
to " << x ∗ x << s t d : : e n d l ;
}
d i s p l a y ( s t d : : s t r i n g name , i n t age )
{
s t d : : cout << "Name : " << name << "\ tAge : " <<
age << s t d : : e n d l ;
}
75

Функцiя display приймає параметри типiв string i int. При виклику цiєї фун-
кцiї в неї спочатку передається рядок (”Tom”), оскiльки параметр типу string
йде першим, а потiм число (33), оскiльки параметр типу int йде другим: di-
splay(”Tom”, 33)
Функцiя square приймає число i виводить на консоль його квадрат. Пара-
метр функцiї представляє тип int, проте при її виклику їй передається число
з плаваючою крапкою, тобто значення типу double. Тому проводиться пе-
ретворення вiд типу double до типу int, дробова частина вiдкидається, i у
результатi функцiя одержує число 4.

7.3. Аргументи за замовчуванням

Функцiя може приймати аргументи за замовчанням, тобто деякi значення,


якi функцiя використовує, якщо при виклику для параметрiв явним чином
не передається значення:
#i n c l u d e <i o s t r e a m >
m u l t i p l y ( i n t n , i n t m = 3)
{
i n t r e s u l t = n ∗ m;
s t d : : cout << "n ∗ m = " << r e s u l t << s t d : : e n d l ;
} main ( )
{
multiply (4 , 5);
multiply ( 4 ) ;
return 0;
}

Для установки значення за умовчанням параметру привласнюється це значе-


ння int m = 3. I якщо для другого параметра не буде передано значення, то
вiн використовуватиме значення за умовчанням. Консольне виведення про-
грами:
n ∗ m = 20
n ∗ m = 12

При оголошеннi прототипу подiбної функцiї вiн теж може мiстити значення
за умовчанням для параметра. I в цьому випадку ми можемо не визнача-
ти у функцiї значення за умовчанням для параметра - воно братиметься з
прототипу:
#i n c l u d e <i o s t r e a m >
m u l t i p l y ( i n t n , i n t m=3);
main ( )
76

{
multiply (4 , 5);
multiply ( 4 ) ;
return 0;
}
m u l t i p l y ( i n t n , i n t m)
{
i n t r e s u l t = n ∗ m;
s t d : : cout << "n ∗ m = " << r e s u l t << s t d : : e n d l ;

7.4. Передача аргументiв по значенню i по посиланню

Передача аргументiв по значенню

Аргументи можуть передаватися по значенню (value) i по посиланню


(reference). При передачi аргументiв по значенню зовнiшнiй об’єкт, який пе-
редається як аргумент у функцiю, не може бути змiнений в цiй функцiї. У
функцiю передається саме значення цього об’єкту. Наприклад:
#i n c l u d e <i o s t r e a m >
square ( int , int ) ;
main ( )
{
int а = 4;
int b = 5;
s t d : : cout << " B e f o r e s q u a r e :
а = " << а << "\ tb=" << b << s t d : : e n d l ;
square (а , b ) ;
s t d : : cout << " A f t e r s q u a r e :
а = " << а << "\ tb=" << b << s t d : : e n d l ;

return 0;
} square ( int а , int b)
{
а = а ∗ а;
b = b ∗ b;
s t d : : cout << " In s q u a r e :
а = " << а << "\ tb=" << b << s t d : : e n d l ;
}

Функцiя square приймає два числа i зводить їх в квадрат. У функцiї main


перед i пiсля виконання функцiї square вiдбувається висновок на консоль
77

значень змiнних а i b, якi передаються в square як аргументи.


I при виконаннi ми побачимо, що змiни аргументiв у функцiї square дiють
тiльки в рамках цiєї функцiї. Зовнi її значення змiнних а i b залишаються
незмiнними:
square : а = 4 b = 5
s q u a r e : а = 16 b = 25
square : а = 4 b = 5

Чому так вiдбувається? При компiляцiї функцiї для її параметрiв видiляю-


ться окремi дiлянки пам’ятi. При виклику функцiї обчислюються значення
аргументiв, якi передаються на мiсце параметрiв. I потiм значення аргументiв
заносяться в цi дiлянки пам’ятi. Тобто функцiя манiпулює копiями значень
об’єктiв, а не самими об’єктами.

Передача параметрiв по посиланню

При передачi параметрiв по посиланню передається посилання на об’єкт, че-


рез яке ми можемо манiпулювати самим об’єктiв, а не просто його значенням.
Так, перепишемо попереднiй приклад, використовуючи передачу по посилан-
ню:
#i n c l u d e <i o s t r e a m >
s q u a r e ( i n t &, i n t &);
main ( )
{
int а = 4;
int b = 5;
s t d : : cout << " B e f o r e s q u a r e :
а = " << а << "\ tb=" << b << s t d : : e n d l ;
square (а , b ) ;
s t d : : cout << " A f t e r s q u a r e :
а = " << а << "\ tb=" << b << s t d : : e n d l ;

return 0;
} s q u a r e ( i n t &a , i n t &b )
{
а = а ∗ а;
b = b ∗ b;
s t d : : cout << " In s q u a r e :
а = " << а << "\ tb=" << b << s t d : : e n d l ;
}
78

Тепер параметри а i b передаються по посиланню. Посилальний параметр


зв’язується безпосередньо з об’єктом, тому через посилання можна мiняти
сам об’єкт.
I якщо ми скомпiлюємо i запустимо програму, то результат буде iншим:
square : а = 4 b = 5
s q u a r e : а = 16 b = 25
s q u a r e : а = 16 b = 25

Передача по посиланню дозволяє повернути з функцiї вiдразу декiлька зна-


чень. Також передача параметрiв по посиланню є бiльш ефективною при
передачi дуже великих об’єктiв. Оскiльки в цьому випадку не вiдбувається
копiювання значень, а функцiя використовує сам об’єкт, а не його значення.
Вiд передачi аргументiв по посиланню слiд вiдрiзняти передачу посилань як
аргументи:
#i n c l u d e <i o s t r e a m >
square ( int , int ) ;
main ( )
{
int а = 4;
int b = 5;
i n t &aRef = а ;
i n t &bRef = b ;
s t d : : cout << " B e f o r e s q u a r e :
а = " << а << "\ tb=" << b << s t d : : e n d l ;
s q u a r e ( aRef , bRef ) ;
s t d : : cout << " A f t e r s q u a r e :
а = " << а << "\ tb=" << b << s t d : : e n d l ;

return 0;
} square ( int а , int b)
{
а = а ∗ а;
b = b ∗ b;
s t d : : cout << " In s q u a r e :
а = " << а << "\ tb=" << b << s t d : : e n d l ;
}

Якщо функцiя приймає аргументи по значенню, то змiна параметрiв усере-


динi функцiї також нiяк не позначиться на зовнiшнiх об’єктах, навiть якщо
при виклику функцiї в неї передаються посилання на об’єкти.
square : а = 4 b = 5
79

s q u a r e : а = 16 b = 25
square : а = 4 b = 5

Передача параметрiв по значенню бiльше пiдходить для передачi у функцiю


невеликих об’єктiв, значення яких копiюються в певнi дiлянки пам’ятi, якi
потiм використовує функцiя.
Передача параметрiв по посиланню бiльше пiдходить для передачi у функцiю
великих об’єктiв, в цьому випадку не потрiбно копiювати весь вмiст об’єкту
в дiлянку пам’ятi, за рахунок чого збiльшується продуктивнiсть програми.

7.5. Константнi параметри та посилання

Константнi параметри

Параметри можуть бути константними - значення таких параметрiв не мо-


жуть мiнятися. Наприклад:
void square ( const i n t n)
{
// n = n ∗ n ;
// можна рахувати значення параметра , але не змiнювати його
s t d : : cout << n ∗ n << s t d : : e n d l ;
}

Те ж саме торкається i передачi параметра по посиланню:


v o i d s q u a r e ( c o n s t i n t &n )
{
// n = n ∗ n ;
// можна рахувати значення параметра , але не змiнювати його
s t d : : cout << n << s t d : : e n d l ;
}

Константному параметру можна передати як аргумент як константу, так i


змiнну:
#i n c l u d e <i o s t r e a m >
void square ( const int , const i n t ) ;
i n t main ( )
{
const int а = 4;
int b = 5;
square (а , b ) ; // 20
return 0;
80

}
void square ( const i n t а , const i n t b)
{
// a = а ∗ а ; так не можна зробити
//b = b ∗ b ; так не можна зробити
s t d : : cout << " In s q u a r e : а ∗ b = " << а ∗ b << s t d : : e n d l ;
}

Вiд цiєї ситуацiї слiд вiдрiзняти передачу констант як аргументи для некон-
стантних параметрiв:
#i n c l u d e <i o s t r e a m >
void square ( int , i n t ) ;
i n t main ( )
{
const int а = 4;
const int b = 5;
square (а , b ) ; // 400
return 0;
}
void square ( i n t а , i n t b)
{
а = а ∗ а;
b = b ∗ b;
s t d : : cout << " In s q u a r e : а ∗ b = " << а ∗ b << s t d : : e n d l ;
}

Не дивлячись на те, що при виклику функцiї їй передаються константи, але


оскiльки самi параметри не є константними, то функцiя може змiнювати їх
значення.

Константнi посилання

Якщо функцiя одержує аргументи по посиланню, то щоб передати у функцiю


константу, параметри теж повиннi представляти посилання на константу:
#i n c l u d e <i o s t r e a m >
v o i d s q u a r e ( c o n s t i n t &, c o n s t i n t &);
i n t main ( )
{
const int а = 4;
const int b = 5;
square (а , b ) ; // 20
81

return 0;
}
v o i d s q u a r e ( c o n s t i n t &a , c o n s t i n t &b )
{
// а = а ∗ а ; так не можна зробити
// b = b ∗ b ; так не можна зробити
s t d : : cout << " In s q u a r e : а ∗ b = " << а ∗ b << s t d : : e n d l ;
}

I якщо у функцiю необхiдно передати великi об’єкти, якi не повиннi змiнюва-


тися, то визначення параметрiв саме як константних посилань якнайбiльше
пiдходить для даної задачi.

7.6. Оператор return i повернення результату

Для повернення результату з функцiї застосовується оператор return. Цей


оператор має двi форми:
return ;
r e t u r n выражение ;

Перша форма використовується, якщо як тип функцiї, що повертається, за-


стосовується тип void. Наприклад:
#i n c l u d e <i o s t r e a m >
void f a c t o r i a l ( i n t ) ;
i n t main ( )
{
f a c t o r i a l ( −3);
factorial (5);
factorial (4);
return 0;
}

void f a c t o r i a l ( i n t n)
{
i f ( n<1)
{
s t d : : cout << " I n c o r r e c t number" << s t d : : e n d l ;
return ;
}
int result = 1;
f o r ( i n t i = 1 ; i <=n ; i ++)
82

{
r e s u l t ∗= i ;
}
s t d : : cout << " F a c t o r i a l " << n <<
" i s e q u a l to " << r e s u l t << s t d : : e n d l ;
}

У даному випадку функцiя factorial обчислює факторiал переданого числа.


Проте якщо число менше 1, то функцiя виводить вiдповiдне повiдомлення, i
за допомогою оператора return здiйснюється вихiд з функцiї.
Друга форма оператора return застосовується для повернення результату з
функцiї. Якщо функцiя має як тип, що повертається, будь-який тип, вiдмiн-
ний вiд void, то така функцiя обязятельно повинна повернути деяке значення
за допомогою оператора return. Причому значення, яке повертається операто-
ром return, повинне вiдповiдати типу функцiї, що повертається, або допускати
неявне перетворення в цей тип.
Наприклад, повернений з функцiї факторiал числа:
#i n c l u d e <i o s t r e a m >
int factorial ( int );
i n t main ( )
{
int n = 5;
int result = f a c t o r i a l (n ) ;
s t d : : cout << " F a c t o r i a l " << n <<
" i s e q u a l to " << r e s u l t << s t d : : e n d l ;
return 0;
}

int f a c t o r i a l ( int n)
{
int result = 1;
f o r ( i n t i = 1 ; i <=n ; i ++)
{
r e s u l t ∗= i ;
}
return r e s u l t ;
}

Оскiльки функцiя factorial повертає значення, то її результат можна привла-


снити якiй-небудь змiннiй або константi:
int result = f a c t o r i a l (n ) ;
83

Повернення посилання Не слiд повертати посилання на локальний об’єкт,


який створюється усерединi функцiї. Оскiльки всi створюванi у функцiї об’є-
кти вiддаляються пiсля її завершення, а їх пам’ять очищається, те ссыла, що
повертається, указуватиме на неiснуючий об’єкт, як в наступному випадку:
i n t &f a c t o r i a l ( i n t n )
{
int result = 1;
f o r ( i n t i = 1 ; i <=n ; i ++)
{
r e s u l t ∗= i ;
}
return r e s u l t ;
}

7.7. Рекурсивнi функцiї

Рекурсивнi функцiї - це функцiї, якi викликають самi себе. Наприклад, ви-


значимо обчислення факторiалу у виглядi рекурсивної функцiї:
#i n c l u d e <i o s t r e a m >
int factorial ( int );
i n t main ( )
{
int n = 5;
int result = f a c t o r i a l (n ) ;
s t d : : cout << " F a c t o r i a l " << n <<
" i s e q u a l to " << r e s u l t << s t d : : e n d l ;
return 0;
}

int f a c t o r i a l ( int n)
{
i f ( n>1)
r e t u r n n ∗ f a c t o r i a l ( n −1);
return 1;
}

У функцiї factorial задана умова, що якщо число n бiльше 1, то це число


умножається на результат цiєї ж функцiї, в яку як параметр передається
число n-1. Тобто вiдбувається рекурсивний спуск. I так далi, поки не дiйдемо
того моменту, коли значення параметра не буде рiвне 1. В цьому випадку
функцiя поверне 1.
84

Рекурсивна функцiя обов’язково повинна мати деякий базовий варiант, який


використовує оператор return i до якого сходиться виконання решти викли-
кiв цiєї функцiї. У випадку з факторiалом базовий варiант представлений
ситуацiєю, при якiй n = 1. В цьому випадку спрацює iнструкцiя return 1;.
Наприклад, при виклику factorial(5) вийде наступний ланцюг викликiв:
5 ∗ f a c t o r i a l (4)

5 ∗ 4 ∗ f a c t o r i a l (3)

5 ∗ 4 ∗ 3 ∗ f a c t o r i a l (2)

5 ∗ 4 ∗ 3 ∗ 2 ∗ f a c t o r i a l (1)

5 ∗ 4 ∗ 3 ∗ 2 ∗ 1

Iншим поширеним показовим прикладом рекурсивної функцiї служить фун-


кцiя, що обчислює числа Фiббоначчи. n-й член послiдовностi чисел Фiбоначчi
визначається по формулi: f(n)=f(n-1) + f(n-2), причому f(0)=0, а f(1)=1. Зна-
чення f(0)=0 i f(1)=1, таким чином, визначають базовi варiанти для даної
функцiї:
#i n c l u d e <i o s t r e a m >
int fibonachi ( int );
i n t main ( )
{
int n;
f o r ( i n t i = 0 ; i < 1 0 ; i ++)
{
n = fibonachi ( i );
s t d : : cout << n << "\ t " ;
}
s t d : : cout << s t d : : e n d l ;
return 0;
}

int fibonachi ( int n)


{
i f ( n == 0)
return 0;
i f ( n == 1)
return 1;
r e t u r n f i b o n a c h i ( n − 1)+ f i b o n a c h i ( n − 2 ) ;
85

Результат роботи програми - висновок 10 чисел з послiдовностi Фiббоначчи


на консоль:
0 , 1 , 1 , 2 , 3 , 5 , 8 , 13 , 21 , 34

7.8. Об’єкти. Класифiкацiя об’єктiв та їх застосування

Область видимостi (scope) представляє частину програми, в межах якої мо-


жна використовувати об’єкт. Як правило, область видимостi обмежується
блоком коду, який полягає у фiгурнi дужки Залежно вiд областi видимостi
створюванi об’єкти можуть бути глобальними, локальними або автоматични-
ми.

Глобальнi об’єкти

Глобальнi змiннi визначенi у файлi програми зовнi будь-якої з функцiй i мо-


жуть використовуватися будь-якою функцiєю.
#i n c l u d e <i o s t r e a m >
void pri nt ( ) ;
int n = 5;
i n t main ( )
{
p r i n t ( ) ; // n=6
n++;
s t d : : cout << "n=" << n << s t d : : e n d l ; // n=7
return 0;
}

void pri nt ( )
{
n++;
s t d : : cout << "n=" << n << s t d : : e n d l ;
}

Тут змiнна n є глобальною i доступна з будь-якої функцiї. При цьому будь-яка


функцiя може змiнити її значення.
86

Локальнi об’єкти

Об’єкти, якi створюються усерединi блоку коду (вiн може представляти фун-
кцiю або яку-небудь конструкцiю типу циклiв), називаються локальними. Та-
кi об’єкти доступнi в межах тiльки того блоку коду, в якому вони визначенi.

Автоматичнi об’єкти

Локальнi об’єкти, якi iснують тiльки пiд час виконання того блоку, в якому
вони визначенi, є автоматичними.
При входi в блок для подiбних змiнних видiляється пам’ять, а пiсля завер-
шення роботи цього блоку, видiлена пам’ять звiльняється, а об’єкти вiддаля-
ються.
#i n c l u d e <i o s t r e a m >
void pri nt ( i n t ) ;
i n t main ( )
{
int z = 2;
p r i n t ( z ) ; // n=10
//n++; так зробити не можна ,
// оскiльки n визначена у функцiї p r i n t
return 0;
}

void pri nt ( i n t x )
{
int n = 5 ∗ x ;
// z++; так зробити не можна ,
// оскiльки z визначена у функцiї main
s t d : : cout << "n=" << n << s t d : : e n d l ;
}

Тут у функцiї print визначена локальна змiнна n. У функцiї main визначена


автоматична змiнна z. Поза своїми функцiями цi змiннi неприступнi. Напри-
клад, ми не можемо використовувати змiнну n у функцiї main, оскiльки її
область видимостi обмежена функцiєю print. Вiдповiдно також ми не може-
мо використовувати змiнну z у функцiї print, оскiльки ця змiнна обмежена
фукцией main.
Параметри функцiї також, як i локальнi змiннi, iснують, поки виконується
функцiя, поза функцiєю вони не доступнi.
87

Так само за допомогою блоку коду можна визначити вкладенi областi види-
мостi:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
int n = 2;

{
int x = 5;
s t d : : cout << "x=" << x << s t d : : e n d l ;
n++; // так можна ,
// оскiльки n визначена в зовнiшньому к о н т е к с т i
}
//x++; // так зробити не можна ,
// оскiльки x визначена в блоцi коду
s t d : : cout << "n=" << n << s t d : : e n d l ;
return 0;
}

Для кожної областi видимостi доступнi всi тi об’єкти, якi визначенi в зов-
нiшнiй областi видимостi або в зовнiшньому контекстi. Глобальна область
видимостi є зовнiшньою для функцiї, тому функцiя може використовувати
глобальнi змiннi. А фукция є зовнiшнiм контекстом для вкладеного блоку
коду, тому блок коду може використовувати змiнну n, яка визначена у фун-
кцiї зовнi цього блоку. Проте змiннi, визначенi в блоцi коду, зовнi цього блоку
використовувати не можна.

Заховування об’єктiв

Локальнi об’єкти, визначенi усерединi одного контексту, можуть приховувати


об’єкти з тим же iм’ям, визначенi в зовнiшньому контекстi:
#i n c l u d e <i o s t r e a m >
int n = 5;
i n t main ( )
{
int n = 10;
s t d : : cout << "n=" << n << s t d : : e n d l ; // n=10

{
int n = 20;
s t d : : cout << "n=" << n << s t d : : e n d l ; // n=20
88

}
return 0;
}

Тут визначено три змiнних з iм’ям n. Змiнна n, визначена на рiвнi функцiї


main (int n = 10;) приховує глобальну змiнну n. А змiнна n, визначена на
рiвнi блоку, приховує змiнну, визначену на рiвнi функцiї main.

Статичнi об’єкти

Окрiм автоматичних є особливий тип локальних об’єктiв - статичнi об’єкти.


Вони визначаються на рiвнi функцiй за допомогою ключового слова static.
Якщо автоматичнi змiннi визначаються i iнiцiалiзувалися при кожному входi
у функцiю, то статичнi змiннi iнiцiалiзувалися тiльки один раз, а при подаль-
ших викликах функцiї використовується старе значення статичної змiнної.
Наприклад, хай у нас буде функцiя iз стандартною автоматичною змiнною:
#i n c l u d e <i o s t r e a m >
void dis play ( ) ;
i n t main ( )
{
display ( ) ;
display ( ) ;
display ( ) ;

return 0;
}

void dis play ( )


{
int i = 0;
i ++;
s t d : : cout << " i =" << i << s t d : : e n d l ;
}

Функцiя display викликається три рази, i при кожному виклику програма


повторно видiлятиме пам’ять для змiнної i, яка визначена у функцiї. А пiсля
завершення роботи display, пам’ять для змiнної i звiльнятиметься. Вiдповiдно
її значення при кожному виклику буде незмiнне:
i =1
i =1
i =1
89

Тепер зробимо змiнну i статичною:


#i n c l u d e <i o s t r e a m >
void dis play ( ) ;
i n t main ( )
{
display ( ) ;
display ( ) ;
display ( ) ;

return 0;
}

void dis play ( )


{
static int i = 0;
i ++;
s t d : : cout << " i =" << i << s t d : : e n d l ; // n=20
}

До змiнної був додано ключове слово static, тому при завершеннi роботи
функцiї display змiнна не знищується, її пам’ять не очищається, навпаки, вона
зберiгається в пам’ятi. I вiдповiдно результат роботи програми буде iншим:
i =1
i =2
i =3

Зовнiшнi об’єкти

Окрiм функцiй зовнiшнi файли можуть мiстити рiзнi об’єкти - змiннi i кон-
станти. Для пiдключення зовнiшнiх об’єктiв у файл коду застосовується клю-
чове слово extern.
Для оголошення об’єктiв визначимо файл objects.h з наступним вмiстом:
extern const int x ;
e x t e r n double у ;

Тут оголошуються константа x i змiнна у. Оскiльки цi об’єкти будуть зовнi-


шнiми по вiдношенню до початкового коду, який буде їх використовувати, то
вони визначаються з ключовим словом extern.
Для визначення цих об’єктiв додамо новий файл objects.cpp:
#i n c l u d e " o b j e c t s . h"
90

const int x = 5;
double у = 3 . 4 ;

Використовуємо цi об’єкти у файлi app.cpp:


#i n c l u d e <i o s t r e a m >
#i n c l u d e " o b j e c t s . h"
i n t main ( )
{
s t d : : cout << "x = " << x << s t d : : e n d l ;
s t d : : cout << "у = " << у << s t d : : e n d l ;

return 0;
}

7.9. Питання та завдання для самоконтролю знань

• Яке призначення функцiй у мовi програмування С/С++?


• Як програмується формальне визначення функцiї?
• Яким чином здiйснюється виклик функцiї?
• Що таке прототип функцiї i як вiн використовується у мовi програмува-
ння С/С++?
• На якому етапi програмування функцiї визначають її параметри?
• Чим вiдрiзняються аргументи функцiї вiд її параметрiв?
• Як програмуються i використовуються аргументи функцiї за замовчу-
ванням?
• В чому заключається рiзниця передача аргументiв функцiї по значенню
i по посиланню?
• Яку роль вiдiграє оператор return у програмуваннi функцiй?
• Що таке рекурсивнi функцiї i в яких випадках їх доцiльно використову-
вати?
• Яким чином визначається область видимостi об’єктiв?
• Якi об’єкти називаються глобальними, а якi локальними?
• Якi об’єкти називаються зовнiшнiми i коли їх доцiльно використовувати?
91

8. Покажчики у мовi програмування С/C++

8.1. Покажчики

Покажчики є об’єктами, значенням яких служать адреси iнших об’єктiв


(змiнних, констант, покажчикiв) або функцiй. Як i посилання, покажчики
застосовуються для непрямого доступу до об’єкту. Проте на вiдмiну вiд по-
силань покажчики володiють великими можливостями.
Для визначення покажчика треба вказати тип об’єкту, на який указує по-
кажчик, i символ зiрочки *. Наприклад, визначимо покажчик на об’єкт типу
int:
i n t ∗p ;

Поки покажчик не посилається нi на який об’єкт. При цьому на вiдмiну вiд по-
силання покажчик необов’язково iнiцiалiзувати яким-небудь значенням. Те-
пер привласнимо покажчику адресу змiнної:
i n t x = 1 0 ; // визначаємо змiнну
i n t ∗p ; // визначаємо покажчик =
p = &x ; // покажчик одержує адресу з м i н н о ї

Для отримання адреси змiнної застосовується операцiя &. Що важливе, змiн-


на x має тип int, i покажчик, який указує на її адресу, теж має тип int. Тобто
повинна бути вiдповiднiсть по типу.
Якщо ми спробуємо вивести адресу змiнної на консоль, то побачимо, що вiн
представляє шiстнадцяткове значення:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
i n t x = 1 0 ; // визначаємо змiнну
i n t ∗p ; // визначаємо покажчик
p = &x ; // покажчик одержує адресу з м i н н о ї
s t d : : cout << "p = " << p << s t d : : e n d l ;
return 0;
}

Консольне виведення програми:


p = 0 x60fe98

У кожному окремому випадку адреса може вiдрiзнятися, але наприклад, в


моєму випадку машинна адреса змiнної x - 0x60fe98. Тобто в пам’ятi комп’ю-
тера є адреса 0x60fe98, по якому розташовується змiнна x. Оскiльки змiнна
92

x представляє тип int, то на бiльшостi архiтектури вона займатиме наступнi


4 байти (на конкретнiй архiтектурi розмiр пам’ятi для типу int може вiдрi-
знятися). Таким чином, змiнна типу int послiдовно займе елементи пам’ятi з
адресами 0x60FE98, 0x60FE99, 0x60FE9A, 0x60FE9B.
I покажчик p посилатиметься на адресу, по якiй розташовується змiнна x,
тобто на адресу 0x60FE98.
Але оскiльки покажчик береже адресу, то ми можемо за цiєю адресою на-
бути значення, що бережеться там, тобто значення змiнної x. Для цього за-
стосовується операцiя * або операцiя разыменования, тобто та операцiя, яка
застосовується при визначеннi покажчика. Результатом цiєї операцiї завжди
є об’єкт, на який указує покажчик. Застосуємо дану операцiю i набудемо зна-
чення змiнної x:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
int x = 10;
i n t ∗p ;
p = &x ;
s t d : : cout << " Address = " << p << s t d : : e n d l ;
s t d : : cout << " Value = " << ∗p << s t d : : e n d l ;
return 0;
}

Консольний висновок:
Address = 0 x 6 0 f e 9 8
Value = 10

Значення, яке одержане в результатi операцiї разыменования, можна привла-


снити iншiй змiннiй:
int x = 10;
int ∗p = &x ;
int у = ∗p ;
std : : cout << " Value = " << у << s t d : : e n d l ; // 10

I також використовуючи покажчик, ми можемо мiняти значення за адресою,


яка бережеться в покажчику:
int x = 10;
i n t ∗p = &x ;
i n t ∗p = 4 5 ;
: : cout << "x = " << x << s t d : : e n d l ; // 45
93

Оскiльки за адресою, на яку указує покажчик, розташовується змiнна x, то


вiдповiдно її значення змiниться.
Створимо ще декiлька покажчикiв:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
short c = 12;
int d = 10;
short s = 2;

s h o r t ∗pc = &c ; // одержуємо адресу з м i н н о ї c типу s h o r t


i n t ∗pd = &d ; // одержуємо адресу з м i н н о ї d типу i n t
s h o r t ∗ ps = &s ; // одержуємо адресу з м i н н о ї s типу s h o r t

s t d : : cout << " V a r i a b l e c:


a d d r e s s=" << pc << "\ t v a l u e=" << ∗pc << s t d : : e n d l ;
s t d : : cout << " V a r i a b l e d:
a d d r e s s=" << pd << "\ t v a l u e=" << ∗pd << s t d : : e n d l ;
s t d : : cout << " V a r i a b l e s:
a d d r e s s=" << ps << "\ t v a l u e=" << ∗ ps << s t d : : e n d l ;

return 0;
}

У моєму випадку я одержу наступний консольний висновок:


V a r i a b l e c : a d d r e s s=0x 6 0 f e 9 2 v a l u e =12
V a r i a b l e d : a d d r e s s=0x 6 0 f e 8 c v a l u e =10
V a r i a b l e s : a d d r e s s=0x 6 0 f e 8 a v a l u e=2

За адресами можна побачити, що змiннi часто розташованi в пам’ятi поряд,


але не обов’язково в тому порядку, в якому вони визначенi в кодi програми:

8.2. Операцiї з покажчиками

Покажчики пiдтримують ряд операцiй: привласнення, отримання адреси по-


кажчика, отримання значення по покажчику, деякi арифметичнi операцiї i
операцiї порiвняння.
94

Привласнення

Покажчику можна привласнити або адресу об’єкту того ж типу, або значення
iншого покажчика.
Привласнення покажчику адреси вже розглядалося в минулiй темi. Для отри-
мання адреси об’єкту використовується операцiя &:
int а = 10;
i n t ∗pa = &a ; // покажчик pa береже адресу з м i н н о ї а

При цьому покажчик i змiнна повиннi мати один i той же тип, в даному
випадку це тип int.
Привласнення покажчику iншого покажчика:
#i n c l u d e <i o s t r e a m >
u s i n g s t d : : cout ;
using std : : endl ;
i n t main ( )
{
int а = 10;
int b = 2;

i n t ∗pa = &a ;
i n t ∗pb = &b ;

cout << " V a r i a b l e а : a d d r e s s=" << pa <<


"\ t v a l u e=" << ∗pa << e n d l ;
cout << " V a r i a b l e b : a d d r e s s=" << pb <<
"\ t v a l u e=" << ∗pb << e n d l ;

pa = pb ; // тепер покажчик pa береже адресу з м i н н о ї b


cout << " V a r i a b l e b : a d d r e s s=" << pa <<
"\ t v a l u e=" << ∗pa << e n d l ;

return 0;
}

Коли покажчику привласнюється iнший покажчик, то фактично перший по-


кажчик починає також указувати на ту ж адресу, на яку указує другий по-
кажчик.
95

Нульовi покажчики

Нульовий покажчик (null pointer) - це покажчик, який не указує нi на який


об’єкт. Якщо ми не хочемо, щоб покажчик указував на якусь конкретну адре-
су, то можна привласнити йому умовне нульове значення. Для створення ну-
льового покажчика можна застосовувати рiзнi способи:
i n t ∗p1 = n u l l p t r ;
i n t ∗p2 = NULL;
i n t ∗p3 = 0 ;

Посилання на покажчики

Оскiльки посилання не є об’єктом, то не можна визначити покажчик на по-


силання, проте можна визначити посилання на покажчик. Через подiбне по-
силання можна змiнювати значення, на яке указує покажчик або змiнювати
адресу самого покажчика:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
int а = 10;
int b = 6;

i n t ∗p = 0 ; // покажчик
i n t ∗&pRef = p ; // посилання на покажчик
pRef = &a ; // через посилання покажчику p
// привласнюється адреса з м i н н о ї а
s t d : : cout << "p v a l u e=" << ∗p << s t d : : e n d l ; // 10
∗ pRef = 7 0 ; // змiнюємо значення за адресою ,
// на яку указує покажчик
s t d : : cout << "а v a l u e=" << а << s t d : : e n d l ; // 70

pRef = &b ; // змiнюємо адресу , на яку указує покажчик


s t d : : cout << "p v a l u e=" << ∗p << s t d : : e n d l ; // 6

return 0;
}
96

Розiменування покажчика

Операцiя розiменування покажчика представляє вираз у виглядi *iм’я_покажчика


Ця операцiя дозволяє одержати об’єкт за адресою, яка бережеться в покаж-
чику.
#i n c l u d e <i o s t r e a m >
u s i n g s t d : : cout ;
using std : : endl ;
i n t main ( )
{
int а = 10;

i n t ∗pa = &a ;
i n t ∗pb = pa ;

∗pa = 2 5 ;

cout << " Value on p o i n t e r pa : " << ∗pa << e n d l ; // 25


cout << " Value on p o i n t e r pb : " << ∗pb << e n d l ; // 25
cout << " Value v a r i a b l e а : " << а << e n d l ; // 25

return 0;
}

Через вираз *pa ми можемо набути значення за адресою, яка бережеться в


покажчику pa, а через вираз типу *pa = значення вкласти за цiєю адресою
нове значення.
I оскiльки в даному випадку покажчик pa указує на змiнну а, то при змiнi
значення за адресою, на яку указує покажчик, також змiниться i значення
змiнної а.

Адреса покажчика

Покажчик береже адресу змiнної, i за цiєю адресою ми можемо набути зна-


чення цiєї змiнної. Але крiм того, покажчик, як i будь-яка змiнна, сам має
адресу, по якiй вiн розташовується в пам’ятi. Цю адресу можна одержати
також через операцiю &:
int а = 10;
int ∗pa = &a ;
std : : cout << " a d d r e s s p o i n t e r =" << &pa <<
std : : e n d l ; // адреса покажчика
97

s t d : : cout << " a d d r e s s s t o r e d i n p o i n t e r =" << pa <<


s t d : : e n d l ; // адреса ,
// яка бережеться в покажчику − адреса з м i н н о ї а
s t d : : cout << " v a l u e on p o i n t e r = " << ∗pa << s t d : : e n d l ;
// значення за адресою в покажчику − значення з м i н н о ї а

Операцiї порiвняння

До покажчикiв можуть застосовуватися операцiї порiвняння > >= <


<=,== !=. Операцiї порiвняння застосовуються тiльки до покажчикiв одно-
го типу i до значень NULL i null ptr. Для порiвняння використовуються
номери адрес:
#i n c l u d e <i o s t r e a m > s t d : : cout ; s t d : : e n d l ;
i n t main ( )
{
int а = 10;
int b = 20;
i n t ∗pa = &a ;
i n t ∗pb = &b ;

i f ( pa > pb )
cout << "pa (" << pa << ") i s
g r e a t e r than pb ("<< pb << ")" << e n d l ;
else
cout << "pa (" << pa << ") i s
l e s s or e q u a l pb ("<< pb << ")" << e n d l ;

return 0;
}

Консольний висновок в моєму випадку:


pa (0 x 6 0 f e 9 4 ) i s g r e a t e r than pb (0 x 6 0 f e 9 0 )

Приведення типiв

Iнодi вимагається привласнити покажчику одного типу значення покажчика


iншого типу. В цьому випадку слiд виконати операцiю приведення типiв за
допомогою операцiї (тип_покажчика *):
#i n c l u d e <i o s t r e a m >
i n t main ( )
98

{
char з = ’N ’ ;
char ∗pc = &c ;
i n t ∗pd = ( i n t ∗) pc ;
v o i d ∗pv = ( v o i d ∗) pc ;
s t d : : cout << "pv=" << pv << s t d : : e n d l ;
s t d : : cout << "pd=" << pd << s t d : : e n d l ;

return 0;
}

Для перетворення покажчика до iншого типу в дужках перед покажчиком


ставиться тип, до якого треба перетворити. Причому якщо ми не можемо
просто створити об’єкт, наприклад, змiнну типу void, то для покажчика це
цiлком працюватиме. Тобто можна створити покажчик типу void.
Крiм того, слiд зазначити, що покажчик на тип char (char *pc = &c) при
висновку на консоль система iнтерпретує як рядок:
s t d : : cout << " pc=" << pc << s t d : : e n d l ;

Тому якщо ми все-таки хочемо вивести на консоль адресу, яка бережеться


в покажчику типу char, то це покажчик треба перетворити до iншого типу,
наприклад, до void* або до int*.

8.3. Арифметика покажчикiв

Покажчики можуть брати участь в арифметичних операцiях (складання, вiд-


нiмання, iнкремент, декремент). Проте самi операцiї проводяться трохи iна-
кше, нiж з числами. I багато що тут залежить вiд типу покажчика.
До покажчика можна додавати цiле число, i також можна вiднiмати з по-
кажчика цiле число. Крiм того, можна вiднiмати з одного покажчика iнший
покажчик.
Розглянемо спочатку операцiї iнкремента i декремента i для цього вiзьмемо
покажчик на об’єкт типу int:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
int n = 10;

i n t ∗ p t r = &n ;
s t d : : cout << " a d d r e s s=" << p t r <<
99

"\ t v a l u e=" << ∗ p t r << s t d : : e n d l ;

p t r ++;
s t d : : cout << " a d d r e s s=" << p t r <<
"\ t v a l u e=" << ∗ p t r << s t d : : e n d l ;

ptr −−;
s t d : : cout << " a d d r e s s=" << p t r <<
"\ t v a l u e=" << ∗ p t r << s t d : : e n d l ;

return 0;
}

Операцiя iнкремента ++ збiльшує значення на одиницю. У випадку з покаж-


чиком збiльшення на одиницю означатиме збiльшення адреси, яка бережеться
в покажчику, на розмiр типу покажчика. Тобто в даному випадку покажчик
на тип int, а розмiр об’єктiв int в бiльшостi архiтектури рiвний 4 байтам. То-
му збiльшення покажчика типу int на одиницю означає збiльшення значення
покажчика на 4.
I в моєму випадку консольний висновок виглядає таким чином:
a d d r e s s=0x 6 0 f e 9 8 v a l u e =10
a d d r e s s=0x 6 0 f e 9 c v a l u e =6356636
a d d r e s s=0x 6 0 f e 9 8 v a l u e =10

Тут видно, що пiсля iнкремента значення покажчика збiльшилося на 4: з


0x60fe98 до 0x60fe9c. А пiсля декремента, тобто зменшення на одиницю,
покажчик одержав попередню адресу в пам’ятi.
Фактично збiльшення на одиницю означає, що ми хочемо перейти до насту-
пного об’єкту в пам’ятi, який знаходиться за поточним i на який указує по-
кажчик. А зменшення на одиницю означає перехiд назад до попереднього
об’єкту в пам’ятi.
Пiсля змiни адреси ми можемо набути значення, яке знаходиться за новою
адресою, проте це значення може бути невизначеним, як показано у випадку
вище.
У випадку з покажчиком типу int збiльшення/зменшення на одиницю озна-
чає змiну адреси на 4. Аналогiчно, для покажчика типу short цi операцiї
змiнювали б адресу на 2, а для покажчика типу char на 1.
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
100

double d = 1 0 . 6 ;
double ∗pd = &d ;
s t d : : cout << " P o i n t e r pd :
a d d r e s s : " << pd << s t d : : e n d l ;
pd++;
s t d : : cout << " P o i n t e r pd :
a d d r e s s : " << pd << s t d : : e n d l ;

char з = ’N ’ ;
char ∗pc = &c ;
s t d : : cout << " P o i n t e r pc :
a d d r e s s : " << ( v o i d ∗) pc << s t d : : e n d l ;
pc++;
s t d : : cout << " P o i n t e r pc :
a d d r e s s : " << ( v o i d ∗) pc << s t d : : e n d l ;

return 0;
}

У моєму випадку консольний висновок виглядатиме таким чином:


Pointer pd : a d d r e s s=0x 6 0 f e 9 0
Pointer pd : a d d r e s s=0x 6 0 f e 9 8
Pointer pc : a d d r e s s=0 x 6 0 f e 8 f
Pointer pc : a d d r e s s=0x 6 0 f e 9 0

Як видно з консольного висновку, збiльшення на одиницю покажчика типу


double дало збiльшення береженої в ньому адреси на 8 одиниць (розмiр об’-
єкту double - 8 байт), а збiльшення на одиницю покажчика типу char дало
збiльшення береженої в ньому адреси на 1 (розмiр типу char - 1 байт).
Аналогiчно покажчик змiнюватиметься при надбавцi/вiднiманнi не одиницi,
а якогось iншого числа.
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
double d = 1 0 . 6 ;
double ∗pd = &d ;
s t d : : cout << " P o i n t e r pd :
a d d r e s s : " << pd << s t d : : e n d l ;
pd = pd + 2 ;
s t d : : cout << " P o i n t e r pd :
a d d r e s s : " << pd << s t d : : e n d l ;
101

char з = ’N ’ ;
char ∗pc = &c ;
s t d : : cout << " P o i n t e r pc :
a d d r e s s : " << ( v o i d ∗) pc << s t d : : e n d l ;
pc = pc − 3 ;
s t d : : cout << " P o i n t e r pc :
a d d r e s s : " << ( v o i d ∗) pc << s t d : : e n d l ;

return 0;
}

Додавання до покажчика типу double числа 2


pd = pd + 2 ;

означає, що ми хочемо перейти на два об’єкти double вперед, що має на увазi


змiну адреси на 2 * 8 = 16 байт.
Вiднiмання з покажчика типу char числа 3
pc = pc − 3 ;

означає, що ми хочемо перейти на три об’єкти char назад, що має на увазi


змiну адреси на 3 * 1 = 3 байти.
I в моєму випадку я одержу наступний консольний висновок:
Pointer pd : a d d r e s s=0x 6 0 f e 9 0
Pointer pd : a d d r e s s=0x 6 0 f e a 0
Pointer pc : a d d r e s s=0 x 6 0 f e 8 f
Pointer pc : a d d r e s s=0x 6 0 f e 8 c

На вiдмiну вiд складання операцiя вiднiмання може застосовувати не тiльки


до покажчика i цiлого числа, але i до двох покажчикiв одного типу:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
int а = 10;
int b = 23;
i n t ∗pa = &a ;
i n t ∗pb = &b ;
i n t з = pa − pb ;

s t d : : cout << "pa : " << pa << s t d : : e n d l ;


s t d : : cout << "pb : " << pb << s t d : : e n d l ;
102

s t d : : cout << " з : " << з << s t d : : e n d l ;

return 0;
}

Консольний висновок в моєму випадку:


pa : 0 x 6 0 f e 9 0
pb : 0 x 6 0 f e 8 c
c: 1

Результатом рiзницi двох покажчикiв є "вiдстань"мiж ними. Наприклад, у


випадку вище адреса з першого покажчика на 4 бiльше, нiж адреса з другого
покажчика (0x60fe8c + 4 = 0x60fe90). Оскiльки розмiр одного об’єкту
int рiвний 4 байтам, та вiдстань мiж покажчиками буде рiвна (0x60fe90 -
0x60fe8c)/4 = 1.

Деякi особливостi операцiй з покажчиками

При роботi з покажчиками треба вiдрiзняти операцiї з самим покажчиком i


операцiї iз значенням за адресою, на яку указує покажчик.
int а = 10;
i n t ∗pa = &a ;
i n t b = ∗pa + 2 0 ;
// операцiя i з значенням , на який указує указатель
pa++; // операцiя з самим покажчиком
s t d : : cout << "b : " << b << s t d : : e n d l ; // 30

Тобто в даному випадку через операцiю разыменования *pa набуваємо зна-


чення, на яке указує покажчик pa, тобто число 10, i виконуємо операцiю
складання. Тобто в даному випадку звичайна операцiя складання мiж двома
числами, оскiльки вираз *pa представляє число.
Але в той же час є особливостi, зокрема, з операцiями iнкремента i декре-
мента. Рiч у тому, що операцiї *, ++ i – мають однаковий прiоритет i при
розмiщеннi поряд виконуються справа налiво.
Наприклад, здiйснимий постфiксний iнкремент:
int а = 10;
i n t ∗pa = &a ;
s t d : : cout << "pa :
a d d r e s s=" << pa << "\ t v a l u e=" << ∗pa << s t d : : e n d l ;
i n t b = ∗pa++; // iнкремент адреси покажчика
s t d : : cout << "b : v a l u e=" << b << s t d : : e n d l ;
103

s t d : : cout << "pa :


a d d r e s s=" << pa << "\ t v a l u e=" << ∗pa << s t d : : e n d l ;
У виразi b = *pa++; спочатку до покажчика привласнюється одиниця (тоб-
то до адреси додається 4, оскiльки покажчик типу int). Потiм оскiльки iнкре-
мент постфiксний, за допомогою операцiї разiменування повертається значе-
ння, яке було до iнкремента - тобто число 10. I це число 10 привласнюється
змiнної b. I в моєму випадку результат роботи буде наступний:
pa : a d d r e s s=0x 6 0 f e 9 4 v a l u e =10
b : v a l u e =10
pa : a d d r e s s=0x 6 0 f e 9 8 v a l u e =6356648
Змiнимо вираз:
b = (∗ pa)++;
Дужки змiнюють порядок операцiй. Тут спочатку виконується операцiя ра-
зыменования i отримання значення, потiм це значення збiльшується на 1.
Тепер за адресою в покажчику знаходиться число 11. I потiм оскiльки iнкре-
мент постфiксний, змiнна b набуває значення, яке було до iнкремента, тобто
знову число 10. Таким чином, на вiдмiну вiд попереднього випадку всi опера-
цiї проводяться над значенням за адресою, яка береже покажчик, але не над
самим покажчиком. I, отже, змiниться результат роботи:
pa : a d d r e s s=0x 6 0 f e 9 4 v a l u e =10
b : v a l u e =10
pa : a d d r e s s=0x 6 0 f e 9 4 v a l u e =11
Аналогiчно буде з префiксним iнкрементом:
b = ++∗pa ;
У даному випадку спочатку за допомогою операцiї разыменования набуваємо
значення за адресою з покажчика pa, до цього значення додається одиниця.
Тобто тепер значення за адресою, яка бережеться в покажчику, рiвне 11.
Потiм результат операцiї привласнюється змiнної b:
pa : a d d r e s s=0x 6 0 f e 9 4 v a l u e =10
b : v a l u e =11
pa : a d d r e s s=0x 6 0 f e 9 4 v a l u e =11
Змiнимо вираз:
b = ∗++pa ;
Тепер спочатку змiнює адресу в покажчику, потiм ми набуваємо за цiєю адре-
сою значення i привласнюємо його змiнної b. Набуте значення в цьому ви-
падку може бути невизначеним:
104

pa : a d d r e s s=0x 6 0 f e 9 4 v a l u e =10
b : v a l u e =6356864
pa : a d d r e s s=0x 6 0 f e 9 8 v a l u e =6356864

8.4. Константи i покажчики

Покажчики та константи

Покажчики можуть указувати як на змiннi, так i на константи. Щоб визна-


чити покажчик на константу, вiн теж повинен оголошуватися з ключовим
словом const:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
const int а = 10;
c o n s t i n t ∗pa = &a ;
s t d : : cout << " a d d r e s s=" << pa <<
"\ t v a l u e=" << ∗pa << s t d : : e n d l ;

return 0;
}
Тут покажчик pa указує на константу а. Тому навiть якщо ми схочемо змi-
нити значення за адресою, яка бережеться в покажчику, ми не зможемо це
зробити, наприклад так:
∗pa = 3 4 ;
У цьому випадку ми просто одержимо помилку пiд час компiляцiї.
Можлива також ситуацiя, коли покажчик на константу насправдi указує на
змiнну:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
int а = 10;
c o n s t i n t ∗pa = &a ;
s t d : : cout <<"v a l u e=" << ∗pa << s t d : : e n d l ; // 10
а = 22;
s t d : : cout <<"v a l u e=" << ∗pa << s t d : : e n d l ; // 22
//∗ pa = 3 4 ; // так робити не можна

return 0;
105

У цьому випадку змiнну окремо ми зможемо змiнювати, проте як i ранiше


змiнити її значення через покажчик ми не зможемо.
Через покажчик на константу ми не можемо змiнювати значення змiнної-
/константи. Але ми можемо привласнити покажчику адресу будь-якої iншої
змiнної або константи:
const int а = 10;
c o n s t i n t ∗pa = &a ; // покажчик указує на константу а
const int b = 45;
pa = &b ; // покажчик указує на константу b
s t d : : cout <<"v a l u e=" << ∗pa << s t d : : e n d l ; // 45
s t d : : cout <<"v a l u e=" << а << s t d : : e n d l ;
// 10 − константа а не змiнюється

Константний покажчик

Вiд покажчикiв на константи треба вiдрiзняти константнi покажчики. Вони


не можуть змiнювати адресу, яка в них бережеться, але можуть змiнювати
значення за цiєю адресою.
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
int а = 10;
i n t ∗ c o n s t pa = &a ;
s t d : : cout << " v a l u e=" << ∗pa << s t d : : e n d l ; // 10
∗pa = 2 2 ; // мiняємо значення
s t d : : cout << " v a l u e=" << ∗pa << s t d : : e n d l ; // 22

i n t b = 4 5 ; // pa = &b ; так не можна зробити

return 0;
}

Константний покажчик на константу

I об’єднання обох попереднiх випадкiв - константний покажчик на константу,


який не дозволяє мiняти нi бережену в ньому адресу, нi значення за цiєю
адресою:
106

int а = 10;
c o n s t i n t ∗ c o n s t pa = &a ;
// ∗pa = 2 2 ; так зробити не можна
int b = 45;
// pa = &b ; так зробити не можна

8.5. Питання та завдання для самоконтролю знань

• Яким чином визначаються покажчика в мовi програмування C++ i коли


їх доцiльно використовувати?
• Чи є потреба при визначення покажчика на об’єкт вказувати його тип?
• Чи є потреба при визначення покажчика в його iнiцiалiзацiї певним зна-
ченням?
• Назвiть основнi операцiї з покажчиками та наведiть прикладi їх програ-
мування.
• Чи можна визначити покажчик на посилання?
• Як програмується операцiя розiменування покажчика?
• Чи можуть застосовуватися операцiї порiвняння для покажчикiв?
• Яким чином застосовується операцiя приведення типiв для покажчикiв?
• Яким чином проводяться арифметичнi операцiї з покажчиками?
• В чому рiзниця мiж операцiями з покажчиком i операцiями iз значення-
ми за адресою?
• Що таке константнi покажчики i якi вони мають властиврстi?

9. Використання покажчикiв у функцiях мови програ-


мування C++

9.1. Покажчики у параметрах функцiї

Параметри функцiї в C++ можуть представляти покажчики. Покажчики пе-


редаються у функцiю по значенню, тобто функцiя одержує копiю покажчика.
В той же час копiя покажчика як значення матиме ту ж адресу, що оригiналь-
ний покажчик. Тому використовуючи як параметри покажчики, ми можемо
дiстати доступ до значення аргументу i змiнити його.
107

Наприклад, хай у нас буде найпростiша функцiя, яка збiльшує число на оди-
ницю:
#i n c l u d e <i o s t r e a m >

void increment ( i n t ) ;

i n t main ( )
{
int n = 10;
increment (n ) ;
s t d : : cout << "main f u n c t i o n :
" << x << s t d : : e n d l ;
return 0;
}
void increment ( i n t x )
{
x++;
s t d : : cout << " i n c r e m e n t f u n c t i o n :
" << x << s t d : : e n d l ;
}

Тут змiнна n передається як аргумент для параметра x. Передача вiдбуває-


ться по значенню, тому будь-яка змiна параметра x у функцiї increment нiяк
не позначиться на значеннi змiнної n. Що ми можемо побачити, запустимо
програму:
i n c r e m e n t f u n c t i o n : 11
main f u n c t i o n : 10

Тепер змiнимо функцiю increment, використавши як параметр покажчик:


#i n c l u d e <i o s t r e a m >

void increment ( i n t ∗ ) ;

i n t main ( )
{
int n = 10;
i n c r e m e n t (&n ) ;
s t d : : cout << "main f u n c t i o n :
" << n << s t d : : e n d l ;
return 0;
}
108

v o i d i n c r e m e n t ( i n t ∗x )
{
(∗ x)++;
s t d : : cout << " i n c r e m e n t f u n c t i o n :
" << ∗x << s t d : : e n d l ;
}

Для змiни значення параметра застосовується операцiя разыменования з


подальшим iнкрементом: (*x)++. Це змiнює значення, яке знаходиться за
адресою, береженою в покажчику x.
Оскiльки тепер функцiя як параметр приймає покажчик, то при її виклику
необхiдно передати адресу змiнної: increment(&n);.
У результатi змiна параметра x також вплине на змiнну n:
i n c r e m e n t f u n c t i o n : 11
main f u n c t i o n : 11

У той же час оскiльки аргумент передається у функцiю по значенню, тобто


функцiя одержує копiю адреси, то якщо усерединi функцiї буде змiнена адре-
са покажчика, то це не торкнеться зовнiшнього покажчика, який передається
як аргумент:
#i n c l u d e <i o s t r e a m >

void increment ( i n t ∗ ) ;

i n t main ( )
{
int n = 10;
i n t ∗ p t r = &n ;
increment ( ptr ) ;
s t d : : cout << "main f u n c t i o n :
" << n << s t d : : e n d l ;
return 0;
}
v o i d i n c r e m e n t ( i n t ∗x )
{
int z = 6;
x = &z ; // переустанавлюємо адрес покажчика x
s t d : : cout << " i n c r e m e n t f u n c t i o n :
" << ∗x << s t d : : e n d l ;
}
109

У функцiю increment передається покажчик ptr. При виклику функцiя одер-


жує копiю цього покажчика у виглядi парамета x. У функцiї змiнюється
адреса покажчика x. Але це нiяк не торкнеться покажчика ptr, оскiльки
вiн предствляет iншу копiю. У результатi поле встановлення адреси заново
покажчики x i ptr берегтимуть рiзнi адреси.
Результат роботи програми:
increment function : 6
main f u n c t i o n : 10

9.2. Покажчики на функцiї

Покажчик на функцiю (function pointer) береже адресу функцiї. По сутi


покажчик на функцiю мiстить адресу першого байта в пам’ятi, по якому
розташовується виконуваний код функцiї.
Найпоширенiшим покажчиком на функцiю є її iм’я. За допомогою iменi фун-
кцiї можна викликати її i одержувати результат її роботи.
Але також покажчик на функцiю ми можемо визначати у виглядi окремої
змiнної за допомогою наступного синтаксису:
тип (∗ имя_указателя ) ( параметры ) ;

Тут тип представляє тип значення, що повертається функцiєю.


iм’я_покажчика представляє довiльно вибраний iдентифiкатор вiдповiдно
до правил про найменування змiнних.
I параметри визначають тип i назву параметрiв через кому при їх наявностi.
Наприклад, визначимо покажчик на функцiю:
v o i d (∗ message ) (); >

У даному випадку визначений покажчик, який має iм’я message. Вiн може
указувати на функцiї без параметрiв, якi повертають тип void (тобто нiчого
не повертають).
Використовуємо покажчик на функцiю:
#i n c l u d e <i o s t r e a m >

void h e l l o ( ) ;
v o i d goodbye ( ) ;

i n t main ( )
{
110

v o i d (∗ message ) ( ) ;

message=h e l l o ;
message ( ) ;
message = goodbye ;
message ( ) ;

return 0;
}
void h e l l o ( )
{
s t d : : cout << " H e l l o , World" << s t d : : e n d l ;
}
v o i d goodbye ( )
{
s t d : : cout << "Good Bye , World" << s t d : : e n d l ;
}

Покажчику на функцiю можна привласнити функцiю, яка вiдповiдає покаж-


чику по типу i специфiкацiї параметрiв, що повертається:
message=h e l l o ;

Тобто в даному випадку покажчик message тепер береже адресу функцiї


hello. I за допомогою звернення до покажчика ми можемо викликати цю
функцiю:
message ( ) ;

Як альтернатива ми можемо звертатися до покажчика на функцiю таким


чином:
(∗ message ) ( ) ;

Згодом ми можемо привласнить покажчику адресу iншої функцiї, як в дано-


му випадку. У результатi результатом даної програми буде наступний висно-
вок:
H e l l o , World
Good Bye , World

При визначеннi покажчика варто звернути увагу на дужки навкруги iменi.


Так, використане вище визначення
v o i d (∗ message ) ( ) ;

Не буде аналогiчно наступному визначенню:


111

v o i d ∗ message ( ) ;
У другому випадку визначений не покажчик на функцiю, а прототип функцiї
message, яка повертає покажчик типу void*.
Розглянемо ще один покажчик на функцiю:
#i n c l u d e <i o s t r e a m >

i n t add ( i n t , i n t ) ;
int subtract ( int , int ) ;

i n t main ( )
{
int a = 10;
int b = 5;
int result ;
i n t (∗ o p e r a t i o n ) ( i n t a , i n t b ) ;

o p e r a t i o n=add ;
result = operation (a , b ) ;
// r e s u l t = (∗ o p e r a t i o n ) ( a , b ) ;
// альтернативний варiант
s t d : : cout << " r e s u l t =" << r e s u l t <<
s t d : : e n d l ; // r e s u l t =15

operation = subtract ;
result = operation (a , b ) ;
s t d : : cout << " r e s u l t =" << r e s u l t <<
s t d : : e n d l ; // r e s u l t =5

return 0;
}
i n t add ( i n t x , i n t y )
{
r e t u r n x+y ;
}
int subtract ( int x , int y)
{
r e t u r n x−y ;
}
Тут визначений покажчик operation, який може указувати на функцiю з дво-
ма параметрами типу int, що повертає також значення типу int. Вiдповiдно
112

ми можемо привласнити покажчику адреси функцiй add i subtract i викли-


кати їх, передавши при виклику покажчику деякi значення для параметрiв.

Масиви покажчикiв на функцiї

Окрiм одиночних покажчикiв на функцiї ми можемо визначати їх масиви.


Для цього використовується наступний формальний синтаксис:
тип (∗ имя_массива [ размер ] ) ( параметры )

Наприклад:
double (∗ a c t i o n s [ ] ) ( i n t , i n t )

Тут асtions представляє масив покажчикiв на функцiї, кожна з яких обов’яз-


ково повинна приймати два параметри типу int i повертати значення типу
double.
Подивимося вживання масиву покажчикiв на функцiї на прикладi:
#i n c l u d e <i o s t r e a m >

v o i d add ( i n t , i n t ) ;
void subtract ( int , i n t ) ;
void multiply ( int , i n t ) ;

i n t main ( )
{
int a = 10;
int b = 5;
v o i d (∗ o p e r a t i o n s [ 3 ] ) ( i n t , i n t ) =
{add , s u b t r a c t , m u l t i p l y } ;

// получаем длину массива


int length =
s i z e o f ( o p e r a t i o n s )/ s i z e o f ( o p e r a t i o n s [ 0 ] ) ;

f o r ( i n t i =0; i < l e n g t h ; i ++)


{
operations [ i ] ( a , b ) ;
// вызов функции по указателю
}

return 0;
}
113

v o i d add ( i n t x , i n t y )
{
s t d : : cout << "x + y = " << x + y << s t d : : e n d l ;
}
void subtract ( i n t x , i n t y )
{
int result = x − y ;
s t d : : cout << "x − y = " << x − y << s t d : : e n d l ;
}
void multiply ( i n t x , i n t y )
{
s t d : : cout << "x ∗ y = " << x ∗ y << s t d : : e n d l ;
}

Тут масив operations мiстить три функцiї add, subtract i multiply, якi по-
слiдовно викликаються в циклi через перебiр масиву у функцiї main.
Консольне виведення програми:
x + y = 15
x − y = 5
x ∗ y = 50

9.3. Покажчики на функцiї як параметри

Покажчик на функцiю може передаватися в iншу функцiю як параметр. На-


приклад:
#i n c l u d e <i o s t r e a m >

i n t add ( i n t , i n t ) ;
int subtract ( int , int ) ;
int operation ( int (∗)( int , int ) , int , int ) ;

i n t main ( )
{
int a = 10;
int b = 5;
int result ;
r e s u l t = o p e r a t i o n ( add , a , b ) ;
s t d : : cout << " r e s u l t : " << r e s u l t << s t d : : e n d l ;

r e s u l t = operation ( subtract , a , b ) ;
114

s t d : : cout << " r e s u l t : " << r e s u l t << s t d : : e n d l ;


return 0;
}

i n t add ( i n t x , i n t y )
{
return x + y ;
}
int subtract ( int x , int y)
{
return x − y ;
}
i n t o p e r a t i o n ( i n t (∗ op ) ( i n t , i n t ) , i n t a , i n t b )
{
r e t u r n op ( a , b ) ;
}

У даному випадку перший параметр функцiї operation - int (*op)(int, int) -


представляє покажчик на функцiю, яка повертає значення типу int i приймає
два параметри типу int. Результатом функцiї є виклик тiєї функцiї, на яку
указує покажчик.
Визначенню покажчика вiдповiдають двi функцiї: add i subtract, тому їх
адресу можна передати у виклик функцiї operation: operation(add, а, b);.
Результат роботи програми:
r e s u l t : 15
result : 5

Iнший приклад - функцiя, яка може приймати як параметр деяку умову:


#i n c l u d e <i o s t r e a m >

i n t isEven ( i n t ) ;
int isPositive ( int );
void action ( i n t ( ∗ ) ( i n t ) , i n t [ ] , i n t ) ;

i n t main ( )
{
i n t nums [ ] = { −5, −4, −3, −2, −1, 0 , 1 , 2 , 3 , 4 , 5 } ;
i n t n = s i z e o f ( nums )/ s i z e o f ( nums [ 0 ] ) ;

s t d : : cout << "Even numbers : " << s t d : : e n d l ;


a c t i o n ( isEven , nums , n ) ;
115

s t d : : cout << " P o s i t i v e numbers : " << s t d : : e n d l ;


a c t i o n ( i s P o s i t i v e , nums , n ) ;

return 0;
}
i n t isEven ( i n t x )
{
r e t u r n x % 2 == 0 ;
}
int isPositive ( int x)
{
r e t u r n x >0;
}
v o i d a c t i o n ( i n t (∗ c o n d i t i o n ) ( i n t ) , i n t numbers [ ] , i n t n )
{
f o r ( i n t i = 0 ; i <n ; i ++)
{
i f ( c o n d i t i o n ( numbers [ i ] ) != 0)
{
s t d : : cout << numbers [ i ] << "\ t " ;
}
}
s t d : : cout << s t d : : e n d l ;
}

Перший параметр функцiї асtion - покажчик int (*condition)(int) пред-


ставляє функцiю, яка приймає цiле число i залежно вiд того, вiдповiдає воно
умовi чи нi, повертає 1 (якщо вiдповiдає) або 0. На момент визначення фун-
кцiї асtion точна умова може бути невiдоме.
У поточнiй програмi умови представленi двома функцiями. Функцiя isEven()
повертає 1, якщо число парне, i 0, якщо число непарне. А функцiя isPositi-
ve() повертає 1, якщо число позитивне, i 0, якщо негативне.
При виклику функцiї action() в неї можна передати потрiбну умову: асti-
on(isEven, nums, n);. У результатi програма виведе на екран числа з масиву
nums, якi вiдповiдають переданiй умовi:
Even numbers : −4 −2 0 2 4
P o s i t i v e numbers : 1 2 3 4 5
116

9.4. Покажчик на функцiю як значення, що повертається

Функцiя може повертати покажчик на iншу функцiю. Це може актуально,


якщо є обмежена кiлькiсть варiантiв - виконуваних функцiй, i треба вибра-
ти одну з них. Але при цьому набiр варiантiв i вибiр з них визначається в
промiжнiй функцiї.
Розглянемо найпростiший приклад:
#i n c l u d e <i o s t r e a m >

v o i d goodmorning ( ) ;
v o i d goodevening ( ) ;
v o i d (∗ message ( i n t ) ) ( ) ;

i n t main ( )
{
v o i d (∗ a c t i o n ) ( ) ;
// покажчик на вибрану функцiю
a c t i o n = message ( 1 5 ) ;
action ( ) ;
// виконуємо одержану функцiю
return 0;
}

v o i d (∗ message ( i n t hour ) ) ( )
{
i f ( hour > 12)
r e t u r n goodevening ;
else
r e t u r n goodmorning ;
}
v o i d goodmorning ( )
{
s t d : : cout << "Good Morning ! " << s t d : : e n d l ;
}
v o i d goodevening ( )
{
s t d : : cout << "Good Evening ! " << s t d : : e n d l ;
}

Тут визначена функцiя message, яка залежно вiд переданого числа повертає
одну з двох функцiй goodmorning або goodevening. Розглянемо оголоше-
117

ння функцiї message:


v o i d (∗ message ( i n t hour ) ) ( )

Спочатку вказаний тип, який повертається функцiєю, яка повертається з


message, тобто тип void (функцiї goodmorning i goodevening мають тип
void). Далi йде в дужках iм’я функцiї iз списком параметрiв, тобто функцiя
message приймає один параметр типу int: (*message(int hour)). Пiсля
цього окремо в дужках йде специфiкацiя параметрiв функцiї, яка повертати-
меться з message. Оскiльки функцiї goodmorning i goodevening не при-
ймають нiяких параметрiв, то указуються порожнi дужки.
Iм’я функцiї фактично представляє покажчик на неї, тому у функцiї message
ми можемо повернути нудну функцiю, вказавши пiсля оператора return її
iм’я.
Для отримання покажчика на функцiю визначаємо змiнну асtion:
v o i d (∗ a c t i o n ) ( ) ;

Ця змiнна представляє покажчик на функцiю, яка не приймає параметрiв


i має як тип, що повертається, тип void, тобто вона вiдповiдає функцiям
goodmorning i goodevening.
Потiм викликаємо функцiю message i одержуємо покажчик на функцiю в
змiнну асtion:
a c t i o n = message ( 1 5 ) ;

Далi, використовуючи покажчик асtion, викликаємо одержану функцiю:


action ( ) ;

Оскiльки у функцiю message передається число 15, то вона повертатиме


покажчик на функцiю goodevening, тому при її виклику на консоль буде
виведено повiдомлення ”Good Evening!”
Розглянемо складнiший приклад, в якому залежно вiд вибору користувача
виконується та або iнша арифметична операцiя над двома числами:
#i n c l u d e <i o s t r e a m >

i n t add ( i n t , i n t ) ;
int subtract ( int , int ) ;
int multiply ( int , int ) ;
i n t (∗ s e l e c t ( ) ) ( i n t , i n t ) ;

i n t main ( )
{
118

int x = 8;
int y = 5;
s t d : : cout << "x = " << x <<
"\ t y = " << y << s t d : : e n d l ;
s t d : : cout << " 1 : Add" << s t d : : e n d l ;
s t d : : cout << " 2 : S u b t r a c t " << s t d : : e n d l ;
s t d : : cout << " 3 : M u l t i p l y " << s t d : : e n d l ;
s t d : : cout << " 4 : Exit " << s t d : : e n d l ;

i n t (∗ a c t i o n ) ( i n t , i n t ) ; // покажчик на вибрану функцiю


i n t r e s u l t ; // результат функцiї
while (1)
{
a c t i o n = s e l e c t ( ) ; / / одержуємо покажчик на функцiю
i f ( a c t i o n == NULL)
break ;
r e s u l t = a c t i o n ( x , y ) ; // виконуємо функцiю
s t d : : cout << " R e s u l t : " << r e s u l t << s t d : : e n d l ;
}
s t d : : cout << "The End" << s t d : : e n d l ;

return 0;
}

i n t (∗ s e l e c t ( ) ) ( i n t , i n t )
{
i n t c h o i c e ; // вибраний пункт
// масив покажчикiв на функцiї , я к i повертатимуться
i n t (∗ a c t i o n s [ ] ) ( i n t x , i n t y ) =
{add , s u b t r a c t , m u l t i p l y } ;
//
s t d : : cout << " Enter a c t i o n ( 1 , 2 , 3 , 4 ) : " ;
s t d : : c i n >> c h o i c e ;
// повертаємо потрiбну функцiю
i f ( c h o i c e >0 && c h o i c e <4)
return actions [ choice − 1 ] ;
else
r e t u r n NULL;
}
i n t add ( i n t x , i n t y )
{
119

return x + y ;
}
int subtract ( int x , int y)
{
return x − y ;
}
int multiply ( int x , int y)
{
return x ∗ y ;
}

У данiй програмi ми припускаємо, що користувач повинен вибрати для ви-


конання одну з трьох функцiй: add, subtract, multiply. I вибрана функцiя
виконуватиме певну дiю над двома числами x i у.
Сам вибiр вiдбувається у функцiї select(). Вона повертає покажчик на фун-
кцiю - по сутi вибрану функцiю.
Всi вибиранi функцiї мають прототип вигляду:
i n t add ( i n t , i n t ) ;

I прототип функцiї select повинна вiдповiдати цьому прототипу:


i n t (∗ s e l e c t ( ) ) ( i n t , i n t )

Тобто на початку йде тип - тип, що повертається, покажчика на функцiю,


тобто int. Потiм йде визначення самої функцiї select - її назва iз списком
параметрiв помiщається в дужках - (*select()). Потiм йде специфiкацiя па-
раметрiв функцiї, на яку визначається покажчик. Оскiльки функцiї add,
subtract i multiply приймають два значення типу int, то вiдповiдно спе-
цифiкацiя параметрiв виглядає таким чином (int, int).
Для зберiгання всiх дiй у функцiї select визначений масив покажчикiв на
функцiї асtions:
i n t (∗ a c t i o n s [ ] ) ( i n t x , i n t y ) =
{add , s u b t r a c t , m u l t i p l y } ;

За допомогою введеного з клавiатури числа визначаємо номер потрiбної дiї,


яку треба виконати. Якщо номер менше 1 або бiльше 3, то повертається кон-
станта NULL.
У головнiй функцiї main() в нескiнченному циклi викликаємо функцiю
select, одержуючи як результат покажчик на функцiю:
action = select ( ) ;
120

I якщо покажчик не рiвний NULL, то пiсля цього ми зможемо викликати


функцiю по покажчику. Оскiльки функцiя по покажчику повинна приймати
два значення типу int, то ми їх можемо передати у функцiю, що викликає-
ться, i одержати її результат:
result = action (x , y ) ;

Консольне виведення роботи програми:


x = 8 y = 5
1 : Add
2: Subtract
3: Multiply
4 : Exit
Enter a c t i o n ( 1 , 2 , 3 , 4 ) : 1
R e s u l t : 13
Enter a c t i o n ( 1 , 2 , 3 , 4 ) : 2
Result : 3
Enter a c t i o n ( 1 , 2 , 3 , 4 ) : 4
The End

9.5. Питання та завдання для самоконтролю знань

• Яким чином визначаються покажчики у параметрах функцiй i в яких


випадках їх використання є доцiльним?
• Яким чином визначаються покажчики на функцiї i в яких випадках їх
використання є доцiльним?
• Яким чином визначаються масиви покажчикiв на функцiї?
• Чи може покажчик на функцiю передаватися в iншу функцiю як пара-
метр?
• Чи може функцiя повертати покажчик на iншу функцiю?

10. Покажчики на масиви. Динамiчнi об’єкти та масиви


у мовi програмування С/C++

10.1. Покажчики на масиви

У C++ покажчики i масиви тiсно зв’язанi. Звичайно компiлятор перетворить


масив в покажчики. За допомогою покажчикiв можна манiпулювати елемен-
тами масиву, як i за допомогою iндексiв.
121

Iм’я масиву по сутi є адресою його першого елементу. Вiдповiдно через опе-
рацiю разiменування ми можемо набути значення за цiєю адресою:
i n t a [ ] = {1 , 2 , 3 , 4 , 5 } ;
s t d : : cout << "а [ 0 ] = " << ∗a << s t d : : e n d l ; // а [ 0 ] = 1

Додаючи до адреси першого елементу деяке число, ми можемо одержати


определенны елемент масив. Наприклад, в циклi пробiжимося по всiх еле-
ментах:
#i n c l u d e <i o s t r e a m >
i n t main ( )
{
const int n = 5;
i n t а [ n]= {1 , 2 , 3 , 4 , 5 } ;

f o r ( i n t i =0; i < n ; i ++)


{
s t d : : cout << "а [ " << i << " ] : a d d r e s s=" << a+i <<
"\ t v a l u e=" << ∗( a+i ) << s t d : : e n d l ;
}

return 0;
}

Тобто, наприклад, адреса другого елементу представлятиме вираз a+1, а


його значення - *(a+1).
Вiдносно складання i вiднiмання тут дiють тi ж правила, що i в операцiях з
покажчиками. Додавання одиницi означає надбавку до адреси значення, яке
рiвне розмiру типу масиву. Так, в даному випадку масив представляє тип
int, розмiр якого, як правило, складає 4 байти, тому надбавка одиницi до
адреси означає збiльшення адреси на 4. Додаючи до адреси 2, ми збiльшуємо
значення адреси на 4 * 2 = 8. I так далi.
I у результатi програма виведе на консоль наступний результат:
a[0]: a d d r e s s=0x 6 0 f e 8 4 v a l u e=1
a[1]: a d d r e s s=0x 6 0 f e 8 8 v a l u e=2
a[2]: a d d r e s s=0x 6 0 f e 8 c v a l u e=3
a[3]: a d d r e s s=0x 6 0 f e 9 0 v a l u e=4
a[4]: a d d r e s s=0x 6 0 f e 9 4 v a l u e=5

Але при цьому iм’я масиву це не стандартний покажчик, i ми не можемо


змiнити його адресу, наприклад, так:
i n t а [ 5 ] = {1 , 2 , 3 , 4 , 5 } ;
122

a++; // так зробити не можна


int b = 8;
a = &b ; // так теж зробити не можна

Iм’я масиву завжди береже адресу найпершого елементу. I нерiдко для пере-
мiщення по елементах масиву використовуються окремi покажчики:
int a [ 5 ] = {1 , 2 , 3 , 4 , 5 } ;
int ∗ ptr = a ;
int a2 = ∗( p t r +2);
std : : cout << " v a l u e : " << a2 <<
std : : e n d l ; // v a l u e : 3

Тут покажчик ptr спочатку указує на перший елемент масиву. Збiльшивши


покажчик на 2, ми пропустимо 2 елементи в масивi i перейдемо до елементу
а[2].
За допомогою покажчикiв легко перебрати масив:
i n t a [ 5 ] = {1 , 2 , 3 , 4 , 5 } ;

f o r ( i n t ∗ p t r=a ; ptr<=&a [ 4 ] ; p t r++)


{
s t d : : cout << " a d d r e s s=" << p t r <<
"\ t v a l u e=" << ∗ p t r << s t d : : e n d l ;
}

Оскiльки покажчик береже адресу, то ми можемо продовжувати цикл, поки


адреса в покажчику не стане рiвною адресi останнього елементу.
Аналогiчним чином можна перебрати i багатовимiрний масив:
i n t main ( )
{
i n t a [ 3 ] [ 4 ] = { {1 , 2 , 3 , 4} ,
{5 , 6 , 7 , 8} , {9 , 10 , 11 , 1 2 } } ;
i n t n = s i z e o f ( a )/ s i z e o f ( a [ 0 ] ) ; // число рядкiв
i n t m = s i z e o f ( a [ 0 ] ) / s i z e o f ( a [ 0 ] [ 0 ] ) ; // число с т о в б ц i в

i n t ∗end = а [ 0 ] + n ∗ m − 1 ;
// покажчик на самий
// останнiй елемент 0 + 3 ∗ 4 − 1 = 11
f o r ( i n t ∗ p t r=a [ 0 ] , i =1; p t r <= end ; p t r ++, i ++)
{
s t d : : cout << ∗ p t r << "\ t " ;
// якщо залишок в i д цiлочисельного розподiлу рiвний 0
123

// переходимо на новий рядок


i f ( i%m == 0)
{
s t d : : cout << s t d : : e n d l ;
}
}

return 0;
}

Оскiльки в даному випадку ми маємо справу з двомiрним масивом, то адре-


сою першого елементу буде вираз а[0]. Вiдповiдно покажчик указує на цей
елемент. З кожною iтерацiєю покажчик збiльшується на одиницю, поки йо-
го значення не стане рiвним адресi останнього елементу, який бережеться в
покажчику end.
Ми також могли б обiйтися i без покажчика на останнiй елемент, перевiряючи
значення лiчильника:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
i n t a [ 3 ] [ 4 ] = { {1 , 2 , 3 , 4} ,
{5 , 6 , 7 , 8} , {9 , 10 , 11 , 1 2 } } ;
i n t n = s i z e o f ( a )/ s i z e o f ( a [ 0 ] ) ; // число рядкiв
i n t m = s i z e o f ( a [ 0 ] ) / s i z e o f ( a [ 0 ] [ 0 ] ) ; // число с т о в б ц i в

i n t ∗end = a [ 0 ] + n ∗ m − 1 ;
// покажчик на самий останнiй елемент
// 0 + 3 ∗ 4 − 1 = 11
f o r ( i n t ∗ p t r=a [ 0 ] , i =0; i <m∗n ; )
{
s t d : : cout << ∗ p t r++ << "\ t " ;
// якщо залишок в i д цiлочисельного розподiлу рiвний 0
// переходимо на новий рядок
i f (++ i%m == 0)
{
s t d : : cout << s t d : : e n d l ;
}
}

Але в обох випадках програма вивела б наступний результат:


124

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

Покажчик на масив символiв

Оскiльки масив символiв може iнтерпретуватися як рядок, то покажчик на


значення типу char теж може iнтерпретуватися як рядок:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
char l e t t e r s [ ] = " h e l l o " ;
char ∗p = l e t t e r s ;
s t d : : cout << p << s t d : : e n d l ; // h e l l o
return 0;
}

Якщо ж необхiдно вивести на консоль адресу покажчика, то його треба пе-


реобразовать до типу void*:
s t d : : cout << ( v o i d ∗) p << s t d : : e n d l ; // 0 x 6 0 f e 8 e

У iншому робота з покажчиком на масив символiв проводиться також, як i з


покажчиками на масиви iнших типiв.

10.2. Покажчики на масиви у параметрах функцiї

Якщо функцiя приймає як параметр масив, то фактично у цю функцiю пе-


редається покажчик на перший елемент масиву. Тобто як i у випадку з по-
кажчиками нам доступна адреса, по якiй ми можемо мiняти значення. Тому
наступнi оголошення функцiї будуть по сутi рiвноцiннi:
v o i d p r i n t ( i n t numbers [ ] ) ;
v o i d p r i n t ( i n t ∗numbers ) ;

Передамо у функцiю масив:


#i n c l u d e <i o s t r e a m >

void pri nt ( i n t [ ] ) ;

i n t main ( )
125

{
i n t nums [ ] = {1 , 2 , 3 , 4 , 5 } ;
p r i n t ( nums ) ;
return 0;
}

v o i d p r i n t ( i n t numbers [ ] )
{
s t d : : cout << " F i r s t number :
" << numbers [ 0 ] << s t d : : e n d l ;
}

У даному випадку функцiя print виводить на консоль перший елемент масиву.


Тепер визначимо параметр як покажчик:
#i n c l u d e <i o s t r e a m >

void pri nt ( i n t ∗ ) ;

i n t main ( )
{
i n t nums [ ] = {1 , 2 , 3 , 4 , 5 } ;
p r i n t ( nums ) ;
return 0;
}

v o i d p r i n t ( i n t ∗numbers )
{
s t d : : cout << " F i r s t number :
" << ∗numbers << s t d : : e n d l ;
}

Тут також у функцiю передається масив, проте параметр представляє по-


кажчик на перший елемент масиву.

Обмеження

Оскiльки параметр, визначений як масив, розглядається саме як покажчик


на перший елемент, то ми не зможемо коректно одержати довжину масиву,
наприклад, таким чином:
v o i d p r i n t ( i n t numbers [ ] )
{
126

i n t s i z e = s i z e o f ( numbers ) / s i z e o f ( numbers [ 0 ] ) ;
s t d : : cout << s i z e << s t d : : e n d l ;
}

I також ми не зможемо використовувати цикл for для перебору цього масиву:


v o i d p r i n t ( i n t numbers [ ] )
{
f o r ( i n t n : numbers )
s t d : : cout << n << s t d : : e n d l ;
}

10.3. Передача маркера кiнця масиву

Щоб належним чином визначати кiнець масив, перебирати елементи масиву,


необхiдно використовувати спецiальний маркер, який би сигналiзував про
закiнчення масиву. Для цього можуть використовуватися рiзнi пiдходи.
Перший пiдхiд полягає в тому, щоб один з елементiв масиву сам сигналiзу-
вав про його закiнчення. Зокрема, масив символiв може представляти рядок
- набiр символiв, який завершується нульовим символом ’/0’. Фактично ну-
льовий символ служить признком закiнчення символьного масиву:
#i n c l u d e <i o s t r e a m >

v o i d p r i n t ( char [ ] ) ;

i n t main ( )
{
char c h a r s [ ] = " H e l l o " ;

print ( chars ) ;
return 0;
}

v o i d p r i n t ( char c h a r s [ ] )
{
f o r ( i n t i = 0 ; c h a r s [ i ] != ’ \ 0 ’ ; i ++)
{
s t d : : cout << c h a r s [ i ] << "\ t " ;
}
}

Другий пiдхiд полягає в передачi у функцiю розмiру масиву:


127

#i n c l u d e <i o s t r e a m >

void pri nt ( i n t [ ] , i n t ) ;

i n t main ( )
{
i n t nums [ ] = {1 , 2 , 3 , 4 , 5 } ;
i n t n = s i z e o f ( nums )/ s i z e o f ( nums [ 0 ] ) ;
p r i n t ( nums , n ) ;
return 0;
}

v o i d p r i n t ( i n t numbers [ ] , i n t n )
{
f o r ( i n t i =0; i < n ; i ++)
{
s t d : : cout << numbers [ i ] << "\ t " ;
}
}

Третiй пiдхiд полягає в передачi покажчика на кiнець масиву. Можна вручну


обчислювати обчислювати покажчик на кiнець масиву. А можна використо-
вувати вбудованi бiблiотечнi функцiї std::begin() i std::end():
i n t nums [ ] = { 1 , 2 , 3 , 4 , 5 } ;
i n t ∗ b e g i n = s t d : : b e g i n ( nums ) ;
// покажчик на початок масиву
i n t ∗end = s t d : : end ( nums ) ;
// покажчик на кiнець масиву

Причому end повертає покажчик не на останнiй елемент, а адреса за останнiм


елементом в масивi.
Застосуємо данi функцiї:
#i n c l u d e <i o s t r e a m >

void pri nt ( i n t ∗ , i n t ∗ ) ;

i n t main ( )
{
i n t nums [ ] = { 1 , 2 , 3 , 4 , 5 } ;
i n t ∗ b e g i n = s t d : : b e g i n ( nums ) ;
i n t ∗end = s t d : : end ( nums ) ;
128

p r i n t ( begin , end ) ;
return 0;
}

v o i d p r i n t ( i n t ∗ begin , i n t ∗end )
{
f o r ( i n t ∗ p t r = b e g i n ; p t r != end ; p t r++)
{
s t d : : cout << ∗ p t r << "\ t " ;
}
}

Константнi масиви

Оскiльки при передачi масиву передається фактично покажчик на перший


елемент, то використовуючи цей покажчик, ми можемо змiнити элемены ма-
сиву. якщо немає необхiдностi в змiнi масиву, то краще параметр-масив ви-
значати як константний:
#i n c l u d e <i o s t r e a m >

void pri nt ( const i n t ∗ , const i n t ∗ ) ;


void twice ( i n t ∗ , i n t ∗ ) ;

i n t main ( )
{
i n t nums1 [ ] = { 1 , 2 , 3 , 4 , 5 } ;
i n t ∗ b e g i n = s t d : : b e g i n ( nums1 ) ;
i n t ∗end = s t d : : end ( nums1 ) ;
p r i n t ( begin , end ) ;
s t d : : cout << s t d : : e n d l ;

i n t nums2 [ ] = { 1 , 2 , 3 , 4 , 5 } ;
b e g i n = s t d : : b e g i n ( nums2 ) ;
end = s t d : : end ( nums2 ) ;
t w i c e ( begin , end ) ;
f o r ( i n t ∗ p t r = b e g i n ; p t r != end ; p t r++)
{
s t d : : cout << ∗ p t r << "\ t " ;
}
s t d : : cout << s t d : : e n d l ;
129

return 0;
}

v o i d p r i n t ( c o n s t i n t ∗ begin , c o n s t i n t ∗end )
{
f o r ( c o n s t i n t ∗ p t r = b e g i n ; p t r != end ; p t r++)
{
s t d : : cout << ∗ p t r << "\ t " ;
}
}
v o i d t w i c e ( i n t ∗ begin , i n t ∗end )
{
f o r ( i n t ∗ p t r = b e g i n ; p t r != end ; p t r++)
{
∗ ptr = ∗ ptr ∗ 2;
}
}

У даному випадку функцiя print просто виводить значення з масиву, тому


параметри цiєї функцiї позначаються як константнi.
Функцiя twice змiнює елементи масиву - збiльшує їх в два рази, тому в цiй
функцiї параметри є неконстантними. Причому поле виконання функцiї twice
масив nums3 буде змiнено.
Консольне виведення програми:
1 2 3 4 5
2 4 6 8 10

10.4. Передача багатовимiрного масиву

Багатовимiрний масив також передається як покажчик на його перший еле-


мент. В той же час оскiльки елементами багатовимiрного масиву є iншi маси-
ви, то покажчик на перший елемент багатовимiрного масиву фактично пред-
ставлятиме покажчик на масив.
При визначеннi параметра як покажчика на масив розмiр другої розмiрностi
(а також всiх подальших размерностей) повинен бути визначений, оскiльки
даний розмiр є частиною типу елементу. Приклад оголошення:
v o i d p r i n t ( i n t (∗ numbers ) [ 3 ] )

Тут передбачається, що передаваний масив буде двомiрним, i всi його пiдма-


сиви матимуть по 3 елементи. Варто звернути увагу на дужки навкруги iменi
130

параметра, якi i дозволяють визначити параметр як покажчик на масив. I вiд


цiєї ситуацiї варто вiдрiзняти наступну:
v o i d p r i n t ( i n t ∗numbers [ 3 ] )

У даному випадку параметр визначений як масив покажчикiв, а не як по-


кажчик на масив.
Розглянемо вживання покажчика на масив як параметр:
#i n c l u d e <i o s t r e a m >

void pri nt ( i n t ( ∗ ) [ 3 ] , i n t ) ;
i n t main ( )
{
i n t t a b l e [ 3 ] [ 3 ] = {{1 , 2 , 3} , {4 , 5 , 6} , {7 , 8 , 9 } } ;
// к i л ь к i с т ь рядкiв або п i д м а с и в i в
i n t rowsCount = s i z e o f ( t a b l e ) / s i z e o f ( t a b l e [ 0 ] ) ;

p r i n t ( t a b l e , rowsCount ) ;
return 0;
}

v o i d p r i n t ( i n t (∗ numbers ) [ 3 ] , i n t rowsCount )
{
// к i л ь к i с т ь с т о в п ц i в або елементiв в кожному п i д м а с и в i
i n t columnsCount = s i z e o f (∗ numbers )/ s i z e o f (∗ numbers [ 0 ] ) ;
f o r ( i n t i =0; i < rowsCount ; i ++)
{
f o r ( i n t j = 0 ; j < columnsCount ; j ++)
{
s t d : : cout << numbers [ i ] [ j ] << "\ t " ;
}
s t d : : cout << s t d : : e n d l ;
}
}

У функцiї main визначається двомiрних масив - вiн складається з трьох пiд-


масивiв. Кожний пiдмасив має по три елементи.
У функцiю print разом з масивом передається i число рядкiв - по сутi число
пiдмасивiв. В самiй функцiї print одержуємо кiлькiсть елементiв в кожному
пiдмасивi i за допомогою двох циклiв перебираємо всi елементи. За допомо-
гою виразу number[0] можна звернутися до першого пiдмасиву в двомiрному
масивi, а за допомогою виразу numbers[0][0] - до першого елементу першо-
131

го пiдмасиву. I таким чином, манiпулюючи iндексами можна перебрати весь


двомiрний масив.
У результатi ми одержимо наступний консольний висновок:
1 2 3
4 5 6
7 8 9

Також ми могли б визначити параметр функци print безпосередньо як дво-


мiрний масив, але в цьому випадку знову ж таки треба б було вказати явним
чином другу розмiрнiсть:
v o i d p r i n t ( i n t numbers [ ] [ 3 ] , i n t rowsCount )
{
// к i л ь к i с т ь с т о в п ц i в або елементiв в кожному п i д м а с и в i
i n t columnsCount =
s i z e o f ( numbers [ 0 ] ) / s i z e o f ( numbers [ 0 ] [ 0 ] ) ;
f o r ( i n t i =0; i < rowsCount ; i ++)
{
f o r ( i n t j = 0 ; j < columnsCount ; j ++)
{
s t d : : cout << numbers [ i ] [ j ] << "\ t " ;
}
s t d : : cout << s t d : : e n d l ;
}
}

10.5. Динамiчнi об’єкти

У C++ можна використовувати рiзнi типи об’єктiв, якi розрiзняються по


використовуванню пам’ятi. Так, глобальнi об’єкти створюються при запуску
програми i звiльняються при її завершеннi. Локальнi автоматичнi об’єкти
створюються в блоцi коду i вiддаляються, коли цей блок коду завершує ро-
боту. Локальнi статичнi об’єкти створюються перед їх першим використову-
ванням i звiльняються при завершеннi програми.
Глобальнi, а також статичнi локальнi об’єкти помiщаються в статичнiй пам’я-
тi, а локальнi автоматичнi об’єкти розмiщуються в стеку. Об’єкти в статичнiй
пам’ятi i стеку створюються i вiддаляються компiлятором. Статична пам’ять
очищається при завершеннi програми, а об’єкти iз стека iснують, поки вико-
нується блок, в якому вони визначенi.
На додаток до цих типiв в C++ можна створювати динамiчнi об’єкти. Три-
валiсть їх життя не залежить вiд того, де вони створенi. Динамiчнi об’єкти
132

iснують, поки не будуть видаленi явним чином. Динамiчнi об’єкти розмiщу-


ються в динамiчнiй пам’ятi (free store).
Для управлiння динамiчними об’єктами застосовуються оператори new i
delete.
Оператор new видiляє мiсце в динамiчнiй пам’ятi для об’єкту i повертає по-
кажчик на цей об’єкт.
Оператор delete одержує покажчик на динамiчний об’єкт i видаляє його з
пам’ятi.

10.6. Видiлення та звiльнення пам’ятi

Видiлення пам’ятi

Створення динамiчного об’єкту:


i n t ∗ p t r = new i n t ;

Оператор new створює новий об’єкт типу int в динамiчнiй пам’ятi i повертає
покажчик на нього. Значення такого об’єкту невизначено.
Також можна iнiцiалiзувати об’єкт при створеннi:
i n t ∗p1 = new i n t ( ) ; // значення по замовчуваннi − 0
s t d : : cout << "p1 : " << ∗p1 << s t d : : e n d l ; // 0

i n t ∗p2 = new i n t ( 1 2 ) ;
s t d : : cout << "p2 : " << ∗p2 << s t d : : e n d l ; // 12

Звiльнення пам’ятi

Динамiчнi об’єкти iснуватимуть поки не будуть явним чином видаленi. I пiсля


завершення використовування динамiчних об’єктiв слiд звiльнити їх пам’ять
за допомогою оператора delete:
i n t ∗p1 = new i n t ( 1 2 ) ;
s t d : : cout << "p1 : " << ∗p1 << s t d : : e n d l ; // 12
d e l e t e p1 ;

Особливо це треба враховувати, якщо динамiчний об’єкт створюється в однiй


частинi коду, а використовується в iншiй. Наприклад:
#i n c l u d e <i o s t r e a m >

int ∗ createPtr ( int value )


133

{
i n t ∗ p t r = new i n t ( v a l u e ) ;
return ptr ;
}
void usePtr ( )
{
i n t ∗p1 = c r e a t e P t r ( 1 0 ) ;
s t d : : cout << ∗p1 << s t d : : e n d l ; // 10
d e l e t e p1 ;
}
i n t main ( )
{
usePtr ( ) ;

return 0;
}

У функцiї usePtr одержуємо з функцiї createPtr покажчик на динамiчний


об’єкт. Проте пiсля виконання функцiї usePtr цей об’єкт автоматично не
вiддаляється з пам’ятi (як це вiдбувається у випадку з локальними автома-
тичними об’єктами). Тому його треба явним чином видалити, використавши
оператор delete.
Використовування об’єкту по покажчику пiсля його видалення або повторне
вживання оператора delete до покажчика можуть привести до непередбачу-
ваних результатiв:
i n t ∗p1 = new i n t ( 1 2 ) ;
s t d : : cout << ∗p1 << s t d : : e n d l ; // 12
d e l e t e p1 ;

// помилковi с ц е н а р i ї
s t d : : cout << ∗p1 << s t d : : e n d l ;
d e l e t e p1 ;

Тому слiд видаляти об’єкт тiльки один раз.


Також нерiдко має мiсце ситуацiя, коли на один i той же динамiчний об’єкт
указують вiдразу декiлька покажчикiв. Якщо оператор delete застосований
до одного з покажчикiв, то пам’ять об’єкту звiльняється, i по другому покаж-
чику цей об’єкт ми використовувати вже не зможемо. Якщо ж пiсля цього до
другого покажчика застосувати оператор delete, то динамiчна пам’ять може
бути порушена.
134

У той же час неприпустимiсть покажчикiв пiсля вживання до них оператора


delete не означає, що цi покажчики ми у принципi не зможемо використову-
вати. Ми зможемо їх використовувати, якщо привласнимо їм адресу iншого
об’єкту:
#i n c l u d e <i o s t r e a m >

i n t main ( )
{
i n t ∗p1 = new i n t ( 1 2 ) ;
i n t ∗p2 = p1 ;
d e l e t e p1 ;
// адреса в p1 i p2 недопустимi

p1 = new i n t ( 1 1 ) ;
// p1 вказує на новий об ’ єкт
s t d : : cout << ∗p1 << s t d : : e n d l ; // 11
d e l e t e p1 ;

return 0;
}

Тут пiсля видалення об’єкту, на який указує p1, цьому покажчику передає-
ться адреса iншого об’єкту в динамiчнiй пам’ятi. Вiдповiдно ми також може-
мо використовувати покажчик p1. В той же час адреса в покажчику p2 як i
ранiше буде недiйсною.

10.7. Динамiчнi масиви

Окрiм окремих динамiчних об’єктiв в язицi C++ ми можемо використовувати


динамiчнi масиви. Для видiлення пам’ятi пiд динамiчний масив також вико-
ристовується оператор new, пiсля якого в квадратних дужках указується,
скiльки масив мiститиме об’єктiв.

Динамiчнi одновимiрнi масиви

Вiдмiнностi динамiчного масиву вiд звичайного полягають у тому, що:

• память пiд динамiчний масив видiляється динамiчно за допомогою ви-


щерозглянутих функцiй;
• кiлькiсть елементiв динамiчного масиву може бути задано змiнною (але
у програмi її неодмiнно має бути визначено до видiлення пам’ятi пiд
135

масив).

Синтаксис оголошення динамiчного одновимiрного масиву за допомогою опе-


ратора new є такий:
<базовий тип> ∗<i м ’ я>=
new <базовий тип>[< к i л ь к i с т ь елементiв > ] ;
Приклад оголошення дiйсного динамiчного масиву зi змiнною кiлькiстю еле-
ментiв:
i n t N;
N=StrToInt ( Edit0−>Text ) ;
f l o a t ∗a=new f l o a t [N ] ;
Тут кiлькiсть елементiв масиву є змiнною i вводиться з клавiатури з Edit0
перед оголошенням масиву. Звiльнення пам’ятi вiд цього масиву a матиме
вигляд:
delete [ ] a ;
Синтаксис оголошення динамiчного одновимiрного масиву за допомогою
функцiї malloc() є такий:
<тип> ∗<i м ’ я > =
(<тип>∗) m a l l o c ( s i z e o f (<тип>) ∗<кiльк . ел . > ) ;
Приклад оголошення дiйсного динамiчного масиву зi змiнною кiлькiстю еле-
ментiв за допомогою функцiї malloc():
i n t N;
N=StrToInt ( Edit1−>Text ) ;
f l o a t ∗a=( f l o a t ∗) m a l l o c ( s i z e o f ( f l o a t )∗N) ;
Приклад оголошення дiйсного динамiчного масиву зi змiнною кiлькiстю еле-
ментiв за допомогою функцiї calloc():
i n t N;
N=StrToInt ( Edit1−>Text ) ;
f l o a t ∗a=( f l o a t ∗) c a l l o c ( s i z e o f ( f l o a t ) , N) ;
Звiльнення пам’ятi зпiд цього масиву a:
free (a );

Динамiчнi двовимiрнi масиви (матрицi)

Двовимiрний динамiчний масив з m рядкiв i n стовпчикiв займає в пам’ятi


сусiднi m*n комiрок, тобто зберiгається так само, як i одновимiрний масив з
136

m*n елементiв. При розмiщеннi елементи двовимiрних масивiв розташовую-


ться в пам’ятi пiдряд один за одним з першого до останнього без промiжкiв
у послiдовно зростаючих адресах пам’ятi.
Для прикладу розглянемо дiйсний масив a розмiром 3*5. Нагадаємо, що a –
покажчик на початок масиву, тобто на елемент a[0][0]. Щоб звернутися, на-
приклад, до елемента a[1][3], слiд “перестрибнути” вiд початку масиву через
5 елементiв нульового рядка й 3 елементи першого рядка, тобто написати:
*(a+1*5+3). У загальному випадку до елемента a[i][j] можна звернутися в
такий спосiб: *(a+i*5+j). Але цей спосiб роботи з двовимiрним масивом є не
надто зручний, тому що в програмi при звертаннi до елемента масиву дово-
диться розадресовувати покажчик i обчислювати iндекс елемента. Оголосити
дiйсний динамiчний масив 3*5 можна як одновимiрний з 15-ти елементiв:
f l o a t ∗a=new f l o a t [ 3 ∗ 5 ] ;

чи то
f l o a t ∗a=( f l o a t ∗) с a l l o c ( 3 ∗ 5 , s i z e o f ( f l o a t ) ) ;

Пам’ять вiд створеного в такий спосiб масиву очищується за допомогою опе-


рацiй вiдповiдно delete й free:
delete [ ] a ;

чи то
free (a );

Розглянемо iнший спосiб роботи з динамiчним двовимiрним масивом. Для


цього розмiстимо в пам’ятi машини матрицю 3*5. При цьому буде видiле-
но пам’ять пiд кожний рядок матрицi окремо, тобто буде утворено три рiзнi
одновимiрнi масиви. Адреси нульових елементiв цих масивiв зберiгатимуться
в допомiжному масивi a (пам’ять пiд нього слiд видiлити заздалегiдь). Еле-
ментами цього масиву будуть адреси дiйсних чисел, тому вони матимуть тип
“покажчик на дiйсне число”, тобто float*. Нагадаємо, що загальний вигляд
оголошення покажчика на динамiчний масив є такий:
<тип елемента> ∗ <i м ’ я масиву >;

Тодi при оголошеннi масиву а слiд записати двi зiрочки:


f l o a t ∗∗ a = new f l o a t ∗ [ 3 ] ;
// Оголошення й розмiщення в п а м ’ я т i
// допомiжного масиву з 3−х елементiв типу f l o a t ∗
f o r ( i n t i =0; i <3; i ++) a [ i ] = new f l o a t [ 5 ] ;
// У циклi видiляється пам’ять пiд 3 масиви
// по 5 елементiв ( рядки матрицi ) й адреси
137

// нульових елементiв цих масивiв


// записуються у в i д п о в i д н i елементи масиву a
Пiсля цього можна працювати з матрицею як зi звичайним двовимiрним ма-
сивом, звертаючись до кожного елемента за його iндексом, наведеним у ква-
дратних дужках: a[i][j], – що є бiльш природним i зручним, анiж попереднiй
спосiб. Аналогiчне є оголошення за допомогою сalloc():
f l o a t ∗∗ a = ( f l o a t ∗∗) c a l l o c ( 3 , s i z e o f ( f l o a t ∗ ) ) ;
f o r ( i n t i =0; i <3; i ++)
a [ i ] = ( f l o a t ∗) c a l l o c ( 5 , s i z e o f ( f l o a t ) ) ;

10.8. Питання та завдання для самоконтролю знань

• Чи можна манiпулювати в мовi програмування С/С++ елементами ма-


сиву за допомогою покажчикiв так, як за допомогою iндексiв?
• Що береже iм’я масиву при його визначеннi?
• Як застосовують цикли для перебору елементiв масиву?
• Як за допомогою покажчикiв можна перебрати елементи масиву?
• Яким iснують способи перебору елементiв двомiрного масиву?
• Який зв’язок мають масиви в параметрах функцiї i покажчики?
• Якi iснують пiдходи до визначення маркера кiнця масиву?
• В яких випадках доцiльно використовувати константнi масиви?
• Який особливостi має зв’язок багатовимiрних масивiв та покажчикiв?
• В чому полягає рiзниця мiж статичними та динамiчними об’єктами в
мовi програмування С/С++?
• Який оператор використовуються для видiлення мiсця в пам’ятi для ди-
намiчних об’єктiв?
• Який оператор використовуються для очистки в пам’ятi вiд динамiчних
об’єктiв?
• Який оператор використовуються для видiлення пам’ятi пiд динамiчний
масив? В чому специфiка його використання для видiлення пам’ятi пiд
динамiчний масив в порiвняннi з динамiчними об’єктами?
• Якi особливостi в манiпуляцiях елементами масиву мають двовимiрнi
динамiчнi масиви (матрицi) в порiвняннi з одновимiрними динамiчними
масивами?
138

11. Багатофайловi програми у мовi програмування


С/C++

11.1. Мiжфайлова взаємодiя

Вiдповiдно до засад технологiї програмування, великi програми розбиваю-


ться на частини за функцiональною направленiстю. Один файл може мiсти-
ти програмний код пiдтримки графiчного виведення, другий – математичнi
розрахунки, третiй – код введення-виведення даних файла. При написаннi
великих програм тримати всi цi частини в одному файлi-програмi складно
чи взагалi неможливо.
Iнколи виникає потреба у використовуваннi в рiзних програмах однакових
типiв i функцiй. В таких ситуацiях є сенс винести оголошення цих типiв i
функцiй до окремого файла й долучати його за потреби до рiзних програм.
Має сенс створювати окремi бiблiотеки класiв i розмiщувати їх в окремих
файлах.
У багатофайлових програмах елементи програми (змiннi, константи, функцiї
тощо), якi зберiгаються у рiзних файлах, повиннi взаємодiяти один з одним.
Елементи програми, оголошенi зовнi усiх функцiй та класiв, мають глобаль-
ну область видимостi, тобто доступ до них можливий з кожного мiсця коду
програми. Окрiм того, глобальнi елементи є видимi i з iнших файлiв про-
екту. Глобальну змiнну може бути визначено у програмi лише одноразово,
незалежно вiд того, скiльки файлiв мiститься у програмi. Локальнi змiннi з
однаковими iменами може бути визначено у рiзних областях видимостi. Але
використовувати змiннi з однаковими iменами, навiть у рiзних блоках про-
грами, не рекомендовано, оскiльки це може призвести до помилок.
Щоб глобальна змiнна, визначена в одному файлi, була видимою у всiх фай-
лах програми, слiд оголосити її iз зарезервованим словом extern:
// Файл А
i n t X; // Визначення з м i н н о ї Х у файлi А
// Файл В
e x t e r n i n t X; // Визначення з м i н н о ї Х у файлi B
X=3;

У цьому прикладi оголошення змiнної Х у файлi А зробило її видимою у фай-


лi В. Зарезервоване слово extern означає, що оголошення в даному разi – це
лише оголошення, й нiчого бiльше. Воно спонукає компiлятор, який кожного
моменту часу бачить лише один з файлiв, не звертати уваги на те, що змiнна
Х не є визначена у файлi В.
Слiд пам’ятати про одне обмеження: змiнну не можна iнiцiалiзувати при ого-
139

лошеннi з extern. Вираз extern int X=27; примусить компiлятор вважати, що


це не є оголошення, а визначення. Тобто вiн проiгнорує слово extern i сприйме
оголошення за визначення. Якщо цю змiнну в iншому файлi вже визначено,
буде видано помилку повторного визначення.
Якщо треба мати двi глобальнi змiннi з однаковими iменами у двох рiзних
файлах, їх слiд визначати iз зарезервованим словом static. У такий спосiб
область видимостi змiнної звужується до файла, в якому її визначено. Ре-
шта змiнних з таким iм’ям можуть в iнших файлах використовуватися без
обмежень.
// Файл А
s t a t i c i n t X; // Визначення : змiнна Х є видима лише у файлi А
// Файл В
s t a t i c i n t X; // Визначення : змiнна Х є видима лише у файлi В

Код, до якого входить звертання до Х, звертатиметься лише до змiнної, ви-


значеної у даному файлi. У такому разi вважають, що статична змiнна має
внутрiшнє зв’язування. Нестатичнi глобальнi змiннi мають зовнiшнє зв’язу-
вання.
У багатофайлових програмах рекомендовано глобальнi змiннi робити стати-
чними, якщо за логiкою роботи доступ до них вимагається не бiльше нiж з
одного файла.
Звернiмо увагу на те, що зарезервоване слово static має декiлька значень
залежно вiд контексту. У контекстi глобальних змiнних слово static звужує
область видимостi змiнної до одного файла.
Змiнна-константа, яка визначається за допомогою const, у загальному випад-
ку є невидимою за межами одного файла. В цьому вона є подiбна до static.
Але її можна зробити видимою з кожного файла програми i для визначення,
i для оголошення за допомогою слова extern:
// Файл А
e x t e r n c o n s t i n t X=99; // Визначення Х
// Файл В
e x t e r n c o n s t i n t X; // Оголошення Х

Тут файл В матиме доступ до константи Х, визначеної в А.


Оголошення функцiї задає її iм’я, тип значення, яке повертає функцiя, i типи
всiх аргументiв. Визначення функцiї – це оголошення плюс реалiзацiя фун-
кцiї (код, який мiститься всерединi фiгурних скобок).
В оголошеннi функцiї визначаються лише її iм’я, тип результату i типи пара-
метрiв (аргументiв). Тому можна легко визначити функцiю в одному файлi,
а викликати її з iншого. Жодних додаткових дозволiв (як-от слова extern) не
140

вимагається. Слiд лише оголосити функцiю в тому файлi, звiдки ця функцiя


викликатиметься.
// Файл А
i n t add ( i n t a , i n t b ) // Визначення функцiї
{ r e t u r n a+b ; } // з тiлом функцiї
// Файл В
i n t add ( i n t , i n t ) ; // Оголошення функцiї ( без т i л а )
. . .
i n t answer=add ( 2 , 3 ) ; // Виклик функцiї

Зарезервоване слово extern з функцiями не використовується, оскiльки ком-


пiлятор здатен вiдрiзнити оголошення вiд визначення за наявностi чи вiдсу-
тностi тiла функцiї.
Можна оголосити (не визначити!) функцiю чи iнший елемент програми кiль-
каразово. Якщо оголошення не суперечать одне одному, компiлятор не ви-
дасть помилку:
// Файл А
i n t add ( i n t , i n t ) ; // Оголошення функцiї ( без т i л а ) ;
i n t add ( i n t , i n t ) ; // друге оголошення – помилки немає

Подiбно до змiнних, функцiї можна створити невидимими для iнших файлiв.


Для цього при їхньому оголошеннi використовується одне й те саме слово
static.
// Файл А
s t a t i c i n t add ( i n t a , i n t b ) // Визначення функцiї
{ r e t u r n a+b ; }
// Файл В
s t a t i c i n t add ( i n t a , i n t b ) // Iнша функцiя
{ r e t u r n a+b ; }

Цей код створює двi рiзнi функцiї. Жодна з них не є видима поза файлом, у
якому її визначено.

11.2. Заголовочнi файли

Заголовнi файли мають розширення h чи hpp i мiстять заголовки (прото-


типи) функцiй, а також оголошення типiв, констант та змiнних з ключовим
словом extern. Перевага заголовних файлiв полягає у тому, що, коли ство-
рено заголовний файл для одного модуля (одного чи декiлькох cpp-файлiв)
програми, можна переносити всi зробленi оголошення, якi були зробленi, до
iншої програми С/С++. Для цього слiд просто залучити заголовний файл
141

за допомогою директиви #include. Зазвичай для кожного cpp-файла ство-


рюється власний заголовний файл з таким самим iм’ям i розширенням h. I,
навпаки, здебiльшого, для кожного заголовного файла створюється cpp-файл
з реалiзацiєю функцiй, оголошених у h-файлi.
При залученнi до програми заголовного файла його текст (а з ним i текст
вiдповiдного cpp-файла) автоматично вставляється до програми замiсть ряд-
ка з вiдповiдною директивою #include. При компiляцiї програми всi залученi
заголовнi файли з файлами їхньої реалiзацiї перекомпiльовуються, тому за-
лучення надмiрної кiлькостi заголовних файлiв уповiльнює компiляцiю про-
грами.
Заголовнi файли можна роздiлити на стандартнi й створюванi програмiстом.
Стандартнi заголовнi файли зберiгаються у спецiальнiй тецi INCLUDE, яка
є пiдтекою теки, в яку було встановлено Borland C++. Iмена стандартних
заголовних файлiв пишуться у кутових дужках “<“ i “>“, наприклад: #include
<math.h> /* Залучення заголовного файла з математичними функцiями */
Окрiм того, для таких файлiв можна не зазначати розширення *.h.
Якщо стандартний заголовний файл розмiщено в iншiй тецi, слiд зазначити
його мiсцезнаходження, наприклад, щоб залучити до програми заголовний
файл types.h, який мiститься у тецi IN CLU DE/SY S, слiд написати:
\ i n c l u d e <i n c l u d e \ s y s \ t y p e s . h>
Якщо cpp-файл i h-файл мають однаковi iмена, редактор коду Borland C++
Builder дозволяє перемикатися помiж цими файлами. Для цього слiд клацну-
ти правою кнопкою мишi на iменi вкладки, в якiй вiдкрито один з файлiв, i
в контекстному меню обрати Open Source / Header File (Вiдкрити файл коду
/ Заголовний файл).
Заголовнi файли, створенi програмiстом, зазвичай розташовують у тецi про-
екту. Iмена цих файлiв у директивi #include пишуться у подвiйних лапках i
завжди з розширенням h або hpp.
Для створення заголовного файла слiд виконати команду File / New / Unit.
Буде створено новий модуль, який складатиметься з двох файлiв: заголовного
файла, наприклад з iм’ям Unit2.h (рис. 8.1), i вiдповiдного до нього файла
реалiзацiї Unit2.cpp (рис. 8.2). При зберiганнi буде запитано iм’я тiльки cpp-
файла. Iм’я h-файла буде створено автоматично.
Для використання заголовного файла у проектi його слiд додати до проекту
командою Project / Add To Project. У дiалоговому вiкнi слiд обрати iм’я
заголовного файла. Окрiм того, для використання в iнших модулях проекту
функцiй, типiв, констант, оголошених у заголовному файлi, слiд залучити
заголовний файл до модуля за допомогою директиви #include чи то виконати
команду File / Include Unit Hdr i обрати вiдповiдний файл.
142

Заголовний файл може мiстити:

• оголошення типiв typedef double arr [14];


• шаблони типiв
template <class T>
class V /* ... */
• оголошення (прототипи) функцiй extern int strlen(const char*);
• визначення функцiй-пiдстановок
inline char get()
return *p++;
• оголошення даних extern int a;
• визначення констант const float pi = 3.141593;
• перерахування enum bool false, true ;
• оголошення iмен class Matrix;
• команди долучення файлiв #include <signal.h>
• макровизначення #define Case break; case
• коментарi /* Перевiрка на кiнець файла */

Перелiк того, що саме слiд розмiщувати в заголовному файлi, не є вимо-


гою мови С/С++, це є лише порада розумного використовування долучення
файлiв. З iншого боку, в заголовному файлi нiколи не повинно бути:

• визначення функцiй char get() return *p++;


• визначення даних int a;
• визначення складених констант const tb[i] = /* ... */ ;

Не можна визначити функцiю чи то змiнну в заголовному файлi, який ви-


користовуватиметься кiлькома файлами програми. Це призводе до помилок
повторних визначень.
Для попередження помилок повторних включень визначення у заголовному
файлi слiд розпочинати з директиви препроцесора:
#i f ! d e f i n e d (HEADCOM)
143

На мiсцi HEADCOM може бути який завгодно iдентифiкатор. Цей вираз го-
ворить про те, що якщо HEADCOM ще не було визначено, то весь текст
звiдси i до директиви #endif просто вставлятиметься до файла реалiзацiї.
В iншому разi (якщо HEADCOM вже було визначено ранiш, в чому можна
впевнитися за допомогою директиви #define HEADCOM) наступний за #if
текст не буде долучено до початкового коду. Оскiльки змiнну HEADCOM не
було визначено до того як ця директива зустрiлася вперше, але одразу ж пi-
сля #if!defined() вона стала визначеною, увесь текст, розмiщений помiж #if
i #endif, буде долучено один раз, але це буде перший i останнiй раз. Напри-
клад:
#i f ! d e f i n e d (HEADCOM) // Якщо змiнну HEADCOM ще не визначено ,
#d e f i n e HEADCOM // визначити ї ї ,
i n t X; // визначити змiнну Х,
i n t f u n c ( i n t a , i n t b ) // визначити функцiю f u n c ( ) .
{ r e t u r n a+b ; }
#e n d i f // Директива , яка закриває умову .

Цей пiдхiд слiд використовувати завжди, коли iснує можливiсть випадково


долучити заголовний файл до початкового понад одного разу.
Ефективною органiзацiйною стратегiєю у цьому вiдношеннi є використання
заголовного файла для визначення типiв користувача i прототипiв для фун-
кцiй, якi опрацьовують типи користувача. Визначення функцiй слiд виносити
до окремого файла. В обох файлах – у h-файлi й у cpp-файлi визначається
i реалiзується тип даних користувача, а також те, в який спосiб його може
бути використано. Код, який викликає цi функцiї, слiд розмiщувати в iншому
cpp-файлi.

11.3. Бiблiотеки функцiй

Функцiї, прототипи яких мiстяться у стандартних заголовних файлах, зазви-


чай визначено у бiблiотеках функцiй. Бiблiотеки функцiй С/С++ – це набiр
функцiй, але не у формi коду, а у вже скомпiльованому виглядi. Файли бiблiо-
тек С/С++ мають розширення lib. При використаннi бiблiотек неможливо
обiйтися без заголовних файлiв, оскiльки це є єдиний спосiб надати програмi
iнформацiю про функцiї, якi зберiгаються у бiблiотеках. Файли бiблiотек у
Borland C++ Builder можна подiлити на три категорiї:

• файли стандартної бiблiотеки С/С++ (якi мiстять функцiї та iншi еле-


менти, спiльнi для всiх реалiзацiй С/С++);
• файли бiблiотек Windows;
144

• файли бiблiотек Borland С/С++ Builder, якi реалiзують специфiчнi фун-


кцiї С/С++ Builder.

Для того, щоб використовувати бiблiотеку у програмi, яка створюється у


Borland С/С++ Builder, слiд долучити до програми цю бiблiотеку. Це можна
зробити двома способами:

• скористатися командою iнтегрованого середовища Project / Add To


Project. Для цього слiд налагодити дiалогове вiкно, яке вiдкриється в
такий спосiб, щоб у ньому вiдображалися файли з розширенням lib, та
обрати потрi- бний файл бiблiотеки;
• долучити бiблiотеку директивою #pragma link. У загальному виглядi
використання цiєї директиви виглядає як
#pragma link ”<iм’я файла бiблiотеки>”

Приклад створення заголовного файлу, який мiстить прототипи таких фун-


кцiй:

• обчислення елементiв матрицi A розмiрнiстю 8 × 8 за формулою

 i + j + 2 4/3 при sin(i + j) > 0


 
Aij = 7i + 1 (11.1)
3
π sin (i − j) при sin(i + j) 6 0

• обчислення одновимiрного масиву, елементи якого є сумами елементiв


вiдповiдних рядкiв матрицi A
• пошук елемента матрицi A, найбiльш наближеного до значення сере-
днього арифметичного елементiв матрицi A

Для написання коду створимо файл TstLib.h зробимо там потрiбнi оголо-
шення й заголовки функцiй. Використаємо директиву #define, щоб задати
константi k значення 8.
#i f n d e f TstLibH
#d e f i n e TstLibH
// Альтернативний варiант задання константи : #d e f i n e k 8
c o n s t i n t k=8;
// Оголошення типiв matrix ( ’ ’ матриця ’ ’ ) i v e c t o r ( ’ ’ вектор ’ ’ )
t y p e d e f double matrix [ k ] [ k ] , v e c t o r [ k ] ;
// Оголошення прототипiв функцiй
v o i d elem_matrix ( matrix c ) ;
v o i d elem_Vect ( matrix c , v e c t o r v ) ;
double G( matrix c ) ;
#e n d i f
145

У файлi TstLib.cpp пропишемо визначення функцiй, прототипи яких розмi-


щено у файлi TstLib.h.
#pragma h d r s t o p
#i n c l u d e <math . h> // Долучення математичної б i б л i о т е к и
#i n c l u d e " TstLib . h"
#pragma package ( s m a r t _ i n i t )
// Визначення функцiї розрахунку елементiв матрицi
v o i d elem_matrix ( matrix c )
{ f o r ( i n t i =0; i <k ; i ++)
f o r ( i n t j =0; j <k ; j ++)
i f ( s i n ( i+j ) >0)
c [ i ] [ j ] = pow ( ( i+j +2.)/(7∗ i +1)+j , 4 . / 3 ) − 3 . 7 ∗ ( i+j ) ;
else
c [ i ] [ j ] = M_PI∗pow ( s i n ( i −j ) , 3 ) ;
}
// Визначення функцiї розрахунку елементiв вектора
v o i d elem_Vect ( matrix c , v e c t o r v )
{ f o r ( i n t i =0; i <k ; i ++)
{ v [ i ]=0;
f o r ( i n t j =0; j <k ; j ++) v [ i ]+=c [ i ] [ j ] ;
}
}
// Пошук найближчого елемента до середнього арифметичного
double G( matrix c ) {
i n t n i =0, n j =0, i , j ;
double s r =0;
f o r ( i =0; i <k ; i ++)
f o r ( j =0; j <k ; j ++) s r += c [ i ] [ j ] / pow ( k , 2 ) ;
f o r ( i =0; i <k ; i ++)
f o r ( j =0; j <k ; j ++)
i f ( f a b s ( sr−c [ i ] [ j ]) < f a b s ( sr−c [ n i ] [ n j ] ) )
{ n i=i ; n j=j ; }
return c [ ni ] [ nj ] ;
}

Головний файл проекту Unit1.cpp:


#i n c l u d e <v c l . h>
#pragma h d r s t o p
#i n c l u d e " TstLib . h" // Долучення в л а с н о ї б i б л i о т е к и
#i n c l u d e " Unit1 . h"
#pragma package ( s m a r t _ i n i t )
146

#pragma r e s o u r c e " ∗ . dfm"


TForm1 ∗Form1 ;
//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
_ _ f a s t c a l l TForm1 : : TForm1 ( TComponent∗ Owner )
: TForm( Owner )
{}
//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
// Глобальне оголошення матрицi та вектора
matrix a ; v e c t o r x ;
v o i d _ _ f a s t c a l l TForm1 : : FormCreate ( TObject ∗ Sender )
{// Компоненти Button2 та Button3 стануть невидимими
Button2−>V i s i b l e = 0 ;
Button3−>V i s i b l e = 0 ;
// Заповнення фiксованих комiрок компонентiв S t r i n g G r i d
f o r ( i n t i =1; i<=k ; i ++)
{ SG1−>C e l l s [ 0 ] [ i ] = IntToStr ( i ) + "−й " ;
SG1−>C e l l s [ i ] [ 0 ] = IntToStr ( i ) + "−й " ; ;
}
}
//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
// Виведення елементiв матрицi
v o i d _ _ f a s t c a l l TForm1 : : Button1Click ( TObject ∗ Sender )
// Виклик функцiї обчислення елементiв матрицi
{ elem_matrix ( a ) ;
f o r ( i n t i =0; i <k ; i ++)
f o r ( i n t j =0; j <k ; j ++)
SG1−>C e l l s [ j + 1 ] [ i +1] = FormatFloat ( " 0 . 0 0 0 " , a [ i ] [ j ] ) ;
Button2−>V i s i b l e = 1 ;
Button3−>V i s i b l e = 1 ;
}
//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
// Виведення елементiв вектора
v o i d _ _ f a s t c a l l TForm1 : : Button2Click ( TObject ∗ Sender )
// Виклик пiдпрограми обчислення елементiв вектора
{ elem_Vect ( a , x ) ;
f o r ( i n t i =0; i <k ; i ++)
SG2−>C e l l s [ 0 ] [ i ] = FormatFloat ( " 0 . 0 0 0 " , x [ i ] ) ;
}
//−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−
// Виведення значення скаляра
v o i d _ _ f a s t c a l l TForm1 : : Button3Click ( TObject ∗ Sender )
147

{ Edit1−>Text = FormatFloat ( " 0 . 0 0 0 " ,G( a ) ) ; }

11.4. Питання та завдання для самоконтролю знань

• На якi частини за функцiональною направленiстю розбиваються великi


програми у мовi програмування С/С++?
• Який специфiкатор використовується для об’єкта визначеного в одному
файлi для того щоб вiн був видимий у всiх файлах програми?
• Як впливає на видимiсть об’єкта його специфiкатор static?
• Як впливає на видимiсть об’єкта його специфiкатор extern?
• Яким чином можна визначити функцiю в одному файлi, а викликати її
з iншого?
• Який специфiкатор визначає функцiю в одному файлi як невидиму з
iнших файлiв?
• Яку роль виконують заголовнi файли в багатофайловiй програмi? Що
можуть мiстити заголовнi файли?
• Яким чином програмується доступ до заголовних файлiв iз iнших файлiв
програми?
• Що таке бiблiотеки функцiй С/С++? Яке розширення мають файли
бiблiотек С/С++?
• Як можна запрграмувати доступ до бiблiотек функцiй С/С++ у програ-
мi?

12. Потоки i система введення-виведення у мовi програ-


мування С/C++

12.1. Базовi типи для роботи з потоками

Всi iнструменти для роботи з системою введення-виведення i потоками в мовi


програмування С/С++ визначенi в стандартнiй бiблiотецi. Заголовний файл
iostream визначає наступнi базовi типи для роботи з потоками:

• istream i wistream: читають данi з потоку


• istream i wostream: записують данi в потiк
148

• istream i wiostream: читають i записують данi в потiк

Для кожного типу визначений його двiйник, який починається на букву w i


який призначений для пiдтримки даних типу wchar_t.
Цi типи є базовими для iнших класiв, що управляють потоками введення-
виведення.
Об’єкт типу ostream набуває значення рiзних типiв, перетворить їх в послiдов-
нiсть символiв i передає їх через буфер в певне мiсце для висновку (консоль,
файл, мережнi iнтерфейси i т.д.)
Потiк istream одержує через буфер з певного мiсця послiдовностi символiв (з
консолi, з файлу, з мережi i т.д.) i перетворить цi послiдовностi в значення
рiзних типiв. Тобто коли ми вводимо данi (з тiєї ж клавiатури в консолi),
спочатку данi нагромаджуються в буферi i тiльки тодi передаються об’єкту
istream.
За умовчанням в стандартнiй бiблiотецi визначенi об’єкти цих класiв — cout,
cin, сеrr, якi працюють з консоллю.

Запис в потiк

Для запису даних в потiк ostream застосовується оператор «. Цей оператор


одержує два операнди. Лiвий операнд представляє об’єкт типу ostream, а
правий операнд — значення, яке треба вивести в потiк.
Наприклад, за умовчанням стандартна бiблiотека C++ надає об’єкт cout,
який представляє тип ostream i дозволяє виводити данi на консоль:

#include <iostream>

int main()
{
std::cout << "Hello" << std::endl;
return 0;
}

Оскiльки оператор « повертає лiвий операнд — cout, то за допомогою лан-


цюжка операторiв ми можемо передати на консоль декiлька значень:

std::cout << "Hello" << " world" << std::endl;


149

Читання даних

Для читання даних з потоку застосовується оператор введення », який при-


ймає два операнди. Лiвий операнд представляє потiк istream, з якого прово-
диться прочитування, а правий операнд — об’єкт, в який прочитуються данi.
Для читання з консолi застосовується об’єкт cin, який представляє тип
istream.

#include <iostream>

int main()
{
int age;
double weight;
std::cout << "Input age: ";
std::cin >> age;
std::cout << "Input weight: ";
std::cin >> weight;
std::cout << "Your age: "
<< age << "\t your weight: " << weight << std::endl;
return 0;
}

Проте такий спосiб не дуже пiдходить для читання рядкiв з консолi особливо
коли прочитуваний рядок мiстить пробiльнi символи. В цьому випадку краще
використовувати вбудовану функцiю getline(), яка як параметр приймає потiк
istream i змiнну типу string, в яку треба рахувати данi:

#include <iostream>
#include <string>

int main()
{
std::string name;
std::cout << "Input name: ";
getline(std::cin, name);
//std::cin >> name;
std::cout << "Your name: " << name <<std::endl;
return 0;
}
Консольне виведення даної програми:
150

name: Tom Smit

Виведення помилок

Для виведення повiдомлення про помилку на консоль застосовується об’єкт


сеrr, який представляє об’єкт типу ostream:

#include <iostream>

int main()
{
std::cerr << "Error occured" << std::endl;
return 0;
}

Потоки символiв wchar_t

Для роботи з потоками даних типiв wchar_t в стандартнiй бiблiотецi визна-


ченi об’єкти wcout (тип wostream), wcerr (тип wostream) i wcin (тип wistream),
якi є аналогами для об’єктiв cout, сеrr i cin i працюють аналогiчно

#include <iostream>

int main()
{
int age;
double weight;
std::wcout << "Input age: ";
std::wcin >> age;
std::wcout << "Input weight: ";
std::wcin >> weight;
if (age <= 0 || weight <= 0)
std::wcerr << "Invalid data" << std::endl;
else
std::wcout << "Your age:
" << age << "\t your weight: " << weight << std::endl;
return 0;
}
151

12.2. Файловi потоки. Вiдкриття i закриття файлiв

Для роботи з файлами в стандартнiй бiблiотецi визначений заголовний файл


fstream, який визначає базовi типи для читання i запису файлiв. Зокрема, це:

• ifstream: для читання з файлу


• ofstream: для запису у файл
• fstream: сумiщає запис та читання

Для роботи з даними типу wchar_t для цих потокiв визначенi двiйники:

• wifstream
• wofstream
• wfstream

Вiдкриття файлу

При операцiях з файлом спочатку необхiдно вiдкрити файл за допомогою


функцiї open(). Дана функцiя має двi версiї:

• open(шлях)
• open(шлях, режим)

Для вiдкриття файлу у функцiю необхiдно передати шлях до файлу у виглядi


рядка. I також можна вказати режим вiдкриття. Список доступних режимiв
вiдкриття файлу:

• ios::in: файл вiдкривається для введення (читання). Може бути встанов-


лений тiльки для об’єкту ifstream або fstream
• ios::out: файл вiдкривається для виведення (записи). При цьому старi
данi видаляються. Може бути встановлений тiльки для об’єкту ofstream
або fstream
• ios::app: файл вiдкривається для дозапису. Старi данi не видаляються.
• ios::ate: пiсля вiдкриття файлу перемiщає покажчик в кiнець файлу
• ios::trunc: файл усiкається при вiдкриттi. Може бути вiдновлений, якщо
також встановлений режим out
• ios::binary: файл вiдкривається в бiнарному режимi
152

Якщо при вiдкриттi режим не вказаний, то за умовчанням для об’єктiв


ofstream застосовується режим ios::out, а для об’єктiв ifstream — режим ios::in.
Для об’єктiв fstream поєднуються режими ios::out i ios::in.

std::ofstream out; // потiк для запису


out.open("D:\\hello1.txt"); // вiдкривається файл для запису

std::ofstream out2;
out2.open("D:\\hello2.txt", std::ios::app);
// вiдкривається файл для дозапису

std::ofstream out3;
out2.open("D:\\hello3.txt", std::ios::out | std::ios::trunc);
// установка дккiлькох режимiв

std::ifstream in; // потiк для читання


in.open("D:\\hello4.txt"); // вiдкриваємо файл для читання

std::fstream fs;
// потiк для читання-запису
fs.open("D:\\hello5.txt");
// вiдкриваємо файл для читання-запису

Проте у принципi необов’язково використовувати функцiю open для вiд-


криття файлу. Як альтернатива можна також використовувати конструктор
об’єктiв-потокiв i передавати в них шлях до файлу i режим вiдкриття:

fstream(шлях)
fstream(шлях, режим)

При виклику конструктора, в який переданий шлях до файлу, даний файл


автоматично вiдкриватиметься:

std::ofstream out("D:\\hello.txt");
std::ifstream in("D:\\hello.txt");
std::fstream fs("D:\\hello.txt", std::ios::app);

Взагалi використовування конструкторiв для вiдкриття потоку є бiльш пере-


важним, оскiльки визначення змiнної, представляючої файлової потiк, вже
передбачає, що цей потiк буде вiдкритий для читання або запису. А викори-
стовування конструктора позбавить вiд ситуацiї, коли ми забудемо вiдкрити
потiк, але при цьому почнемо його використовувати.
153

У процесi роботи ми можемо перевiрити, чи вiдкритий файл за допомогою


функцiї is_open(). Якщо файл вiдкритий, то вона повертає true:

std::ifstream in; // потiк для читання


in.open("D:\\hello.txt"); // вiдкриваємо файл для читання
// якщо файл вiдкритий
if (in.is_open())
{
}

Закриття файлу

Пiсля завершення роботи з файлом його слiд закрити за допомогою функцiї


close(). Також варто вiдзначити, то при виходi об’єкту потоку з областi ви-
димостi, вiн вилучається, i у нього автоматично викликається функцiя close.

#include <iostream>
#include <fstream>

int main()
{
std::ofstream out; // потiк для запису
out.open("D:\\hello.txt"); // вiдкриваємо файл для запису
out.close(); // закриваємо файл

std::ifstream in; // потiк для читання


in.open("D:\\hello.txt"); // вiдкриваємо файл для читання
in.close(); // закриваємо файл

std::fstream fs; // потiк для читання-запису


fs.open("D:\\hello.txt"); // вiдкриваємо файл для читання-запису
fs.close(); // закриваємо файл

return 0;
}

Варто вiдзначити, що при компiляцiї через g++ слiд використовувати прапор


— static, якщо програма працює з файлами i використовує типи iз заголовного
файлу fstream:

++ app.cpp -o арp -static


154

12.3. Читання i запис текстових файлiв

Потоки для роботи з текстовими файлами представляють об’єкти, для яких


не заданий режим вiдкриття ios::binary.

Запис у файл

Для запису у файл до об’єкту ofstream або fstream застосовується оператор


« (як i при висновку на консоль):

#include <iostream>
#include <fstream>

int main()
{
std::ofstream out; // потiк для запису
out.open("D:\\hello.txt"); // вiдкриваємо файл для запису
if (out.is_open())
{
out << "Hello World!" << std::endl;
}

std::cout << "End of program" << std::endl;


return 0;
}

Даний спосiб перезаписує файл наново. Якщо треба дозаписати текст в кiнець
файлу, то для вiдкриття файлу потрiбно використовувати режим ios::app:

Читання з файлу

Якщо треба рахувати весь рядок цiлком або навiть всi рядки з файлу, то
краще використовувати вбудовану функцiю getline(), яка приймає потiк для
читання i змiнну, в яку треба рахувати текст:

std::ofstream out("D:\\hello.txt", std::ios::app);


if (out.is_open())
{
out << "Welcome to CPP" << std::endl;
}
out.close();
155

Також для читання даних з файлу для об’єктiв ifstream i fstream може засто-
совуватися оператор » (також як i при читаннi з консолi):

std::ofstream out("D:\\hello.txt", std::ios::app);


if (out.is_open())
{
out << "Welcome to CPP" << std::endl;
}
out.close();

Тут вектор структур оperation записується у файл.

for (int i = 0; i < operations.size(); i++)


{
out << operations[i].sum << " " <<
operations[i].rate << std::endl;
}

При записi в даному випадку створюватиметься файл у форматi

120 57.7
1030 57.4
980 58.5
560 57.2

Використовуючи оператор », можна рахувати послiдовно данi в змiннi sum i


rate i ними iнiцiалiзувати структуру.

while (in >> sum >> rate)


{
new_operations.push_back(Operation(sum, rate));
}

12.4. Перевизначення операторiв введення i виведення

Оператори введення » i виведення « чудово працюють для примiтивних ти-


пiв даних, таких як int або double. В той же час для використовування їх з
об’єктами класiв необхiдно перевизначати цi оператори.
156

Оператор «

Звичайно перший параметр оператора « представляє посилання на некон-


стантний об’єкт ostream. Даний об’єкт не повинен представляти константу,
оскiльки запис в потiк змiнює його полягання. Причому параметр представ-
ляє саме посилання, оскiльки не можна копiювати об’єкт класу ostream.
Другий параметр оператора визначається як посилання на константу об’єкту
класу, який треба вивести в потiк.
Для сумiсностi з iншими операторами оператор, що перевизначається, пови-
нен повертати значення параметра ostream.
Також слiд зазначити, що оператори введення i виведення не повиннi бути
членами в класi, а визначаються зовнi класу як звичайнi функцiї.

#include <iostream>
#include <string>

struct Person
{
std::string name;
int age;
};
std::ostream& operator << (std::ostream &os, const Person &p)
{
return os << p.name << " " << p.age;
}
int main()
{
Person tom;
tom.name = "Tom";
tom.age = 31;
std::cout << tom << std::endl;

return 0;
}

У даному випадку оператор виведення визначається для об’єктiв структури


Person. Сам оператор по сутi просто виводить iм’я i вiк користувача через
пропуск:

Том 31
157

Оператор »

Перший параметр оператора », як правило, представляє посилання на об’єкт


istream, з якого здiйснюється читання. Другий параметр представляє поси-
лання на неконстантний об’єкт, в який треба рахувати данi.
Звичайно як результат оператори повертають посилання на потiк введення
istream з першого параметра.

#include <iostream>
#include <string>

struct Person
{
std::string name;
int age;
};
std::istream& operator >> (std::istream& in, Person& p)
{
in >> p.name >> p.age;
return in;
}
int main()
{
Person bob;
std::cout << "Input name and age: ";
std::cin >> bob;
std::cout << "Name:
" << bob.name << "\t Age: " << bob.age << std::endl;
return 0;
}

Оператор введення послiдовно прочитує з потоку iм’я i вiк користувача. При


цьому в даному випадку передбачається, що iм’я представляє одне слово.
Якщо стоїть задача, рахувати складне iм’я, якi складається з декiлькох слiв,
або iм’я i прiзвище, то природно треба визначати складнiшу логiку.
Приклад роботи програми:

Input name and age: Bob 32


Name: Bob Age: 32

Проте що якщо ми введемо для вiку замiсть числа рядок? В цьому випадку
змiнна age набуде невизначене значення. Iснують рiзнi варiанти, як обробляти
158

подiбнi ситуацiї. Але як приклад ми можемо у разi некоректного введення


встановлювати значення за умовчанням:

std::istream& operator >> (std::istream& in, Person& p)


{
in >> p.name >> p.age;
if (!in)
{
p = Person();
}
return in;
}

За допомогою виразу if(!is) перевiряємо, чи є введення невдалим. Якщо вiн


невдалий, то використовуємо конструктор без параметрiв для створення об’-
єкту.

12.5. Читання i запис бiнарного файлу

Визначивши оператори введення i виведення, ми можемо їх використовувати


також i для читання i запису файлу:

#include <iostream>
#include <fstream>
#include <string>
#include <vector>

class Person
{
public:
std::string name;
int age;
Person(std::string n, int a): name(n),age(a) { }
Person(){ }
};
std::ostream& operator << (std::ostream &os, const Person &p)
{
return os << p.name << " " << p.age;
}
std::istream& operator >> (std::istream& in, Person& p)
{
in >> p.name >> p.age;
159

if (!in)
{
p = Person();
}
return in;
}
int main()
{
// початковi данi - вектор об’єктiв Person
std::vector<Person> people =
{
Person("Tom", 23),
Person("Bob", 25),
Person("Alice", 22),
Person("Kate", 31)
};
// запис даних у файл
std::ofstream out("D:\\users.txt");
if (out.is_open())
{
for (int i = 0; i < people.size(); i++)
{
out << people[i] << std::endl;
}
}
out.close();
// вектор для считування даних
std::vector<Person> users;
// зчитування ранiше записаних даних iз файлу
std::ifstream in("D:\\users.txt");
if (in.is_open())
{
Person p;
while (in >> p)
{
users.push_back(p);
}
}
in.close();
// виведення зчитаних даних на консоль
std::cout << "All users:" << std::endl;
for (int i = 0; i < users.size(); i++)
160

{
std::cout << users[i] << std::endl;
}

return 0;
}

Тут для класу Person визначенi оператори введення i виведення. За допомо-


гою оператора виведення данi записуватимуться у файл users.txt, а за до-
помогою оператора введення — прочитуватися з файлу. В кiнцi зчитанi данi
виводяться на консоль:
Результат роботи програми:

All users:
Tom 23
Bob 25
Alice 22
Kate 31

12.6. Питання та завдання для самоконтролю знань

• Якi базовi типи визначає заголовний файл iostream в для роботи з пото-
ками мови програмування С/С++?
• Яких значень може набувати об’єкт типу ostream?
• Для чого призначенi об’єкти cout, cin, сеrr в мовi програмування
С/С++?
• Який оператор використовується для запису даних в потiк ostream?
• Який оператор використовується для читання даних з потоку та якi його
основнi властивостi?
• В якмх випадках доцiльно використовувати вбудовану функцiю getli-
ne()?
• Який оператор використовується для виведення повiдомлення про по-
милку на консоль?
• Який заголовний файл використовується для роботи з файлами в стан-
дартнiй бiблiотецi С/С++?
• Якi базовi типи для читання i запису файлiв визначаються в стандартнiй
бiблiотецi С/С++?
161

• Яка функцiя та якi її режими використовуються для вiдкриття файлу?


• Як можна використовувати конструктор об’єктiв-потокiв для вiдкриття
файлiв?
• Яка функцiя та якi її режими використовуються для закриття файлу?
• Якi особливостi має читання i запис текстових файлiв?
• Яким чином вiдбувається перевизначення операторiв введення i виведе-
ння для роботи з файлами?
162

Рекомедована лiтература

Обов’язкова лiтература

[1] Вступ до програмування мовою С++. Органiзацiя обчисле-


нь: Навчальний посiбник, Ю.А.Бєлов, Т.О.Карнаух, Ю.В.Коваль,
А.Б.Ставровський, - Київ: Видавничо-полiграфiчний центр ”Київський
унiверситет”, 2012. – 175 с.
[2] Основи сучасного програмування: Пiдручник О.С.Бичков, - Київ:
Видавничо-полiграфiчний центр ”Київський унiверситет”, 2008. - 273 с.
[3] Алгоритмiзацiя та програмування: Практикум. Навчальний по-
сiбник, Л.I.Кублiй, — Київ: КПI iм. Iгоря Сiкорського, 2019. — 209 с.
[4] Уроки програмування на С++, Переклад з Ravesli | Уроки по С++
— WEB-ресурс, 2022.

Додаткова лiтература

[5] C/С++ и Borland С++ Builder для студента, Пахомов Б.И., —


СПб.: БХВ-Петербург, 2006. — 448 с.
[6] Самоучитель C++ Builder, Культин Н.Б., СПб.: БХВ-Петербург,
2004. - 320 с.
[7] C/C++. Программирование на языке высокого уровня, Т.А.Пав-
ловская, СПб.: Питер, 2003. — 461 с.
[8] C/C++. Структурное и объектно-ориентированное программи-
рование: Практикум., Павловская Т.А., Щупак Ю.А., — СПб.: Питер,
2011. — 352 с.
[9] C++. Объектно-ориентированное программирование: Практи-
кум., Павловская Т.А., Щупак Ю.А., — СПб.: Питер, 2006. — 265 с:
[10] Язык программирования Си. Справочник: Пер. с англ., М.И.Бо-
лски, – М.: Радио и связь, 1988. – 96 с.

You might also like