2008-09-22 5 views
27

Следующие два фрагмента кода C# производят разные результаты (при условии, что уровень переменной используется как до, так и после рекурсивного вызова). Зачем?Когда ++ не дает те же результаты, что и +1?

public DoStuff(int level) 
{ 
    // ... 
    DoStuff(level++); 
    // ... 
} 

,

public DoStuff(int level) 
{ 
    // ... 
    DoStuff(level+1); 
    // ... 
} 

После прочтения некоторых ответов ниже, я думал, что было бы целесообразно разместить трассировки стека на уровне ++, ++ уровень и уровень + 1, чтобы показать, как обманывают эту проблему.

Я упростил их для этой публикации. Рекурсивная последовательность вызовов начинается с DoStuff (1).

// уровень ++

DoStuff(int level = 1) 
DoStuff(int level = 2) 
DoStuff(int level = 2) 
DoStuff(int level = 2) 

// ++ уровень

DoStuff(int level = 4) 
DoStuff(int level = 4) 
DoStuff(int level = 3) 
DoStuff(int level = 2) 

// уровень + 1

DoStuff(int level = 4) 
DoStuff(int level = 3) 
DoStuff(int level = 2) 
DoStuff(int level = 1) 
+0

Отличный вопрос и отличный ответ! Я использую C++ в течение многих лет и C# совсем недавно, и я понятия не имел! – 2008-09-22 14:38:52

+0

Ваши следы стека ошибочны. уровень ++ должен быть 1, 1, 1, 1; ++ уровень должен быть 1, 2, 3, 4; и уровень + 1 должен быть 1, 2, 3, 4 – 2008-09-23 12:16:22

+0

Орион - трассировка стека была взята непосредственно из VS2008. Я проследил функции вызова до четырех уровней рекурсии и сделал вырез и вставку. – 2008-09-23 20:37:31

ответ

27

уровень ++ будет проходить уровень в DoStuff, а затем приращение уровня для использования в остальной части функции. Это может быть довольно неприятная ошибка, так как рекурсия никогда не закончится (из того, что показано DoStuff всегда передается с тем же значением). Возможно, ++ уровень означает, что это противоположность level ++ (увеличивает уровень и передает добавочное значение в DoStuff)?

уровень + 1 будет проходить уровень + 1 в DoStuff и оставить уровень неизменным для остальной части функции.

+0

++ также создает другое поведение. См. Изменения в моем вопросе. – 2008-09-23 08:22:40

2

Первый использует значение в уровне и THEN incrmenting.

Последний использует уровень + 1 в качестве переданной переменной.

0

Первый фрагмент кода использует оператор приращения после операции, поэтому вызов выполняется как DoStuff (уровень) ;. Если вы хотите использовать оператор инкремента здесь, используйте DoStuff (++ level) ;.

1

level++ возвращает текущее значение level, а затем увеличивает level. level+1 не изменяется level вообще, но DoStuff вызывается со значением (level + 1).

29

Поскольку первый пример действительно эквивалентно:

public DoStuff(int level) 
{ 
    // ... 
    int temp = level; 
    level = level + 1; 
    DoStuff(temp); 
    // ... 
} 

Обратите внимание, что вы можете также писать ++ уровень; что будет эквивалентно:

public DoStuff(int level) 
{ 
    // ... 
    level = level + 1; 
    DoStuff(level); 
    // ... 
} 

На мой взгляд, лучше не злоупотреблять операторами ++ и -; он быстро становится запутанным и/или неопределенным, что действительно происходит, и современные компиляторы C++ не генерируют более эффективный код с этими операторами в любом случае.

+0

Согласились, чтобы не злоупотреблять ими. То, что также «отличная забава», - это перегрузка пост и pre ++ с классом, так как тогда все ставки отключены. – workmad3 2008-09-22 12:03:20

+1

Я должен не согласиться; `++` и `--` необычайно интуитивно понятны и легки. Единственные проблемы времени возникают, когда люди либо пытаются получить симпатичные, либо даже не хотят искать поведение операторов, которые они используют. – 2008-09-22 12:03:56

+0

Итак, что интуитивно и легко? :-) DoMoreStuff (уровень ++, уровень ++); – 2008-09-22 12:08:21

0

уровень + 1 посылает любой уровень + 1 в функцию. уровень ++ отправляет уровень функции, а затем увеличивает его.

Вы можете сделать уровень ++, и это, скорее всего, даст вам желаемые результаты.

0

В первом примере используется значение 'index', увеличивается значение и обновления 'index'.

Во втором примере используется значение «index» плюс 1, но не изменяется содержание «индекса».

Итак, в зависимости от того, что вы хотите сделать здесь, в магазине могут быть сюрпризы!

-2

Насколько я понял, выражение параметра сначала оценивается и получает значение уровня. Эта переменная сама по себе увеличивается до того, как вызывается функция, потому что компилятор не заботится о том, используете ли вы это выражение в качестве параметра или иначе ... Все, что он знает, это то, что он должен увеличивать значение и получать старое значение в результате выражение.

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

12

возвращаемое значение level++ будет level и therefore проход level в DoStuff. Это может быть довольно неприятная ошибка, поскольку рекурсия никогда не закончится (из того, что показано, DoStuff всегда передается с тем же значением). Возможно, вместо этого стоит ++level или level + 1?

level + 1 будет проходить level + 1 в DoStuff и оставить level без изменений для остальной функции.


Оператор пост-инкремент (переменная ++), в точности эквивалентна функции

int post_increment(ref int value) 
{ 
    int temp = value; 
    value = value + 1 
    return temp; 
} 

в то время как оператор предварительно приращение (++ переменная) точно эквивалентна функции

int pre_increment(ref int value) 
{ 
    value = value + 1; 
    return value; 
} 

Следовательно, если вы разворачиваете оператор inline в код, операторы эквивалентны:

DoStuff(a + 1) 

int temp = a + 1; 
DoStuff(temp); 

DoStuff(++a) 

a = a + 1; 
DoStuff(a); 

DoStuff(a++); 

int temp = a; 
a = a + 1; 
DoStuff(temp); 

Важно отметить, что пост-инкремент не эквивалентно:

DoStuff(a); 
a = a + 1; 

Кроме того, в качестве точки стиля нельзя увеличивать значение, если только намерение не должно использовать добавочное значение (конкретная версия правила), не присваивайте значение переменной, если вы не планируете используя это значение "). Если значение i + 1 больше не используется, то предпочтительным использованием должно быть DoStuff(i + 1), а не DoStuff(++i).

44

Для уточнения всех других ответов:

+++++++++++++++++++++

DoStuff(a++); 

эквивалентно:

DoStuff(a); 
a = a + 1; 

+++++++++++++++++++++

DoStuff(++a); 

эквивалентно т О:

a = a + 1; 
DoStuff(a); 

+++++++++++++++++++++

DoStuff(a + 1); 

эквивалентно:

b = a + 1; 
DoStuff(b); 

++ +++++++++++++++++++

1
public DoStuff(int level) 
{ 

    // DoStuff(level); 
    DoStuff(level++); 
    // level = level + 1; 
    // here, level's value is 1 greater than when it came in 
} 

Он фактически увеличивает значение уровня.

public DoStuff(int level) 
{ 
    // int iTmp = level + 1; 
    // DoStuff(iTmp); 
    DoStuff(level+1); 
    // here, level's value hasn't changed 
} 

фактически не увеличивает значение уровня.

Не проблема перед вызовом функции, но после вызова функции значения будут разными.

0

Хотя заманчиво переписать как:

DoStuff(++level); 

Я лично думаю, что это менее читаемыми, чем увеличивающиеся переменную до вызова метода. Как было отмечено несколько приведенных выше ответов, следующий будет понятнее:

level++; 
DoStuff(level); 
0

При использовании языка, что позволяет перегружать оператор, и «+ < целое >» было определено, чтобы сделать что-то другое, чем пост- и префикс '++'.

С другой стороны, я видел такие мерзости в школьных проектах *, если вы столкнулись с этим в дикой природе, у вас, вероятно, есть действительно хорошая, хорошо документированная причина.

[* стопка целых чисел, если я не ошибаюсь. '++' и '-' нажали и выскочили, а «+» и «-» выполнили нормальную арифметику]

1

В уровне ++ вы используете постфиксный оператор. Этот оператор работает после использования переменной. То есть после того, как он помещается в стек для вызываемой функции, он увеличивается. С другой стороны, уровень + 1 является простым математическим выражением, и он оценивается, и результат передается вызываемой функции. Если вы хотите сначала увеличиваем переменную, а затем передать его в вызываемой функции, вы можете использовать префикс оператора: ++ уровень

0

Выражаясь самым простым способом, ++var является оператором префиксом и увеличит переменные до оценивается остальная часть выражения. var++, постфиксный оператор, приращение переменной после вычисляется остальная часть выражения. И, как говорили другие, конечно, var+1 создает только временную переменную (отдельную в памяти), которая начинается с var и увеличивается с постоянной 1.