Почему я не могу сделать ++i++ на C-подобных языках?

Наполовину в шутку наполовину серьезно : почему я не могу сделать ++i++ в C-подобных языках, особенно в C#?

Я ожидаю, что оно будет увеличивать значение, использовать его в моем выражении, а затем снова увеличивать.

8 ответов

Решение

Короткий ответ: i++ не является "lvalue", поэтому не может быть предметом назначения.

Хотя короткий ответ "это не lvalue" является правильным, это, возможно, просто напрашивается вопрос. Почему это не lvalue? Или, как мы говорим в C#, переменная.

Причина в том, что вы не можете получить свой торт и съесть его тоже. Работайте логически:

Прежде всего, значение оператора ++ в C#, будь то постфикс или префикс, заключается в том, чтобы "взять значение этой переменной, увеличить значение, присвоить новое значение переменной и получить значение в результате". Значение, полученное в результате, является либо исходным значением, либо увеличенным значением, в зависимости от того, был ли это постфикс или префикс. Но так или иначе, вы производите ценность.

Во-вторых, значение переменной всегда является текущим содержимым этой переменной. (По модулю определенных причудливых сценариев с многопоточностью, которые уведут нас далеко.)

Я надеюсь, что вы согласны, что это совершенно разумные правила.

Теперь должно быть понятно, почему результат i++ не может быть переменной, но если это не так, позвольте мне прояснить:

Предположим, что i равен 10. Значение i++ должно быть следующим: "получить значение i - 10 - увеличить его - 11 - сохранить - i теперь 11 - и дать исходное значение в качестве результата - 10". Таким образом, когда вы говорите print(i++), он должен вывести 10, а 11 должен быть сохранен в i.

Теперь предположим, что смысл i++ - возвращать переменную, а не значение. Вы говорите print(i++) и что происходит? Вы получаете значение i - 10 - увеличиваете его - 11 - сохраняете - i теперь 11 - и возвращаете переменную в результате. Каково текущее значение переменной? 11! Что именно то, что вы не хотите печатать.

Короче говоря, если бы i++ вернул переменную, то это было бы в точности противоположно предполагаемому значению оператора! Ваше предложение логически противоречиво, поэтому ни один язык так не поступает.

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

Я протестировал (++i,i++) в качестве обходного пути:

#include <stdio.h> 

int main(){
  int i=0;

  printf(" i:         %i\n", i         );
  printf(" (++i,i++): %i\n", (++i,i++) );
  printf(" i:         %i\n", i         );
}

Результат:


i:         0
(++i,i++): 1
i:         2

Потому что результат i++ не lvalue

Я считаю, что оператору приращения (или уменьшения) нужно присвоить lvalue. Однако ++ я не lvalue, это выражение. Кто-то, кто лучше разбирается в компиляторах, сможет уточнить, есть ли какие-либо технические причины для такого ограничения.

Из раздела 7.5.9 спецификации C# 3.0:

Операндом операции увеличения или уменьшения постфикса должно быть выражение, классифицированное как переменная, доступ к свойству или доступ индексатора. Результатом операции является значение того же типа, что и операнд. Если операндом операции увеличения или уменьшения постфикса является доступ к свойству или индексатору, свойство или индексатор должны иметь методы доступа get и set. Если это не так, возникает ошибка времени компиляции.

Кроме того, выражение после увеличения (i++) будет оцениваться первым, потому что он имеет более высокий приоритет, чем предварительное увеличение (++i) оператор.

Из спецификации С# :

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

Оператор приращения можно применять только к переменной (и ㏇), и он возвращает значение (не переменную). Вы не можете применить приращение к значению (просто потому, что нет переменной для присвоения результата), поэтому в C# вы можете увеличивать переменную только один раз.

Ситуация на самом деле отличается для C++. Согласно спецификации С++ :

префикс инкремента или декремента является выражением lvalue

это означает, что в C++ вы можете вызывать инкремент в результате инкремента или декремента префикса. Ig следующий код C++ действительно действителен:

      #include <iostream>

using namespace std;

int main()
{
    int i = 13;

    (--i)++;
    cout<<i<<endl;

    (++i)--;
    cout<<i<<endl;

    return 0;
}

NB: термин lvalue используется только в C и C++. И ради разнообразия в C результатом приращения префикса на самом деле является rvalue , поэтому вы не можете увеличивать приращение в C.
Язык C# использует термин variable_reference для аналогичной концепции:

Ссылка_переменной — это выражение, которое классифицируется как переменная. Переменная_ссылка обозначает место хранения, к которому можно получить доступ как для извлечения текущего значения, так и для сохранения нового значения.

Другие вопросы по тегам