Ось детальне пояснення, яке, я сподіваюся, буде корисним. Почнемо з вашої програми, оскільки це найпростіше пояснити.
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
Перше твердження:
const char* p = "Hello";
оголошує pяк покажчик на char. Коли ми говоримо "вказівник на char", що це означає? Це означає, що значення p- це адреса а 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. Постфікс-експресія побічні ефекти . Постфікс ++має значення поточного операнда, але він має побічний ефект збільшення цього операнда. Так? Погляньте на цей 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++