JavaRush /Курсы /C++ SELF /Signed vs unsigned

Signed vs 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};     // обычно ошибка компиляции: braces запрещают такое
}

Смысл: {} стараются защитить вас от «неочевидной смены смысла». И в реальном коде это часто хорошо: лучше один раз увидеть ошибку компиляции, чем потом час смотреть на дебаггер и шептать: «Почему индекс 4 миллиарда?..»

5. Практика: где unsigned появляется и как выбирать тип

Даже если вы сами не пишете unsigned, вы всё равно можете с ним столкнуться. И это важный момент, потому что ошибка обычно происходит не в момент объявления переменной, а позже — когда значения начинают сравниваться, вычитаться или участвовать в проверках.

Где unsigned появляется «сам»

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

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

Индекс из ввода: почему лучше начать с signed

Когда вы пишете интерактивную программу, пользователь может ввести что угодно. Даже если вы уверены, что «нормальный человек» не введёт -1, ваш пользователь может быть очень творческой личностью. Или просто ошибиться. Поэтому индекс позиции, который приходит из ввода, часто разумнее временно хранить в signed-типе: так вы сможете честно обработать отрицательные значения как ошибку, а не как «переход в параллельную вселенную больших чисел».

Плохой вариант: индекс сразу как unsigned.

#include <iostream>

int main() {
    unsigned idx = 0;

    std::cout << "Enter index: ";
    std::cin >> idx;

    std::cout << "idx = " << idx << '\n'; // если ввести -1, увидите большое число
}

Если пользователь введёт -1, поток попытается прочитать это в unsigned. В итоге переменная получит преобразованное значение, и вы уже потеряли информацию о том, что пользователь ввёл «минус».

Хороший вариант для ввода: индекс как int.

#include <iostream>

int main() {
    int idx = 0;

    std::cout << "Enter index: ";
    std::cin >> idx;

    if (idx < 0) {
        std::cout << "Index can't be negative\n";
    } else {
        std::cout << "Index looks non-negative\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-ловушки обычно рождаются раньше: в выборе типов и в преобразованиях. Условие всего лишь честно сравнивает значения после того, как язык привёл их к согласованному виду. Поэтому при странных числах сначала задайте себе вопрос: «А какие тут типы, и не превратился ли мой минус в “очень плюс”?»

1
Задача
C++ SELF, 8 уровень, 1 лекция
Недоступна
Кольцевой спидометр
Кольцевой спидометр
1
Задача
C++ SELF, 8 уровень, 1 лекция
Недоступна
Осторожный счётчик
Осторожный счётчик
1
Задача
C++ SELF, 8 уровень, 1 лекция
Недоступна
Символ по индексу
Символ по индексу
1
Задача
C++ SELF, 8 уровень, 1 лекция
Недоступна
Склад без провала
Склад без провала
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ