Урок №36. Литералы и магические числа

  Юрий  | 

  |

  Обновл. 11 Сен 2021  | 

 124540

 ǀ   25 

В языке C++ есть два вида констант: литеральные и символьные. На этом уроке мы рассмотрим литеральные константы.

Литеральные константы

Литеральные константы (или просто «литералы») — это значения, которые вставляются непосредственно в код. Поскольку они являются константами, то их значения изменить нельзя. Например:

С литералами типов bool и int всё понятно, а вот для литералов типа с плавающей точкой есть два способа определения:

Во втором способе определения, число после экспонента может быть и отрицательным:

Числовые литералы могут иметь суффиксы, которые определяют их типы. Эти суффиксы не являются обязательными, так как компилятор понимает из контекста, константу какого типа данных вы хотите использовать.

Тип данных Суффикс Значение
int u или U unsigned int
int l или L long
int ul, uL, Ul, UL, lu, lU, Lu или LU unsigned long
int ll или LL long long
int ull, uLL, Ull, ULL, llu, llU, LLu или LLU unsigned long long
double f или F float
double l или L long double

Суффиксы есть даже для целочисленных типов (но они почти не используются):

По умолчанию литеральные константы типа с плавающей точкой являются типа double. Для конвертации литеральных констант в тип float можно использовать суффикс f или F:

Язык C++ также поддерживает литералы типов string и char:

Литералы хорошо использовать в коде до тех пор, пока их значения понятны и однозначны. Это выполнение операций присваивания, математических операций или вывода текста в консоль.

Литералы в восьмеричной и шестнадцатеричной системах счисления


В повседневной жизни мы используем десятичную систему счисления, которая состоит из десяти цифр: 0, 1, 2, 3, 4, 5, 6, 7, 8 и 9. По умолчанию язык C++ использует десятичную систему счисления для чисел в программах:

В двоичной (бинарной) системе счисления всего 2 цифры: 0 и 1. Значения: 0, 1, 10, 11, 100, 101, 110, 111 и т.д.

Есть еще две другие системы счисления: восьмеричная и шестнадцатеричная.

Восьмеричная система счисления состоит из 8 цифр: 0, 1, 2, 3, 4, 5, 6 и 7. Значения: 0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12 и т.д.

Примечание: В восьмеричной системе счисления нет цифр 8 и 9, так что сразу перескакиваем от 7 к 10.

Десятичная система счисления 0 1 2 3 4 5 6 7 8 9 10 11
Восьмеричная система счисления 0 1 2 3 4 5 6 7 10 11 12 13

Для использования литерала из восьмеричной системы счисления, используйте префикс 0 (ноль):

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

10

Почему 10 вместо 12? Потому что std::cout выводит числа в десятичной системе счисления, а 12 в восьмеричной системе = 10 в десятичной.

Восьмеричная система счисления используется крайне редко.

Шестнадцатеричная система счисления состоит из 16 символов: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, А, В, С, D, Е, F.

Десятичная система 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
Шестнадцатеричная система 0 1 2 3 4 5 6 7 8 9 A B C D E F 10 11

Для использования литерала из шестнадцатеричной системы счисления, используйте префикс 0x:

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

15

Поскольку в этой системе 16 символов, то одна шестнадцатеричная цифра занимает 4 бита. Следовательно, две шестнадцатеричные цифры занимают 1 байт.

Рассмотрим 32-битное целое число из двоичной системы счисления: 0011 1010 0111 1111 1001 1000 0010 0110. Из-за длины и повторения цифр его сложно прочесть. В шестнадцатеричной системе счисления это же значение будет выглядеть следующим образом: 3A7F 9826. Такой удобный/сжатый формат записи является преимуществом шестнадцатеричной системы счисления, поэтому шестнадцатеричные значения часто используются для представления адресов или необработанных значений в памяти.

До C++14 использовать литерал из двоичной системы счисления было невозможно. Тем не менее, шестнадцатеричная система счисления может нам в этом помочь:

Бинарные литералы и разделитель цифр в C++14

В C++14 мы можем использовать бинарные (двоичные) литералы, добавляя префикс 0b:

Поскольку длинные литералы читать трудно, то в C++14 добавили возможность использовать одинарную кавычку ' в качестве разделителя цифр:

Если ваш компилятор не поддерживает C++14, то использовать бинарные литералы и разделитель цифр вы не сможете — компилятор выдаст ошибку.

Магические числа. Что с ними не так?


Рассмотрим следующий фрагмент кода:

В вышеприведенном примере число 30 является магическим числом. Магическое число — это хорошо закодированный литерал (обычно, число) в строке кода, который не имеет никакого контекста. Что это за 30, что оно означает/обозначает? Хотя из вышеприведенного примера можно догадаться, что число 30 обозначает максимальное количество учеников, находящихся в одном кабинете — в большинстве случаев, это не будет столь очевидным и понятным. В более сложных программах контекст подобных чисел разгадать намного сложнее (если только не будет соответствующих комментариев).

Использование магических чисел является плохой практикой, так как в дополнение к тому, что они не предоставляют никакого контекста (для чего и почему используются), они также могут создавать проблемы, если их значения необходимо будет изменить. Предположим, что школа закупила новые парты. Эта покупка, соответственно, увеличила максимально возможное количество учеников, находящихся в одном кабинете, с 30 до 36 — это нужно будет продумать и отобразить в нашей программе.

Рассмотрим следующий фрагмент кода:

Чтобы обновить число учеников в кабинете, нам нужно изменить значение константы с 30 на 36. Но что делать с вызовом функции setMax(30)? Аргумент 30 и константа 30 в коде, приведенном выше, являются одним и тем же, верно? Если да, то нам нужно будет обновить это значение. Если нет, то нам не следует вообще трогать этот вызов функции. Если же проводить автоматический глобальный поиск и замену числа 30, то можно ненароком изменить и аргумент функции setMax(), в то время, когда его вообще не следовало бы трогать. Поэтому вам придется просмотреть весь код «вручную», в поисках числа 30, а затем в каждом конкретном случае определить — изменить ли 30 на 36 или нет. Это может занять очень много времени, кроме того, вероятность возникновения новых ошибок повышается в разы.

К счастью, есть лучший вариант — использовать символьные константы. О них мы поговорим на следующем уроке.

Правило: Старайтесь свести к минимуму использование магических чисел в ваших программах.

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

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

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

  1. Раф:

    Подскажите пожалуйста что тогда не является литералом? Раз буквы, цифры и символы это литералы, тогда нелитерал этот только переменная

    1. Андрей:

      литерал по простому это значение переменной оно может быть любого типа кроме void

  2. Анатолий:

    Ну 30 конечно никто не пишет. а пишут или именованную константу или макрос. Насчёт литералов. Да удобно. Но можно использовать и свои литералы. Последние версии С++ это позволяют. Хотя код становится малопонятным если вы туда понапихаете кучу своих литералов… В С кстати бинарный литерал тоже есть. Это не прерогатива С++. Насчёт групп ну мне не нравится. Я не люблю группированные числа… Особенно в виде строк, а не констатнт. С ними столько возни… Но это моё личное мнение.

  3. Анатолий:

    Я всегда сравниваю С/С++ с фортраном. Как там? Там вообще задаётся размер типа… А функции универсальны… Если ваш комп не поддерживает этот размер вылетит ошибка… Я не пропагандирую фортран. Но концепция типов в С/С++ мягко говоря ущербна. Ибо там изначально заложили в названия типов их размен. В фортране такое тоже было. Но сейчас нет. И правильно. Я молу легко работать с любыми типами и они все работают. И там не надо выдумывать дурацкие названия. Просто пишешь типа real(16) и всё у вас тип _float128. Или integer(16) и вуаля у вас тип __int128. Никаких проблем. Да, фотран это вам не Си… Хотите его преобразовать в строку? Да пожалуйста! Там нет многочисленных функций преобразований как в Си. Всего 2 write и read, но онир делают всё и гораздо лучше где либо. И в любой системе счисления. Даже двоичной. Конечно есть библиотека gmp. Там можно использовать числа любого размера. главное чтобы в память поместилось… Но то библиотека. А это аппаратная поддержка! А функция скажем sin там всего одна, но на все случаи жизни. Да фортран не поддерживает беззнаковые числа. Ну и ладно… Но слышал, что компилятор ifort их поддерживает. но он платный… Поэтому если на С/С++ вам понадобятся эти типы данных, то ни одна библиотека не будет с ними работать. Но сами типы там есть… вам придётся все эти функции писать самим. Особенно если вам понадобится двоичный формат… Или подключите модуль на фортране, там это уже есть. В Qt кстати поддержка функций есть… Но нет преобразований в строки. Эта среда более продвинута, чем С/С++. Хотя это только если вы используете это Qt.

  4. ХейЛонг:

    Немного мудрёные формулировки с магическими числами. Константы предполагаются как неизменяемые. Если есть вероятность, что константу придётся изменить, надо изначально создавать переменную, не придумывая названий для "изменяемой константы". Хотя можно написать десять программ для десяти разных чисел парт в классе, но программистами всё равно станут только адекватные люди.

    1. Фантазм:

      Понятие переменной подразумевает изменение в течение одного запуска программы. Тогда как значение константы остается неизменным в течение одного запуска, но может быть изменено между запусками.

  5. Анастасия:

    Пояснение для тех, кто, как и я, сначала не понял, как связаны следующие присваивания с комментариями:

    Например, мы хотим понять, почему 0x80 — это 1000 0000.
    80 в шестнадцатеричной системе переведём в привычную десятеричную, для этого каждая цифра справа налево умножается на степени 16, начиная от 0: 0х80=0*1+8*16=0+128=128
    128 переводим в двоичную систему, 128 — это 2 в 7 степени, значит двоичная запись будет 1000 0000 (тут тоже степени двойки справа налево, начиная с 0). Подробные пояснения по переводу из одной системы в другую есть в 44 уроке.

    Скорей всего, всё можно понять и проще, осознав чудо превращения сразу из 16-ричной системы в двоичную. Кто осознал, делитесь.

    1. Анастасия:

      Хорошая демонстрация и объяснение, как быстро переводить числа из двоичной системы в шестнадцатеричную, есть здесь.

    2. Анастасия:

      Есть гораздо более удобный способ конвертации чисел в двоичную систему и из неё. Достаточно выучить таблицу тетрад (а в ней же и триад(а в ней же диад)) чтобы на ходу переводить числа из четверичной/восьмеричной/шестнадцатеричной системы счисления.
      Жаль, что пока не знаю как всё быстро и на ходу в десятеричную систему переводить, но пока поделюсь тем, что есть.
      (exe.exe.exe, попыталась это всё расписать, но здесь нужно наглядное представление)
      Пожалуй, лучшее объяснение есть у информатика бу. А вот и ссылка: https://www.youtube.com/watch?v=npB8lF-V4mc&list=PLgvtHXe0kJXaNH57H5yolkewq-p5hfpDR

    3. anton:

      Не надо переводить в десятичную и обратно. Всё просто, никакой магии:
      Для кодирования 0,1 нужен1 бит.

      То есть 4 бита можно выразить либо 16-ю двоичными комбинациями:
      0000
      0001
      0010
      ...
      1111

      Либо 16-ю символами: 0123456789ABCDEF.
      Соответственно:
      0000 - 0
      0001 - 1
      0010 - 2
      ...
      1110 - E
      1111 - F

      Вот и всё.
      А чтоб не запоминать таблицу, запоминаешь, что A — это десять., дальше по алфавиту загибаешь пальцы на руке.
      B — первый палец ( то есть результат 11)
      С — второй
      D — третий ( то есть D — это тринадцать, а тринадцать это 8+4+1 , то есть 0b1101)
      Вот так я делаю ))))

      1. Fray:

        В книге Чарльза Петцольда "КОД. Тайный язык информатики" есть глава, где системы счисления ассоцируются с количеством пальцев на руках и остальных существ планеты. У человека 10 пальцев и поэтому он привык считать в десятичной системе счисления, то есть если вести счет 0,1,2,3,4,5,6,7,8,9 после девяти идет 11-ая цифра (0 это первая) и добавляется новый разряд числа — единица и счет продолжается с ним 10,11,12,13… и так далее. Но если бы мы были дельфинами, то счет бы был в двоичной системе, потому что "пальцев"(плавников) то два, то есть мы могли бы считать только до двух, а после добавлялся бы новый разряд единицы: то есть после счета чисел 0, 1 добавляется новый разряд числа и получаем 10, 11 дальше — третий разряд и получаем сразу 100, 101, 110, 111 что равносильно в нашей десятичной системе числам 5, 6, 7, 8.
        Каждый разряд в двоичной системе можно сравнить с битом, где 1 бит может хранить два возможных значения это 0 и 1. 2 бит — 4 значения это 00, 01, 10 11. 3 бит — 8 значений это 000, 001, 010, 011, 100, 101, 110, 111. С каждым добавлением нового разряда количество возможных значений удваивается от крайнего значения предыдущего(младшего) разряда. 1 бит- 2 значения, 2 бит = (2)*2 значения, 3 бит = (2*2)*2 значений, 4 бит = (2*2*2)*2 возможных значений отсюда и формула [количества возможных значений] = 2^(количество бит(разрядов двоичного числа)).
        Дальше шестнадцатеричная система счисления будет обозначаться как hex (сокр. от hexadecimal)
        Одно шестнадцатеричное число занимает 4 бит, потому что для представления 16 значений 3 битов будет недостаточно, 2^3 = 8, 2^4 = 16. Соответственно первое число в hex (0) в двоичном представлении будет 0000, второе число (1) — 0001, восьмое число (7) — 0111, девятое (8) — 1000, десятое и одиннадцатое (9) и (A) — 1001 и 1010. В программе выше переменной присваивается литерал в виде двух чисел hex, каждое из которых занимает по 4 бит. Вот и каждому hex числу соответствует свой бинарный код.
        Прошу строго не судить за ответ, я еще только начинаю осваивать базы, что то дается хорошо, что то не очень. Поправьте если нужно.

    4. ХейЛонг:

      Мой препод по программированию приводил хороший пример. Программирование как занятия музыкой. Чтобы развиваться, нужно заниматься постоянно, часами и сутками. А вот мой пример. Чтобы начать играть на инструменте, надо выучить ноты. Базовый курс информатики помогает с пониманием систем счисления и прочих важных мелочей.

  6. Артур:

    Спасибо, отличный перевод, все очень понятно.

    Понятно почему вы не перевели слово statement (заявление? утверждение? высказывание? изложение?), но в этой статье (простите за придирку)
    > число avogadro
    число Авогадро оставили непереведенным, несмотря на то, что оно вполне себе имеет общепризнанный перевод.

  7. alexk:

    Чтобы можно было применять цифровые разделители, т.е. записывать литералы в виде:

    необходимо установить ключ компиляции:

    g++ -Wall -std=c++1y ...

    Это справедливо для:
    g++ (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609

  8. Константин:

    И ещё не могу эту последовательность разгадать после 0x: 01; 02; 04; 08; 10; 20; 40; 80; FF; особенно здесь B3; F770;

    1. Константин:

      Уррр-ааа!!! Разгадал! А это значение в этот тип не помещается:

      Нужен long long

  9. Константин:

    Юра, пардон, но я не понял для чего была создана int bin, и, тем более, для чего мы в ней столько раз подряд значения меняли?

  10. Иван:

    До меня дошло. Невнимательно прочитал определение литерла. Спасибо!

    1. Фото аватара Юрий:

      Пожалуйста 🙂

  11. Иван:

    "Литеральные константы (обычно просто литералы) – это значения, которые вставляются непосредственно в код. Они являются константами, так как изменить их значения нельзя. "

    А почему мы не можем изменить их значения? Можно же просто присвоить другое значение этому литералу, судя по примерам в начале урока

    1. Фото аватара Юрий:

      Судя по какому примеру? Вы можете присвоить числу 4 (четыре) значение 5 (пять), чтобы 4 был равен 5? Или можно сделать, чтобы true был false, а false = true? Есть литералы, а есть переменные. Значение переменных можно изменять, значения литералов — нет.

      1. Дмитрий:

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

        1. Фото аватара Юрий:

          Ну смотрите, если регрессия увеличивается с каждым уроком — то, возможно, стоит посмотреть другие учебные курсы на просторах Интернета.

      2. Дмитрий:

        Я бы так и сделал, если бы первые странницы учебного материала, на вашем сайте, я бы не счел одним из лучших учебных материалов которые я встречал. В других источниках/книгах авторы то графоманией страдают, то недоговаривают там, где хотелось бы. Вот и огорчает сей момент, ведь привыкаешь что инфа пережевана хорошо, а тут неоднозначность. Юрий, автор оригинала расстроил не на долго, но думаю, было бы не лишним поблагодарить вас за ваш труд и результат, думаю ваш сайт многим помог и помогает. Конкретно эта глава, просто, не лучше описана чем в других источниках. В общем — спасибо!

        1. Фото аватара Юрий:

          Ваш месседж понятен, некоторые уроки/темы действительно могут быть объяснены лучше, чем другие: вопрос уже в уровне этого самого "лучше" и в том, с чем сравнивать.

          Пожалуйста 🙂

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

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