4.13 – Литералы

Добавлено 2 мая 2021 в 12:57

В программировании константа – это фиксированное значение, которое нельзя изменять. В C++ есть два типа констант: литеральные константы и символьные константы. В этом уроке мы рассмотрим литеральные константы, а в следующем – символьные константы.

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

return 5;                   // 5 - целочисленный литерал
bool myNameIsAlex { true }; // true - это логический литерал
std::cout << 3.4;           // 3.4 – это литерал типа double

Они являются константами, потому что их значения не могут быть изменены динамически (сначала вы должны изменить их, а затем перекомпилировать программу, чтобы изменение вступило в силу).

Так же, как у объектов есть тип, тип есть и у всех литералов. Тип литерала предполагается из значения и формата самого литерала.

По умолчанию:

Значение литералаПримерыТип по умолчанию
целое число5, 0, -3int
логическое значениеtrue, falsebool
число с плавающей запятой3.4, -2.2double (не float)
символ'a'char
строка в стиле C"Hello, world!"const char[14]

Суффиксы литералов

Если тип литерала по умолчанию не соответствует необходимому, вы можете изменить тип литерала, добавив суффикс:

Тип данныхСуффиксНазначение
intu или Uunsigned int
intl или Llong
intul, uL, Ul, UL, lu, lU, Lu или LUunsigned long
intll или LLlong long
intull, uLL, Ull, ULL, llu, llU, LLu или LLUunsigned long long
doublef или Ffloat
doublel или Llong double

Обычно вам не нужно использовать суффиксы для целочисленных типов, но вот пара примеров:

unsigned int value1 { 5u }; // 5 имеет тип unsigned int
long value2 { 6L };         // 6 имеет тип long

По умолчанию литеральные константы с плавающей точкой имеют тип double. Чтобы они были типа float, необходимо использовать суффикс f (или F):

float f { 5.0f }; // 5.0f имеет тип float

Начинающие программисты часто не понимают, почему следующий код работает не так, как ожидалось:

float f { 4.1 }; // предупреждение: 4.1 - это литерал типа double, а не типа float

Поскольку у 4.1 нет суффикса, он рассматривается как литерал типа double, а не как литерал типа float. Когда C++ определяет тип литерала, ему всё равно, что вы делаете с литералом (например, в данном случае используете его для инициализации переменной float). Следовательно, 4.1 необходимо преобразовать из double в float, прежде чем его можно будет присвоить переменной f, и это может привести к потере точности.

Литералы можно использовать в коде C++, если их значения понятны. Чаще всего это происходит при использовании для инициализации или присвоения значения переменной, выполнения математических операций или вывода текста на экран.

Строковые литералы

В уроке «4.11 – Символы» мы определили строку как набор последовательных символов. C++ поддерживает строковые литералы:

std::cout << "Hello, world!";    // "Hello, world!" - строковый литерал в стиле C
std::cout << "Hello," " world!"; // C++ объединит последовательные строковые литералы

Строковые литералы в C++ по историческим причинам обрабатываются очень странно. На данный момент можно использовать строковые литералы для печати текста с помощью std::cout, но не пытайтесь назначать их переменным или передавать их функциям – это либо не сработает, либо будет работать не так, как вы ожидаете. О строках в стиле C (и о том, как обойти все эти странные проблемы) мы поговорим подробнее в будущих уроках.

Экспоненциальная запись для числовых литералов с плавающей запятой

Есть два разных способа объявить литералы с плавающей точкой:

double pi { 3.14159 };       // 3.14159 - литерал типа double в стандартной записи
double avogadro { 6.02e23 }; // 6.02 x 10^23 - литерал типа double в экспоненциальной записи

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

double electron { 1.6e-19 }; // заряд электрона равен 1.6 x 10^-19

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

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

int x { 12 }; // 12 считается десятичным числом

В двоичной системе счисления всего 2 цифры: 0 и 1, поэтому она называется «с основанием 2». В двоичном формате мы считаем так: 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).

Десятичная система01234567891011
Восьмеричная система0123456710111213

Чтобы использовать литерал в восьмеричном формате, добавьте к вашему литералу префикс 0 (ноль):

#include <iostream>
 
int main()
{
    int x{ 012 }; // 0 перед числом означает, что оно в восьмеричном (octal) формате
    std::cout << x;
    return 0;
}

Эта программа печатает:

10

Почему 10, а не 12? Потому что числа печатаются в десятичном формате, а 12 в восьмеричном формате = 10 десятичном формате.

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

Шестнадцатеричная система счисления - с основанием 16. В шестнадцатеричной системе мы считаем так: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, 10, 11, 12,…

Десятичная система01234567891011121314151617
Восьмеричная система0123456789ABCDEF1011

Чтобы использовать литерал в шестнадцатеричном формате, добавьте к нему префикс 0x.

#include <iostream>
 
int main()
{
    int x{ 0xF }; // 0x перед числом означает, что это шестнадцатеричное число
    std::cout << x;
    return 0;
}

Эта программа печатает:

15

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

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

До C++14 не было возможности назначить литерал в двоичном формате. Однако шестнадцатеричные пары предоставляют нам для этого полезный обходной путь:

#include <iostream>
 
int main()
{
    int bin{};
    bin = 0x01;   // присваиваем переменной число, равное двоичном формате 0000 0001
    bin = 0x02;   // присваиваем переменной число, равное двоичном формате 0000 0010
    bin = 0x04;   // присваиваем переменной число, равное двоичном формате 0000 0100
    bin = 0x08;   // присваиваем переменной число, равное двоичном формате 0000 1000
    bin = 0x10;   // присваиваем переменной число, равное двоичном формате 0001 0000
    bin = 0x20;   // присваиваем переменной число, равное двоичном формате 0010 0000
    bin = 0x40;   // присваиваем переменной число, равное двоичном формате 0100 0000
    bin = 0x80;   // присваиваем переменной число, равное двоичном формате 1000 0000
    bin = 0xFF;   // присваиваем переменной число, равное двоичном формате 1111 1111
    bin = 0xB3;   // присваиваем переменной число, равное двоичном формате 1011 0011
    bin = 0xF770; // присваиваем переменной число, равное двоичном формате 1111 0111 0111 0000
 
    return 0;
}

Литералы в двоичном формате и разделители цифр C++14

В C++14 мы можем назначать литералы в двоичном формате с помощью префикса 0b:

#include <iostream>
 
int main()
{
    int bin{};
    bin = 0b1;        // присваиваем переменной двоичное значение 0000 0001
    bin = 0b11;       // присваиваем переменной двоичное значение 0000 0011
    bin = 0b1010;     // присваиваем переменной двоичное значение 0000 1010
    bin = 0b11110000; // присваиваем переменной двоичное значение 1111 0000
 
    return 0;
}

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

#include <iostream>
 
int main()
{
    int bin{ 0b1011'0010 };      // присваиваем переменной двоичное значение 1011 0010
    long value{ 2'132'673'462 }; // прочитать намного легче, чем 2132673462
 
    return 0;
}

Если ваш компилятор несовместим с C++14, он пожалуется, если вы попытаетесь использовать любой из этих приемов.

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

По умолчанию C++ выводит значения в десятичном формате. Однако вы можете указать ему, что необходимо печатать в других форматах. Печать в десятичном, восьмеричном или шестнадцатеричном формате упрощается благодаря использованию std::dec, std::oct и std::hex:

#include <iostream>
 
int main()
{
    int x { 12 };
    std::cout << x << '\n';             // десятичный (по умолчанию)
    std::cout << std::hex << x << '\n'; // шестнадцатеричный
    std::cout << x << '\n';             // всё еще шестнадцатеричный
    std::cout << std::oct << x << '\n'; // восьмеричный
    std::cout << std::dec << x << '\n'; // возвращаемся к десятичному формату
    std::cout << x << '\n';             // десятичный
 
    return 0;
}

Эта программа печатает:

12
c
c
14
12
12

Печать в двоичном формате немного сложнее, поскольку std::cout не имеет для этого встроенной возможности. К счастью, стандартная библиотека C++ включает в себя тип std::bitset (в заголовке <bitset>), который сделает это за нас. Чтобы использовать std::bitset, мы можем определить переменную std::bitset и указать std::bitset, сколько бит мы хотим сохранить. Количество бит должно быть константой времени компиляции. std::bitset может быть инициализирован целочисленным значением без знака (в любом формате, включая десятичный, восьмеричный, шестнадцатеричный или двоичный).

#include <bitset> // для std::bitset
#include <iostream>
 
int main()
{
	// std::bitset<8> означает, что мы хотим сохранить 8 бит
	std::bitset<8> bin1{ 0b1100'0101 }; // двоичный литерал для двоичного значения 1100 0101
	std::bitset<8> bin2{ 0xC5 };        // шестнадцатеричный литерал для двоичного значения 1100 0101
 
	std::cout << bin1 << ' ' << bin2 << '\n';
	std::cout << std::bitset<4>{ 0b1010 } << '\n'; // мы также можем печатать из std::bitset напрямую
 
	return 0;
}

Эта программа напечатает:

11000101 11000101
1010

Мы также можем создать временный (анонимный) std::bitset для печати одного значения. В приведенном выше коде эта строка:

std::cout << std::bitset<4>{ 0b1010 } << '\n'; // мы также можем печатать из std::bitset напрямую

создает временный объект std::bitset с 4 битами, инициализирует его с помощью литерала 0b1010, печатает его значение в двоичном формате, а затем отбрасывает временный std::bitset.

Магические числа, и почему это плохо

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

int maxStudents{ numClassrooms * 30 };

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

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

int maxStudents{ numClassrooms * 30 };
setMax(30);

Чтобы обновить нашу программу для использования нового размера класса, нам нужно будет обновить константу с 30 до 35. Но как насчет вызова setMax()? Имеет ли это число 30 то же значение, что и другое число 30? Если да, то его следует обновить. В противном случае его следует оставить в покое, иначе мы можем сломать нашу программу где-нибудь еще. Если вы выполняете глобальный поиск и замену, вы можете случайно обновить аргумент setMax(), хотя он не должен был изменяться. Таким образом, вам нужно просмотреть весь код для каждого экземпляра литерала 30, а затем определить, нужно ли его изменить или нет. Это может занять много времени (и привести к ошибкам).

Хотя мы говорим «магические числа», это относится ко всем видам значений. Рассмотрим следующий пример:

std::cout << "Enter a number less than 100: ";
 
int input{};
std::cin >> input;
 
if (input >= 100)
{
  std::cout << "Invalid input! The number has to be less than 100.";
}

В этом примере только одно число (100), но оно также используется в строках. Если мы решим обновить максимальное количество, скажем, на 200, нам придется обновить три разных случая, где встречается 100.

К счастью, существуют лучшие варианты (символьные константы). Об этом мы поговорим на следующем уроке.

Лучшая практика


Не используйте магические числа в своем коде.

Теги

C++ / Cppstd::bitsetВосьмеричная система счисленияДвоичная система счисленияДесятичная система счисленияДля начинающихКонстантаЛитералОбучениеПрограммированиеСистема счисленияШестнадцатеричная система счисления

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.