Ось детальне пояснення, яке, я сподіваюся, буде корисним. Почнемо з вашої програми, оскільки це найпростіше пояснити.
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++