Dlaczego ++i jest uważany za wartość l, ale i++ nie jest?

Dlaczego ++i jest wartością l, a i++ nie?

Author: yesraaj, 2008-12-16

12 answers

Cóż, jak już inna odpowiedź wskazała, powodem, dla którego ++i jest lvalue, jest przekazanie go do referencji.

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
W przeciwieństwie do tego, że nie jest on w pełni zgodny z regułą const, nie jest on w pełni zgodny z regułą const.]}
void taking_refc(int const& v);
taking_refc(10); // valid, 10 is an rvalue though!

Dlaczego w ogóle Wprowadzamy wartość rvalue. Cóż, terminy te pojawiają się przy budowaniu reguł językowych dla tych dwóch sytuacji:

  • chcemy mieć wartość lokalizatora. To będzie reprezentować lokalizacja, która zawiera wartość, którą można odczytać.
  • chcemy reprezentować wartość wyrażenia.

Powyższe dwa punkty pochodzą ze standardu C99, który zawiera ten ładny przypis całkiem pomocny: {]}

[nazwa" lvalue " pochodzi oryginalnie z wyrażenia przypisania E1 = E2, w którym lewy operand E1 jest wymagane jest (modyfikowalne) lvalue. Być może lepiej jest rozważyć jako reprezentowanie obiektu " lokalizator wartość". Co? jest czasami nazywany "rvalue" jest w tym międzynarodowym Standard opisany jako " wartość wyrażenie". ]

Wartość lokalizatora nazywa się lvalue , podczas gdy wartość wynikająca z oceny tej lokalizacji nazywa się rvalue. Jest to zgodne ze standardem C++ (mówiącym o konwersji lvalue-to-rvalue):

4.1/2: wartość zawarta w obiekcie wskazywany przez lvalue jest wartością rvalue wynik.

Podsumowanie

Używając powyższej semantyki, teraz jest jasne, dlaczego i++ nie jest lvalue, ale rvalue. Ponieważ zwrócone wyrażenie nie znajduje się już w i (jest inkrementowane!), to tylko wartość, która może być interesująca. Modyfikowanie tej wartości zwracanej przez i++ nie miałoby sensu, ponieważ nie mamy lokalizacji, z której moglibyśmy odczytać tę wartość ponownie. Tak więc Standard mówi, że jest to wartość R, a więc może wiązać się tylko z reference-to-const.

W constraście wyrażenie zwracane przez ++i jest lokacją (lvalue) i. Wywołanie konwersji lvalue-to-rvalue, jak w int a = ++i; spowoduje odczytanie z niej wartości. Alternatywnie, możemy zrobić punkt odniesienia do niego i odczytać wartość później: int &a = ++i;.

Zwróć również uwagę na inne sytuacje, w których generowane są wartości R. Na przykład, wszystkie wyrażenia tymczasowe są wartościami r, wynikiem binarnych / uniarnych + i minus oraz wszystkimi wyrażeniami zwracającymi wartość, które nie są referencjami. Wszystkie wyrażenia te nie znajdują się w nazwanym obiekcie, lecz zawierają jedynie wartości. Wartości te mogą być oczywiście wspierane przez obiekty, które nie są stałe.

Następna wersja C++ będzie zawierać tak zwane rvalue references, które, mimo że wskazują na nonkonst, mogą wiązać się z wartością r. Uzasadnieniem jest możliwość "wykradania" zasobów z tych anonimowych obiektów i unikania kopiowania. Zakładając, że typ klasy ma przeciążony prefiks ++ (Return Object&) i postfix ++ (Return Object), następujące wywołanie spowoduje skopiowanie jako pierwsze, a w drugim przypadku wykradnie zasoby z wartości R:

Object o1(++a); // lvalue => can't steal. It will deep copy.
Object o2(a++); // rvalue => steal resources (like just swapping pointers)
 23
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-17 10:42:14

Inni ludzie zajęli się funkcjonalną różnicą między post i pre inkrement.

Jeśli chodzi o to, że jest lvalue , i++ nie może być przypisany do, ponieważ nie odnosi się do zmiennej. Odnosi się do obliczonej wartości.

Jeśli chodzi o przypisanie, oba poniższe nie mają sensu w ten sam sposób:

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

Ponieważ pre-increment zwraca odniesienie do zmiennej inkrementowanej, a nie tymczasową kopię, ++i jest lvalue.

Preferowanie pre-increment ze względów wydajnościowych staje się szczególnie dobrym pomysłem, gdy zwiększasz coś w rodzaju obiektu iteracyjnego (np. w STL), który może być dobrym nieco cięższym niż int.

 31
Author: mackenir,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-16 15:13:23

Wydaje się, że wiele osób wyjaśnia, w jaki sposób ++i jest lvalue, ale nie Dlaczego , jak w, dlaczego Komitet Normalizacyjny C++ umieścił tę funkcję, zwłaszcza w świetle faktu, że C nie pozwala na to ani jako lvalue. Z tej dyskusji na komp.choroby weneryczne.c++, wydaje się, że jest to więc można wziąć jego adres lub przypisać do odniesienia. Przykład kodu zaczerpnięty z postu Christiana 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 */

Przy okazji, jeśli i stanie się Wbudowany typ, a następnie polecenia przypisania, takie jak ++i = 10 wywołują niezdefiniowane zachowanie , ponieważ i jest modyfikowane dwukrotnie między punktami sekwencji.

 10
Author: Nietzche-jou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-16 15:42:24

Dostaję błąd lvalue podczas próby kompilacji

i++ = 2;

Ale nie kiedy zmienię na

++i = 2;

Dzieje się tak dlatego, że operator przedrostka (++i) zmienia wartość w i, a następnie zwraca i, więc nadal może być przypisany do. Operator postfix (i++) zmienia wartość w i, ale zwraca tymczasową kopię starej wartości , której operator przypisania nie może zmienić.


Odpowiedź na pierwotne pytanie :

Jeśli mówisz o używanie operatorów przyrostowych w instrukcji samodzielnie, jak w pętli for, naprawdę nie robi różnicy. Preincrement wydaje się być bardziej wydajny, ponieważ postincrement musi się zwiększać i zwracać wartość tymczasową, ale kompilator zoptymalizuje tę różnicę.

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

Jest tym samym co

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

Sprawy stają się nieco bardziej skomplikowane, gdy używasz wartości zwracanej operacji jako części większej instrukcji.

Nawet dwa proste statements

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

I

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

Są różne. To, który operator przyrostu zostanie użyty jako część instrukcji dla wielu operatorów, zależy od zamierzonego zachowania. Krótko mówiąc, Nie, Nie możesz po prostu wybrać jednego. Musisz zrozumieć jedno i drugie.

 5
Author: Bill the Lizard,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-16 15:54:58

POD przedrostek:

Pre-inkrement powinien działać tak, jakby obiekt został inkrementowany przed wyrażeniem i być użyteczny w tym wyrażeniu tak, jakby do tego doszło. W związku z tym standardy C++ zdecydowały, że może być również używana jako wartość L.

POD Post increment:

Post-increment powinien zwiększyć obiekt POD i zwrócić kopię do użycia w wyrażeniu(Patrz n2521 sekcja 5.2.6). Ponieważ kopia nie jest w rzeczywistości zmienną, co czyni ją wartością l nie czyni żadnej sens.

Obiekty:

Pre I Post increment na obiektach jest tylko składniowym cukrem języka, który umożliwia wywołanie metod na obiekcie. Tak więc technicznie obiekty nie są ograniczone standardowym zachowaniem języka, ale jedynie ograniczeniami narzuconymi przez wywołania metod.

To do implementatora tych metod należy, aby zachowanie tych obiektów odzwierciedlało zachowanie obiektów POD (nie jest wymagane, ale oczekiwane).

Obiekty Pre-increment:

Wymóg (oczekiwane zachowanie) jest taki, że obiekty są inkrementowane (czyli zależne od obiektu), a Metoda zwraca wartość, która jest możliwa do modyfikacji i wygląda jak oryginalny obiekt po przyroście (tak, jakby przyrost miał miejsce przed tą instrukcją).

Aby to zrobić jest siple i wymaga tylko, aby metoda zwróciła odniesienie do it-self. Referencja jest wartością l i dlatego będzie zachowywać się zgodnie z oczekiwaniami.

Obiekty Post-increment:

Wymóg (oczekiwane zachowanie) jest taki, że obiekt jest inkrementowany (tak samo jak pre-increment) , a zwrócona wartość wygląda jak Stara wartość i nie jest zmienna(tak, że nie zachowuje się jak wartość l).

Nie-Mutable:
aby to zrobić należy zwrócić obiekt. Jeśli obiekt jest używany w wyrażeniu, zostanie skopiowany do zmiennej tymczasowej. Zmienne tymczasowe są const, a więc nie będą mutowalne i zachowają się jak oczekiwane.

Wygląda jak Stara wartość:
jest to po prostu osiągnięte przez utworzenie kopii oryginału (prawdopodobnie za pomocą konstruktora kopiującego) przed dokonaniem jakichkolwiek modyfikacji. Kopia powinna być głęboką kopią, w przeciwnym razie wszelkie zmiany w oryginale wpłyną na kopię i tym samym stan zmieni się w stosunku do wyrażenia używającego obiektu.

W taki sam sposób jak pre-increment:
prawdopodobnie najlepiej jest zaimplementować post increment w kategoriach pre-increment, aby uzyskać to samo zachowanie.

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;
     }
};
 4
Author: Loki Astari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-16 19:44:24

Regarding LValue

  • W C (i na przykład w Perlu), ani ++i nor i++ are LValues.

  • W C++, i++ is not and LValue but ++i is.

    ++i jest równoważne i += 1, co jest równoważne i = i + 1.
    W rezultacie wciąż mamy do czynienia z tym samym obiektem i.
    Może być postrzegany jako:

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

    i++ z drugiej strony może być postrzegany jako:
    najpierw używamy wartość z i, następnie zwiększ obiekt i.

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

(edit: updated after a blocked first-attempt).

 1
Author: Renaud Bompuis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-17 03:08:50

Główna różnica polega na tym, że i++ zwraca wartość przed przyrostem, podczas gdy ++i zwraca wartość po przyrostie. Zwykle używam ++i, chyba że mam bardzo przekonujący powód, aby używać i++ - mianowicie, jeśli naprawdę do potrzebuję wartości pre-increment.

IMHO dobrze jest używać formy '++i'. Podczas gdy różnica między pre-i post-increment nie jest tak naprawdę mierzalna podczas porównywania liczb całkowitych lub innych PODs, dodatkowa kopia obiektu, którą musisz wykonać i zwrócić, gdy używasz 'i++' może mieć znaczący wpływ na wydajność, jeśli obiekt jest albo dość drogi w kopiowaniu,albo często zwiększany.

 0
Author: Timo Geusch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-16 14:48:40

Przy okazji-unikaj używania wielu operatorów przyrostowych na tej samej zmiennej w tej samej instrukcji. Dostajesz się do bałaganu "gdzie są punkty sekwencji" i nieokreślona kolejność operacji, przynajmniej w C. myślę, że część z tego została wyczyszczona w Javie i C#.

 0
Author: Paul Tomblin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-16 14:50:13

Może to ma coś wspólnego ze sposobem implementacji post-inkrementacji. Może to coś takiego:

  • Tworzenie kopii oryginalnej wartości w pamięci
  • zwiększ oryginalną zmienną
  • zwróć kopię

Ponieważ kopia nie jest zmienną ani odniesieniem do dynamicznie przydzielanej pamięci, nie może być wartością l.

 0
Author: pyon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-16 19:36:19

Jak kompilator tłumaczy to wyrażenie? a++

Wiemy, że chcemy zwrócić wersję , starą wersję przed przyrostem. Chcemy również zwiększyć a jako efekt uboczny. Innymi słowy, zwracamy starą wersję a, która nie reprezentuje już bieżącego stanu a, nie jest już samą zmienną.

Zwracana wartość jest kopią a umieszczoną w Zarejestruj się . Następnie zmienna jest zwiększana. Więc tutaj nie zwracasz samej zmiennej, ale zwracasz kopię, która jest oddzielnym bytem! Kopia ta jest tymczasowo przechowywana w rejestrze, a następnie jest zwracana. Przypomnijmy, że lvalue w C++ jest obiektem, który ma identyfikowalną lokalizację w pamięci. Ale kopia jest przechowywana w rejestrze w procesorze, a nie w pamięci. wszystkie wartości R są obiektami, które nie mają identyfikowalnej lokalizacji w pamięci . To wyjaśnia, dlaczego Kopia starej wersji a jest wartością r, ponieważ jest tymczasowo przechowywana w rejestrze. Ogólnie rzecz biorąc, wszelkie kopie, wartości tymczasowe lub wyniki długich wyrażeń, takich jak (5 + a) * b, są przechowywane w rejestrach, a następnie są przypisywane do zmiennej, która jest lvalue.

Operator postfix musi zapisać oryginalną wartość w rejestrze, aby mógł zwrócić wartość nieujawnioną jako wynik. Rozważ następujące kwestie kod:

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

Ta pętla for liczy do pięciu, ale i++ jest najciekawszą częścią. W rzeczywistości są to dwie instrukcje w 1. Najpierw musimy przenieść starą wartość i do rejestru, a następnie zwiększyć i. W kodzie pseudo-assembly:

mov i, eax
inc i

eax register zawiera teraz starą wersję i jako kopię. Jeśli zmienna i znajduje się w pamięci głównej, procesor może zająć dużo czasu, aby pobrać kopię z pamięci głównej i przenieść ją do kasy. Zwykle jest to bardzo szybkie w przypadku nowoczesnych systemów komputerowych, ale jeśli twoja pętla for powtarza się sto tysięcy razy, wszystkie te dodatkowe operacje zaczynają się sumować! Byłaby to znacząca kara za występ.

Nowoczesne Kompilatory są zwykle wystarczająco inteligentne, aby zoptymalizować tę dodatkową pracę dla typów liczb całkowitych i wskaźników. W przypadku bardziej skomplikowanych typów iteratorów, a może typów klas, ta dodatkowa praca może być bardziej kosztowna.

A co z przyrostkiem przedrostka ++a?

Chcemy zwrócić zwiększoną wersję a, nową wersję a po przyrost. Nowa wersja a przedstawia bieżący stan a, ponieważ jest samą zmienną.

Pierwszy a jest zwiększony. Ponieważ chcemy uzyskać zaktualizowaną wersję a, Dlaczego po prostu nie zwrócić zmiennej a samej? Nie musimy tworzyć tymczasowej kopii w rejestrze, aby wygenerować wartość R. To wymagałoby niepotrzebna dodatkowa praca. Więc po prostu zwracamy samą zmienną jako lvalue.

Jeśli nie potrzebujemy wartości nieujemnej, nie ma potrzeby dodatkowej pracy kopiowania starej wersji a do rejestru, która jest wykonywana przez operatora postfix. Dlatego powinieneś używać a++tylko wtedy, gdy naprawdę musisz zwrócić wartość nieujawnioną. Do wszystkich innych celów wystarczy użyć ++a. Przyzwyczajając się do wersji z prefiksem, nie musimy się martwić o to, czy wydajność różnica ma znaczenie.

Kolejną zaletą używania ++a jest to, że wyraża on intencje programu bardziej bezpośrednio: chcę tylko zwiększyć a! Jednak gdy widzę a++ w cudzym kodzie, zastanawiam się, dlaczego chce zwrócić starą wartość? Do czego to służy?

 0
Author: Galaxy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-19 18:20:13

Przykład:

var i = 1;
var j = i++;

// i = 2, j = 1

I

var i = 1;
var j = ++i;

// i = 2, j = 2
 -2
Author: weiran,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-16 14:48:50

C#:

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

/* Output:
n
n+2
*/
 -4
Author: LeppyR64,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-12-16 16:38:50