it-swarm.com.ru

В чем разница между const int *, const int * const и int const *?

Я всегда путаюсь, как правильно использовать const int*, const int * const и int const *. Существует ли набор правил, определяющих, что вы можете и не можете делать?

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

1126
ultraman

Прочитайте это в обратном направлении (как управляется по часовой стрелке/спиральным правилом ):

  • int* - указатель на int
  • int const * - указатель на const int
  • int * const - постоянный указатель на int
  • int const * const - константный указатель на const int

Теперь первая const может быть с любой стороны типа, поэтому:

  • const int * == int const *
  • const int * const == int const * const

Если вы хотите сойти с ума, вы можете сделать что-то вроде этого:

  • int ** - указатель на указатель на int
  • int ** const - постоянный указатель на указатель на int
  • int * const * - указатель на константный указатель на int
  • int const ** - указатель на указатель на const int
  • int * const * const - константный указатель на константный указатель на int
  • ...

И чтобы убедиться, что мы ясно понимаем значение const

const int* foo;
int *const bar; //note, you actually need to set the pointer 
                //here because you can't change it later ;)

foo является указателем переменной на постоянное целое число. Это позволяет вам изменить то, на что вы указываете, но не значение, на которое вы указываете. Чаще всего это наблюдается в строках в стиле C, где у вас есть указатель на const char. Вы можете изменить строку, на которую вы указываете, но вы не можете изменить содержимое этих строк. Это важно, когда сама строка находится в сегменте данных программы и не должна изменяться.

bar - это постоянный или фиксированный указатель на значение, которое можно изменить. Это как ссылка без лишнего синтаксического сахара. Из-за этого обычно вы используете ссылку, в которой вы используете указатель T* const, если вам не нужно разрешать указатели NULL.

1920
Matt Price

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

вот демо:

pointer to int

const pointer to int const

pointer to int const

pointer to const int

const pointer to int

274
Shijing Lv

Я думаю, что все уже здесь ответили, но я просто хочу добавить, что вы должны остерегаться typedefs! Они НЕ просто текстовые замены.

Например:

typedef char *ASTRING;
const ASTRING astring;

Тип astring - char * const, а не const char *. Это одна из причин, по которой я всегда стараюсь ставить const справа от типа, а не в начале.

130
Kaz Dragon

Как почти все отметили:

В чем разница между const X* p, X* const p и const X* const p?

Вы должны прочитать объявления указателя справа налево.

  • const X* p означает «p указывает на постоянный X»: объект X нельзя изменить с помощью p.

  • X* const p означает «p является константным указателем на X, который не является константным»: вы не можете изменить сам указатель p, но вы можете изменить X-объект с помощью p.

  • const X* const p означает, что «p является константным указателем на X, который является константой»: вы не можете изменить сам указатель p, а также не можете изменить объект X с помощью p.

47
luke
  1. Постоянная ссылка:

    Ссылка на переменную (здесь int), которая является постоянной. Мы передаем переменную в основном как ссылку, потому что ссылки меньше по размеру, чем фактическое значение, но есть побочный эффект, и это потому, что это похоже на псевдоним для фактической переменной. Мы можем случайно изменить основную переменную через наш полный доступ к псевдониму, поэтому мы устанавливаем ее постоянной, чтобы предотвратить этот побочный эффект.

    int var0 = 0;
    const int &ptr1 = var0;
    ptr1 = 8; // Error
    var0 = 6; // OK
    
  2. Постоянные указатели

    Как только постоянный указатель указывает на переменную, он не может указывать на любую другую переменную. 

    int var1 = 1;
    int var2 = 0;
    
    int *const ptr2 = &var1;
    ptr2 = &var2; // Error
    
  3. Указатель на постоянную

    Указатель, с помощью которого нельзя изменить значение переменной, на которую он указывает, называется указателем на константу.

    int const * ptr3 = &var2;
    *ptr3 = 4; // Error
    
  4. Постоянный указатель на постоянную

    Постоянный указатель на константу - это указатель, который не может изменить адрес, на который он указывает, и не может изменить значение, хранящееся по этому адресу.

    int var3 = 0;
    int var4 = 0;
    const int * const ptr4 = &var3;
    *ptr4 = 1;     // Error
     ptr4 = &var4; // Error
    
41
Behrooz Tabesh

Этот вопрос показывает именно, почему мне нравится делать вещи так, как я упоминал в своем вопросе допустимо ли const после идентификатора типа?

Короче говоря, я считаю, что самый простой способ запомнить правило состоит в том, что «const» идет после того, к чему оно относится. Итак, в вашем вопросе «int const *» означает, что int является константой, а «int * const» будет означать, что указатель является константой.

Если кто-то решит поместить его на передний план (например, «const int *»), в качестве особого исключения в этом случае он применяется к предмету после него.

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

16
T.E.D.

Общее правило заключается в том, что ключевое слово const применяется к тому, что ему предшествует. Исключение, начальная const применяется к следующему.

  • const int* аналогичен int const* и означает «указатель на константу int».
  • const int* const совпадает с int const* const и означает «постоянный указатель на константу int».

Edit: Что нужно делать и чего не делать, если этот ответ недостаточно, не могли бы вы уточнить, что вы хотите?

15
AProgrammer

Простое использование «const»

Самое простое использование - объявить именованную константу. Для этого нужно объявить константу так, как если бы она была переменной, но перед ней добавить «const». Нужно немедленно инициализировать его в конструкторе, потому что, конечно, нельзя установить значение позже, так как это изменило бы его. Например,

const int Constant1=96; 

создаст целочисленную константу, не названную по имени «Constant1», со значением 96.

Такие константы полезны для параметров, которые используются в программе, но их не нужно изменять после компиляции программы. Он имеет преимущество для программистов перед командой препроцессора C '#define' в том, что он понимается и используется самим компилятором, а не просто подставляется препроцессором в текст программы перед тем, как добраться до основного компилятора, поэтому сообщения об ошибках гораздо более полезны ,.

Он также работает с указателями, но нужно быть осторожным, где «const», чтобы определить, является ли указатель или то, на что он указывает, постоянным или и тем, и другим. Например,

const int * Constant2 

объявляет, что Constant2 является переменным указателем на постоянное целое число и

int const * Constant2

альтернативный синтаксис, который делает то же самое, тогда как

int * const Constant3

объявляет, что Constant3 является постоянным указателем на целое число переменной и

int const * const Constant4

объявляет, что Constant4 является постоянным указателем на постоянное целое число. По сути, «const» относится ко всему, что находится непосредственно слева от него (кроме случаев, когда там ничего нет, в этом случае оно применяется к тому, что непосредственно справа от него).

ссылка: http://duramecho.com/ComputerInformation/WhyHowCppConst.html

14
ufukgun

У меня были те же сомнения, что и у вас, пока я не наткнулся на эту книгу Гуру С ++ Скотта Мейерса. Обратитесь к третьему пункту в этой книге, где он подробно рассказывает об использовании const.

Просто следуйте этому совету

  1. Если слово const появляется слева от звездочки, то, что указано, является постоянным
  2. Если слово const появляется справа от звездочки, сам указатель является постоянным
  3. Если const появляется с обеих сторон, оба являются постоянными
7
rgk

Синтаксис объявления C и C++ неоднократно описывался разработчиками как неудачный эксперимент.

Вместо этого давайте name тип «указатель на Type»; Я назову это Ptr_:

template< class Type >
using Ptr_ = Type*;

Теперь Ptr_<char> является указателем на char.

Ptr_<const char> - это указатель на const char.

И const Ptr_<const char> - это const указатель на const char.

Там.

 enter image description here

5
Cheers and hth. - Alf

Это просто, но сложно. Обратите внимание, что мы можем поменять квалификатор const на любой тип данных (int, char, float и т.д.).

Давайте посмотрим на приведенные ниже примеры.


const int *p ==> *p только для чтения [p - указатель на постоянное целое число]

int const *p ==> *p только для чтения [p - указатель на постоянное целое число]


int *p const ==> Неверно Заявление. Компилятор выдает синтаксическую ошибку.

int *const p ==> p только для чтения [p является постоянным указателем на целое число] . Поскольку указатель p здесь только для чтения, объявление и определение должны находиться в одном месте.


const int *p const ==> Неверно Заявление. Компилятор выдает синтаксическую ошибку.

const int const *p ==> *p только для чтения

const int *const p1 ==> *p и p доступны только для чтения [p - постоянный указатель на постоянное целое число]. Поскольку указатель p здесь только для чтения, объявление и определение должны находиться в одном месте.


int const *p const ==> Неверно Заявление. Компилятор выдает синтаксическую ошибку.

int const int *p ==> Неверно Заявление. Компилятор выдает синтаксическую ошибку.

int const const *p ==> *p доступен только для чтения и эквивалентен int const *p

int const *const p ==> *p и p доступны только для чтения [p - постоянный указатель на постоянное целое число]. Поскольку указатель p здесь только для чтения, объявление и определение должны находиться в одном месте.

5
Abhijit Sahu

Есть много других тонких моментов, касающихся правильности констант в C++. Я предполагаю, что вопрос здесь был просто о C, но я приведу несколько связанных примеров, так как тег C++:

  • Вы часто передаете большие аргументы, такие как строки, как TYPE const &, что предотвращает изменение или копирование объекта. Пример :

    TYPE& TYPE::operator=(const TYPE &rhs) { ... return *this; }

    Но TYPE & const не имеет смысла, потому что ссылки всегда постоянны.

  • Вы всегда должны помечать методы класса, которые не изменяют класс, как const, в противном случае вы не можете вызвать метод из ссылки TYPE const &. Пример :

    bool TYPE::operator==(const TYPE &rhs) const { ... }

  • Есть общие ситуации, когда и возвращаемое значение, и метод должны быть постоянными. Пример :

    const TYPE TYPE::operator+(const TYPE &rhs) const { ... }

    Фактически, методы const не должны возвращать данные внутреннего класса как ссылку на неконстантный тип.

  • В результате часто приходится создавать как константный, так и неконстантный метод с использованием константной перегрузки. Например, если вы определили T const& operator[] (unsigned i) const;, вы, вероятно, также захотите неконстантную версию, заданную как

    inline T& operator[] (unsigned i) { return const_cast<char&>( static_cast<const TYPE&>(*this)[](i) ); }

На самом деле, в C нет константных функций, функции, не являющиеся членами, сами по себе не могут быть константными в C++, методы const могут иметь побочные эффекты, а компилятор не может использовать функции const, чтобы избежать дублирования вызовов функций. Фактически, даже простая ссылка int const & может свидетельствовать об изменении значения, на которое она ссылается, в другом месте.

5
Jeff Burdges

Константа с int с обеих сторон сделает указатель на константу int .

const int *ptr=&i;

или же 

int const *ptr=&i;

const после '*' сделает константный указатель на int .

int *const ptr=&i;

В этом случае все они являются указателем на постоянное целое число , но ни один из них не является постоянным указателем. 

 const int *ptr1=&i, *ptr2=&j;

В этом случае все являются указатель на постоянное целое число и ptr2 является постоянный указатель на постоянное целое число . Но ptr1 не является постоянным указателем.

int const *ptr1=&i, *const ptr2=&j;
2
Hunter

В основном это касается второй строки: лучшие практики, назначения, параметры функций и т.д.

Общая практика. Попробуйте сделать все, что можете, const. Или, говоря иначе, сделайте все для начала const, а затем удалите ровно минимальный набор consts, необходимый для работы программы. Это будет большим подспорьем в достижении константности и поможет избежать появления незаметных ошибок, когда люди попытаются заняться вещами, которые они не должны изменять.

Избегайте const_cast <>, как чума. Есть один или два законных варианта использования для него, но их очень мало и далеко друг от друга. Если вы пытаетесь изменить объект const, вам будет гораздо лучше найти того, кто объявил его const в первом темпе, и обсудить с ними вопрос, чтобы достичь консенсуса относительно того, что должно произойти.

Что приводит очень аккуратно в назначениях. Вы можете назначить что-то, только если это неконстантно. Если вы хотите присвоить что-то, что является постоянным, см. Выше. Помните, что в объявлениях int const *foo; и int * const bar; разные вещи const - другие ответы здесь освещают эту проблему превосходно, поэтому я не буду вдаваться в подробности.

Параметры функции:

Передать по значению: например, void func(int param) вам не важно, так или иначе на вызывающем сайте. Можно привести аргумент, что существуют варианты использования для объявления функции как void func(int const param), но это не влияет на вызывающую функцию, а только на саму функцию, поскольку любое переданное значение не может быть изменено функцией во время вызова.

Передать по ссылке: например, void func(int &param) Теперь это имеет значение. Как только что было объявлено, func может изменить param, и любой вызывающий сайт должен быть готов справиться с последствиями. Изменение объявления на void func(int const &param) изменяет контракт и гарантирует, что func теперь не может изменить param, что означает, что то, что передано, и будет возвращено. Как уже отмечали другие, это очень полезно для дешевой передачи большого объекта, который вы не хотите менять. Передача ссылки намного дешевле, чем передача большого объекта по значению.

Передать по указателю: например, void func(int *param) и void func(int const *param) Эти два в значительной степени синонимичны с их эталонными ссылками, с оговоркой, что вызываемая функция должна теперь проверять наличие nullptr, если какая-либо другая договорная гарантия не гарантирует func, что она никогда не получит nullptr в param.

Часть мнения на эту тему. Доказать правильность в таком случае адски сложно, просто слишком легко ошибиться. Так что не рискуйте, и всегда проверяйте параметры указателя для nullptr. Вы избавите себя от боли и страданий, и вам будет трудно найти ошибки в долгосрочной перспективе. А что касается стоимости проверки, то она очень дешевая, и в случаях, когда статический анализ, встроенный в компилятор, может им управлять, оптимизатор все равно ее исключит. Включите генерацию временного кода канала для MSVC или WOPR (я думаю) для GCC, и вы получите всю программу, то есть даже в вызовах функций, которые пересекают границу модуля исходного кода.

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

1
dgnuff

Для меня положение const, то есть, отображается ли оно влево или вправо или влево и вправо относительно *, помогает мне понять фактическое значение.

  1. const в LEFT из * указывает, что объект, на который указывает указатель, является объектом const.

  2. const на ПРАВО * указывает, что указатель является указателем const.

Следующая таблица взята из Stanford CS106L Standard C++ Programming Laboratory Reader.

 enter image description here

1
srivatsahc

Просто ради полноты для C, следуя другим объяснениям, не уверен для C++.

  • pp - указатель на указатель
  • р - указатель
  • данные - вещь указанная, в примерах x
  • полужирный - переменная только для чтения

Указатель

  • p data - int *p;
  • p data - int const *p;
  • p data - int * const p;
  • p data - int const * const p;

Указатель на указатель

  1. pp p data - int **pp;
  2. pp p данные - int ** const pp;
  3. pp p data - int * const *pp;
  4. pp p data - int const **pp;
  5. pp p data - int * const * const pp;
  6. pp p data - int const ** const pp;
  7. pp p data - int const * const *pp;
  8. pp p данные - int const * const * const pp;
// Example 1
int x;
x = 10;
int *p = NULL;
p = &x;
int **pp = NULL;
pp = &p;
printf("%d\n", **pp);

// Example 2
int x;
x = 10;
int *p = NULL;
p = &x;
int ** const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);

// Example 3
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const *pp = NULL;
pp = &p;
printf("%d\n", **pp);

// Example 4
int const x = 10; // Definition must happen during declaration
int const * p = NULL;
p = &x;
int const **pp = NULL;
pp = &p;
printf("%d\n", **pp);

// Example 5
int x;
x = 10;
int * const p = &x; // Definition must happen during declaration
int * const * const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);

// Example 6
int const x = 10; // Definition must happen during declaration
int const *p = NULL;
p = &x;
int const ** const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);

// Example 7
int const x = 10; // Definition must happen during declaration
int const * const p = &x; // Definition must happen during declaration
int const * const *pp = NULL;
pp = &p;
printf("%d\n", **pp);

// Example 8
int const x = 10; // Definition must happen during declaration
int const * const p = &x; // Definition must happen during declaration
int const * const * const pp = &p; // Definition must happen during declaration
printf("%d\n", **pp);

N-уровни разыменования

Просто продолжай, но пусть человечество отлучит тебя от церкви.

 int x = 10;
 int *p = &x;
 int **pp = &p;
 int ***ppp = &pp;
 int ****pppp = &ppp;

 printf("%d \n", ****pppp);
0
Undefined Behavior