Вот подробное объяснение, которое, я надеюсь, будет полезно. Начнем с вашей программы, так как ее проще всего объяснить.
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
Первое утверждение:
const char* p = "Hello";
объявляется p
как указатель на char
. Когда мы говорим «указатель на объект char
», что это означает? Это означает, что значение p
является адресом a char
; p
сообщает нам, где в памяти есть место для храненияchar
.
Оператор также инициализируется, p
чтобы указать на первый символ в строковом литерале "Hello"
. Ради этого упражнения, важно понять , p
как указывает не на всю строку, но только на первый символ, 'H'
. В конце концов, p
это указатель на одну char
, а не на всю строку. Значение p
- это адрес 'H'
в"Hello"
.
Затем вы настраиваете цикл:
while (*p++)
Что означает условие цикла *p++
? Здесь работают три вещи, которые делают это озадачивающим (по крайней мере, до тех пор, пока не начнется знакомство):
- Приоритет двух операторов, постфикса
++
и косвенного обращения*
- Значение выражения приращения постфикса
- Побочный эффект выражения приращения постфикса
1. Приоритет . Быстрый взгляд на таблицу приоритетов операторов покажет вам, что приращение постфикса имеет более высокий приоритет (16), чем разыменование / косвенное обращение (15). Это означает , что комплексное выражение *p++
собирается быть сгруппированы следующим образом: *(p++)
. Другими словами, *
часть будет применена к стоимости p++
части. Итак, давайте p++
сначала возьмем часть.
2. Значение постфиксного выражения . Значение p++
- это значение p
до приращения . Если у вас есть:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
вывод будет:
7
8
потому что i++
оценивается i
до приращения. Аналогично p++
будет оцениваться текущее значение p
. Как известно, текущее значение p
- это адрес 'H'
.
Итак, теперь p++
часть *p++
была оценена; это текущее значение p
. Затем *
происходит часть. *(current value of p)
означает: получить доступ к значению по адресу p
. Мы знаем, что значение по этому адресу равно 'H'
. Таким образом, выражение *p++
оценивается как 'H'
.
Вы говорите, подождите минутку. Если *p++
оценивается как 'H'
, почему это не 'H'
печатается в приведенном выше коде? Вот тут и проявляются побочные эффекты .
3. Побочные эффекты выражения Postfix . Постфикс ++
имеет значение текущего операнда, но имеет побочный эффект увеличения этого операнда. А? Взгляните еще раз на этот int
код:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
Как отмечалось ранее, результат будет следующим:
7
8
Когда i++
оценивается в первом printf()
, он оценивает до 7. Но стандартные гарантии C , что в какой - то момент перед вторым printf()
начинает выполнение команд, побочный эффект от ++
оператора будет иметь место. То есть до того, как printf()
произойдет второе , i
будет увеличено в результате действия ++
оператора в первом printf()
. Это, кстати, одна из немногих гарантий, которые стандарт дает о времени появления побочных эффектов.
Тогда в вашем коде, когда выражение *p++
оценивается, оно оценивается как 'H'
. Но к тому времени, когда вы дойдете до этого:
printf ("%c", *p)
этот неприятный побочный эффект произошел. p
был увеличен. Вау! Он больше не указует на 'H'
, но один символ прошлым 'H'
: к 'e'
, других слова. Это объясняет ваш нелепый результат:
ello
Отсюда хор полезных (и точных) предложений в других ответах: чтобы распечатать полученное произношение, "Hello"
а не его копию кокни, вам нужно что-то вроде
while (*p)
printf ("%c", *p++);
Вот и все. А как насчет остальных? Вы спрашиваете о значении этих слов:
*ptr++
*++ptr
++*ptr
Мы только что говорили о первом, поэтому давайте посмотрим на вторую: *++ptr
.
В нашем предыдущем объяснении мы видели, что у постфиксного приращения p++
есть определенный приоритет , значение и побочный эффект . Приращение префикса ++p
имеет тот же побочный эффект, что и его постфиксный аналог: он увеличивает свой операнд на 1. Однако у него другой приоритет и другое значение .
Приращение префикса имеет более низкий приоритет, чем постфикс; он имеет приоритет 15. Другими словами, он имеет тот же приоритет, что и оператор разыменования / косвенного обращения *
. В таком выражении, как
*++ptr
не имеет значения приоритет: два оператора идентичны по приоритету. Таким образом, вступает в силу ассоциативность . Приращение префикса и оператор косвенного обращения имеют право-левую ассоциативность. Из-за этой ассоциативности операнд ptr
будет сгруппирован с самым правым оператором ++
перед оператором, расположенным левее *
,. Другими словами, выражение будет сгруппировано *(++ptr)
. Итак, как и в случае, *ptr++
но по другой причине, здесь тоже *
часть будет применяться к значению++ptr
части.
Так что это за ценность? Значение выражения приращения префикса - это значение операнда после приращения . Это сильно отличает его от постфиксного оператора приращения. Допустим, у вас есть:
int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);
Результатом будет:
8
8
... отличается от того, что мы видели с оператором postfix. Аналогично, если у вас есть:
const char* p = "Hello";
printf ("%c ", *p); // note space in format string
printf ("%c ", *++p); // value of ++p is p after the increment
printf ("%c ", *p++); // value of p++ is p before the increment
printf ("%c ", *p); // value of p has been incremented as a side effect of p++
вывод будет:
H e e l // good dog
Вы понимаете почему?
Теперь мы переходим к третьему выражению вы спросили о, ++*ptr
. На самом деле это самая сложная из всех. Оба оператора имеют одинаковый приоритет и ассоциативность справа и слева. Это означает, что выражение будет сгруппировано ++(*ptr)
. ++
Часть будет применяться к значению *ptr
части.
Итак, если у нас есть:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
удивительно эгоистичный результат будет:
I
Какой?! Итак, *p
часть будет оценивать 'H'
. Затем ++
вступает в игру, и в этот момент он будет применен к 'H'
, а не к указателю! Что происходит, когда вы добавляете 1 к 'H'
? Вы получите 1 плюс значение ASCII 'H'
72; вы получаете 73. Представьте , что , как char
, и вы получите char
со значением ASCII 73: 'I'
.
Это касается трех выражений, которые вы задали в своем вопросе. Вот еще один, упомянутый в первом комментарии к вашему вопросу:
(*ptr)++
Это тоже интересно. Если у вас есть:
char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);
он даст вам такой восторженный результат:
HI
В чем дело? Опять же, это вопрос приоритета , значения выражения и побочных эффектов . Из-за скобок *p
часть рассматривается как основное выражение. Первичные выражения важнее всего остального; они оцениваются первыми. И *p
, как известно, оценивает 'H'
. Остальная часть выражения, ++
часть, применяется к этому значению. Итак, в этом случае (*p)++
становится 'H'++
.
В чем ценность 'H'++
? Если вы сказали 'I'
, вы забыли (уже!) Наше обсуждение значения и побочного эффекта с постфиксным приращением. Помните, 'H'++
оценивает текущее значение 'H'
. Итак, это сначала printf()
будет напечатано 'H'
. Затем, как побочный эффект , это 'H'
значение будет увеличено до 'I'
. Второй printf()
печатает это 'I'
. И тебе радостное приветствие.
Хорошо, но в последних двух случаях зачем мне
char q[] = "Hello";
char* p = q;
Почему я не могу просто что-то вроде
/*const*/ char* p = "Hello";
printf ("%c", ++*p); // attempting to change string literal!
Потому "Hello"
что это строковый литерал. Если вы попытаетесь ++*p
, вы попытаетесь изменить 'H'
в строке на 'I'
, создав всю строку "Iello"
. В C строковые литералы доступны только для чтения; попытка изменить их вызывает неопределенное поведение. "Iello"
также не определено в английском языке, но это просто совпадение.
И наоборот, у вас не может быть
char p[] = "Hello";
printf ("%c", *++p); // attempting to modify value of array identifier!
Почему нет? Потому что в данном случае p
это массив. Массив не является изменяемым l-значением; вы не можете изменить положение p
точек с помощью пре- или пост-инкремента или декремента, потому что имя массива работает так, как будто это постоянный указатель. (На самом деле это не так; это просто удобный способ взглянуть на это.)
Подводя итог, вот три вещи, о которых вы спрашивали:
*ptr++ // effectively dereferences the pointer, then increments the pointer
*++ptr // effectively increments the pointer, then dereferences the pointer
++*ptr // effectively dereferences the pointer, then increments dereferenced value
И вот четвертый, не менее интересный, чем остальные три:
(*ptr)++ // effectively forces a dereference, then increments dereferenced value
Первый и второй вызовут сбой, если ptr
на самом деле это идентификатор массива. Третий и четвертый будут аварийно завершены, если будут указывать ptr
на строковый литерал.
Вот и все. Надеюсь, теперь все кристально. Вы были прекрасной публикой, и я буду здесь всю неделю.
(*ptr)++
(круглые скобки необходимы для устранения неоднозначности*ptr++
)