До этого момента, все переменные, которые мы рассматривали, были обычными. Их значения можно было изменить в любое время, например:
1 2 |
int x { 4 }; // инициализация переменной x значением 4 x = 5; // изменяем значение x на 5 |
Тем не менее, иногда полезно использовать переменные, значения которых изменить нельзя — константы.
Константы
Возьмем к примеру величину силы тяжести на Земле: 9.8м/с^2
. Она вряд ли поменяется в ближайшее время. Использовать константу в этом случае будет наилучшим вариантом, так как мы предотвратим, таким образом, любое (даже случайное) изменение этого значения.
Чтобы сделать переменную константой — используйте ключевое слово const перед типом переменной или после него. Например:
1 2 |
const double gravity { 9.8 }; // предпочтительнее использовать const перед типом данных int const sidesInSquare { 4 }; // ок, но вариант выше - лучше |
Несмотря на то, что язык C++ позволяет размещать const
как перед типом данных, так и после него, хорошей практикой считается размещать const
перед типом данных.
Константы должны быть инициализированы при объявлении. Изменить их значения с помощью операции присваивания нельзя:
1 2 |
const double gravity { 9.8 }; gravity = 9.9; // не допускается - ошибка компиляции |
Объявление константы без её инициализации также вызовет ошибку компиляции:
1 |
const double gravity; // ошибка компиляции, константа должна быть инициализирована |
Обратите внимание, константы могут быть инициализированы и с помощью неконстантных значений:
1 2 3 4 5 |
std::cout << "Enter your age: "; int age; std::cin >> age; const int usersAge (age); // в дальнейшем значение переменной usersAge не может быть изменено |
Ключевое слово const
является наиболее полезным (и наиболее часто используемым) с параметрами функций:
1 2 3 4 |
void printInteger(const int myValue) { std::cout << myValue; } |
Таким образом, при вызове функции константа-параметр сообщает и гарантирует нам то, что функция не изменит значение переменной myValue
.
Время компиляции и время выполнения
Когда вы находитесь в процессе компиляции программы, то это время компиляции (англ. «compile time»). Компилятор проверяет вашу программу на синтаксические ошибки и, если их нет, конвертирует код в объектные файлы.
Временной промежуток с момента старта выполнения программы и до момента окончания её работы называется временем выполнения программы (англ. «runtime»). Код выполняется строка за строкой.
Спецификатор constexpr
В языке C++ есть два вида констант:
Константы времени выполнения. Их значения определяются только во время выполнения программы. Переменные типа usersAge
и myValue
выше являются константами времени выполнения, так как компилятор не может определить их значения во время компиляции. usersAge
зависит от пользовательского ввода (который можно получить только во время выполнения программы), а myValue
зависит от значения, переданного в функцию (это значение также определится только во время выполнения программы).
Константы времени компиляции. Их значения определяются во время компиляции программы. Например, переменная со значением силы тяжести на Земле является константой времени компиляции, так как мы её определяем во время написания программы (до начала её выполнения).
В большинстве случаев не важно какой тип константы вы используете: времени выполнения или времени компиляции. Однако, все же есть несколько ситуаций, когда C++ может потребовать константу времени компиляции вместо времени выполнения (например, при определении длины массива фиксированного размера — мы рассмотрим это несколько позже). Так как есть 2 типа констант, то компилятору нужно постоянно отслеживать, к какому из них относится какая переменная. Чтобы упростить это задание, в C++11 добавили спецификатор constexpr, который сообщает компилятору, что текущая переменная является константой времени компиляции:
1 2 3 4 5 6 7 |
constexpr double gravity (9.8); // ок, значение определяется во время компиляции программы constexpr int sum = 4 + 5; // ок, результат выражения 4 + 5 определяется во время компиляции программы std::cout << "Enter your age: "; int age; std::cin >> age; constexpr int myAge = age; // неправильно, переменная age не определяется во время компиляции программы |
Использовать его вы, скорее всего, не будете, но знать о нем не помешает.
Правило: Любая переменная, которая не должна изменять свое значение после инициализации, должна быть объявлена с помощью спецификатора const (или constexpr).
Имена констант
Некоторые программисты пишут имена констант заглавными буквами. Другие используют обычные имена, только с префиксом k
. Мы же не будем их как-то выделять, так как константы — это те же обычные переменные, просто с фиксированными значениями, вот и всё. Особой причины их выделять — нет. Однако, это дело привычки.
Символьные константы
На предыдущем уроке мы говорили о магических числах — литералы, которые используются в программе в виде констант. «Поскольку использовать их не рекомендуется, то какой выход?» — спросите вы. А я отвечу: «Использовать символьные константы». Символьная константа — это тот же литерал (магическое число), только с идентификатором. Есть 2 способа объявления символьных констант в языке C++. Первый способ хороший, а второй — не очень. Рассмотрим сначала плохой способ.
Плохой способ: Использовать макросы-объекты с текст_замена в качестве символьных констант.
Раньше этот способ широко использовался, так что вы все еще можете его увидеть в старых программах.
Как мы уже знаем, макросы-объекты имеют две формы: с текст_замена
и без текст_замена
. Рассмотрим первый вариант с текст_замена
. Он выглядит следующим образом:
#define идентификатор текст_замена
Как только препроцессор встретит эту директиву, все дальнейшие появления идентификатор
будут заменены на текст_замена
. идентификатор
обычно пишется заглавными буквами с нижним подчёркиванием вместо пробелов.
Например:
1 2 3 4 5 |
#define MAX_STUDENTS_PER_CLASS 30 //... int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS; //... |
Во время компиляции программы, препроцессор заменит все идентификаторы MAX_STUDENTS_PER_CLASS
на литерал 30
.
Согласитесь, это гораздо лучше, нежели использовать магические числа, как минимум, по нескольким причинам. MAX_STUDENTS_PER_CLASS
дает понимание того, что это за значение и зачем оно используется (это понятно даже без комментариев). Во-вторых, если число нужно будет изменить — достаточно будет внести правки только в директиву #define, все остальные идентификаторы MAX_STUDENTS_PER_CLASS
в программе будут автоматически заменены новым значением при повторной компиляции.
Рассмотрим еще один пример:
1 2 3 4 5 |
#define MAX_STUDENTS_PER_CLASS 30 #define MAX_NAME_LENGTH 30 int max_students = numClassrooms * MAX_STUDENTS_PER_CLASS; setMax(MAX_NAME_LENGTH); |
Здесь понятно, что 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.
Например:
1 2 |
const int maxStudentsPerClass { 30 }; const int maxNameLength { 30 }; |
Такие значения отображаются в отладчике, а также следуют всем правилам обычных переменных (в том числе и по области видимости).
Правило: Используйте спецификатор const для создания символьных констант.
Использование символьных констант в программе
Во многих программах символьные константы используются часто (в разных местах кода). Это могут быть физические или математические константы (например, число Пи или число Авогадро), или специфические значения в вашей программе. Чтобы не писать их каждый раз, когда они необходимы — просто определите их в одном месте и используйте везде, где они нужны. Таким образом, если вам придется изменить их значения — достаточно будет зайти в один файл и внести в него правки, а не искать эти константы по всей программе.
Алгоритм использования символьных констант в вашей программе:
Шаг №1: Создайте заголовочный файл для хранения констант.
Шаг №2: В заголовочном файле объявите пространство имен.
Шаг №3: Добавьте все ваши константы в созданное пространство имен (убедитесь, что все константы имеют спецификатор const
).
Шаг №4: #include заголовочный файл везде, где нужны константы.
Например, файл constants.h:
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifndef CONSTANTS_H #define CONSTANTS_H // Определите собственное пространство имен для хранения констант namespace constants { const double pi(3.14159); const double avogadro(6.0221413e23); const double my_gravity(9.2); // ... другие константы } #endif |
Используйте оператор разрешения области видимости ::
для доступа к константам в файлах .cpp:
1 2 3 4 5 |
#include "constants.h" //... double circumference = 2 * radius * constants::pi; //... |
Если в программе много констант разных типов, то сделайте отдельные файлы для каждого из этих типов. Таким образом, отделив математические константы от специфических значений (которые могут быть разными в разных программах), вы сможете подключать файл с математическими константами к любой другой программе.
Вот это плохо?
Как сокращение чтобы меньше писать было.
Использование #define-ов вместо констант скрывает в себе одну неочевидную пакость, связанную с тем, что оный в действительности не производит вычислений, а просто подставляет строки
Пусть мы усложнили код из примера, определив отдельно число студентов-мужчин, и число студентов-женщин в классе. А общее число студентов в классе будет «просто» равно сумме тех и других
Всё на первый взгляд выглядит гладко, но по факту последняя строка после компиляции превратится в
, а никак не в требуемое
Здравствуйте, Юрий! Пытаюсь скомпилировать программу на основе вашего последнего кода, но постоянно 3 ошибки:
E0276 — имя перед которым стоит :: должно определять класс или пространство имён.
C2653 — constants не является классом или пространством имён.
C2065 — pi необъявленный идентификатор.
Хотя код к пространству имен идентичен вашему в примере:
Единственное, присутствует double, т.к. при отсутствие выдаёт ошибку:
Отсутствует явный тип данных.
Помогите, пожалуйста, как написать пространство без ошибок.
Visual Studio Community 2019 v. 16.1.1
Алексей, предположу,что ошибки из-за опечатки в названии вашего пространства имен для констант, должно быть constants (вы пропустили букву t в конце), а далее по коду вы пытаетесь использовать constants и конечно компилятор такого пространства имен не находит.
Возможно вы создали только файл .срр с исходным кодом. Необходимо так же создать файл заголовка с расширением .h и описать в нем пространство имен. Файл заголовка должен быть подключен к компиляции. Если все это есть — ищите ошибки в написании пространства имен, файла заголовков (в том числе и подключенную строку #include проверьте).
9,8 м/с2 — это не сила тяжести, а ускорение свободного падения. Сила тяже для каждого объекта рассчитывается отдельно.
Привет, Юра! Создал файл geoConstants.h со следующим содержимым:
,где enterNumber() простейшая функция ввода числа,
подключил к своему проекту, всё прекрасно работает (т.е. при выполнении программы я ввожу некие свои константы, например, w — влажность в процентах и т.д.), но в пустое консольное окно, где только лишь моргает курсор. Любые попытки применить cout или std::cout или using std или using namespace std для отображения ввода очередного значения заканчиваются ошибкой. А мне нужно много таких константных переменных создать. Как быть?
Ты же создал пространство имён, поэтому нужно его использовать, т.е.
using namespace constants;
Куда вы пытаетесь вставить вывод в консоль? Не уверен, но логично предположить что в функции определения пространства имен не получится использовать cout.
cout нужно использовать либо в функции enterNumber… но это не логично, т.к. вы вводите разные переменные через одну функцию ввода. Либо… как вариант… принимать значения ДО определения пространства имен и уже там вводить константы из других переменных. Тоже не красиво, но в таких условиях я не вижу еще каких-то выходов.
Юрий, пожалуйста, дайте пример кода с run time константой (например высота башни с которой бросают мячик) и место где она хранится, это не тот же файл где лежат compile константы (типа сила тяжести и пр.)?
У меня выдаёт ошибку E2140 (выражение должно относиться к целочисленному типу или типу перечисления без области видимости). Что делать? Я чёт не пойму в чём прикол.
Возведение в степень в C++ выполняется не оператором ^, а функцией pow. ^ — это побитовое исключающее ИЛИ (смотрите урок о побитовых операторах). Правильно следующее:
Ой затупил xdd. Понял, спасибо большое =DDD
S = pi * r ^ 2
Вот где сидит чертик. Изменяем возведение в степень на умножение и все работает.
Скажите, почему myValue не инициализирована ?
А при выполнении :
выдает :
5
6
7
8
а, что тогда константа ?
Константа — это значение, которое используется в функции printInteger. Параметр функции имеет локальную область видимости, а константа — это параметр функции и изменять значение константы нельзя в функции printInteger. Вы можете выполнять любые операции с константой в printInteger, но не присваивать ей другое значение. Так как константа имеет локальную область видимости, то в main() она никак не видна. И то, что вы передаете разные значения, то этому следует процесс выделения памяти для константы внутри функции, а затем удаление этой памяти. При каждом новом вызове этот процесс повторяется. Смотрите урок 15.
Скажите пожалуйста, я правильно понял — значение константы нужно для выполнения какого то кода ВНУТРИ функции и после } это
значение "умирает".
А повторное использование функции в теле главной это уже другая история, с присвоением другого значения константы .
Спасибо.
Да, верно. В printInteger вы просто выполняете операции с константой.
В уроке ведь говорится, что есть константы runtime (значения определяются только во время выполнения) и константы compile-time (значения определяются во время компиляции). Константам нельзя присваивать значения через знак равенства после объявления. Использовать в качестве параметров в функции можно — их значения таким образом могут быть как runtime, так и compile-time. В функции они используются в качестве параметров для гарантии того, что сами функции в своем теле не изменят значения, которые вы будете передавать.
В printInteger константа может инициализироваться как во время выполнения (например, пользователь вводит своё число и затем вы передаете его в функцию для вывода), так и во время компиляции (например, вы передаете уже готовое значение для выполнения математических операций внутри функции).
>>»В заголовочном файле объявите пространство имен (урок 24).»
Вот только в уроке 24 «объявление пространства имен в заголовочных файлах» не объясняется…
Пример объявления пространства имен приведен ниже — на примере файла constants.h. Буквально сразу, после перечисления в этом уроке. Если хотите подробнее прочитать о пространствах имен — урок 53, исправил.