На цьому уроці ми розглянемо цілочисельні типи даних, їх діапазони значень, а також переповнення (що це таке і приклади).
Цілочисельні типи даних
Цілочисельний тип даних — це тип, змінні якого можуть містити тільки цілі числа (без дробової частини, наприклад: -2, -1, 0, 1, 2). У мові C++ є 5 основних цілочисельних типів, доступних для використання:
Тип | Мінімальний розмір | |
Символьний тип даних | char | 1 байт |
Цілочисельний тип даних | short | 2 байти |
int | 2 байти (але частіше 4 байти) | |
long | 4 байти | |
long long | 8 байт |
Примітка: Тип char — це особливий випадок: він є як цілочисельним, так і символьним типом даних. Детально про це ми поговоримо на одному з наступних уроків.
Основною відмінністю між вищеперерахованими цілочисельними типами є їх розмір, чим він більший, тим більше значень зможе зберігати змінна цього типу.
Оголошення цілочисельних змінних
Оголошення цілочисельних змінних відбувається наступним чином:
1 2 3 4 5 6 7 8 |
char c; short int si; // допустимо short s; // пріоритетно int i; long int li; // допустимо long l; // пріоритетно long long int lli; // допустимо long long ll; // пріоритетно |
У той час як повні назви short int
, long int
і long long int
можуть використовуватися, їх скорочені версії (без int
) є пріоритетними для використання. До того ж постійне додавання int
ускладнює читання коду (легко сплутати зі змінною).
Діапазони значень і знак цілочисельних змінних (signed і unsigned)
Як ви вже знаєте з попереднього уроку, змінна з n-ною кількістю біт може зберігати 2n можливих значень. Але що це за значення? Це значення, які знаходяться в діапазоні. Діапазон — це значення від і до, які може зберігати певний тип даних. Діапазон цілочисельної змінної визначається двома факторами: її розміром (в бітах) і її знаком (який може бути signed або unsigned).
Цілочисельний тип signed (зі знаком) означає, що змінна може містити як додатні, так і від’ємні числа. Щоб оголосити змінну як signed, використовуйте ключове слово signed
:
1 2 3 4 5 |
signed char c; signed short s; signed int i; signed long l; signed long long ll; |
За замовчуванням, ключове слово signed
пишеться перед типом даних.
1-байтова цілочисельна змінна зі знаком (signed
) має діапазон значень від -128 до 127. Будь-яке значення від -128 до 127 (включно) може зберігатися в ній безпечно.
У деяких випадках ми можемо заздалегідь знати, що від’ємні числа в програмі використовуватися не будуть. Це дуже часто зустрічається при використанні змінних для зберігання кількості або розміру чого-небудь (наприклад, ваш зріст або вага не може бути від’ємною).
Цілочисельний тип unsigned (без знаку) може містити тільки додатні числа. Щоб оголосити змінну як unsigned, використовуйте ключове слово unsigned
:
1 2 3 4 5 |
unsigned char c; unsigned short s; unsigned int i; unsigned long l; unsigned long long ll; |
1-байтова цілочисельна змінна без знаку (unsigned
) має діапазон значень від 0 до 255.
Зверніть увагу, оголошення змінної як unsigned
означає, що вона не може містити від’ємні числа (тільки додатні).
Тепер, коли ви зрозуміли різницю між signed
і unsigned
, давайте розглянемо діапазони значень різних типів даних:
Розмір/Тип | Діапазон значень |
1 байт signed | від -128 до 127 |
1 байт unsigned | від 0 до 255 |
2 байти signed | від -32 768 до 32 767 |
2 байти unsigned | від 0 до 65 535 |
4 байти signed | від -2 147 483 648 до 2 147 483 647 |
4 байти unsigned | від 0 до 4 294 967 295 |
8 байт signed | від -9 223 372 036 854 775 808 до 9 223 372 036 854 775 807 |
8 байт unsigned | від 0 до 18 446 744 073 709 551 615 |
Для математиків: Змінна signed
з n-ною кількістю біт має діапазон від -(2n-1) до 2n-1-1. Змінна unsigned
з n-ною кількістю біт має діапазон від 0 до (2n)-1.
Для нематематиків: Використовуємо таблицю 🙂
Початківці іноді плутаються між signed
і unsigned
змінними. Але є простий спосіб запам’ятати їх відмінності. Чим відрізняється від’ємне число від додатного? Правильно! Мінусом спереду. Якщо мінуса немає, значить число є додатним. Отже, цілочисельний тип зі знаком (signed
) означає, що мінус може бути присутнім, тобто числа можуть бути як додатними, так і від’ємними. Цілочисельний тип без знака (unsigned
) означає, що мінус спереду відсутній, тобто числа можуть бути тільки додатними.
Що використовується за замовчуванням: signed чи unsigned?
Так що ж станеться, якщо ми оголосимо змінну без вказівки signed
чи unsigned
?
Тип | За замовчуванням | |
Символьний тип даних | char | signed або unsigned (найчастіше signed) |
Цілочисельний тип даних | short | signed |
int | signed | |
long | signed | |
long long | signed |
Всі цілочисельні типи даних, крім char, є signed
за замовчуванням. Тип char може бути як signed
, так і unsigned
(але найчастіше signed
).
У більшості випадків ключове слово signed
не пишеться (воно і так використовується за замовчуванням), за винятком типу char (тут краще уточнити).
Програмісти, як правило, уникають використання цілочисельних типів unsigned
, якщо в цьому немає особливої потреби, так як зі змінними unsigned
помилок, за статистикою, виникає більше, ніж зі змінними signed
.
Правило: Використовуйте цілочисельні типи signed, замість unsigned.
Переповнення
Питання: “Що станеться, якщо ми спробуємо використовувати значення поза діапазону допустимих значень?”. Відповідь: “Переповнення”.
Переповнення (англ. “overflow”) трапляється при втраті біт через те, що змінній не було виділено достатньо пам’яті для їх зберігання.
На уроці №31 ми говорили про те, що дані зберігаються в бінарному (двійковому) форматі і кожен біт може мати тільки 2 можливих значення (0
або 1
). Ось як виглядає діапазон чисел від 0 до 15 в десятковій та двійковій системах:
Десяткова система | Двійкова система |
0 | 0 |
1 | 1 |
2 | 10 |
3 | 11 |
4 | 100 |
5 | 101 |
6 | 110 |
7 | 111 |
8 | 1000 |
9 | 1001 |
10 | 1010 |
11 | 1011 |
12 | 1100 |
13 | 1101 |
14 | 1110 |
15 | 1111 |
Як ви можете бачити, чим більше число, тим більше йому потрібно біт. Оскільки наші змінні мають фіксований розмір, то на них накладаються обмеження на кількість даних, які вони можуть зберігати.
Приклади переповнення
Розглянемо змінну unsigned
, яка складається з 4 біт. Будь-яке з двійкових чисел, перерахованих в таблиці вище, поміститься усередині цієї змінної.
Але що станеться, якщо ми спробуємо присвоїти значення, яке займає більше 4 біт? Правильно! Переповнення. Наша змінна зберігатиме тільки 4 найменш значущих (ті, що праворуч) біти, а всі інші біти — загубляться.
Наприклад, якщо ми спробуємо помістити число 21 в нашу 4-бітну змінну:
Десяткова система | Двійкова система |
21 | 10101 |
Число 21 займає 5 біт (10101). 4 біти справа (0101) помістяться в змінну, а крайній лівий біт (1) просто загубиться. Тобто наша змінна міститиме 0101, що дорівнює 101 (нуль спереду не рахується), а це вже число 5, а не 21.
Примітка: Про конвертацію чисел з двійкової системи в десяткову і навпаки є окремий урок.
Тепер давайте розглянемо приклад на практиці (тип short займає 16 біт):
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { unsigned short x = 65535; // найбільше значення, яке може зберігати 16-бітна змінна unsigned std::cout << "x was: " << x << std::endl; x = x + 1; // 65536 - це число більше нашого максимально допустимого числа з діапазону допустимих значень. Отже, відбудеться переповнення, тому що змінна x не може зберігати 17 біт std::cout << "x is now: " << x << std::endl; return 0; } |
Результат виконання програми:
x was: 65535
x is now: 0
Що сталося? А сталося переповнення, оскільки ми спробували присвоїти змінній x
значення, яке вона не може містити.
Для тих, хто хоче знати більше: Число 65 535 в двійковій системі числення представлено як 1111 1111 1111 1111. 65 535 — це найбільше число, яке може зберігати 2-байтова (16 біт) цілочисельна змінна unsigned, оскільки це число використовує всі 16 біт. Коли ми додаємо 1, то отримуємо число 65 536. Число 65 536 представлено в двійковій системі як 1 0000 0000 0000 0000 і займає 17 біт! Отже, найголовніший біт (яким є 1) втрачається, а всі 16 біт праворуч — залишаються. Комбінація 0000 0000 0000 0000 відповідає десятковому 0, що і є нашим результатом.
Аналогічним чином, ми отримаємо переповнення, використавши число, менше мінімального з діапазону допустимих значень:
1 2 3 4 5 6 7 8 9 10 |
#include <iostream> int main() { unsigned short x = 0; // найменше значення, яке 2-байтова змінна unsigned може містити std::cout << "x was: " << x << std::endl; x = x - 1; // переповнення! std::cout << "x is now: " << x << std::endl; return 0; } |
Результат виконання програми:
x was: 0
x is now: 65535
Переповнення призводить до втрати інформації, а це ніколи не є добре. Якщо є хоча б найменша підозра або припущення, що значенням змінної може бути число, яке виходить за рамки допустимих значень, то відразу використовуйте більший тип даних!
Правило: Ніколи не допускайте можливості виникнення переповнення в ваших програмах!
Ділення цілочисельних змінних
У мові C++ при діленні двох цілих чисел, де результатом є інше ціле число, все досить передбачувано:
1 2 3 4 5 6 7 |
#include <iostream> int main() { std::cout << 20 / 4 << std::endl; return 0; } |
Результат:
5
Але що станеться, якщо в результаті ділення двох цілих чисел ми отримаємо дробове число? Наприклад:
1 2 3 4 5 6 7 |
#include <iostream> int main() { std::cout << 8 / 5 << std::endl; return 0; } |
Результат:
1
У мові C++ при діленні цілих чисел результатом завжди буде інше ціле число. А такі числа не можуть мати дріб (дріб просто відкидається, а НЕ округляється!).
Розглянемо детально вищенаведений приклад: 8/5 = 1.6
. Як ми вже знаємо, при діленні цілих чисел, результатом є інше ціле число. Таким чином, дробова частина (0.6
) відкидається і залишається 1
.
Правило: Будьте обережні при діленні цілих чисел, оскільки будь-яка дробова частина завжди відкидається.