Zmienne i ich typy

   Czasem trzeba w programie przechować jakąś wartość. Służy do tego oczywiście pamięć komputera. Ale odwoływanie się do komórek pamięci po ich numerach było by strasznie nieporęczne! Dlatego wymyślono, żeby nadawać komórkom pamięci nazwy, po których łatwiej się zorientować, co się w nich znajduje. Taki "nazwany" obszar pamięci to właśnie jest zmienna. Możemy sobie ją też wyobrażać jako pudełko z etykietką do przechowywania w nim jakiejś wartości, albo jako matematyczny X, jak kto woli ;-). Dodatkowo, żeby nie było pomyłek, każda zmienna ma swój typ określający, jakiego rodzaju wartości może przechowywać. Poznamy teraz kolejno każdy z nich, a później zobaczysz, jak je wykorzystać w programie.

   Zaczniemy od typu char. Zmienna tego typu zajmuje jeden bajt i może mieć wartość od -128 do +127. Dlaczego akurat taki zakres? Bierze się to z zapisu takiej liczby w pamięci. W jednym bajcie można pomieścić 256 różnych kombinacji bitów, czyli liczby od 0 do 255. Jednak żeby mieć możliwość korzystania z liczb ujemnych, jeden z bitów przeznaczono na znak. Jeśli bit ten jest ustawiony, oznacza to liczbę ujemną, a gdy jest zgaszony, liczba jest dodatnia. Pozostałe siedem bitów oznacza liczbę, dlatego właśnie mamy zakres od -128 do +127.
Istnieje jednak możliwość traktowania całego bajtu jako liczby, tyle że wtedy rezygnujemy ze znaku i mamy do dyspozycji tylko liczby dodatnie. Służy do tego przedrostek unsigned. Liczba typu unsigned char ma, jak się już pewnie spodziewasz, zakres od 0 do 255. Odpowiada to zakresowi numerów znaków z tablicy kodów ASCII, więc zmienna tego typu jest często używana do przechowywania kodów liter i znaków z tablicy ASCII.

   Dalej jest typ int. W systemach 32-bitowych zajmuje cztery bajty i może pomieścić wartości od -2147483648 do +2147483647. Oczywiście nikt nie każe ci tego zapamiętywać! Wystarczy jeśli wiesz, że są to duuże liczby ;->, a w razie wątpliwości możesz zajrzeć na tą stronę ;-). Również w tym przypadku można skorzystać z przedrostka unsigned i zrezygnować z liczb ujemnych na rzecz liczb dodatnich. Wtedy zakres zmiennej zmieni się na 0 do 4294967295. Jeśli nie potrzebujesz tak dużego zakresu możesz też oszczędzić pamięć używając przedrostka short, dzięki czemu zmienna typu short int zajmuje tylko dwa bajty i mieści liczby od -32768 do +32767. Gdy jeszcze dodamy przedrostek unsigned, zakres zmieni się oczywiście na 0 do 65535. Zmienna typu int przydaje się do przechowywania zwykłych liczb całkowitych.

   A co, jeśli potrzeba nam przechować ułamek dziesiętny? Do tego służy typ float. Zajmuje 4 bajty i służy do przechowywania liczb zmiennoprzecinkowych od 3.4*10-38 do 3.4*10+38. Jeśli jesteś jakimś naukowcem i precyzja do tylu miejsc po przecinku ci nie wystarcza, jest jeszcze typ double. Zajmuje 8 bajtów i służy do przechowywania liczb zmiennoprzecinkowych podwójnej precyzji, czyli od 1.7*10-308 do 1.7*10+308. A dla megamózgów jest typ long double. Zajmuje aż 10 bajtów i ma zakres 3.4*10-4932 do 1.1*10+4932. Wątpię jednak, żeby kiedykolwiek były ci potrzebne aż tak precyzyjne liczby [chyba że jesteś jakimś astrofizykiem :-P]. W normalnych programach w zupełności wystarczy typ float. Przykładowa liczba tego typu: 0.245.

   Do operacji logicznych przydaje się natomiast typ bool. Zmienna tego typu może przyjmować tylko wartości logiczne: 0 lub 1. Cyfrom tym przypisane są zwykle nazwy: dla 0 jest false [fałsz], a dla 1 jest true [prawda]. Jeśli przypiszesz do tej zmiennej jakąkolwiek niezerową wartość, kompilator i tak zamieni ją na 1. Przypisanie zera to już chyba nie muszę wyjaśniać ;-).

   Jak dojdziemy już do funkcji przyda ci się jeszcze jeden typ, a właściwie jego brak. Na przykład jeśli funkcja nie pobiera żadnych parametrów, trzeba to jakoś powiedzieć kompilatorowi. Wtedy stosuje się typ void, który oznacza brak jakiegokolwiek typu. Może ci się to narazie wydaje dziwne, ale nie bój nic! ;-) Wszystkiego się nauczysz w swoim czasie. Są jeszcze tak zwane typy złożone, ale nimi też zajmiemy się później.

Zmienne w praktyce

   Okej. Czas wypróbować działanie zmiennych w programie. Skoro konsola ma służyć do rozmowy z użytkownikiem, to przeprowadźmy taką rozmowę. Zapytamy użytkownika ile ma lat. Gdy odpowie, program pokaże użytkownikowi to, co przed chwilą wpisał. Do przechowania liczby lat posłużymy się zmienną o nazwie Latka. Przeciętny człowiek nie żyje dłużej niż 127 lat, więc wystarczyłaby nam zmienna typu char. Nie możemy jej jednak użyć, ale dlaczego, to ci powiem za chwilę. A narazie zmienna Latka będzie typu int. Tak wygląda deklaracja takiej zmiennej:

int Latka; Deklaracja mówi kompilatorowi, że chcemy używać w naszym programie takiej a takiej zmiennej. Jak widzisz, najpierw jest typ zmiennej, później nazwa, a na końcu średnik [bo każda instrukcja w C++ musi się kończyć średnikiem]. Taka instrukcja w systemach 32-bitowych zarezerwuje nam cztery bajty [32 bity] pamięci dla zmiennej Latka już na etapie kompilacji. Jaką wartość będzie mieć taka zmienna? To zależy od tego, gdzie umieścisz jej deklarację.

Jeśli deklaracja znajduje się "na czysto" w kodzie programu, poza jakąkolwiek funkcją czy innym blokiem zamkniętym w nawiasy klamrowe, to kompilator rezerwuje dla niej pamięć w sekcji danych. Pamięć ta jest czyszczona przed uruchomieniem programu, więc wartością naszej zmiennej będzie zero. Taką zmienną nazywać będziemy GLOBALNĄ, bo jest dostępna w całym programie.

Jeśli natomiast deklaracja znajduje się wewnątrz jakiegoś bloku lub funkcji [na przykład wewnątrz funkcji main], kompilator rezerwuje dla niej miejsce w specjalnej pamięci zwanej stosem. O takiej zmiennej mówimy, że jest LOKALNA dla tej funkcji i może być używana tylko wewnątrz niej. Poza funkcją zmienna po prostu nie istnieje, bo gdy funkcja się kończy, jej zmienne lokalne są usuwane ze stosu [zmienne lokalne to takie jakby zmienne tymczasowe ;-)]. Na stosie wciąż układane są różne wartości. Nie wiadomo co tam zastaniemy rezerwując pamięć dla naszej zmiennej [nigdy nie wiesz, co ci Gingers przyniesie :-P], więc jej wartość jest zazwyczaj przypadkowa. Dlatego sami musimy ustawić jej jakąś wartość. Są na to dwa sposoby, które nieco się różnią.

W pierwszym z nich najpierw deklarujemy zmienną [co wiąże się z zarezerwowaniem dla niej miejsca w pamięci], a dopiero w następnej instrukcji przypisujemy jej jakąś wartość. Wygląda to tak:
int Latka; //Deklaracja zmiennej Latka = 7; //Przypisanie jej wartości 7 Drugi sposób polega na nadaniu zmiennej początkowej wartości już w momencie, gdy jest ona tworzona. Tym razem nie jest to już przypisanie [mimo że wygląda podobnie], lecz inicjalizacja, czyli nadanie początkowej wartości. Tak to wygląda:
int Latka = 7; //Inicjalizacja Jak już wspomniałem, między pierwszym a drugim sposobem jest drobna różnica. Dla zwykłej zmiennej możemy bowiem użyć dowolnego z nich. Jednak w języku C++ istnieje jeszcze takie coś jak STAŁA. Stałą w programie może być np. liczba Pi, ładunek elektronu, masa Ziemi, liczba palców w jednej ręce, i tym podobne :-). Żeby ze zwykłej zmiennej zrobić stałą, trzeba dodać do jej typu przedrostek const. Jak sama nazwa wskazuje, wartość stałej nie może się zmieniać. Dlatego właśnie stałą możemy jedynie zainicjalizować w momencie tworzenia, a więc skorzystać z drugiego sposobu. Wszelkie próby przypisania do stałej kończą się błędem kompilacji. Tak deklarujemy stałą:
const float LiczbaPi = 3.1415926;    Ale wróćmy teraz do naszego programu. Mamy zadeklarowaną zmienną, pora coś do niej wpisać. Wiesz już, w jaki sposób wysłać tekst na ekran. A jak pobrać dane z klawiatury? W podobny sposób, tylko zamiast strumienia wyjściowego [cout] trzeba użyć strumienia wejściowego [cin] i dać odwrotnie strzałki.
cin >> Latka; Taka linijka spowoduje pobranie liczby ze strumienia wejściowego [czyli klawiatury] i wstawienie jej do zmiennej Latka. Strumień wejściowy jest wyjątkowo sprytny, bo sam rozpoznaje typ danych, który chcesz wczytać. Podobnie jest przy wysyłaniu zmiennej do strumienia wyjściowego. Tu również kompilator rozpoznaje, co chcesz wrzucić do strumienia i w odpowiedni sposób to wyświetli. I właśnie dlatego nie mogliśmy użyć zmiennej typu char - po prostu strumień pomyślałby, że chcesz pobrać z klawiatury jeden znak [tyle mieści się w zmiennej char], a co nam po jednym znaku? :-P  Gdy chcemy pobrać liczbę typu int, strumień postępuje inaczej. Pobiera kilka znaków [cyfr] i zamienia je na liczbę.

   Możemy już więc napisać cały program.
#include <iostream.h> int Latka; int main() { cout << "Hej, czlowiek!\nIle masz lat?" << endl; cin >> Latka; cout << "Spoko, masz " << Latka << " lat\n"; }    Jak to cholerstwo działa? Po kolei. Najpierw jest deklarowana globalna zmienna Latka typu int. Dalej zaczyna się funkcja main. Na ekran leci napis "Hej, człowiek!", tekst przerzucany jest do następnej linijki ekranu i dalej pisze "Ile masz lat?", po czym znowu tekst jest przełamywany. Wtedy program pobiera od użytkownika liczbę lat [z klawiatury] i umieszcza ją na przechowanie w zmiennej Latka. Na końcu na ekran leci napis, liczba lat ze zmiennej Latka, oraz reszta napisu. Proste? No to świetnie...

   Żeby był komplet, wspomnę jeszcze tylko o jednej rzeczy. Większe programy często są dzielone na kilka plików. Gdy kompilator kompiluje plik, to nie wie jakie zmienne zadeklarowano w innych plikach. Jak to zrobić, by zmienna zadeklarowana w jednym pliku była też dostępna w drugim? Trzeba w tym drugim pliku dopisać jeszcze raz tą deklarację, tyle że z przedrostkiem extern, tak jak to widać poniżej:
extern int Latka; Taka instrukcja mówi kompilatorowi: "Jakby co, to nazwa Latka oznacza zmienną typu int zadeklarowaną w jakimś innym pliku składającym się na program". Trzeba jednak uważać, żeby taka zmienna rzeczywiście była już zadeklarowana w jakimś innym pliku. Jeśli nie będzie, to Linker nie da rady połączyć plików wynikowych w gotowy program [bląd linkowania].

   No. To teraz wiesz już wystarczająco dużo. Możesz się pobawić i dopisać w programie inne pytania do użytkownika [np. ile ma nóg, ile jest 2+2 i takie tam bzdury ;-)].