Kolejnym łatwym zagadnieniem, które powinniśmy znać ze szkoły podstawowej jest porównywanie wartości. Zacznijmy najpierw od poznania operatorów porównania (nazywanych również operatorami relacji):
Wynikiem operacji porównania jest prawda albo fałsz, czyli warunek jest spełniony albo nie. Słowa kluczowe, które opisują te stany w języku C++ to odpowiednio
true i
false. Tak samo jak w przypadku operacji arytmetycznych, operacje porównania możemy wykonywać bezpośrednio na wartościach jak i zmiennych. Przejdźmy teraz do przykładowego kodu źródłowego:
#include <iostream>
int main()
{
bool porownanie = 123 >= 321;
std::cout << "porownanie = " << porownanie << std::endl;
std::cout << "porownanie = " <<( 111 != 222 ) << std::endl;
std::cout << "porownanie = " <<( 777 == 777 ) << std::endl;
std::cout << "porownanie = " <<( 777 < 777 ) << std::endl;
return 0;
}
Powyższy program wyświetli nam następujący tekst:
porownanie = 0
porownanie = 1
porownanie = 1
porownanie = 0
Zauważ, że w przypadku gdy wartość logiczną zapisywaliśmy do zmiennej to nawiasy zaokrąglone nie były nam potrzebne. W przypadku, gdy chcemy wypisać wynik porównania na ekran za pomocą strumienia std::cout, są one konieczne. W przypadku braku nawiasów otrzymamy następujący komunikat:
error: invalid operands of types 'int' and '<unresolved overloaded function type>' to binary 'operator<<'
Błąd ten nie wynika z tego, że kompilator jest źle napisany tylko z faktu, że operatory mają różne priorytety. Operacja porównania posiada niższy priorytet niż operator <<, co prowadzi do błędu, który naprawić możemy poprzez wymuszenie kolejności wykonania operacji za pomocą zaokrąglonych nawiasów. Poniżej zamieszczam kod, który powinien Ci lepiej zobrazować opisany problem:
#include <iostream>
int main()
{
int zmienna = 5;
bool popatrzNaTo = 10 * 4 + zmienna / 2 <= zmienna * 8;
std::cout << "popatrzNaTo = " << popatrzNaTo << std::endl;
return 0;
}
Aby dokonać porównania dwóch wartości, należy najpierw wykonać wszystkie działania. Kompilator przekształci więc ten kod tak, aby najpierw zostały wykonane obliczenia lewej i prawej części nierówności, a następnie porówna wartości obu stron równania i zwróci interesujący nas wynik.
Co jest prawdą, a co fałszem?
Odejdźmy na chwilę od zagadnienia porównywania wartości. Wiemy, że istnieje coś takiego jak zmienna typu bool i przyjmuje ona dwie wartości:
true i
false. Wyświetlając jednak zawartość zmiennej
bool dostajemy wartość
0 lub
1. Nasuwa się więc następujące pytanie:
jak to jest z tym typem w rzeczywistości?. Aby to wyjaśnić posłużę się kolejnym przykładem:
#include <iostream>
int main()
{
bool eksperymentujemy = 12312;
std::cout << "eksperymentujemy = " << eksperymentujemy << std::endl;
eksperymentujemy = 0;
std::cout << "eksperymentujemy = " << eksperymentujemy << std::endl;
eksperymentujemy = - 123.0;
std::cout << "eksperymentujemy = " << eksperymentujemy << std::endl;
return 0;
}
Powyższy program wygląda na pierwszy rzut oka absurdalnie. Co więcej prawdopodobnie nie wiesz co się wyświetli na ekranie konsoli. Zobaczmy więc efekt działania programu:
eksperymentujemy = 1
eksperymentujemy = 0
eksperymentujemy = 1
Kolejne pytanie jakie już Ci się na język ciśnie to:
"Dlaczego otrzymałem takie wartości?!". Możesz zacząć się zastanawiać nad regułą kiedy się pojawia jedynka, a kiedy zero. Być może tworzysz teraz jakieś dziwne teorie kiedy jaką wartość otrzymamy, albo że trudno będzie zapamiętać kiedy jakie wartości się otrzymuje. Jeśli tak już zaczynasz myśleć - jesteś w błędzie. Reguła w C++ jest prosta:
jeśli wartość jest różna od zera to prawda - w przeciwnym wypadku fałsz.
Często popełniany błąd przez początkujących
Po krótkim wywodzie na temat wartości logicznych w C++ pora przejść do bardzo ważnego tematu, czyli do często popełnianych błędów przez początkujących programistów. Spójrzmy teraz na następujący program:
#include <iostream>
int main()
{
int xyz = 321;
bool wynik = xyz = 123;
std::cout << "xyz = " << xyz << std::endl;
std::cout << "wynik = " << wynik << std::endl;
return 0;
}
Powyższy program może na pierwszy rzut oka wyglądać prawidłowo. Kompilator grzecznie go skompiluje i program się uruchomi, jednak nie otrzymamy oczekiwanego wyniku. Na ekranie zastaniemy natomiast następujący tekst:
xyz = 123
wynik = 1
Zamiast operacji porównania została wykonana operacja przypisania. W konsekwencji kompilator najpierw przypisał wartość 123 do zmiennej o nazwie xyz, a następnie xyz przypisał do zmiennej wynik, którą uznał za prawdę, ponieważ wartość xyz jest różna od zera. Zauważmy też, że w powyższym programie stosunkowo łatwo było namierzyć ten błąd dlatego, że wartości wypisywaliśmy na ekran. Pisząc programy nie wypisujemy jednak prawie nigdy wyniku porównania tak więc pamiętaj, żeby zwracać
szczególną uwagę na wszystkie warunki, w których ma występować porównanie wartości
==. Nie jednemu programiście taki błąd krwi napsuł i nie raz patrzył on w kod szukając błędu wszędzie dookoła tylko nie w tej prostej operacji porównania.
Wielokrotne przypisanie
Skoro już użyliśmy w poprzednim programie 'niechcący' wielokrotnego przypisania, warto w tym miejscu wspomnieć co to jest i jak to działa. Wielokrotne przypisanie służy do przypisywania kilku zmiennym tej samej wartości. Przykładowo: chcemy mieć trzy wyniki obliczeń i wszystkie wyniki mają mieć początkową wartość równą 0. Przypisanie takie możemy wykonać na trzy sposoby. Pierwszym z nich jest przypisanie wartości odrazu przy tworzeniu zmiennej. Drugi i trzeci sposób przedstawiam poniżej:
int x1, y1, z1;
x1 = 0;
y1 = 0;
z1 = 0;
int x2, y2, z2;
x2 = y2 = z2 = 0;
Efekt działania zarówno sposobu drugiego jak i trzeciego będzie taki sam. Różnicą jest tu jednak kolejność przypisania w przypadku trzecim, tj. kolejność wykonywanych operacji przebiegnie od prawej do lewej, czyli:
Dodatkową zaletą tego rozwiązania jest fakt, że kompilator będzie mógł trochę ten kod zoptymalizować i przypisać zmiennym nowe wartości szybciej, niż w przypadku wystąpienia trzech osobnych operacji. Wadą tego rozwiązania dla początkujących może być jednak zmniejszenie czytelności kodu. Dobrze wiedzieć, że coś takiego istnieje, ale stosować na siłę tego nie ma co głównie ze względu na to, że obecne PC'ty i kompilatory są na tyle dobre, że nie zauważyłbyś różnicy nawet dla miliona takich przypisań. Ponadto w większości przypadków kompilatory same z siebie wygenerują taki sam kod dla obu fragmentów kodu, tak więc traktuj tą informację raczej jako ciekawostkę, którą można sobie stosować, a nie jako coś z czego trzeba korzystać. Innymi słowy: da się bez tego obyć i ja właśnie takie zapisy omijam szerokim łukiem po to, żeby kod był bardziej przyjazny programiście.
Zadanie domowe
Napisz krótki program, wykorzystujący wielokrotne przypisanie.