Выражения указателя: * ptr ++, * ++ ptr и ++ * ptr

Недавно я столкнулся с этой проблемой, которую я не могу понять сам.

Что означают эти три выражения ДЕЙСТВИТЕЛЬНО?

*ptr++
*++ptr
++*ptr

Я попробовал Ритчи. Но, к сожалению, он не смог следить за тем, что он рассказал об этих трех операциях.

Я знаю, что все они выполняются для увеличения указателя/указанного значения. Я также могу догадаться, что может быть много вещей о приоритетности и порядке оценки. Подобно тому, как сначала инкремент указателя, затем извлекает содержимое этого указателя, он просто извлекает содержимое, а затем увеличивает указатель и т.д. Как вы можете видеть, я не имею четкого понимания их фактического которые я хотел бы как можно скорее очистить. Но я действительно потерян, когда получаю возможность применить их в программах. Например:

int main()
{
    const char *p = "Hello";
    while(*p++)
         printf("%c",*p);
    return 0;
}

дает мне этот результат:

ello

Но я ожидал, что он напечатает Hello. Один окончательный запрос. Пожалуйста, дайте мне примеры того, как каждое выражение работает в данном фрагменте кода. Поскольку большую часть времени только один абзац теории пролетает над моей головой.

Ответ 1

Вот подробное объяснение, которое, я надеюсь, будет полезно. Пусть начнется с вашей программы, как это проще объяснить.

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++? Здесь есть три вещи, которые делают это озадачивающим (по крайней мере, до тех пор, пока не наступит знакомство):

  • Приоритет двух операторов, постфикс ++ и косвенный *
  • Значение выражения postfix increment
  • Побочный эффект выражения приращения postfix

1. Внеочередные. Быстрый взгляд на таблицу приоритетов для операторов скажет вам, что приращение постфикса имеет более высокий приоритет (16), чем разыменование/косвенность (15). Это означает, что сложное выражение *p++ будет сгруппировано как: *(p++). То есть часть * будет применена к значению части p++. Итак, сначала возьмите часть p++.

2. Значение выражения Postfix. Значение 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.

Итак, что это за значение? Значение выражения приращения префикса - это значение операнда после приращения. Это делает его совсем другим зверем от оператора приращения postfix. Скажем, у вас есть:

int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);

Выход будет:

8
8

... отличается от того, что мы видели с помощью постфиксного оператора. Аналогично, если у вас есть:

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 строковые литералы доступны только для чтения; попытка их изменения вызывает поведение undefined. "Iello" является undefined на английском языке, но это просто совпадение.

И наоборот, вы не можете

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 указывает на строковый литерал.

Там у вас есть. Надеюсь, теперь все хрустальное. Вы были отличной аудиторией, и я буду здесь всю неделю.

Ответ 2

Предположим, что 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.

Ответ 3

*ptr++ : post increment a pointer ptr

*++ptr : Pre Increment a pointer ptr

++*ptr : preincrement the value at ptr location

Прочитайте здесь об операторах предварительного увеличения и последующего увеличения


Это даст Hello в качестве вывода

int main()
{
    const char *p = "Hello";
    while(*p)
         printf("%c",*p++);//Increment the pointer here 
    return 0;
}

Ответ 4

Условие в вашем цикле плохое:

while(*p++)
    printf("%c",*p);

То же, что и

while(*p)
{
    p++;
    printf("%c",*p);
}

И это неправильно, это должно быть:

while(*p)
{
    printf("%c",*p);
    p++;
} 

*ptr++ совпадает с *(ptr++), который:

const char  *ptr = "example";
char  value;

value = *ptr;
++ptr;
printf("%c", value); // will print 'e'

*++ptr совпадает с *(++ptr), что:

const char  *ptr = "example";
char  value;

++ptr;
value = *ptr;
printf("%c", value); // will print 'x'

++*ptr совпадает с ++(*ptr), который:

const char  *ptr = "example";
char  value;

value = *ptr;
++value;
printf("%c", value); // will print 'f' ('e' + 1)

Ответ 5

Вы имеете право на приоритет, обратите внимание на то, что * имеет приоритет над приращением префикса, но не над приращением постфикса. Вот как это разбиение:

*ptr++ - перемещение слева направо, разыменование указателя, а затем увеличение значения указателя (а не того, на что указывает он, из-за приоритета постфикса по разыменованию)

*++ptr - увеличивать указатель, а затем разыгрывать его, это связано с тем, что префикс и разыменование имеют одинаковый приоритет, и поэтому они оцениваются по порядку справа налево

++*ptr - аналогично предыдущему в терминах приоритета, снова переходя от права налево, чтобы разыменовать указатель, а затем увеличивать то, на что указывает указатель. Обратите внимание, что в вашем случае это приведет к поведению undefined, потому что вы пытаетесь изменить переменную только для чтения (char* p = "Hello";).

Ответ 6

Я собираюсь добавить свой дубль, потому что, хотя остальные ответы верны, я думаю, что они что-то упустили.

 v = *ptr++

означает

 temp = ptr;
 ptr  = ptr + 1
 v    = *temp;

Где как

 v = *++ptr

означает

 ptr = ptr + 1
 v   = *ptr

Важно понимать, что пост-увеличение (и уменьшение) означает

 temp = ptr       // Temp created here!!!
 ptr  = ptr + 1   // or - 1 if decrement)
 v    = *temp     // Temp destroyed here!!!

Почему это важно? Ну в С это не так важно. В C++, хотя ptr может быть сложным типом, подобным итератору. Например,

 for (std::set<int>::iterator it = someSet.begin(); it != someSet.end(); it++)

В этом случае, поскольку it является сложным типом, it++ может иметь побочные эффекты из-за создания temp. Конечно, если вам повезет, компилятор попытается выбросить ненужный код, но если итератор-конструктор или деструктор что-то сделают, тогда it++ покажет эти эффекты при создании temp.

Суть в том, что я пытаюсь сказать, это напишите, что вы имеете в виду. Если вы имеете в виду приращение ptr, напишите ++ptr, а не ptr++. Если вы имеете в виду temp = ptr, ptr += 1, temp, напишите ptr++

Ответ 7

*ptr++    // 1

Это то же самое, что:

    tmp = *ptr;
    ptr++;

Итак, значение объекта, на которое указывает ptr, извлекается, то ptr увеличивается.

*++ptr    // 2

Это то же самое, что:

    ++ptr;
    tmp = *ptr;

Таким образом, указатель ptr увеличивается, тогда объект, на который указывает ptr, считывается.

++*ptr    // 3

Это то же самое, что:

    ++(*ptr);

Таким образом, объект, на который указывает ptr, увеличивается; ptr сам не изменяется.

Ответ 8

постфикс и префикс имеют более высокий приоритет, чем разыменование, поэтому

* ptr ++ здесь добавьте шаг ptr, а затем указывая на новое значение ptr

* ++ ptr здесь Предварительно увеличивайте кулак, указывая на новое значение ptr

++ * ptr здесь сначала получите значение ptr, указывающее и увеличивающее значение vlaue

Ответ 9

Выражения указателя: * ptr++, * ++ptr и ++ * ptr:

Примечание: указатели должны быть инициализированы и должны иметь действительный адрес. Потому что в ОЗУ, помимо нашей программы (a.out), выполняется еще много программы, т.е. если вы пытаетесь получить доступ к некоторой памяти, которая не была зарезервирована для вас, OS будет через ошибку сегментации.

Прежде чем объяснять это, рассмотрим простой пример?

#include<stdio.h>
int main()
{
        int num = 300;
        int *ptr;//uninitialized pointer.. must be initialized
        ptr = &num;
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        *ptr = *ptr + 1;//*ptr means value/data on the address.. so here value gets incremented
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        /** observe here that "num" got changed but manually we didn't change, it got modified by pointer **/
        ptr = ptr + 1;//ptr means address.. so here address got incremented
        /**     char pointer gets incremented by 1 bytes
          Integer pointer gets incremented by 4 bytes
         **/
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
}

проанализируйте вывод вышеуказанного кода, я надеюсь, что вы получили вывод выше кода. Из вышеизложенного ясно, что имя указателя (ptr) означает, что мы говорим об адресе, а * ptr означает, что мы говорим об абсолютном значении/данных.

CASE 1: * ptr++, * ++ptr, * (ptr++) и * (++ptr):

вышеупомянутый синтаксис всех 4 аналогичен, во всех address gets incremented но как увеличивается адрес, который отличается от другого.

Примечание: для решения любого выражения выясните, сколько операторов существует в выражении, а затем выясните приоритеты оператора. я несколько операторов с таким же приоритетом, а затем проверяют порядок эволюции или ассоциативность, которые могут быть правыми (R) влево (L) слева направо.

* ptr++: Здесь есть два оператора: de-reference (*) и ++ (increment). Оба имеют одинаковый приоритет, а затем проверяют ассоциативность, которая равна от R до L. Поэтому начинается решение с Right налево, независимо от того, какие операторы идут первым.

* ptr++: first ++ приходил при решении из R в L, поэтому адрес увеличивался, но увеличивался по порядку.

* ++ptr: То же, что и первый здесь, адрес также увеличивается, но его предварительный прирост.

* (ptr++): Здесь есть 3 оператора, среди которых группировка() имеет наивысший приоритет. Итак, сначала ptr++ решена, то есть адрес увеличивается, но публикуется.

* (++ptr): То же, что и выше, здесь также адрес увеличивается, но pre increment.

CASE 2: ++ * ptr, ++ (* ptr), (* ptr) ++:

выше, все синтаксис 4 аналогичны, во всех значениях/данных увеличивается, но как изменяется значение, которое отличается.

++ * ptr: first * пришел при решении из R в L, поэтому значение изменяется, но его pre increment.

++ (* ptr): То же, что и выше, значение изменяется.

(* ptr) ++: Здесь есть 3 оператора, среди которых есть grouping() с наивысшим приоритетом, Inside() * ptr есть, So first * ptr разрешен, т.е. значение увеличивается, но post.

Примечание: ++ * ptr и * ptr = * ptr + 1 оба одинаковы, в обоих случаях значение изменяется. ++ * ptr: используется только 1 инструкция (INC), прямое значение изменяется в одиночном кадре. * ptr = * ptr + 1: здесь первое значение получает инкремент (INC), а затем назначается (MOV).

Чтобы понять все вышеперечисленное синтаксис приращения на указателе, рассмотрим простой код:

#include<stdio.h>
int main()
{
        int num = 300;
        int *ptr;
        ptr = &num;
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        *ptr++;//address changed(post increment), value remains un-changed
//      *++ptr;//address changed(post increment), value remains un-changed
//      *(ptr)++;//address changed(post increment), value remains un-changed
//      *(++ptr);//address changed(post increment), value remains un-changed

//      ++*ptr;//value changed(pre increment), address remains un-changed
//      (*ptr)++;//value changed(pre increment), address remains un-changed
//      ++(*ptr);//value changed(post increment), address remains un-changed

        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
}

В приведенном выше коде попробуйте комментировать/не комментировать комментарии и анализировать результаты.

Указатели как константы: нет способов, по которым вы можете сделать указатели постоянными, немногие, о которых я упоминаю здесь.

1) const int * p OR int const * p: Здесь value постоянное, адрес не является постоянным, т.е. где p указывает? Какой-то адрес? На этом адресе какая ценность? Некоторая ценность правильная? Это значение является постоянным, вы не можете изменить это значение, но где указатель указывает? Какой-то адрес? Он также может указывать на другой адрес.

Чтобы понять это, рассмотрим ниже код:

#include<stdio.h>
int main()
{
        int num = 300;
        const int *ptr;//constant value, address is modifible
        ptr = &num;
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        *ptr++;//
//      *++ptr;//possible bcz you are trying to change address which is possible
//      *(ptr)++;//possible
//      *(++ptr);//possible

//      ++*ptr;//not possible bcz you trying to change value which is not allowed
//      (*ptr)++;//not possible
//      ++(*ptr);//not possible

        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
}

Попробуйте проанализировать вывод выше кода

2) int const * p: он называется " **constant pointe**r ", т.е. address is constant but value is not constant. Здесь вам не разрешается изменять адрес, но вы можете изменить значение.

Примечание: константный указатель (выше) должен инициализироваться, а сам declariung.

Чтобы понять это, можно проверить простой код.

#include<stdio.h>
int main()
{
        int x = 300;
        int* const p;
        p = &x;
        printf("x = %d p =%p and *p = %d\n",num,p,*p);
}

В приведенном выше коде, если вы заметили, что нет ++ * p или * p++, вы можете подумать, что это простой случай, потому что мы не меняем адрес или значение, но это приведет к ошибке. Зачем? Причина, которую я упоминаю в комментариях.

#include<stdio.h>
int main()
{
        int x = 300;
        /** constant pointer must initialize while decaring itself **/
        int* const p;//constant pointer i.e its pointing to some address(here its pointing to garbage), it should point to same address(i.e garbage ad
dress only 
        p = &x;// but here what we are doing ? we are changing address. we are making p to point to address of x instead of garbage address.
        printf("x = %d p =%p and *p = %d\n",num,p,*p);
}

Итак, каково решение этой проблемы?

     int* const p = &x;

для получения дополнительной информации об этом случае рассмотрим ниже пример.

#include<stdio.h>
int main()
{
        int num = 300;
        int *const ptr = &num;//constant value, address is modifible
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        *ptr++;//not possible
//      *++ptr;//not possible bcz you are trying to change address which is not possible
//      *(ptr)++;//not possible
//      *(++ptr);//not possible

//      ++*ptr;// possible bcz you trying to change value which is allowed
//      (*ptr)++;// possible
//      ++(*ptr);// possible
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
}

3) const int * const p: Здесь оба адреса и значения являются постоянными.

Чтобы это понять, давайте проверим ниже код

#include<stdio.h>
int main()
{
        int num = 300;
        const int* const ptr = &num;//constant value,constant address 
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
        *ptr++;//not possible
        ++*ptr;//not possible
        printf(" num = %d ptr = %p and data on ptr : %d \n",num,ptr,*ptr);
}

Ответ 10

const char *p = "Hello";   

*p means "Hello"
          ^
          | 
          p

*p++ means "Hello"
             ^
             | 
             p

*++p means "Hello"
            ^
            |     (WHILE THE STATEMENT IS EXECUTED)
            p

*++p means "Hello"
             ^
             |     (AFTER THE STATEMENT IS EXECUTED)
             p

++*p означает, что вы пытаетесь увеличить значение ASCII *p, которое

   is "Hello"
       ^
       | 
       p

вы не можете увеличить значение, потому что это константа, поэтому вы получите ошибку

так как для цикла while цикл работает до тех пор, пока *p++ не достигнет конца строки, где есть символ '\0' (NULL).

Теперь, поскольку *p++ пропускает первый символ, вы получите только ваш результат, начиная со второго символа.

Следующий код ничего не выводит, поскольку в цикле '\0'

const char *p = "Hello";
    while('\0') 
         printf("%c",*p);

Следующий код даст вам тот же результат, что и следующий код i.e ello.

const char *p = "Hello";
    while(*++p)
         printf("%c",*p);

...................................

const char *p = "Hello";
    while(*p++)
         printf("%c",*p);