Что означает const после функции c
const (C++)
При изменении объявления данных const ключевое слово указывает, что объект или переменная не являются изменяемыми.
Синтаксис
Значения-константы
const Ключевое слово указывает, что значение переменной является константой и сообщает компилятору о том, что программист не сможет его изменить.
В C++ const вместо директивы препроцессора const можно использовать ключевое слово, чтобы определить постоянные значения. Значения, определенные с помощью, const подчиняются проверке типа и могут использоваться вместо константных выражений. В C++ можно указать размер массива с помощью const переменной следующим образом:
В языке C константные значения по умолчанию имеют внешнюю компоновку, поэтому они могут использоваться только в файлах исходного кода. В языке C++ константные значения по умолчанию имеют внутреннюю компоновку, которая позволяет использовать их в файлах заголовков.
const Ключевое слово также можно использовать в объявлениях указателей.
Указатели на данные-константы можно использовать в качестве параметров функций, чтобы функция не могла изменять параметр, переданный посредством указателя.
Для объектов, не объявленных как константы, можно вызывать как константные, так и неконстантные функции-члены. Можно также перегружать функцию-член с помощью const ключевого слова; это позволяет вызывать другую версию функции для постоянных и неконстантных объектов.
Нельзя объявлять конструкторы или деструкторы с const ключевым словом.
Функции-члены-константы
Объявление функции-члена с помощью const ключевого слова указывает, что функция является функцией «только для чтения», которая не изменяет объект, для которого она вызывается. Функция-член константы не может изменять какие-либо нестатические элементы данных или вызывать функции-члены, не являющиеся константами. Чтобы объявить функцию-член константы, поместите const ключевое слово после закрывающей скобки списка аргументов. const Ключевое слово требуется как в объявлении, так и в определении.
Различия констант C и C++
При объявлении переменной, как const в файле исходного кода на языке C, это можно сделать следующим образом:
Затем эту переменную можно использовать в другом модуле следующим образом:
Но чтобы получить такое же поведение в C++, необходимо объявить const переменную как:
Если вы хотите объявить extern переменную в файле исходного кода C++ для использования в файле исходного кода на языке C, используйте:
для предотвращения изменения имени компилятором C++.
Remarks
При использовании списка параметров функции-члена const ключевое слово указывает, что функция не изменяет объект, для которого она вызывается.
Дополнительные сведения о const см. в следующих разделах:
Многоликий const
Ключевое слово const — одно из самых многозначных в C++. Правильно использование const позволяет организовать множество проверок ещё на этапе компиляции и избежать многих ошибок из числа тех, которые бывает трудно найти при помощи отладчиков и/или анализа кода.
Первая половина заметки рассчитана скорее на начинающих (надеюсь мнемоническое правило поможет вам запомнить, где и для чего используется const), но, возможно, и опытные программисты смогут почерпнуть интересную информацию о перегрузке методов по const.
Константы и данные
Самый простой случай — константные данные. Возможно несколько вариантов записи:
Все они правильные и делают одно и тоже — создают переменную, значение которой изменить нельзя.
Константы и указатели
При использовании const с указателями, действие модификатора может распространяться либо на значение указателя, либо на данные на которые указывает указатель.
Работает (const относится к данным):
Тоже самое и тоже работает:
А вот это уже не работает:
Если бы операция присвоения изменяла бы не указатель, а данные:
то ситуация была бы диаметрально противоположной.
Существует мнемоническое правило, позволяющее легко запомнить, к чему относится const. Надо провести черту через «*», если const слева, то оно относится к значению данных; если справа — к значению указателя.
Ну и конечно, const можно написать дважды:
Константы и аргументы/результаты функций
C функциями слово const используется по тем же правилам, что при описании обычных данных.
Константы и методы (перегрузка)
А вот с методами есть одна тонкость.
Во-первых, для методов допустимо использование const, применительно к this. Синтаксис таков:
Кроме того, этот const позволяет перегружать методы. Таким образом, вы можете писать оптимизированные варианты методов для константных объектов.
То есть для константного объекта (с x=2) был вызван соответствующий метод.
Осталось только добавить, что если вы планируете использовать const-объекты, то вам надо обязательно реализовать const-методы. Если вы в этом случае не реализуете не-const-методы, то во всех случаях будут молча использоваться const-методы. Одним словом, const лучше использовать там, где это возможно.
И ещё… я собрался в отпуск… возможно, не смогу ответить на комментарии до понедельника-вторника. Не сочтите за невнимание 🙂
Так вы думаете, что знаете Const?
Думаете, что вы знаете все правила использования const для С? Подумайте еще раз.
Основы const
Скалярные переменные
Вы знакомы с простым правилом const в С.
const перед hello означает, что во время компиляции происходит проверка того, что hello никогда не меняется.
Кроме того, C не сильно беспокоится о том, где расположен const до тех пор пока он находится перед идентификатором, так что объявления const uint32_t и uint32_t const идентичны:
Скалярные переменные в прототипах
Сравните прототип и реализацию следующей функции:
Однако, ваш компилятор будет жаловаться на несоответствие const для параметров, являющихся указателями или массивами, так как в таком случае ваша функция будет иметь возможность манипулировать данными на которые ссылается передаваемый указатель.
Массивы
Вы можете указать const для всего массива.
const также может указываться после объявления типа:
Структуры
Обычные структуры
Вы можете указать const для всей структуры.
Если мы попытаемся изменить какой-либо член someStructA :
const внутри структуры
Вы можете указать const для отдельных членов структуры:
Если мы попытаемся изменить какие-либо члены someOtherStructB :
Указатели
const для указателей — вот где начинается веселье.
Один const
Давайте использовать указатель на целое число в качестве примера.
Два const
Давайте добавим ещё один const и посмотрим как пойдут дела.
Ага, у нас получилось сделать и данные, и сам указатель неизменяемыми.
Интерлюдия — объясняем объявления const
Но подождите, дальше — больше!
Это распространенный шаблон для перебора последовательностей данных: переходить к следующему элементу, увеличивая указатель, но не позволяя указателю изменять данные.
Допустимое значение указателя это всегда скалярный адрес памяти ( uintptr_t ), поэтому здесь const оказывает тот же эффект, как и в случае с обычными целочисленными значениями, т.е. совершенно нормально, если ваша реализация использует const для определения параметров, но прототип вашей функции не обязан включать их, так как этот const защищает только адрес, но не данные.
Три const
Сколько способов мы можем использовать, чтобы добавить const к двойному указателю?
Давайте быстро это проверим.
Какие из этих операций допускаются, исходя из объявления выше?
Только первое присваивание не сработало, потому что, если мы прочитаем наше объявление справа налево:
Что, если мы хотим добавить еще один модификатор const на уровень глубже?
Теперь мы дважды защищены от изменений, потому что, если мы прочитаем наше объявление справа налево:
Что если мы хотим заблокировать все изменения при объявлении двойного указателя?
Что теперь мы (не)можем сделать?
Ничего не работает! Успех!
Дополнительные правила
Хаки приведения типов
Что если вы умны и создали изменяемый указатель на неизменяемое хранилище?
Поскольку это C, вы можете отбросить квалификатор const явным преобразованием типа и избавиться от предупреждения (а также нарушения инициализации const ):
Хаки памяти
Что если структура содержит const члены, но вы измените хранящиеся в ней данные после объявления?
Давайте объявим две структуры, различающиеся только константностью их членов.
Будет ли это работать?
Неа, это не работает, потому что прототип 6 для memcpy выглядит так:
memcpy не позволяет передавать ей неизменяемые указатели в качестве dst аргумента, так как dst изменяется при копировании (а someStructA неизменяема).
Заключение
Не создавайте изменяемых значений без необходимости. Будьте внимательны к тому, чтобы ваша программа на самом деле работала так, как вы планировали.
Что означает const после функции c
Подсистема const одна из самых грязных фич языка C++.
Вроде бы концепция должна быть простой: переменные, декларированные с модификатором const становятся константами, т. е. объектами, которые не может изменить программа. Однако этот термин используется как костыль для починки недостающих возможностей C++, и здесь смысл получается неприятно сложным и иногда разочаровывающе строгим. Далее будут сделаны попытки объяснить, как используется ключевое слово ‘const’, и почему оно вообще существует (перевод статьи [1]).
[Простое использование const]
Чтобы создать декларацию константы, слева от её имени добавляется ключевое слово const. Конечно же, необходимо сразу же инициализировать значение в конструкторе (в момент декларации константы), потому позже по правилам языка мы не имеем права изменить константу. Например,
Создаст целочисленную константу, из-за отсутствия воображения названную как ‘Constant1’, со значением 96.
Такие константы полезны для параметров, которые используются в программе, если их не надо изменять после того, как программа скомпилирована. Достоинство const по сравнению с директивой препроцессора языка C ‘#define’ в том, что компилятор знает о типе константы, и не просто подставляет в нужное место программы нужный текст. Таким образом, при компиляции может быть сделана дополнительная проверка типа, и если что-то не так, то сообщения об ошибке несоответствия типа могут пригодиться.
декларирует указатель именем Constant2, который указывает на какое-то постоянное значение типа int. Т. е. сам указатель изменять можно, но значение, на которое указывает указатель, изменять нельзя. Вот альтернативная запись того же самого (подробнее см. [2]):
декларирует, что Constant3 является постоянным указателем на некоторую переменную int. Т. е. значение указателя будет всегда неизменным, а значение переменной, на которую указывает указатель, менять можно. И следующее выражение:
декларирует, что Constant4 является постоянным указателем, указывающим на постоянный int. Просто запомните, что ‘const’ прикладывается непосредственно к объекту справа от const (кроме случая, когда справа от const нет ничего; тогда const прикладывается непосредственно к тому, что слева от него).
[Использование const в возвращаемых значениях функции]
Из возможных комбинаций указателей и const, когда постоянный указатель указывает на переменную. Это полезно для ячеек хранения, которые можно изменить, но их положение в памяти (адрес) не меняется.
Еще полезнее указатель (постоянный или нет), указывающий на ‘const’ значение. Это полезно для возврата постоянных строк и массивов из функции, потому что эти строки и массивы реализованы как указатели, и иначе программа могла бы их случайно попытаться изменить и разрушить. Вместо трудной задачи найти место сбоя в программе, попытка изменить неизменяемое значение будет обнаружена еще во время компиляции.
Например, если функция, возвращающая постоянную строку ‘Some text’ написана так:
то программа потерпит сбой, если случайно поменяет значение примерно так:
В то время как компилятор сразу нашел бы ошибку, если бы функция была написана с ключевым словом const:
потому что компилятор знает о том, что возвращенное функцией значение поменять нельзя.
Когда подпрограмма или функция вызывается с параметрами, то переменные, переданные как параметры, могут быть использованы не только для передачи данных в функцию, но еще и для целей возврата обработанных данных из функции. Некоторые языки разрешают такую фичу специальным синтаксисом, как например добавлением типов параметров ‘in:’, ‘out:’ и ‘inout:’, в то время как на языке C нужно работать на более низком уровне, и указывать метод передачи переменных, выбрав такой, который разрешает нужное направление перемещения данных.
Например, подпрограмма наподобие такой:
то по завершению работы функции переданная в параметре переменная останется неизмененной, она не будет установлена в значение 96.
Добавление символа ‘&’ к имени параметра в C++ (очень запутывающий синтаксис, потому что & перед именем переменной в коде программы генерируют в языке C указатели!) приведет к тому, что в подпрограмму (или функцию) будет передана не копия переменной, а ссылка на неё. Т. е. внутри функции будет использоваться сама переменная, так что функция может её поменять. Таким образом, если подпрограмма написана так:
то она установит на выходе переданную переменную в значение 96. Этот метод передачи переменной не как копии называется на языке ‘ссылкой’ (reference).
Передача переменных по ссылке является дополнением языка C++, которого не было в C. Чтобы передать изменяемую переменную на оригинальном C, использовалась передача указателя на переменную. Тогда внутри тела функции появляется возможность поменять внешнюю по отношению к функции переменную, которая передана в параметре через указатель. Пример:
Это будет работать (в том числе и на C++), но синтаксис получается довольно громоздким.
Чтобы устранить проблему, в списке параметров может быть использовано ключевое слово ‘const’. Например, если написать
то это заставит компилятор передать переменную как есть, не копируя, но кроме этого запретит её изменение в теле функции. Это выглядит грязным, потому что по сути искусственно превращает доступ к переменной на только на чтение, хотя цель этого всего состояла в трюке заставить компилятор выполнить некоторую оптимизацию по экономии памяти (обычно в стеке).
В ООП вызов ‘метода’ (это теперь так называется функция в стиле ООП) объекта имеет дополнительное усложнение. Кроме доступа к переменным в списке параметров метода, метод имеет доступ к полям (или членам, т. е. переменным) самого объекта класса, которые всегда передаются непосредственно, не как копии. Например, простейший класс Class1, определенный так:
не имеет вообще никаких явных параметров для метода Method1, но вызов Method1 в этом классе мог бы поменять значение члена класса MemberVariable1. Например, так:
Решение состоит в том, чтобы добавить const после списка параметров:
Это запретит методу Method1 в классе Class2 изменять какие бы ни было члены в объекте этого класса.
И конечно, иногда нужно комбинировать некоторые из этих разных использований ключевого слова const, что может ввести в конфуз:
В этом примере целых 5 раз было использовано ключевое слово const, что означает следующее: переменная, на которую будет указывать возвращенный из метода Method3 указатель, менять нельзя. Нельзя также менять и сам возвращенный указатель. Причем переданный в метод указатель не будет изменен, как и значение, на который указывает, не может быть изменено, и Method3 не имеет права изменять поля своего класса!
[Неудобства от const]
Помимо запутывающего синтаксиса const, есть другие полезные фичи, которыми система может ограничивать действия выполняемой программы.
Нельзя просто избегать использования const в методах класса, потому что const заразная. Объект, который сделан как const, например будучи переданным как параметр способом ‘const &’, может вызывать только те из его методов, которые явно были декларированы как ‘const’ (потому что система вызовов C++ слишком проста, чтобы работать с такими методами, которые не объявлены явно как const, и они ничего фактически не изменили бы). Поэтому методы класса, которые не изменяют объект, лучше всего декларировать как ‘const’, чтобы не было препятствий для их вызова, когда объект класса так или иначе получил состояние ‘const’. В более поздних версиях C++ объект или переменная, которая была объявлена как ‘const’, может быть преобразована к изменяемой путем использования ‘const_cast’ которая является костылем, похожим на ‘mutable’, и снова делает ‘const’ виртуально бесполезным.
Урок №37. const, constexpr и символьные константы
Обновл. 11 Сен 2021 |
До этого момента, все переменные, которые мы рассматривали, были обычными. Их значения можно было изменить в любое время, например:
Тем не менее, иногда полезно использовать переменные, значения которых изменить нельзя — константы.
Константы
Чтобы сделать переменную константой — используйте ключевое слово const перед типом переменной или после него. Например:
Несмотря на то, что язык C++ позволяет размещать const как перед типом данных, так и после него, хорошей практикой считается размещать const перед типом данных.
Константы должны быть инициализированы при объявлении. Изменить их значения с помощью операции присваивания нельзя:
Объявление константы без её инициализации также вызовет ошибку компиляции:
Обратите внимание, константы могут быть инициализированы и с помощью неконстантных значений:
Ключевое слово const является наиболее полезным (и наиболее часто используемым) с параметрами функций:
Время компиляции и время выполнения
Когда вы находитесь в процессе компиляции программы, то это время компиляции (англ. «compile time»). Компилятор проверяет вашу программу на синтаксические ошибки и, если их нет, конвертирует код в объектные файлы.
Временной промежуток с момента старта выполнения программы и до момента окончания её работы называется временем выполнения программы (англ. «runtime»). Код выполняется строка за строкой.
Спецификатор constexpr
В языке C++ есть два вида констант:
Константы времени выполнения. Их значения определяются только во время выполнения программы. Переменные типа usersAge и myValue выше являются константами времени выполнения, так как компилятор не может определить их значения во время компиляции. usersAge зависит от пользовательского ввода (который можно получить только во время выполнения программы), а myValue зависит от значения, переданного в функцию (это значение также определится только во время выполнения программы).
Константы времени компиляции. Их значения определяются во время компиляции программы. Например, переменная со значением силы тяжести на Земле является константой времени компиляции, так как мы её определяем во время написания программы (до начала её выполнения).
В большинстве случаев не важно какой тип константы вы используете: времени выполнения или времени компиляции. Однако, все же есть несколько ситуаций, когда C++ может потребовать константу времени компиляции вместо времени выполнения (например, при определении длины массива фиксированного размера — мы рассмотрим это несколько позже). Так как есть 2 типа констант, то компилятору нужно постоянно отслеживать, к какому из них относится какая переменная. Чтобы упростить это задание, в C++11 добавили спецификатор constexpr, который сообщает компилятору, что текущая переменная является константой времени компиляции: