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

  Юрий  | 

    | 

  Обновл. 7 Апр 2019  | 

 21541

 ǀ   15 

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

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

Константы

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

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

Несмотря на то, что C++ позволяет размещать const как перед типом данных, так и после него, хорошей практикой считается размещать const перед типом данных.

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

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

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

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

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

Время компиляции и время выполнения


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

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

Спецификатор constexpr

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

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

Константы времени компиляции. Их значения определяются во время компиляции программы. Например, переменная со значением силы тяжести на Земле является константой времени компиляции, так как мы её определяем во время написания программы (до начала её выполнения).

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

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

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

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


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

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

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

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

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

Как мы уже знаем, макросы-объекты имеют две формы: с текстом-заменой и без текста-замены. Рассмотрим первый вариант: с текстом-заменой. Он выглядит следующим образом:

#define идентификатор текст_замена

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

Например:

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

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

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

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

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

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

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

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

Хороший способ: Использовать переменные со спецификатором const.

Например:

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

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

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


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

Алгоритм использования символьных констант в вашей программе:

   Создайте заголовочный файл для хранения констант.

   В заголовочном файле объявите пространство имён.

   Добавьте все ваши константы в созданное пространство имён (убедитесь, что все константы имеют спецификатор const).

   #include заголовочный файл везде, где нужны константы.

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

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

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

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

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

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

  1. Аватар Алексей:

    Здравствуйте, Юрий! Пытаюсь скомпилировать программу на основе вашего последнего кода, но постоянно 3 ошибки:
    E0276 — имя перед которым стоит :: должно определять класс или пространство имён.
    C2653 — constants не является классом или пространством имён.
    C2065 — pi необъявленный идентификатор.
    Хотя код к пространству имен идентичен вашему в примере:

    Единственное, присутствует double, т.к. при отсутствие выдаёт ошибку: Отсутствует явный тип данных.
    Помогите, пожалуйста, как написать пространство без ошибок.
    Visual Studio Community 2019 v. 16.1.1

  2. Аватар somebox:

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

  3. Аватар Константин:

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

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

  4. Аватар Константин:

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

  5. Аватар San_Rembak:

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

    1. Юрий Юрий:

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

      1. Аватар San_Rembak:

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

  6. Аватар aleksandr:

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

    1. Аватар aleksandr:

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

      выдает :
      5
      6
      7
      8

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

      1. Юрий Юрий:

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

        1. Аватар aleksandr:

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

          Спасибо.

        2. Юрий Юрий:

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

    2. Юрий Юрий:

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

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

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

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

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

    1. Юрий Юрий:

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

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

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