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-ловушки обычно рождаются раньше: в выборе типов и в преобразованиях. Условие всего лишь честно сравнивает значения после того, как язык привёл их к согласованному виду. Поэтому при странных числах сначала задайте себе вопрос: «А какие тут типы, и не превратился ли мой минус в “очень плюс”?»
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ