Какая точка указателей на const?

Я не говорю о указателях на значения const, но сами указатели const.

Я изучаю C и С++ за пределами самых простых вещей, и до сегодняшнего дня я понял, что указатели передаются по значению функциям, что имеет смысл. Это означает, что внутри функции я могу сделать скопированную точку указателя на какое-то другое значение, не влияя на исходный указатель от вызывающего.

Итак, какая точка имеет заголовок функции, который говорит:

void foo(int* const ptr);

Внутри такой функции вы не можете заставить ptr указывать на что-то другое, потому что оно const, и вы не хотите, чтобы оно было изменено, но функция вроде этого:

void foo(int* ptr);

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

Ответ 1

const - это инструмент, который вы должны использовать для достижения очень важной концепции С++:

Найти ошибки во время компиляции, а не во время выполнения, путем получения компилятора для обеспечения того, что вы имеете в виду.

Несмотря на то, что он не изменяет функциональность, добавление const генерирует ошибку компилятора, когда вы делаете то, что вы не хотели делать. Представьте следующую опечатку:

void foo(int* ptr)
{
    ptr = 0;// oops, I meant *ptr = 0
}

Если вы используете int* const, это приведет к ошибке компилятора, потому что вы меняете значение на ptr. Добавление ограничений с помощью синтаксиса - хорошая вещь вообще. Просто не заходите слишком далеко - пример, который вы дали, - это случай, когда большинство людей не беспокоится об использовании const.

Ответ 2

Я использую только аргументы const, потому что это позволяет больше проверок компилятора: если я случайно переопределяю значение аргумента внутри функции, компилятор укусит меня.

Я редко использую переменные, их чистые, чтобы создавать новые переменные для хранения новых значений, поэтому по существу все мои объявления переменных const (за исключением некоторых случаев, таких как переменные цикла, где const будет препятствовать работе кода).

Обратите внимание, что это имеет смысл только в определении функции. Он не входит в объявление, что и видит пользователь. И пользователю все равно, использую ли я const для параметров внутри функции.

Пример:

// foo.h
int frob(int x);
// foo.cpp
int frob(int const x) {
   MyConfigType const config = get_the_config();
   return x * config.scaling;
}

Обратите внимание, как и аргумент, и локальная переменная const. Также нет необходимости, но с функциями, которые даже немного больше, это неоднократно спасало меня от ошибок.

Ответ 3

Ваш вопрос затрагивает нечто более общее: должны ли аргументы функции быть const?

Константа аргументов значения (например, ваш указатель) является деталью реализации, и она делает не частью объявления функции. Это означает, что ваша функция всегда такова:

void foo(T);

Это полностью зависит от исполнителя функции, хочет ли она использовать переменную аргумента function-scope в изменяемом или постоянном виде:

// implementation 1
void foo(T const x)
{
  // I won't touch x
  T y = x;
  // ...
}

// implementation 2
void foo(T x)
{
  // l33t coding skillz
  while (*x-- = zap()) { /* ... */ }
}

Итак, следуйте простому правилу, чтобы никогда не помещать const в объявление (заголовок) и не помещать его в определение (реализацию), если вы не хотите или не должны изменять переменную.

Ответ 4

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

Ответ 5

Существует много ключевого слова const, оно довольно сложное. Как правило, добавление большого количества const в вашу программу считается хорошей практикой программирования, поиск в Интернете для "const correctness", и вы найдете много информации об этом.

Ключевое слово const - это так называемый "классификатор типов", другие - volatile и restrict. По крайней мере, волатильность следует тем же (запутывающим) правилам, что и const.


Прежде всего, ключевое слово const выполняет две цели. Наиболее очевидным является защита данных (и указателей) от преднамеренного или случайного злоупотребления, делая их доступными только для чтения. Любая попытка изменить переменную const будет обнаружена компилятором во время компиляции.

Но есть и другая цель в любой системе с постоянной памятью, а именно для обеспечения того, чтобы в такой памяти была выделена определенная переменная - например, EEPROM или flash. Они называются энергонезависимыми памятью NVM. Переменная, выделенная в NVM, по-прежнему будет следовать всем правилам константной переменной.

Существует несколько различных способов использования ключевого слова const:

Объявить постоянную переменную.

Это можно сделать либо как

const int X=1; or
int const X=1;

Эти две формы полностью эквивалентны. Последний стиль считается плохим и не должен использоваться.

Причина, по которой вторая строка считается плохим, вероятно, потому, что "спецификаторы класса хранения", такие как static и extern, также могут быть объявлены после фактического типа int static и т.д. Но это делается для спецификаторов класса хранения обозначается как устаревшая функция комитетом С (проект стандарта ISO 9899 N1539, 6.11.5). Поэтому для согласованности не следует также записывать квалификаторы типов. Это не служит никакой другой цели, но все равно путать читателя.

Объявить указатель на постоянную переменную.

const int* ptr = &X;

Это означает, что содержимое "X" не может быть изменено. Это обычный способ объявления таких указателей, в основном как часть параметров функции для "const correctness". Поскольку "X" фактически не нужно объявлять как const, это может быть любая переменная. Другими словами, вы всегда можете "обновить" переменную до const. Технически, C также позволяет понижать рейтинг от const до простой переменной с помощью явных приемов, но это считается плохим программированием, и компиляторы обычно дают предупреждения против него.

Объявить константный указатель

int* const ptr = &X;

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

void func (int*const* ptrptr)

Я сомневаюсь, что многие программисты C могут получить const и * прямо там. Я знаю, что не могу - мне нужно было проверить с GCC. Я думаю, почему вы редко когда-либо видели этот синтаксис для указателя на указатель, хотя он считается хорошей практикой программирования.

Константные указатели также могут использоваться для обеспечения того, чтобы сама указательная переменная объявлялась в постоянной памяти, например, вы могли бы захотеть объявить какую-то таблицу поиска на основе указателя и выделить ее в NVM.

И, конечно, как указано в других ответах, постоянные указатели также могут использоваться для обеспечения "корректности констант".

Объявить постоянный указатель на постоянные данные

const int* const ptr=&X;

Это два типа указателей, описанных выше, вместе со всеми их атрибутами.

Объявить функцию-член только для чтения (С++)

Поскольку это помечено как С++, я должен также упомянуть, что вы можете объявлять функции-члены класса как const. Это означает, что функции не разрешается изменять какой-либо другой член класса при его вызове, что предотвращает случайные ошибки программиста класса, а также сообщает вызывающей стороне функции-члена, что они не будут возиться с чем-либо путем его вызова. Синтаксис:

void MyClass::func (void) const;

Ответ 6

Вы правы, для звонящего это абсолютно не имеет значения. Но для автора функции это может быть защитная сетка "ладно, мне нужно убедиться, что я не говорю об этом неправильно". Не очень полезно, но не бесполезно.

Это в основном то же самое, что с int const the_answer = 42 в вашей программе.

Ответ 7

... сегодня я понял, что указатели передаются по значению в функции, что имеет смысл.

(imo) это действительно не имеет смысла в качестве значения по умолчанию. более разумное значение по умолчанию должно передаваться как непереводимый указатель (int* const arg). то есть я предпочел бы, чтобы указатели, переданные как аргументы, были неявно объявлены const.

Итак, какое преимущество const?

преимущество заключается в том, что он достаточно прост, а иногда и неясен, когда вы изменяете адрес, на который указывает аргумент, так что вы можете ввести ошибку, когда она не const довольно легко. изменение адреса является нетипичным. более ясно создать локальную переменную, если вы намерены изменить адрес. а также манипуляции с необработанным указателем - это простой способ введения ошибок.

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

void func(int* const arg) {
    int* a(arg);
    ...
    *a++ = value;
}

добавив, что локальный виртуально свободен, и это уменьшает вероятность ошибок, улучшая читаемость.

на более высоком уровне: если вы манипулируете аргументом как массив, он обычно более ясен и меньше подвержен ошибкам клиенту, чтобы объявить аргумент как контейнер/коллекцию.

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

Ответ 8

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

16-битный регистр периферийных чипов только для чтения может выглядеть примерно так:

static const unsigned short *const peripheral = (unsigned short *)0xfe0000UL;

Затем вы можете легко прочитать аппаратный регистр, не прибегая к ассемблеру:

input_word = *peripheral;

Ответ 9

int iVal = 10; int * const ipPtr = & iVal;

Как обычная константная переменная, указатель const должен быть инициализирован значением при объявлении, и его значение не может быть изменено.

Это означает, что указатель const всегда будет указывать на одно и то же значение. В приведенном выше случае ipPtr всегда укажет на адрес iVal. Однако, поскольку указанное значение еще не является константным, можно изменить значение, на которое указывает, путем разыменования указателя:

* ipPtr = 6;//разрешено, так как pnPtr указывает на не const const

Ответ 10

Можно задать один и тот же вопрос о любом другом типе (не только указатели):

/* Why is n const? */
const char *expand(const int n) {
    if (n == 1) return "one";
    if (n == 2) return "two";
    if (n == 3) return "three";
    return "many";
}

Ответ 11

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

В вашем конкретном случае функционально это не влияет, как во многих других случаях, когда вы объявляете локальную переменную как const, но она устанавливает ограничение, которое вы не можете изменить эту переменную.

Ответ 12

Передача указателя const на функцию имеет мало смысла, так как она будет передаваться по значению anyways. Это всего лишь одна из тех вещей, которые разрешены общим языком. Запрет на это только потому, что это не имеет смысла, просто сделает спецификацию языка. больше.

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

Ответ 13

Я думаю, преимущество в том, что компилятор может выполнять более агрессивные оптимизации внутри функции, зная, что этот указатель не может измениться.

Он также избегает, например. передавая этот указатель на подфункцию, которая принимает ссылку на указатель не const (и поэтому может изменить указатель как void f(int *&p)), но я согласен, что в этом случае полезность несколько ограничена.

Ответ 14

Пример того, где указатель const очень применим, можно продемонстрировать таким образом. У вас есть класс с динамическим массивом внутри него, и вы хотите передать пользователю доступ к массиву, но не предоставив им права на изменение указателя. Рассмотрим:

#include <new>
#include <string.h>

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const GetArray(){ return Array; }
};

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' '; //You can still modify the chars in the array, user has access
    Temp.GetArray()[1] = ' '; 
    printf("%s\n",Temp.GetArray());
}

Что производит:

Входные данные
  поместить данные

Но если мы попробуем это:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    Temp.GetArray() = NULL; //Bwuahahahaa attempt to set it to null
}

Получаем:

error: lvalue требуется как левый операнд присваивания //Drat foiled again!

Таким образом, мы можем изменить содержимое массива, но не указатель массива. Хорошо, если вы хотите убедиться, что указатель имеет согласованное состояние при передаче его обратно пользователю. Однако есть один улов:

int main()
{
    TestA Temp;
    printf("%s\n",Temp.GetArray());
    Temp.GetArray()[0] = ' ';
    Temp.GetArray()[1] = ' ';
    printf("%s\n",Temp.GetArray());
    delete [] Temp.GetArray(); //Bwuahaha this actually works!
}

Мы все еще можем удалить ссылку на указатель памяти, даже если мы не можем изменить сам указатель.

Итак, если вы хотите, чтобы ссылка на память всегда указывала на что-то (IE никогда не будет изменен, подобно тому, как работает эта ссылка в настоящий момент), тогда это очень применимо. Если вы хотите, чтобы у пользователя был полный доступ и его изменение, то для вас неконсолидация.

Edit:

После замечания okorz001 о невозможности назначить из-за того, что GetArray() является операндом правого значения, его комментарий является полностью правильным, но выше все еще применяется, если вы должны были вернуть ссылку на указатель (я полагаю, что я предполагаемый GetArray ссылался на ссылку), например:

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; } //Note & reference operator
        char * &GetNonConstArray(){ return Array; } //Note non-const
};

int main()
{
    TestA Temp;
    Temp.GetArray() = NULL; //Returns error
    Temp.GetNonConstArray() = NULL; //Returns no error
}

Вернется в первую очередь, что приведет к ошибке:

ошибка: назначение места только для чтения 'Temp.TestA:: GetArray()'

Но второй будет происходить весело, несмотря на потенциальные последствия снизу.

Очевидно, вопрос будет поднят: "Почему вы хотите вернуть ссылку на указатель"? Существуют редкие случаи, когда вам необходимо назначить память (или данные) непосредственно исходному указателю (например, создание собственного malloc/free или new/free front-end), но в этих случаях это неконстантная ссылка, Ссылка на указатель const Я не сталкивался с ситуацией, которая бы это оправдывала (разве что, возможно, как объявленные константные ссылочные переменные, а не возвращаемые типы?).

Рассмотрим, есть ли у нас функция, которая принимает указатель const (по сравнению с тем, который этого не делает):

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const &GetArray(){ return Array; }

        void ModifyArrayConst(char * const Data)
        {
            Data[1]; //This is okay, this refers to Data[1]
            Data--; //Produces an error. Don't want to Decrement that.
            printf("Const: %c\n",Data[1]);
        }

        void ModifyArrayNonConst(char * Data)
        {
            Data--; //Argh noo what are you doing?!
            Data[1]; //This is actually the same as 'Data[0]' because it relative to Data position
            printf("NonConst: %c\n",Data[1]);
        }
};

int main()
{
    TestA Temp;
    Temp.ModifyArrayNonConst("ABCD");
    Temp.ModifyArrayConst("ABCD");
}

Ошибка в const вызывает сообщение:

error: уменьшение параметра только для чтения "Данные"

Это хорошо, поскольку мы, вероятно, не хотим этого делать, если только мы не хотим вызывать проблемы, обозначенные в комментариях. Если мы отредактируем декремент в функции const, произойдет следующее:

NonConst: A
Const: B

Ясно, что хотя A является "данными [1]", он обрабатывается как "Данные [0]", потому что указатель NonConst разрешил операцию уменьшения. Когда const реализован, как пишет другой человек, мы обнаруживаем потенциальную ошибку до ее возникновения.

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

int main()
{
    int A = 10;
    int * const B = &A;
    *B = 20; //This is permitted
    printf("%d\n",A);
    B = NULL; //This produces an error
}

При попытке компиляции возникает следующая ошибка:

error: назначение переменной только для чтения "B"

Скорее всего, это плохо, если нужна постоянная ссылка на A. Если комментарий B = NULL закомментирован, компилятор с радостью позволит нам изменить *B и, следовательно, A. Это может показаться нецелесообразным с ints, но подумайте, была ли у вас одна позиция графического приложения, в которой вы хотели, чтобы не поддающийся модификации указатель указывал к которому вы могли бы пройти.

Это использование переменной (извините непреднамеренный каламбур), но используется правильно, это еще один инструмент в поле, чтобы помочь с программированием.

Ответ 15

Нет ничего особенного в указателях, где вы никогда не захотите, чтобы они были const. Так же, как вы можете иметь постоянные константы класса int, вы также можете иметь постоянные указатели по тем же причинам: вы хотите убедиться, что никто не изменит то, на что указывает. Ссылки на С++ несколько адресуют это, но поведение указателя наследуется от C.

Ответ 16

Я считаю, что это предотвратит увеличение или уменьшение кода указателя внутри тела функции.

Ответ 17

Типы объявления любых переменных, например -
  (1) Объявление постоянной переменной.
       DataType const varibleName;

 int const x;
    x=4; //you can assign its value only One time
(2) Объявить указатель на постоянную переменную
const dataType* PointerVaribleName=&X;
 const int* ptr = &X;
     //Here pointer variable refer contents of 'X' that is const Such that its cannot be modified
dataType* const PointerVaribleName=&X;
 int* const ptr = &X;
     //Here pointer variable itself is constant  Such that value of 'X'  can be modified But pointer can't be modified