You are on page 1of 62

1

Міністерство освіти і науки України


Дніпровський національний університет ім.О.Гончара
Факультет Фізики, електроніки та комп’ютерних систем
Кафедра Комп’ютерних наук та інформаційних технологій

ОПОРНИЙ КОНСПЕКТ ЛЕКЦІЙ

З НАВЧАЛЬНОЇ ДИСЦИПЛІНИ

”Об′єктно-орієнтоване програмування”

Дніпро
2
Структура программи
#include <stdio.h> //1 #include <iostream.h> //1
#include <conio.h> //1
int sum(int,int); //2 int sum(int,int); //2
int a,b; //2
int main () //3 void main () //3
{ //4 {
scanf ("%d", &a); //4б int a,b; //4а
scanf ("%d", &b); //4б cin >> a>>b; //4б
printf ("a+b=%5d\n”, sum(a,b)); //4в cout<<"a+b=”<<sum(a+b)<< “\n"; //4в
return 0; //4г }
}
int sum(int a,int b){return (a+b);} //5 int sum(int a, int b){return (a+b);} //5
При аналізі наведеної програми можна виділити наступні великі частини типового вихідного
модуля:
1) підключення заголовних файлів бібліотек (з функціями, константами і ін.);
2) оголошення користувальницьких символічних констант, прототипів допоміжних функцій,
класів і типів даних, глобальних змінних;
3) заголовок головної функції
4) визначення (тіло) головної функції - блок, що містить:
• оголошення локальних змінних і констант і їх ініціалізацію (присвоєння початкових значень);
// 4а
• введення вихідних даних (діалог з користувачем); // 4б
• обробка - звернення до функцій, обчислення виразів, виконання операторів;
• виведення результату; //4 в
• повернення коду завершення головної функції // 4г
5) визначення користувальницьких допоміжних функцій і методів класів;
Подібне структурування тексту вихідного модуля, який розміщується в одному файлі з
розширенням срр, не єдино можлива. Але наслідування даного зразку полегшує розуміння і
налагодження програм.

Програма на мові С++ складається з функцій, описів і директив препроцесора.


1) Перший рядок цієї програми - директива процесора, за якою до текста програми
вставляється заголовний файл (stdio.h), що містить опис використаних в програмі функцій
введення/виведення (в даному випадку кутові дужки є елементом мови).
Препроцесором називається перша фаза компілятора. Інструкції препроцесора називаються
директивами. Вони повинні починатися з символу #, перед яким у рядку можуть перебувати тільки
пробільні символи.
Директива #include <ім’я_файлу> вставляє вміст зазначеного файлу, де вона записана.
Файл, який включається, також може містити директиви #include. Пошук файлу, якщо не вказано
повний шлях, ведеться в стандартних каталогах файлів, які включаються. Замість кутових дужок
можуть використовуватися лапки (" ") - в цьому випадку пошук файлу ведеться в каталозі, що
містить вихідний файл, а потім вже в стандартних каталогах.
Директива #inсlude є найпростішим засобом забезпечення узгодженості оголошень в різних
файлах, вона включає в них інформацію про інтерфейс із заголовних файлів.
2) Третій рядок - опис цілих змінних a, b; і прототипу функції.
3, 4) Одна з функцій повинна мати ім'я main. Виконання програми починається з першого
оператора цієї функції. Як правило, функція використовується для обчислення будь-якого значення,
тому перед ім'ям функції вказується його тип. Функція main може бути або int або void.
• Якщо функція не повинна повертати значення, вказується тип void;
• Тіло функції є блоком і, отже, укладається у фігурні дужки;
• Функції не можуть бути вкладеними;
• Кожен оператор закінчується крапкою з комою (крім складеного оператора).
Програма може складатися з декількох модулів (вихідних файлів).
3
4б, 4в) У мові С++ немає вбудованих засобів введення/виведення - він здійснюється за
допомогою функцій, типів і об'єктів, що містяться в стандартних бібліотеках. Використовується два
способи: функції, успадковані з мови С, і об'єкти С++.
Основні функції вводу/виводу в стилі С:
int scanf (<формат>,<список аргументів>); // введення
int printf (<формат>,<список аргументів>); // виведення
Вони виконують форматовані введення і виведення довільної кількості величин відповідно до
рядку формату формат. Рядок формату містить символи, які при введенні копіюються в потік (на
екран) або запитуються з потоку (з клавіатури) при введенні, і специфікації перетворення, які
починаються зі знака%, які при введенні і виведенні замінюються конкретними величинами. Список
найбільш уживаних специфікацій перетворення наведено в таблиці 1.

Таблиця 1. Специфікації формата для функцій введення-виведення С.


Специфіка Пояснення
ция
С аргумент розглядається як окремий символ
D, i аргумент перетворюється до десяткового виду
е, Е аргумент, що розглядається як змінна типу float або double, перетворюється в десяткову
форму у вигляді [-]m.nnnnnne[+|-]xx, де довжина рядка з n визначається із зазначеною
точністю. Точність за замовчуванням дорівнює 6
f аргумент, що розглядається як змінна типу float або double, перетворюється в десяткову
форму у вигляді [-]mmm.nnnnnn, де довжина рядка з n визначається із зазначеною
точністю. Точність за замовчуванням дорівнює 6
g, G використовується формат% е або% f, який коротше; незначущі нулі не виводяться
o аргумент перетвориться в беззнакову восьмеричну форму (без лідируючого нуля)
p виведення вказівника в шістнадцятковому форматі (ця специфікація не входить до
стандарту, але вона існує практично у всіх реалізаціях)
s аргумент є рядком: символи рядка друкуються до тих пір, поки не буде досягнуто
нульовий символ, чи не буде надруковано кількість символів, яка вказана в специфікації
точності
u аргумент перетвориться в беззнакову десяткову форму
х,Х аргумент перетвориться в беззнакову шістнадцяткову форму (без лідируючих 0х)
% виводиться символ %

Основні функції вводу / виводу в стилі С++:


Заголовковий файл (iostream.h) містить опис набору класів для управління
вводом/виводом. У ньому визначені стандартні об'єкти-потоки для введення з клавіатури і для
виведення на екран, а також методи поміщення в потік cout<< (і читання з потоку cin>>).
В одній програмі змішувати ці два способи не рекомендується

5) Після визначення головної функції йде визначення допоміжної функції sum(a, b). У тілі
функції є один оператор повернення (return). В результаті виконання цього оператора буде
обчислено значення суми двох змінних і отриманий результат буде повернений в місце виклику
функції. Для функції main типу int можна опускати оператор return 0.
Для зручності фігурна дужка, що закриває блок визначення (реалізації) функції, коментується
ім'ям функції.

Правила оформлення тексту програми, спрямовані на полегшення розуміння сенсу і


підвищення наочності, такі:
1) розділяти логічні частини програми порожніми рядками;
2) розділяти операнди і операції пробілами;
3) для кожної фігурної дужки відводити окремий рядок;
4) в кожному рядку повинно бути, як правило, не більше одного оператора;
5) обмежувати довжину рядка 60-70 символами;
4
6) відступами зліва відображати вкладеність операторів і блоків;
7) довгі оператори розташовувати в декількох рядках;
8) проводити алгоритмізацію так, щоб визначення однієї функції займало, як правило, не
більше одного екрану тексту;
9) прагнути використовувати типові заготовки фрагментів програм, включаючи і типову
структуру блоку і програми в цілому.
Коментарі
Коментар або починається з двох символів «пряма коса риска» (//) і закінчується символом
переходу на новий рядок, або укладається між символами-дужками /* і */. Всередині коментаря
можна використовувати будь-які допустимі на даному комп'ютері символи, а не тільки символи з
алфавіту мови C ++, оскільки компілятор коментарі ігнорує.
Рекомендується використовувати для пояснень // - коментарі, а дужки /* */ застосовувати для
тимчасового виключення блоків коду при налагодженні.

Використання процесора.
Препроцесором називається перша фаза компілятора. Препроцесор входить до складу системи
програмування С++ та забезпечує обробку вихідного тексту програми перед її компіляцією. В ході
препроцесорної обробки програми реалізуються наступні дії:
1. виконується підстановка макророзширеннь замість імен макросів і їх аргументів.
2. в вихідний текст програми включаються файли, що містять допоміжну інформацію (описи
об'єктів, класів, прототипи функцій і т. п.)
3. Виконується умовна компіляція програми - визначення остаточного вигляду вихідного
тексту програми шляхом залишення або виключення окремих її фрагментів.
Завдання препроцесору описується за допомогою директив препроцесора, які починаються зі
знака #, що розміщується з першої позиції в рядку, перед яким у рядку можуть перебувати тільки
пробільні символи.
Препроцесорна обробка дозволяє скоротити час розробки програм, підвищити їх наочність і
мобільність. Зокрема, при зміні характеристик апаратно-програмних засобів досить змінити вміст
файлів, які підключаються.

Директива #include
Директива #include <ім'я файлу> вставляє вміст зазначеного файлу в ту точку вихідного
фала, де вона записана. Файл, який включається також може містити директиви #include. Пошук
файлу, якщо не вказано повний шлях, ведеться в стандартних каталогах включення файл. Замість
кутових дужок можуть використовуватися лапки («») - в цьому випадку пошук файлу ведеться в
каталозі, що містить вихідний файл, а потім вже в стандартних каталогах.
Директива #include є найпростішим засобом забезпечення узгодженості оголошень в різних
файлах, вона включає до них інформацію про інтерфейс із заголовних файлів.
Заголовны файли зазвичай мають розширення .h і можуть містити:
 Визначення типів, констант, вбудованих функцій, шаблонів, перерахувань (злічень);
 Оголошення функцій, даних, імен, шаблонів;
 Простори імен;
 Директиви процесора;
 Коментар.
В заголовному файлі не повинно бути визначень функцій і даних. Ці правила не є вимогами
мови, але також відображають розумний спосіб використання директиви.
При вказівці заголовних файлів стандартної бібліотеки розширення .h можна опускати. Більш
ранні версії компіляторів можуть не підтримувати (це свіжа вимога стандарту).

Директива #define
Директива #define визначає підстановку в тексті програми. Вона використовується для визначення:
 Символічних констант:
#define ім'я текст_підстановки
(Всі входження імені замінюються на текст_підстановки);
5
 Макросів, які виглядають як функції, але реалізуються підстановкою їх тексту в текст
програми:
#define ім'я (параметри) текст_підстановки
 Символів, керуючих умовною компіляцією. Вони використовуються разом з директивами
#ifdef і #ifndef. формат:
#define ім'я
Приклади:
#define VERSION 1
#define VASIA "Василь Іванович"
#define MAX (x, y) ((x)> (y)? (x) :( y))
#define MUX
Імена рекомендується записувати прописними буквами, щоб візуально відрізняти їх від імен змінних і
функцій. Параметри макросу використовуються при макропідстановці, наприклад, якщо в тексті програми
використовується виклик макросу y = MAX(sum1, sum2) :, він буде замінений на
y =((sum1,)>(sum2)?(sum1) :( sum2));
Відсутність круглих дужок може привести до неправильного порядку обчислення, оскільки препроцесор не
оцінює вставляється текст, щододається, з точки зору синтаксису. Наприклад, якщо до макросу #define
sqr(x)(x*x) звернутися як sqr(y+1), в результаті підстановки отриаємо вираз (y+1*y+1).
Макроси і символічні константи встановлені з мови С, при написанні програм на С++ їх слід уникати. Замість
символічних констант краще використовувати const або enum, а замість макросів - вбудовані функції і
шаблони.

Список ключових слів..


asm else new this
auto enum operator throw
bool explicit private true
break export protected try
case extern public typedef
catch false register typeid
char float reinterpret_cast typename
class for return union
const friend short unsigned
const_cast goto signed using
continue if sizeof virtual
default inline static void
delete int static_cast volatile
do long struct wchar_t
double mutable switch while
dynamic_cast namespace template
6
Класифікация типів даних

Таблиця 1. Діапазони значень базових типів даних


тип даних назва розмір, біт діапазон значень
(16/32 разр.)

[signed] char цілий довжиною не менш 8 бітів 8/8 -128 . . 127

unsigned char беззнаковий цілий довжиною не менш 8 8/8 0 . . 255


бітів

[signed] short [int] (short) короткий цілий 16/16 -32768 . . 32767

unsigned short [int] беззнаковий короткий цілий 16/32 0 . . 65535

[signed] int цілый 16/32 -32768 . . 32767

unsigned int беззнаковий цілий 16/32 0 . . 65535

[signed] long [int] (long) довгий цілий 32/32 -214748348 . . 2147483647

unsigned long [int] беззнаковий довгий цілий 32/32 0 . . 4294967295

float дійсний одинарної точності 32/32 3.4Е-38 . . 3.4Е+38

double дійсний подвійної точності 64/64 1.7Е-308 . . 1.7Е+308

long double дійсний максимальної точності 80/80 3.4Е-4932 . . 1.1Е+4932

enum злічений 16/16 -32768 . . 32767


Мінімальні та максимальні допустимі значення для цілих типів залежать від реалізації і приведені в
заголовках <limits.h> (<climits>), характеристики дійсних типів - в файлі <float.h> (<cfloat>), а також в шаблоні
класу numeric_limits. Розмір цілих типів залежить від реалізації, але для всіх версій С++ прийнято:
sizeof (short) <= sizeof (int) <= sizeof (long)
Крім то гарантується у всіх реалізаціях:
1 = sizeof (char) <= sizeof (short) 1 <= sizeof (bool) <= sizeof (long)
Для дійсних типів виконується співвідношення:
sizeof (float) <= sizeof (double) <= sizeof (long double)
7
Таблиця 2. Формати констант, відповідних кожному типу.

Константа Формат Приклади

Ціла Десятковий: послідовність десяткових цифр, що 8, 0, 199226


починається не з нуля, якщо це не число нуль

Вісімковий: нуль, за яким слідують вісімкові цифри 01, 020, 07155


(0,1,2,3,4,5,6,7)

Шістнадцятковий: 0х або 0Х, за яким слідують 0хА, 0x1В8, 0X00FF


шістнадцяткові цифри (0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F)
Дійсна Десятковий: 5.7, .001, 35.
[цифри].[цифри] (можуть бути опущені ціла частина, або
десяткова, але не обидві відразу. Якщо вказані обидві частини,
символ точки обов'язковий).

Експонентний: 0.2Е6, .11е-3, 5Е10


[цифри][.][цифри]{Е|е}[+|-][цифри] (можуть бути
опущені ціла частина, або дробова, але не обидві відразу.
Якщо вказані обидві частини, символ точки обов'язковий)
Символьна Один або два символи, укладених в апострофи клавіатурні:
'А', 'ю', '*',
кодові:
'\n', '\a', '\t',
'\?',
кодові числові:
'\0', '\012',
Рядкова Послідовність символів, укладена в лапки "Здесь
'\x07' был Vasia",
"\tЗначення
r=\0xF5\n"

Таблиця 3. Управляючі послідовності в мові C++


Зображення Шістнадцятковий код Найменування
\а 7 Звуковий сигнал
\b 8 Повернення на крок
\f С Перевод страниці (формата)
\n А Перевод рядка
\r D Повернення каретки
\t 9 Горизонтальна табуляція
\v В Вертикальна табуляція
\\ 5С Зворотна коса риска
\’ 27 Апостроф
\" 22 Лапки
\? 3F Знак питання
\0ddd — Вісімковий код символа
\0xddd ddd Шістнадцятковий код символа
8

Таблиця 4. Характеристики змінних


Місце
Модификатор Область
оголошення Час життя Область дії
виділення пам’яті видимості
змінної
не вказан (за
поза функцією до кінця файлу постійне глобальна змінна файла
замовчуванням static)
не вказан (за
в функції до кінця блока тимчасове локальна змінна
замовчуванням auto)
заборонено поза
auto
функцією
auto в функції до кінця блока тимчасове локальна змінна
заборонено поза
register
функцією
register в функції до кінця блока тимчасове швидка локальна змінна
static поза функцією до кінця файлу постійне глобальна змінна файла
локальна змінна, яка
static в функції до кінця блока постійне
зберігається
глобальна змінна
extern поза функцією до кінця файлу постійне
програми
спеціальна глобальна
extern в функції до кінця файлу постійне
змінна

Простір імен
Іменовані області служать для логічного групування оголошень і обмеження доступу до них. Чим
більше програма, тим більш актуально використання іменованих областей. Найпростішим прикладом
застосування є відділення коду, написаного однією людиною, від коду, написаного іншим. При використанні
єдиного глобального контекста форматувати програму з окремих частин дуже складно через можливі збіги і
конфлікти імен. Використання іменованих областей (простору імен, namespace) перешкоджає доступу до
непотрібних засобів.
Простір імен (namespace) являє собою деяку оголошувану за допомогою спеціальної конструкції
область, яка служить для локалізації (обмеження області дії) імен змінних і об'єктів з метою уникнення
конфліктів при використанні однакових імен. Ситуація, коли в програмі застосовується кілька однакових
ідентифікаторів, зазвичай виникає у разі використання бібліотек функцій і класів різних виробників. При
використанні простору імен, крім необов'язкового глобального простору імен, виділяється кілька приватних
просторів. У різних приватних просторах можна використовувати однакові ідентифікатори змінних, класів і
об'єктів, які беруть участь в рамках свого приватного простору імен і мають в ньому свій сенс.
Простір імен задається наступним чином:
namespace ім'я
{
// оголошення
}
тут:
namespace - ключове слово, що задає простір імен; ім'я - будь-який ідентифікатор.
У фігурних дужках знаходиться сам простір імен, що має назву із заданим ім'ям. В результаті такого
визначення простору все імена, що входять в даний простір імен, будуть видимі тільки всередині введеного
простору.
Приклад 1. Визначення і використання простору імен.
#include <iostream>
namespace A
{ int x,y;
class M
{
public:
void wodx(int с) {х=с;}
int dostx() {return x; }
}
void vyvod (int k) {cout << ;}
}
9
void main()
{ int z,t,y;
y=3;
A::y=7;
A::M b;
z=9;
b.vvodx (z) ;
A::x=z;
cout << "\n A::x=";
A::vyvod(A::x);
cout << "\n y=";
A::vyvod(y);
cout << "\n A::y=" << A::y;
t=b.dostx() ;
cout << "\n t=" << t;
}
В результаті роботи програми на екран буде видано наступні повідомлення:

А::х=9
У=3
А::у=7
t=9
У прикладі змінні х, у, клас М і функція vyvod() знаходяться в області видимості, визначеної простором
імен А. Опис функції main() знаходиться в глобальному просторі імен. У глобальному просторі імен видимі
змінні r, t і у. Таким чином, в програмі оголошені дві змінні з одним ім'ям у: перша видима в просторі імен
А, а друга - в глобальному просторі.
Застосування імен ідентифікаторів всередині простору імен проводиться звичайним чином. При
використанні імен ідентифікаторів іншого простору імен необхідно застосовувати операцію :: вказівки
області видимості. Наприклад, у функції main() оператор А::x=z; присвоює значення змінної z (з
глобального простору імен) змінній х, що належить простору імен A. Для оголошення об'єкта b класу М, опис
якого знаходиться в просторі імен А, необхідно також використовувати операцію :: вказівки області
видимості при оголошенні об'єкту: А :: М b ;.
Існує можливість введення більш одного простору імен з одним і тим же ім'ям, причому наступні
оголошення розглядаються як розширення попередніх. Це дозволяє розділити простір імен на кілька файлів
або на кілька частин всередині одного файлу.
Наприклад:
namespace L { int x;}
namespace L { float у;}
Тут оголошені дві частини одного простору імен L.
Пример 2.
namespace demo
{ int i = 1
int k = 0
void funcl (int);
void func2 (int) { /* … */ }
}
namespace demo{ // Розширення
// int I = 2 невірно - подвійне визначення
void funcl (double); // Перевантаження
void func2 (int); // Вірно (повторне оголошення)
}
В оголошенні іменованої області можуть бути присутніми як оголошення, так і визначення. Логічно
поміщати в неї тільки оголошення, а визначати їх пізніше за допомогою імені області та оператора доступу до
області видимості ::, наприклад:
void demo :: funcl (int) {/* … */}
Це застосовується для поділу інтерфейсу і реалізації. Таким способом не можна оголосити новий елемент
простору імен.
10
Об'єкти, оголошені всередині області, є видимими з моменту оголошення. До них можна явно звертатися за
допомогою імені області та оператора доступу до області видимості ::, наприклад:
demo :: i = 100; demo :: func2(10);
Якщо необхідно часто застосовувати операцію вказівки області видимості, то доцільно використовувати
оператор using, який дозволяє отримати доступ до конкретного імені в просторі імен, або до всіх імен
простору імен.
Для отримання доступу до окремого імені в просторі імен використовується оператор в наступному
форматі:
using імя_пространства_імен :: ім'я;
Наприклад, для того, щоб забезпечити доступ до змінної х простору імен А з функції main() приведеної
програми, досить виконати оператор using A :: х.
Для забезпечення доступу до всіх імен деякого простору імен використовується оператор using в
наступному форматі:
using namespace імя_пространства_імен;
Після виконання такого оператора вказаний простір імен додається до поточної області видимості.
Оператори using і using namespace можна використовувати і всередині оголошення пойменованої області,
щоб зробити в ній доступними оголошення з іншої області:
namespace Department_of_Applied_Mathematics
{ using demo:: i;
// …
}
Імена, оголошені в пойменованій області явно або за допомогою оператора using, мають пріоритет по
відношенню до імен, оголошених за допомогою оператора using namespace (це має значення при
включенні декількох названих областей, що містять співпадаючі імена).
Короткі імена просторів імен можуть увійти в конфлікт один з одним, а довгі непрактичні при написанні
реального коду, тому допускається вводити синоніми імен:
namespace DAM = Department_of_Applied_Mathematics;
Для зменшення ймовірності конфлікту імен в останніх версіях компіляторів бібліотека C ++ визначена у
власному просторі імен std. Використання оператора using namespace std; дає можливість додати в
поточну область видимості все імена, що містяться всередині бібліотеки C ++.
У мові C ++ можна оголосити безіменний простір імен:
namespace {// оголошення }
Безіменні простори імен дають можливість повністю закрити доступ ззовні до імен простору імен, оскільки
отримати доступ до імен за допомогою оператора using або операції :: неможливо, т.к. відсутнє ім'я у
простору імен.
Якщо ім'я області не задано, компілятор визначає його самостійно за допомогою унікального
ідентифікатора, різного для кожного модуля. Оголошення об'єкта в різних неіменованій області рівнозначно
його опису як глобального з модифікатором static. Поміщати оголошення в таку область корисно для того,
щоб зберегти локальність коду. Не можна отримати доступ з одного файлу до елементу неіменованої області
іншого файлу.
Приклад 2. Застосування декількох просторів імен.
#include <iostream>
//Визначення першого простору імен
namespace name_A
{ int i;
class Р
{
int х;
public:
P(int newy) {x=newy;}
void izmx(int c) {x=c;}
int dost() {return x;}
};
char str[]="\n Простір імен";
}
11
//Визначення першої частини другого простору імен
namespace name_B
{ int у;
}
//Визначення другої частини другого простору імен
namespace name_B
{int z; }
int main()
{
//Створення об’єкта b класа Р
name_A :: P b(2) ;
cout<<"\n Значення елемента даних х об’єкта b:"<<b.dost();
cout << endl;
b.izmx (15);
cout<<"Нове значення елемента даних х об’єкта b:"<<b.dost();
//Завдання видимості рядка str в поточному просторі імен
using name_A :: str;
cout << str << endl;
// Завдання видимості простору імен name_A в поточному просторі імен
using namespace name_A;
for (i=l;i<ll;i++)
cout << i << " ";
//Використання обох частин другого простору імен
name_B :: у=100;
cout << "\n name_B :: у=" << name_B :: у;
name_B :: z=200;
cout << "\n name_B :: z=" << name_B :: z;
//Приєднання простору імен name_B до поточного простору імен
using namespace name_B;
Р by(у), bz(z);
cout<<"\n Значення елемента даних х об’єкта by:"<<by.dost();
cout<<"\n Значення елемента даних х об’єкта bz:"<<bz.dost();
cout << endl;
return 0;
}
В результаті роботи програми на екран буде виведено наступне:
Значення елемента даних х об’єкта b:2
Нове значення елемента даних х об’єкта b:15
Простір імен
12345678910
name_B :: у=100
name_B :: z=200
Значення елемента даних х об’єкта by:100
Значення елемента даних х об’єкта bz:200
У наведеній програмі визначені два простори імен: name_А і name_B. Крім того, існує глобальний простір
імен програми. У просторі імен name_А видимі клас Р, змінна i і рядок str. Простір name_В складається з
двох частин. У першій частині простору name_В видима змінна у, а в другій - змінна z. Опис функції main()
знаходиться в глобальному просторі імен.
При розширенні області видимості за допомогою оператора using необхідно стежити за тим, щоб в
отриманому спільному просторі імен були відсутні однакові імена. У разі наявності таких, чинним є ім'я, яке
оголошено в поточному просторі імен.
Механізм просторів імен разом з директивою #include забезпечують необхідну при написанні великих
програм гнучкість шляхом поєднання логічного групування пов'язаних величин і обмеження доступу.
Як правило, в будь-якому функціонально закінченому фрагменті програми можна виділити інтерфейсну
частину (наприклад, заголовки функцій, опису типів), необхідну для використання цього фрагмента, і частину
реалізації, тобто допоміжні змінні, функції та інші засоби, доступ до яких ззовні не потрібен. Простори імен
дозволяють приховати деталі реалізації і, отже, спростити структуру програми і зменшити кількість
потенційних помилок. Продумане розбиття програми на модулі, чітка специфікація інтерфейсів і обмеження
доступу дозволяють організувати ефективну роботу над проектом групи програмістів.
12
Таблиця 5. Операції С++
Унарні:
& отримання адреси операнда
* звертання за адресою (розіменування)
+ унарний плюс
- унарний мінус, змінює знак арифметичного операнда
~ поразрядне інвертування внутрішнього двійкового кода (побітовое заперечення)
! логичене заперечення (НЕ). Вякості логічних значень використовуються 0 - брехня и
не 0 - істина, запереченям 0 буде 1, запереченням будь-якого ненульового числа буде
0.
++ збільшення на одиницю:
префіксна операція - збільшує операнд до його використання,
постфіксна операція - збільшує операнд після його використання.
-- зменшення на одиницю:
префіксна операція - зменшує операнд до його використання,
постфіксна операція - зменшує операнд після його використання.
sizeof обчислення розміру (в байтах) для об’єкта того типу, який має операнд
new виділення пам'яті
delete звільнення пам'яті
:: доступ до області видимості
(тип)значение перетворення типу
. вибор елемента за іменем
-> вибор елемента за вказівником
[] вибор елемента за індексом
.* вибор елемента за іменем через вказівник
->* вибор елемента за вказівником через вказівник
() виклик функції
typeid идентифікація типу (запрошує дані про тип)
const_cast перетоворення константної змінної в неконстантну
dynamic_cast динамичне перетворення типів (має сенс для поліморфних класів)
static_cast перетворення одного типу в інший, але вона не може буть використана для виконання
неприпустимого перетворення (наприклад, значення у вказівник)
reinterpret_cast операція приведення типів даних, не може бути використана для приведення
іерархії класів або перетоврення константних змінних
Бінарні операції:
Аддитивні:
+ бінарний плюс (додавання арифметичних операндів)
- бінарний минус (віднімання арифметичних операндів)
Мультиплікативні:
* множення операндів арифметичного типу
/ ділення операндів арифметичного типу (якщо операнды цілочислові, то виконується цілочислове
ділення)
% отримання остачі від ділення цілочислових операндів
Операції зсуву (визначені тільки для цілочислових операндів).
Формат виразу з операцією зсуву:
операнд_лівий операція_зсуву операнд_правий
<< зсув вліво бітового подання значення лівого цілочисельного операнда на кількість розрядів, що
дорівнює значенню правого операнда
>> зсув вправо бітового подання значення правого цілочисельного операнда на кількість розрядів, що
дорівнює значенню правого операнда
Поразрядні операції:
& поразрядна кон'юнкція (І) бітових подань значень цілочислових операндів
| поразрядна диз'юнкція (АБО) бітових подань значень цілочислових операндів
^ поразрядне виключаення АБО бітових подань значень цілочислових операндів
13
Логичні бинарні операції:
&& кон'юнкція (І) цілочислових операндів або відношень, цілочисельний результат неправда (0) або
істина (1)
|| диз'юнкція (АБО) цілочислових операндів або відношень, цілочисельний результат неправда (0) або
істина (1)
Операції сравнения:
< менше, чим
> більше, чим
<= менше або дорівнює
>= більше або дорівнює
== дорівнює
!= не дорівнює
Операції присвоєння:
= просте присвоєння
*= присвоїти результат множення
/= присвоїти результат ділення
%= присвоїти остаток от ділення
+= присвоїти результат додавання
-= присвоїти результат віднімання
<<= присвоїти результат зсуву вліво
>>= присвоїти результат зсуву вправо
&= присвоїти результат поразрядної (побітової) кон'юнкції
^= присвоїти результат поразрядного (побітового) виключающого АБО
|= присвоїти результат поразрядної (побітової) диз'юнкції
Післядовність виразів:
, послідовне виконання виразів

Тернарна операція:
? : якщо вираз до ? не дорівнює 0, виконується оператор до :
якщо дорівнює 0, виконується оператор після :
14
Таблиця 6. Пріоритети операцій С++
При- Знаки операцій Назва операцій Порядок
орі- обчислення
тет
1 :: доступ до області видимості зліва
2 . вибор елемента за іменем зліва
-> вибор елемента за вказівником
[] вибор елемента за індексом
() виклик функції або конструювання значення
++ постфіксний інкремент
-- постфіксний декремент
typeid ідентифікація типу
dynamic_cast перетворення з перевіркою при виконанні
static_cast перетворення з перевіркою при компіляції
reinterpret_cast перетворення без перевірки
const_cast константне перетворення
3 sizeof розмір операнда в байтах зправа
++ префіксний інкремент
-- префіксний декремент
~ інверсія (поразрядне НЕ)
! логічне НЕ
+ унарний плюс
- унарний мінус
& адреса
* розйменування
new виділення пам'яті або створення
delete звільнення пам'яті або знищення
(имя_типу) перетворення типу
4 .* вибір елемента за іменем через вказівник зліва
->* вибір елемента за вказівником через вказівник
5 * множення зліва
/ ділення
% остача від ділення цілих (ділення за модулем)
6 + додавання зліва
- віднімання
7 << зсув вліво зліва
>> зсув вправо
8 < менше зліва
> більше
<= менше або дорівнює
>= більше або дорівнює
9 == дорівнює зліва
!= не дорівнює
10 & поразрядне І зліва
11 ^ поразрядне виключаче АБО зліва
12 | поразрядне АБО зліва
13 && логическое І зліва
14 || логическое АБО зліва
15 ?: умовна (тернарна) зправа
16 = присвоєння (просте та складені) зправа
*=, /=, %=
+=, -=
<<=, >>=
&=, ^=, |=
17 throw генерація виключень зправа
18 , послідовність виразів зліва
15
Перетворення Типів
Велике розмаїття типів в C ++ дозволяє обрати тип, що задовольняє необхідним вимогам.
Однак крім користі це розмаїття ще й ускладнює комп'ютеру життя. Наприклад, при складанні двох
значень, що мають тип short, можуть використовуватися апаратні інструкції, відмінні від
застосовуваних при складанні двох значень long. При наявності 11 цілочісловіх типів і 3 типів чисел з
плаваючою точкою комп'ютер стикається з безліччю випадків їх обробки, особливо якщо змішуєте
різні типи. Щоб впоратися з потенційною плутаниною в типах, багато перетвореннь типів в C ++
здійснюються автоматично.
• C ++ перетворює значення під час присвоєння значення одного арифметичного типу
змінній, яка відноситься до іншого арифметичного типу.
• C ++ перетворює значення при комбінуванні різних типів в виразах.
• C ++ перетворює значення при передачі аргументів функціям.
Перетворення бувають двох типів, які:
1. змінюють внутрішнє подання величин (з втратою точності або без втрати точності);
2. змінюють тільки інтерпретацію внутрішнього уявлення.
До першого типу відносяться, наприклад, перетворення цілого числа в дійсне (без втрати
точності) і навпаки (можливо, з втратою точності), до другого - перетворення знакового цілого в
беззнакове.
Якщо ви не розумієте, що відбувається під час автоматичного перетворення типів, то деякі
результати виконання ваших програм можуть виявитися дещо несподіваними.

Перетворення при ініціалізації і присвоєнні


Мова C ++ досить ліберальна, дозволяючи прісвоєння числового значення одного типу
змінній іншого типу. Кожного разу, коли ви це робите, значення перетворюється до типу змінної, яка
його отримує. Припустимо, наприклад, що змінна so_long має тип long, змінна thirty - тип
short, а в програмі є наступний оператор:
so_long = thirty; // присвоєння значення типу short змінній типу long
Програма приймає значення змінної thirty (зазвичай 16-бітове) і розширює його до
значення long (зазвичай 32-бітове) під час присвоєння. Зверніть увагу, що в процесі розширення
створюється нове значення, яке буде присвоєно змінній so_long; вміст змінної thirty залишається
незмінним.
Нульове значення, присвоєне змінній bool, перетворюється в false, а ненульове значення -
в true.
Присвоєння значення змінній, тип якої має більш широкий діапазон, зазвичай відбувається
без особливих проблем. Наприклад, при присвоєнні значення змінній, що має тип short, змінній
типу long саме значення не змінюється; в цьому випадку значення просто отримує кілька
додаткових байтів, які залишаються незайнятими. А якщо велике значення, яке має тип long,
наприклад, 2111222333, прісвоїті змінній типу float, точність буде втрачена. Оскільки змінна типу
float може мати тільки шість значущих цифр, значення може бути округлено до 2.11122Е9. Таким
чином, якщо одні перетворення безпечні, то інші можуть призвести до складнощів. У табл. 7 вказані
деякі можливі проблеми, пов'язані з перетвореннями.

Таблиця 7. Потенційні проблеми при перетворенні чисел


Тип перетворення Можливі проблеми
Більший тип з плаваючою точкою в Втрата точності (значущих цифр); початкове значення
менший тип з плаваючою точкою, може перевищити діапазон, допустимий для цільового
наприклад, double в float типу, тому результат виявиться невизначеним
Тип з плаваючою точкою в Втрата дробової частини; початкове значення може
цілочисельний тип перевищити діапазон цільового типу, тому результат буде
невизначеним
Більший цілочисельний тип в менший Початкове значення може перевищити діапазон,
цілочисельний тип, наприклад, long в допустимий для цільового типу; зазвичай копіюються
short тільки молодші байти.
16
Присвоєння значень, які мають тип з плаваючою точкою, цілочисельним змінним породжує
пару проблем. По-перше, перетворення значення з плаваючою точкою в цілочислове призводить до
усічення числа (відкидання дробової частини), а не до округлення (заміна найближчим цілим
числом). По-друге, значення типу float може виявитися занадто великим, щоб його вмістити в
змінну int. В цьому випадку стандарт C ++ не визначає, яким повинен бути результат, тобто різні
реалізації C ++ будуть по-різному реагувати на подібні ситуації. Деякі компілятори попереджають
про можливу втрату даних для тих операторів, які ініціалізують цілочіслові змінні значеннями з
плаваючою точкою.

Перетворення при ініціалізації за допомогою {} (С++ 11)


В С++ 11 ініціалізація, при якій використовуються фігурні дужки, називається списковою
ініціалізацією. Причина в тому, що ця форма дозволяє надавати списки значень для більш складних
типів даних. Це більш обмежує тип перетворення. Зокрема, спискова ініціалізація не допускає
звуження, при якому тип змінної може бути не в змозі подати присвоєне значення. Наприклад,
перетворення типів з плаваючою точкою в цілочіслові типи не дозволені. Перетворення з
цілочісловіх типів в інші цілочіслові типи або типи з плаваючою точкою може бути дозволено, якщо
компілятор має можливість повідомити, чи здатна цільова змінна коректно зберігати значення.
Наприклад, форматувати змінну long значенням int цілком нормально, оскільки тип long завжди
не може перевищувати int. Перетворення в іншому напрямку можуть бути дозволені, якщо значення
- це константа, яка підтримується цільовим типом:

const int code = 66;


int x = 66;
char cl {31325}; // звуження, не дозволено
char c2 = {66}; // дозволено, оскільки char може зберігати значення 66
char c3 {code}; // те ж саме
char c4 = {x}; // не дозволено, х не є константою
x = 31325; char c5 = x; // дозволено цією формою ініціалізації

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

Перетворення у виразах
У вираз можуть входити операнди різних типів. Якщо операнди мають однаковий тип, то
результат операції матиме той же тип. Якщо операнди різного типу, перед обчисленнями
виконуються перетворення типів за певними правилами, що забезпечує перетворення більш коротких
типів в більш довгі для збереження значущості і точності. У подібних випадках C ++ виконує два
види автоматичних перетворень. По-перше, деякі типи автоматично перетворюються всюди, де вони
зустрічаються. По-друге, деякі типи перетворюються, коли вони скомбіновані з іншими типами в
виразі.
Спочатку розглянемо автоматичні перетворення. Коли C ++ оцінює вирази, значення bool,
char, unsigned char, signed char і short перетворюються в int. Зокрема, значення true
перетвориться в 1, a false - в 0. Такі перетворення називаються цілочисельними розширеннями. Як
приклад розглянемо наступні оператори:

short chickens = 20; // рядок 1


short ducks = 35; // рядок 2
short fowl = chickens + ducks; // рядок 3

Щоб виконати оператор в рядку 3, програма C ++ отримує значення змінних chickens і


ducks і перетворює їх в тип int. Потім програма перетворює отриманий результат назад в short,
оскільки кінцевий результат присвоюється змінній типу short. Це може здатися ходінням по колу,
17
проте воно має сенс. Для комп'ютера int зазвичай є найбільш природним типом, тому всі
обчислення з ним можуть виявитися найшвидшими.
Існують і інші цілочислові розширення: тип unsigned short перетворюється в int, якщо
тип short коротше, ніж int. Якщо обидва типи мають однаковий розмір, то unsigned short
перетворюється в unsigned int. Це правило гарантує, що ніякої втрати даних при розширенні
unsigned short не буде. Подібним чином розширюється і тип wchar_t до першого з наступних
типів, який достатньо широкий, щоб умістити його діапазон: int, unsigned int, long або
unsigned long.
Можливі також перетворення при арифметичному комбінуванні різних типів, наприклад, при
додаванні значень int і float. Якщо в арифметичній операції беруть участь два різних типи, то
менший тип перетворюється в більший. Наприклад, в програмі значення 9.0 ділиться на 5. Оскільки
9.0 має тип double, програма, перш ніж зробити ділення, перетворює значення 5 в тип double.
Взагалі кажучи, при визначенні, які перетворення необхідні в арифметичному виразі, компілятор
керується контрольним списком.
1. Перш за все кожен операнд типу char або short перетворюється в значення типу int, а
операнди типу unsigned short перетворюються в значення типу unsigned int.
2. Крім того, операнди типу float до початку операції перетворюються в значення типу
double.
3. Потім якщо один з операндів має тип double, то інший перетвориться в значення типу
double і результат буде мати тип double.
4. Інакше якщо один з операндів має тип unsigned long, то інший перетвориться в значення
типу unsigned long і таким же буде тип результату.
5. Інакше якщо один з операндів має тип long, то інший перетвориться в значення типу
long і таким же буде його результат.
6. Інакше якщо один з операндів має тип long, а інший - тип unsigned int, то обидва
операнда перетворюються в значення unsigned long.
7. Інакше якщо один з операндів має тип unsigned, то інший перетвориться в значення типу
unsigned і тип результату unsigned.
8. Інакше обидва операнди повинні бути типу int і таким же буде тип результату.

У C ++ 11 цей список зазнав деяких змін і представлений нижче.


1. Якщо один з операндів має тип long double, то інший операнд перетвориться в long
double.
2. Інакше, якщо один з операндів має тип double, то інший операнд перетвориться в
double.
3. Інакше, якщо один з операндів має тип float, то інший операнд перетвориться в float.
4. Інакше, операнди мають цілочисельний тип, тому віконується цілочислове розширення.
5. У цьому випадку, якщо обидва операнда мають знак або обидва операнда беззнакові, і один
з них має менший ранг, ніж інший, він перетворюється в більший ранг.
6. Інакше, один операнд має знак, а інший беззнаковий. Якщо беззнаковий операнд має
більший ранг, ніж операнд зі знаком, післядній перетворює в тип беззнакового операнда.
7. Інакше, якщо тип зі знаком може подати всі значення беззнакового типу, беззнаковий
операнд перетвориться до типу операнда зі знаком.
8. Інакше, обидва операнда перетворюються в беззнакову версію типу зі знаком.
Стандарт ANSI С слідує тим же правилам, що і ISO 2003 C ++, які злегка відрізняються від
наведених вище, а класична версія K&R С має також правила, які трохи відрізняються. Наприклад, в
класичному С тип float завжди розширюється до double, навіть якщо обидва операнда
відносяться до типу float.
У цьому списку була введена концепція призначення рангів цілочисельним типам. Якщо
коротко, як ви могли очікувати, базові ранги для цілочісловіх типів зі знаком, від більшого до
меншого, виглядають наступним чином: long long, long, int, short і signed char.
Беззнакові типи мають ті ж самі ранги, що і відповідні їм типи зі знаком. Три типи - char, signed
18
char і unsigned char - мають один і той же ранг. Тип bool має найменший ранг. Типи
wchar_t, charl6_t і char32_t мають ті ж ранги, що і типи, що лежать в їх основі.

Перетворення при передачі аргументів


Зазвичай в C ++ перетвореннями типів при передачі аргументів керують прототипи функцій.
Однак можливо, хоча і нерозсудливо, відмовитися від управління передачею аргументів з боку
прототипу. В цьому випадку C ++ застосовує цілочислове розширення для типів char і short
(signed і unsigned). Крім того, для збереження сумісності з великим об'ємом коду на класичному
С, C ++ розширює аргументи float до double при передачі їх функції, в якій не використовується
управління з боку прототипу.

Явне приведення типів


Програміст може задати перетворення типу явно. C ++ дозволяє явно забезпечити
перетворення типів через механізм приведення. (C ++ визнає необхідність наявності правил,
пов'язаних з типами, але також дозволяє часом перевизначати ці правила.) Приведення типу може
бути здійснено двома способами. Наприклад, щоб перетворити значення int, що зберігається в
змінній на ім'я thorn, в тип long, можна використовувати один з наступних виразів:

(long) thorn // повертає результат перетворення thorn в тип long


long (thorn) // повертає результат перетворення thorn в тип long

Приведення типу не змінює значення самої змінної thorn; замість цього створюється нове
значення зазначеного типу, яке потім можна використовувати в виразі, наприклад:
cout << int ('Q'); // відображає цілочисельний код для 'Q'
Синтаксис:

(ім'яТипа) значення // перетворює значення в тип ім'яТипа


Ім'яТипа (значення) // перетворює значення в тип ім'яТипа

Перша форма являє стиль С, а друга - стиль C ++. Ідея нової форми полягає в тому, щоб
оформити приведення типу точно так же, як і виклик функції. В результаті приведення для
вбудованих типів будуть виглядати так само, як і перетворення типів, що розробляються для
визначених користувачами класів.

// Приклад. Явне приведення типів.

int auks, bats, coots;


// наступний оператор додає числа як дані типу double,
// а потім перетворює результат в дані типу int
auks = 19.99 + 11.99;
// ці оператори додають числа як дані типу int
bats = (int) 19.99 + (int) 11.99; // старий синтаксис мови С
coots = int (19.99) + int (11.99); // новий синтаксис мови C++
cout << "auks = " << auks << ", bats = " << bats;
cout << ", coots = " << coots << '\n';
char ch = ‘Z’;
cout << "The code for " << ch << " is ";
//виводить значення змінної як дані типа char
cout << int(ch) << '\n';
// виводить значення змінної як дані типа int
return 0;
}
Результат виконання цієї програми:
auks = 31, bats = 30, coots = 30
The code for Z is 90
19
Отримувані результати будуть залежати від того, коли здійснюється перетворення. Спочатку
при обчисленні значення змінної auks додавання чисел 19.99 і 11.99 дає результат 31.98. Коли це
число присвоюється змінної auks типу int, воно урізається до числа 31. Але при обчисленні
значень змінних bats і coots спочатку числа з плаваючою точкою перетворюються в дані типу
int за допомогою методу приведення типів (урізаються відповідно до чисел 19 і 11 ще до
додавання) . В результаті значення змінних bats і coots виходять рівними 30. У останньому
операторі cout метод приведення типів використовується для перетворення значення типу char в
значення типу int перед відображенням на екрані. Це призводить до того, що об'єкт cout виводить
дане значення як ціле число, а не як символ.
Цей фрагмент ілюструє дві причини, за якими доводиться застосовувати метод приведення
типів. По-перше, в програмі можуть бути числа, які зберігаються як дані типу double, але
використовуються для обчислення значень типу int. Наприклад, можна моделювати цілі величини,
наприклад чисельність населення, використовуючи числа з плаваючою крапкою. Вам може
знадобитися, щоб при обчисленнях ці числа трактувалися як дані типу int. Приведення типів
дозволяє зробити це безпосередньо. Зверніть увагу, що ви отримуєте різні результати (по крайней
мере, для цих чисел) в тому випадку, коли спочатку перетворюєте їх в дані типу int, а потім
додаєте, і коли спочатку додаєте, а потім перетворите в дані типу int. У другій частині програми
продемонстрована найбільш поширена причина використання приведення типу: можливість змусити
дані в одній формі задовольняти різним очікуванням. Наприклад, змінна ch типу char зберігає код
букви Z. Використання cout для виведення ch призводить до відображення літери Z, оскільки cout
концентрує увагу на факті приналежності змінної ch до типу char. Однак за рахунок приведення ch
до типу int об'єкт cout перемикається на режим int і виводить ASCII-код, що зберігається в ch.
C ++ також пропонує чотири операции приведення типів з більш обмеженими можливостями
застосування:
const_cast
dynamic_cast
static_cast
reinterpret_cast
Одна з цих чотирьох операцій, static_cast <>, може використовуватися для
перетворення значень з одного числового типу в інший. Наприклад, її можна застосовувати для
перетворення змінної thorn в значення типу long:
static_cast <long> (thorn) // повертає результат перетворення
// thorn в тип long
У загальних рисах можна робити наступне:

static_cast <ім'яТипа> (значення)


// перетворює значення в тип ім’яТипа

Страуструп був переконаний, що традиційне приведення типу в стилі С небезпечно


необмежено у своїх можливостях. Операція static_cast є більш обмеженою, ніж традиційне
приведення типу.

Оператори
Оператор переходу goto мітка - передає управління на оператор з міткою.
Оператор безумовного переходу goto має формат:
goto мітка;
Використовується для виходу з вкладених керуючих операторів.
Область дії обмежена поточної функцією.
У тілі тієї ж функції повинна бути присутня рівно одна конструкція виду:
Мітка: оператор;
Мітка - це звичайний ідентифікатор, область видимості якого є функцією, в тілі якої він
заданий.
Приклад goto ABC;
Використання оператора безумовного переходу виправдано в двох випадках:
20
1. примусовий вихід вниз по тексту програми з декількох вкладених циклів або перемикачів;
2. перехід з декількох місць функції в одне (наприклад, якщо перед виходом з функції завжди
необхідно виконувати будь-які дії).
В інших випадках для запису будь-якого алгоритму існують більш підходящі засоби, а
використання goto призводить тільки до ускладнення структури програми і утруднення
налагодження. Застосування goto порушує принципи структурного і модульного програмування, за
якими всі блоки, з яких складається програма, повинні мати тільки один вихід і один вхід.
У будь-якому разі не слід передавати управління всередину операторів if, switch і циклів.
Не можна переходити всередину блоків, що містять ініціалізацію змінних, на оператори розташовані
після неї, оскільки в цьому випадку ініціалізація не буде виконана:

int k; …
goto Mitka; …
{int a = 3, b = 4;
k = a + b;
Mitka: int m = k + 1 …
}
Після виконання цього фрагмента програми значення змінної m не визначено

Цикли
Найпоширеніші помилки програмування циклів - використання в тілі циклу неініціалізованих
змінних і невірний запис умови виходу з циклу.
Щоб уникнути помилок рекомендується:
• перевірити, чи всім змінним, які зустрічаються в правій частині операторів присвоєння в тілі циклу,
присвоєні до цього початкові значення;
• перевірити, чи змінюється в циклі хоча б одна змінна, що входить в умову виходу з циклу;
• передбачити аварійний вихід з циклу по досягненню деякої кількості ітерацій;
• і звичайно, не забувати про те, що якщо в тілі циклу потрібно виконати більше одного оператора,
потрібно укладати їх у фігурні дужки.
Оператори циклу взаємозамінні. Але можна навести деякі рекомендації по вибору найкращого в
кожному конкретному випадку.
Оператор do while зазвичай використовують, коли цикл потрібно виконати хоча б раз.
Оператором while зручніше користуватися у випадках, коли число ітерацій заздалегідь невідомо,
очевидних параметрів циклу немає.
Оператор for кращий в більшості інших випадків (однозначно для організації циклів із лічильниками).
В С++ він є більш гнучким засобом, ніж аналогічні оператори циклів в інших мовах програмування.

Можливості використання циклу типу for.


1. Можна застосовувати операцію зменшення для рахунку в порядку спадання.
Приклад. Потрібно обчислити у5. Можливе рішення має вигляд:
for (i = 5, r = 1; i> = 1; i--) r = r * y;
cout << "r =" << r;
2. При бажанні можна організувати рахунок двійками, трійками, десятками і т.д.
Приклад. Приріст лічильника, відмінний від 1.
for (n = 5; n <61; n + = 15) cout << n;
3. Можна в якості лічильника використовувати не тільки цифри, але і символи.
Приклад. Потрібно надрукувати алфавіт. Можливе рішення має вигляд:
for (chr = 'A'; chr <= 'Z'; chr ++) cout << chr;
4. Можна задати зростання значень лічильника не в арифметичній, а в геометричній прогресії.
Приклад. Потрібно підрахувати борг. Можливе рішення має вигляд:
for (k = 100; k <185; k * = 1.1) соut << "Борг =" << k;
5. В якості третього виразу можна використовувати будь-який правильно складений вираз. Він буде
обчислюватися в кінці кожної ітерації.
Приклад. Використання в якості лічильника вираза.
for (k = 1; z <= 196; z = 5 * k + 23) cout << z;
6. Можна пропускати один або кілька виразів. (При цьому не можна пропускати символи «крапка з комою».)
Приклад. Неповний список виразів в заголовку тіла циклу.
for (р = 2; p <= 202;) p = p + n / k;
7. Перший вираз не обов'язково має ініціювати змінні, він може бути будь-якого типу.
21
Приклад. Довільний перший вираз в заголовку циклу.
for (соut << "Введіть числа."; р <= 30;) cin >> p;
8. Змінні, що входять до виразу специфікації циклу, можна змінювати в тілі циклу.
Приклад. Зміна керівників змінних в тілі циклу.
delta = 0.1;
for (k = l; k <500; k + = delta) if (a> b) delta = 0.5;
9. Використання операції «кома» в специфікації циклу дозволяє включати кілька ініціюючих і коригувальних
виразів.
Приклад. Використання операции «кома» в специфікації циклу.
for (i = 1, r = 1; i <= 10; i ++, r * = y)
cout << "y в cтепені" << i << "=" << r;
10. Можна перевіряти будь-яку іншу умову, яка відрізняється від кількості ітерацій.
22
Функції для роботи з випадковими числами в C ++
Для ефективного використання в програмі випадкових чисел необхідні два засоби:
1. функція-генератор, яка при кожному виклику буде повертати випадкове число;
2. функція-ініціалізатор для випадкової ініціалізації генератора.
Необхідність в другій функції викликана тим, що випадкові числа, отримані програмним шляхом,
насправді є псевдовипадковими. Вони обчислюються за певною формулою, і при однакових початкових
умовах послідовність чисел буде повторюватися. При виконанні функції-ініціалізатора для функції-генератора
встановлюється нове початкове значення.
C ++ пропонує цілий ряд стандартних функцій для роботи з випадковими об'єктами.

Функції rand і srand

Синтаксис:
#include <stdlib.h>
int rand (void);

Файл, що містить прототип


stdlib.h

Прототип: int rand (void);


мультиплікативний конгруентний генератор випадкових чисел з періодом 2 в 32 ступені. При кожному
виклику повертає наступне псевдовипадкове число в межах від 0 до RAND_MAX. RAND_MAX - символьна
константа, оголошена в файлі stdlib.h, яка визначає найбільше випадкове число.

/ * (В модулі stdlib.h) * /
#define RAND_MAX 32767;

Сумісність: POSIX, Win32, ANSI C, ANSI C ++.

Приклад:

#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int i;
printf ( "10 випадкових чисел від 0 до 99 \ n \ n");
for (i = 0; i <10; i ++)
printf ( "% d \ n", rand ()% 100);
return 0;
}

Синтаксис:
#include <time.h>
void srand (unsigned seed);

Файл, що містить прототип


time.h

Прототип:
void srand (unsigned seed);
ініціалізує генератор випадкових чисел. Початкова ініціалізація відбувається викликом srand з аргументом 1.
Нове початкове значення встановлюють викликом цієї функції з іншим аргументом.

Сумісність: POSIX, Win32, ANSI C, ANSI C ++.

Приклад.
#include <iostream>
#include <time.h>
int main () {
int a;
23
srand (time (0)); // ініціалізація генератора псевдовипадкових чисел
a = rand (); // отримуємо чергове псевдовипадкове число
cout << a; // вивід на екран
cin.get ();
}
/* srand - Це ініціалізація генератора випадкових чисел, time - повертає
поточний календарний час системи, в якості аргументу вона приймає вказівник на
змінну типу time_t, якій і буде присвоєний календарний час.
Прототип функції виглядає так:
time_t time (time_t * time);
Календарний час і повертається функцією, і поміщається в переданий аргумент,
але можна передавати нульовий вказівник (тобто 0 або null). 0 в даному випадку
не число, а нульовий вказівник. * /

приклад:
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <time.h>

int main ()
{
int num;
// ініціалізувавши генератор випадкових чисел
srand (time (NULL));
printf ( "RAND_MAX:% d \ n", RAND_MAX);
num = rand ();
printf ( "Guess the number:% d \ n", num);
num = rand ()% 10;
printf ( "Guess the number (0 to 9):% d \ n", num);
num = rand ()% 10 + 1;
printf ( "Guess the number (1 to 10):% d \ n", num);
num = (rand ()% 2) * 2 - 1;
printf ( "Guess the number (-1 or 1):% d \ n", num);
getch ();
return 0;
}

Приклад.
/ * Встановлює генератор випадкових чисел в початковий стан і зберігає в масиві
20 перших згенерованих випадкових чисел. * /
24
Посилання
Змінні посилального типу можуть використовуватися в насиупних цілях:
• посилання замість передачі в функцію об'єкта за значенням;
• для визначення конструктора копії;
• для перевантаження унарних операцій;
• для запобігання неявного виклику конструктора копії при передачі у функцію за значенням об'єкта
визначеного користувачем класу
Зазвичай посилання використовуються при передачі аргументів функцій, при поверненні значень функцій
через посилання, а також при створенні псевдонімів об'єктів (змінних). У останньому випадку мова йде про
незалежне посилання (independent reference), яка використовується в якості псевдоніма (іншого імені) змінної,
зазначеного при ініціалізації посилання. Для визначення посилання використовується символ &, що
вказується перед змінною-посиланням. Формат оголошення посилання:
тип & ім'я;
де тип - це тип величини, на яку вказує посилання, &-оператор посилання, що означає, що наступне за
ним ім'я є ім'ям змінної посилального типу. Значення посилання - це значення змінної, на яку посилання
виконане (в цьому сенсі вона відрізняється від вказівника, значенням якого є адреса комірки, на яку
посилається вказівник). При оголошенні посилання необхідно відразу вказувати, на яку змінну вона
посилається (в цьому випадку говорять про ініціалізацію посилання). Посилання ініціалізується тільки один
раз. Надалі ім'я посилання служить ще однією альтернативною назвою для змінної, на яку виконано
посилання.
int n;
int & copy = n;
copy = 100;
n ++; // n = 101;
Використання незалежних посилань:
#include <iostream.h>
int main ()
{іnt t = 15, & r = t // ініціалізація посилання на t тепер
// r синонім імені t, а & r-адреса
cout << "t =" << t; // виводить 15
r + = 10; // зміна значення t через посилання
count << "t =" << t; // виводить 25
return 0;
}
int n,*p,*q;
int &copy=n;
p=&n;
copy=100;
(*p)/=10;
q=&copy;
n++;
cout << n << "\n" ;
cout<<copy<<" \n";
cout << * p << "\n";
cout << * q << "\n" ;
cout <<p<<"\n";
cout<<q<<" \n";
Запам'ятайте такі правила.
• Змінна посилання повинна явно ініціалізуватися при її описі, крім випадків, коли вона є параметром
функції, описана як extern або посилається на поле даних класа.
• Після ініціалізації посиланню не може бути присвоєна інша змінна.
• Тип посилання повинен збігатися з типом величини, на яку воне посилається.
• Не дозволяється визначати вказівники на посилання, створювати масиви посилань і посилання на
посилання.
• Не можна взяти адресу змінної посилального типу.
• Заборонено використовувати посилання на бітові поля.
Вони виключно важливі при роботі з функціями і складають основу одного з двох фундаментальних
механізмів передачі функції аргументів. Що стосується незалежних посилань, то їх широке використання на
практиці залишається під питанням, оскільки наявність псевдонімів у змінних, як правило, свідчить про
погану організацію програми. Проте, при роботі з класами посилання бувають дуже корисними.
25
Масиви символів
У C ++ не існує вбудованого типу для текстових даних. Текстові рядки реалізуються у вигляді масивів
символів або у вигляді об'єктів класу string.
За великим рахунком масив символів мало чим відрізняється від масивів інших типів. Головна
особливість пов'язана з тим, що при оголошенні масиву із символів необхідно зарезервувати достатньо місця
для того, щоб в такий масив можна було записувати рядки різної довжини. Іншими словами, при реалізації
рядків у вигляді масивів існує принципове обмеження на довжину рядка. Таке обмеження існує і при
використанні статичних масивів інших типів. Однак там ця проблема не настільки актуальна. Оскільки при
роботі із символьними масивами мова йде про подання типу даних, а не одиничного об'єкта, необхідно
передбачити можливість частої зміни даних. Важливими показниками при роботі з текстовими даними є
довжина рядка. Оскільки в символьному масиві кожен символ рядка відповідає елементу масиву, довжина
рядка безпосередньо має відношення до розміру символьного масиву. З проблемою обмеженості розміру
рядків пов'язана ще одна проблема. Навіть якщо розмір масиву досить великий для того, щоб записувати в
нього строкові значення, необхідний індикатор, який дозволяв би визначити, де в масиві записана корисна
інформація, а де починається неінформативний «хвіст». В якості такого індикатора використовують
спеціальний символ '\0', який називається нуль-символом.
Таким чином, рядкова змінна реалізується в програмі у вигляді масиву символів. Ознакою закінчення
рядка є нуль-символ. Щоб вписати в масив рядок, необхідно, щоб розмір масиву принаймні на одиницю
перевищував кількість символів в рядку. Цей додатковий елемент необхідний для запису нуль-символу '\0'
закінчення рядка. Оголошуються масиви символів, як і інші масиви: вказується тип елементів масиву (для
символьних масивів це char), назва і розмір масиву. Приклад оголошення символьного масиву:
char str [80];
В даному випадку оголошений масив із 80 символів. При цьому в такий масив можна записати рядок з
максимальною довжиною в 79 символів. Ініціалізуватися символьні масиви можуть так само, як і, наприклад,
числові: після імені та розміру масиву вказується знак рівності і в фігурних дужках список символів, які є
значеннями елементів масиву.
Однак існує ще один більш зручний спосіб ініціалізації символьного масиву: замість списку символів
вказується в подвійних лапках текстовий рядок:
char str1[20]= "hello";
char str2[20] = {'h', 'e', 'l', l', 'o', '\0'};
cout << str1 << "\n" ;
cout << str2 << “\n" ;
В обох випадках оголошується масив з 20 символів. Ініціалізація в першому випадку виконується шляхом
вказівки текстового значення (значення укладено в подвійні лапки). У другому випадку це ж значення
передається у вигляді укладеного у фігурні дужки списку, причому в явному вигляді вказується нуль-символ
закінчення рядка. При ініціалізації масиву за допомогою текстового літерала нуль-символ додається
автоматично.
Для виведення значень символьного масиву на екран його ім'я вказується праворуч від оператора
виведення
cout << str1 и cout << str2.
Раніше зазначалося, що якщо масив ініціалізується при оголошенні, розмір масиву вказувати
необов'язково. Розглянемо два наступних способи ініціалізації символьного масиву
char str1[] ="hello" ;
char str2[]={'h', 'e', 'l', l', 'o', '\0'};
Формально і в одному, і в іншому випадку значенням масиву є один і той же текстовий рядок hello.
Однак формуються масиви по-різному. Масив, ініціалізіємий командою
char str1[ ] ="hello" ,
складається з 6 елементів (хоча букв в слові hello тільки 5). Як уже зазначалося, якщо символьний масив
ініціалізується значенням-рядком (значення в подвійних лапках), то в масив в якості елементів послідовно
заносяться всі символи рядка і ще один символ в кінці масиву - символ закінчення рядка, тобто нуль-символ
'\0'.
При ініціалізації символьного масиву командою
char str2 [] = {'h','е','l','l','o'}
нічого подібного не відбувається. В цьому випадку масив складається з 5 елементів - відповідно до кількості
символьних елементів в списку значень масиву.
При зчитуванні значення текстового рядка з клавіатури із занесенням цього значення в символьний масив
ім'я масиву, в який віконується запис, вказується праворуч від оператора введення.
26
// Зчитування рядка з клавіатури
#include <iostream>
using namespace std;
int main ()
{
char str[100];
cout<<”nter your text, please: ";
cin>> str;
cout<<"Your text is: "<<str<<endl;
return 0 ;
}

Командою char str[100] оголошується символьний масив зі 100 елементів, а командою cin>>str;
з клавіатури зчитується текстове значення і заноситься в цей масив. Якщо ввести на запит програми слово
Hello, результат виконання програми буде виглядати наступним чином:
Enter your text, please: Hello
Your text is: Hello
Формально в масив можна занести текст довжиною до 99 символів. Однак на практиці все обмежується
одним словом. Справа в тому, що оператор введення >> зчитує інформацію, поки не зустріне символ переходу
на новий рядок, символ табуляції або пробіл. Оскільки слова в тексті розділяються пробілами, буде
зчитуватися тільки перше слово. Наприклад, якщо на запит програми ввести текст Hello, World!,
результат отримаємо наступний:
Enter your text, please: Hello, World!
Your text is: Hello,
Проблема вирішується, якщо замість оператора введення >> використовувати функцію gets().
Аргументом функції вказується назва масиву, в який заноситься значення. Після внесення змін програмний
код буде виглядати так:
// Зчитування рядка з клавіатури, використовуючи функцію gets()
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
char str[100];
cout<<"Enter your text, please: ";
// Для зчитування рядка використана функція gets():
gets(str);
cout<<"Your text is: "<<str<<endl;
return 0 ;
}
У загальному випадку для використання функції gets() необхідно підключити заголовок <cstdio>.
Результат виконання програми в цьому випадку може мати такий вигляд:
Enter your text, please: Hello, World!
Your text is: Hello, World!
При поелементному заповненні символьного масиву вже після оголошення необхідно пам'ятати, що
останнім символом після введення символів рядка повинен бути нуль-символ. Приклад програми з
поелементним заповненням символьного масиву.
// поелементне заповнення символьного масиву
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
char str[30];
int n=26;
char s=’а’;
for(int i=0 ; i<n; i++,s++)
str[i]=s;
str[n] ='\0';
cout << str <<endl;
return 0 ;
27
}
Результат виконання цієї команди має вигляд:
abcdefghijklmnopqrstuvwxyz
Звертаємо увагу на спосіб зміни символьної змінної s: незважаючи на те, що змінна має тип char, для її
зміни використовується оператор інкремента ++. З таким же успіхом замість оператора циклу можна
використовувати наступний оператор циклу (інші команди незмінні):
for(int i=0 ;i<n;i++)
str[i]=s+i;
Якщо після цього заповнити масив новими значеннями, заповнюється тільки початкова частина масиву.
При цьому ті елементи масиву, які не потрапили під перевизначення, залишаються незмінними.
// Поелементне заповнення масива
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
int i;
const int n=20;
char str[20];
for(i=0; i<n; i++)
{
str[i] = 'A'+i;
cout<<str [i] ;
}
cout<<endl;
cout<<"Enter a string: " ;
gets(str);
cout<<str<<endl;
for (i = 0; i<n; i++)
cout<<str[i];
cout<<endl;
return 0 ;
}

Спочатку поелементно заповнюється символьний масив str розміру 20 (розмір заданий через
цілочислову константу n). Занесені в масив значення також поелементно виводяться на екран одним рядком.
Перебираються всі елементи масиву. Для заповнення масиву та виведення значень на екран використовується
оператор циклу. Далі користувачеві пропонується ввести текстовий рядок. При зчитуванні рядок заноситься в
масив. Рядок командою cout << str << endl виводиться на екран, після чого за допомогою оператора
циклу виводяться всі елементи масиву. Якщо в якості рядка користувача ввести фразу Hello, World!, то
результат виконання програми може бути наступним:
ABCDEFGHIJKLMNOPQRSТ
Enter a string: Hello, World!
Hello, World!
Hello, World! OPQRST
Інтерес представляють передостанній і останній рядки виведення результатів виконання програми. У
передостанньому рядку виводиться фраза Hello, World! (Та фраза, що введена користувачем з
клавіатури). Це результат виконання команди cout << str. При зчитуванні фрази користувача і запису її в
масив останнім після безпосередньо тексту рядка автоматично додається нуль-символ. Тому при виконанні
команди cout << str виведення на екран здійснюється до цього символу.
Останній рядок виводу результатів отримуємо при виконанні команд cout << str [i] в рамках
останнього оператора циклу. Оскільки перебираються всі елементи масиву, то на екран виводяться не тільки
елементи масиву до нуль-символу, а й елементи, розміщені після цього символу.
Сам нуль-символ відображається у вигляді пропуску. У цьому сенсі нуль-символ є дуже важливим
індикатором, який відокремлює корисний, робочий текст, занесений в символьний масив, від "сміття",
розміщеного в хвості масиву.

Нуль-символ закінчення рядка


28
На використанні нуль-символу закінчення рядка в символьному масиві заснована робота багатьох
корисних і ефективних алгоритмів. На деяких особливостях використання нуль-символу зупинимося більш
детально.
В першу чергу проілюструємо спосіб посимвольного виведення рядка на екран.
// поелементне виведення рядка на екран
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
char str[20];
cout<<"Enter a string: ";
gets(str);
for(int i=0; str[i]; i++)
cout<<str[i];
cout<<endl;
return 0;
}
Користувачем вводиться з клавіатури рядок, а командою gets(str) він заноситься до масиву str.
Після цього в рамках оператора циклу рядок виводиться на екран. Звернути увагу в даному випадку варте на
другий блок в інструкції for: в якості умови, якка перевіряється, вказано вираз str[i]. Тут використана та
чудова обставина, що в C ++ будь-яке ненульове значення інтерпретується як логічне значення true, а
нульове - як false.
Тому поки при переборі значень не зустрінеться нуль-символ закінчення рядка (який інтерпретується як
логічне значення false), значення елементів str[i] інтерпретуються як логічне значення true. Виникає
природне запитання: а що буде, якщо рядок містить 0? Відповідь така: все буде так само, як з будь-яким
іншим рядком, оскільки число 0, занесене в символьний масив, числом бути перестає і інтерпретується як
символ.
Такому "символьному" нулю відповідає логічне значення true.
Використовуючи нуль-символ як ознаку закінчення рядка, можемо створити простеньку функцію
користувача для обчислення реальної довжини рядка, занесеної в масив (в C ++ для цих цілей є вбудована
функція strlen()).

//Функція користувача для обчислення довжини рядка


#include <iostream>
#include <cstdio>
using namespace std;
// Функція для обчислення довжини рядка:
int length(char *str)
{
int i=0 ;
while(str[i])
{ i++ ;}
return i;
}
//Перевірка роботи функції length():
int main()
{
char str[80];
cout<<"Enter a string: ";
gets(str);
cout << “String length is "<<length(str) <<endl;
return 0 ;
}
У функції length() всього один елемент - вказівник на масив типу char. Це ім'я того масиву, куди
записаний рядок і довжину якого слід обчислити. У тілі функції length() ініціалізується з нульовим
значенням цілочислова змінна i з нульовим значенням, після чого це значення послідовно збільшується до тих
пір, поки не зустрінеться нуль-символ закінчення рядка. Як значення повертається змінна i. У головному
методі програми буде запропоновано ввести користувачем текстовий рядок, який записується до масиву, після
29
чого обчислюється довжина рядка в масиві. Так, якщо ввести чергову фразу Hello, World!, отримаємо
такий результат:
Enter a string: Hello, World!
String length is 13
За допомогою нуль-символів можна створювати і більш хитромудрі конструкції. Наприклад, формально в
один символьний масив можна записувати одночасно кілька рядків.
// Запис до масиву декількох рядків
#include <iostream>
#include <cstdio>
using.namespace std;
// Функція для внесення рядки в масив.
// Аргументи функції - масив strl для внесення рядка,
// вноситься рядок str2, а також порядковий індекс n-го рядка
void StringIn(char *str1,char *str2, int n)
{
// Пошук позиції в масиві для запису рядка
while(n!=0 )
{
if ( ! (*str1)) n-- ;
str1++;
}
//Запис рядка в масив
while(*str2)
{
*str1=*str2;
Str1++;
str2++;
}
//Запис нуль-символа в кінець рядка в масиві
*str1='\0';
}
// Функція для виведення на екран рядка з масиву.
// Аргументи функції - масив str, з якого витягується рядок,
// а також порядковий індекс n рядка,який витягується
void StringOut(char *str,int n)
{
// Пошук початку рядка, що витягується
while(n!=0 )
{
if (!(*str)) n--;
str++;}
//Виведення рядка на екран
cout<<str<<endl ;
}
// Перевірка роботи створених функцій
int main ()
{
int i;
// Масив для запису кількох рядків
char str[12 0];
//Масив для зчитування рядка, який вводиться користувачем.
char s [30];
//Запис рядка в масив
for (i=0 ; i<3; i++)
{
Cout<<"Enter a string: ";
gets(s);
StringIn(str,s,i);
}
StringIn(str,"One more string",3);
// Зчитування рядків з масиву
30
for (i=0 ; i<=3; i++)
StringOut(str, i);
return 0 ;
}
Ідея, реалізована в наведеному програмному коді, досить проста. Полягає вона в тому, що в символьний
масив вноситься поспіль кілька рядків, а в якості роздільників використовуються нуль-символи. Для запису
рядків в масив і вилучення рядків з масиву в програмі визначаються дві функції. Функція StringIn()
використовується для занесення рядків в масив, а функція StringOut() потрібна для вилучення рядків з
масиву.
Розглянемо кожну з цих функцій.
У функції StringIn() три аргументи: масив, в який записується рядок, безпосередньо рядок, який
записується, а також порядковий індекс цього рядка в масиві. Останній аргумент дорівнює кількості рядків в
масиві, які вже туди записані. Цей аргумент оголошений як цілочисельний. Перші два аргументи функції -
вказівники на значення типу char, що відповідає іменам відповідних масивів. Якщо з першим аргументом
такий підхід зрозумілий і виправданий, то інтерпретація рядка, який заноситься в масив, у вигляді
символьного вказівника може викликати здивування. Насправді нічого незвичайного в цьому немає - в C ++
текстові літерали реалізуються у вигляді масиву символів, тому вказівником символьного типу в якості
значення можна привласнювати текстовий літерал. Умовно код функції StringIn() складається з двох
частин: спочатку виконується пошук позиції в масиві (перший аргумент функції str1), починаючи з якої туди
буде записуватися рядок (другий аргумент функції str2). Після того, як місце запису рядка визначено,
виконується посимвольне копіювання рядка в масив. Для визначення позиції рядка в масиві використовується
оператор циклу while(). Перевіряємою умовою (n! = 0) є відмінність від нуля значення змінної n -
третього аргументу функції. Безпосередньо тіло оператора циклу складається з двох команд: умовний
оператор if(!(* Str1)) n-- дозволяє зменшити на одиницю значення змінної n, якщо поточний елемент
масиву strl не є нуль-символом закінчення рядка. Командою str1++ здійснюється перехід до наступного
елементу масиву.
Таким чином, здійснюється перебір елементів масиву, поки не буде знайдений n-й за рахунком символ
закінчення рядка, а завдяки тому, що команда str1++ в операторі циклу розміщена после умовного
оператора, по завершенні оператора циклу вказівник str1 буде посилатися на перший після n-го нуль-
символу елемент. Саме з цього символу необхідно починати запис рядка в масив. Запис здійснюється за
допомогою ще одного оператора циклу:
while(*str2 )
{
*str1=*str2;
Str1++;
str2++;
}
У цьому випадку цикл виконується до тих пір, поки не буде досягнутий кінець рядка, який записується в
масив. Запис посимвольний реалізується командою * str1 = * str2, після чого за допомогою команд
str1++ і str2++ здійснюється перехід до наступного елементу масиву і наступного символу рядка
відповідно. Після виконання оператора циклу рядок записаний в масив, проте його необхідно завершити нуль-
символом, для чого після оператора циклу використана команда * str1 = '\0'.
Програмний код функції StringOut() дещо простіше. У функції два аргументи - символьний масив str
для вилучення рядка і порядковий індекс рядка n (перший за рахунком рядок масиву має нульовий індекс). Як
і в випадку функції StringIn(), в функції StringOut() спочатку виконується пошук позиції рядка в масиві
(код такий же, як і при пошуку позиції для запису рядка в масив в функції StringIn(). Далі за допомогою
команди cout << str цей рядок виводиться на екран. Щоб зрозуміти, чому це відбувається, слід врахувати
дві обставини. По-перше, на момент виконання команди cout << str вказівник str посилається на
перший символ рядка, який витягується (досягається завдяки оператору циклу на початку тіла функції). По-
друге, оператором виведення << символи рядка виводяться до першого нуль-символу, починаючи від позиції,
з якої здійснюється виведення.
У головному методі програми в рамках оператора циклу тричі буде запропоновано ввести користувачу
текстовий рядок. Кожен раз при зчитуванні рядка цей рядок заноситься в масив s. Далі ці рядки послідовно
заносяться в масив str за допомогою команди StringIn(str, s, i), де індексна змінна i пробігає
значення від 0 до 2 включно.
Після оператора циклу, як ілюстрація до того, що другим аргументом функції StringIn() може
вказуватися текстовий рядок, йде команда StringIn(str, "One more string", 3). Ще один
оператор циклу використовується для виведення на екран рядків, що містяться в масиві str. Робиться це за
допомогою виклику функції StringOut(). Результат виконання програми може виглядати, наприклад, так:
31
Enter a string: String number one
Enter a string: String number two
Enter a string: String number three
String number one
String number two
String number three
One more string
Якщо спробувати вивести на екран за допомогою оператора виведення << вміст масиву, в який записано
кілька рядків, на екрані з'явиться тільки перший рядок. Також варто відзначити, що використання при
створенні функцій StringIn() і StringOut() команд виду str1++ стало можливим завдяки тому, що
відповідні аргументи-вказівники на символьні масиви (зокрема, str1) передаються за значенням.

Функції для роботи з рядками і символами


Для роботи з текстовими рядками, реалізованими у вигляді символьних масивів, є цілий ряд вбудованих
функцій. Одна з них уже згадувалася: це функція strlen() для визначення довжини рядка, записаного в
масив. У таблиці перераховані інші корисні в цьому відношенні функції.
Таблиця 8. Функції для роботи стекстовимі рядками

Функція Заголовок Опис


strcpy(si,s2) <cstring> Копіювання рядка s2 в рядок si
strcat(si,s2) <cstring> Рядок s2 приєднується до рядка si
strcmp(si,s2) <cstring> Порівняння рядків si і s2: якщо рядки рівні, повертається значення 0
strchr(s,ch) <cstring> Вказівник на першу позицію символу ch в рядку s
strstr(si,s2) <cstring> Вказівник на першу позицію підрядка s2 в рядку si
atoi(s) <cstdlib> Перетворення рядка з цифр s в ціле число типу int
atol(s) <cstdlib> Перетворення рядка з цифр s в ціле число типу long
atof(s) <cstdlib> Перетворення рядка з цифр s в дійсне число типу double
tolower(ch) <cctype> Перетворення літерного символу ch до рядкового формату
toupper(ch) <cctype> Перетворення літерного символу ch до прописаного формату

Вбудованих функцій C ++ для роботи з текстом і символами набагато більше.


Для виконання функцій необхідно підключити заголовок <cstring>, <cctype> або <cstdlib>, як це
зазначено в таблиці. Наступний приклад містить приклади виконання функцій для перетворення рядків до
числових форматів.
//Функції для роботи з рядками та символами
#include <iostream>
#include <cstdio>
#include <cctype>
using namespace std;
int main()
{
int n;
double x;
char str1[20];
char str2 [20] ;
cout<<"Enter a int-string: ";
gets(str1);
n=atoi str1)/2 ;
cout<< " n = " << n << endl;
cout<<”Enter a double-string: ";
gets(str2 );
x=atof(str2 )+2 ;
cout<< " x = "<<x<<endl;
return 0 ;
}
Приклад виконання програми може виглядати наступним чином:
Enter a int-string: 123
n= 61
Enter a double-string: 23.6
32
х= 25.6

//Операції з рядками
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
int main()
{
char s1 [20],s2 [20];
strcpy(s1,"My name is ") ;
strcpy(s2,"Alex") ;
for(int i=0; s2[i]; s2[i]=toupper(s2[i]),i++);
strcat(s1,s2);
cout<<s1<<endl;
return 0 ;
}
Результатом виконання цієї програми є рядок
My name is ALEX
Спочатку за допомогою функції strcpy() записуються значення в масиви s1 і s2, після чого в рядку s2
всі символи змінюються на прописні (в операторі циклу використана функція toupper()), рядок s2 додається
командою strcat(s1, s2) в кінець рядка s1 , і результат виводиться на екран.

Рядкові літерали

Як уже зазначалося, рядкові літерали в C ++ зберігаються у вигляді символьного масиву, причому на цей
літерал автоматично створюється посилання.
Ця важлива обставина істотно спрощує роботу з літералами текстового типу.
// Робота з літералами
# include <iostream>
using namespace std;
intt main()
{
char *p,*q;
p="Hello, World'";
q="Hello, World!"+7;
cout<<p<<endl;
cout<<q<<endl;
cout<<*p<<endl;
p++;
cout<<*p<<endl;
return 0 ;
}

Результат виконання програми виглядає так:


Hello, World'
World!
H
e

На початку програми оголошуються два вказівника р і q на значення символьного типу. Значення


вказівнику р присвоюється командою р = "Hello, World!", В результаті чого цей вкказівник
посилається на перший символ відповідного литерала - як і в разі символьного масиву. Однак тут є один
важливий виняток: вказівник р можна змінити (команда р++), а ось якщо б р був оголошений як масив
(наприклад, char р[20]), значення р міняти було б не можна. В даному випадку після виконання команди
cout << p на екран виводиться текстовий літерал "Hello, World!". Значенням *р є перший символ Н
зазначеного літерала, після команди р++ інструкція * р є посиланням на другий елемент е літерала.
З вказівником q справи йдуть набагато цікавіше. Значення вказівника визначається командою q =
"Неllо, World!" + 7. Інструкцією "Hello, World!" повертається вказівник на перший елемент
цього літерала в таблиці рядків. Тому обчислення вираза "Hello, World!" + 7 здійснюється відповідно
33
до правил адресної арифметики. Вказівником q в якості значення буде присвоєно адресу 8-го символу (1-й
символ плюс 7 позицій) рядкового літерала, тобто адреса символу W. Виконання команди cout << q
призводить до відображення літерала, починаючи з символу W. В результаті на екрані з'являється
повідомлення World !.

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


Крім одновимірних символьних масивів, з успіхом використовуються і багатовимірні символьні масиви.
Зокрема, двовимірний символьний масив можна розглядати як текстову таблицю або як одновимірний масив,
елементами якого є текстові рядки (реалізовані у вигляді символьних масивів). Багато в чому робота з
двовимірними символьними масивами нагадує роботу з двовимірними масивами інших типів.
// Оголошення та заповнення двовимірного масиву символів
#include <iostream>
#include <cstdio>
using namespace std;
int main()
{
const int n=3;
int i;
char s[n][50];
for(i=0; i<n; i++)
{
cout<<"Enter a string: " ;
gets(s[i]);
}
for(i=0; i<n; i++)
cout << s[i] << endl;
return 0;
}
Оголошується двовимірний масив точно так же, як і масиви інших типів: вказується тип елементів, назва
масиву і розмір по кожному індексу. В даному випадку командою char s [n] [50] (константа const
int n = 3) оголошується масив з трьох елементів, кожен з яких є рядком довжиною до 49 символів. Далі в
рамках оператора циклу робиться запит на введення користувачем текстового рядка, і ці рядки зберігаються як
елементи масиву s. Запис рядків в масив здійснюється командою виду gets(s [i]) (індексна змінна i
приймає значення від 0 до n-1 включно). В даному випадку використано ту обставину, що в двовимірному
масиві s інструкція виду s[i] є посиланням на (i + 1)-ий рядок (елемент з індексом i). На цьому
принципі заснована і процедура виведення рядків-елементів масиву за допомогою команди cout << s[i] в
рамках оператора циклу.
Результат виконання програми може виглядати наступним чином:
Enter a string: First string
Enter a string: Second string
Enter a string: Third string
First string
Second string
Third string
Зрозуміло, доступ до окремого елемента можна отримати традиційним способом - вказавши після імені
масиву в квадратних дужках індекси елементів масиву.
Ініціалізація двовимірного символьного масиву здійснюється так само, як і ініціалізація двовимірних
масивів інших типів.
char s [3][100]={"Hello!","My name is Alex.","What is your name?"};
34
Функції
Приклад функції, що повертає суму двох цілих величин:
#include <iostream.h>
int sum(int a, int b);
//оголошення функції (прототип) можно так int sum(int, int);
int main ( )
{
int a=2, b=3, c, d;
c=sum(a, b); // виклик функції
cin >> d;
cout << sum(c, d); // виклик функції
return 0;
}
#include “sum.cpp”; //включення файла sum.cpp з функцією sum

Опис функції sum знаходиться в файлі sum.cpp і має такий вигляд:


int sum(int a, int b) //Визначення функції
{return (a+b);}

Приклад. Функція без значення, що повертається.


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

# include<iostream.h>
void pr(char); //прототип
void main()
{
char x;
do
{cout<<"\n введіть символ";
cin>>x;
pr(x); //виклик як оператор
}
while(x!= 'C');
}
void pr(char a)
{cout<<"\n введений символ:" <<a;}

Масиви як параметри функцій

Одновимірні масиви

Масиви можуть бути параметрами функцій і функції в якості результату можуть повертати вказівник
на масив. При використанні масивів як параметрів функції виникає необхідність визначення в тілі функції
кількості елементів масиву, який є аргументом при зверненні до функції.
При роботі з рядками, тобто з масивами типу char[], проблема вирішується просто, оскільки останній
елемент рядка має значення '\0'. Тому при обробці масиву-аргументу кожен його елемент аналізується на
наявність символу кінця рядка.

Приклад 1. Рядки як параметри функцій.


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

#include <iostream.h>
#include <conio.h>
int dl(char[]); // прототип функції dl
void main()
{
clrscr();
char c[]="kafedra KNIT";
cout << "\n kol-vo simvolov:" << dl(c);
35
getch();
}

int dl(char c[])


{
int i;
for (i=0; ;i++)
if (c[i]=='\0') break;
return i;
}
В результаті виконання програми на екран буде видано повідомлення:
kol-vo simvolov: 12.

Якщо масив-параметр функції не є символьним рядком, то необхідно або використовувати масиви з


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

Приклад 2. Одновимірний масив як параметр функції.


Нехай потрібно скласти програму, в якій функція обчислює суму елементів масиву, що складається з 5
елементів.

// Одновимірний масив як параметр


#include <iostream.h>
float sum(float x[5])
{
float s=0;
for (int i=0; i<5; i++)
s=s+x[i];
return s;
}

void main()
{
float z[5],y;
for (int i=0; i<5; i++)
{
cout <<"\nВведіть поточний елемент масива:";
cin >> z[i];
}
y=sum(z) ;
cout <<"\n Сума елементів масива:"<< y;
}

В результаті виконання програми на екран буде виведено значення суми елементів масиву z.
В даному прикладі відсутній прототип функції sum(), оскільки опис функції sum() компілятор аналізує
раніше, ніж звернення до неї. У наведеному прикладі заздалегідь відомо число елементів - 5, тому в функції
всього один параметр - масив х. Оскільки функція повертає значення, то виклик функції може бути тільки
виразом або частиною виразу. У програмі оператор присвоювання y = sum(z); містить такий вираз в
правій частині. Тут аргументом функції є масив z.

Приклад 3. Одновимірний масив з довільним числом елементів в якості параметра функції.


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

// Пошук максимального елемента


#include <iostream.h>

float max(int n, float a[])


{
float m=a[0];
for (int i=1;i<n;i++)
if (m<a[i]) m=a[i];
36
return m;
}

void main()
{
float z[6];
for (int i=0;i<6;i++)
{
cout <<"\nВведіть поточний елемент масива:";
cin >> z[i];
}
cout <<"\nМаксимальний елемент масива:"<< max(6,z);
}

В результаті виконання програми на екран монітора буде видано повідомлення із значенням


максимального елемента масиву z, який в даному прикладі складається з шести елементів.
У наведеній програмі прототип відсутній, оскільки опис функції йде раніше її виклику. У функції mах()
використовується два параметри: перший вказує число елементів в масиві, а другий - ім'я і розмірність масиву.
Аргументи вказані при виклику функції max(6, z). Тут 6 - розмір масиву, a z - ім'я масиву.
Ім'я масиву є константним вказівником адреси нульового елемента масиву. Тому, ім'я масиву є сталою
покажчиком адреси елемента масиву з нульовим індексом. Звідси випливає, що при використанні імені масиву
в якості параметра функції допускається змінювати значення елементів масиву.

Приклад 4. Масив як параметр функції.


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

#include <iostream.h>
void der(int, float[]); // прототип функції
void main() {
const int k=10;
float a[k];
int i;
for (i=0; i<k; i++)
{
cout << "\nВведіть елемент масива ";
cin >> a[i];
}
der(k,a); // виклик функції
for (i=0; i<k; i++)
cout << "\ta[" << i << "]=" << a[i];
}
void der (int k1, float a1[])
{
int j;
for (j=0; j<k1;j++)
a1[j]--;
}

Приклад 5. Масиви з довільним числом елементів як параметри функції.


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

// Вказівники на масив як параметри


#include <iostream.h>
#include <conio.h>

void maxl(int,int*,int*,int*); //прототип

void main()
{
37
clrscr();
int a[]={0,1,2,3,4};
int b[]={5,6,0,7,1};
int d[5];
maxl (5,a,b,d) ;
cout << "\n";
for (int i=0;i<5;i++)
cout << "\t" << d[i] ;
getch();
}

void maxl(int n, int *x, int *y, int *z)


{
for (int i=0;i<n;i++)
z[i]=x[i]>y[i] ? x[i] :y[i];
}

В результаті виконання програми на екран монітора будуть видані елементи результуючого масиву d:
5 6 2 7 4.
У наведеній програмі у функції є чотири параметри: число n елементів в масивах і три вказівника х, у,
z. При виконанні функції max1(5, a, b, d) (за допомогою оператора, оскільки відсутнє значення, що
повертається) в якості аргументів використовуються імена вихідних масивів а й b і результуючого d.

Багатовимірні масиви
Особливістю мови C ++ є несамовиизначенність масивів, тобто за іменем масиву неможливо дізнатися його
розмірність і розміри по кожному виміру. Крім того, в C ++ багатовимірні масиви не визначені. Наприклад,
якщо оголошений масив float d [3] [4] [5], то це не тривимірний, а одновимірний масив d, що
включає три елементи, кожен з яких має тип float [4] [5]. У свою чергу, кожен з чотирьох елементів
типу float [5]. І, відповідно, кожен з цих елементів є масивом з п'яти елементів типу float. Ці
особливості ускладнюють використання масивів як параметрів функцій.
При передачі масивів як параметрів через заголовок функції слід враховувати, що передавати масиви
можна тільки з однієї невизначеною розмірністю (ця розмірність повинна бути найлівішій).

Приклад 6. Двовимірний масив як параметр функції.


Нехай потрібно скласти програму з функцією, яка підраховує суму елементів матриці.

#include <iostream.h>
float summa(int n,float a[][3])
{
float s=0;
for (int i=0;i<n;i++)
for (int j=0;j<3;j++)
s=s+a[i][j];
return s;
}

void main()
{
float z[4][3] = {0,1,2,3,4,5,6,7,7,6,5,4};
cout << "\n Сума елементів матриці дорівнює " << summa(4,z);
}

В результаті виконання програми на екран буде виведено повідомлення:


Сума елементів матриці дорівнює 50.
У наведеній програмі параметром функції summa() є матриця a [] [3], у якої не визначено число
рядків. Число стовпців має бути заздалегідь визначено, воно дорівнює 3. Таким чином, при використанні
даної функції вводиться істотне обмеження - фіксоване число стовпців. Зазначене обмеження можна обійти за
допомогою використання допоміжних масивів вказівників на масиви.

Приклад 7. Допоміжний масив вказівниікв на масив як параметр функції.


38
Потрібно скласти програму з функцією, яка повертає в якості результату мінімальний елемент матриці d
розміром mxn.

#include <iostream.h>

float min(int m,int n,float *p[]); //прототип

void main()
{
float d[3][4]={1,2,-2,4,5,0,-3,18,-9,6,7,9};
float *r[]={(float*)&d[0], (float*)&d[1], (float*)&d[2]};
int m=3;
int n=4;
cout<<"\n Мінімальний елемент матриці дорівнює "<<min(m,n,r);
}

float min(int m,int n,float *p[])


{
float x=p[0][0];
for (int i=0;i<m;i++)
for (int j=0;j<n;j++)
if (x>p[i][j]) x=p[i][j];
return x;
}
В результаті виконання програми на екран буде виведено повідомлення:
Мінімальний елемент матриці дорівнює -9.
У функції min() в якості параметрів використовуються int m - число рядків, int n - число стовпців і
float * p[] - масив вказівників на одновимірні масиви елементів типу float, причому розміри
двовимірного масиву заздалегідь невідомі.
У тілі функції звернення до елементів матриці здійснюється за допомогою подвійного індексування. Тут
p[i][j] - значення елемента двовимірного масиву.
У функції main() оголошена і ініціалізована матриця d[3][4], що має фіксовані розміри. Такий розмір
не можна використовувати безпосередньо як аргумент функції, тому оголошено додатковий допоміжний
масив вказівників float * r[]. Як значення елементів цього масиву присвоюються адреси перших
елементів рядків матриці, тобто &D[0], &d[1], &d[2], перетворені до типу float * (вказівник на тип
flоаt).

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

Приклад 8. Використання одновимірного динамічного масиву. Знаходження суми елементів масиву.


#include <iostream.h>
int sum(const int *mas, const int n); //прототип
int const n=10;
int main()
{
int marks[n] = {3, 4, 5, 4, 4};
cout << "Сума елементів масива: " << sum(marks, n);
return 0;
}

int sum(const int *mas, const int n)


// варианты: int sum(int mas[], int n)
// или int sum(int mas[n], int n)
// (величина n повинна бути константою)
{
int s = 0;
for (int i=0; i<n; i++)
39
s += mas[i];
return s;
}

Приклад 9. Динамічні масиви як параметри функцій.


Потрібно скласти програму з використанням функції, яка заповнює елементи матриці розміром mхn
послідовно значеннями цілих чисел: 0, 1,2, 3, .... Для формування матриці в основній програмі використані
динамічні масиви, так як розміри матриці заздалегідь не відомі.

// Матриця як набір одновимірних динамічних масивів


#include <iostream.h>
void fun(int m, int n, int **uc)
{
int k=0;
for (int i=0;i<m;i++)
for (int j=0;j<n;j++)
uc[i][j]=k++;
}

void main()
{
int **pi; // вказівник на масив вказівників
int m1; // кількість рядків в матриці
cout << "\n Введіть кількість рядків матриці:";
cin >> m1;
int n1; // кількість стовпців в матриці
cout << "\n Введіть кількість стовпців матриці:";
cin >> n1;
int i,j;
// виділення допоміжного масива вказівників
pi=new int* [m1];
for (i=0;i<m1;i++)
// формування i-й рядків матриці
pi[i]=new int [n1];
fun(m1,n1,pi); // виклик функції
for (i=0;i<m1;i++) // цикл перебора рядків
{
cout << "\n рядків " << i+1 << ":";
for (j=0;j<n1;j++)
cout << "\t" << pi[i][j];
}
//
for (i=0;i<m1;i++)
delete []pi[i];
delete []pi;
}

Якщо при введенні задати розмірність матриці 3x4, то в результаті виконання програми на екран буде
видано три наступні рядки:
рядок 1: 0 1 2 3
рядок 2: 4 5 6 7
рядок 3: 8 9 10 11

У функції fun() є три наступні параметри: int m - число рядків, int n - число стовпців матриці і int
**pi - вказівнік на масив вказівників.
У функції main для визначеності значення числа рядків m1 задано 3, а число стовпців матриці n1 задано 4.
При виконанні оператора присвоювання pi = new int * [m1]; операцією new виділяється пам'ять для
допоміжного масиву вказівників. В результаті виконання цієї операции для масиву вказівників виділяється
необхідна кількість пам'яті, а адреса першого елемента виділеної пам'яті присвоюється вказівнику pi. При
виконанні в тілі циклу другої операції new формуються m1 рядків матриці.
40
При зверненні до функції відбувається прісвоєння елементам матриці послідовно значень цілих чисел від 0
до 11. Наприкінці програми за допомогою delete виділена пам'ять звільняється.

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

Приклад 10. Формування багатовимірного динамічного масиву в функції.


Нехай потрібно скласти програму з функцією, яка формує квадратну матрицю порядку n з одиничними
діагональними елементами і іншими нульовими.
// Одинична діагональна матриця порядку n
#include <iostream.h>
#include <process.h> //для exit ()
// функція формування матриці
int **matr(int n)
{
int **p; //допоміжний вказівник
p=new int* [n]; //масив вказівників на рядки
if (p==NULL)
{
cout << "\n He створений динамічний масив!!";
exit(1);
}
//цикл створення рядків (зовнішній)
for (int i=0;i<n;i++)
{
p[i]=new int [n]; //виділення пам’яті для i-го рядка
if (p[i]==NULL)
{
cout << "\n He створений динамічний масив!";
exit(1);
}

//цикл заповнення рядків (вкладений)


for (int j=0;j<n;j++)
if (j!=i) p[i][j]=0;
else p[i][j]=1;
}
return p;
}

void main()
{
int n1; //порядок матриці
cout << "\n Введіть порядок матриці:";
cin >> n1;
int **ma; //вказівник для формування матриці
ma=matr(n1); //виклик функції
//друк елементів матриці
for (int i=0;i<n1;i++)
{
cout << "\n рядок " << i+1 << ":";
for (int j=0;j<n1;j++)
cout << "\t" << ma[i][j];
}
//звільнення пам’яті
for (i=0;i<n1;i++)
delete []ma[i]; //видаляємо вміст рядків
delete []ma; //видаляемо масив вказівників на рядки
}
41
При виконанні програми на екрані з'явиться підказка:
Введіть порядок матриці:
Якщо у відповідь на підказку користувач введе з клавіатури порядок матриці, що дорівнює 5, то на екрані
дисплея він отримає повідомлення з п'яти рядків виду:
рядок 1: 1 0 0 0 0
рядок 2: 0 1 0 0 0
рядок 3: 0 0 1 0 0
рядок 4: 0 0 0 1 0
рядок 5: 0 0 0 0 1

У даній програмі функція matr() має один параметр int n - порядок матриці. У тілі функції формується
квадратна матриця розміром nxn (створюються n одновимірних масивів з елементами типу int і масив
вказівників на ці одновимірні масиви) і її елементи заповнюються необхідними значеннями. Значенням
функції matr() є значення вказівника на сформовану матрицю.

Приклад 11. Знаходження суми елементів двох двовимірних масивів.


Усередині функції масив інтерпретується як одновимірний, а його індекс перераховується в програмі.
Розмірність масиву b відома на етапі компіляції, під масив а пам'ять виділяється динамічно (програма
написана в стилі С).

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <malloc.h>

int sum(const int *a, const int nstr, const int nstb);

int main()
{
clrscr();
int b[2][2]= {{2, 2},{4,3}};
printf ("Cума елементів b: %d\n", sum(&b[0][0], 2, 2));
// ім'я масиву передавати в sum не можна через невідповідність типів
int i,j, nstr, nstb, *a;
printf ("Введіть кількість рядків і стовпців: \n");
scanf("%d%d", &nstr, &nstb);
a=(int *)malloc(nstr* nstb* sizeof(int)); //1
printf ("Введіть елементи масива: \n");
for (i=0; i<nstr; i++)
for (j=0; j<nstb; j++)
scanf("%d", &a[i * nstb + j]); //2
printf("Сума елементів a: %d\n", sum(a, nstr, nstb));
free(a);
return 0;
}

int sum(const int *a, const int nstr, const int nstb)
{
int i,j,s=0;
for (i=0; i<nstr; i++)
for (j = 0; j<nstb; j++)
s += a[I * nstb + j];
return s;
}
Пам'ять виділяється відразу під всі елементи масиву (оператор 1), тобто, по суті двовимірний динамічний
масив займає суцільну ділянку пам'яті. При заповненні масиву використовується формула для визначення
індексу відповідного елемента (2). В кінці роботи програми масив видаляється.

Можна вирішити цю задачу по-іншому виділяючи пам'ять (програма написана в стилі С++).

#include <iostream.h>
42

int sum(int **a, const int nstr, const int nstb);

int main()
{
int nstr, nstb;
cout<<" Введіть кількість рядків і стовпців: \n";
cin >> nstr >> nstb;
int i,j, **a;
a=new int *[nstr];
for (i=0; i<nstr; i++)
a[i]=new int [nstb];
cout<<" Введіть елементи масива: \n";
for (i=0; i<nstr; i++)
for (j=0; j<nstb; j++)
cin>>a[i][j];
cout<<" Сума елементів a:"<<sum(a, nstr, nstb);
//звільнення пам’яті
for (i=0;i<nstr;i++)
delete []ma[i]; //видаляємо вміст рядків
delete []ma; //видаляємо масив вказівників на рядки
return 0;
}

int sum(int **a, const int nstr, const int nstb)


{
int i,j,s=0;
for (i=0; i<nstr; i++)
for (j = 0; j<nstb; j++)
s += a[i][j];
return s;
}

У цьому випадку пам'ять спочатку виділяється під стовпець вказівників на рядки матриці, а потім в циклі
під кожен рядок. Звільнення пам'яті повинно виконуватися у зворотному порядку.

Передача імен функцій в якості параметрів


// Функція піднесення до квадрата:
double sqr (double x) {return x * x;}
// Функція піднесення до кубу:
double cube (double x) {return x * x * x;}
// Функція з другим аргументом-вказівником на функцію:
void myfunc(double x,double (*f)(double))
{cout<<f(x) <<endl;}
int main()
{
double z;
double (*p)(double); //Вказівник на функцію:
cout << ”z = ”;
cin>>z;
p=cube; //Вказівнику присвоюється значення, можно p=&cube;
myfunc(z,sqr); //використання імені функції
myfunc(z, p); // використання вказівника
cout << p(z) << endl;
//Адреса функції:
cout<<sqr << endl;
cout << cube << endl;
cout << p << endl;
return 0;
}
43
У програмі оголошуються три функції. У функції sqr() аргумент типу doublе, результатом є квадрат
аргументу - число типу doublе. Функцією cube() в якості значення повертається значення типу double -
куб аргументу, який також має тип double. У функції myfunc() два аргументи. Перший аргумент має тип
double, а другий аргумент є вказівником на функцію. Він оголошений як double(* f)(double). В
даному випадку f - формальна назва аргументу. Оператор * означає, що це вказівник. Ключове слово
double в круглих дужках - тип аргументу функції, a double перед ім'ям вказівника - тип результату функції.
Таким чином, функції myfunc() першим аргументом передається число, а другим - ім'я функції.
В результаті виклику функції відображається значення f(z), тобто значення функції, ім'я якої
зазначено другим аргументом, від аргументу - першого параметра функції myfunc().
У головному методі програми командою double(* р)(double) оголошується вказівнік на
функцію. Значення вказівнику присвоюється командою p = cube. После цього вказівнік р посилається на
функцію cube(). Далі цей вказівнік і імена функцій використовуються в командах myfunc(z, sqr)
(квадрат числа z), myfunc(z, p) (куб числа z) і р(z) (куб, числа z). В кінці програми різними способами
відображаються адреси функцій із використанням вказівника р і імен функцій. Результат виконання програми
може мати вигляд:
z = 5
25
125
125
00401195
0040128F
0040128F
Для того щоб зробити програму такою, що легко читається, при описі вказівників на функції
використовують перейменування типів (typedef). Можна оголошувати масиви вказівників на функції (це
може бути корисно, наприклад, при реалізації меню):
// Опис типу PF як вказівнік на функцію
// з одним параметром типу int:
typedef void (* PF) (int);
// Опис і ініціалізація масиву вказівників:
PF menu [] = {& new, & open, & save};
menu [1] (10); // Виклик функції open
Тут new, open і save - імена функцій, які повинні бути оголошені раніше.

Функції зі змінним числом параметрів


Приклад 1. Завдання числа додаткових параметрів функції за допомогою одного з параметрів.
Нехай потрібно скласти програму, яка містить функцію зі змінним числом параметрів. Функція
повинна обчислювати суму значень додаткових параметрів. Список явних параметрів повинен складатися з
одного параметра, що використовується для завдання числа додаткових параметрів.
/ * Функції зі змінним числом параметрів * /
#include <iostream.h>
int sum (int, ...); // прототип функції
void main () {
cout << "\n 4+6=" << sum(2,4,6);
cout << "\n 1+2+3+4+5+6=" << sum(6,1,2,3,4,5,6);
cout << "\n Параметри відсутні. Сума дорівнює:" << sum(0);
}
int sum(int n, ...) // n - число додаваємих параметрів
{
int *p=&n; // вихід на початок списка додаткових параметрів
int s=0;
for (int i=l; i<=n; i++)
{
if (n==0) break;
s+=*(++p);
}
return s;
}
Результат виконання програми:
4+6=10
1+2+3+4+5+6=21
44
Параметри відсутні. Сума дорівнює:0
У наведеному прикладі для організації аналізу аргументів використовується значення першого параметра.
Для доступу до нього використовується вказівнік р. Йому присвоюється значення адреси першого аргументу,
який відповідає явно заданому параметру n. Таким чином вказівник встановлюється на початок списку
аргументів в пам'яті. Потім в циклі вказівник р переміщається за адресами таких аргументів, відповідним
додатковим параметрам. За допомогою операції розйменування * р виконується вибірка і підсумовування
значень аргументів.
Зауваження.
Особливість розглянутого підходу полягає в тому, що всі параметри (явний і додаткові) повинні мати
однаковий тип int або double.
Приклад 2. Визначення кінця списку додаткових параметрів функції за допомогою параметра-індикатора.
Нехай потрібно скласти програму, яка містить функцію зі змінним числом параметрів. Функція повинна
обчислювати добуток значень додаткових параметрів. Для визначення кінця списку параметрів
використовується параметр-індикатор.

#include <iostream.h>
double pr(double a, ...)
{
double b=1.0; // b - добуток
double *p=&a;
if(*p==0.0) return 0.0;
for(; *p; p++) b* =*p;
return b;
}
void main()
{
cout << "\n pr(6e0,5e0,3e0,0e0)=" << pr(6e0,5e0,3e0,0e0);
cout << "\n pr(l.5,2.0,0.0,3.0,0.0)=" << pr(1.5,2.0,0.0,3.0,0.0);
cout << "\n pr(0.0)=" << pr(0.0);
}
Результат виконання програми:
pr(6е0,5е0,3е0,0е0)=90
pr(1.5,2.0,0.0,3.0,0.0)=3
pr(0.0)=0
У функції рr() переміщення по списку аргументів здійснюється за рахунок зміни значення вказівника р.
При другому зверненні до функції в середині списку аргументів знаходиться аргумент з нульовим значенням.
Він сприйнмається функцією як індикатор, який сигналізує про закінчення списку аргументів. У наведеній
програмі, так само як і в першому прикладі, аргументи повинні мати один тип, але не обов'язково int або
double.
Для використання в програмі функцій зі змінним числом параметрів, при виклику яких можна
використовувати аргументи різних типів, необхідно включати в текст програми спеціальний набір
макроозначень. Ці макровизначення знаходяться в заголовку stdarg.h.

Функція main()
Функція, якій передається управління після запуску програми, повинна мати ім'я main. Вона може
повертати значення в систему і приймати параметри з зовнішнього оточення. Значення, що повертається
повинно бути цілого типу. Стандарт передбачає два формати функції:
// без параметрів:
тип main () {/ * - * /}
// з двома параметрами:
тип main (int argc, char * argv []) {/ * - * /}
При запуску програми параметри розділяються пробілами. Імена параметрів в програмі можуть бути будь-
якими, але прийнято використовувати argc u argv. Перший параметр (argc) визначить кількість
параметрів, переданих функції, включаючи ім'я самої програми, другий параметр (argv) є вказівником на
масив вказівників типу char *. Кожен елемент масиву містить вказівник на окремий параметр командного
рядка, що зберігається в вигляді С-рядка, що закінчується нуль-символом. Перший елемент масиву
(argv[0]) посилається на повне ім'я файлу запускається на виконання, наступний (argv [l]) вказує на
перший параметр, argv [2] - на другий параметр, і так далі. Параметр argv [argc] має дорівнювати 0.
// програма виводить на екран всі передані в командний рядок
// параметри функції
45
#include <iostream.h>
void main (argc, char* argv[]) {
for (int i = 0; i<argc; i++)
cout << argv[i] << '\n';
}
Перший цілочисельний argc аргумент особливих питань не викликає. Другий аргумент, оголошений
як char * argv [] - масив, елементами якого є текстові рядки, реалізовані у вигляді символьних масивів.
Індексна змінна i пробігає значення відповідно до розміру масиву (від 0 до size-1 включно). Для кожного
значення цього індексу на екран виводиться відповідний параметр командного рядка, для чого
використовується посилання argv [i].
Якщо функція main() нічого не повертає, викликаюча система отримає значення, що означає успішне
завершення. Ненульове значення означає аварійне завершення. Оператор повернення з main() можна
опускати. Якщо параметри в командний рядок передавати не планується, аргументи для методу main() можна
не вказувати (а можна вказувати).

Значення, що повертається
Механізм повернення з функції у функцію, яка її викликала, реалізується оператором
return [вираз];
Значення виразу, якщо воно задано, повертається в викликаючу функцію в якості значення, що
викликається. Якщо вираз опущений, то значення, яке повертається, не визначене. Вираз може бути
укладено в (). Функція може містити кілька операторів return (це визначається потребами алгоритму).
Вираз, вказаний після return, неявно перетвориться до типу значення, що повертається функцією і
передається в точку виклику функції. Якщо в функції відсутній оператор return, то передача управління в
викликаючу функцію відбувається після останнього оператора функції, що викликається. При цьому значення,
що повертається не визначене.
Приклади:
int f1 () {return 1;} // правильно

void f2 () {return 1;} // неправильно f2 не повинна повертати значення

double f3 () {return 1;} // правильно 1 перетвориться до типу double

int max (int a, int b) {return (a> b? a: b); }

Повернення функцією вказівника


Функція як значення може повертати вказівник. Головне, що для цього потрібно зробити, -
передбачити відповідні інструкції в програмному коді функції. У прототипі функції, яка повертає в якості
значення вказівник, перед ім'ям функції вказують оператор *.
// функція, що повертає вказівнік
int *mpoint(int &n,int &m)
{
if(n>m) return &n; //return(n>m?n:m);
else return &m;
}
int main ()
{int n=3,m=5;
int *p;
p=mpoint(n,m);
(*p) ++;
cout << “n = " << n << endl;
cout << "m =” << m << endl;
return 0;
}

У програмі оголошується функція mpoint(), яка повертає в якості результату вказівник на


максимальний з двох її аргументів. Відразу відзначимо, що обидва аргументи передаються по посиланню -
якщо б вони передавалися за значенням, не було б сенсу повертати вказівник на один з аргументів, оскільки в
цьому випадку вказівник посилався б на тимчасову змінну-копію аргументу.
46
При поверненні в якості значення вказівника не слід забувати, що повертається адреса більшого з
аргументів, а не сам аргумент. Адресу змінної (аргументу) отримуємо, вказавши перед ім'ям відповідної
змінної оператор &, що і було зроблено в командах return & n і return & m.
У головному методі програми оголошуються дві цілочислові змінні n і m зі значеннями 3 і 5
відповідно. Саме вони передаються аргументами функції mpoint(). Результат записується в змінну-вказівник
р. В результаті змінна р в якості значення отримує адресу змінної m. Тому після виконання команди (*р)++
значення змінної m збільшується на одиницю. Результатом виконання програми стане пара рядків
n = 3
m = 6
В кінцевому рахунку вказівник - це всього лише змінна, значенням якої є адреса пам'яті. Немає нічого
дивного в тому, що адреса повертається функцією.
Увага
Не можна повертати з функції вказівнік на локальну змінну, оскільки пам'ять виділена для локальних
змінних при вході в функцію, звільняється після повернення з неї.
Приклад:
int* f()
{
int a = 5;
return &a; //не можна!
}

Повернення функцією посилання


У C ++ функції можуть повертати в якості результату посилання на значення. Для того щоб функція
повертала посилання на значення, перед ім'ям функції в її прототипі (і при описі) необхідно використовувати
оператор &.
// Функція, результатом якої є посилання
#include <iostream>
using namespace std;
int &mpoint ( int &n , int &m)
{
if(n>m) return n;
else return m;
}
int main ()
{
int n=3,m=5;
int р;
mpoint(n,m)=2 ;
p=mpoint(n, m) ;
cout << "n="<<n<<endl;
cout << "m="<<m<<endl;
cout << "p="<<p<<endl;
return 0;
}
Наведений програмний код є модифікацією попереднього прикладу, тільки в даному випадку
функцією mpoint() повертається не вказівнік, а посилання на більший зі своїх аргументів. При оголошенні
функції перед назвою зазначений оператор & (ознака того, що функцією повертається посилання). Як
значення, як зазначалося, повертається більший з аргументів n і m (інструкції return n і return m в
умовному операторі). Однак в силу того, що функція оголошена як посилання, насправді повертається не
значення відповідної змінної, а посилання на неї! Іронія цього твердження легко підтвердити за допомогою
коду, представленого в головному методі програм.
В методі main(), як і в попередньому прикладі, ініціалізуються дві цілочислові змінні n = 3 і m = 5,
а також оголошується цілочислова змінна р. Далі слід на перший погляд досить дивна команда
mpoint(n,m)=2, після чого командою p = mpoint(n, m) присвоюється значення змінній р. Але
найцікавіше - це результат, який отримуємо при виконанні цієї програми:
n =3
m =2
Р =3
47
Принаймні,, дивно виглядають значення змінних n і p. Зрозуміло, це не зовсім так, а точніше, зовсім не
так. Почнемо з команди mpoint(n, m) = 2. Щоб зрозуміти сенс цієї команди, згадаємо, що результатом
інструкції mpoint(n, m) є посилання на більший з аргументів - при поточних значеннях аргументів n і m це
посилання на змінну m. Тому команда mpoint(n, m) = 2 еквівалентна присвоюванню змінній m значення
2. Далі, командою p = mpoint(n, m) змінній р присвоюється значення максимального з аргументів n і m -
тепер це змінна n (оскільки на момент виклику зазначеної команди значення n дорівнює 3, а значення m
дорівнює 2). Таким чином, змінна n залишається зі значенням 3, таке ж значення має змінна р, а значення
змінної m дорівнює 2. Відзначимо також, що аргументи функції mpoint() передаються через посилання з тих
же причин, що і в попередньому прикладі.

Отримання кількох результатів


У багатьох практичних завданнях відповідних значень буває кілька. Оскільки ім'я функції є носієм
одного результуючого значення, для вирішення поставленого завдання безпосередньо його використовувати
не можна. Розглянемо, яким чином в таких випадках описуються і викликаються функції.
Якщо аргументи функції передаються за значенням, прямим шляхом значення відповідних змінних в
функції змінювати не можна (щоб повернути відповідні значення в якості результатів обчислення). Однак
відповідної мети можна досягти, якщо в якості параметрів і аргументів функції використовувати адреси
змінних, а не їх значення.
Приклад 1. Отримання кількох результатів функцією.
Нехай потрібно скласти програму, яка містить звернення до функції, яка обмінює змінні їх значеннями.
Можливий варіант рішення має вигляд:
void change(int*, int *); // Прототип функції
void main()
{int a,b;
cout << "\nВведіть а та b";
cin >> a;
cin >> b;
change(&a, &b);
cout << "\nНові значення: а=" << a << "\tb=" << b);
}
void change(int *x, int *y) //заголовок функції
{int z; // x та у — вказівники,
z=*x; *x=*y; *y=z; //*x та *у — значення, на які вони вказують
}
У наведеній програмі вказівники &a і &b використовуються як аргументи функції change(). При
зверненні до функції значення адрес-аргументів передаються за значенням вказівникам-параметрам х і у. При
виконанні функції значення, які адресуються вказівниками, міняються місцями і управління передається назад
в точку виклику функції.
Також можна передавати параметри через посилання:
void change (int & x, int & y) // заголовок функції
{ int z; // x і у змінні, передані по посиланню,
z = x; x = y; y = z; //
}
Виклик функції:
change(a, b);

Рекурсивні функції
Для реалізації рекурсивних алгоритмів в C ++ передбачена можливість створення рекурсивних функцій.
Рекурсивна функція являє собою функцію, в тілі якої здійснюється виклик цієї ж функції.
Приклад 1. Використання рекурсивної функції для обчислення факторіала.
Нехай потрібно скласти програму обчислення факторіала довільного позитивного числа.
#include <iostream.h>
int fact(int n)
{
int a;
if (n<0) return 0;
if (n==0) return 1;
a =n * fact(n-l);
return a;
}
48
void main()
{
int m;
cout << "\nВведіть ціле число:";
cin >> m;
cout << "\n Фактoріал числа " << m << " дорівнює " << fact(m);
}

Для негативного аргументу факторіала не існує, тому функція в цьому випадку повертає нульове значення.
Так як факторіал 0 дорівнює 1 за визначенням, то в тілі функції передбачений і цей варіант. У разі коли
аргумент функції fact() різниться від 0 і 1, то викликається функція fact() зі зменшеним на одиницю
значенням параметра і результат множиться на значення поточного параметра. Таким чином, в результаті
вбудованих викликів функцій буде повернуто наступний результат:
n *(n-1) *(n-2) *. . . * 2 * 1 * 1
Остання одиниця при формуванні результату належить виклику fact(0).
Оскільки в мові C ++ відсутня операція піднесення до степеня, то для виконання піднесення до степеня
дійсного ненульового числа можна використовувати рекурсивну функцію.
Приклад 2. Використання рекурсивної функції для піднесення до степеня.
Нехай потрібно скласти програму піднесення до позитивного цілого степеня дійсного ненульового числа.
/ * Піднесення основи х до степеня n * /
#include <iostream.h>
float st (float x, int n)
{
if (n==0) return 1;
if (x==0) return 0;
if (n>0) return x*st(x,n-l);
if (n<0) return st(x,n+l)/x;
}
void main()
{
int m;
float a,y;
cout << "\nВведіть основу степеня:";
cin >> a;
cout << "\nВведіть степень числа:";
cin >> m;
y=st(a,m);
cout << "\n Число " << a << " в степені " << m << " дорівнює:" << у;
}
У функції передбачені чотири гілки виконання піднесення до степеня дійсного числа:
• степінь числа дорівнює нулю;
• основа дорівнює нулю;
• степінь більше нуля;
• ступінь менше нуля.
При виконанні функції, коли основа степеня більше нуля, наприклад, якщо при введенні задані значення
змінних а = 2.0 і m = 4, тоді виклик st(a, m) призводить до наступного обчислення:
2.0 * 2.0 * 2.0 * 2.0 * 1
Виклик функції для негативного степеня, наприклад, коли значення аргументів рівні а = 3.0 і m = -2
призводить до обчислення наступного виразу:
1 / 3.0 / 3.0

Перевантаження функцій.
// Перший варіант функції:
void showArgs (double x) {cout << "Double-number" << x << endl;}
// Другий варіант функції:
void showArgs (double x, double y)
{cout << "Double-numbers" << x << "and" << y << endl;}
// Третій варіант функції:
void showArgs (char s)
{cout << "Symbol" << s << endl;}
// Четвертий варіант функції:
49
int showArgs (int n)
{return n;}
int main ()
{int n = 3;
double x = 2.5, y = 5.1;
char s = 'w';
// Перший варіант функції:
showArgs (x);
// Другий варіант функції:
showArgs (х, у);
// Третій варіант функції:
showArgs (s);
// Четвертий варіант функції:
cout << "Int-number" << showArgs (n) << endl;
}
У програмі описано чотири варіанти функції showArgs(), призначення якої полягає у виведенні на екран
інформації про її аргументи. Однак в залежності від кількості та типу аргументів виконуються трохи різні дії.
Зокрема, передбачені такі варіанти передачі аргументів:
1. Один аргумент типу double
2. Два аргументи типу double
3. Один аргумент типу char
4. Один аргумент типу int
У перших трьох випадках результат функцією не повертається (тип функції void), а на екран виводиться
повідомлення про тип і значення аргументу (або аргументів). У четвертому випадку функцією в якості
значення повертається аргумент.
У головному методі програми функція showArgs() викликається в різному контексті: виклик
showArgs(х) відповідає варіанту функції для одного аргументу типу double, виклик showArgs(х, у)
відповідає варіанту функції для двох аргументів типу double, виклик showArgs(s) відповідає варіанту
функції для одного аргументу типу char, виклик cout<<"Int-number"<<showArgs(n)<<endl
відповідає варіанту функції з одним аргументом типу int. Результат виконання програми буде наступним:
Double-number 2.5
Double-numbers 2.5 and 5.1
Symbol w
Int-number 3
При роботі з перевантаженими функціями не слід забувати про автоматичне приведення типів. Ці два
механізми в симбіозі можуть давати дуже цікаві, а іноді і дивні результати. Розглянемо приклад.
// Автоматичне приведення без перевантаження
void ndiv(double x, double y)
{cout<<"x/y= " << x/y << endl; }
int main ()
{ double x=l0 ,y=3;
int n=10,m=3;
ndiv(x,y);
ndiv(n,m);
}
Результат виконання такої програми буде наступним:
х/у= 3.33333
х/у= 3.33333
Перший рядок, який є результатом виконання команди ndiv(х, у), думається, питань не викликає.
Результатом виклику функції є частка двох чисел - аргументів функції. Хоча в команді ndiv(n, m)
аргументами функції вказані цілі числа, завдяки автоматичному приведення типів цілочіслові значення
розширюються до типу double, після чого обчислюється потрібний результат.
Однак варто змінити програмний код, перевантаживши функцію ndiv(), і результат зміниться
кардинально.
// Перевантаження і автоматичне приведення типів
void ndiv(double x, double y)
{ cout<<"x/y= "<<x/y<<endl;}
void ndiv(int n, int m)
{ cout<<"n/m= "<<n/m<<endl;}
int main()
50
{ double x=10,y=3;
int n=10,m=3;
ndiv(x,y);
ndiv(n,m);
}
Зокрема, на екрані в результаті виконання програми з'явиться повідомлення:
х/у= 3.33333
n/m= 3
Справа в тому, що змінений програмний код містить перевантажену функцію ndiv(), для якої
передбачений виклик як з двома аргументами типу double, так і з двома аргументами типу int. У
останньому випадку, хоча результат функції формально обчислюється так само, як і в початковому варіанті,
для цілих чисел оператор / означає цілочислове ділення з відкиданням остачі. Тому в результаті отримуємо
число 3, а не 3.33333, як для виклику функції з double -аргументами. Більш того, нерідко трапляються
ситуації, коли з урахуванням автоматичного приведення типів неможливо однозначно визначити, який з
варіантів перевантаженої функції необхідно викликати. Наприклад, якщо для перевантаженої функції
передбачені два варіанти передачі аргументу для типу даних float і для типу даних double, то передача
функції як аргумент int-змінної призведе до помилки компіляції, оскільки незрозуміло, до якого формату
даних (float або double) потрібно перетворювати тип int. Такі ситуації відносяться до розряду логічних
помилок, і їх потрібно уважно відстежувати.
Як і звичайні функції, перевантажені функції можуть мати аргументи зі значеннями, що
використовуються за замовчуванням. Причому для кожного варіанта перевантаженої функції ці значення за
замовчуванням можуть бути різними. Єдине, за чим необхідно постійно стежити, щоб наявність значень за
замовчуванням у аргументів не призводило до неоднозначних ситуацій при виклику перевантаженої функції.
// Перевантаження і значення за замовчуванням
void hello()
{cout<<"Hello, my friend! \n";}
void hello (char str[], char name[] = “Alex")
{ cout << str << " , "<<name<<" ! "<<endl; }
int main()
{
hello();
hello("Hello","Peter");
hello("Hi");
return 0 ;
}

У перевантаженої функції hello() два варіанти: без аргументів і з двома аргументами-масивами типу
char. Причому в останньому випадку другий аргумент має значення за замовчуванням.
Якщо функція викликається без аргументів, в результаті її виконання на екран виводиться
повідомлення Hello, my friend !. При виконанні функції з двома аргументами (кожен аргумент -
текстовий рядок, реалізований у вигляді масиву символів) на екран послідовно виводяться текстові значення
аргументів функції. Оскільки другий аргумент має значення за замовчуванням Alex, то формально функцію
можна викликати і з одним текстовим аргументом. У головному методі функція hello() послідовно
викликається без аргументів (команда hello()), з двома аргументами (команда hello( "Hello",
"Peter")) і з одним аргументом (команда hello( "Hi")). Результат виконання програми виглядає
наступним чином:
Hello, my friend!
Hello, Peter!
Hi, Alex!
Але якщо спробувати для другого варіанту функції (з двома аргументами) вказати значення за
замовчуванням і для першого аргументу, виникне помилка. Причина в тому, що наявність у кожного з
аргументів функції hello() значення за замовчуванням призводить до неоднозначної ситуації: якщо функція
при виклику вказана без аргументів, неможливо визначити, викликається цей варіант функції без аргументів,
або потрібно використовувати варіант функції з двома аргументами зі значеннями за замовчуванням. Як уже
зазначалося, на подібні ситуації слід постійно звертати увагу при створенні програмних кодів у процесі
перевантаження функцій.
51
Структури (struct)
На відміну від масиву, всі елементи якого однотипні, структура може містити елементи різних типів. У мові
С++ структура є видом класу і має всі його властивості, але в багатьох випадках достатньо використовувати
структури так, як вони визначені в мові С:
Struct [Ім'я типу]
{ Тип1 елемент1;
Тип2 елемент2;
Типn елементn;
} [список описателів];
Елементи структури називаються полями структури і можуть мати будь-який тип, крім типу цієї ж структури,
але можуть бути вказівниками на нього. Якщо відсутнє ім'я типу, повинен бути вказаний список описателів
змінних, вказівників або масивів. У цьому випадку опис структури служить визначенням елементів цього
списку: Розмір структури не обов'язково дорівнює сумі розмірів її елементів, оскільки вони можуть бути
вирівняні по межах слова.
После фігурних дужок, які закінчують безпосередньо опис структури, можна вказати (а можна і не вказувати)
список змінних структури. Справа в тому, що сам по собі опис структури цю структуру не створює. Опис
структури - це всього лише якийсь шаблон, за яким потім створюються змінні. Процес створення змінних
структури можна не відкладати в довгий ящик, а створити їх відразу, вказавши список з назвами після опису
структури.
// Визначення масиву структур і вказівника на структуру:
struct One
{ char fio[30]
int date, code;
double salary;
}staff [100], * ps;
Якщо список відсутній, опис структури визначає новий тип, ім'я якого можна використовувати в подальшому
поряд зі стандартними типами, наприклад:
Struct Worker
{char fio[30];
int date, code;
double salary
}; // Опис завершується капкою з комою
Worker a; //змінна а типу Worker
З точки зору використання в програмі має сенс говорити лише про змінні структури. Фактично змінна
структури - це об'єкт, який має ім'я (ім'я змінної структури) і поля, тип і назви яких визначаються описом
структури. Щоб в програмі створити змінну структури, необхідно вказати ім'я структури, відповідно до опису
якої створюється змінна, і ім'я цієї змінної. Іншими словами, змінна структури в програмі створюється точно
так же, як і змінна будь-якого базового типу, тільки замість назви типу змінної вказується назва структури.
Сама по собі змінна структури інтерес представляє більше спортивний, ніж практичний. Всі дані, які можуть
знадобитися в процесі виконання програми, записані в поля змінної. Звернення до поля змінної структури
здійснюється через так званий точковий синтаксис (стандартний синтаксис для об'єктно-орієнтованого
програмування) - вказується ім'я змінної структури, і через точку ім'я поля, до якого віконується звернення,
тобто в форматі структура.поле.
Доступ до полів структури виконується за допомогою операцій вибору (точка) при зверненні до поля через
ім'я структури і через -> при зверненні через вказівник, наприклад:
Worker worker, staff[100], * ps;

worker.fio = ”Страусенко”;
staff[8].code = 215;
ps->salary = 0.12;
Якщо елементом структури є інша структура, то доступ до її елементів виконується через дві операции
вибору:
struct A { int a; double x;};
struct B { int a; double x;} x[2];
x[0].a.a=1;
x[1].x=0.1;
Як видно з прикладу поля різних структур можуть мати однакові імена, оскільки у них різна область
видимості. Більш того, можна оголошувати в одній області видимості структуру і інший об'єкт (наприклад,
змінну або масив) з однаковими іменами, якщо при визначенні структурної змінної використовувати слово
struct, але не варто це робити - заплутати компілятор важче, ніж себе.
// Оголошення та використання структури
52
struct Marks
{ char name[80];
int phys;
int chem;
int maths;
} ivanov,petrov,sidorov;
struct Exams
{ double phys;
double chem;
double maths;
};
int main()
{ strcpy (ivanov.name, "Sergei Ivanov") ;
ivanov.phys=4;
ivanov.chem=3;
ivanov.maths=3;
strcpy (petrov.name, "Igor Petrov");
petrov.phys=5;
petrov.chem=4;
petrov.maths=4;
strcpy(sidorov.name,"Ivan Sidorov");
sidorov.phys=5;
sidorov.chem=4;
sidorov.maths=3;
Exams LastYear,ThisYear;
LastYear.chem=4.33333;
LastYear.phys=3.66667;
LastYear.maths=3.33333;
ThisYear.chem=(double)(ivanov.chem + petrov.chem + sidorov.chem)/3;
ThisYear.phys=(double)(ivanov.phys + petrov.phys + sidorov.phys)/3;
ThisYear.maths=(double)(ivanov.maths + petrov.maths + sidorov.maths)/3;
cout<<"Last year marks:" << endl;
cout<<"Physics" << LastYear.phys<<endl;
cout<<"Chemistry"<<LastYear.chem<<endl;
cout<<"Mathematics"<<LastYear.maths<<endl;
cout<<"This year marks:" << endl;
cout<<"Physics"<<ThisYear.phys<<endl;
cout<<"Chemistry"<<ThisYear.chem<<endl;
cout<<"Mathematics "<<ThisYear.maths<<endl;
return 0;
}
У програмі оголошені дві структури: Marks і Exams. У структуру Marks входять чотири поля: символьний
масив name типу char і цілочислові поля (тип int) phys, chem і maths. Одночасно з оголошенням
структури віконується оголошення і змінних структури з іменами ivanov, petrov і sidorov.
Структура Marks створюється в програмі для зберігання інформації про успішність учнів. Оцінки з трьох
предметів (фізика, хімія і математика) заносяться в цілочислові поля phys, chem і maths відповідно. Поле-
масив name визначено для запису імені та прізвища учня.
Опис структури є лише загальним шаблоном, відповідно до якого формуються змінні структури. Щоб було
куди записати оцінки, а також ім'я та прізвище, необхідно створити змінну структури. В даному випадку
змінні структури створені шляхом перерахування назв в кінці опису структури.
Призначення структури Exams полягає в зберіганні середніх оцінок по кожному з предметів в різні роки.
Кожному року відповідає змінна структури. Змінні структури створюються безпосередньо в головному методі
програми. У структури три поля, які мають такі ж назви (phys, chem і maths), однак тепер поля оголошені
як такі, що тип double. Справа в тому, що середня оцінка, на відміну від оцінки учня з предмета, в
загальному випадку не є цілим числом.
У головному методі програми спочатку виконується ініціалізація полів змінних структури Marks. Зокрема,
заповнення масиву name для змінної ivanov виконується командою strcpy (ivanov.name, "Sergei
Ivanov") (копіювання рядка "Sergei Ivanov" в поле-масив name).
ivanov.phys = 4;
ivanov.chem = 3;
53
ivanov.maths = 3;
Точно так же заповнюються поля двох інших змінних (petrov і sidorov) структури Marks.
Командою Exams LastYear, ThisYear в головному методі програми оголошуються дві змінні LastYear і
ThisYear структури Exams. Змінна LastYear призначена для запису середніх оцінок минулого року.
Оцінки (середні) за поточний рік записуються в змінну ThisYear структури Exams. Значення полів змінної
структури LastYear присвоюються командами
LastYear.chem = 4.33333;
LastYear.phys = 3.66667;
LastYear.maths = 3.33333;
Відповідні поля змінної структури визначаються як середнє арифметичне по набору учнів (їх в даному
випадку всього три):
ThisYear.chem = (double) (ivanov.chem + petrov.chem + sidorov.chem) / 3;
ThisYear.phys = (double) (ivanov.phys + petrov.phys + sidorov.phys) / 3;
ThisYear.maths = (double) (ivanov.maths + petrov.maths + sidorov.maths) / 3;
Інструкція (double) використана для явного приведення типів, оскільки за замовчуванням для цілочислових
операндів операція ділення віконується як ділення з відкиданням дробової частини (цілочислове ділення). Далі
отримані значення для полів змінних LastYear і ThisYear структури Exams виводяться на екран.
Результат має наступний вигляд:
Last year marks:
Physics 3.66667
Chemistry 4.33333
Mathematics 3.33333
This year marks:
Physics 4.66667
Chemistry 3.66667
Mathematics 3.33333.
Для змінних одного і того ж структурного типу визначена операція присвоювання, при цьому відбувається
поелементне копіювання. В результаті такого присвоювання з однієї змінної в іншу копіюються значення всіх
полів структури:
ivanov=petrov;
Для ініціалізації структури значення її елементів перераховують в фігурних дужках в порядку їх опису:
Struct Worker
{
char fio[30];
int date, code;
double salary
} worker = { “Страусенкo“, 31,215,3400.55};

Масиви структур
Як і звичайні змінні, структури можуть бути елементами масивів.
// Масиви структур
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
struct Marks
{
char name[80];
int phys;
int chem;
int maths;
};
int main()
{
const int n=3;
bool state;
char s [80];
Marks students[n];
for (int i=0 ;i<n;i++)
{
54
cout << ("Student name:");
gets(students[i].name);
students[i].phys=3+rand()%3;
students[i].chem=3+rand()%3;
students[i].maths=3+rand()%3;
}
do
{
cout<<"What is the student name?";
gets (s);
if (!strcmp(s,"exit")) return 0 ;
state=true;
for(int i=0 ;i<n;i++)
{
if (!strcmp(students[i].name,s))
{ state=false;
cout<<"Physiscs:"<<students[i].phys<<endl;
cout<<"Chemistry:"<<students[i].chem<<endl;
cout<<"Mathematics:"<<students[i].maths<<endl;
break;
}
}
if (state) cout<<"There is no student with such name\n";
}while(true);
}
Приклад виконання програми може виглядати наступним чином:
Student name: Sergei Ivanov
Student name: Igor Petrov
Student name: Ivan Sidorov
What is the student name? Sergei Ivanov
Physics: 5
Chemistry: 5
Mathematics: 4
What is the student name? Ivan Sidorov
Physics: 3
Chemistry: 3
Mathematics: 4
What is the student name? Igor Petrovski
There is no student with such name
What is the student name? Igor Petrov
Physics: 4
Chemistry: 5
Mathematics: 4
What is the student name? exit
У програмі командою Marks student s [n] визначається масив students змінних структури Marks з n
елементів (попередньо оголошена цілочисельна константа const int n = 3). Заповнення елементів
масиву виконується в рамках оператора циклу. Індексна змінна i пробігає значення від 0 до n-1 включно.
Кожен раз буде запропоновано ввести ім'я учня і це ім'я командою gets(students[i].name) записується
в поле name змінної структури students[i] -елемента масиву students. Оцінки з трьох предметів для
кожного учня визначаються як випадкові числа командами students[i].
phys=3+rand()%3, students[i].chem=3+rand()%3 і students[i].maths=3+rand()%3.
До базової оцінки 3 додається ціле випадкове число в діапазоні від 0 до 2 включно (результат команди
rand()%3 - остача від ділення випадкового числа на 3).
Командою char s[80] оголошується символьний масив, в який буде виконуватися зчитування імені учня,
яке вводиться користувачем, для відображення його оцінок. Цей масив використовується в операторі циклу do
{...} while(true). Причому варто звернути увагу, що в якості умови, яка перевіряється, вказано логічне
значення true, що означає, що формально цикл нескінченний. Можливість виходу з цього циклу передбачена
в самому циклі. На початку циклу командою cout << "What is the student name?" Вам буде
запропоновано ввести ім'я учня. Введене користувачем ім'я за допомогою команди gets(s) зчитується і
записується в масив s. Після того, як ім'я учня введено, віконується умовний оператор
55
if (! strcmp(s, "exit")) return 0.
Цією командою передбачена можливість не тільки виходу з оператора циклу, але і завершення роботи
програми: якщо користувач в якості імені введе exit, робота програми завершується (інструкція return 0).
Там же командою state = true логічній змінній state (змінна використовується для індикації того,
знайдено збіг імен чи ні) присвоюється значення true. Після цього за допомогою оператора циклу
віконується послідовний перебір елементів масиву students і проводиться порівняння значень рядків,
записаних в масив s і в поле-масив students[i].name. Для порівняння рядків використовується функція
strcmp(). Нагадаємо, що якщо рядки збігаються, то в якості значення функцією повертається 0, тому в
умовному операторі зазначена умова !Strcmp(students[i].name, s). Якщо умова істинна, командою
state = false змінюється стан логічної змінної state, після чого виводиться інформація про оцінки
відповідного учня. В кінці умовного оператора розміщена команда break для передчасного виходу з
оператора циклу - якщо збіг знайдено, продовжувати пошук не має сенсу. Якщо збігу немає (тобто введене
користувачем ім'я не знайдено при перегляді полів елементів масиву students), змінна state має значення
true. На цей випадок передбачена команда
if (state) cout << "There is no student with such name\n".

При ініціалізації масивів структур слід укладати в фігурні дужки кожен елемент масиву:
struct Complex
{
float real, im;
}compl[2][3]={{{1.1},{1.1},{1,1}},/*Строка1*/{{2.2},{2.2},{2.2}}}; //Строка 2,
// тобто масив compl[1]

Структури як аргументи функцій


Структури можуть передаватися в якості аргументів функціям. У цьому випадку в якості типу аргументу
функції вказується назва структури, змінна якої буде передана в функцію.
// структури – аргументи функцій
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
using namespace std;
struct Marks
{ char name[80];
int phys;
int сhem;
int maths;
};
void set_one(Marks &str)
{ cout << ("Student name:");
gets(str.name);
str.phys=3+rand()%3;
str.chem=3+rand()%3;
str.maths=3+rand()%3;
}
void set_all(Marks *str, int m)
{ for(int i=0; i<m; i++) set_one(str[i]); }
void get(Marks *str,int m )
{ bool state;
char s [80];
do
{ cout<<"What is the student name? ";
gets (s);
if (!strcmp(s,"exit")) return;
state=true;
for (int i=0; i<m; i++)
{ if (!strcmp(str[i].name,s))
{ state=false;
cout<<"Physiscs:"<<str[i].phys<<endl;
cout<<"Chemistry:"<<str[i].chem<<endl;
56
cout<<"Mathematics:"<<str[i].maths<<endl;
break;
}
}
if (state) cout<<"There is no student with such name\n";
} while(true);
}
int main ()
{
const int n=3;
Marks students[n];
set_all(students,n);
get (students,n);
return 0 ;
}
Основний програмний код реалізований у вигляді декількох функцій. Функція set_one() призначена для
заповнення полів структури, переданої аргументом функції. Функція оголошена як void set_one(Marks
& str). Аргумент функції зазначений як такий, що має тип Marks - тип оголошеної раніше структури. Крім
того, аргумент (змінна структури) передається по посиланню, тому перед формальним ім'ям str аргументу
функції вказується оператор &. Причина передачі аргументу функції через посилання, а не за значенням (як
завжди) полягає в тому, що в результаті виконання функції необхідно змінити аргумент, переданий цієй
функції.
При виконанні коду функції виводиться запрошення ввести ім'я учня і за допомогою генерування випадкових
чисел визначаються оцінки з трьох предметів.
У функції set_all() в рамках оператора циклу викликається функція set_one() для заповнення елементів
масиву, вказаного першим аргументом функції set_all(). Прототип цієї функції має вигляд void
set_all(Marks * str, int m). Перший аргумент - вказівник на змінну структури Marks. В даному
випадку це ім'я масиву, який заповнюється. Другий аргумент - розмір масиву. Оскільки ім'я масиву є
посиланням на його перший елемент, проблеми з передачею аргументів, як в попередньому випадку, не
виникає - при заповненні масиву буде змінюватися саме той масив, ім'я якого передано аргументом функції.
Функція get() з прототипом void get(Marks * str, int m) призначена для зчитування імені учня і
виведення його оцінок. Перший аргумент функції - масив, в якому віконується пошук на предмет збігу імен
учнів, другий аргумент - розмір масиву. Використана інструкція if(! Strcmp(s, "exit")) return для
завершення роботи функції (але не програми!). Безпосередньо после інструкції return нічого не вказується,
оскільки функція визначена як така, що не повертає результат.
Як наслідок використання описаних функцій істотно спрощується код головного методу програми. Крім
ініціалізації константи, що визначає розмір масиву, і оголошення масиву з елементами-змінними структури,
основний код головного методу складається з виклику двох функцій (set_all() і get()) і стандартної
інструкції return 0.

Вказівники на структури
При оголошенні вказівника на структуру, як і в разі створення вказівників на значення базових типів,
вказується тип структури, а перед ім'ям змінної-вказівника ставиться оператор *. Цей же оператор
використовується для отримання доступу до змінної структури за вказівником на цю змінну.
Крім того, через вказівнік на структуру можна звертатися безпосередньо до полів структури, для чого
використовують оператор -> (стрілка, складається з двох символів – і >). Наприклад, якщо в програмі
визначено вказівнік на змінну структури, у якій є поле, то доступ до цього поля можна отримати за допомогою
інструкції вказівнік-> поле.
// Вказівники на структуру>
#include <iostream>
using namespace std;
struct Numbers
{ int integer;
double real;
char symbol;
};
void show(Numbers x )
{ cout << "Integer:"<<x.integer<<endl;
cout << “Real:"<<x.real<<endl;
cout<<"Symbol:" << x.symbol<<endl;
57
}
int main()
{ Numbers a,b;
Numbers *p,*q;
p=&a;
q=&b;
p->integer=1;
p->real=2.5;
p->symbol=’a’;
(*q).integer=2;
(*q).real=5.1;
(*q).symbol='b';
show(a);
show(*q);
return 0;
}
У програмі оголошена структура Numbers, що має три поля: типу int, типу double і типу char. Крім
цього, описана функція show(), аргументом якої є змінна структури Numbers, а в результаті виконання
функції виводяться значення всіх полів аргументу.
У головному методі програми командою Numbers а, b оголошуються змінні а і b структури Numbers, а
командою Numbers * р, * q оголошені вказівники р і q на змінні структури Numbers. Значення
вказівникам (адреси) присвоюються командами р=&а і q=&b відповідно. При цьому використаний, як і в разі
змінних базових типів, оператор отримання адреси &. Поля змінної а заповнюються за допомогою команд p-
>integer=1, p->real=2.5 і p->symbol= а'. У цьому випадку посилання на поле реалізується через
вказівнік р і оператор ->. Поля змінної b заповнюються більш консервативним способом. Для цього
використані команди (* q).integer=2,(*q).real=5.1 і (*q).symbol='b' відповідно. Тут прийнято
до уваги, що інструкція *q є нічим іншим, як тією змінною, на яку посилається вказівнік q (тобто змінна b).
Аналогічно при виконанні функції show() її аргументом можна вказати як ім'я змінної (а чи b) структури
Numbers, так і інструкцію виду *р або *q. В результаті виконання програми отримаємо наступне:

Integer: 1
Real: 2.5
Symbol: а
Integer: 2
Real: 5.1
Symbol: b

Ім'я структури можна використовувати відразу після його оголошення в тих випадках, коли компілятору не
потрібно знати розмір структури, наприклад:
struct List; // Оголошення структури List
struct Link
{
List * p; // Вказівник на структуру List
Link * prev, * succ; // Указатели на структуру Link
};
struct List { / * Визначення структуры List */ };
Це дозволяє створювати зв'язні списки структур.
Вказівники на структури знаходять широке застосування, і в першу чергу при складанні динамічних списків.

Бітові поля
Бітові поля - це особливий вид полів структури. Вони використовуються для щільної упаковки даних,
наприклад, прапорців типу «так/ні». Мінімальна адресуєма комірка пам'яті - 1 байт, а для зберігання прапорця
досить одного біта. При описі полів структури в явному вигляді можна вказувати розмір полів. Мінімальний
розмір поля структури - один біт. Загальний синтаксис оголошення структури з явним зазначенням розмірів
полів має вигляд:
struct Ім'я
{
тип_поля1 ім'я_поля1: розмір1;
тип_поля2 ім'я_поля2: розмір2;
тип_поляN ім'я_поляN: розмірN;
58
};
При описі бітового поля після імені через двокрапку вказується довжина поля в бітах (ціла позитивна
константа):
struct Options
{
bool cеnterX: 1;
bool centerY: 1;
unsigned int shadow : 2;
unsigned int pаlette : 4;
};
Бітові поля можуть бути будь-якого цілого типу. При цьому для частини полів можна вказувати розмір, а для
інших - ні. Якщо поле має розмір в один біт, як його тип вказується unsigned (у числа, реалізованого за
допомогою одного біта, не може бути знака). Ім'я поля може бути відсутнім, такі поля служать для
вирівнювання на апаратну границю. Доступ до поля здійснюється звичайним способом - по імені. Адресу поля
отримати не можна, однак в іншому бітові поля можна використовувати точно так же, як звичайні поля
структури. Слід враховувати, що операції з окремими бітами реалізуються набагато менш ефективно, ніж з
байтами і словами, так як компілятор повинен генерувати спеціальні коди, і економія пам'яті під змінні
обертається збільшенням обсягу коду програми. Розміщення бітових полів в пам'яті залежить від компілятора
і апаратури.
// Явна вказівка розмірів полів структури
#include <iostream>
using namespace std;
struct BitFields
{ unsigned int state:1;
int n:2;
int m;
} str;
int main ()
{ cout<<"Enter a number:";
cin>>str.m;
str.state=str.m%2;
str.n=str.m%4-2;
cout<<"state=" << str.state<<endl;
cout<<"n=”<< str.n<<endl;
return 0;
}
У структури BitFields три поля: однобітове цілочислове поле state, двухбітове цілочислове поле n і
цілочислове поле m (розмір поля не вказаний).
Поле state може приймати всього два значення: 0 або 1. Поле n приймає цілочислові значення в діапазоні
від -2 до 1 включно, тобто всього 4 можливих значення: нагадаємо, що старший біт визначає знак числа, тому
двухбітове число 11 відповідає числу -2, двухбітове число 10 відповідає числу -1, число 00 відповідає нулю і
число 01 відповідає числу 1.
У головному методі програми буде запропоновано ввести користувачу ціле число. Це число записується в
поле m змінної str структури BitFields. У змінну state заноситься остача від ділення цього числа на 2, а
в змінну n записується остача від ділення введеного користувачем числа на 4 мінус 2. Значення полів змінної
str структури BitFields виводяться на екран. В результаті можемо отримати щось схоже на це:
Enter a number: 9
state = 1
n = -1
Таким чином, шляхом явної вказівки розмірів полів структури вдалося домогтися економії системних
ресурсів: для запису значень використовується мінімальна необхідна кількість бітів. Виникає природне
запитання: а що буде, якщо присвоюється бітовому полю (тобто полю, для якого явно вказано розмір)
значення, що виходить за допустимі для цього поля границі? Наприклад, що буде, якщо полю n значення
привласнювати за допомогою команди str.n = str.m% 4-4? Відповідь така: в зазначених випадках
відбувається автоматичне відкидання зайвих бітів. Зокрема, якщо поле m дорівнює 9, а поле n визначається як
str.n = str.m% 4-4, то цьому полю буде присвоєно значення 1. Пояснимо це. Так, остачею від ділення 9
на 4 є 1. Якщо відняти 4, отримаємо -3. У двійковому поданні число -3 має вигляд (останні три біта, всі старші
біти рівні 1) 101. Старші біти відкидаються, і в результаті в двухбітовому поданні отримуємо 01, що відповідає
числу 1.
59
Об'єднання (union)
Об'єднання (union) являє собою окремий випадок структури, всі поля якої розташовуються за однією і тією ж
адресою. Формат опису такий ж, як у структури, тільки замість ключового слова struct використовується
слово union. Довжина об'єднання дорівнює найбільшому з довжин його полів. У кожен момент часу в
змінній типу об'єднання зберігається тільки одне значення, і відповідальність за його правильне використання
лежить на програмістові.
Об'єднання застосовують для економії пам'яті в тих випадках, коли відомо, що більше одного поля одночасно
не потрібно:

#include <iostream.h>
int main()
{
enum Paytype {CARD, CHECK};
Paytype ptype;
union payment
{ char card[25];
long check;
} info;
/*присвоєння значень info та ptype*/
Switch (ptype)
{
case CARD: cout <<Оплата картою: “<<info.card; break;
case CHECK: cout <<Оплата чеком: “<<info.check; break;
}
return 0;
}
Об'єднання часто використовують в якості поля структури, при цьому в структуру зручно включити додаткове
поле, яке визначає, який саме елемент об'єднання використовується в кожен момент. Ім'я об'єднання можна не
вказувати, що дозволяє звертатися до його полів безпосередньо:
#include <iostream.h>
int main()
{ enum Paytype {CARD, CHECK};
struct
{ Paytype ptype;
Union
{ char card[25];
long check;
};
} info;
... /*присвоєння значень info*/
switch (info.ptype)
{ case CARD: cout <<Оплата картою: “<<info.card: break;
case CHECK: cout <<Оплата чеком: “<<info.check; break;
}
return 0;
}
//Використання об'єднань
#include <iostream>
using namespace std;
union nums
{ unsigned short int n;
short int m;
};
void show(nums a )
{ cout << "n =”<< a.n << endl;
cout << "m = "<<a.m<<endl;
cout<<endl ;
};
int main()
{ nums un;
un.m=1;
60
show(un);
un.m=32767;
show(un);
un.m=65535;
show(un);
un.m=-1;
show(un);
un.m=-65536;
show(un);
return 0;
}

Оголошене в програмі об'єднання nums містить два члена: цілочислову беззнакову змінну n типу unsigned
short int і цілочислову змінну m типу short int. Підкрякщомо, що для запису обох змінних
використовується загальна область пам'яті. Зазвичай обсяг пам'яті для зберігання даних об'єднання
вибирається виходячи з розміру найбільшою (за типом) змінної. В даному випадку розмір пам'яті для
зберігання змінних однаковий, різниця тільки в інтерпретації записаних в цю пам'ять значень.
Ситуація наступна: при зверненні до змінної n або m, що є членами об'єднання, проглядається одна і та ж
область пам'яті. Тому змінюючи змінну n ми змінюємо змінну m і навпаки. На все, що відбувається можна
подивитися і з іншої точки зору: є область пам'яті, до якої можна звертатися через різні змінні, і в залежності
від типу змінної по-різному інтерпретувати записане в пам'ять значення. Щось подібне ілюструє і наведений у
лістингу програмний код: у головному методі програми створюється екземпляр un об'єднання nums, одному з
членів екземпляра об'єднання присвоюється значення, після чого перевіряється значення іншого члена. Для
зручності в програмі описана функція show (), якою виводяться на екран значення членів екземпляра
об'єднання (ім'я екземпляра об'єднання вказується аргументом функції).
Простежимо, яким буде результат виконання програмного коду. Для цього необхідно згадати деякі
особливості форматів unsigned short int і short int. Для використовуваного компілятора діапазон
зміни чисел типу short int становить від -32768 до 32767. Значення для чисел типу unsigned short
int лежать в межах від 0 до 65535. І в тому, і в іншому випадку числа реалізуються у вигляді 16-бітових
двійкових послідовностей. Іншими словами, в область пам'яті, виділену під екземпляр об'єднання, записується
послідовність з 16-ти нулів і одиниць. Якщо звернення до області пам'яті здійснюється через змінну m, то цей
двійковий код інтерпретується як число типу short int, а при зверненні до пам'яті через змінну n код
інтерпретується як число типу unsigned short int. Але сам код один і той же! Спочатку виконується
присвоєння m = 1. У цьому випадку як член n, так і член m отримують однакові значення (точніше, одне і те
ж значення інтерпретується однаково), тому результатом виконання команди show (un) буде

n = 1
m = 1
Аналогічний результат отримуємо в результаті присвоєння un.m = 32767 (обидва члени мають однакове
значення):
n = 32767
m = 32767
Ситуація принципово змінюється після виконання команди un.m = 65535. В результаті отримаємо:
n = 65535
m = -1
Щоб зрозуміти, чому так відбувається, більш детально розглянемо процедуру інтерпретації чисел в
двійковому коді для різних типів даних. Значення 65535 виходить за допустимі межі діапазону даних short
int, тому при запису значення в змінну m старші біти в двійковому поданні числа відкидаються. Само по собі
значення 65535 в двійковому коді представляється у вигляді 0 ... 00111 ... 11, тобто 16-ть одиниць з нульовими
старшими бітами (загальна кількість біт залежить від специфіки використовуваного компілятора). Після
відкидання "зайвих" бітів залишаються останні 16 одиниць.
Таким чином, в пам'ять, виділену під екземпляр об'єднання, записано значення 111 ... 11. Якщо це значення
інтерпретується як unsigned short int (змінна n), отримуємо в десятковій системі значення 65535. Якщо же
значення інтерпретується як таке, що відноситься до типу short int, ситуація дещо інша. В цьому випадку
старший біт відповідає за знак числа, і одиничний старший біт означає, що число негативне. Для отримання
відповідного десяткового значення необхідно виконати побітову інверсію, додати одиницю, перевести число в
десяткову систему числення і додати "мінус". После побітової інверсії отримуємо 000. ..00. Додавши одиницю,
отримуємо число виду 000. ..01, що відповідає числу 1, а зі знаком "мінус" це -1 (значення змінної m).
Аналогічна ситуація складається после виконання команди un.m = -1.
61
Описані вище перетворення необхідно виконати в зворотному порядку, после чого стане ясно, що значенням
змінної n буде 65535. До речі, такий же результат отримаємо, якщо дамо un.n = 65535. А ось якщо
виконати команду un.m = -65536 і потім вивести значення змінних екземпляра об'єднання, отримаємо
n = 0
m = 0
Щоб перевести число -65536 в двійкове подання, необхідно модуль цього числа (тобто 65536) перевести в
двійковий код, інвертувати кожен біт і додати одиницю. Число 65536 в двійковому поданні має вигляд 000 ...
001 000 ... 00 (в даному випадку кількість N старших незначущих нульових бітів значення не має). Після
інвертування отримуємо 111 ... 110 111 ... 11. Додавши одиницю, отримаємо 1000 ... 001000 ... 00.
У змінну m записуються останні 16 біт, тобто число 000 ... 00, або 0. Таке ж значення і у змінної n.
Об'єднання застосовуються також для різної інтерпретації одного і того ж бітового подання (але, як правило, в
цьому випадку краще використовувати явні операції перетворення типів). Як приклад розглянемо роботу зі
структурою, що містить бітові поля:
struct Options
{ bool centerX:1;
bool centerY:2;
unsigned int shadow:2;
unsigned int palette:4;
};
union
{ unsigned char ch;
options bit:
} option = {0xC4};
cout << option.bit.palette;
option.ch= 0xF0; // накладення маски
У порівнянні зі структурами на об'єднання накладаються деякі обмеження. Сенс деяких з них стане
зрозумілий пізніше:
1. Об'єднання може ініціалізуватися тільки значенням його першого елемента;
2. Об'єднання не може містити бітові поля;
3. Об'єднання не може містити віртуальні методи, конструктори, деструктори і операцію присвоєння;
4. Об'єднання не може вxодити в ієрархію класів.

Перерахування та визначення типів


Під перерахуванням мають на увазі набір іменованих цілочіслових констант, які використовуються для
визначення можливих значень змінної типу перерахування. Іншими словами, перерахування визначає
спеціальний тип даних, змінні якого можуть приймати цілочислові значення, причому кожне цілочислове
значення має ім'я. Оголошується перерахування за допомогою ключового слова enum, після якого слід ім'я
перерахування і список (в фігурних дужках) іменованих констант:
enum тип_перерахування {константа]., константа2,. . . , константаn};
Після списку іменованих констант можна вказувати список змінних типу перерахування.

enum color {red, green, blue, yellow, black} car;


color bus;
car=green;
bus=yellow;
cout<<"Car is”<< car << "and bus is "<<bus<<endl;

Командою enum color {red, green, blue, yellow, black} car створюється перерахування
color. Змінні, що відносяться до цього типу (тобто відносяться до перерахування color), можуть набувати
значень red, green, blue, yellow і black. Це не текстові значення, а числові. Іншими словами,
ключові слова red, green, blue, yellow і black - це назви цілочислових констант. За замовчуванням
перша в списку значень константа дорівнює 0, а кожна наступна на одиницю більше попередньої. Таким
чином, red відповідає значенню 0, green відповідає значенню 1, blue відповідає значенню 2, yellow
відповідає значенню 3 і black відповідає значенню 4.
У програмі оголошуються дві змінні перерахування color. Змінна саr вказана при оголошенні
перерахування одразу после фігурної дужки варіанти вибору. Змінна bus оголошена командою color bus.
Значення цих змінних присвоюються командами car = green і bus = yellow відповідно. Проте слід
пам'ятати, що змінні, насправді, отримують цілочислові значення. У цьому легко переконатися за результатом
виконання програми. Зокрема, на екрані з'явиться повідомлення
Car is 1 and bus is 3
62
Легко здогадатися, що в даному випадку замість ідентифікаторів green і yellow при виведенні на екран
використовувалися відповідні числові значення.
Використовувані за замовчанням значення для цілочислових констант в списку можливих значень можуть
бути змінені. В цьому випадку после відповідного імені через знак рівності вказується значення відповідної
константи.
// У списку перерахування явно вказані значення констант
enum color {red=10, green, blue=100, yellow, black};
color car,bus,van;
car=green; bus=blue; van=black;
cout<<”Car is “<<car<<", bus is " << bus << “and van is “<<van<<endl;
В результаті виконання програми з'явиться рядок:

Car is 11, bus is 100 and van is 102

Константі red явно присвоєно значення 10, тому константа green має значення 11 (на одиницю більше). Для
константи blue явно вказано значення 100, тому константи yellow і black мають значення 101 і 102
відповідно.
Скориставшись оголошенням виду typedef тип нове_ім'я_типу, в програмі створюють нове ім'я для
типу даних.

typedef int* IntPointer;


int n=100;
IntPointer p;
p=&n;
(*p)++;
cout << "n="<<n<<endl;

Командою typedef int * IntPointer оголошено нове ім’я IntPointer для даних "вказівник
на ціле число". Згодом ідентифікатор IntPointer використовується при оголошенні нових змінних,
причому результат, наприклад, команди IntPointer * р, виглядає як якщо б це було оголошення int *
р. В результаті виконання команди з'явиться повідомлення n = 101.

You might also like