Многоликий const
Ключевое слово const — одно из самых многозначных в C++. Правильно использование const позволяет организовать множество проверок ещё на этапе компиляции и избежать многих ошибок из числа тех, которые бывает трудно найти при помощи отладчиков и/или анализа кода.
Первая половина заметки рассчитана скорее на начинающих (надеюсь мнемоническое правило поможет вам запомнить, где и для чего используется const), но, возможно, и опытные программисты смогут почерпнуть интересную информацию о перегрузке методов по const.
Константы и данные
Самый простой случай — константные данные. Возможно несколько вариантов записи:
const int i(1); int const j(1); int const k=1;
Все они правильные и делают одно и тоже — создают переменную, значение которой изменить нельзя.
const int k=1; k = 7; //Константы и указатели
При использовании const с указателями, действие модификатора может распространяться либо на значение указателя, либо на данные на которые указывает указатель.
Работает (const относится к данным):
const char * a = "a"; a="b";Тоже самое и тоже работает:
char const * a = "a"; a="b";А вот это уже не работает:
char * const a = "a"; a="b"; //Если бы операция присвоения изменяла бы не указатель, а данные:
*a = 'Y';то ситуация была бы диаметрально противоположной.
Существует мнемоническое правило, позволяющее легко запомнить, к чему относится const. Надо провести черту через "*", если const слева, то оно относится к значению данных; если справа — к значению указателя.
Ну и конечно, const можно написать дважды:
const char * const s = "data";Константы и аргументы/результаты функций
C функциями слово const используется по тем же правилам, что при описании обычных данных.
Константы и методы (перегрузка)
А вот с методами есть одна тонкость.
Во-первых, для методов допустимо использование const, применительно к this. Синтаксис таков:
class A < private: int x; public: void f(int a) const < x = a; // >;Кроме того, этот const позволяет перегружать методы. Таким образом, вы можете писать оптимизированные варианты методов для константных объектов.
И ещё… я собрался в отпуск… возможно, не смогу ответить на комментарии до понедельника-вторника. Не сочтите за невнимание 🙂
Присваивание символа const char *
Вопрос в следующем. Почему, когда мы const char * присваиваем строку, он присваивает, а символ — нет? Может, где-то для const char * перегружен оператор присваивания, и он ему присваивает новый адрес? А при присваивании литерала мы пытаемся записать по тому же адресу?
Отслеживать
11.5k 8 8 золотых знаков 42 42 серебряных знака 69 69 бронзовых знаков
задан 15 мар 2013 в 8:58
679 4 4 золотых знака 11 11 серебряных знаков 24 24 бронзовых знака
Может быть потому что c и v - указатели? Попробуйте *c[2] = '5';
15 мар 2013 в 9:00эм. так нельзя. и я не понял, к чему вы клоните сказав, что это указатели. То что вы написали по моему это типо адресу присвоить '5'.
15 мар 2013 в 9:04
3 ответа 3
Сортировка: Сброс на вариант по умолчанию
Вопрос в следующем. Почему, когда мы const char * присваиваем строку, он присваивает, а символ — нет?
Нужно различать константный указатель и указатель на константу.
1) Указатель на константу: нельзя менять содержимое объекта, на который указывает указатель, но можно менять содержимое самого указателя (указатель — это переменная, содержащая адрес другой переменной).
char array[] = "string"; const char * с = array; // Указатель на объект, который нельзя менять c[1] = 'a'; // Нельзя, т. к. меняется содержимое указываемого объекта с = "345"; // Можно, т. к. меняется значение самого указателя
2) Константный указатель: можно менять содержимое объекта, но нельзя менять значение самого указателя. Проще говоря, указатель нельзя переназначить на другой объект, но сам указатель поменять можно.
char array[] = "string"; char * const с = array; c[1] = 'a'; // можно с = "345"; // нельзя
3) Константный указатель на константу: совокупность первых двух.
const char * const с = "123";
Отслеживать
11.5k 8 8 золотых знаков 42 42 серебряных знака 69 69 бронзовых знаков
ответ дан 15 мар 2013 в 9:17
3,456 12 12 серебряных знаков 9 9 бронзовых знаков
странно, у меня меняется ideone.com/zUcHdh, может быть Вы адрес самого указателя смотрели?
15 мар 2013 в 9:31
cout << &с; я делал так 2 раза до присваивания и после. 15 мар 2013 в 9:43cout
15 мар 2013 в 9:49спс, а я не мог понять, значит переопределен, ок) то есть можно сказать, что в адресе переменной указателя записан адрес char * ?
15 мар 2013 в 9:54
спасибо, понял.
15 мар 2013 в 9:58После присваивания const char *c = "123"; в c храниться некий адрес, по которому размещена строка "123". Компилятор обычно размещает ее в памяти, которая "read only" (записать то можно туда, просто операционная система знает, что это неправильно и пресекает попытки записи). Но так как использовать будет только для чтения, то все нормально.
Строка c[2] = '5'; пытается записать в эту неизменяемую память. Память только для чтения.
Почему так сделано? для оптимизаций и упрощения. Пусть в Вашей программе есть ещё десять мест, где есть константа "123". Компилятор не будет создавать 11 копий, а будет использовать один и тот же адрес. А теперь представьте, что вышеуказанное изменение разрешено:).
Старые компиляторы этого не делали и часто наблюдались интересные спецэффекты, когда константы изменяли свое значение.
Но компилятор может поступить и по другому.
const char * test = "test111"; printf(test);
в этом случае может даже выбросить переменную test, а ее значение вывести непосредственно.
присваивание c = v; разрешено, так как не изменяются данные по указателю, а только он сам. А указатель не константный. Аналогично и с присваиванием c = "asdfas"; .
Отслеживать
ответ дан 15 мар 2013 в 9:16
112k 6 6 золотых знаков 93 93 серебряных знака 159 159 бронзовых знаковУчебник по программированию на С++ говорит, что:
Официальный тип строковых литералов, таких как "Привет",- const char []. Строка "Привет" имеет тип const char [7]. Секундочку! Ведь в слове Привет шесть букв, а не семь! Не переживайте, ошибки здесь нет. Дополнительный элемент содержит завершающий пустой символ, ' \0' (значение 0), который сообщает компьютеру длину строки. В конце каждого строкового литерала есть скрытый пустой символ (\0), который позволяет алгоритмам определять длину строки и факт достижения конца строки. Длина строки нужна не всем алгоритмам, но большинству из них. Присвоить строковый литерал переменной типа char* можно следующим образом:
char* х = "Привет";
Однако со значением, на которое указывает х, нельзя работать. Если оно не будет постоянным, возникнет ошибка, как в следующем примере:
*х = 'Т';
Этот код вызывает ошибку, потому что нельзя изменять значение константы. Чтобы получить строку, которую можно изменять, следует присвоить строковый литерал строковому объекту или символьному массиву. Пример объявления символьного массива, элементы которого могут изменяться:
char s[] = "Привет"; s[0] = 'Т';
С этим кодом все в порядке, а новым значением строки будет "Тривет". Многие из стандартных библиотечных функций С принимают в качестве одного из аргументов значение типа char*. Однако если строка хранится в виде указателя на символ, ее длина не может быть определена, как в случае строковых литералов, завершаемых пустым символом. В отсутствие пустого символа невозможно понять, где кончается строка.
Почему конструктор std::string принимает const char*
Добрый день, зачем ctor std::string хочет на вход именно указатель на символьную константу, а не просто указатель на строку символов. Там проводятся оптимизации какие-нибудь? И если да, то какие?
da17 ★
31.05.21 18:20:22 MSK
Намек на то что конструктор не будет изменять входную строку, только прочитает ее
vertexua★★★★★
( 31.05.21 18:23:53 MSK )указатель на символьную константу - это строка и есть (указатель на начало её), в классическом C-шном смысле, в частности выражения "foobar" такие производят
dumdum
( 31.05.21 18:24:38 MSK )
Последнее исправление: dumdum 31.05.21 18:25:07 MSK (всего исправлений: 1)char c_str[] = "123"; std::string str;
Обычный char* также принимается.
anonymous
( 31.05.21 18:25:20 MSK )
хочет на вход именно указатель на символьную константу, а не просто указатель на строку символов.
Конструктор std::string принимает const char* . const char* это указатель на константный char, в данном случае интерпретирующийся как первый байт «строки». Напоминаю, что в С строк нет.
const там говорит не о том, что это «символьная константа» (что бы это ни значило), а о том, что std::string не будет изменять переданную строку, а просто скопирует себе ее содержимое.
Siborgium ★★★★★
( 31.05.21 18:26:39 MSK )
Ответ на: комментарий от Siborgium 31.05.21 18:26:39 MSKВот это я и хотел уточнить, std::string хочет что бы ему пришла строка которую никто не будет изменять, что бы просто создать указатель на нее без всяких malloc. Т.е. будет ли разница в реализации хранения символьного массива в зависимости от того передаем мы const char* или просто char*
da17 ★
( 31.05.21 18:44:20 MSK ) автор топика
Последнее исправление: da17 31.05.21 18:44:58 MSK (всего исправлений: 1)Ответ на: комментарий от da17 31.05.21 18:44:20 MSK
Вот это я и хотел уточнить, std::string хочет что бы ему пришла строка которую никто не будет изменять, что бы просто создать указатель на нее без всяких malloc.
Нет и нет. const char* может изменяться, и string всегда копирует себе строку. const char* говорит только что сам конструктор string не будет изменять данные по этому указателю.
Т.е. будет ли разница в реализации хранения символьного массива в зависимости от того передаем мы const char* или просто char*
slovazap ★★★★★
( 31.05.21 18:48:37 MSK )
Последнее исправление: slovazap 31.05.21 18:49:38 MSK (всего исправлений: 2)Ответ на: комментарий от da17 31.05.21 18:44:20 MSK
std::string хочет что бы ему пришла строка которую никто не будет изменять, что бы просто создать указатель на нее без всяких malloc
Нет. Не «никто не будет изменять», а которую std::string обещает не изменять.
Т.е. будет ли разница в реализации хранения символьного массива в зависимости от того передаем мы const char* или просто char*
Нет. Передаваемый char* будет приводиться к const char* .
С как изменить const char
В действительности, в соответствии со стандартом на языки C и C++ (правило чтения операторов "Read Declarations Right-to-Left", т. е. надо читать всегда оператор справа налево), модификатор const влияет на элемент, слева от которого стоит const. Поэтому если использовать const перед типом, то это фактически ничего не значит, и используется просто для удобства запоминания.
К примеру, следующие два оператора совершенно эквивалентны:
char const * УказательНаНЕизменяемоеСодержимое1;const char * УказательНаНЕизменяемоеСодержимое2;Оба указателя УказательНаНеизменяемоеСодержимое1 и УказательНаНеизменяемоеСодержимое2 указывают на строку, являющуюся константой, и оба эти указателя можно модифицировать runtime в коде программы.
Примечание: в какой реально памяти находится константа - зависит от платформы, для которой компилируется код. Например, для процессоров ARM встраиваемых систем константа может находиться в памяти программ FLASH, а для процессоров PC константы, как и обычные переменные, хранятся в памяти RAM.
Если же Вы хотели получить от указателя другое, чтобы это был постоянный указатель, который нельзя модифицировать, то нужно поместить const после звездочки (тогда получится наоборот, строка может быть изменяемой, а сам указатель изменить нельзя):
char * const НЕизменяемыйУказательНаИзменяемоеСодержимое;Чтобы защитить от изменения как сам указатель, так и содержимое, на которое он указывает, то нужно использовать два ключевых слова const:
char const * const НЕизменяемыйУказательНаНЕизменяемоеСодержимое;[Примеры ключевого слова const при использовании с указателями]
Ключевое слово const используется, когда мы хотим сделать что-то - наподобие переменной - доступным только для чтения. При этом это что-то совсем не обязательно может храниться в памяти ROM, данные объекта "только для чтения" могут храниться и в RAM (зависит от используемой платформы, для которой компилируется код). Ниже приведено несколько примеров использования ключевого слова const.
const int j = 10;В этом простом примере если мы попробуем в коде присвоить значение переменной j, то это будет обнаружено на этапе компиляции программы, компилятор выдаст ошибку.
Указатель на константу. Когда ключевое слово const используется с указателями, то важно помнить, что реальный смысл определения зависит от положения ключевого слова const.
Указатель на константу указывает на данные, которые должны быть постоянными. Это просто означает, что мы не можем поменять данные, на которые ссылается указатель. Вот несколько примеров кода, иллюстрирущие то, что мы подразумеваем указателем на константу:
// Создание указателя на константу:
const char* pntr = "не изменяемые данные";// Это тоже создает указатель на константу. Хотя здесь синтаксис
// отличается от приведенного выше, это в сущности одно и то же.
char const* pntr = "не изменяемые данные";// Этот код приведет к ошибке, потому что данные не могут быть
// изменены.
*pntr = "проверка";// Но этот код считается безошибочным, потому что адрес указателя
// можно поменять (сам указатель не константа). pntr = "проверка";Постоянный указатель. В таком указателе находящийся в нем адрес является константой. Но сама переменная, на которую ссылается указатель может быть изменена, т. е. она может не являться константой. Вот несколько примеров:
// Это определение const-указателя, хранящийся в нем
// адрес поменять нельзя, но сами данные поменять можно.
char* const pntr = "какие-то данные";// Этот код корректен и допустим, потому что данные менять можно:
*pntr = "проверка";// Но этот код приведет к ошибке, потому что адрес
// постоянного указателя поменять нельзя: pntr = "проверка";Постоянный указатель на константу. Можно думать об этом случае как о комбинации двух примеров выше. Т. е. нельзя поменять ни сам адрес (значение указателя), ни данные, на которые он указывает. Пример:
// Это const-указатель на константу.
const char* const pntr = "Это поменять нельзя";[Ссылки]
1. const char* and char const* - are they the same? site:stackoverflow.com .
2. Барт Симпсон Генератор картинок онлайн site:advice-dog.ru .
3. Использование описателя const в C.
4. Как читать сложные декларации C/C++.Комментарии
+4 #1 Ruslan 22.04.2020 19:03
Но позвольте, вы же говорите что const влияет только на содержимое слева от const, а сами используете const самым левым (т.е. не влияющим ни на что).
/*const int j = 10;
В этом простом примере если мы попробуем в коде присвоить значение переменной j, то это будет обнаружено на этапе компиляции программы, компилятор выдаст ошибку." */