문제

왜 ++ i가 l- 값이고 I ++가 아닌가?

도움이 되었습니까?

해결책

다른 답변자가 이미 그 이유를 지적했듯이 ++i LValue는 그것을 참조로 전달하는 것입니다.

int v = 0;
int const & rcv = ++v; // would work if ++v is an rvalue too
int & rv = ++v; // would not work if ++v is an rvalue

두 번째 규칙의 이유는 참조가 const에 대한 참조 인 경우 문자를 사용하여 참조를 초기화 할 수있는 것입니다.

void taking_refc(int const& v);
taking_refc(10); // valid, 10 is an rvalue though!

왜 우리는 당신이 물어볼 수있는 rvalue를 소개합니까? 글쎄,이 용어는이 두 가지 상황에 대한 언어 규칙을 구축 할 때 발생합니다.

  • 우리는 로케이터 가치를 원합니다. 이는 읽을 수있는 값이 포함 된 위치를 나타냅니다.
  • 우리는 표현의 가치를 나타내고 싶습니다.

위의 두 가지 점은 C99 표준에서 가져와이 멋진 각주가 포함됩니다.

''lvalue '라는 이름은 원래 할당 표현식 E1 = E2에서 나오며 왼쪽 피연산자 E1은 (수정 가능한) lvalue가되어야합니다. 아마도 객체``로케이터 가치 ''를 나타내는 것으로 간주 될 것입니다. 때때로``rvalue ''라고 불리는 것은이 국제 표준에 있으며``표현의 가치 ''로 묘사됩니다. ]

로케이터 값이 호출됩니다 lvalue, 해당 위치를 평가하여 발생하는 값이 호출됩니다. rvalue. C ++ 표준 (lvalue-to-rvalue 변환에 대한 이야기)에 따르면 다음과 같습니다.

4.1/2 : LValue로 표시된 물체에 포함 된 값은 rvalue 결과입니다.

결론

위의 의미론을 사용하면 이제 이유가 분명합니다. i++ LValue가 아니라 rvalue입니다. 반환 된 표현이 있지 않기 때문입니다 i 더 이상 (증가했습니다!), 그것은 단지 관심을 가질 수있는 가치 일뿐입니다. 반환 된 해당 값을 수정합니다 i++ 우리는 그 값을 다시 읽을 수있는 위치가 없기 때문에 의미가 없습니다. 따라서 표준은 그것이 rvalue라고 말하며, 따라서 참조에 대해서만 바인딩 할 수 있습니다.

그러나, 구조에서, 표현은에 의해 반환되었다 ++i 위치 (lvalue)입니다 i. IN과 같이 LValue-to-RValue 변환을 자극합니다 int a = ++i; 그 가치를 읽을 것입니다. 또는 참조 지점을 만들고 나중에 값을 읽을 수 있습니다. int &a = ++i;.

또한 rvalues가 생성되는 다른 경우에도 주목하십시오. 예를 들어, 모든 임시는 rvalues, 이진/단지 + 및 마이너스의 결과 및 참조가 아닌 모든 반환 값 표현입니다. 이러한 모든 표현식은 이름이 지정된 객체에 있지 않지만 오히려 값만 가지고 있습니다. 물론 이러한 값은 일정하지 않은 객체에 의해 백업 될 수 있습니다.

다음 C ++ 버전에는 소위가 포함됩니다 rvalue references 비록 그들이 정식을 가리키더라도 rvalue에 결합 할 수 있습니다. 이론적 근거는 익명의 대상에서 자원을 "훔치기"하고 사본을 피할 수 있어야합니다. Prefix ++를 과부하 한 클래스 타입을 가정합니다 (반환 Object&) 및 postfix ++ (반환 Object), 다음은 사본을 먼저 일으키고 두 번째 경우에는 rvalue에서 자원을 훔칠 것입니다.

Object o1(++a); // lvalue => can't steal. It will deep copy.
Object o2(a++); // rvalue => steal resources (like just swapping pointers)

다른 팁

다른 사람들은 우편과 사전 증가 사이의 기능적 차이를 해결했습니다.

그까지 lvalue 걱정, i++ 변수를 참조하지 않기 때문에 할당 할 수 없습니다. 계산 된 값을 나타냅니다.

과제 측면에서 다음 둘 다 같은 방식으로 의미가 없습니다.

i++   = 5;
i + 0 = 5;

사전 증가는 임시 사본이 아닌 증분 변수에 대한 참조를 반환하기 때문에 ++i LValue입니다.

성능 이유를 선호하는 것은 INT보다 약간 더 헤비급 일 수있는 반복자 객체 (예 : STL의)와 같은 것을 증가시킬 때 특히 좋은 아이디어가됩니다.

많은 사람들이 ++i LValue이지만 그렇지 않습니다 ,에서와 같이, C ++ 표준위원회는 특히 C가 LValues로 허용하지 않는다는 사실에 비추어이 기능을 제시 했습니까? 에서 comp.std.c ++에 대한이 토론, 주소를 가져 가거나 참조에 할당 할 수있는 것으로 보입니다. Christian Bau의 게시물에서 발췌 한 코드 샘플 :

   int i;
   extern void f (int* p);
   extern void g (int& p);

   f (&++i);   /* Would be illegal C, but C programmers
                  havent missed this feature */
   g (++i);    /* C++ programmers would like this to be legal */
   g (i++);    /* Not legal C++, and it would be difficult to
                  give this meaningful semantics */

그건 그렇고, 만약 i 내장 유형이 된 다음 다음과 같은 과제 명세서입니다. ++i = 10 부르다 정의되지 않은 행동, 왜냐하면 i 시퀀스 지점 사이에서 두 번 수정됩니다.

컴파일하려고 할 때 LValue 오류가 발생합니다.

i++ = 2;

그러나 내가 그것을 바꿀 때는 아닙니다

++i = 2;

접두사 연산자 (++ i)가 i의 값을 변경 한 다음 i를 반환하여 여전히 할당 할 수 있기 때문입니다. PostFix 연산자 (i ++)는 i의 값을 변경하지만 이전의 임시 사본을 반환합니다. , 과제 연산자가 수정할 수 없습니다.


원래 질문에 대한 답변:

For Loop과 같이 자체적으로 증분 연산자를 진술서에 사용하는 것에 대해 이야기하고 있다면 실제로는 차이가 없습니다. 사전 승인은 더 효율적인 것으로 보입니다. 우세는 자체적으로 증가하고 임시 가치를 반환해야하지만 컴파일러는 이러한 차이를 최적화 할 것입니다.

for(int i=0; i<limit; i++)
...

와 같다

for(int i=0; i<limit; ++i)
...

더 큰 문의 일부로 작업의 반환 값을 사용할 때 상황이 조금 더 복잡해집니다.

두 가지 간단한 진술조차도

int i = 0;
int a = i++;

그리고

int i = 0;
int a = ++i;

다르다. 다중 공급기 문의 일부로 사용하기로 선택한 증분 연산자는 의도 된 동작이 무엇인지에 따라 다릅니다. 요컨대, 당신은 하나만 선택할 수 없습니다. 둘 다 이해해야합니다.

포드 사전 증분 :

사전 점수는 표현 전에 물체가 증가한 것처럼 작용하고 마치 마치 마치이 표현에서 사용할 수 있어야합니다. 따라서 C ++ 표준 COMITEE는 L- 값으로도 사용할 수 있다고 결정했습니다.

포드 포스트 증분 :

점수 후 인상은 POD 객체를 증가시키고 표현에 사용하기 위해 사본을 반환해야합니다 (N2521 섹션 5.2.6 참조). 사본은 실제로 변수가 아니기 때문에 l- 값이되는 것은 의미가 없습니다.

사물:

객체의 사전 및 사후 증가는 언어의 구문 설탕이 객체에서 메소드를 호출하는 수단을 제공합니다. 따라서 기술적으로 객체는 언어의 표준 동작이 아니라 메소드 호출에 의해 부과 된 제한에 의해서만 제한됩니다.

이러한 객체의 동작을 POD 객체의 동작을 반영하는 것은 이러한 방법의 구현 자에게 달려 있습니다 (필요하지는 않지만 예상).

사전 증가 된 개체 :

여기서 요구 사항 (예상 동작)은 객체가 증가하고 (객체에 따라 의존적) 방법은 수정 가능한 값을 반환하고 증분이 발생한 후 원래 객체처럼 보이는 값을 반환합니다 (이 명령문 앞에 증분이 발생한 것처럼).

이를 수행하려면 Siple이며 메소드가 IT 스펠에 대한 참조를 반환하면됩니다. 참조는 l- 값이므로 예상대로 작동합니다.

점유 후 물체 :

여기서 요구 사항 (예상되는 동작)은 객체가 예비 점수와 같은 방식으로 증가하고 반환 된 값은 기존 값처럼 보이고 안정성이 없다는 것입니다 (따라서 l- 값처럼 행동하지 않음). .

측정 불가능 :
이렇게하려면 객체를 반환해야합니다. 객체가 표현식 내에서 사용되는 경우 임시 변수로 구성됩니다. 임시 변수는 const이므로 검증 할 수없고 예상대로 작동합니다.

오래된 가치처럼 보입니다.
이것은 수정을하기 전에 원본 (아마도 사본 생성자를 사용)의 사본을 만들어 단순히 달성됩니다. 사본은 딥 카피 여야합니다. 그렇지 않으면 원본에 대한 변경 사항은 사본에 영향을 미치므로 상태는 객체를 사용하여 표현식과 관련하여 변경됩니다.

사전 증가와 같은 방식으로 :
동일한 동작을 얻을 수 있도록 사전 증가 측면에서 사후 증분을 구현하는 것이 가장 좋습니다.

class Node // Simple Example
{
     /*
      * Pre-Increment:
      * To make the result non-mutable return an object
      */
     Node operator++(int)
     {
         Node result(*this);   // Make a copy
         operator++();         // Define Post increment in terms of Pre-Increment

         return result;        // return the copy (which looks like the original)
     }

     /*
      * Post-Increment:
      * To make the result an l-value return a reference to this object
      */
     Node& operator++()
     {
         /*
          * Update the state appropriatetly */
         return *this;
     }
};

lvalue에 관해

  • ~ 안에 C (예 : Perl), 어느 것도 아니다 ++i ...도 아니다 i++ LValues입니다.

  • ~ 안에 C++, i++ LValue는 아니지만 ++i 이다.

    ++i 동일합니다 i += 1, 이는 동등합니다 i = i + 1.
    결과적으로 우리는 여전히 같은 개체를 다루고 있습니다. i.
    다음과 같이 볼 수 있습니다.

    int i = 0;
    ++i = 3;  
    // is understood as
    i = i + 1;  // i now equals 1
    i = 3;
    

    i++ 반면에 다음과 같이 볼 수 있습니다.
    먼저 우리는 사용합니다 i, 그런 다음 물체 i.

    int i = 0;
    i++ = 3;  
    // would be understood as 
    0 = 3  // Wrong!
    i = i + 1;
    

(편집 : 얼룩이있는 첫 번째 동점 후 업데이트).

주요 차이점은 i ++가 사전 증가 된 값을 반환하는 반면 ++ i는 사후 증가 된 값을 반환한다는 것입니다. I ++를 사용해야 할 매우 설득력있는 이유가 없다면 일반적으로 ++를 사용합니다. 하다 사전 증가 값이 필요합니다.

IMHO '++ i'양식을 사용하는 것이 좋습니다. 정수 나 다른 포드를 비교할 때 사전 및 사후 분쟁의 차이는 실제로 측정 할 수 없지만 'I ++'를 사용하면 객체가 상당히 비싸면 상당한 성능 영향을 나타낼 수 있습니다. 복사하거나 자주 증가합니다.

그건 그렇고 - 동일한 문서에서 동일한 변수에서 여러 증분 연산자를 사용하지 마십시오. 당신은 적어도 C에서 "시퀀스 포인트는 어디에 있습니까?"와 정의되지 않은 운영 순서를 혼란스럽게합니다. 나는 그 중 일부가 Java nd C#에서 정리되었다고 생각합니다.

어쩌면 이것은 개수 후 구현되는 방식과 관련이있을 수 있습니다. 아마도 그것은 다음과 같은 것일 것입니다.

  • 메모리에서 원래 값의 사본을 만듭니다
  • 원래 변수를 증가시킵니다
  • 사본을 반환하십시오

사본은 변수 나 동적으로 할당 된 메모리에 대한 참조가 아니기 때문에 l- 값이 될 수 없습니다.

컴파일러는이 표현을 어떻게 번역합니까? a++

우리는 우리가 반환하고 싶다는 것을 알고 있습니다 개척되지 않았습니다 버전 a, a의 이전 버전 ~ 전에 증분. 우리는 또한 증가하고 싶습니다 a 부작용으로. 다시 말해, 우리는 이전 버전을 반환하고 있습니다. a, 더 이상 현재 상태를 나타내지 않습니다 a, 더 이상 변수 자체가 아닙니다.

반환 된 값은 사본입니다 a a에 배치됩니다 등록하다. 그런 다음 변수가 증가합니다. 그래서 여기에서 당신은 변수 자체를 반환하지 않지만 사본을 반환하고 있습니다. 분리된 실재! 이 사본은 일시적으로 레지스터 내부에 저장된 다음 반환됩니다. C ++의 LValue는 식별 가능한 위치가있는 객체임을 상기하십시오. 메모리에서. 그러나 사본은 내부에 저장됩니다 메모리가 아닌 CPU의 레지스터. 모든 rvalues는 식별 가능한 위치가없는 개체입니다. 메모리에서. 그것은 이전 버전의 사본을 설명합니다. a rvalue는 레지스터에 일시적으로 저장되기 때문에 rvalue입니다. 일반적으로 모든 사본, 임시 가치 또는 긴 표현 결과와 같은 결과 (5 + a) * b 레지스터에 저장된 다음 LValue 인 변수에 할당됩니다.

PostFix 연산자는 원래 값을 레지스터에 저장하여 결과적으로 인정되지 않은 값을 반환 할 수 있도록해야합니다. 다음 코드를 고려하십시오.

for (int i = 0; i != 5; i++) {...}

이 for-loop는 최대 5 개까지 계산하지만 i++ 가장 흥미로운 부분입니다. 실제로 1의 두 가지 지침입니다. 먼저 우리는 이전 가치를 이동해야합니다. i 레지스터로, 우리는 증가합니다 i. 의사 조립 코드 :

mov i, eax
inc i

eax 등록은 이제 이전 버전을 포함합니다 i 사본으로. 변수 인 경우 i 기본 메모리에 상주하면 CPU가 메인 메모리에서 사본을 가져 와서 레지스터로 이동하는 데 많은 시간이 걸릴 수 있습니다. 그것은 일반적으로 최신 컴퓨터 시스템의 경우 매우 빠르지 만, 루프가 10 만 번 반복되면 모든 추가 작업이 추가되기 시작합니다! 그것은 중요한 성과 페널티가 될 것입니다.

최신 컴파일러는 일반적으로 정수 및 포인터 유형을 위해이 추가 작업을 최적화 할 수있을 정도로 똑똑합니다. 보다 복잡한 반복자 유형 또는 클래스 유형의 경우이 추가 작업이 더 많은 비용이들 수 있습니다.

접두사 증가는 어떻습니까? ++a?

우리는 반환하고 싶습니다 증분 버전 a, 새 버전 a ~ 후에 증분. 새 버전 a 현재 상태를 나타냅니다 a, 변수 자체이기 때문에.

첫 번째 a 증가합니다. 업데이트 된 버전을 얻고 싶기 때문에 a, 왜 그냥 반환하지 마십시오 변하기 쉬운 a 그 자체? RValue를 생성하기 위해 레지스터에 임시 사본을 만들 필요가 없습니다. 불필요한 추가 작업이 필요합니다. 따라서 변수 자체를 LValue로 반환합니다.

우리가 비밀 값이 필요하지 않은 경우, 이전 버전을 복사하는 추가 작업이 필요하지 않습니다. a PostFix 연산자가 수행하는 레지스터로. 그렇기 때문에 사용해야합니다 a++ 만약 너라면 진짜 비수분되지 않은 가치를 반환해야합니다. 다른 모든 목적으로 사용하십시오 ++a. 접두사 버전을 습관적으로 사용하면 성능 차이가 중요한지 걱정할 필요가 없습니다.

사용의 또 다른 장점 ++a 프로그램의 의도를보다 직접적으로 표현한다는 것입니다. a! 그러나 내가 볼 때 a++ 다른 사람의 코드에서 나는 왜 이전 가치를 반환하고 싶어하는지 궁금합니다. 그것은 무엇입니까?

씨#:

public void test(int n)
{
  Console.WriteLine(n++);
  Console.WriteLine(++n);
}

/* Output:
n
n+2
*/
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top