JavaRush /Курси /C++ SELF /Signed і unsigned

Signed і unsigned

C++ SELF
Рівень 8 , Лекція 1
Відкрита

1. Два світи цілих чисел: «зі знаком» і «без знака»

Якщо раніше цілі числа сприймалися як «ну, це просто числа», то сьогодні ми зробимо крок до зрілішого погляду: тип — це не просто коробочка для значення, а ще й контракт. Коли ви обираєте signed чи unsigned, то обираєте не «що модніше», а сенс: чи може значення бути відʼємним, чи це за логікою задачі неможливо.

Що означають signed і unsigned

Тип signed (наприклад, int) уміє зберігати відʼємні й додатні значення. Тип unsigned (наприклад, unsigned int) зберігає лише невідʼємні значення.

Простіше кажучи, signed — це «термометр» (там є мінус), а unsigned — це «лічильник людей у кімнаті» (мінуса не буває, хіба що якщо хтось пішов у паралельний всесвіт).

Ось коротка порівняльна таблиця:

Властивість Signed (int) Unsigned (unsigned)
Відʼємні значення Так Ні
Нуль Так Так
Додатні значення Так Так
Головна ідея «може бути мінус» «мінуса не буває за змістом»

І одразу невеликий приклад, щоб закріпити думку: це два різні типи.

#include <iostream>

int main() {
    int a = -10;
    unsigned b = 10;

    std::cout << "a = " << a << '\n'; // a = -10
    std::cout << "b = " << b << '\n'; // b = 10
}

2. unsigned — не «покращений int», а інший контракт

Поширена думка серед новачків звучить приблизно так: «unsigned же зберігає більше додатних чисел, отже він кращий». Це майже як сказати: «Вантажівка краща за велосипед, бо вміщує більше картоплі». Можливо, так… але спробуйте припаркувати вантажівку в квартирі. Тип обирають не за принципом «краще/гірше», а за тим, які значення можливі за змістом задачі.

Де unsigned трапляється на практиці

Unsigned найчастіше зʼявляється там, де значення за означенням не можуть бути відʼємними: довжини, кількості, розміри, індекси, байти, різні «лічильники чогось». Вам не потрібно вже зараз памʼятати все це досконало, але корисно заздалегідь знати: коли ви починаєте працювати зі стандартною бібліотекою, вона часто «мислить розмірами» як невідʼємними числами.

Нюанс про char і bool

Іноді студенти запитують: «А char — він signed чи unsigned?» І тут зʼявляється один із мовних нюансів: char і bool належать до цілочисельних (integral) типів, але char може бути як signed, так і unsigned — це залежить від компілятора та платформи.

Практичний висновок для новачка простий: якщо вам справді потрібна знаковість байта, пишіть явно signed char або unsigned char. А char поки що використовуйте для текстових символів.

3. Чому -1 перетворюється на велике число в unsigned

Зараз буде головне «вау» цієї лекції. У C++ запис unsigned x = -1; не є синтаксичною помилкою. Компілятор не зобовʼязаний сигналізувати про помилку. Він робить те, що дозволяє мова: перетворює значення до типу змінної.

І саме тут починається магія, схожа на фокус зі зниклою монеткою: ми поклали -1, а дістали «величезне додатне число».

Мініприклад: «мінус один» стає великим

#include <iostream>

int main() {
    unsigned x = -1;   // компілюється
    std::cout << x << '\n'; // зазвичай щось дуже велике (залежить від розрядності)
}

За змістом відбувається ось що: unsigned не вміє зберігати -1. Тому значення перетворюється на відповідне число з діапазону unsigned за правилами мови. На поширених платформах, де unsigned часто 32-бітний, ви побачите 4294967295, але важливо памʼятати: конкретне число залежить від розміру типу у вашій системі.

Щоб не привʼязуватися до конкретних 32 бітів, корисно тримати в голові таку математичну картинку: unsigned-арифметика працює ніби по колу.

flowchart LR
    A["signed: ... -2 -1 0 1 2 ..."] --> B["перетворення на unsigned"]
    B --> C["unsigned: 0 1 2 ... MAX"]
    D["-1"] --> B --> E["MAX (найбільше значення для unsigned)"]

Ідея така: якщо unsigned зберігає числа від 0 до MAX, то -1 після перетворення стає саме MAX. Тому в новачків часто виникає відчуття, що «мінус один перетворився на максимум».

Ще один приклад: «мінус» в unsigned не живе

#include <iostream>

int main() {
    unsigned u = 0;
    std::cout << (u - 1) << '\n'; // знову "величезне число"
}

Тут u - 1 не може стати відʼємним, бо результат має тип unsigned. Тому він «іде в кінець діапазону». Це не помилка компілятора, а властивість типу: у світі unsigned «нижче нуля» немає, і мова викручується за правилами подання.

4. Ініціалізація {} як захисна сітка

Ми вже говорили про різні форми ініціалізації, але сьогодні у фігурних дужок зʼявляється ще одна дуже практична роль: забороняти деякі небезпечні перетворення. Якщо звичайне = може «тихо» погодитися й перетворити значення, то {} часто ніби кажуть: «Ні-ні, друже, спочатку подумайте».

Приклад: = пропускає, {} — зупиняє

#include <iostream>

int main() {
    unsigned ok = -1;     // компілюється: значення перетворюється
    std::cout << ok << '\n'; // велике число

    unsigned bad{-1};     // зазвичай помилка компіляції: фігурні дужки забороняють таке
}

Ідея проста: {} намагаються захистити вас від «неочевидної зміни змісту». І в реальному коді це радше плюс: краще один раз побачити помилку компіляції, ніж потім годину дивитися в налагоджувач і шепотіти: «Чому індекс 4 мільярди?..»

5. Практика: де unsigned зʼявляється і як обирати тип

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

Де unsigned зʼявляється «сам собою»

У мові є операції та функції, які за своєю природою повертають невідʼємне значення. Наприклад, оператор sizeof повертає розмір у байтах — відʼємний розмір, погодьтеся, звучить як сюжет для фантастики про «антибайти». У стандартній бібліотеці багато «розмірів» і «довжин» теж живуть у світі без знака.

Поки що ми свідомо не заглиблюємося в конкретний тип для розмірів — до цього ми обережно підійдемо в наступних лекціях. Наше завдання зараз простіше: навчитися розпізнавати ситуацію, коли unsigned раптово входить у гру, і розуміти, що -1 тоді може перетворитися на «дуже багато».

Індекс із введення: чому краще почати із signed

Коли ви пишете інтерактивну програму, користувач може ввести що завгодно. Навіть якщо ви впевнені, що «нормальна людина» не введе -1, ваш користувач може виявитися дуже творчою особистістю. Або просто помилитися. Тому індекс, який ви зчитуєте з введення, часто розумніше тимчасово зберігати в signed-типі: так ви зможете чесно обробити відʼємні значення як помилку, а не як «перехід у паралельний всесвіт великих чисел».

Невдалий варіант: індекс одразу як unsigned.

#include <iostream>

int main() {
    unsigned idx = 0;

    std::cout << "Введіть індекс: ";
    std::cin >> idx;

    std::cout << "idx = " << idx << '\n'; // якщо ввести -1, побачите велике число
}

Якщо користувач введе -1, потік спробує зчитати це в unsigned-змінну. У результаті змінна отримає перетворене значення, і ви вже втратите інформацію про те, що користувач увів «мінус».

Кращий варіант для введення: індекс як int.

#include <iostream>

int main() {
    int idx = 0;

    std::cout << "Введіть індекс: ";
    std::cin >> idx;

    if (idx < 0) {
        std::cout << "Індекс не може бути відʼємним\n";
    } else {
        std::cout << "Індекс невідʼємний\n";
    }
}

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

Правило здорового глузду: якщо можливий мінус — рахуйте в signed

Є дуже корисна звичка, яка заощаджує купу нервів: якщо за логікою задачі проміжні значення можуть бути відʼємними (різниці, зміщення, «на скільки залишилося», «на скільки пішли в мінус»), тримайте обчислення в signed-типах. Unsigned не запобігає помилці — він просто перетворює її на дивний результат.

Уявіть: ви рахуєте, «скільки днів залишилося до дедлайну». Якщо дедлайн уже минув, коректна відповідь може бути відʼємною: «-2 дні» (тобто «на два дні пізніше»). Отже, signed — природний вибір.

А якщо ви рахуєте, «скільки яблук лежить у коробці», то відʼємна кількість яблук зазвичай означає, що хтось уже почав їсти коробку разом із яблуками. Тоді unsigned справді може відображати контракт: «мінуса не буває».

6. Типові помилки під час роботи із signed/unsigned

Помилка № 1: обирати unsigned «на всяк випадок».
Спочатку це здається логічним: «ну раз він зберігає більше додатних значень, візьму його». Проблема в тому, що unsigned змінює не лише діапазон, а й сенс обчислень. Щойно десь помилково зʼявляється відʼємне значення, ви не отримуєте зрозумілий мінус — ви отримуєте велике число, яке починає жити своїм життям і ламати перевірки.

Помилка № 2: писати unsigned x = -1; і очікувати, що там залишиться «мінус один».
У C++ тип важливіший за «написані символи». Запис -1 — це вираз, але значення змінної після ініціалізації має відповідати її типу. У світі unsigned мінусів немає, тому відбувається перетворення, і ви фактично кладете в змінну інше число. Якщо ви побачили в налагоджувачі величезний індекс або гігантський розмір, насамперед перевірте: чи не сталося саме такого перетворення.

Помилка № 3: ігнорувати фігурні дужки {} там, де вони могли б зупинити проблему.
Новачки часто звикають до = і використовують його всюди, бо «так простіше». Але фігурні дужки корисні саме там, де ви не хочете «тихої магії» перетворення. Якщо компілятор може попередити або навіть заборонити небезпечну ініціалізацію, це не його примха, а ваша майбутня економія часу.

Помилка № 4: зберігати користувацьке введення одразу в unsigned.
Користувач може помилитися, а ви маєте вміти це виявити. Якщо індекс одразу unsigned, то відʼємні значення перетворюються на великі, і ви втрачаєте шанс зробити нормальну перевірку idx < 0. Набагато спокійніше спочатку прийняти значення в signed, перевірити його на адекватність, а вже потім, за потреби, переходити у беззнаковий світ.

Помилка № 5: думати, що проблема в if, а не в типах.
Коли програма починає поводитися дивно, часто хочеться звинуватити умову: «Мабуть, я неправильно написав if». Але пастки signed/unsigned зазвичай виникають раніше: під час вибору типів і в перетвореннях. Умова лише чесно порівнює значення після того, як мова перетворила їх до узгодженого вигляду. Тому, коли бачите дивні числа, спочатку поставте собі питання: «А які тут типи, і чи не перетворився мій мінус на “дуже плюс”?»

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ