Урок 37. Символьные константы. Const, constexpr

  Юрий Ворон  | 

    | 

  Обновлено 23 Окт 2017  | 

 9596

 ǀ   14 

До этого момента, все переменные, которые мы рассматривали, были не константами. Их значения можно было изменить в любое время. Например:

Тем не менее, иногда полезно использовать переменные, значения которых нельзя изменить – константы. Вот например, сила тяжести на Земле: 9.8м/с^2. Оно вряд ли поменяется в ближайшее время. Использовать константу для этого случая — наилучший вариант, так как таким способом мы предотвратим любое (даже случайное) изменение значения.

Чтобы сделать переменную константой – используйте ключевое слово const перед типом переменной или после. Например:

Несмотря на то, C++ примет const как до, так и после типа, мы рекомендуем использовать именно перед типом.

Константы должны быть инициализированы при объявлении. Их значения с помощью операции присваивания изменить не получится.

Объявление константы без инициализации также вызовет ошибку компиляции:

Обратите внимание, константы могут быть инициализированы и с помощью неконстантных значений:

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

Таким образом, при вызове функции, константа-параметр говорит нам, что функция не будет изменять значение переменной myValue. Во-вторых, она гарантирует, что функция не изменит значение myValue.

Compile time и runtime

Когда вы находитесь в процессе компиляции программы – это compile time (время компиляции). Компилятор проверяет вашу программу на синтаксические ошибки, затем конвертирует код в объектные файлы.



Когда вы находитесь в процессе запуска вашей программы или когда программа уже выполняется — это runtime (время выполнения). Код выполняется строка за строкой.

Constexpr

В C++ есть два вида констант.

Константы runtime. Их значения определяются только во время выполнения (когда программа запущена). Переменные типа usersAge и myValue (в коде выше) — это константы runtime, так как компилятор не может определить их значения во время компиляции. usersAge зависит от пользовательского ввода (который можно получить только во время выполнения программы), а myValue зависит от значения, переданного в функцию (которое станет известным также во время выполнения программы).

Константы compile-time. Их значения определяются во время компиляции. Например, сила тяжести Земли – это константа compile-time, мы её определили сами в ходе написания программы.

В большинстве случаев, неважно какой тип константы: compile-time или runtime. Однако, есть все же несколько ситуаций, когда C++ может требовать константу compile-time вместо runtime (например, при определении длины массива фиксированного размера — мы рассмотрим это позже). Так как типов есть два, то компилятору нужно постоянно отслеживать, к какому из них относится какая переменная. Чтобы упростить это задание, в C++ 11 появляется ключевое слово constexpr, которое гарантирует, что тип константы — compile-time:

Использовать вы его, скорее всего, не будете, но знать о нем не помешает.

Правило: Любая переменная, которая не должна изменять свое значение после инициализации — должна быть объявлена, как const (или как constexpr).

Имена констант

Некоторые программисты пишут имена констант заглавными буквами. Другие используют обычные имена, только с префиксом ‘k’. Мы же не будем их как-то выделять, так как константы – это те же обычные переменные, просто с фиксированными значениями, вот и всё. Особой причины, чтобы их выделять нет. Однако, это дело каждого лично.

Символьные константы

В предыдущем уроке, мы обсуждали «магические числа» – литералы, которые используются в программе как константы. Поскольку их использование является плохой практикой, тогда что использовать вместо них? Ответ: символьные константы. Символьная (или еще символическая) константа – это тот же литерал (магическое число), только с идентификатором. Есть два способа объявления символических констант в C++. Один из них хороший, а один не очень. Рассмотрим оба.

Плохой способ: Использование макросов-объектов с текстом-заменой в качестве символьных констант

Раньше этот метод широко использовался, так что вы все еще можете увидеть его в старых кодах.

В уроке 22 о препроцессорах и директивах, мы говорили, что макросы-объекты имеют две формы – с текстом-заменой и без. Рассмотрим первый случай (с текстом-заменой). Он выглядит так:

#define identifier substitution_text

Как только препроцессор встретит эту директиву, все дальнейшие появления ‘identifier’ будет заменяться на ‘substitution_text’. Идентификатор обычно пишется заглавными буквами с нижним подчеркиванием вместо пробелов.



Например:

Во время компиляции, препроцессор заменит все идентификаторы MAX_STUDENTS_PER_CLASS на литерал 30.

Согласитесь, это гораздо лучший вариант, чем использование магических чисел, как минимум по нескольким причинам. MAX_STUDENTS_PER_CLASS обеспечивает контекст того, что это за значение и зачем оно надо, даже без комментариев. Во-вторых, если число нужно будет изменить — достаточно будет сделать правки только в директиве #define, все остальные идентификаторы MAX_STUDENTS_PER_CLASS в программе будут автоматически заменены новым значением при повторной компиляции.

Рассмотрим еще один пример:

Здесь ясно, что MAX_STUDENTS_PER_CLASS и MAX_NAME_LENGTH имеются в виду как разные объекты, даже если у них одни и те же значения (30).

Так почему же этот способ плохой? Есть две причины.

Во-первых, макросы обрабатываются препроцессором, который заменяет идентификаторы на определенные значения. Эти значения не будут отображаться в отладчике (который показывает ваш фактический код). При компиляции int max_students = numClassrooms * 30; в отладчике вы увидите int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS;. А если нужно будет узнать значение MAX_STUDENTS_PER_CLASS? Вам придется самостоятельно найти его в коде. А это может занять некоторое время, в зависимости от размеров программы.

Во-вторых, эти директивы всегда имеют глобальную область видимости (о ней мы поговорим позже). Это означает, что значения #define в одной части кода могут конфликтовать со значениями #define в другой части кода.

Правило: Не используйте #define для создания символьных констант.

Хороший способ: Переменные const

Лучший способ создать символьную константу — использовать const:

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

Правило: Используйте const для создания символьных констант.

Использование символьных констант в программе

Во многих программах символьная константа должна быть использована на протяжении всего кода (а не только в одном месте). Они могут быть физическими или математическими константами, которые не меняются (например, число Пи или число Авогадро) или специфическими значения вашей программы. Чтобы не писать их каждый раз, когда они необходимы — определите их в одном месте и используйте везде, где будет нужно. Таким образом, если вам придется их изменить – достаточно будет зайти в один файл и там внести правки, а не рыскать по всей программе.

Как это осуществить? Очень просто:

  1. Создайте заголовочный файл для хранения констант.
  2. В заголовочном файле объявите пространство имен (урок 53).
  3. Добавьте все ваши константы в созданное пространство имен (убедитесь, что все они с ключевым словом const).
  4. #include заголовочный файл везде, где нужно.

Например, файл constants.h:

Используйте оператор разрешения области видимости (::) для доступа к константам в файлах .cpp:

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

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

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

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

  1. somebox:

    9,8 м/с2 — это не сила тяжести, а ускорение свободного падения. Сила тяже для каждого объекта рассчитывается отдельно.

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

    Привет, Юра! Создал файл geoConstants.h со следующим содержимым:

    ,где enterNumber() простейшая функция ввода числа,
    подключил к своему проекту, всё прекрасно работает (т.е. при выполнении программы я ввожу некие свои константы, например, w — влажность в процентах и т.д.), но в пустое консольное окно, где только лишь моргает курсор. Любые попытки применить cout или std::cout или using std или using namespace std для отображения ввода очередного значения заканчиваются ошибкой. А мне нужно много таких константных переменных создать. Как быть?

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

    Юрий, пожалуйста, дайте пример кода с run time константой (например высота башни с которой бросают мячик) и место где она хранится, это не тот же файл где лежат compile константы (типа сила тяжести и пр.)?

  4. San_Rembak:

    У меня выдаёт ошибку E2140 (выражение должно относиться к целочисленному типу или типу перечисления без области видимости). Что делать? Я чёт не пойму в чём прикол.

    1. Юрий Юрий:

      Возведение в степень в C++ выполняется не оператором ^, а оператором pow. ^ — это побитовое исключающее ИЛИ (смотрите урок о побитовых операторах). Правильно следующее:

      1. San_Rembak:

        Ой затупил xdd. Понял, спасибо большое =DDD

  5. aleksandr:

    Скажите, почему myValue не инициализирована ?

    1. aleksandr:

      А при выполнении :

      выдает :
      5
      6
      7
      8

      а, что тогда константа ?

      1. Li4ik Li4ik:

        Константа — это значение, которое используется в функции printInteger. Параметр функции имеет локальную область видимости, а константа — это параметр функции и изменять значение константы нельзя в функции printInteger. Вы можете выполнять любые операции с константой в printInteger, но не присваивать ей другое значение. Так как константа имеет локальную область видимости, то в main() она никак не видна. И то, что вы передаете разные значения, то этому следует процесс выделения памяти для константы внутри функции, а затем удаление этой памяти. При каждом новом вызове этот процесс повторяется. Смотрите урок 15.

        1. aleksandr:

          Скажите пожалуйста, я правильно понял — значение константы нужно для выполнения какого то кода ВНУТРИ функции и после } это
          значение "умирает".
          А повторное использование функции в теле главной это уже другая история, с присвоением другого значения константы .

          Спасибо.

        2. Li4ik Li4ik:

          Да, верно. В printInteger вы просто выполняете операции с константой.

    2. Li4ik Li4ik:

      В уроке ведь говорится, что есть константы runtime (значения определяются только во время выполнения) и константы compile-time (значения определяются во время компиляции). Константам нельзя присваивать значения через знак равенства после объявления. Использовать в качестве параметров в функции можно — их значения таким образом могут быть как runtime, так и compile-time. В функции они используются в качестве параметров для гарантии того, что сами функции в своем теле не изменят значения, которые вы будете передавать.

      В printInteger константа может инициализироваться как во время выполнения (например, пользователь вводит своё число и затем вы передаете его в функцию для вывода), так и во время компиляции (например, вы передаете уже готовое значение для выполнения математических операций внутри функции).

  6. Виталий:

    >>»В заголовочном файле объявите пространство имен (урок 24).»

    Вот только в уроке 24 «объявление пространства имен в заголовочных файлах» не объясняется…

    1. Li4ik Li4ik:

      Пример объявления пространства имен приведен ниже — на примере файла constants.h. Буквально сразу, после перечисления в этом уроке. Если хотите подробнее прочитать о пространствах имен — урок 53, исправил.

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

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