4. Operatory arytmetyczne
Język C jest bardzo bogato wyposażony we wszelkiego rodzaju operatory arytmetyczne. Poznaliśmy już operator przypisania, przyszła pora na następne. Przedstawię to w formie tabeli, która będzie także zawierać pascalowy odpowiednik.
C
Pascal
Opis
+
+
Dodanie dwóch wartości
–
–
Odjęcie jednej wartości od drugiej
/
/ lub div
Podzielenie jednej wartości przez drugą
*
*
Pomnożenie jednej wartości przez drugą (ma także inne znaczenie, ale o tym później)
%
mod
Reszta z dzielenia dwóch liczb
++
brak
Zwiększenie o 1 (występują tu dwa różne przypadki omówione poniżej)
–
brak
Zmniejszenie o 1 (także występują tu dwa różne przypadki)
<<
shl
Przesunięcie bitowe w lewo (omówimy je szczegółowo poniżej)
>>
shr
Przesunięcie bitowe w prawo (omówimy je szczegółowo poniżej)
4.1. Mój pierwszy program
Gwoli ścisłości pierwszy program mamy już za sobą (został on przedstawiony we „Wprowadzeniu do języka C”), ale wtedy nic on nie robił. Teraz napiszemy pierwszy program, który wykonuje jakaś operację. Zanim jednak przejdziemy do napisania (i co najważniejsze przeanalizowania) tego programu muszę wspomnieć jeszcze o jednej rzeczy, a mianowicie o komentarzach. Komentarz jest tekstem wpisanym do kodu programu, który jest jednak pomijany przy jego analizie przez kompilator. Jak sama nazwa wskazuje, służy on do skomentowania danego fragmentu kodu tak, aby po powrocie po miesiącu do kodu programu wiedzieć jak to naprawdę działa. W języku C komentarzem jest wszystko, co znajduje się między znakami /* i */ . Dla przypomnienia – w Pascalu komentarz był zawarty między { i } lub {* i *) . Trzeba także wspomnieć o drugim typie komentarza, a mianowicie // . W tym przypadku komentarzem jest wszystko od tych znaczków, aż do końca linii. Co prawda komentarz tego typu został dopiero wprowadzony w C++, ale większość kompilatorów pozwala na jego użycie także dla programów pisanych w C. Ja także w przestawionych przykładach będę częściej stosował komentarz tego typu, ponieważ uważam, że jest on wygodniejszy. Jeśli jednak Twój kompilator go nie obsługuje, po prostu zamień „// jakiś tekst” na „/* jakiś tekst */” i program skompiluje się bez żadnych problemów. Dobrze, możemy wreszcie przejść do napisania programu. Oto on:
void main(void)
{
// ——————————
// Deklaracja używanych zmiennych
// ——————————
int a, b, c; // Deklaracja trzech zmiennych typu int (całkowita).
// Jak widać możemy zadeklarować kilka zmiennych tego samego
// typu w jednej linijce. Wystarczy je rozdzielić przecinkiem.
float r = 5.3; // Deklaracja zmiennej typu float (rzeczywista) wraz z
// przypisaniem wartości początkowej.
// ———————
// Właściwy kod programu
// ———————
a = 5; b = 3; // Przypisujemy zmiennym a i b wartości, odpowiednio, 5 i 3.
// Jak widać w jednej linijce programu można wpisać kilka
// instrukcji kończąc każda przy pomocy średnik
c = a + b; // Dodanie zmiennych a oraz b i wpisanie wyniku do zmiennej c.
// Zmienna c jest teraz równa 8
c = a – b; // Odjęcie zmiennej b od a i wpisanie wyniku do zmiennej c.
// Zmienna c jest teraz równa 2.
c = a * b; // Pomnożenie zmiennej a przez b i wpisanie wyniku do c.
// Zmienna c jest teraz równa 15;
// — poniższe instrukcje są omówione szczegółowo w tekście kursu –
c++; // Zwiększenie zmiennej c o 1. Teraz jest ona równa 16.
++c; // Zwiększenie zmiennej c o 1. Teraz jest ona równa 17.
–c; // Zmniejszenie zmiennej c o 1. Teraz jest ona równa 16.
c–; // Zmniejszenie zmiennej c o 1. Teraz jest ona równa 15.
c = a % b; // Wpisanie do c reszty z dzielenia a przez b.
// Zmienna c jest teraz równa 2
r = a / b; // Podzielenie zmiennej a przez b i wpisanie wyniku do r.
// Zmienna r jest teraz równa 1.
r = a; // Przypisanie wartości zmiennej a do zmiennej r. Teraz r jest
// równe 5. Jak widać mimo różnych typów (a jest całkowite,
// natomiast r jest rzeczywiste) można bezproblemowo dokonać
// takiego przypisania. Zmienna typu całkowitego jest konwertowana
// na zmienną typu rzeczywistego. Natomiast w Pascalu przy próbie
// kompilacji czegoś takiego zostałby zgłoszony błąd.
r = r / b; // Podzielenie zmiennej r przez b. Teraz r jest równe 1.66666
// —————————
// tworzymy nowy blok programu
// —————————
{
// ————————————–
// Deklaracja używanych w bloku zmiennych
// ————————————–
int x=5; // Deklarujemy lokalną dla tego bloku zmienna typu całkowitego.
// Zmiennej tej nie możemy wykorzystywać poza obrębem tego bloku,
// w takim przypadku kompilator zgłosi błąd.
int r=7; // Tutaj mamy ciekawą rzecz. Deklarujemy wewnątrz tego bloku
// zmienna lokalną o takiej samej nazwie jak zmienna występująca
// w bloku nadrzędnym, jednak o innym typie (wcześniej był to
// float)
// ————————————–
// Kod bloku
// ————————————–
x += r; // Dodajemy do zmiennej x wartość zmiennej r. Teraz zmienna x
// jest równa 12. Widzimy więc, że w przypadku, gdy zmienna
// lokalna ma taką samą nazwę jak zmienna występująca w bloku
// nadrzędnym używana jest zmienna lokalna.
x += a; // Dodajemy do zmiennej x wartość zmiennej a. Teraz zmienna x
// jest równa 17.
// Jak widzimy wewnątrz tego bloku możemy używać zmiennych
// należących zarówno do tego bloku programu, jak i zmiennych
// zadeklarowanych w blokach nadrzędnych (w tym przypadku
// zmiennej a)
}
}
Wpiszmy go w edytorze i nazwijmy go „first.c”. Teraz już możemy spróbować go skompilować. W zależności od kompilatora robi się to w różny sposób, więc nie będę tego omawiał – przeczytaj w dokumentacji od Twojego kompilatora. Mamy już więc nasz pierwszy program, przejdźmy do jego analizy.
Pierwsza linijka to definicja funkcji main, która nie pobiera, ani nie zwraca żadnych parametrów (zostało to omówione w punkcie Typy danych). Następnie deklarujemy zmienne, które będziemy używać. Jak widać na przykładzie, możemy zadeklarować od razu kilka zmiennych jeśli są one tego samego typu. Zmiennar jest zadeklarowana wraz z przypisaniem wartości początkowej. Następnych parę linijek to przykłady użycia podstawowych operatorów. Myślę, że nie trzeba ich szczegółowo omawiać, gdyż jest to logiczne. Jednak następne cztery linijki to coś nowego. Jak widać w komentarzu instrukcje c++; i ++c; wydają się działać identycznie. Po co więc dwie instrukcje, które robią to samo ? Diabeł tkwi w szczegółach. W przedstawionym przykładzie ich działanie jest rzeczywiście identyczne jednak obie różnią się sposobem działania. Operator ++ użyty jako przyrostek to tzw. postinkrementacja, natomiast użyty jako przedrostek to tzw. preinkrementacja. Aby pokazać różnicę w ich działaniu posłużę się przykładem.
Załóżmy, że zmienna a zawiera wartość pięć i wykonujemy taką oto instrukcję:
c = a++;
W takim przypadku do zmiennej c zostanie przypisana wartość znajdująca się w zmiennej a (czyli pięć) i dopiero po tym przypisaniu zmienna a zostanie zwiększona o jeden. Czyli w efekcie po wykonaniu tej instrukcji zmienna c będzie zawierała wartość pięć, natomiast zmienna a będzie równa sześć.
Teraz przy założeniach takich samych jak powyżej wykonujemy taką instrukcję:
c = ++a;
W takim przypadku najpierw zmienna a zostanie zwiększona o jeden (czyli teraz będzie równa sześć) i następnie ta wartość będzie przypisana do zmiennej c . Czyli w efekcie po wykonaniu tej instrukcji obie zmienne będą równe sześć.
Analogicznie działa operator — tylko zamiast zwiększania, zmniejsza wartość o jeden.
Następna linia zawiera operator % . Dzięki niemu możemy uzyskać resztę z dzielenia całkowitego dwóch liczb. W tym przypadku dzielimy pięć przez trzy, czyli w wyniku otrzymujemy resztę z dzielenia równą dwa.
Przejdźmy do dalszej analizy programu. Znowu widzimy dziwną rzecz – po wykonaniu:
r = a / b;
przy a równym pięć i b równym trzy otrzymujemy w wyniku jeden. Natomiast wykonanie ciągu instrukcji:
r = a; r = r / b;
powoduje, że otrzymujemy wynik którego oczekiwaliśmy, czyli 1.666667.
Dlaczego tak się dzieje ? Otóż trzeba zwrócić uwagę na typy zmiennych, które biorą udział w operacji dzielenia. W pierwszym przypadku dzielimy liczbę całkowitą przez liczbę całkowitą. W takiej sytuacji wynik, który otrzymujemy jest również całkowity i jest on wpisywany po konwersji do zmiennej r. A ponieważ liczba całkowita nie posiada części ułamkowej, wynik tej operacji pokazuje ile razy trzy całkowicie mieści się w piątce, a reszta z tego dzielenia jest odrzucana. Aby zaradzić takiej sytuacji można wykorzystać tzw. rzutowanie, ale o tym napiszę w dalszej części. Uwaga dla znających Pascala: operacji dzielenia odpowiada tutaj operator div z tego języka.
Natomiast w drugim przypadku nie ma takiego problemu ponieważ zmienna r jest typu rzeczywistego. Tak więc dzielimy liczbę rzeczywistą (która jest równa 5.0 – zwróćcie uwagę na to zero po kropce) przez trzy i w efekcie otrzymujemy wynik, który jest także rzeczywisty. Dlatego właśnie jego część ułamkowa nie jest tracona i wynik jest zgodny z oczekiwanym. Operacji dzielenia w tym przypadku odpowiada Pascalowe / .
Idźmy dalej… Jak widzimy deklarujemy tutaj nowy logiczny blok programu. Dla przypomnienia – blokiem jest tekst zawarty między { i } . Każdy blok może mieć swoje zmienne, więc tutaj także je deklarujemy – zmienną x typu int oraz zmienną r także tego typu. Z oboma zmiennymi wiążą się ciekawe rzeczy. Po pierwsze zmienna zadeklarowana wewnątrz danego bloku jest dostępna tylko i wyłącznie dla tego bloku (oraz wszystkich bloków, które będą zadeklarowane wewnątrz niego). Tak więc próba użycia zmiennej x po znaku kończącym blok } , spowoduje, że kompilator zgłosi błąd. Po drugie zmienna r była także zadeklarowana w bloku nadrzędnym ! Tak więc, która z nich zostanie użyta przy próbie dodania r do x ? Odpowiedź brzmi następująco: użyta zostanie zmienna zadeklarowana „bliżej” miejsca jej użycia.
Po zadeklarowaniu zmiennych widzimy nową konstrukcję: x += r; Cóż to takiego ? Otóż jest to kompaktowa wersja operatora + . Programiści to leniwy naród i lubią sobie upraszczać życie 😉
Instrukcja a += b; odpowiada zapisowi a = a + b; Którego z tych dwóch zapisów będziesz używał jest to obojętne, oba działają w identyczny sposób. Ja preferuję ten drugi – mniej trzeba stukać w klawiaturę 😉
Analogicznie wygląda sytuacja w przypadku pozostałych przedstawionych operatorów (oprócz oczywiście ++ i — , gdyż nie miałoby to wtedy sensu).
Pozostały jeszcze do wyjaśnienia dwa operatory, które nie zostały użyte w tym przykładowym programie. Są nimi bliźniacze << i >> . Operator << przesuwa wszystkie bity argumentu w lewo, natomiast >> przesuwa je w prawo. Być może nie wiesz nic o systemie dwójkowym i nie rozumiesz co to tak naprawdę znaczy, ale nie martw się. Jedyne co musisz zapamiętać, to fakt, że przesunięciu liczby w lewo o jeden bit odpowiada pomnożenie jej przez dwa, natomiast przesunięcie w prawo podzieleniu jej przez dwa (całkowicie). Poniżej podałem kilka przykładów:
a = a << 1; // pomnożenie zmiennej a przez 2^1, czyli 2
a = a << 2; // pomnożenie zmiennej a przez 2^2, czyli 4
a = a << 3; // pomnożenie zmiennej a przez 2^3, czyli 8
a = a >> 1; // podzielenie zmiennej a przez 2^1, czyli 2
a = a >> 2; // podzielenie zmiennej a przez 2^2, czyli 4
Mógłbyś jeszcze zapytać – po co stosować przesunięcia bitowe, skoro mogę użyć zwykłego mnożenia lub dzielenia ? Owszem możesz, z tym, że przesunięcia bitowe są o wiele szybsze, dzięki czemu możesz zwiększyć szybkość działania swojego programu. Co prawda nowoczesne kompilatory starają się optymalizować Twój kod poprzez zamienienie mnożenia, czy dzielenia na odpowiednie przesunięcia bitowe, jednak nie robią tego we wszystkich przypadkach.
Mamy więc już za sobą nasz pierwszy program. Co prawda wyników jego działania nie widać na ekranie, ale miał on tylko wytłumaczyć zasadę używania operatorów w języku C. Następny nasz program także będzie miał za zadanie zobrazować pewny sposób pisania programu i nic nie wyświetli, ale zaraz po nim napiszemy program, który wyświetli wreszcie pierwszy tekst. Mam nadzieję, że po przeanalizowaniu naszego pierwszego programu wszystko stało się dla Ciebie bardziej jasne i zrozumiałe.