Вот подробное объяснение, которое, я надеюсь, будет полезно. Начнем с вашей программы, так как ее проще всего объяснить.
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++)