C ++에서 증가-언제 x ++ 또는 ++ x를 사용합니까?


93

저는 현재 C ++를 배우고 있으며 얼마 전에 증가에 대해 배웠습니다. 나는 "++ x"를 사용하여 이전에 증분을 만들고 "x ++"를 사용하여 이후에 할 수 있다는 것을 알고 있습니다.

그래도 언제 둘 중 하나를 사용해야할지 모르겠습니다. "++ x"를 실제로 사용한 적이 없으며 지금까지 모든 것이 잘 작동했습니다. 언제 사용해야합니까?

예 : for 루프에서 "++ x"를 사용하는 것이 좋을 때는 언제입니까?

또한 누군가 다른 증가 (또는 감소)가 어떻게 작동하는지 정확히 설명 할 수 있습니까? 정말 감사하겠습니다.

답변:


117

선호의 문제가 아니라 논리의 문제입니다.

x++현재 명령문 처리 한 변수 x의 값을 증가시킵니다 .

++x현재 명령문을 처리 하기 전에 변수 x의 값을 증가시킵니다 .

따라서 작성하는 논리를 결정하십시오.

x += ++ii를 증가시키고 i + 1을 x에 추가합니다. x += i++x에 i를 더한 다음 i를 증가시킵니다.


27
for 루프에서, 프리미티브에서는 전혀 차이가 없습니다. 많은 코딩 스타일은 오해 할 수있는 증분 연산자를 사용하지 않는 것이 좋습니다. 즉, x ++ 또는 ++ x는 자체 줄에만 존재해야하며 y = x ++가 아니어야합니다. 개인적으로, 나는 이런 식으로하지 않습니다, 그러나 그것은 드문 일이다
Mikeage

2
그리고 자체 라인에서 사용되는 경우 생성 된 코드는 거의 동일합니다.
Nosredna

14
이것은 pedantry처럼 보일 수 있지만 (주로 :)이기 때문에 C ++에서는 증가 전의 x++값을 가진 rvalue이고 x증가 후의 x++값을 가진 lvalue입니다 x. 두 식 모두 실제 증가 된 값이 x에 다시 저장 될 때를 보장하지 않으며 다음 시퀀스 포인트 이전에 발생한다는 보장 만합니다. '현재 명령문 처리 후'는 일부 표현식에 시퀀스 포인트가 있고 일부 명령문은 복합 명령문이므로 엄격하게 정확하지 않습니다.
CB Bailey

10
사실 그 대답은 오해의 소지가 있습니다. 변수 x가 수정되는 시점은 실제로 다르지 않습니다. 차이점은 x ++는 x의 이전 값의 rvalue를 반환하도록 정의되어 있지만 ++ x는 여전히 변수 x를 참조한다는 것입니다.
sellibitze

5
@BeowulfOF : 대답은 존재하지 않는 주문을 의미합니다. 증분이 발생할 때 표준에는 말할 것이 없습니다. 컴파일러는 "x + = i ++"를 다음과 같이 구현할 수 있습니다. int j = i; i = i + 1; x + = j; "(즉, 'i'가"현재 명령문 처리 "전에 증가 함) 이것이"i = i ++ "의 동작이 정의되지 않은 이유와 대답에"뒤틀기 "가 필요한 이유입니다."x + = ++ i "는 순서 제안이 없기 때문에 정확합니다 :"i를 증가시키고 x에 i + 1을 추가합니다 ".
Richard Corden

53

Scott Meyers 는 논리가 접미사가 적절하다고 지시하는 경우를 제외하고 접두사를 선호하라고 말합니다.

"더 효과적인 C ++"항목 # 6- 저에게 충분한 권한입니다.

책을 소유하지 않은 사람들을 위해 적절한 인용문이 있습니다. 32 페이지에서 :

C 프로그래머로 일하던 시절부터 증분 연산자의 접두어 형식은 "증가 및 가져 오기"라고하는 반면 접미어 형식은 "가져 오기 및 증분"이라고도합니다. 두 구절은 모두 공식적인 사양으로 작동하기 때문에 기억해야합니다.

34 페이지 :

효율성이 걱정되는 분이라면 접미사 증가 기능을 처음 보았을 때 땀을 흘렸을 것입니다. 이 함수는 반환 값에 대한 임시 객체를 생성해야하며 위의 구현은 생성 및 소멸되어야하는 명시 적 임시 객체도 생성합니다. 접두사 증가 함수에는 그러한 임시가 없습니다 ...


4
컴파일러가 증분 이전의 값이 중요하지 않다는 것을 인식하지 못하면 여러 명령어에서 접미사 증분을 구현할 수 있습니다. 이전 값을 복사 한 다음 증분합니다. 접두사 증분은 항상 하나의 명령어 여야합니다.
gnud

8
어제 gcc : 실행 후 값이 버려지는 for 루프에서 i++또는 ++i생성 된 코드가 동일합니다.
Giorgio

for 루프 외부에서 시도하십시오. 과제의 동작은 달라야합니다.
duffymo

저는 Scott Meyers의 두 번째 요점에 대해 명시 적으로 동의하지 않습니다. "x ++"또는 "++ x"의 90 % 이상 사례가 일반적으로 할당에서 격리되고 최적화 프로그램은 임시 변수가 필요하지 않다는 것을 인식 할 수있을만큼 똑똑하기 때문에 일반적으로 관련이 없습니다. 그런 경우에 만들어집니다. 이 경우 두 형식은 완전히 상호 교환 할 수 있습니다. 이것의 의미는 "x ++"로 가득 찬 오래된 코드베이스는 그대로 두어야한다는 것입니다. 성능을 향상시키는 것보다 "++ x"로 변경하는 미묘한 오류가 발생할 가능성이 더 큽니다. "x ++"를 사용하고 사람들이 생각하게 만드는 것이 더 낫습니다.
omatai

2
당신은 모든 당신, 그러나 당신의 코드가있는 경우 성능에 의존 사이의 성능 차이 때문에 원하는 스콧 마이어스를 신뢰할 수 ++xx++실제로 중요한, 당신이 실제로 완전하고 적절하게 최적화 할 수 없습니다 컴파일러 사용하는 것이 훨씬 더 중요 하거나 상관없이이 버전을 문맥. "내가이 형편없는 낡은 망치를 사용하고 있기 때문에 43.7도 각도로만 못을 박을 수있다"는 것은 못을 43.7 도만 돌려 집을 짓는 것에 대한 잘못된 주장이다. 더 나은 도구를 사용하십시오.
Andrew Henle

28

반복자를 증가 시킬cppreference 에서 :

이전 값을 사용하지 않으려면 사전 증가 연산자 (++ iter)를 사후 증가 연산자 (iter ++)보다 선호해야합니다. 사후 증가는 일반적으로 다음과 같이 구현됩니다.

   Iter operator++(int)   {
     Iter tmp(*this); // store the old value in a temporary object
     ++*this;         // call pre-increment
     return tmp;      // return the old value   }

분명히 사전 증가보다 효율성이 떨어집니다.

사전 증가는 임시 개체를 생성하지 않습니다. 개체를 만드는 데 비용이 많이 드는 경우 이는 상당한 차이를 만들 수 있습니다.


8

시맨틱 (사전 / 사후의)이 중요하지 않은 사전 / 사후 증분을 사용하는 경우 생성 된 코드가 동일하게 공격된다는 것을 알고 싶습니다.

예:

pre.cpp :

#include <iostream>

int main()
{
  int i = 13;
  i++;
  for (; i < 42; i++)
    {
      std::cout << i << std::endl;
    }
}

post.cpp :

#include <iostream>

int main()
{

  int i = 13;
  ++i;
  for (; i < 42; ++i)
    {
      std::cout << i << std::endl;
    }
}

_

$> g++ -S pre.cpp
$> g++ -S post.cpp
$> diff pre.s post.s   
1c1
<   .file   "pre.cpp"
---
>   .file   "post.cpp"

5
정수와 같은 기본 유형의 경우 예. 과 같은 것에 대한 차이점이 무엇인지 확인 했습니까 std::map::iterator? 물론 두 연산자가 다르지만 결과가 사용되지 않으면 컴파일러가 접두사를 접두사로 최적화할지 여부가 궁금합니다. 후위 버전에 부작용이있을 수 있다는 점을 고려할 때 허용되지 않는다고 생각합니다.
seh

또한 ' 컴파일러는 아마도 부작용이 필요하지 않다는 것을 깨닫고이를 최적화 할 것입니다. '는 아마도 너무 많은 사실을 제외하고는 어떤 이유없이 더 복잡한 접미사 연산자를 사용하는 조잡한 코드를 작성하는 변명이되어서는 안됩니다. 가정 교재는 명백한 이유없이 접미사를 사용하고 도매로 복사됩니다.
underscore_d

6

명심해야 할 가장 중요한 점은 imo입니다. x ++는 실제로 증가가 발생하기 전에 값을 반환해야하므로 객체의 임시 복사본을 만들어야합니다 (사전 증가). 이것은 제자리에서 증분되고 반환되는 ++ x보다 덜 효율적입니다.

하지만 언급 할 가치가있는 또 다른 점은 대부분의 컴파일러가 가능하면 불필요한 것들을 최적화 할 수 있다는 것입니다. 예를 들어 두 옵션 모두 여기에서 동일한 코드로 이어질 것입니다.

for (int i(0);i<10;++i)
for (int i(0);i<10;i++)

5

나는 @BeowulfOF에 동의하지만 명확성을 위해 항상 논리가 절대적으로 명확하도록 진술을 분할하는 것을 옹호합니다.

i++;
x += i;

또는

x += i;
i++;

그래서 내 대답은 당신이 명확한 코드를 작성한다면 이것은 거의 중요하지 않을 것입니다 (그리고 그것이 중요하다면 당신의 코드는 아마도 충분히 명확하지 않을 것입니다).


2

++ x가 x ++보다 빠를 것으로 예상된다는 점을 다시 강조하고 싶었습니다 .


2
저는이 강조가 오해의 소지가 있음을 강조하고 싶습니다. 분리 된 "x ++"로 끝나는 루프를보고 "아하!-이것이 너무 느리게 실행되는 이유입니다!" "++ x"로 변경하면 정확히 차이가 없습니다. 옵티마이 저는 아무도 결과를 사용하지 않을 때 임시 변수를 만들 필요가 없다는 것을 인식 할만큼 똑똑합니다. 의미는 "x ++"로 가득 찬 오래된 코드베이스는 그대로 두어야한다는 것입니다. 성능을 향상시키는 것보다 오류를 변경할 가능성이 더 큽니다.
omatai

1

차이점을 올바르게 설명했습니다. 루프를 실행할 때마다 x가 증가할지 아니면 그 후에 증가할지에 따라 다릅니다. 그것은 당신의 프로그램 로직, 무엇이 적절한 지에 달려 있습니다.

STL-Iterator (이러한 연산자도 구현 함)를 다룰 때 중요한 차이점은 it ++이 반복기가 가리키는 객체의 복사본을 만든 다음 증분 한 다음 복사본을 반환한다는 것입니다. ++ 반면에 먼저 증가를 수행 한 다음 반복기가 가리키는 객체에 대한 참조를 반환합니다. 이는 모든 성능이 중요하거나 자체 STL 반복자를 구현할 때 대부분 관련이 있습니다.

편집 : 접두사와 접미사 표기의 혼합 수정


루프 반복의 "전 / 후"에 대한 이야기는 조건에서 사전 / 사후 증가 / 감소가 발생하는 경우에만 의미가 있습니다. 더 자주, 그것은 어떤 논리도 변경할 수없는 continuation 절에있을 것입니다. 비록 클래스 타입이 postfix를 사용하는 것이 더 느릴 수 있고 사람들은 이유없이 그것을 사용해서는 안됩니다.
underscore_d

1

++,-연산자의 접미사 형식은 use-then-change 규칙을 따릅니다. ,

접두사 형식 (++ x,-x)은 change-then-use 규칙을 따릅니다 .

예 1 :

cout 을 사용하여 여러 값이 <<로 계단식으로 연결되면 계산 (있는 경우)이 오른쪽에서 왼쪽으로 수행되지만 인쇄는 왼쪽에서 오른쪽으로 수행됩니다 (예 : 처음에 val 이 10 인 경우).

 cout<< ++val<<" "<< val++<<" "<< val;

결과적으로

12    10    10 

예 2 :

Turbo C ++에서 표현식에서 ++ 또는 (어떤 형태로든)의 여러 항목이 발견되면 먼저 모든 접두어 형식이 계산 된 다음식이 평가되고 마지막으로 접미어 형식이 계산됩니다.

int a=10,b;
b=a++ + ++a + ++a + a;
cout<<b<<a<<endl;

Turbo C ++의 출력은 다음과 같습니다.

48 13

현대 컴파일러의 출력은 (규칙을 엄격하게 따르기 때문에)

45 13
  • 참고 : 하나의 표현식에서 동일한 변수에 대해 증가 / 감소 연산자를 여러 번 사용하는 것은 권장되지 않습니다. 이러한
    식 의 처리 / 결과는 컴파일러마다 다릅니다.

다중 증가 / 감소 연산을 포함하는 표현식이 "컴파일러마다 다릅니다"가 아니라 오히려 더 나쁩니다. 시퀀스 포인트 간의 이러한 다중 수정은 정의되지 않은 동작을 가지고 프로그램을 손상시킵니다.
underscore_d

0

코드의 명확성을 고려할 때 언어 구문을 이해하는 것이 중요합니다. 예를 들어 사후 증가로 문자열 복사를 고려하십시오.

char a[256] = "Hello world!";
char b[256];
int i = 0;
do {
  b[i] = a[i];
} while (a[i++]);

문자열 끝에서 0 문자 (거짓 테스트)를 만나면 루프가 실행되기를 원합니다. 이를 위해서는 사전 증가 값을 테스트하고 인덱스도 증가시켜야합니다. 그러나 반드시 그 순서는 아닙니다. 사전 증분으로 이것을 코딩하는 방법은 다음과 같습니다.

int i = -1;
do {
  ++i;
  b[i] = a[i];
} while (a[i]);

더 분명한 것은 맛의 문제이며 기계에 레지스터가 많으면 a [i]가 비싸거나 부작용이 있더라도 둘 다 동일한 실행 시간을 가져야합니다. 중요한 차이는 인덱스의 종료 값일 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.