Почему я не могу сделать ++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
Я считаю, что оператору приращения (или уменьшения) нужно присвоить 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 для аналогичной концепции:
Ссылка_переменной — это выражение, которое классифицируется как переменная. Переменная_ссылка обозначает место хранения, к которому можно получить доступ как для извлечения текущего значения, так и для сохранения нового значения.