6.4 — Operatory inkrementacji/dekrementacji i skutki uboczne

Inkrementacja i dekrementacja zmiennych

Inkrementacja (dodawanie 1 do) i zmniejszanie (odejmowanie 1 od) zmiennej są tak powszechne, że mają własne operatory.

OperatorSymbolFormularzOperacja
Zwiększenie prefiksu (wstępne zwiększanie)++++xZwiększanie x, następnie zwracanie x
Zmniejszanie przedrostka (wstępne zmniejszanie)––––xZmniejszanie x, następnie zwróć x
Przyrost postfiksowy (po zmniejszeniu)++x++Kopiuj x, następnie zwiększ x, następnie zwróć kopię
Zmniejszenie przyrostkowe (po zmniejszeniu)––x––Kopiuj x, następnie zmniejsz x, a następnie zwróć copy

Zauważ, że istnieją dwie wersje każdego operatora — wersja przedrostkowa (gdzie operator występuje przed operandem) i wersja postfiksowa (gdzie operator występuje po operandzie).

Inkrementacja i dekrementacja przedrostka

Operatory zwiększania/zmniejszania przedrostka są bardzo proste. Najpierw operand jest zwiększany lub zmniejszany, a następnie wyrażenie zwraca wartość argumentu. Na przykład:

#include <iostream>

int main()
{
    int x { 5 };
    int y { ++x }; // x is incremented to 6, x is evaluated to the value 6, and 6 is assigned to y

    std::cout << x << ' ' << y << '\n';
    return 0;
}

Wypisuje:

6 6

Inkrementacja i dekrementacja przyrostkowa

Operatory inkrementacji/zmniejszenia przyrostkowego są trudniejsze. Najpierw tworzona jest kopia operandu. Następnie operand (nie kopia) jest zwiększany lub zmniejszany. Na koniec oceniana jest kopia (a nie oryginał). Na przykład:

#include <iostream>

int main()
{
    int x { 5 };
    int y { x++ }; // x is incremented to 6, copy of original x is evaluated to the value 5, and 5 is assigned to y

    std::cout << x << ' ' << y << '\n';
    return 0;
}

Wypisuje:

6 5

Przyjrzyjmy się bardziej szczegółowo, jak działa ta linia 6. Najpierw tworzona jest tymczasowa kopia x , która zaczyna się od tej samej wartości co x (5). Następnie wartość rzeczywista x jest zwiększana od 5 Do 6. Następnie kopia x (która nadal ma wartość 5) jest zwracana i przydzielana do y. Następnie kopia tymczasowa jest odrzucana.

W rezultacie y kończy się wartością 5 (wartość wstępnie zwiększona) i x kończy się wartością 6 (wartość zwiększana po zwiększeniu).

Zauważ, że wersja postfiksowa wymaga znacznie większej liczby kroków i dlatego może nie być tak samo wydajny jak wersja z przedrostkiem.

Więcej przykładów

Oto kolejny przykład pokazujący różnicę między wersją z przedrostkiem i postfiksem:

#include <iostream>

int main()
{
    int x { 5 };
    int y { 5 };
    std::cout << x << ' ' << y << '\n';
    std::cout << ++x << ' ' << --y << '\n'; // prefix
    std::cout << x << ' ' << y << '\n';
    std::cout << x++ << ' ' << y-- << '\n'; // postfix
    std::cout << x << ' ' << y << '\n';

    return 0;
}

To daje wynik:

5 5
6 4
6 4
6 4
7 3

W ósmej linii wykonujemy zwiększanie i zmniejszanie przedrostka. W tej linii x i y są zwiększane/zmniejszane przed ich wartości są wysyłane do std::cout, więc ich zaktualizowane wartości są odzwierciedlane przez std::cout.

W 10. linii wykonujemy przyrostek inkrementacji i dekrementacji. W tej linii kopia x i y (z wartościami wstępnie zwiększonymi i wstępnie zmniejszonymi) jest wysyłana do std::cout, więc nie widzimy tutaj odzwierciedlonych przyrostów i ubytków. Zmiany te pojawiają się dopiero w następnym wierszu, kiedy x i y są ponownie oceniane.

Kiedy używać przedrostka a postfiksu

W wielu przypadkach operatory przedrostka i postfiksu powodują to samo zachowanie:

int main()
{
    int x { 0 };
    ++x; // increments x to 1
    x++; // increments x to 2

    return 0;
}

W przypadkach, gdy kod może zostać napisany z użyciem przedrostka lub postfiksu, preferuj wersje z przedrostkiem, ponieważ są one zazwyczaj bardziej wydajne i rzadziej powodują niespodzianek.

Najlepsza praktyka

Preferuj wersje z przedrostkiem, ponieważ są one bardziej wydajne i rzadziej powodują niespodzianki.

Użyj wersji z postfiksem, jeśli w ten sposób tworzysz znacznie bardziej zwięzły i zrozumiały kod niż równoważny kod napisany przy użyciu wersji z przedrostkiem.

Efekty uboczne

Mówi się, że funkcja lub wyrażenie ma stronę efekt jeśli ma jakiś zauważalny efekt wykraczający poza wygenerowanie wartości zwracanej.

Typowe przykłady efektów ubocznych obejmują zmianę wartości obiektów, wprowadzanie lub wyprowadzanie danych lub aktualizację graficznego interfejsu użytkownika (np. włączanie lub wyłączanie przycisku).

W większości przypadków efekty uboczne są przydatne:

x = 5; // the assignment operator has side effect of changing value of x
++x; // operator++ has side effect of incrementing x
std::cout << x; // operator<< has side effect of modifying the state of the console

Operator przypisania w powyższym przykładzie ma efekt uboczny w postaci zmiany wartości z x na stałe. Nawet po zakończeniu wykonywania instrukcji, x będzie nadal mieć wartość 5. Podobnie w przypadku operatora++, wartość x jest zmieniana nawet po zakończeniu oceniania instrukcji. Wyjście x ma także efekt uboczny w postaci modyfikacji stanu konsoli, ponieważ możesz teraz zobaczyć wartość x wydrukowaną na konsoli.

Kluczowa informacja

Operatory przypisania, operator przedrostka i operator postfiksu mają skutki uboczne, które trwale zmieniają wartość obiektu.
Inne operatory (takie jak operatory arytmetyczne) zwracają wartość i nie modyfikują ich operandy.

Efekty uboczne mogą powodować problemy z kolejnością ewaluacji

W niektórych przypadkach skutki uboczne mogą prowadzić do problemów z porządkiem ewaluacji. Na przykład:

#include <iostream>

int add(int x, int y)
{
    return x + y;
}

int main()
{
    int x { 5 };
    int value{ add(x, ++x) }; // undefined behavior: is this 5 + 6, or 6 + 6?
    // It depends on what order your compiler evaluates the function arguments in

    std::cout << value << '\n'; // value could be 11 or 12, depending on how the above line evaluates!

    return 0;
}

Standard C++ nie definiuje kolejności, w jakiej oceniane są argumenty funkcji. Jeśli lewy argument zostanie oceniony jako pierwszy, stanie się to wywołaniem metody add(5, 6), co równa się 11. Jeśli jako pierwszy zostanie obliczony prawy argument, stanie się to wywołaniem add(6, 6), co równa się 12! Należy zauważyć, że jest to problem tylko dlatego, że jeden z argumentów funkcji add() ma skutek uboczny.

Na marginesie…

Standard C++ celowo nie definiuje tych rzeczy, aby kompilatory mogły zrobić wszystko, co jest najbardziej naturalne (a tym samym najbardziej wydajne) dla danej architektury.

Kolejność skutków ubocznych

W wielu przypadkach C++ nie określa również, kiedy należy zastosować efekty uboczne operatorów. Może to prowadzić do niezdefiniowanego zachowania w przypadkach, gdy obiekt z zastosowanym skutkiem ubocznym zostanie użyty więcej niż raz w tej samej instrukcji.

Na przykład wyrażenie x + ++x jest zachowaniem nieokreślonym. Kiedy x jest inicjowany do 1, Visual Studio i GCC oceniają to jako 2 + 2, a Clang ocenia to jako 1 + 2! Wynika to z różnic w stosowaniu przez kompilatory efektu ubocznego inkrementacji x.

Nawet jeśli standard C++ wyraźnie wyjaśnia, jak należy oceniać pewne rzeczy, historycznie rzecz biorąc, był to obszar, w którym występowało wiele błędów kompilatora. Problemów tych można ogólnie uniknąć by zapewnić, że zmienna, do której zastosowano efekt uboczny, zostanie użyta w danej instrukcji nie więcej niż raz.

Ostrzeżenie

C++ nie definiuje kolejności oceniania argumentów funkcji ani operandów operatorów.

Ostrzeżenie

Nie używaj w danej instrukcji zmiennej, do której zastosowano efekt uboczny więcej niż raz. Jeśli to zrobisz, wynik może być niezdefiniowany.

Jeden wyjątek dotyczy prostych wyrażeń przypisania, takich jak x = x + y (co jest zasadniczo równoważne x += y).

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
355 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze