BAZA WIEDZY
KURSY
Bazy danych w PHP
Kurs AdvancedAJAX
Kurs ASP
Kurs ASP.NET
Kurs C++
Kurs CSS
Kurs HTML
Kurs HTML drugi
Kurs JavaScript
Kurs MySQL
Kurs PHP
Kurs RSS
Kurs XHTML
Obiekty DOM
MANUALE
CSS1 - W3C
DOM - w budowie
PHP 2005
PHP 2006
Wyrażenia regularne
SHOUTBOX
STAT
Online: 5 | UU: 208

Kalkulacje na liczbach

Poznamy teraz kilka standardowych operacji, które możemy wykonywać na danych liczbowych. Najpierw będą to odpowiednie funkcje, których dostarcza nam C++, a następnie uzupełnienie wiadomości o operatorach arytmetycznych. Zaczynajmy więc :)

Przydatne funkcje

C++ udostępnia nam wiele funkcji matematycznych, dzięki którym możemy przeprowadzać proste i nieco bardziej złożone obliczenia. Prawie wszystkie są zawarte w pliku nagłówkowym cmath , dlatego też musimy dołączyć ten plik do każdego programu, w którym chcemy korzystać z tych funkcji. Robimy to analogicznie jak w przypadku innych nagłówków - umieszczając na początku naszego kodu dyrektywę:

#include <cmath>

Po dopełnieniu tej drobnej formalności możemy korzystać z całego bogactwa narzędzi matematycznych, jakie zapewnia nam C++. Spójrzmy więc, jak się one przedstawiają.

Funkcje potęgowe

W przeciwieństwie do niektórych języków programowania, C++ nie posiada oddzielnego operatora potęgowania 1 . Zamiast niego mamy natomiast funkcję pow() (ang. power - potęga), która prezentuje się następująco:

double pow( double base, double exponent);

Jak widać, bierze ona dwa parametry. Pierwszym ( base ) jest podstawa potęgi, a drugim ( exponent ) jej wykładnik. W wyniku zwracany jest oczywiście wynik potęgowania (a więc wartość wyrażenia base exponent ).

Podobną do powyższej deklarację funkcji, przedstawiającą jej nazwę, ilość i typy parametrów oraz typ zwracanej wartości, nazywamy prototypem .

Oto kilka przykładów wykorzystania funkcji pow() :

double fX;
fX = pow( 2 , 8 ); // ósma potęga dwójki, czyli 256
fX = pow( 3 , 4 ); // czwarta potęga trójki, czyli 81
fX = pow( 5 , - 1 ); // odwrotność piątki, czyli 0.2

Inną równie często wykonywaną czynnością jest pierwiastkowanie. Realizuje ją między innymi funkcja sqrt() (ang. square root - pierwiastek kwadratowy):

double sqrt( double x);

Jej jedyny parametr to oczywiście liczba, która chcemy pierwiastkować. Użycie tej funkcji jest zatem niezwykle intuicyjne:

fX = sqrt( 64 ); // 8 (bo 8*8 == 64)
fX = sqrt( 2 ); // około 1.414213562373
fX = sqrt(pow(fY, 2 )); // fY

Nie ma natomiast wbudowanej formuły, która obliczałaby pierwiastek dowolnego stopnia z danej liczby. Możemy jednak łatwo napisać ją sami, korzystając z prostej własności:


1 Znak ^ , który służy w nich do wykonywania tego działania, jest w C++ zarezerwowany dla jednej z operacji bitowych - różnicy symetrycznej. Więcej informacji na ten temat możesz znaleźć w Dodatku B, Reprezentacja danych w pamięci .


Po przełożeniu tego równania na C++ uzyskujemy następującą funkcję:

double root( double x, double a) { return pow(x, 1 / a); }

Zapisanie jej definicji w jednej linijce jest całkowicie dopuszczalne i, jak widać, bardzo wygodne. Elastyczność składni C++ pozwala więc na zupełnie dowolną organizację kodu.

Dokładny opis poznanych funkcji pow() i sqrt() znajdziesz w MSDN.

Funkcje wykładnicze i logarytmiczne

Najczęściej stosowaną w matematyce funkcją wykładniczą jest , niekiedy oznaczana także jako . Taką też formę ma ona w C++:

double exp( double x);

Zwraca ona wartość stałej e 1 podniesionej do potęgi x . Popatrzmy na kilka przykładów:

fX = exp( 0 ); // 1
fX = exp( 1 ); // e
fX = exp( 2.302585093 ); // 10.000000

Natomiast funkcję wykładniczą o dowolnej podstawie uzyskujemy, stosując omówioną już wcześniej formułę pow() .

Przeciwstawne do funkcji wykładniczych są logarytmy. Tutaj mamy aż dwie odpowiednie funkcje :) Pierwsza z nich to log() :

double log( double x);

Jest to logarytm naturalny (o podstawie e ), a więc funkcja dokładnie do odwrotna do poprzedniej exp() . Otóż dla danej liczby x zwraca nam wartość wykładnika, do którego musielibyśmy podnieść e , by otrzymać x . Dla pełnej jasności zerknijmy na poniższe przykłady:

fX = log( 1 ); // 0
fX = log( 10 ); // 2.302585093
fX = log(exp(x)); // x

Drugą funkcją jest log10() , czyli logarytm dziesiętny (o podstawie 10):

double log10( double x);

Analogicznie, funkcja ta zwraca wykładnik, do którego należałoby podnieść dziesiątkę, aby otrzymać podaną liczbę x , na przykład:

fX = log10( 1000 ); // 3 (bo 10 3 == 1000)
fX = log10( 1 ); // 0
fX = log10(pow( 10 , x)); // x

Niestety, znowu (podobnie jak w przypadku pierwiastków) nie mamy bardziej uniwersalnego odpowiednika tych dwóch funkcji, czyli logarytmu o dowolnej podstawie. Ponownie jednak możemy skorzystać z odpowiedniej tożsamości matematycznej 2 :

Nasza własna funkcja może więc wyglądać tak:

double log_a( double a, double x) { return log(x) / log(a); }

Oczywiście użycie log10() w miejsce log() jest również poprawne.

1 Tak zwanej stałej Nepera, podstawy logarytmów naturalnych - równej w przybliżeniu 2.71828182845904 .

2 Znanej jako zmiana podstawy logarytmu.

Zainteresowanych ponownie odsyłam do MSDN celem poznania dokładnego opisu funkcji exp() oraz log() i log10() .

Funkcje trygonometryczne

Dla nas, (przyszłych) programistów gier, funkcje trygonometryczne są szczególnie przydatne, gdyż będziemy korzystać z nich niezwykle często - choćby przy różnorakich obrotach. Wypadałoby zatem dobrze znać ich odpowiedniki w języku C++.

Na początek przypomnijmy sobie (znane, mam nadzieję :D) określenia funkcji trygonometrycznych. Posłuży nam do tego poniższy rysunek:

Rysunek 1. Definicje funkcji trygonometrycznych dowolnego kąta

Zwróćmy uwagę, że trzy ostatnie funkcje są określone jako odwrotności trzech pierwszych. Wynika stąd fakt, iż potrzebujemy do szczęścia jedynie sinusa, cosinusa i tangensa - resztę funkcji i tak będziemy mogli łatwo uzyskać.

C++ posiada oczywiście odpowiednie funkcje:

double sin( double alfa); // sinus
double cos( double alfa); // cosinus
double tan( double alfa); // tangens

Działają one identycznie do swoich geometrycznych odpowiedników. Jako jedyny parametr przyjmują miarę kąta w radianach i zwracają wyniki, których bez wątpienia można się spodziewać :)

Jeżeli chodzi o trzy brakujące funkcje, to ich definicje są, jak sądzę, oczywiste:

double cot( double alfa) { return 1 / tan(alfa); } // cotangens
double sec( double alfa) { return 1 / cos(alfa); } // secant
double csc( double alfa) { return 1 / sin(alfa); } // cosecant

Gdy pracujemy z kątami i funkcjami trygonometrycznymi, nierzadko pojawia się konieczność zamiany miary kąta ze stopni na radiany lub odwrotnie. Niestety, nie znajdziemy w C++ odpowiednich funkcji, które realizowałyby to zadanie. Być może dlatego, że sami możemy je łatwo napisać:

const double PI = 3.1415923865 ;
double degtorad( double alfa) { return alfa * PI / 180 ; }
double radtodeg( double alfa) { return alfa * 180 / PI; }

Pamiętajmy też, aby nie mylić tych dwóch miar kątów i zdawać sobie sprawę, iż funkcje trygonometryczne w C++ używają radianów. Pomyłki w tej kwestii są dość częste i powodują nieprzyjemne rezultaty, dlatego należy się ich wystrzegać :)

Jak zwykle, więcej informacji o funkcjach sin() , cos() i tan() znajdziesz w MSDN. Możesz tam również zapoznać się z funkcjami odwrotnymi do trygonometrycznych - asin() , acos() oraz atan() i atan2() .

Liczby pseudolosowe

Zostawmy już te zdecydowanie zbyt matematyczne dywagacje i zajmijmy się czymś, co bardziej zainteresuje przeciętnego zjadacza komputerowego i programistycznego chleba :) Mam tu na myśli generowanie wartości losowych.

Liczby losowe znajdują zastosowanie w bardzo wielu programach. W przypadku gier mogą służyć na przykład do tworzenia realistycznych efektów ognia, deszczu czy śniegu. Używając ich możemy również kreować za każdym inną mapę w grze strategicznej czy zapewnić pojawianie się wrogów w przypadkowych miejscach w grach zręcznościowych. Przydatność liczb losowych jest więc bardzo szeroka.

Uzyskanie losowej wartości jest w C++ całkiem proste. W tym celu korzystamy z funkcji rand() (ang. random - losowy):

int rand();

Jak możnaby przypuszczać, zwraca nam ona przypadkową liczbę dodatnią 1 . Najczęściej jednak potrzebujemy wartości z określonego przedziału - na przykład w programie ilustrującym działanie pętli while losowaliśmy liczbę z zakresu od 1 do 100 . Osiągnęliśmy to w dość prosty sposób:

int nWylosowana = rand() % 100 + 1 ;

Wykorzystanie operatora reszty z dzielenia sprawia, że nasza dowolna wartość (zwrócona przez rand() ) zostaje odpowiednio "przycięta" - w tym przypadku do przedziału < 0 ; 99 > (ponieważ resztą z dzielenia przez sto może być 0 , 1 , 2 , ., 98 , 99 ). Dodanie jedynki zmienia ten zakres do pożądanego < 1 ; 100 > .

W podobny sposób możemy uzyskać losową liczbę z jakiegokolwiek przedziału. Nie od rzeczy będzie nawet napisanie odpowiedniej funkcji:

int random( int nMin, int nMax)
{ return rand() % (nMax - nMin + 1 ) + nMin; }

Używając jej, potrafimy bez trudu stworzyć chociażby symulator rzutu kostką do gry:

void main()
{
std::cout << "Wylosowano " << random( 1 , 6 ) << " oczek." ;
getch();
}

Zdaje się jednak, że coś jest nie całkiem w porządku. Uruchamiając parokrotnie powyższy program, za każdym razem zobaczymy jedną i tą samą liczbę! Gdzie jest więc ta obiecywana losowość?!

Cóż, nie ma w tym nic dziwnego. Komputer to tylko wielkie liczydło, które działa w zaprogramowany i przewidywalny sposób. Dotyczy to także funkcji rand() , której działanie opiera się na raz ustalonym i niezmiennym algorytmie. Jej wynik nie jest zatem w żaden sposób losowany, lecz wyliczany na podstawie formuł matematycznych. Dlatego też liczby uzyskane w ten sposób nazywamy pseudolosowymi , ponieważ tylko udają prawdziwą przypadkowość.

Wydawać by się mogło, że fakt ten czyni je całkowicie nieprzydatnymi. Na szczęście nie jest to prawdą: liczby pseudolosowe można z powodzeniem wykorzystywać we właściwym im celu - pod warunkiem, że robimy to poprawnie.

Musimy bowiem pamiętać, aby przed pierwszym użyciem rand() wywołać inną funkcję - srand() :

void srand( unsigned int seed);

Jej parametr seed to tak zwane ziarno. Jest to liczba, która inicjuje generator wartości pseudolosowych. Dla każdego możliwego ziarna funkcja rand() oblicza nam inny ciąg liczb. Zatem, logicznie wnioskując, powinniśmy dbać o to, by przy każdym uruchomieniu programu wartość ziarna była inna.

Dochodzimy tym samym do pozornie błędnego koła - żeby uzyskać liczbę losową, potrzebujemy. liczby losowej! Jak rozwiązać ten, zdawałoby się, nierozwiązywalny problem?.

Otóż należy znaleźć taką wartość, która będzie się zmieniać miedzy kolejnymi uruchomieniami programu. Nietrudno ją wskazać - to po prostu czas systemowy .

Jego pobranie jest bardzo łatwe, bowiem C++ udostępnia nam zgrabną funkcję time() , zwracająca aktualny czas 1 w sekundach:

time_t time(time_t* timer);

Być może wygląda ona dziwnie, ale zapewniam cię, że działa świetnie :) Wymaga jednak, abyśmy dołączyli do programu dodatkowy nagłówek c time :

#include <ctime>

Teraz mamy już wszystko, co potrzebne. Zatem do dzieła! Nasza prosta aplikacja powinna obecnie wyglądać tak:

// Random - losowanie liczby
#include <iostream>
#include <ctime>
#include <conio.h>

int random( int nMin, int nMax) { return rand() % nMax + nMin; }
void main()
{
// zainicjowanie generatora liczb pseudolosowych aktualnym czasem
srand ( static_cast < unsigned int >(time(NULL)));
// wylosowanie i pokazanie liczby
std::cout << "Wylosowana liczba to " << random( 1 , 6 ) << std::endl;
getch();
}

Kompilacja i kilkukrotne uruchomienie powyższego kodu utwierdzi nas w przekonaniu, iż tym razem wszystko funkcjonuje poprawnie.

Screen 22. Przykładowy rezultat "rzutu kostką"

Dzieje się tak naturalnie za sprawą tej linijki:

srand ( static_cast < unsigned int >(time(NULL)));

Wywołuje ona funkcję srand() , podając jej ziarno uzyskane poprzez time() . Ze względu na to, iż time() zwraca wartość należącą do specjalnego typu time_t , potrzebne jest rzutowanie jej na typ unsigned int .

Wyjaśnienia wymaga jeszcze parametr funkcji time() . NULL to tak zwany wskaźnik zerowy, niereprezentujący żadnej przydatnej wartości. Używamy go tutaj, gdyż nie mamy nic konkretnego do przekazania dla funkcji, zaś ona sama niczego takiego od nas nie wymaga :)

Kompletny opis funkcji rand() , srand() i time() znajdziesz, jak poprzednio, w MSDN.

Zaokrąglanie liczb rzeczywistych

Gdy poznawaliśmy rzutowanie typów, podałem jako przykład konwersję wartości float na int . Wspomniałem też, że zastosowane w tym przypadku zaokrąglenie liczby rzeczywistej polega na zwyczajnym odrzuceniu jej części ułamkowej.

Nie jest to wszakże jedyny sposób dokonywania podobnej zamiany, gdyż C++ posiada też dwie specjalnie do tego przeznaczone funkcje. Działają one w inaczej niż zwykłe rzutowanie, co samo w sobie stanowi dobry pretekst do ich poznania :D

Owe dwie funkcje są sobie wzajemnie przeciwstawne - jedna zaokrągla liczbę w górę (wynik jest zawsze większy lub równy podanej wartości), zaś druga w dół (rezultat jest mniejszy lub równy). Świetne obrazują to ich nazwy, odpowiednio: ceil() (ang. ceiling - sufit) oraz floor() ('podłoga').

Przyjrzyjmy się teraz nagłówkom tych funkcji:

double ceil( double x);
double floor( double x);

Nie ma tu żadnych niespodzianek - no, może poza typem zwracanego wyniku. Dlaczego nie jest to int ? Otóż typ double ma po prostu większą rozpiętość przedziału wartości, jakie może przechowywać. Ponieważ argument funkcji także należy do tego typu, zastosowanie int spowodowałoby otrzymywanie błędnych rezultatów dla bardzo dużych liczb (takich, jakie "nie zmieściłyby się" do int -a).

Na koniec mamy jeszcze kilka przykładów, ilustrujących działanie poznanych przed chwilą funkcji:

fX = ceil( 6.2 ); // 7.0
fX = ceil(- 5.6 ); // -5.0
fX = ceil( 14 ); // 14.0
fX = floor( 1.7 ); // 1.0
fX = floor(- 2.1 ); // -3.0

 

Szczególnie dociekliwych czeka kolejna wycieczka wgłąb MSDN po dokładny opis funkcji ceil() i floor() ;D

Inne funkcje

Ostatnie dwie formuły trudno przyporządkować do jakiejś konkretnej grupy. Nie znaczy to jednak, że są one mniej ważne niż pozostałe.

Pierwszą z nich jest abs() (ang. absolute value ), obliczająca wartość bezwzględną (moduł) danej liczby. Jak pamiętamy z matematyki, wartość ta jest tą samą liczbą, lecz bez znaku - zawsze dodatnią.

Ciekawa jest deklaracja funkcji abs() . Istnieje bowiem kilka jej wariantów, po jednym dla każdego typu liczbowego:

int abs( int n);
float abs( float n);
double abs( double n);

Jest to jak najbardziej możliwe i w pełni poprawne. Zabieg taki nazywamy przeciążaniem (ang. overloading ) funkcji.

Przeciążanie funkcji (ang. function overloading ) to obecność kilku deklaracji funkcji o tej samej nazwie, lecz posiadających różne listy parametrów i/lub typy zwracanej wartości.

Gdy więc wywołujemy funkcję abs() , kompilator stara się wydedukować, który z jej wariantów powinien zostać uruchomiony. Czyni to przede wszystkim na podstawie przekazanego doń parametru. Jeżeli byłaby to liczba całkowita, zostałaby wywołana wersja przyjmująca i zwracająca typ int . Jeżeli natomiast podalibyśmy liczbę zmiennoprzecinkową, wtedy do akcji wkroczyłby inny wariant funkcji.

Zatem dzięki mechanizmowi przeciążania funkcja abs() może operować na różnych typach liczb:

int nX = abs(- 45 ); // 45
float fX = abs( 7.5 ); // 7.5
double fX = abs(- 27.8 ); // 27.8

Druga funkcja to fmod() . Działa ona podobnie do operatora % , gdyż także oblicza resztę z dzielenia dwóch liczb. Jednak w przeciwieństwie do niego nie ogranicza się jedynie do liczb całkowitych, bowiem potrafi operować także na wartościach rzeczywistych. Widać to po jej nagłówku:

double fmod( double x, double y);

Funkcja ta wykonuje dzielenie x przez y i zwraca pozostałą zeń resztę, co oczywiście łatwo wydedukować z jej nagłówka :) Dla porządku zerknijmy jeszcze na parę przykładów:

fX = fmod( 14 , 3 ); // 2
fX = fmod( 2.75 , 0.5 ); // 0.25
fX = fmod(- 10 , 3 ); // -1

 

Wielbiciele MSDN mogą zacierać ręce, gdyż z pewnością znajdą w niej szczegółowe opisy funkcji abs() 1 i fmod() ;)

***

Zakończyliśmy w ten sposób przegląd asortymentu funkcji liczbowych, oferowanego przez C++. Przyswoiwszy sobie wiadomości o tych formułach będziesz mógł robić z liczbami niemal wszystko, co tylko sobie zamarzysz :)

Znane i nieznane operatory

Dobrze wiemy, że funkcje to nie jedyne środki służące do manipulacji wartościami liczbowymi. Od początku używaliśmy do tego przede wszystkim operatorów, które odpowiadały doskonale nam znanym podstawowym działaniom matematycznym.

Nadarza się dobra okazja, aby przypomnieć sobie o tych elementach języka C++, przy okazji poszerzając swoje informacje o nich.

Dwa rodzaje

Operatory w C++ możemy podzielić na dwie grupy ze względu na liczbę "parametrów", na których działają. Wyróżniamy więc operatory unarne - wymagające jednego "parametru" oraz binarne - potrzebujące dwóch.

Do pierwszej grupy należą na przykład symbole + oraz - , gdy stawiamy je przed jakimś wyrażeniem. Wtedy bowiem nie pełnią roli operatorów dodawania i odejmowania, lecz zachowania lub zmiany znaku . Może brzmi to dość skomplikowanie, ale naprawdę jest bardzo proste:

int nX = 5 ;
int nY = +nX; // nY == 5
nY = -nX; // nY == -5

Operator + zachowuje nam znak wyrażenia (czyli praktycznie nie robi nic, dlatego zwykle się go nie stosuje), zaś - zmienia go na przeciwny ( neguje wyrażenie). Operatory te mają identyczną funkcję w matematyce, dlatego, jak sądzę, nie powinny sprawić ci większego kłopotu :)

Do grupy operatorów unarnych zaliczamy również ++ oraz -- , odpowiadające za inkrementację i dekrementację. Za chwilę przyjrzymy im się bliżej.

Drugi zestaw to operatory binarne; dla nich konieczne są dwa argumenty. Do tej grupy należą wszystkie poznane wcześniej operatory arytmetyczne, a więc + (dodawanie), - (odejmowanie), * (mnożenie), / (dzielenie) oraz % (reszta z dzielenia).

Ponieważ swego czasu poświęciliśmy im sporo uwagi, nie będziemy teraz dogłębnie wnikać w działanie każdego z nich. Więcej miejsca przeznaczymy tylko na operator dzielenia.

Sekrety inkrementacji i dekrementacji

Operatorów ++ i -- używamy, aby dodać do zmiennej lub odjąć od niej jedynkę. Taki zapis jest najkrótszy i najwygodniejszy, a poza tym najszybszy. Używamy go szczególnie często w pętlach for .

Jednak może być on także częścią złożonych wyrażeń. Poniższe fragmenty kodu są absolutnie poprawne i w dodatku nierzadko spotykane:

int nA = 6 ;
int nB = ++nA;

int nC = 4 ;
int nD = nC++;

 

Od tej pory będę mówił jedynie o operatorze inkrementacji, jednak wszystkie przedstawione tu własności dotyczą także jego dekrementującego brata.

Nasuwa się naturalne pytanie: jakie wartości będą miały zmienne nA , nB , nC i nD po wykonaniu tych czterech linijek kodu?

Jeżeli chodzi o nA i nC , to sprawa jest oczywista. Każda z tych zmiennych została jednokrotnie poddana inkrementacji, zatem ich wartości są o jeden większe niż na początku. Wynoszą odpowiednio 7 i 5 .

Pozostałe zmienne są już twardszym orzechem do zgryzienia. Skupmy się więc chwilowo na nB . Jej wartość na pewno ma coś wspólnego z wartością nA - może to być albo 6 (liczba przed inkrementacją), albo 7 (już po inkrementacji). Analogicznie, nD może być równa 4 (czyli wartości nC przed inkrementacją) lub 5 (po inkrementacji).

Jak jest w istocie? Sam się przekonaj! Stwórz nowy program, wpisz do jego funkcji main() powyższe wiersze kodu i dodaj instrukcje pokazujące wartości zmiennych.

Cóż widzimy? Zmienna nB jest równa 7 , a więc została jej przypisana wartość nA już po inkrementacji. Natomiast nD równa się 4 - tyle, co nC przed inkrementacją.

Przyczyną tego faktu jest rzecz jasna rozmieszczenie plusów. Gdy napisaliśmy je przed inkrementowaną zmienną, dostaliśmy w wyniku wartość zwiększoną o 1. Kiedy zaś umieściliśmy je za tą zmienną, otrzymaliśmy jeszcze stary rezultat.

Jak zatem mogliśmy się przekonać, odpowiednie zapisanie operatorów ++ i -- ma całkiem spore znaczenie.

Umieszczenie operatora ++ ( -- ) przed wyrażeniem nazywamy preinkrementacją ( predekrementacją ). W takiej sytuacji najpierw dokonywane jest zwiększenie (zmniejszenie) jego wartości o 1. Nowa wartość jest potem zwracana jako wynik.

Kiedy napiszemy operator ++ ( -- ) po wyrażeniu, mamy do czynienia z postinkrementacją ( postdekrementacją ). W tym przypadku najpierw następuje zwrócenie wartości, która dopiero potem jest zwiększana (zmniejszana) o jeden 2 .

Czyżby trzeba było tych regułek uczyć się na pamięć? Oczywiście, że nie :) Jak większość rzeczy w programowaniu, możemy je traktować intuicyjnie.

Kiedy napiszemy plusy (lub minusy) przed zmienną, wtedy najpierw "zadziałają" właśnie one. A skutkiem ich działania będzie inkrementacja lub dekrementacja wartości zmiennej, a więc otrzymamy w rezultacie już zmodyfikowaną liczbę.

Gdy zaś umieścimy je za nazwą zmiennej, ustąpią jej pierwszeństwa i pozwolą, aby jej stara wartość została zwrócona. Dopiero potem wykonają swoją pracę, czyli in/dekrementację.

Jeżeli mamy możliwość dokonania wyboru między dwoma położeniami operatora ++ (lub --) , powinniśmy zawsze używać wariantu prefiksowego (przed zmienną). Wersja postfiksowa musi bowiem utworzyć w pamięci kopię zmiennej, żeby móc zwrócić jej starą wartość po in/dekrementacji. Cierpi na tym zarówno szybkość programu, jak i jego wymagania pamięciowe (chociaż w przypadku typów liczbowych jest to niezauważalna różnica).

Słówko o dzieleniu

W programowaniu mamy do czynienia z dwoma rodzajami dzielenia liczb: całkowitoliczbowym oraz zmiennoprzecinkowym. Oba zwracają te same rezultaty w przypadku podzielnych przez siebie liczb całkowitych, ale w innych sytuacjach zachowują się odmiennie.

Dzielenie całkowitoliczbowe podaje jedynie całkowitą część wyniku, odrzucając cyfry po przecinku. Z tego powodu wynik takiego dzielenia może być bezpośrednio przypisany do zmiennej typu całkowitego. Wtedy jednak traci się dokładność ilorazu.

Dzielenie zmiennoprzecinkowe pozwala uzyskać precyzyjny rezultat, gdyż zwraca liczbę rzeczywistą wraz z jej częścią ułamkową. Ów wynik musi być wtedy zachowany w zmiennej typu rzeczywistego.

Większa część języków programowania rozróżnia te dwa typy dzielenia poprzez wprowadzenie dwóch odrębnych operatorów dla każdego z nich 3 . C++ jest tu swego rodzaju wyjątkiem, ponieważ posiada tylko jeden operator dzielący, / . Jednakże posługując się nim odpowiednio, możemy uzyskać oba rodzaje ilorazów.

Zasady, na podstawie których wyróżniane są w C++ te dwa typy dzielenia, są ci już dobrze znane. Przedstawiliśmy je sobie podczas pierwszego spotkania z operatorami arytmetycznymi. Ponieważ jednak powtórzeń nigdy dość, wymienimy je sobie ponownie :)

Jeżeli obydwa argumenty operatora / (dzielna i dzielnik) są liczbami całkowitymi, wtedy wykonywane jest dzielenie całkowitoliczbowe .

W przypadku, gdy chociaż jedna z liczb biorących udział w dzieleniu jest typu rzeczywistego, mamy do czynienia z dzieleniem zmiennoprzecinkowym .

Od chwili, w której poznaliśmy rzutowanie, mamy większą kontrolę nad dzieleniem. Możemy bowiem łatwo zmienić typ jednej z liczb i w ten sposób spowodować, by został wykonany inny rodzaj dzielenia. Możliwe staje się na przykład uzyskanie dokładnego ilorazu dwóch wartości całkowitych:

int nX = 12 ;
int nY = 5 ;
float
fIloraz = nX / static_cast < float >(nY);

Tutaj uzyskamy precyzyjny rezultat 2.4 , gdyż kompilator przeprowadzi dzielenie zmiennoprzecinkowe. Zrobi tak, bo drugi argument operatora / , mimo że ma wartość całkowitą, jest traktowany jako wyrażenie typu float . Dzieje się tak naturalnie dzięki rzutowaniu.

Gdybyśmy go nie zastosowali i wpisali po prostu nX / nY , wykonałoby się dzielenie całkowitoliczbowe i ułamkowa część wyniku zostałaby obcięta. Ten okrojony rezultat zmieniłby następnie typ na float (ponieważ przypisalibyśmy go do zmiennej rzeczywistej), co byłoby zupełnie zbędne, gdyż i tak w wyniku dzielenia dokładność została stracona.

Prosty wniosek brzmi: uważajmy, jak i co tak naprawdę dzielimy, a w razie wątpliwości korzystajmy z rzutowania.

***

Kończący się właśnie podrozdział prezentował podstawowe instrumentarium operacyjne wartości liczbowych w C++. Poznając je zyskałeś potencjał do tworzenia aplikacji wykorzystujących złożone obliczenia, do których niewątpliwie należą także gry.

Jeżeli czujesz się przytłoczony nadmiarem matematyki, to mam dla ciebie dobrą wiadomość: nasza uwaga skupi się teraz na zupełnie innym, lecz również ważnym typie danych - tekście.

 

1 Standardowo dołączona do Visual Studio .NET biblioteka MSDN posiada lekko nieaktualny opis tej funkcji - nie są tam wymienione jej wersje przeciążane dla typów float i double .

2 To uproszczone wyjaśnienie, bo przecież zwrócenie wartości kończyłoby działanie operatora. Naprawdę więc wartość wyrażenia jest tymczasowo zapisywana i zwracana po dokonaniu in/dekrementacji.

3 W Visual Basicu jest to \ dla dzielenia całkowitoliczbowego i / dla zmiennoprzecinkowego. W Delphi odpowiednio div i / .

1 Funkcja ta zwraca liczbę sekund, jakie upłynęły od północy 1 stycznia 1970 roku.

1 Liczba ta należy do przedziału < 0; RAND_MAX> , gdzie RAND_MAX jest stałą zdefiniowaną przez kompilator (w Visual C++ .NET ma ona wartość 32767 ).


| | | |
Copyright © 2006-2013 egrafik.pl | Kontakt | Reklama | Projekty domów
jocker