i ++와 ++ i의 차이점은 무엇입니까?


204

나는 모두 C # 코드의 많은 부분에서 사용되는 그들을 본 적이, 내가 사용하는 경우 알고 싶습니다 i++또는 ++i( i같은 숫자 변수 인 int, float, double, 등). 이것을 아는 사람이 있습니까?


13
그것이 아무런 차이가없는 곳을 제외하고는, 당신이 지금 요구하는 것과 같은 질문을 사람들이 유발하게 할 것이기 때문에 어느 쪽도 사용해서는 안됩니다. "나를 생각하지 마라"는 디자인뿐만 아니라 코드에도 적용됩니다.
인스턴스 헌터


2
@Dlaor : 내 의견에있는 링크를 읽었습니까? 첫 번째는 C #에 관한 것이고, 두 번째는 C #에 중점을 둔 대답으로 언어에 구애받지 않습니다.
gnovice

7
@gnovice, 첫 번째는 실제 코드와의 차이점에 대해 묻는 동안 성능 차이에 대해 묻고, 두 번째는 일반적으로 차이점에 대해 묻는 동안 루프의 차이점에 관한 것이고, 세 번째는 C ++에 관한 것입니다.
Dlaor

1
루프에서 i ++와 ++ i차이점 이 중복되지 않는다고 생각 합니까? -Dloar가 위에서 언급 한 다른 질문은 루프 내에서의 사용법에 대해 구체적으로 묻습니다.
chue x

답변:


201

이상하게도 다른 두 가지 대답이 철자를 쓰지 않는 것처럼 보이며 분명히 말할 가치가 있습니다.


i++'의 값을 말한 i다음 증가 시키십시오'를 의미합니다.

++i``증가 i, 그다음 가치를 말하십시오 ''를 의미


사전 증가, 사후 증가 연산자입니다. 두 경우 모두 변수가 증가 하지만 정확히 같은 경우 두 표현식의 값을 가져 오면 결과가 달라집니다.



65
어느 진술도 정확하지 않습니다. 첫 번째 진술을 고려하십시오. i ++는 실제로 "값을 저장하고, 증가시키고, i에 저장 한 다음 원래 저장된 값을 알려줍니다"를 의미합니다. 즉, 말하는 일이 발생한다 하지의 증분 전에 당신이 그것을 언급 한대로. 두 번째 진술을 고려하십시오. i ++는 실제로 "값을 저장하고, 증가시키고, i에 저장하고, 증가 된 값을 알려줍니다"를 의미합니다. 당신이 말한 방식은 그 값이 i의 값인지, i에 할당 된 값인지 확실하지 않습니다. 그들은 다를 수 있습니다 .
Eric Lippert

8
@ 에릭, 당신의 대답조차도 나에게 보일 것입니다. 두 번째 진술은 범주 적으로 정확합니다. 그는 몇 단계를 제외하지만.
Evan Carroll

20
물론, 동작 의미론을 설명하는 조잡하고 잘못된 방법은 대부분 정확하고 정확한 설명과 동일한 결과를 제공합니다. 첫째, 잘못된 추론을 통해 정답을 얻는 데 강력한 가치 를 느끼지 못합니다. 둘째, 정확히 이런 종류의 문제를 일으키는 생산 코드를 보았습니다. 실제 프로그래머로부터 일년에 수십 개의 질문을 받았는데 왜 특정식이 증분과 감소로 가득 차고 배열 역 참조가 그들이 가정했던 방식으로 작동하지 않는지에 대해 궁금합니다.
Eric Lippert

12
@supercat : 토론은 C가 아니라 C #에 관한 것입니다.
Thanatos

428

불행히도 여기에 이미 게시되어있는이 질문에 대한 전형적인 대답은 하나는 남은 작업 이전에 "증가"를 수행하고 다른 하나는 남은 작업 후에 "이후"증가를 수행한다는 것입니다. 그것이 직관적으로 아이디어를 얻었지만, 그 진술은 완전히 잘못되었습니다 . 시간에 이벤트의 순서는 매우 C #으로 잘 정의이며, 단호하다 하지 ++의 접두사 (++ VAR)와 후위 (VAR ++) 버전이 다른 작업에 대한 다른 순서로 일을하는 경우.

이 질문에 대해 많은 잘못된 답변을 보게 될 것은 놀라운 일이 아닙니다. 많은 "C #을 가르쳐라"책도 잘못 이해합니다. 또한 C #의 방식은 C의 방식과 다릅니다. 많은 사람들이 C #과 C가 같은 언어 인 것처럼 추론합니다. 그들은 아닙니다. 제 생각에 C #의 증가 및 감소 연산자의 디자인은 C에서 이러한 연산자의 디자인 결함을 피합니다.

C #에서 prefix 및 postfix ++의 작동이 정확히 무엇인지 확인하려면 두 가지 질문에 대답해야합니다. 첫 번째 질문은 결과무엇입니까? 두 번째 질문은 언제 증분의 부작용이 발생합니까?

두 질문에 대한 답이 무엇인지는 분명하지 않지만 일단 본 후에는 실제로 매우 간단합니다. 변수 x에 대해 x ++와 ++ x가하는 일을 정확하게 설명하겠습니다.

접두사 형식 (++ x)의 경우 :

  1. x는 변수를 생성하기 위해 평가됩니다
  2. 변수 값이 임시 위치에 복사됩니다
  3. 임시 값이 증가하여 새 값을 생성합니다 (임시 값을 덮어 쓰지 않음).
  4. 새로운 값은 변수에 저장됩니다
  5. 연산 결과는 새로운 값 (즉, 임시 값의 증가 된 값)입니다.

접미사 형식 (x ++)의 경우 :

  1. x는 변수를 생성하기 위해 평가됩니다
  2. 변수 값이 임시 위치에 복사됩니다
  3. 임시 값이 증가하여 새 값을 생성합니다 (임시 값을 덮어 쓰지 않음).
  4. 새로운 값은 변수에 저장됩니다
  5. 작업 결과 는 임시 값입니다.

알아 두어야 할 사항 :

첫째, 이벤트의 순서는 두 경우 모두 동일합니다 . 다시 말하지만, 시간의 이벤트 순서가 접두사와 접두사 사이 에서 변경 되는 경우 는 절대 아닙니다 . 평가가 다른 평가 전에 또는 다른 평가 후에 발생한다고 말하는 것은 전적으로 거짓입니다. 1 단계에서 4 단계까지 동일하다는 것을 알 수 있듯이 두 경우 모두 동일한 순서 로 평가 가 수행됩니다. 유일한 차이점은입니다 마지막 단계 - 결과가 임시, 또는 새, 증가 값의 값이 있는지 여부.

간단한 C # 콘솔 앱으로이를 쉽게 보여줄 수 있습니다.

public class Application
{
    public static int currentValue = 0;

    public static void Main()
    {
        Console.WriteLine("Test 1: ++x");
        (++currentValue).TestMethod();

        Console.WriteLine("\nTest 2: x++");
        (currentValue++).TestMethod();

        Console.WriteLine("\nTest 3: ++x");
        (++currentValue).TestMethod();

        Console.ReadKey();
    }
}

public static class ExtensionMethods 
{
    public static void TestMethod(this int passedInValue) 
    {
        Console.WriteLine("Current:{0} Passed-in:{1}",
            Application.currentValue,
            passedInValue);
    }
}

결과는 다음과 같습니다.

Test 1: ++x
Current:1 Passed-in:1

Test 2: x++
Current:2 Passed-in:1

Test 3: ++x
Current:3 Passed-in:3

첫 번째 테스트 currentValue에서 TestMethod()확장 프로그램 에 전달 된 내용과 확장 프로그램 에 전달 된 내용이 예상대로 동일한 값을 표시 한다는 것을 알 수 있습니다 .

그러나 두 번째 경우 에는을 호출 한 후currentValue 발생률 이 증가했음을 알리려고 하지만 결과에서 볼 수 있듯이 '현재 : 2'결과에 표시된대로 호출 전에 발생합니다.TestMethod()

이 경우 먼저 값이 currentValue임시에 저장됩니다. 다음으로, 그 값의 증분 버전은 currentValue원래 값을 저장하는 임시 값을 건드리지 않고 다시 저장됩니다 . 마지막으로 임시가에 전달됩니다 TestMethod(). 호출 증가가 발생하면 증가 TestMethod()하지 않은 동일한 값을 두 번 쓰지만 그렇지 않습니다.

currentValue++++currentValue연산 에서 반환 된 값은 두 연산이 모두 종료 될 때 변수에 저장된 실제 값이 아니라 임시 값을 기반으로 한다는 점에 유의해야합니다 .

위의 순서대로 상기 첫 두 단계는 변수의 현재 값을 임시로 복사합니다. 이것이 반환 값을 계산하는 데 사용되는 것입니다. 접두사 버전의 경우 임시 값이 증가하는 반면 접미사 버전의 경우 직접 / 증가되지 않는 값입니다. 변수 자체는 초기 저장 후 임시로 다시 읽지 않습니다.

간단히 말해, 접미사 버전은 변수에서 읽은 값 (예 : 임시 값)을 반환하고 접두사 버전은 변수에 다시 쓴 값 (예 : 임시 값 증가)을 반환합니다. 변수 값을 반환하지 마십시오.

변수 자체가 변동적일 수 있고 다른 스레드에서 변경 되었기 때문에 이러한 연산의 반환 값이 변수에 저장된 현재 값과 다를 수 있으므로 이해해야합니다.

사람들이 우선 순위, 연관성 및 부작용이 실행되는 순서에 대해 매우 혼란스러워하는 것은 놀랍게 일반적입니다. C에서는 매우 혼란 스럽기 때문에 주로 의심됩니다. 접두사와 접미사 작업이 "시간에 물건을 움직인다"는 아이디어의 허위를 보여주는 것을 포함하여 이러한 문제에 대한 추가 분석은 다음을 참조하십시오.

https://ericlippert.com/2009/08/10/precedence-vs-order-redux/

이 SO 질문으로 이어졌습니다.

int [] arr = {0}; int 값 = arr [arr [0] ++]; 값 = 1?

주제에 대한 이전 기사에 관심이있을 수도 있습니다.

https://ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order/

https://ericlippert.com/2007/08/14/c-and-the-pit-of-despair/

C가 정확성에 대해 추론하기 어려운 흥미로운 경우 :

https://docs.microsoft.com/archive/blogs/ericlippert/bad-recursion-revisited

또한 연결 간단한 할당과 같이 부작용이있는 다른 작업을 고려할 때 비슷한 미묘한 문제가 발생합니다.

https://docs.microsoft.com/archive/blogs/ericlippert/chaining-simple-assignments-is-not-so-simple

다음은 증가 연산자 가 변수가 아닌 C #의 을 만드는 이유에 대한 흥미로운 게시물입니다 .

C와 같은 언어로 ++ i ++를 할 수없는 이유는 무엇입니까?


4
+1 : 정말 궁금한 점이 있다면 "C에서 이러한 작업의 설계 결함"이 무엇을 의미하는지에 대한 언급을 해 줄 수 있습니까?
저스틴 Ardini

21
@ 저스틴 : 나는 몇 가지 링크를 추가했습니다. 그러나 기본적으로 C에서는 시간에 어떤 순서로 발생하는지에 대한 보장이 없습니다. 일치하는 컴파일러는 동일한 시퀀스 포인트에 두 개의 돌연변이가있을 때 만족할만한 모든 작업을 수행 할 수 있으며 구현 정의 동작 인 무언가를하고 있다고 말하지 않아도됩니다. 이로 인해 사람들은 일부 컴파일러에서 작동하고 다른 프로세서에서 완전히 다른 작업을 수행하는 위험한 코드를 작성할 수 있습니다.
Eric Lippert

14
정말 호기심이 많으면 이것은 좋은 지식이지만 평균 C # 응용 프로그램의 경우 다른 답변의 문구와 실제 작업 간의 차이가 실제로 만드는 언어의 추상화 수준보다 훨씬 낮습니다. 차이 없음. C #은 어셈블러가 아니며 시간의 99.9 % i++또는 ++i코드에서 사용되며 백그라운드에서 진행되는 작업은 바로 그 것입니다. 백그라운드에서 . C #을 작성 하여이 수준에서 진행되는 것 이상의 추상화 수준으로 올라가므로 이것이 실제로 C # 코드에 중요하다면 이미 잘못된 언어에있을 수 있습니다.
Tomas Aschan

24
@Tomas : 먼저, 평균적인 응용 프로그램의 양뿐만 아니라 모든 C # 응용 프로그램 에 대해 걱정하고 있습니다. 평균이 아닌 응용 프로그램의 수가 많습니다. 둘째, 차이를 만드는 경우를 제외하고는 차이가 없습니다 . 실제 코드의 실제 버그를 기반으로 한이 질문에 대한 질문은 아마도 일년에 수십 번 정도입니다. 셋째, 더 큰 요점에 동의합니다. ++의 전체 아이디어는 현대 코드에서 매우 기이하게 보이는 매우 낮은 수준의 아이디어입니다. C와 같은 언어가 그러한 연산자를 갖는 것은 관용적이기 때문에 실제로 거기에만 있습니다.
Eric Lippert

11
+1이지만 진실을 말해야합니다 ... 행에서 혼자가 아닌 유일한 접미사 연산자를 사용하는 것은 수년이 지났 i++;습니다 for (int i = 0; i < x; i++). 이것의 행복! (그리고 나는 결코 접두사 연산자를 사용하지 않습니다). 수석 프로그래머가 해독하는 데 2 ​​분이 걸릴 무언가를 작성해야한다면 ... 글쎄 ... 코드를 한 줄 더 쓰거나 임시 변수를 도입하는 것이 좋습니다 :-) 나는 당신의 "기사"라고 생각합니다 (나는 이겼습니다 '답변'이라고 부르지 않음)은 나의 선택을 입증합니다 :-)
xanatos

23

당신이 가지고 있다면:

int i = 10;
int x = ++i;

다음 x이 될 것입니다 11.

그러나 당신이 가지고 있다면 :

int i = 10;
int x = i++;

다음 x이 될 것입니다 10.

Eric이 지적했듯이 두 경우 모두 증분이 동시에 발생하지만 결과는 다른 값으로 제공됩니다 (Eric! 덕분에).

일반적으로, ++i정당한 이유가없는 한 사용하고 싶습니다 . 예를 들어 루프를 작성할 때 다음을 사용하고 싶습니다.

for (int i = 0; i < 10; ++i) {
}

또는 변수를 늘려야하는 경우 다음을 사용하고 싶습니다.

++x;

일반적으로 한 가지 방법은 다른 의미가 없으며 코딩 스타일에 의존하지만 다른 할당 (내 원래 예제와 같이)에서 연산자를 사용하는 경우 잠재적 부작용을 인식하는 것이 중요합니다.


2
코드 줄을 순서대로 넣어서 예제를 명확히 할 수 있습니다. 즉 "var x = 10; var y = ++ x;" "var x = 10; var y = x ++;" 대신에.
Tomas Aschan

21
이 답변은 위험합니다. 증가는 나머지 작업에 대한 변경되지 않습니다 발생하면 하나의 케이스에 완료한다는 있도록 하기 전에 남아있는 운영과 다른 경우가 완료 깊은 오해의 소지 나머지 작업. 증분은 두 경우 모두 동시에 수행됩니다 . 다른 점은 증분을 수행 할 때가 아니라 결과로 제공되는 값입니다 .
Eric Lippert

3
i변수 이름이 varC # 키워드가 아닌 변수 이름 으로 사용되도록 편집했습니다 .
Jeff Yates

2
변수를 지정하지 않고 "i ++;" 또는 "++ i;" 정확히 동일한 결과를 제공합니까 아니면 내가 잘못하고 있습니까?
Dlaor

1
@Eric : 원래 문구가 "위험한"이유를 분명히 설명해 주시겠습니까? 세부 사항이 잘못되어도 올바른 사용법으로 이어집니다.
저스틴 Ardini

7
int i = 0;
Console.WriteLine(i++); // Prints 0. Then value of "i" becomes 1.
Console.WriteLine(--i); // Value of "i" becomes 0. Then prints 0.

이 질문에 대답합니까?


4
접미사 증가와 접두사 감소가 변수에 영향을 미치지 않는다고 상상할 수 있습니다. P
Andreas

5

연산자가 작동하는 방식은 동시에 증가하지만, 변수보다 앞에 있으면 증가 / 감소 된 변수로 표현식이 평가됩니다.

int x = 0;   //x is 0
int y = ++x; //x is 1 and y is 1

변수 뒤에있는 경우, 현재 명령문은 마치 증가 / 감소되지 않은 것처럼 원래 변수로 실행됩니다.

int x = 0;   //x is 0
int y = x++; //'y = x' is evaluated with x=0, but x is still incremented. So, x is 1, but y is 0

필요하지 않은 경우 사전 증가 / 감소 (++ x) 사용에 dcp에 동의합니다. 실제로 증가 후 / 감소를 사용하는 유일한 시간은 while 루프 또는 해당 종류의 루프입니다. 이 루프는 동일합니다 :

while (x < 5)  //evaluates conditional statement
{
    //some code
    ++x;       //increments x
}

또는

while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented
{
    //some code
}

배열을 색인화하는 동안이 작업을 수행 할 수도 있습니다.

int i = 0;
int[] MyArray = new int[2];
MyArray[i++] = 1234; //sets array at index 0 to '1234' and i is incremented
MyArray[i] = 5678;   //sets array at index 1 to '5678'
int temp = MyArray[--i]; //temp is 1234 (becasue of pre-decrement);


4

레코드의 경우 C ++에서 (예를 들어) 작업 순서를 신경 쓰지 않으면 (즉, 증가 또는 감소하고 나중에 사용하려는 경우) 접두사 연산자는 더 효율적이지 않기 때문에 더 효율적입니다. 개체의 임시 복사본을 만들어야합니다. 불행히도 대부분의 사람들은 접두사 (++ var) 대신 posfix (var ++)를 사용합니다. 왜냐하면 우리가 처음에 배웠기 때문입니다. (나는 인터뷰에서 이것에 대해 물었다). 이것이 C #에서 사실인지 확실하지 않지만 그럴 것이라고 가정합니다.


2
그래서 C #에서 ++ i를 단독으로 사용할 때 (즉, 더 큰 표현이 아닌) 사용합니다. i ++에서 ac # 루프를 볼 때마다 약간 울지 만 C #에서는 실제로 이것이 훨씬 더 나은 코드라는 것을 알았습니다. 습관적으로 죽기 때문에 개인적으로 ++ i를 계속 사용합니다.
Mikle
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.