Недавно я столкнулся с этой проблемой, которую сам не могу понять.
Что ДЕЙСТВИТЕЛЬНО означают эти три выражения ?
*ptr++
*++ptr
++*ptr
Я пробовал Ричи. Но, к сожалению, не смог уследить за тем, что он рассказал об этих трех операциях.
Я знаю, что все они выполняются для увеличения указателя / значения, на которое указывает. Я также могу предположить, что может быть много вещей о приоритете и порядке оценки. Как один сначала увеличивает указатель, а затем извлекает содержимое этого указателя, один просто извлекает контент, а затем увеличивает указатель и т.д. и т.д. Как вы можете видеть, у меня нет четкого понимания их фактических операций, которые я хотел бы очистить как можно скорее. Но я действительно теряюсь, когда получаю возможность применять их в программах. Например:
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
дает мне этот результат:
ello
Но я ожидал, что он напечатан Hello
. И последний запрос. Приведите мне примеры того, как каждое выражение работает в данном фрагменте кода. Поскольку в большинстве случаев у меня над головой пролетает лишь абзац теории.
(*ptr)++
(круглые скобки необходимы для устранения неоднозначности*ptr++
)char* p
, указывающий на действительную завершенную строку уникальных символов. Тогда есть функция ,fn(char ch)
которая печатает какch
параметр и текущий символ , на который указываетp
. Теперь вызовитеfn(*p++);
Q: Печатает ли один иfn
тот же символ дважды ? Вы будете удивлены, сколько профессоров неправильно задают этот вопрос.const char* p = "Hello";
Ответы:
Вот подробное объяснение, которое, я надеюсь, будет полезно. Начнем с вашей программы, так как ее проще всего объяснить.
Первое утверждение:
объявляется
p
как указатель наchar
. Когда мы говорим «указатель на объектchar
», что это означает? Это означает, что значениеp
является адресом achar
;p
сообщает нам, где в памяти есть место для храненияchar
.Оператор также инициализируется,
p
чтобы указать на первый символ в строковом литерале"Hello"
. Ради этого упражнения, важно понять ,p
как указывает не на всю строку, но только на первый символ,'H'
. В конце концов,p
это указатель на однуchar
, а не на всю строку. Значениеp
- это адрес'H'
в"Hello"
.Затем вы настраиваете цикл:
Что означает условие цикла
*p++
? Здесь работают три вещи, которые делают это озадачивающим (по крайней мере, до тех пор, пока не начнется знакомство):++
и косвенного обращения*
1. Приоритет . Быстрый взгляд на таблицу приоритетов операторов покажет вам, что приращение постфикса имеет более высокий приоритет (16), чем разыменование / косвенное обращение (15). Это означает , что комплексное выражение
*p++
собирается быть сгруппированы следующим образом:*(p++)
. Другими словами,*
часть будет применена к стоимостиp++
части. Итак, давайтеp++
сначала возьмем часть.2. Значение постфиксного выражения . Значение
p++
- это значениеp
до приращения . Если у вас есть:вывод будет:
потому что
i++
оцениваетсяi
до приращения. Аналогичноp++
будет оцениваться текущее значениеp
. Как известно, текущее значениеp
- это адрес'H'
.Итак, теперь
p++
часть*p++
была оценена; это текущее значениеp
. Затем*
происходит часть.*(current value of p)
означает: получить доступ к значению по адресуp
. Мы знаем, что значение по этому адресу равно'H'
. Таким образом, выражение*p++
оценивается как'H'
.Вы говорите, подождите минутку. Если
*p++
оценивается как'H'
, почему это не'H'
печатается в приведенном выше коде? Вот тут и проявляются побочные эффекты .3. Побочные эффекты выражения Postfix . Постфикс
++
имеет значение текущего операнда, но имеет побочный эффект увеличения этого операнда. А? Взгляните еще раз на этотint
код:Как отмечалось ранее, результат будет следующим:
Когда
i++
оценивается в первомprintf()
, он оценивает до 7. Но стандартные гарантии C , что в какой - то момент перед вторымprintf()
начинает выполнение команд, побочный эффект от++
оператора будет иметь место. То есть до того, какprintf()
произойдет второе ,i
будет увеличено в результате действия++
оператора в первомprintf()
. Это, кстати, одна из немногих гарантий, которые стандарт дает о времени появления побочных эффектов.Тогда в вашем коде, когда выражение
*p++
оценивается, оно оценивается как'H'
. Но к тому времени, когда вы дойдете до этого:этот неприятный побочный эффект произошел.
p
был увеличен. Вау! Он больше не указует на'H'
, но один символ прошлым'H'
: к'e'
, других слова. Это объясняет ваш нелепый результат:Отсюда хор полезных (и точных) предложений в других ответах: чтобы распечатать полученное произношение,
"Hello"
а не его копию кокни, вам нужно что-то вродеВот и все. А как насчет остальных? Вы спрашиваете о значении этих слов:
Мы только что говорили о первом, поэтому давайте посмотрим на вторую:
*++ptr
.В нашем предыдущем объяснении мы видели, что у постфиксного приращения
p++
есть определенный приоритет , значение и побочный эффект . Приращение префикса++p
имеет тот же побочный эффект, что и его постфиксный аналог: он увеличивает свой операнд на 1. Однако у него другой приоритет и другое значение .Приращение префикса имеет более низкий приоритет, чем постфикс; он имеет приоритет 15. Другими словами, он имеет тот же приоритет, что и оператор разыменования / косвенного обращения
*
. В таком выражении, какне имеет значения приоритет: два оператора идентичны по приоритету. Таким образом, вступает в силу ассоциативность . Приращение префикса и оператор косвенного обращения имеют право-левую ассоциативность. Из-за этой ассоциативности операнд
ptr
будет сгруппирован с самым правым оператором++
перед оператором, расположенным левее*
,. Другими словами, выражение будет сгруппировано*(++ptr)
. Итак, как и в случае,*ptr++
но по другой причине, здесь тоже*
часть будет применяться к значению++ptr
части.Так что это за ценность? Значение выражения приращения префикса - это значение операнда после приращения . Это сильно отличает его от постфиксного оператора приращения. Допустим, у вас есть:
Результатом будет:
... отличается от того, что мы видели с оператором postfix. Аналогично, если у вас есть:
вывод будет:
Вы понимаете почему?
Теперь мы переходим к третьему выражению вы спросили о,
++*ptr
. На самом деле это самая сложная из всех. Оба оператора имеют одинаковый приоритет и ассоциативность справа и слева. Это означает, что выражение будет сгруппировано++(*ptr)
.++
Часть будет применяться к значению*ptr
части.Итак, если у нас есть:
удивительно эгоистичный результат будет:
Какой?! Итак,
*p
часть будет оценивать'H'
. Затем++
вступает в игру, и в этот момент он будет применен к'H'
, а не к указателю! Что происходит, когда вы добавляете 1 к'H'
? Вы получите 1 плюс значение ASCII'H'
72; вы получаете 73. Представьте , что , какchar
, и вы получитеchar
со значением ASCII 73:'I'
.Это касается трех выражений, которые вы задали в своем вопросе. Вот еще один, упомянутый в первом комментарии к вашему вопросу:
Это тоже интересно. Если у вас есть:
он даст вам такой восторженный результат:
В чем дело? Опять же, это вопрос приоритета , значения выражения и побочных эффектов . Из-за скобок
*p
часть рассматривается как основное выражение. Первичные выражения важнее всего остального; они оцениваются первыми. И*p
, как известно, оценивает'H'
. Остальная часть выражения,++
часть, применяется к этому значению. Итак, в этом случае(*p)++
становится'H'++
.В чем ценность
'H'++
? Если вы сказали'I'
, вы забыли (уже!) Наше обсуждение значения и побочного эффекта с постфиксным приращением. Помните,'H'++
оценивает текущее значение'H'
. Итак, это сначалаprintf()
будет напечатано'H'
. Затем, как побочный эффект , это'H'
значение будет увеличено до'I'
. Второйprintf()
печатает это'I'
. И тебе радостное приветствие.Хорошо, но в последних двух случаях зачем мне
Почему я не могу просто что-то вроде
Потому
"Hello"
что это строковый литерал. Если вы попытаетесь++*p
, вы попытаетесь изменить'H'
в строке на'I'
, создав всю строку"Iello"
. В C строковые литералы доступны только для чтения; попытка изменить их вызывает неопределенное поведение."Iello"
также не определено в английском языке, но это просто совпадение.И наоборот, у вас не может быть
Почему нет? Потому что в данном случае
p
это массив. Массив не является изменяемым l-значением; вы не можете изменить положениеp
точек с помощью пре- или пост-инкремента или декремента, потому что имя массива работает так, как будто это постоянный указатель. (На самом деле это не так; это просто удобный способ взглянуть на это.)Подводя итог, вот три вещи, о которых вы спрашивали:
И вот четвертый, не менее интересный, чем остальные три:
Первый и второй вызовут сбой, если
ptr
на самом деле это идентификатор массива. Третий и четвертый будут аварийно завершены, если будут указыватьptr
на строковый литерал.Вот и все. Надеюсь, теперь все кристально. Вы были прекрасной публикой, и я буду здесь всю неделю.
источник
Допустим,
ptr
указывает на i-й элемент массиваarr
.*ptr++
вычисляетarr[i]
и устанавливает,ptr
чтобы указать на (i + 1) -й элементarr
. Это эквивалентно*(ptr++)
.*++ptr
устанавливает,ptr
чтобы указать на (i + 1) -й элементarr
и вычисляет значениеarr[i+1]
. Это эквивалентно*(++ptr)
.++*ptr
увеличиваетсяarr[i]
на единицу и оценивается в увеличенное значение; указательptr
остается нетронутым. Это эквивалентно++(*ptr)
.Есть еще один, но для его написания понадобятся круглые скобки:
(*ptr)++
увеличиваетсяarr[i]
на единицу и оценивается до своего значения перед увеличением; указательptr
снова остается нетронутым.В остальном вы сможете разобраться сами; на него также ответил @Jaguar.
источник
*ptr++ : post increment a pointer ptr
*++ptr : Pre Increment a pointer ptr
++*ptr : preincrement the value at ptr location
Прочтите здесь об операторах пре-инкремента и пост-инкремента.
Это даст
Hello
как результатисточник
Hello
Состояние вашего цикла плохое:
Такой же как
А это неправильно, это должно быть:
*ptr++
то же самое*(ptr++)
, что и:*++ptr
то же самое*(++ptr)
, что и:++*ptr
то же самое++(*ptr)
, что и:источник
Вы правы относительно приоритета, обратите внимание, что
*
имеет приоритет над приращением префикса, но не над приращением постфикса. Вот как эти поломки:*ptr++
- переход слева направо, разыменование указателя, а затем увеличение значения указателя (не то, на что он указывает, из-за приоритета постфикса над разыменованием)*++ptr
- увеличить указатель, а затем разыменовать его, это потому, что префикс и разыменование имеют одинаковый приоритет и поэтому они оцениваются в порядке справа налево++*ptr
- аналогично приведенному выше с точки зрения приоритета, снова идя справа налево, чтобы разыменовать указатель, а затем увеличить то, на что указывает указатель. Обратите внимание, что в вашем случае это приведет к неопределенному поведению, потому что вы пытаетесь изменить переменную только для чтения (char* p = "Hello";
).источник
Я собираюсь добавить свой вариант, потому что, хотя другие ответы верны, я думаю, что они чего-то упускают.
средства
В то время как
средства
Важно понимать, что пост-инкремент (и пост-декремент) означает
Почему это имеет значение? Что ж, в C это не так важно. В C ++ же
ptr
может быть сложный тип, такой как итератор. НапримерВ этом случае, поскольку
it
это сложный тип,it++
могут возникнуть побочные эффекты из-заtemp
создания. Конечно, если вам повезет, компилятор попытается выбросить ненужный код, но если конструктор или деструктор итератора что-то сделает, онit++
покажет эти эффекты при созданииtemp
.Я пытаюсь сказать вкратце: « Напиши, что имеешь в виду» . Если вы имеете в виду инкремент ptr, то
++ptr
не пишитеptr++
. Если вы имеете в видуtemp = ptr, ptr += 1, temp
то напишитеptr++
источник
Это то же самое, что:
Таким образом, значение объекта, на который указывает,
ptr
извлекается, а затемptr
увеличивается.Это то же самое, что:
Таким образом, указатель
ptr
увеличивается, затемptr
считывается объект, на который указывает .Это то же самое, что:
Таким образом, объект, на который указывает,
ptr
увеличивается;ptr
сам по себе неизменен.источник
постфикс и префикс имеют более высокий приоритет, чем разыменование, поэтому
* ptr ++ здесь post increment ptr, а затем указание на новое значение ptr
* ++ ptr здесь Pre Increment кулак, затем указывающий на новое значение ptr
++ * ptr здесь сначала получает значение ptr, указывающее на и увеличивающее это значение vlaue
источник
Выражения указателя: * ptr ++, * ++ ptr и ++ * ptr:
Примечание : указатели должны быть инициализированы и иметь действительный адрес. Потому что в ОЗУ, помимо нашей программы (a.out), одновременно работает намного больше программ, т.е. если вы попытаетесь получить доступ к некоторой памяти, которая не была зарезервирована для вас, ОС будет через ошибку сегментации.
Прежде чем объяснять это, давайте рассмотрим простой пример?
проанализируйте вывод вышеуказанного кода, я надеюсь, вы получили вывод вышеуказанного кода. Из приведенного выше кода ясно одно: имя указателя ( ptr ) означает, что мы говорим об адресе, а * ptr означает, что мы говорим о значении / данных.
СЛУЧАЙ 1 : * ptr ++, * ++ ptr, * (ptr ++) и * (++ ptr):
Вышеупомянутые все 4 синтаксиса во всем похожи,
address gets incremented
но то, как увеличивается адрес, отличается.Примечание : для решения любого выражения узнайте, сколько операторов в выражении, затем выясните приоритеты операторов. Если несколько операторов имеют одинаковый приоритет, затем проверяют порядок эволюции или ассоциативности, который может быть справа (R) налево (L) или слева направо.
* ptr ++ : здесь есть 2 оператора, а именно de-reference (*) и ++ (приращение). Оба имеют одинаковый приоритет, затем проверяют ассоциативность, которая является R к L. Итак, начинайте решение справа налево, какие бы операторы ни появлялись раньше.
* ptr ++ : первый ++ появился при решении от R до L, поэтому адрес увеличивается, но увеличивается его пост.
* ++ ptr : То же, что и первый, здесь также адрес увеличивается, но до его увеличения.
* (ptr ++) : Здесь есть 3 оператора, среди которых grouping () имеет наивысший приоритет. Итак, первый ptr ++ решен, т.е. адрес увеличивается, но публикуется.
* (++ ptr) : То же, что и в предыдущем случае, здесь также адрес увеличивается, но предварительно.
СЛУЧАЙ 2 : ++ * ptr, ++ (* ptr), (* ptr) ++:
Вышеупомянутые все 4 синтаксиса похожи, все значение / данные увеличиваются, но то, как значение изменяется, отличается.
++ * ptr : first * возник при решении от R до L, поэтому значение изменяется, но его предварительное приращение.
++ (* ptr) : То же, что и в предыдущем случае, значение изменяется.
(* ptr) ++ : Здесь есть 3 оператора, среди них grouping () с наивысшим приоритетом, есть Inside () * ptr, поэтому сначала решается * ptr, т.е. значение увеличивается, но post.
Примечание : ++ * ptr и * ptr = * ptr + 1 одинаковы, в обоих случаях значение изменяется. ++ * ptr: используется только 1 инструкция (INC), значение изменяется за один раз. * ptr = * ptr + 1: здесь первое значение увеличивается (INC), а затем присваивается (MOV).
Чтобы понять все вышеупомянутые разные синтаксисы приращения указателя, рассмотрим простой код:
В приведенном выше коде попробуйте комментировать / не комментировать комментарии и анализировать результаты.
Указатели как константы : не существует способов сделать указатели постоянными, некоторые из них я здесь упоминаю.
1) Const INT * р OR ИНТ сопз * р : Здесь
value
есть константа , адрес не постоянен т.е. где р указывает? Какой-то адрес? Какова стоимость по этому адресу? Какая-то ценность, верно? Это значение является постоянным, вы не можете изменить это значение, но куда указывает указатель? Какой-то адрес верно? Он также может указывать на другой адрес.Чтобы понять это, давайте рассмотрим код ниже:
Попробуйте проанализировать вывод вышеуказанного кода
2) int const * p : он называется '
**constant pointe**r
' ieaddress is constant but value is not constant
. Здесь вам не разрешено изменять адрес, но вы можете изменить значение.Примечание : постоянный указатель (вышеупомянутый случай) должен инициализироваться при объявлении самого себя.
Чтобы понять это, давайте проверим простой код.
В приведенном выше коде, если вы заметили, что нет ++ * p или * p ++. Итак, вы можете подумать, что это простой случай, потому что мы не меняем адрес или значение, но это приведет к ошибке. Зачем ? Причину я упоминаю в комментариях.
Так каково же решение этой проблемы?
Чтобы узнать больше об этом случае, рассмотрим пример ниже.
3) const int * const p : здесь и адрес, и значение постоянны .
Чтобы понять это, давайте проверим код ниже
источник
++*p
означает, что вы пытаетесь увеличить значение ASCII, для*p
котороговы не можете увеличивать значение, потому что это константа, поэтому вы получите ошибку
что касается вашего цикла while, цикл выполняется до тех пор, пока не
*p++
достигнет конца строки, где есть'\0'
символ (NULL).Теперь, поскольку
*p++
пропускается первый символ, вы получите результат, начиная со второго символа.Следующий код ничего не выведет, потому что цикл while имеет
'\0'
Следующий код даст вам тот же результат, что и следующий код, то есть ello.
...................................
источник