Урок №31. Целочисленные типы данных

  Юрий  | 

    | 

  Обновл. 25 Мар 2019  | 

 17786

 ǀ   11 

В этом уроке мы рассмотрим целочисленные типы данных, их диапазоны значений, деление, а также переполнение: что это такое и примеры.

Целочисленные типы данных

Целочисленный тип данных — это тип, переменные которого могут содержать только целые числа (без дробной части, например: -2, -1, 0, 1, 2). В C++ есть пять основных целочисленных типов, доступных для использования:

Категория Тип Минимальный размер
Символьный тип данных char 1 байт
Целочисленный тип данных short 2 байта
int 2 байта (но чаще всего 4 байта)
long 4 байта
long long 8 байт

Примечание: Тип char — это особый случай, он является как целочисленным, так и символьным типом данных. Об этом детальнее мы поговорим в одном из следующих уроков.

Основным различием между целочисленными типами выше является их размер, чем он больше, тем больше значений сможет хранить переменная этого типа.

Определение целочисленных переменных


Происходит следующим образом:

В то время как полные названия short intlong int и long long int могут использоваться, их сокращённые версии (без int) более предпочтительны для использования. К тому же постоянное добавление int затрудняет чтение кода (легко спутать с переменной).

Диапазоны значений и знак целочисленных типов данных

Как вы уже знаете из предыдущего урока, переменная с n-ным количеством бит может хранить 2возможных значений. Но что это за значения? Те, которые находятся в диапазоне. Диапазон — это значения от и до, которые может хранить определённый тип данных. Диапазон целочисленной переменной определяется двумя факторами: её размером (в битах) и её знаком (который может быть signed или unsigned).

signed (со знаком) означает, что переменная может содержать как положительные, так и отрицательные числа. Чтобы объявить переменную как signed, используйте ключевое слово signed:

По умолчанию, ключевое слово signed пишется перед типом данных.

1-байтовая целочисленная переменная со знаком (signed) имеет диапазон значений от -128 до 127. Любое значение от -128 до 127 (включительно) может храниться в ней безопасно.

В некоторых случаях мы можем заранее знать, что отрицательные числа в программе использоваться не будут. Это очень часто встречается при использовании переменных для хранения количества или размера чего-либо (например, ваш рост или вес не может быть отрицательным). Целочисленный тип unsigned (без знака) может содержать только положительные числа. Чтобы объявить переменную как unsigned, используйте ключевое слово unsigned:

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») случается при потере бит, из-за того, что переменной не было выделено достаточно памяти для их хранения.

В уроке №28 мы говорили о том, что данные хранятся в бинарном (двоичном) формате и каждый бит может иметь только 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 бит):

Результат выполнения программы выше:

x was: 65535
x is now: 0

Что случилось? Произошло переполнение, так как мы попытались впихнуть невпихуемое в переменную x.

Для тех, кто хочет знать больше: Число 65 535 в двоичной системе счисления представлено как 1111 1111 1111 1111. 65 535 — это наибольшее число, которое может хранить 2-байтовая (16 бит) целочисленная переменная без знака, так как это число использует все 16 бит. Когда мы добавляем 1, то получаем число 65 536. Число 65 536 представлено в двоичной системе как 1 0000 0000 0000 0000, и занимает 17 бит! Следовательно, самый главный бит (которым является 1) теряется, а все 16 бит справа — остаются. Комбинация 0000 0000 0000 0000 соответствует десятичному 0, что и является нашим результатом.

Аналогичным образом, мы получим переполнение, использовав число, меньше минимального из диапазона допустимых значений:

Результат выполнения программы выше:

x was: 0
x is now: 65535

Переполнение приводит к потере информации, а это никогда не приветствуется. Если есть хоть малейшее подозрение или предположение, что значением переменной может быть число, которое находится вне диапазона допустимых значений используемого типа данных — используйте тип данных побольше!

Правило: Никогда не допускайте возникновения переполнения в ваших программах!

Деление целочисленных переменных

В C++ при делении двух целых чисел, где результатом является другое целое число, всё довольно предсказуемо:

Результат:

5

Но что произойдёт, если в результате деления двух целых чисел мы получим дробное число? Например:

Результат:

1

В C++, при делении целых чисел, результатом всегда будет другое целое число. А такие числа не могут иметь дробь (она просто отбрасывается, не округляется!).

Рассмотрим детальнее пример выше: 8 / 5 = 1.6. Но, как мы уже знаем, при делении целых чисел, результатом является другое целое число. Таким образом, дробная часть (0.6) отбрасывается и остаётся 1.

Правило: Будьте осторожны при делении целых чисел, так как любая дробная часть всегда отбрасывается.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (163 оценок, среднее: 4,87 из 5)
Загрузка...

Комментариев: 11

  1. Аватар Ulugbek:

    так если при делении дробная часть отбрасывается ,то как создать калькулятор?Если он не будут выводить дробные числа.Или ответ стоит присвоить к переменной которая будет иметь тип float?

    1. Аватар Дед Максим:

      Ну так нужно указывать другой тип переменной(не целое число). Тогда будет дробь.

  2. Аватар Игорь...:

    Забавная история, почему этот урок так важен =)
    В игре Civilization есть баг с механикой агрессии и миролюбия. Суть такова, что агрессивность цивилизации измерялась по шкале от 1 до 10. Девятки и десятки были у всяких Чингисханов, Монтесум и Сталиных, а у духовного пацифиста Махатмы Ганди была единичка. И ещё были модификаторы — строй «республика» уменьшает агрессивность на 1, «демократия» — на 2. Соответственно, сразу же, как только индусы открывали Демократию, у Ганди становилась агрессивность −1.

    А теперь внимание. Эта переменная была однобайтная и строго неотрицательная(unsigned), от 0 до 255. Соответственно, агрессивность Махатмы Ганди становилась равна 255 из 10. Поэтому, построив у себя демократию, Ганди двигался рассудком, клепал ядрёные бомбы и умножал всех на ноль.

    1. Юрий Юрий:

      Действительно хороший пример 🙂 С unsigned нужно быть аккуратным.

  3. Аватар dbnz:

    Как тип char может быть отрицательным?

    1. Юрий Юрий:

      Тип char не может быть отрицательным. Он относится к целочисленным типам данных, диапазон его значений — от 0 до 127. В статье указано, что char — особый случай и о нем есть отдельный урок.

  4. Аватар Виталий:

    >>»Правило: Используйте целочисленные переменные signed, вместо unsigned.»

    Офигенное правило.
    unsigned изобрели просто так, чтоб им не пользоваться…

    1. Аватар Old G.B.:

      Претензии к автору предъявлять нельзя, он только лишь переводит.

      1. Юрий Юрий:

        Это нормально, я ведь учусь вместе с вами. Отвечая на вопросы и претензии — лучше начинаешь понимать суть изучаемого сам 🙂

    2. Юрий Юрий:

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

      1. Аватар Cerberus:

        На самом деле, совсем не факт. Я в последнее время стал много читать статьи от авторов одного статического анализатора (не называю имя, чтоб не было рекламы), в которых неоднократно всплывал такой простой факт: переполнение _знаковых_ переменных — это Undefined behaviour, т.е. компилятор имеет право сделать с ним всё что угодно, если нет явной просьбы зафиксировать тот или иной вариант; переполнение _беззнаковых_ переменных же — стандартно (модульная арифметика). Поэтому вероятность вляпаться в ошибки для знаковых переменных, скорее всего, даже выше, просто потому что UB вполне может работать "как задумано", пока не произойдёт какого-то внешнего изменения (хотя бы и обновления компилятора, в котором прилетела пачка новых оптимизаций).

Добавить комментарий

Ваш E-mail не будет опубликован. Обязательные поля помечены *