Лабораторна робота 2

Операції та твердження C++

1 Завдання на лабораторну роботу

1.1 Програмна реалізація алгоритму з розгалуженням

Розробити програму розв'язання квадратного рівняння. Програма повинна включати перевірку всіх можливих варіантів вихідних даних. Зокрема, слід перевіряти дискримінант, а також передбачити перевірку, чи рівняння є квадратним. Якщо рівняння виродилося в лінійне, слід передбачити знаходження кореня цього лінійного рівняння, або встановити наявність безлічі розв'язків (відсутності розв'язків).

1.2 Програмна реалізація циклічного алгоритму

Розробити програму, яка реалізує алгоритм обчислення виразу:

y = 1/(x + 2) + 2/(x + 4) + ... + (k - 1)/(x + 2(k - 1)) + (k + 1)/(x + 2(k + 1)) + ... + n/(x + 2n)

 

Забезпечити перевірку можливих помилок.

1.3 Обчислення добутку

Розробити програму, яка забезпечує читання x і n і обчислює y:

y = (x + 1)(x - 2)(x + 3)(x - 4) ... (x - 2n)

 

1.4 Обчислення суми

Розробити програму, яка читає значення eps (невеличке число – точність обчислень) і обчислює y:

y = 1/2 + 1/4 + 1/8 + 1/16 + ...

 

Цикл завершується, якщо новий доданок менше, ніж eps.

1.5 Індивідуальне завдання

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

2 Методичні вказівки

2.1 Основні елементи мови програмування C++

2.1.1 Загальна структура програми

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

У глобальній області видимості визначають функцію main(), з якої починається виконання програми. Увесь програмний код (крім оголошень і визначень) повинен міститись всередині функцій.

Окрім безпосередньо тексту мовою C++, сирцевий код зазвичай також містить директиви препроцесору. Ці директиви починаються з символу # і їх обробляє спеціальна програма – препроцесор. Мінімально необхідна директива – #include. Вона обумовлює включення в поточний файл тексту іншого файлу – так званого заголовного файлу. Заголовний файл містить оголошення і визначення, необхідні для компіляції початкового коду. Ім'я заголовного файлу вказано у кутових дужках (для стандартних файлів) або у лапках (для користувацьких файлів, які знаходяться у поточній теці). Фізично копіювання тексту в файл не здійснюється, але у пам'яті створюється так звана одиниця трансляції – сирцевий код, оброблений препроцесором.

2.1.2 Ідентифікатори. Ключові слова. Коментарі

На відміну від мов Pascal і BASIC, у мові C++ відрізняються великі та маленькі літери.

Сирцевий код програми складається з лексем. Лексема (token) – це послідовність символів, що мають певне сукупне значення. Проміж окремими лексемами розташовують розділювачі - пропуск, табуляція, новий рядок тощо.

Лексеми поділяються на такі групи:

  • ключові (зарезервовані) слова;
  • ідентифікатори;
  • літерали (константи);
  • знаки операцій.

Ключове слово – це лексема, яка має єдиний наперед визначений сенс в програмному коді. Набір ключових слів обмежений. Усі ключові слова є зарезервованими. Зарезервовані слова не можна використовувати як ідентифікатори. У стандартному С++ є 63 ключових слова. Ще 11 слів зарезервовано для альтернативного представлення операцій. Нижче наводиться список ключових слів:

asm auto bool break case
catch char class const const_cast
continue default delete do double
dynamic_cast else enum explicit export
extern false float for friend
goto if inline int long
mutable namespace new operator private
protected public register reinterpret_cast return
short signed sizeof static static_cast
struct switch template this throw
true try typedef typeid typename
union unsigned using virtual void
volatile wchar_t while    

Ідентифікатори використовуються для іменування змінних, функцій та інших програмних об'єктів. Першим символом повинна бути літера чи символ підкреслення ("_", underscore character). Далі можуть використовуватися також цифри. Використання символу підкреслення на початку імені не є бажаним, оскільки такі імена дуже часто використовуються як системні у стандартних бібліотеках.

Доцільно використовувати змістовні імена, які показують природу об'єкта або функції. Не можна використовувати пропуски всередині ідентифікатора. Тому, якщо необхідно створити ідентифікатор з кількох слів, можна використовувати один з двох підходів:

  • стиль C: між словами замість пропуску вживають символ підкреслення ("_");
  • стиль C++: слова пишуть злито, починаючи друге, третє та інші слова з великої літери (camel notation).

Наприклад, можна створити такі імена змінних:

this_is_my_variable
thisIsMyVariable

Стиль C++ є більш бажаним.

Для змістовних імен доцільно використовувати англійську мнемоніку. Імена класів і структур слід починати з великої літери, інші імена – тільки з маленької.

Коментарі – це текст всередині сирцевого коду, який не обробляє компілятор. Коментар призначений для інформування читача (програміста) про сенс типів, об'єктів і дій у сирцевому коді програми. Мова C++ підтримує два види коментарів:

  • у стилі C++ – починаючи з символів // компілятор проігнорує весь текст до кінця поточного рядка;
  • у стилі C – починаючи з символів /* до символів */ у цьому або в наступних рядках весь текст буде проігноровано компілятором; всередині тексту коментаря не можуть зустрічатися пари /* та */.

Як правило, велика програма повинна мати коментарі на початку, де роз'яснюється призначення програми. Кожна велика функція повинна також мати коментарі, які пояснюють, що робить ця функція і які значення вона повертає.

2.2 Фундаментальні типи даних

Фундаментальні типи даних у C++, як і в усіх мовах програмування, можна об'єднати у дві групи:

  • дані цілого типу;
  • дані з рухомою комою (дійсні).

До групи цілих типів належать такі типи: int, long, short, char та bool. Усі цілі типи, крім bool, можуть бути звичайними цілими зі знаком (signed) і цілими без знаку (unsigned). Цілі числа без модифікатора unsigned вважаються зі знаком. Цілі числа зі знаком є або від’ємними, або додатними. Беззнакові цілі числа завжди додатні. Символьні дані (char) розглядаються як малі цілі числа.

За числом розрядів, які використовуються для представлення даних (діапазону значень) розрізняють звичайні цілі (int), короткі цілі (short int) та довгі цілі (long int).

Змінні з рухомою крапкою (комою) мають значення, які можна виразити дробом. Іншими словами, вони є дійсними числами. Представлення даних у форматі з рухомою крапкою, або рухомою комою (floating point numbers) дозволяє зберігати дробові значення у формі мантиса*2порядок. Така форма дозволяє зберігати дані дуже широкого діапазону. Група типів з рухомою крапкою містить такі стандартні типи: float, double і long double. Представлення double і long double ідентичні.

У таблиці наведені розміри в байтах і діапазон значень фундаментальних типів:

Категорія Ім'я типу Розгорнуте ім'я Розмір у байтах Діапазон значень
Цілі типи int signed int 4 від -2 147 483 648 до 2 147 483 647
unsigned unsigned int 4 від 0 до 4 294 967 295
char signed char 1 від -128 до 127
unsigned char   1 від 0 до 255
short short int, signed short int 2 від -32 768 до 32 767
unsigned short unsigned short int 2 від 0 до 65 535
long long int, signed long int 4 збігається з int
unsigned long unsigned long int 4 збігається з unsigned int
bool   1 false або true (0 або 1)
Типи з рухомою крапкою float   4 3.4E +/- 38 (7 цифр)
double   8 1.7E +/- 308 (15 цифр)
long double   8 збігається з double

Дані символьного типу (char) мають подвійну природу, а саме: можуть розглядатися як цілі (коди символів) і бути зі знаком і без знака, а також як символи, наприклад '#' . Символьна змінна в одному байті дозволяє зберігати код символу ASCII-таблиці. ASCII означає American Standard Code for Information Interchange (американський стандартний код для обміну інформацією).

Для визначення констант логічного типу (bool) використовують ключові слова true і false. Можна також використовувати 1 і 0 замість true і false.

2.3 Константні значення

Константне значення (неіменована константа) – це лексема, яка представляє певне числове або символьне значення, а також рядок символів.

Константи цілого типу записуються як послідовності цифр. Тип константи залежить від числа цифр у записі константи і може бути уточнений додаванням наприкінці константи букв L чи l (тип long), U або u (тип unsigned) або у сполученні. Цілі константи можуть записуватися у вісімковій системі числення, у цьому випадку першою цифрою повинна бути цифра 0, число може містити тільки цифри 0...7. Цілі константи можна записувати й у шістнадцятковій системі числення. У цьому випадку запис константи починається із символів 0x чи 0X. Для позначення цифр понад 9 використовуються латинські букви a, b, c, d, e та f (великі або маленькі).

Константи символьного типу char беруть в одиночні лапки (апострофи), значення константи визначається знаком з поточного набору символів або цілою константою, якій передує зворотна коса риска (символ із заданим кодом). Є ряд спеціальних символів, що можуть використовуватись як значення константи типу char (такі подвійні символи називаються послідовностями керування, або escape-послідовностями):

Послідовність керування Що представляє
  \a звуковий сигнал
  \b повернення (backspace)
  \f нова сторінка
  \n новий рядок
  \r перехід на початок рядку
  \t горизонтальна табуляція
  \v вертикальна табуляція
  \' одиночні лапки (апостроф)
  \" подвійні лапки
  \\ зворотна коса риска (backslash)
  \ooo ASCII-символ у вісімковій нотації
  \xhh ASCII-символ у шістнадцятковій нотації

Константи дійсних типів можуть записуватись у формі з рухомою крапкою або в експонентному форматі та усталено мають тип double (число з рухомою крапкою подвійної точності). За необхідності тип константи можна уточнити, записавши наприкінці суфікс f чи F для типу float, суфікс l чи L для типу long double. Можна також явно використовувати суфікси d і D для double. Наприклад:

1.5f    // 1.5 типу float
2.5E-2d // 0.25 типу double

Константа-рядок (літерал, string literal) складається із символів, які беруть у подвійні лапки (" "). Наприклад:

"Це рядок"

Константа-рядок може включати escape-послідовності. Якщо між двома константами нічого немає, окрім пропусків та переходів на новий рядок, компілятор поєднує їх в одну. Константа-рядок подається масивом символів, що містить, крім символів рядка, завершальний символ з кодом 0 ('\0'). Такі рядки називаються рядками, які закінчуються нульовим закінченням (null-terminated strings).

2.4 Визначення змінних та іменованих констант

Змінна – це іменована область пам'яті, до якої є доступ у програмі. Кожна змінна має певний тип. Зі змінною пов'язується її значення, яке зберігається в певному місці пам'яті (rvalue – праве значення, у присвоюванні стоїть праворуч), і її місце розташування, тобто адреса в пам'яті, за якою зберігається її значення (lvalue – ліве значення).

Змінна може бути оголошена і повинна бути визначена. Синтаксис оголошення змінних буде розглянуто нижче у контексті створення заголовних файлів.

Визначення змінної викликає виділення пам'яті. Визначення задає ім'я змінної та її тип і закінчується крапкою з комою:

ім'я_типу ім'я_змінної;

Доцільно вживати змістовні імена змінних. Змінні з короткими іменами (з однієї літери) повинні мати обмежену видимість.

Якщо необхідно визначити кілька змінних однакового типу, їхні ідентифікатори записуються через кому:

int x, y; // Дві цілих змінних з невизначеними значеннями 

Оголошення і визначення спільно називаються описами. Слід зауважити, що коли в описах відсутнє ім'я типу але присутні інші модифікатори (long, short, signed, unsigned), припускається, що змінна має тип int. Наприклад,

short s;    // коротке ціле
unsigned s; // беззнакове ціле 

Певне значення змінній можна присвоїти за допомогою оператора присвоєння (=). В такий спосіб можна присвоїти значення 5 змінній width, написавши

unsigned short width;
width = 5;

Визначення часто об'єднують з ініціалізацією:

unsigned short width = 5;

Ініціалізація дуже схожа на присвоювання. Істотна різниця в тому, що ініціалізація відбувається в момент створення змінної.

Повинно бути одне і тільки одне визначення змінної в програмі. Змінна не може використовуватись до її визначення. Ініціалізувати змінну можна також константою в дужках:

int a = 10; // Змінна цілого типу з початковим значенням 10
int b(10);  // Те ж саме 

Можна ініціалізувати більше однієї змінної під час створення. Наприклад:

long width = 5, length = 7; 

Можна об'єднувати визначення та ініціалізацію в різних комбінаціях:

int myAge = 39, yourAge, hisAge = 40;

В останньому прикладі визначені три цілих змінних, але ініціалізовані лише перша та третя.

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

double x = 10; 
int n = 10.5;  // отримали цілу частину, n = 10

Для подання константних величин можуть використовуватися так звані іменовані константи (symbolic constants). Значення іменованої константи не можна змінювати в програмі. Приклад опису константи:

const int yearOfBirth = 1901; 

Тут yearOfBirth – це константа типу int. Її значення не може бути змінене.

Якщо під час опису ім'я типу було пропущене, константа вважається цілою. Наприклад:

const n = 2; // те саме, що const int n = 2;

Слід запобігати подібному стилю опису констант.

У версії C++ 2011 року (C++11) до синтаксису мови додано механізм автоматичного визначення типів, який дозволяє компілятору створювати локальні змінні, тип яких залежить від контексту. Для опису таких змінних застосовують ключове слово auto. Такі змінні обов'язково повинні бути ініціалізовані. Тип змінної компілятор визначає відповідно до типу виразу, яким ця змінна ініціалізується. Наприклад:

auto i = 10;   // ціла змінна
auto x = 10.5; // змінна типу double

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

2.5 Вирази та операції

2.5.1 Вирази, операції та операнди

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

Операція – це атомарний (неподільний) вираз. Об'єктами операцій є операнди. Залежно від кількості операндів виділяють унарні операції (один операнд), бінарні операції (два операнди) і тернарні операції (три операнди).

Знак операції (operator) – це символ (або декілька символів), який визначає дію над операндами.

2.5.2 Присвоювання

Оператор присвоювання (=) обумовлює зміну значення лівого операнду на значення виразу, який знаходиться праворуч. Вираз

x = a + b;

присвоює операнду x значення, яке є результатом додавання a до b.

Результатом операції присвоювання є значення того виразу, що присвоюється лівому операнду. Це значення може бути використане у виразі. Наприклад:

a = (b = (c = (d = 0))); // Ми присвоюємо 0 всім змінним

Дужки не потрібні, оскільки оператор присвоювання виконується справа наліво:

a = b = c = d = 0; 

2.5.3 Математичні операції

Існує п'ять математичних операцій: додавання (+), віднімання (-), множення (*), ділення (/) і залишок від ділення (%). Є два способи ділення: ділення з рухомою крапкою (комою) і ціле ділення. Перший варіант працює, як можна було б очікувати:

double a = 10;
double b = 4;
double c = a / b; // c = 2.5

Операція ділення двох цілих чисел повертає цілу частину результату:

int a = 10;
int b = 4;
int c = a / b;    // c = 2
double d = a / b; // d = 2.0

Примітка: результат залежить від типів операндів. Компілятор ігнорує тип змінної, яка отримує результат.

Оператор отримання залишку від ділення можна застосовувати тільки до цілих операндів:

int a = 10;
int b = 3;
int c = a % b;    // c = 1

Обидва операнди повинні бути цілими числами.

У кожної операції є свій пріоритет. Множення має більш високий пріоритет, ніж складання. Для зміни порядку виконання операцій можна використовувати дужки.

2.5.4 Складене присвоювання

Складене присвоювання можна представити в загальному вигляді в такий спосіб:

a op= b 

У нашому випадку op – арифметична чи побітова операція: + - * / % | & ^ << >>. Кожна складена операція еквівалентна такому присвоюванню:

a = (a) op (b);

Наприклад,

x += 5;

еквівалентно виразу

x = x + 5;

Операція складеного присвоєння також повертає результат – нове значення змінної.

2.5.5 Операції інкременту та декременту

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

k = 1;
// Інкремент:
++k; // тепер k = 2, еквівалентно k += 1
// Декремент:
--k; // тепер знову k = 1, еквівалентно k -= 1

Операції інкременту та декременту мають дві форми: префіксну і постфіксну. Префіксна форма забезпечує збільшення або зменшення змінної до того, як значення операції буде використано, а постфіксна – після. Наприклад:

k = 1;
// Префіксна форма:
n = ++k; // k = 2, n = 2
k = 1;
// Постфіксна форма:
n = k++; // k = 2, n = 1

Аналогічний приклад можна навести для декременту.

2.5.6 Операції відношення

Операції відношення (relational operators) використовують для порівняння значень операндів. Результатом операції відношення може бути 1 (true) або 0 (false). Наведена нижче таблиця містить операції відношення:

Назва Оператор Приклад Результат
дорівнює == 100 == 50; false
50 == 50; true
не дорівнює    != 100 != 50; true
50 != 50; false
більше > 100 > 50; true
50 > 50; false
більше або дорівнює >= 100 >= 50; true
50 >= 50; true
менше < 100 < 50; false
50 < 50; false
менше або дорівнює <= 100 <= 50; false
50 <= 50; true

Слід відрізняти операцію присвоювання (=) від операції порівняння (==). Неправильне вживання присвоювання замість порівняння може призвести до дуже неприємних помилок.

2.5.7 Логічні операції

Логічні операції (logical operators) використовуються для об'єднання декількох умов. У наведеній нижче таблиці наведено логічні операції:

Операція Оператор Приклад
І && вираз1 && вираз2
АБО || вираз1 || вираз2
НЕ ! !вираз

Логічна операція І оцінює два вирази, і якщо обидва вирази істинні, результат логічного І також "істина" (true). В інших випадках результат буде хибним. Логічна операція АБО оцінює два вирази, і якщо обидва вирази хибні, то результат операції АБО також хибний. В інших випадках результат істинний. Логічна операція НЕ дає результат true, якщо вираз хибний, і навпаки.

Примітка: якщо замість пар символів && і || вживати & і | , замість логічних ми отримаємо так звані побітові операції (bitwise operators), які будуть розглянуті окремо.

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

2.5.8 Операція "кома"

Операція "кома" дозволяє замість одного виразу записувати кілька виразів, розділених комами. Ці вирази обчислюються зліва направо. Типом і значенням результату є тип і значення правого (останнього) виразу. Наприклад, вираз

x = (i = 0, j = i + 4, k = j); 

еквівалентний виразу

i = 0; j = i + 4; k = j; x = k; 

Дужки в цьому прикладі є обов'язковими.

2.5.9 Умовна операція

Умовна операція (?:) – це єдина тернарна операція C++ (операція, яка приймає три операнди).

вираз1 ? вираз2 : вираз3

Цей рядок слід читати так "Якщо вираз1 істинний, повертаємо значення вираз2, в іншому випадку повертаємо значення вираз3". Зазвичай це значення буде присвоєно змінній. Наприклад:

min = a < b ? a : b;

У наведеному рядку мінімальне значення з a і b присвоюється змінній min.

2.5.10 Операція sizeof

Унарна операція sizeof повертає розмір операнда у байтах. Операнд операції sizeof може бути одним з таких:

  • ім'я типу; щоб використовувати sizeof з ім'ям типу, ім'я має бути взяте в круглі дужки;
  • вираз; у цьому випадку операнд може бути вказаний з або без дужок; вираз не обчислюється.

Приклад 3.1 демонструє використання операції sizeof.

2.5.11 Використання операцій для введення і виведення даних

У мові C++ немає окремих операцій для введення і виведення. Стандартна бібліотека C надає функції scanf() і printf(), які забезпечують відповідно консольне введення і виведення. Використання цих функцій буде розглянуто пізніше.

Замість функцій scanf() і printf(), які не забезпечують належного контролю типів, мова C++ пропонує більш надійний і зручний спосіб для введення і виведення даних. Цей спосіб побудовано на використанні потоків введення-виведення. Мова C++ дозволяє перевизначати операції для об'єктів стандартних і нестандартних класів. Для класу, який представляє потік введення, визначена операція зсуву праворуч (>>). Ця операція здійснює читання з потоку окремих лексем і перетворення їх у дані, які відповідають типам змінних – операндів, які розташовані ліворуч від об'єкта-потоку. Аналогічно, для потоку виведення визначена операція зсуву ліворуч (<<) для виведення даних. Формат виведення визначається типами змінних. Можна також виводити константи, зокрема, літерали. Для переведення курсору на новий рядок у потік виведення заносять маніпулятор endl.

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

int k;
double x;
char c;
cin >> k >> x >> c;
cout << k << " " << x << " " << c << endl;
// без символів пропусків дані будуть зшиті в одну лексему

Існують додаткові можливості форматування даних під час виведення. Наприклад, додавання в потік setw(n) обумовлює відведення n позицій для виведення поточних даних.

Для роботи зі стандартними потоками до сирцевого коду слід додати підключення заголовного файлу iostream. Засоби для роботи з потоками визначені в просторі імен std.

2.6 Твердження

2.6.1 Поняття твердження. Порожнє твердження

У мові C++ твердження (інструкція, statement, іноді оператор) є мінімальною одиницею програмного коду. Вирази та операції входять в твердження. Послідовність тверджень реалізує певний алгоритм.

Можна навести таку класифікацію тверджень:

  • твердження опису
  • порожнє твердження
  • твердження-вираз
  • складене твердження
  • твердження вибору
  • твердження циклу
  • твердження переходу
  • твердження перехоплення й обробки винятків.

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

Порожнє твердження (null statement) – це просто крапка з комою. Порожнє твердження вживають, коли твердження необхідне синтаксично, але не треба виконувати ніяких дій.

2.6.2 Твердження-вираз

Усі вирази, які завершуються крапкою з комою, є твердженнями-виразами (expression statements). Одним з найбільш вживаних тверджень є твердження з виразом присвоювання:

a = x + 1;

Мова C++ дозволяє створювати твердження з операцій, які не містять присвоєння:

x + 1;
a;

Здебільшого такі твердження не мають сенсу, оскільки результат операції ніде не зберігається. Але саме так здійснюється виклик функцій з результатом void, наприклад:

system("pause");

Подібні твердження також використовують для введення та виведення даних:

cin >> x;
cout << x;

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

#include <iostream>

using namespace std;

int main()
{
    int a, b, c;
    cout << "Уведіть a та b: ";
    cin >> a >> b;
    c = a + b;
    cout << "Сума: " << c << endl;
    return 0;
}

Цю програму можна модифікувати. Створення та обчислення c можна об’єднати:

    ...
    int a, b;
    cout << "Уведіть a та b: ";
    cin >> a >> b;
    int c = a + b;
    ...

Програму можна спростити. Створення змінної c не має сенсу. Ми можемо розрахувати суму безпосередньо перед її виведенням. Тож остаточна версія буде такою:

#include <iostream>

using namespace std;

int main()
{
    int a, b;
    cout << "Уведіть a та b: ";
    cin >> a >> b;
    cout << Сума: " << (a + b) << endl;
    return 0;
}

Тепер компілятор не створює зайву комірку в пам'яті. Зайве копіювання також не виконується.

2.6.3 Складене твердження

Складене твердження – це послідовність тверджень, укладена у фігурні дужки {}. Складене твердження часто іменують блоком. Після фігурної дужки, яка закриває блок, крапка з комою не ставиться. Синтаксично блок може розглядатися як окрема твердження. Наприклад:

{
    temp = a;
    a = b;
    b = temp;
}

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

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

2.6.4 Твердження вибору

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

Умовне твердження (твердження if) забезпечує розгалуження відповідно до умови. Умовне твердження застосовується в двох видах:

Перша форма:

if (умова)
    твердження

Друга форма:

if (вираз-умова)
    твердження1
else
    твердження2

Під час виконання твердження if у першій формі обчислюється вираз-умова і якщо це істина (true, значення, відмінне від 0), то виконується твердження. Якщо вираз-умова повертає 0 (false), ніякі дії не виконуються.

У другій формі виконується твердження1, якщо вираз-умова повертає значення, відмінне від 0 (true), твердження2 виконується, якщо результат виразу-умови інтерпретується як 0 (false). Вираз-умова може бути будь-якого типу, що приводиться до цілого.

Якщо необхідно виконати декілька дій в одній з гілок, використовують складене твердження (блок). Наприклад:

if (i > 0)
    y = x * i;
else
{
    x = i;
    y = f(x);
} 

У цьому прикладі твердження y = x * i виконується, якщо i більше 0. Якщо i менше або дорівнює 0, значення i присвоюється змінній x, а результат функції f(x) записується в y.

Примітка: належна практика кодування рекомендує завжди використовувати складене твердження у конструкції if і циклах, навіть, якщо виконується одне твердження.

Наведений у попередній лабораторній роботі алгоритм обчислення зворотної величини можна програмно реалізувати за допомогою умовного твердження:

#include <iostream>

using namespace std;

int main()
{
    double x;
    cin >> x;
    if (x == 0)
    {
        cout << "Error" << endl;
    }
    else
    {
        double y = 1 / x;
        cout << y << endl;
    }
    return 0;
}

Використання if (x = 0) замість if (x == 0) не спричинить помилок компілятора, але призведе до неприємних наслідків.

Як видно з наведеного прикладу, в обох гілках твердження if розташовано складені твердження відповідно до належної практики кодування. Крім того, змінна y визначена всередині блоку лише в одній з гілок. Такий стиль вважається більш коректним, ніж визначення всіх змінних заздалегідь.

Наведена нижче програма обчислює результат ділення:

#include <iostream>

using namespace std;

int main()
{
    double a, b;
    cout << "Enter a and b: ";
    cin >> a >> b;
    if (a != 0)
    {
        double c = a / b;
        cout << "Quotient is " << c << endl;
    }
    else
    {
        cout << "Error" << endl;
    }
    return 0;
}

Для розгалуження на декілька гілок використовують конструкцію switch (перемикач).

switch (вираз_керування)
{
    /* тіло */
}

Тіло твердження switch складається з послідовності case-міток та необов'язкової мітки default. Мітка складається з ключового слова case після якого розташований константний вираз. Не повинно бути двох константних виразів з однаковими значеннями. Мітка default може з'явитися лише один раз.

Виконання перемикача складається з обчислення виразу керування і переходу до групи тверджень, позначених case-міткою, значення якої дорівнює виразу керування. Якщо такої мітки немає, виконуються твердження після мітки default. Під час виконання перемикача відбувається перехід на твердження з обраною міткою, і далі твердження виконуються у нормальному порядку. Для того щоб не виконувати твердження, які залишилися у тілі перемикача, необхідно використовувати твердження break. Наприклад:

switch (i)
{
    case 1:
        cout << "дорівнює 1"; 
        break;
    case 2:
        cout << "дорівнює 2, ";
    default: 
        cout << "не дорівнює 1";
}

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

i Виведення
1 дорівнює 1
2 дорівнює 2, не дорівнює 1
інші значення не дорівнює 1

Особливістю перемикача є вимога до виразу керування – він повинен бути цілим.

2.6.5 Циклічні твердження

Твердження циклу представлені в трьох варіантах:

  • цикл з передумовою,
  • цикл з постумовою
  • цикл з параметром.

Цикл з передумовою будується за схемою

while (вираз-умова)
    твердження

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

Цикл з постумовою будується за схемою

do
    твердження
while (вираз-умова); 

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

Цикл з параметром будується за схемою

for (вираз1; вираз2; вираз3)
    твердження

Цикл з параметром реалізує такий алгоритм:

  • обчислюється вираз1 (зазвичай цей вираз виконує підготовку до початку циклу);
  • обчислюється вираз2 і якщо він дорівнює нулю, виконується перехід до наступного твердження програми (вихід з циклу);
  • якщо вираз2 не дорівнює нулю, виконується твердження – тіло циклу;
  • обчислюється вираз вираз3 – виконується підготовка до повторення циклу, після чого знову виконується вираз вираз2.

Наведені нижче приклади демонструють використання тверджень циклу для обчислення суми:

y = 12 + 22 + 32 + ... + n2

З використанням циклу while:

int y = 0;
int i = 1;
while (i <= n)
{
    y += i * i;
    i++;
}

З використанням циклу do ... while:

int y = 0;
int i = 1;
do
{
    y += i * i;
    i++;
}
while (i <= n);

З використанням циклу for:

int y = 0;
for (int i = 1; i <= n; i++)
{
    y += i * i;
}

Твердження

for( ; ; );

створює нескінченний цикл.

2.8.6 Твердження break, continue і goto

У сполученні з твердженнями циклу іноді використовують твердження переходуbreak, яке дозволяє перервати виконання поточного циклу, і continue, яке перериває поточну ітерацію циклу while, do або for та обумовлює достроковий перехід на наступну ітерацію. Найчастіше break і continue використовують у таких конструкціях:

if (умова_дострокового_завершення_циклу)
    break;
if (умова_дострокового_завершення_ітерації)
    continue;

Твердження goto дозволяє перейти на мітку. Мітка – ідентифікатор із двокрапкою, що стоїть перед твердженням. Використання твердження goto у більшості випадків недоцільне, оскільки існують структурні конструкції цикли й блоки. Єдиний випадок, коли використання goto виправдане, – це переривання декількох вкладених циклів. Наприклад:

  int a;
  . . .
  double b = 0;  
  for (int i = 0; i < 10; i++)
  {
      for (int j = 0; j < 10; j++)
      {
          if (i + j + a == 0)
          {
              goto label;
          }
          b += 1 / (i + j + a);
      }
  }
label:
  // інші твердження

Мітка – це унікальний ідентифікатор, за яким слідує двокрапка. У нашому прикладі мітка називається label.

3 Приклади програм

3.1 Кількість байтів пам'яті для зберігання даних різних типів

Програма дозволяє отримати кількість байтів пам'яті для зберігання даних різних типів:

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"UKRAINIAN");
    cout << "Розмiр int дорiвнює:\t\t"     << sizeof(int)    << " bytes.\n";
    cout << "Розмiр short int дорiвнює:\t" << sizeof(short)  << " bytes.\n";
    cout << "Розмiр long int дорiвнює:\t"  << sizeof(long)   << " bytes.\n";
    cout << "Розмiр char дорiвнює:\t\t"    << sizeof(char)   << " bytes.\n";
    cout << "Розмiр float дорiвнює:\t\t"   << sizeof(float)  << " bytes.\n";
    cout << "Розмiр double дорiвнює:\t"    << sizeof(double) << " bytes.\n";
    return 0;
}

3.2 Коди символів

Ця програма дозволяє друкувати коди символів, уведених з клавіатури:

#include <iostream>

using namespace std;

int main()
{
    char c;
    do
    {
        cin >> c;     // уводимо символ 
        int i = c;    // перетворюємо символ у ціле число
        cout << i << '\n'; // виводимо код
    }
    while (c != 'A');    // поки не отримаємо c == 'A'
    return 0;
}

Твердження do-while забезпечує виконання складеного твердження (блоку) кілька разів, поки визначена умова повторення (c != 'A') не отримає значення false.

3.3 Шістнадцяткові числа

Наведена нижче програма друкує шістнадцяткове представлення чисел:

#include <iostream>

using namespace std;

int main()
{
    int i;
    do
    {
        cin >> i;   // уводимо ціле
        cout << dec << i << ' ' << hex << i << '\n';
    }
    while (i != 0); // поки не отримаємо i == 0
    return 0;
}

З наведеного прикладу видно, що стан об'єктів-потоків можна змінити за допомогою так званих маніпуляторів. Можна встановити основу системи числення за допомогою dec (десятковий), oct (вісімковий), або hex (шістнадцятковий).

3.4 Ціла частина значень

У наведеній нижче програмі виводиться ціла частина чисел з рухомою крапкою:

#include <iostream>

using namespace std;

int main()
{
    double d;
    int i;
    do
    {
        cin >> d; // уводимо значення з рухомою крапкою
        i = d;    // перетворюємо double на int
        cout << d << ' ' << i << '\n';
    }
    while (i != 0);
    return 0;
}

3.5 Твердження if і умовна операція

Припустимо, необхідно створити програму, в якій здійснюється читання x та обчислення y відповідно до таблиці:

x y
менше, ніж -8 100
від -8 до 1 200
більше, ніж 1 300

3.5.1 Використання твердження if

#include <iostream>

using namespace std;

int main()
{
    double x;
    cin >> x;
    double y;
    if (x < -8)
    { 
        y = 100;
    }
    else
    {
        if (x <= 1)
        {
            y = 200;
        }
        else
        {
            y = 300;
        }
    }
    cout << y;
    return 0;
}

3.5.2 Використання умовної операції

#include <iostream>

using namespace std;

int main()
{
    double x;
    cin >> x;
    double y = x < -8 ? 100 : (x <= 1 ? 200 : 300);
    cout << y;
    return 0;
}

3.6 Лінійне рівняння

Умовне твердження може бути використана для розв'язання лінійного рівняння:

ax + b = 0

У прикладі 3.1 попередньої лабораторної роботи наведено алгоритм з розгалуженням, який розв'язує цю задачу. Програма, яка реалізує цей алгоритм, може бути такою:

#include <iostream>
using namespace std;

int main()
{
    double a, b;
    cout << "Enter a and b: ";
    cin >> a >> b;
    if (a != 0)
    {
        double x = -b / a;
        cout << "Root is " << x << endl;
    }
    else 
    {
        if (b == 0)
        {
            cout << "Infinite count of roots." << endl;
        }
        else
        {
            cout << "No roots." << endl;
        }
    }
}

3.7 Використання твердження switch

Припустимо, необхідно створити програму, в якій здійснюється читання дійсного x і цілого n та обчислення y відповідно до таблиці:

n y
1 x
2 x2
3 x3
4 1 / x
інші значення 0

Для реалізації цієї програми слід використати твердження switch:

#include <iostream>

using namespace std;

int main()
{
    double x, y;
    int n;
    cin >> x >> n;
    switch (n)
    {
        case 1:
            y = x;
            break; 
        case 2:
            y = x * x;
            break; 
        case 3:
            y = x * x * x;
            break;
        case 4:
            y = 1 / x;
            break;
        default:
            y = 0;
    }
    cout << y;
    return 0
}

3.8 Обчислення суми

Припустимо, необхідно реалізувати програму, в якій здійснюється читання x і n та обчислюється y:

y = 1/(x - 1) + 1/(x - 2) + ... + 1/(x - n)

 

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

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"UKRAINIAN");
    double x, y = 0;
    int i, n;
    cout << "Уведiть x i n: ";
    cin >> x >> n;
    for (i = 1; i <= n; i++)
    {
        if (x == i)
        {
            cout << "Помилка!\n";
            break;
        }
        y += 1/(x - i);
    }
    if (i > n) // сума обчислена
    {
        cout << "y = " << y << "\n";
    }
    return 0;
}

Альтернативне рішення:

#include <iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"UKRAINIAN");
    double x, y = 0;
    int i, n;
    cout << "Уведiть x i n: ";
    cin >> x >> n;
    for (i = 1; i <= n; i++)
    {
        if (x == i)
        {
            cout << "Помилка!\n";
            return 1; // після return жодних дій не виконується
        }
        y += 1/(x - i);
    }
    cout << "y = " << y << "\n";
    return 0;
}

3.9 Добуток

У наведеній нижче програмі ми вводимо x, k і n і обчислюємо y:

y = x (x + 1) (x + 2) (x + 3) ... (x + k - 1)(x + k + 1) ... (x + n)
#include <iostream>
using namespace std;

int main()
{
    setlocale(LC_ALL,"UKRAINIAN");
    double x, y = 1;
    int i, k, n;
    cout << "Input x, k, and n: ";
    cin >> x >> k >> n;
    for (i = 0; i <= n; i++)
    {
        if (i == k)
        {
            continue;
        }
        y *= (x + i);
    }
    cout << "y = " << y << "\n";
    return 0;
}

Альтернативна реалізація:

#include <iostream>
using namespace std;

int main()
{
    setlocale(LC_ALL,"UKRAINIAN");
    double x, y = 1;
    int i, k, n;
    cout << "Input x, k, and n: ";
    cin >> x >> k >> n;
    for (i = 0; i <= n; i++)
    {
        if (i != k)
        {
            y *= (x + i);
        }
    }
    cout << "y = " << y << "\n";
    return 0;
}

3.10 Добуток з переміжними знаками

У прикладі 3.3 попередньої лабораторної роботи наведено алгоритм обчислення добутку з переміжними знаками в співмножниках:

p = x(x – 1)(x + 2)(x – 3) ... (x + (–1)n n)

Програма, яка реалізує цей алгоритм, може бути такою:

#include <iostream>
using namespace std;

int main()
{
    double x;
    int n;
    cout << "Enter x, n: ";
    cin >> x >> n;
    int k = 1;
    double p = 1;
    for (int i = 0; i <= n; i++)
    {
        p *= x + i * k;
        k = -k;
    }
    cout << "Product = " << p;
    return 0;
}

3.11 Експонента

Припустимо, необхідно реалізувати програму, в якій здійснюється читання x і обчислення ex:

y = ex = 1 + x + x2/2! + x3/3! + ...

 

Цикл завершується, якщо новий доданок менше 0.00001.

#include<iostream>

using namespace std;

int main()
{
    setlocale(LC_ALL,"UKRAINIAN");
    double x, y = 0;
    double z = 1; // доданок  
    int i = 1;
    cout << "Уведіть x: ";
    cin >> x;
    while (z > 0.00001)
    {
        y += z;
        z *= x / i;
        i++;
    }
    cout << "y = " << y << "\n";
    return 0;
}

Альтернативна реалізація:

#include <iostream>

using namespace std;

int main()
{
    double x, y = 0;
    double z = 1; // доданок  
    cout << "Input x: ";
    cin >> x;
    for (int i = 1; z > 0.00001; i++)
    {
        y += z;
        z *= x / i;
    }
    cout << "y = " << y << "\n";
    return 0;
}

3.12 Вкладені цикли

У прикладі 3.4 попередньої лабораторної роботи наведено алгоритм обчислення суми добутків:

Обчислення реалізуємо за допомогою вкладених циклів. Програма може бути такою:

#include <iostream>
using namespace std;

int main()
{
    int n;
    cout << "Enter n: ";
    cin >> n;
    int sum = 0;
    for (int i = 1; i < n; i++)
    {
        int p = 1;
        for (int j = 1; j < n; j++)
        {
            p *= i + j * j;
        }
        sum += p;
    }
    cout << "Sum = " << sum;
}

4 Вправи для контролю

Завдання 1

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

Завдання 2

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

Завдання 3

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

Завдання 4

Розробити програму, в якій здійснюється читання дійсного x та обчислення y (математична функція signum) з використанням твердження if:

x y
менше 0 -1
0 0
більше 0 1

Завдання 5

Реалізувати попереднє завдання з використанням умовної операції.

Завдання 6

Розробити програму, в якій здійснюється читання цілого n та обчислення y відповідно до таблиці:

n y
0 2
1 4
2 5
3 3
4 1
інші значення 0

Для реалізації програми слід використати твердження switch.

5 Контрольні запитання

  1. У чому різниця між коментарями стилю C і стилю C++?
  2. Що таке препроцесор?
  3. У чому різниця між знаковими і беззнаковими цілими?
  4. Що таке константа?
  5. Як визначити шістнадцяткову константу?
  6. Що таке escape-послідовність?
  7. Що таке літерал?
  8. Що таке змінна?
  9. Як визначити змінну?
  10. Що таке іменована константа?
  11. Для чого використовують операцію sizeof?
  12. Що таке вираз?
  13. Що таке операція?
  14. У чому різниця між операціями присвоєння та складеного присвоєння?
  15. У чому різниця між префіксними і постфіксними операціями інкременту і декременту?
  16. Який тип операції відношення?
  17. Які є логічні операції?
  18. Що таке операція "кома"?
  19. Що таке умовна операція?
  20. Що таке твердження (твердження, statement)?
  21. Що таке твердження-вираз?
  22. Для чого використовують складене твердження (блок)?
  23. Опишіть синтаксис твердження if.
  24. Опишіть синтаксис твердження switch.
  25. Як використовувати твердження break всередині твердження switch?
  26. Для чого використовують твердження default?
  27. У чому різниця між циклом з передумовою і циклом з постумовою?
  28. У чому переваги твердження for?
  29. У чому різниця між твердженнями break і continue?

 

up