Разница между * ptr + = 1 и * ptr ++ в C

123

Я только начал изучать C, и когда делал один пример о передаче указателя на указатель в качестве параметра функции, я обнаружил проблему.

Это мой пример кода:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int* allocateIntArray(int* ptr, int size){
    if (ptr != NULL){
        for (int i = 0; i < size; i++){
            ptr[i] = i;
        }
    }
    return ptr;
}

void increasePointer(int** ptr){
    if (ptr != NULL){
        *ptr += 1; /* <----------------------------- This is line 16 */
    }
}

int main()
{
    int* p1 = (int*)malloc(sizeof(int)* 10);
    allocateIntArray(p1, 10);

    for (int i = 0; i < 10; i++){
        printf("%d\n", p1[i]);
    }

    increasePointer(&p1);
    printf("%d\n", *p1);
    p1--;
    free(p1);
    fgets(string, sizeof(string), stdin);
    return 0;
}

Проблема возникает в строке 16, когда я меняю *ptr+=1на *ptr++. Ожидаемым результатом должен быть весь массив и номер 1, но когда я использую *ptr++результат, будет 0.

Есть ли разница между +=1и ++? Я думал, что они оба одинаковые.

привет нгуен
источник
2
Обратите внимание, что данный код не будет компилироваться, как вы не заявили string.
Spikatrix
6
Другие примечания: 1) allocateIntArray- плохая репутация, поскольку вам кажется, что mallocэто массив из функции, но это не так. Я предлагаю fillIntArrayвместо этого. 2) Вы не используете возвращаемое значение allocateIntArray. Я предлагаю вам изменить тип возвращаемого значения на void. 3) Не следует ли if (ptr != NULL)в функции increasePointerбыть if (*ptr != NULL)? 4) Отливка mallocне требуется. См. Комментарий Сурава выше. 5) Это: for (int i = 0; i < 10; i++){ printf("%d\n", p1[i]); }и printf("%d\n", *p1); p1--;должно быть заключено в if(p1 != NULL). 6) string.hне используется.
Spikatrix
9
p+=1это нравится ++p, а не нравитсяp++
Kos
5
этот вопрос был задан 4 года назад: Is ++ то же самое, что и + = 1 для указателей
ren
3
@ren Почти, но не совсем. Связанный вопрос не связан с оператором разыменования, который является сутью проблемы OP здесь.
Jason C

Ответы:

290

Разница связана с приоритетом операторов.

Оператор постинкремента ++имеет более высокий приоритет, чем оператор разыменования *. Так *ptr++эквивалентно *(ptr++). Другими словами, приращение поста изменяет указатель, а не то, на что он указывает.

Оператор присваивания +=имеет более низкий приоритет, чем оператор разыменования *, поэтому *ptr+=1эквивалентен (*ptr)+=1. Другими словами, оператор присваивания изменяет значение, на которое указывает указатель, и не изменяет сам указатель.

user3386109
источник
3
Для новичков мнемоника - это сходство между *p++и *++p. Операторный приоритет последнего очевиден, далее следует первый.
Уолтер Тросс,
21

Порядок приоритета для 3 операторов, задействованных в вашем вопросе, следующий:

постинкремент ++> разыменование *> присвоение+=

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

При синтаксическом анализе выражения оператор, который указан в некоторой строке, будет более жестко привязан (как если бы в круглые скобки) к своим аргументам, чем любой оператор, указанный в строке ниже. Например, выражение *p++анализируется как *(p++), а не как (*p)++.

Короче говоря, для того, чтобы выразить это присвоение *ptr+=1с помощью оператора постинкремента, вам необходимо добавить скобки к оператору разыменования, чтобы дать этой операции приоритет перед, ++как в этом(*ptr)++

Юнес Регайег
источник
3
Интересно, что на данный момент это единственный ответ, который содержит решение ... (* ptr) ++
hyde
7

Давайте применим круглые скобки, чтобы показать порядок операций

a + b / c
a + (b/c)

Сделаем это снова с

*ptr   += 1
(*ptr) += 1

И снова с

*ptr++
*(ptr++)
  • В *ptr += 1, мы увеличиваем значение переменной нашего указателя указывает .
  • В *ptr++, мы увеличиваем указатель после выполнения всего нашего оператора (строки кода) и возвращаем ссылку на переменную, на которую указывает наш указатель. .

Последний позволяет делать такие вещи, как:

for(int i = 0; i < length; i++)
{
    // Copy value from *src and store it in *dest
    *dest++ = *src++;

    // Keep in mind that the above is equivalent to
    *(dest++) = *(src++);
}

Это распространенный метод, используемый для копирования srcмассива в другой destмассив.

Матин Улхак
источник
"и вернуть ссылку на переменную, на которую указывает наш указатель". У C нет ссылок.
Miles Rout
@MilesRout Может быть, точнее назвать это lvalue? Но я не уверен, как это выразить без добавления жаргона.
Матин Улхак,
3

Очень хороший вопрос.

В K&R "Язык программирования C" "5.1 Указатели и адреса" мы можем получить ответ на этот вопрос.

«Унарные операторы * и & связываются сильнее, чем арифметические операторы»

*ptr += 1      //Increment what ptr points to.

«Унарные операторы, такие как * и ++, связывают справа налево ».

*ptr++        //Increment prt instead of what ptr point to.

// Работает как * (ptr ++).

Правильный способ:

(*ptr)++      //This will work.
Nick.Sang
источник
Я впервые комментирую Stack Overflow. Я обновил формат кода. ^^ Спасибо за ваше предложение.
Nick.Sang
2

* ptr + = 1: увеличивать данные, на которые указывает ptr. * ptr ++: указатель увеличения, указывающий на следующую ячейку памяти вместо данных, на которые указывает указатель.

user5787482
источник