it-swarm.com.ru

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

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

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

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

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

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

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

дает мне этот вывод:

Ello

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

113
allocated

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

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. Приоритет двух операторов: постфикс ++ и косвенный *
  2. Значение выражения приращения постфикса
  3. Побочный эффект выражения приращения постфикса

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' не печатается в приведенном выше коде? Вот где побочные эффекты входят.

. Побочные эффекты выражения 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 указывает на строковый литерал.

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

242
verbose

Предположим, что ptr указывает на i-й элемент массива arr.

  1. *ptr++ оценивается как arr[i] и устанавливает ptr для указания на (i + 1) -й элемент arr. Это эквивалентно *(ptr++).

  2. *++ptr устанавливает ptr для указания на (i + 1) -й элемент arr и возвращает arr[i+1]. Это эквивалентно *(++ptr).

  3. ++*ptr увеличивает arr[i] на единицу и оценивает его увеличенное значение; указатель ptr остается нетронутым. Это эквивалентно ++(*ptr).

Также есть еще один, но вам понадобятся скобки, чтобы написать это:

  1. (*ptr)++ увеличивает arr[i] на единицу и оценивает его значение перед увеличением; указатель ptr снова остается нетронутым.

Остальное вы можете выяснить сами; на него также ответил @Jaguar.

43
nickie

*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;
}
13
Jainendra

Состояние в вашем цикле плохое:

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)
7
nouney

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

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

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

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

4
Nobilis

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

 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.

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

3
gman
*ptr++    // 1

Это так же, как:

    tmp = *ptr;
    ptr++;

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

*++ptr    // 2

Это так же, как:

    ++ptr;
    tmp = *ptr;

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

++*ptr    // 3

Это так же, как:

    ++(*ptr);

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

0
David R Tribble

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

* ptr ++ здесь публикует ptr, а затем указывает на новое значение ptr.

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

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

0
Kiran Padwal

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

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

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

#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);
}

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

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

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

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

* ptr ++: Здесь есть 2 оператора, а именно разыменование (*) и ++ (приращение). Оба имеют одинаковый приоритет, затем проверяют ассоциативность, которая является R к L. Таким образом, начинается решение справа налево, независимо от того, какие операторы идут первыми.

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

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

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

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

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

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

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

++ (* ptr): То же, что и в предыдущем случае, значение изменяется.

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

Примечание: ++ * 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 равно константа, адрес не константа т.е. где р указывает? Какой-то адрес? По этому адресу какая стоимость? Какое-то значение верно? Это значение является постоянным, вы не можете изменить это значение, но куда указывает указатель? Какой-то адрес, верно? Это может также указывать на другой адрес.

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

#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. Здесь вы не можете изменить адрес, но вы можете изменить значение.

Примечание: указатель константы (в верхнем регистре) должен инициализироваться во время самого объявления.

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

#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);
}
0
Achal