Zmienne oraz ich rodzaje
Jak dotąd była tylko sucha teoria dotycząca programowania i nie tylko. Począwszy od tej lekcji zaczniemy powoli wkraczać w fascynujący świat C++. W tej lekcji dowiesz się czym są zmienne oraz jakie są ich rodzaje. Oczywiście będzie też wyjaśniony sposób definiowania, czyli tworzenia zmiennych. Jeśli znasz już jakiś język programowania to zapewne zauważysz pewne zbieżności w nazewnictwie, a jeśli nie to będziesz niestety musiał się tego nauczyć 🙁 Na pocieszenie powiem, że nie jest tego wiele, a przyswojenie całości przychodzi naprawdę łatwo i szybko. Tak więc zacznijmy.
zmienne
Określenie zmienna, jak nie trudno się domyślić tyczy się obiektów, które będą się zmieniać podczas wykonywania programu. To właśnie dzięki nim programy działają. Szczególnie jest to widoczne w przypadku gier akcji, gdzie wszystko zmienia się w przeciągu ułamków sekund! Każda zmienna lub ogólniej obiekt jest wydzielonym miejscem w pamięci. Posiada własną nazwę, dzięki której można się do niej odwołać oraz określoną wartość. Jest wiele rodzajów zmiennych. Dlatego też zmienna musi być określonego typu. Nazwa może być praktycznie dowolna. Powiedziałem praktycznie, bo są pewne znaki i słowa kluczowe, których w nazwach używać nie wolno. Jeśli chodzi o słowa kluczowe to znajdziesz je tutaj. Co do znaków to nie będę ich tu wymieniał, bo zabronione są prawie wszystkie 🙁 Zapamiętaj sobie: w nazwach stosuj jedynie litery, cyfry oraz podkreślnik. Pamiętaj również, że nazwa musi rozpoczynać się literą lub podkreślnikiem '_’.
Zmienne są wykorzystywane w każdym programie i to bez wyjątków. Zawsze bowiem zachodzi konieczność pamiętania pewnych informacji w czasie pracy programu. Weźmy dla przykładu prostacki notatnik z Windows’a. Wpisywany tekst gdzieś musi być zapisany. Do tego celu służy pamięć komputera. To tam znajdują się wszelkie informacje, do których dostęp powinien być natychmiastowy. Zdarzają się też sytuacje, kiedy program pracuje na pliku, który nie jest w stanie załadować się w całości do pamięci komputera. Wtedy ładowane są tylko aktualnie niezbędne dane. Reszta czeka sobie w spokoju na dysku 🙂 Tak właśnie działają niektóre edytory tekstu. Póki co wyjaśnię dokładniej czym jest definicja oraz jak ją zapisać.
definiowanie zmiennych
Definicja zmiennej to nic innego, jak jej utworzenie. Na definicję jak wspomniałem wcześniej składa się nazwa zmiennej, jej rodzaj oraz ewentualna wartość. Kuknij sobie na poniższy przykład:
1 |
typ nazwa_zmiennej; |
Podczas definicji najpierw podajemy typ zmiennej, a później jej nazwę. Zwróć uwagę na średnik kończący definicję. To on właśnie oznacza koniec instrukcji. Tak więc i tutaj można się pobawić 🙂 i zapisać to w ten sposób:
1 2 3 |
typ nazwa_zmiennej; |
Co prawda już o tym mówiłem, ale przypomnienie chyba nie zaszkodziło. Oczywiście nie należy kombinować i przełamywać wiersza w połowie nazwy zmiennej lub słowa kluczowego w taki sposób:
1 2 3 4 5 6 7 8 9 |
ty p nazwa_zmi ennej ; |
Ostateczne zakończenie stanowi średnik. Pamiętaj o tym! Natomiast całkiem poprawny byłby taki zapis:
1 2 3 4 5 |
typ zmienna ; |
Dzięki powyższej instrukcji w pamięci została utworzona zmienna. Za pomocą nadanej nazwy można teraz odwołać się do wartości w niej zapisanej. Można zapisać nową wartość lub odczytać już istniejącą. Ciekawe tylko jaką wartość ma nasza świeżo zdefiniowana zmienna? Tylko nie mów, że zero. Tak naprawdę to nie wiadomo [?] jaka wartość tkwi w nowo utworzonej zmiennej. Są to po prostu jakieś pozostałości po czymś, co przed definicją zmiennej tam się znajdowało. Dlatego też zmienne po utworzeniu zawierają jakieś śmieci. Jeżeli chcesz, aby zmienna miała konkretną wartość nadaną przy utworzeniu wystarczy do definicji dorzucić kilka informacji.
1 |
typ zmienna = wartość; |
W ten sposób zdefiniowaliśmy zmienną, którą zainicjalizowaliśmy [nie mylić z inicjacją 🙂 ] określoną wartością. Myślę, że każdy załapał podstawowe zasady definiowania zmiennych. Pamiętaj tutaj, że definicja oznacza automatycznie zarezerwowanie miejsca w pamięci na tworzoną zmienną. Jak widzisz definiowanie zmiennych jest proste. W powyższym przykładzie celowo nie napisałem konkretnego typu, abyś zrozumiał, iż definicje dotyczą wszystkich typów danych. Zarówno tych wbudowanych, o których będzie poniżej, jak i tworzonych przez programistę. Wiem, że narobiłem Ci smaczka 🙂 ale musisz
typy danych
Już wiesz czym są zmienne. Wiesz też jak je tworzyć. Nie wiesz tylko jednego. Jakie są rodzaje zmiennych. Cierpliwości. Zaraz się dowiesz. Jak zapewne pamiętasz każda zmienną musi posiadać nazwę. Oprócz nazwy należy jeszcze określić rodzaj [typ] zmiennej. Typ zmiennej stanowi informację dla kompilatora dotyczącą tego, ile należy przydzielić pamięci dla tworzonej zmiennej oraz jak się z nią obchodzić :). W C++ istnieje wiele typów. Początkowo ich ilość może być trochę przytłaczająca, więc radzę uczyć się ich kolejno. Bynajmniej nie na pamięć! Po prostu podczas pisania programu korzystaj jedynie z wybranego typu, a później dodawaj inne. W ten sposób zdołasz opanować całość i na pewno zostanie Ci to na długo. Typy zmiennych można podzielić na kilka grup. Typy do przechowywania liczb całkowitych, zmiennoprzecinkowych czy znaków. Nie będę tutaj przytaczał tego podziału, bo nie ma to sensu. Nie jest to niezbędne do zrozumienia istoty tego rozdziału. Tak więc nie będę dodatkowo komplikował. Ważne jest natomiast, abyś dobrze zrozumiał to, co powiem za chwilę. Nie jest to jakoś specjalnie skomplikowane, lecz są to podstawy i bez nich następne lekcje mogą być niejasne. Teraz objaśnię kolejno podstawowe typy występujące w języku C++. Część z nich wyjaśnię w tej lekcji. Innym poświęciłem osobne strony. Zdecydowałem się na taki podział, gdyż niektóre z typów są bardzo specyficzne i wymagają osobnego komentarza.
bool
Jest to chyba najprostszy typ jaki występuje w C++. Przechowuje informacje typu: prawda lub fałsz. Zajmuje 1 bajt pamięci. Stosuje się go często do określenia stanu jakiejś akcji np. odczyt pliku, kontrola urządzeń zewnętrznych. Wartość prawda oznacza sukces, a fałsz niepowodzenie. Istnieją dwa różne sposoby odnoszenia się do wartości zapisanej w zmiennej bool’owskiej. Można używać słów: false, czyli fałsz i true, czyli prawda. Dozwolone są też liczby, przy czym 0 oznacza fałsz, a inna nawet ujemna liczba prawdę. Definicja takiej zmiennej wygląda następująco:
1 2 3 4 5 6 7 |
bool zmienna; //definicja zmiennej typu bool zmienna = false; //przypisanie wartości 'fałsz' zmiennej zmienna = true; //przypisanie wartości 'prawda' zmiennej |
Podczas podstawiania wartości stosujemy operator przypisania. Ten operator to zwykły znak równości doskonale znany Ci z budy.
char
Char w rozwinięciu oznacza charakter [ale nie czarny charakter z bajki 🙂 ] Oczywiście słowo to pochodzi z języka angielskiego i oznacza literę. Obiekty tego typu służą do przechowywania pojedynczych znaków. Wbrew nazwie nie koniecznie muszą to być litery. Każdy znak jest zapisywany na jednym bajcie, jako kod ASCII. Dokładny opis znajdziesz tutaj. Oznacza to że obiekty typu char mogą przyjmować wartość z zakresu <0; 255> lub < -128; 127>. To jaki wariant zostanie użyty zależy od samej definicji. Dokładniej mówiąc zależy to od modyfikatora. Zaraz wyjaśnię. Definicja tych obiektów wygląda następująco:
1 |
char zmienna; |
Chyba nie wymaga to większego komentarza. Sytuacja jest analogiczna, jak przy typie bool. Co do przypisywania wartości do zmiennych char to doskonale to znasz ze szkoły. To właściwie byłoby wszystko poza małym detalem. Wspomniałem, że przy definicji można wybrać jeden z dwóch wariantów używając modyfikatorów. Jeżeli chcesz mieć zmienną przechowującą wartość z zakresu od 0 do 255 to musisz nieco zmodyfikować definicję. Musisz dodać do niej właśnie modyfikator. Nazwa trochę tajemnicza, ale wystarczy zobaczyć przykład i zrozumiesz.
1 |
unsigned char zmienna; |
Modyfikator unsigned oznacza bez znaku. Nie jest to do końca trafne tłumaczenie, ale trzeba się z tym pogodzić. Zapamiętaj sobie, że unsignedoznacza zawsze wartości nieujemne wraz z zerem. Istnieje także modyfikator signed, czyli ze znakiem. Zastosowanie tego modyfikatora oznacza wartości zarówno ujemne jak i dodatnie. Jeżeli jesteś ciekaw, jak są liczone zakresy to zajrzyj do działu systemy liczbowe. Korzystanie ze słowa signedjest jednak opcjonalne. Możesz je umieścić lub nie. Jeśli nie sprecyzujesz dokładnie o jaki rodzaj Ci chodzi to przez domniemanie zostanie użyty modyfikator signed. Zatem zapis:
1 |
signed char zmienna; |
jest równoznaczny z:
1 |
char zmienna; |
short
Bynajmniej nie chodzie tutaj o element garderoby 🙂 Tak na poważnie jest to skrót od short integer, czyli krótki całkowity [no comment 🙂 ] Zmienne typu short są zapisywane na dwóch bajtach. Oznacza to że mogą przechowywać wartości z przedziału < -32768; 32767>. Taki jest zakres oczywiście w przypadku, gdy nie określisz tego sam lub podasz modyfikator signed. Jeżeli użyjesz unsigned to będziesz mógł zapisywać wartości z zakresu <0; 65536>. I mały przykład:
1 |
short zmienna; |
Właściwie ten typ wystarcza w większości przypadków. Jeśli jednak to dla Ciebie za mało to możesz skorzystać z dwóch pozostałych.
int
Typ int jest skrótem od integer, czyli całkowity. Zapisywany jest na czterech bajtach, co daje zakres < -2147483647; 2147483647 > w przypadku signedi <0; 4294967296> dla unsigned. Definicja wygląda jednakowo, jak w przypadku powyższych.
1 |
int zmienna; |
long
Z typów całkowitych został już tylko jeden. Nazywa się long. Nazwa też bardzo wdzięczna. Pochodzi od long integer, czyli dla odmiany długi całkowity[wiem co pomyślałeś 🙂 ]. Co do rozmiaru to pojawia się dziwna sytuacja. Otóż zajmuje on tyle samo, co int. W związku z tym zakres tego typu też jest taki sam. Prawdę mówiąc to nie wiem jaka jest różnica pomiędzy typem int, a long. Tym razem przykładu nie będzie 🙁 bo wszystko jest [chyba] identyczne. Pora teraz przejść do typów zmiennoprzecinkowych.
float
Jak dotąd były jedynie typy mogące przechowywać wartości całkowite. Typ float jest jednym z typów zmiennoprzecinkowych i służy to przechowywania wartości z dokładnością do kilku miejsc po przecinku. Zajmuje cztery bajty. Jeśli chodzi o zakres to trudno przytoczyć jakiekolwiek liczby. Jak powiedziałem jest to typ zmiennoprzecinkowy w pełnym tego słowa znaczeniu. Mówiąc dosadniej występuje tutaj następująca zależność. Im większa liczba, tym mniejsza jest część ułamkowa. I na odwrót. Nadaje się on do programów, gdzie liczy się precyzja. Bynajmniej nie będzie on stosowany w programach matematycznych. Należy zaznaczyć, że w przypadku liczb zmiennoprzecinkowych nie obowiązują modyfikatory. Tak więc zapis:
signed float zmienna;
jest nie tyle nie na miejscu, co zabroniony. Oczywiście tyczy się to również modyfikatora unsigned. Sposób definicji jest oczywiście dalej ten sam.
1 |
float zmienna; |
double
Typ double również zalicza się do grupy zmiennoprzecinkowych. Zapisywany jest na ośmiu bajtach, co pozwala na przechowywanie liczb z większą dokładnością. Tutaj obowiązują te same zasady, jak przy typie float. Definicja również bez zmian.
1 |
double zmienna; |
long double
To jest ostatni typ liczbowy. Na zmiennych tego typu można zapisać duże liczby z ogromną dokładnością. Dlatego też zajmuje on w pamięci aż dziesięć bajtów! W definicji jest trochę inaczej. Jak dotąd podawaliśmy jedno słowo określające typ. Przy long double trzeba podać dwa.
1 |
long double zmienna; |
void
Jeżeli znasz angielski to zapewne nazwa wyda Ci się trochę dziwna i niezrozumiała. Void oznacza pusty, próżny, bezwartościowy. Tłumaczenie jest trochę mylące. Szczególnie w ostatnim przypadku. Przecież jak może być zmienna bezwartościowa :-/ Rzeczywiście to nieco zawiłe. Jednak ma to sens jeśli w programie istnieją zmienne, których rodzaj zostanie nadany dopiero w momencie wykonywania programu. Rozumiesz co to oznacza? Definiujesz sobie zmienną bliżej nieokreślonego typu. Później w zależności od np. reakcji użytkownika zostanie nadany odpowiedni typ dla zmiennej. Można powiedzieć, że jest to taki uniwersalny typ mogący przechowywać dowolne wartości. Wiem, że to zagmatwane, ale należy pamiętać o istnieniu tego typu. Tak naprawdę jest on wykorzystywany głównie w funkcjach, jako zwracany rezultat. Tutaj nie podam przykładu, gdyż wszystko zostanie wyjaśnione w rozdziale o funkcjach. Właściwie mógłbym na tym etapie nawet nie wspominać o typie void. Jednak chciałem, abyś przynajmniej miał o nim pojęcie.
typ wyliczeniowy enum
Typ wyliczeniowy enum jest również typem liczbowym, lecz dość charakterystycznym. Tworzenie takiego obiektu też jest odmienne i zupełnie nie pokrywa się z tym, co poznałeś wcześniej. Również sposób odnoszenia się do wartości zapisanych w nim jest niecodzienny. Sama definicja składa się tutaj z dwóch etapów. Najpierw trzeba zdefiniować listę wyliczeniową. Dopiero teraz można definiować sam obiekt. Prawda, że dziwne? Lista wyliczeniowa informuje kompilator, jakie wartości można podstawiać to obiektu. Może ona być dowolnie długa. Zerknij sobie na poniższy przykład.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
enum typ_enum { element_listy_1, element_listy_2, element_listy_3, element_listy_4, element_listy_5, }; //uwaga: to jest definicja typu, a nie samej zmiennej! typ_enum zmienna;//dopiero tutaj znajduje się definicja zmiennej |
Jak widzisz tutaj sprawa wygląda zupełnie inaczej. Najpierw musisz zdefiniować typ obiektu. To tak, jakbyś rozbudował C++ o nowy rodzaj zmiennej! Można powiedzieć, że w tym momencie narysowałeś plan domu, który za chwilę wybudujesz. Teraz możesz już korzystać z tego nowego typu, czyli wybudować domek 🙂 Tutaj już postępujemy po staremu, czyli typ i nazwa. Jak powiedziałem lista może być dowolnie długa. Nie zapomnij tylko o średniku na końcu. Sposób odnoszenia się do wartości zapisanej na typie enum też jest inny niż znany Ci do tej pory. Jednak żeby było prościej zdefiniujemy sobie kolejnego enum’a.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
enum plik_akcja {odczyt, zapis, kompresja, dekompresja, wydruk, }; //uwaga: to jest definicja typu, a nie samej zmiennej plik_akcja eplik;//dopiero tutaj znajduje się definicja zmiennej |
Ten typ służy do wykonywania pewnych operacji na pliku. Oczywiście są to tylko pseudoakcje. Tak naprawdę nie zostaną one wykonane. Operacje na plikach odbywają się zupełnie inaczej. Jednak dzięki takiemu typowi możemy wybrać żądaną akcję posługując się nazwą konkretnej czynności. Jest to wygodniejsze, aniżeli korzystanie z numerków. Typ enum został wymyślony z myślą usprawnienia programowania. Po zdefiniowaniu typu możemy już z niego korzystać. Robimy to w tradycyjny sposób. Teraz możemy podstawić do zmiennej eplik dowolną wartość z listy.
1 |
eplik = odczyt; |
Aby sprawdzić jaka wartość znajduje się aktualnie w eplik należy wykorzystać operator porównania. O operatorach traktuje cały następny rozdział.
1 |
eplik == 0; |
Zapamiętaj sobie. Podczas podstawiania wartości korzystamy z nazwy [u nas odczyt, zapis, kompresja itd.]. Natomiast przy odczycie używamy liczb. Każda pozycja z listy ma przyporządkowany numer. Numerowania następuje od zera. Możesz też ustalić, aby numerowanie rozpoczynało się od innej wartości. Jeśli chciałbyś, żeby odczyt miał liczbę 1 zamiast 0 musiałbyś w pierwszej linijce definicji typu enum dodać kilka informacji.
1 2 3 4 5 6 7 8 9 10 11 |
enum plik_akcja {odczyt = 1, zapis, kompresja, dekompresja, wydruk, }; |
Od teraz akcja odczytu będzie miała numer 1, a każda następne o numer większy. Zatem cała lista przesunie się o jedną pozycję w górę. Możesz także ustalić konkretny numer dla każdej akcji.
1 2 3 4 5 6 7 8 9 10 11 |
enum plik_akcja {odczyt = 1, zapis = 10, kompresja = 21, dekompresja = 22, wydruk = 31, }; |
union
To jest kolejny dość dziwny typ. Unia służy do przechowywania rozmaitych obiektów dowolnego typu. Podobnie jak poprzednio tutaj jest też sporo rozbieżności. Na wstępie chciałem powiedzieć, że unie mają dość mały udział w programowaniu. Według mnie są one nawet zbędne. Aby łatwiej można było załapać posłużę się tutaj pewną analogią z życia. Wyobraź sobie unie, jako duże pudełko, do którego możesz wrzucić co tylko chcesz. W pudełku jednocześnie może być umieszczony tylko jeden obiekt. W każdej chwili możesz uznać, że chcesz do pudełka włożyć inny przedmiot. Wyjmujesz ten będący zawartością pudła i umieszczasz w nim inny. Tak się składa, że unie to takie pudła, w których możesz przechowywać dowolne typy zmiennych. Raz może to być char, innym razem int czy short. To jakie typy mogą zostać umieszczone w unii zależne jest od samej definicji. Ty decydujesz co będzie potrzebne. Jeśli zapoznałeś się z typem enum będzie Ci łatwiej zrozumieć unie. Teraz zobacz jak się definiuje takie ustrójstwa 🙂
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
union unia_char_short_int { char c; short s; int i; }; //to jest definicja typu, a nie samej zmiennej union_char_short_int pudlo;//dopiero tutaj znajduje się definicja zmiennej |
Pierwsza część podobnie jak w przypadku typu enum stanowi definicję typu. To nasz projekt domu. Następnie jest samo wybudowanie chałupy. To już znamy. Definiowanie jest bardzo zbliżone do typu enum. Sprawa odnoszenia się do takich wartości wygląda zupełnie inaczej. Zatrzymajmy się na chwilę. W unii znajduje się jest z kilku obiektów. Żeby odnieść się do któregoś z nich należy najpierw powiedzieć kompilatorowi, o który obiekt chodzi. Teraz oprócz nazwy trzeba podać jeszcze jakby pod nazwę. Popatrz:
1 2 3 4 5 |
pudło.c = 'D'; pudło.s = 23; pudło.i = 453; |
W pierwszej linijce umieściliśmy w unii literę 'D’. Jest to znak, zatem należy go wstawić do zmiennej znakowej. Taką zmienną zdefiniowaliśmy sobie w unii. Wystarczy teraz określić, że chodzi właśnie o ten składnik unii. Najpierw podajemy nazwę zmiennej [u nas jest to pudlo], później dodajemy kropkę i pod nazwę. My nazwaliśmy sobie składnik char, jako 'c’. Tak więc cały zapis: pudlo.c odnosi się do składnika znakowego naszej unii. Teraz stwierdzamy, że chcemy mieć w unii obiekt typu short. Wystarczy skorzystać z nazwy tego składnika. Nosi on nazwę s. Postępując podobnie, jak poprzednio umieszczamy w nim liczbę 23. Na koniec korzystamy ze składnika int o nazwie i i umieszczamy w nim wartość 453. Wystarczy pamiętać, że aby dobrać się do obiektu należy podać nazwę wraz z kropką i pod nazwą. O typach na razie tyle. Wrócimy do tego tematu przy omawianiu struktur i klas. Są to też typy C++. Jednak ze względu na ich budowę ich opisy są gdzie indziej 🙁
zakresy ważności
O zakresach ciut nadmieniłem przy funkcji main. Teraz będzie nieco szerzej. Każda zmienna jest definiowana w jakimś zakresie ważności. Istnieje kilka zakresów ważności. Pierwszym z nich jest zakres globalny. Zakres globalny bywa też nazywany zakresem pliku. Globalny, czyli dostępny wszędzie. Jeżeli zmienna jest zdefiniowana przed funkcją main jest zmienną globalną. Oznacza to, że jest dostępna w całym programie. Taka zmienna posiada zakres globalny. Drugim zakresem jest zakres lokalny. Jeżeli uznasz, że pewne dane powinny być znane jedynie w części programu możesz utworzyć zakres lokalny. Jego zasięg jest znacznie mniejszy niż zakres pliku. Zakres lokalny oznacza się dwoma klamrami { i }. Klamra rozpoczynająca to { natomiast kończąca to } Zupełnie tak samo jak w funkcji main. Zmienne utworzone wewnątrz takiego bloku mają zakres lokalny. Są więc zmiennymi lokalnymi. Istnieją tylko w tym zakresie. Każda próba odwołania się do zmiennej tak zdefiniowanej spoza bloku, w którym się ona znajduje skończy się błędem kompilacji. Tworzenie zakresów daje większe możliwości co do nazewnictwa zmiennych. Jak dotąd nazwy zmiennych musiały być odmienne. Dwie o identycznych nazwach nie mogły istnieć jednocześnie. Dzięki zakresom można niejako trochę nagiąć tę regułę. Po prostu nie musisz dbać o unikalność nazw o ile istnieją one w różnych zakresach. Tak dla relaksu mały przykład.
1 2 3 |
int zmienna; int zmienna; |
Taki zapis jest zabroniony. Łatwo to zapamiętać w taki sposób. Aby odnieść się do danej zmiennej należy podać jej nazwę. Co w sytuacji, gdy istnieją dwie zmienne o jednakowo brzmiących nazwach? To tak jak w życiu. Zbieżność imion zawsze powoduje dwuznaczność. O którego Jasia chodzi 🙂 Można co prawda podać inne cechy odróżniające tego konkretnego Jasia i człowiek się domyśli. Jednak kompilator [na razie] takiej inteligencji nie posiada 🙁 i dwuznaczność jest tutaj uznawana za błąd. Dopuszczalna jest natomiast taka sytuacja:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
{ int zmienna; } { int zmienna; } |
Tutaj kompilator nie ma nic do gadania. Jest wyraźnie zaznaczone, że każda zmienna znajduje się w innym zakresie ważności.
modyfikatory
W zasadzie dwa z nich już poznałeś. Były to signed i unsigned. Teraz wyjaśnię dokładniej czym są modyfikatory oraz do czego mogą się przydać. Modyfikator, jak zdążyłeś się zorientować na przykładzie dwóch powyższych służą do zmodyfikowania rodzaju zmiennej. Pamiętasz jeszcze jak signed i unsigned wpływały na zmienne? Za ich pomocą mogłeś manipulować zakresem przechowywanej wartości. Teraz przedstawię jak jeszcze można wpłynąć na typ zmiennej.
const
Być może kojarzysz to słowo z lekcji fizyki. Jeśli tak to wiesz do czego ono służy. Jednak dla jasności napiszę. Modyfikator const służy do definiowania stałych. Stałe są znacznie rzadziej stosowane niż zmienne. Pomyślałeś pewnie: Przecież wystarczy utworzyć zmienną, nadać jej wartość i więcej nie modyfikować. Słusznie, ale po co ryzykować. Czasem można przez nieuwagę podstawić inną wartość do takiej zmiennej i co wtedy. Miało być pi = 3.14, a tu przypadkiem wstawiliśmy doń wartość 3. Błąd nie musi się ujawnić od razu. W końcu te dwie liczby są prawie identyczne, ale na pewno odczujesz jeśli pi = 1106.17 :-O Po co się narażać. Lepiej w takich sytuacjach zamiast zmiennej użyć stałej. Dzięki temu masz pewność, że jakakolwiek próba modyfikacji zostanie wykryte już na etapie kompilacji. Tym samym uchronisz się od błędów. Zważywszy, że definicja stałej jest również prosta.
1 |
const long double pi = 3,141592653; |
Dzięki powyższej instrukcji utworzona została stała o nazwie pi. Dodatkowo została ona zainicjalizowana wartością 3,141592653. Od teraz każda próba zmiany tej wartości spowoduje błąd kompilacji. Posłużę się tutaj małą analogią. Stałe można przyrównać do czytników CD. Dozwolony jest jedynieodczyt. Zapisać się nie da 🙁 Skoro operacja przypisania wartości do stałej kończy się błędem to nasuwa się prosty wniosek. Stałe można zainicjalizować jedynie w momencie definiowania. Później przepadło 🙁
volatile
Słówko volatile oznacza jednoznacznie ulotny. To określenie doskonale oddaje istotę zagadnienia. Oczywiście ulotne zmienne nie istnieją. Wartość zmiennej też nie zmieni się dopóki sami tego nie uczynimy. A jednak okazuje się, że pewne zmienne mogą zostać zmodyfikowane bez naszej woli :-/ Powiem więcej mogą zostać zmienione bez naszej wiedzy :-0 Pewnie myślisz, że to jest niemożliwe. Jeśli tak uważasz to masz rację, ale tylko w połowie. No dobra. Już mówię o co tutaj chodzi. Są czasem pewne specyficzne sytuacje, kiedy wartość zmiennej może ulec zmianie bez inicjatywy z naszej strony. Wyobraź sobie taką sytuację. Masz oprogramować jakiś podzespół elektroniczny. Niech to będzie czujnik wilgotności powietrza. Zauważ, że wilgotność może się zmieniać co kilka sekund. Jeżeli nasz program będzie sprawdzał stan czujnika co pół minuty to odczyty mogą być chwilami nieaktualne. Aby się przed tym ustrzec stosuje się właśnie modyfikator volatile. Informuje on kompilator, że dana zmienna jest zawiera ulotną wartość i na wszelki wypadek należy ją kontrolować częściej. Oczywiście ten modyfikator jest zbędny w programach pisanych na co dzień. W takich programach zmienne nie mogą się zmieniać od tak sobie. Ten modyfikator jest natomiast przydatny jeśli masz oprogramować jakiś układ sprzęgający. Zdecydowanie Ci się to nie przyda na w amatorskim programowaniu. Zresztą ja jeszcze nigdy z niego nie korzystałem. Po prostu nie było takiej potrzeby. Na koniec przykład użycia.
1 |
volatile int zmienna; |
Zmienna zdefiniowana w taki sposób będzie bacznie kontrolowana przez procesor.
register
Modyfikator register czasem może przyspieszyć wykonanie programu. Zmienne zdefiniowane z jego użyciem są umieszczane w rejestrze procesora. Jeśli znasz assemblera to wiesz co to oznacza. Dla wszystkich nie wtajemniczonych wyjaśnię, jakie są z tego korzyści. Otóż jeżeli zmienna zostanie umieszczona w jednym z rejestrów procesora to krótko mówiąc dostęp do niej jest znacznie szybszy niż do zwykłych zmiennych. Oczywiście w grę wchodzą nanosekundy, czyli 1/1 000 000 000 sekundy [nie pomyliłem się jedna miliardowa sekundy!] jednak w przypadku kilkuset wywołań ma to znaczenie. Należy jednak pamiętać, że register oznacza tylko prośbę dla procka, aby umieścił zmienną w rejestrze. Bowiem nie zawsze jest to osiągalne. Oczywiście nie próbuj wszystkim zmiennym w programie nadawać tego modyfikatora. Jest on przydatny głównie w przypadku różnego rodzaju pętli. O pętlach jeszcze nie mówiliśmy. Teraz powiem jedynie, że pętle służą do wykonania pewnej akcji określoną ilość razy. Tradycyjnie przykładzik.
1 |
register int zmienna; |
W ten sposób utworzyliśmy zmienną, do której dostęp będzie szybszy. Pamiętaj, że register nie oznacza żądania, a jedynie prośbę umieszczenia zmiennej w rejestrze.
static
Tutaj znowu jest niezbyt dobrana nazwa. Static oznacza statyczny, czyli jakby stały. Zmienne z tym modyfikatorem stałe wcale nie są! To w jaki sposób oddziała on na zmienną zależy od zakresu ważności zmiennej. Na razie omówię to na przykładzie zakresu pliku. Pozostałe będę omawiał sukcesywnie w miarę poznawania C++. Na początku wyjdę od przykładu. Załóżmy, że piszesz program w zespole. Każdy tworzy swój moduł. Wszystkie moduły dotyczą czego innego i są zupełnie niezależne od siebie. Jak wiesz w tym samym zakresie nie może istnieć kilka zmiennych o takiej samej nazwie. Jednak może pojawić się sytuacja, że zarówno w Twoim module jak i w module kolegi pojawią się takie same nazwy. Co wtedy? Można oczywiście zmodyfikować te nazwy, ale po co tracić czas na takie detale. Najlepiej zadbać o to na samym początku. Wystarczy skorzystać z modyfikatora static. Jeżeli użyjesz go dla obiektu globalnego będzie on znany jedynie w Twoim pliku. Looknij se 🙂
1 2 3 4 5 6 7 8 |
Twój plik: int zmienna; plik kumpla: int zmienna; |
Dla kompilatora jest wszystko w porządku. Pliki zostaną skompilowane bez problemów. Jednak w takiej sytuacji czeka Cię niemiła niespodzianka. Mianowicie linker widząc taki zapis zbuntuje się i wywali błąd. Aby tego uniknąć należy w jednym z plików lub najlepiej w obu przy definicji zmiennej umieścić modyfikator static. Zerknij na przykład.
1 2 3 4 5 6 7 8 |
Twój plik: static int zmienna; plik kumpla: static int zmienna; |
Teraz już masz pewność, że kolizja nazw nie spowoduje protestu linkera. Static ma jeszcze inne zastosowania, ale powiem o tym trochę później 🙁 Wierz mi chciałbym to napisać już teraz. Jednak aby zachować jakiś porządek muszę to odwlec na inną lekcję.
extern
Tym razem nazwa bardziej trafna. Extern znaczy tyle co zewnętrzny. Jest on stosowany w przypadku kiedy program jest rozbity na kilka plików. Powróćmy na chwilę do pojęcia definicji. Definicja to przydzielenie odpowiedniej ilości pamięci dla definiowanego obiektu. Okazuje się, że taka definicja jest także deklaracją. Słowa dość zbliżone do siebie jednak postaraj się zrozumieć podstawową różnicę. Raz jeszcze:
1 2 3 |
deklaracja - informacja dla kompilatora dotycząca tego czym jest tworzony obiekt definicja - jest zarówno deklaracją, ale dodatkowo rezerwuje pamięć dla tworzonego obiektu |
Pamiętaj także, że w jednym zakresie może istnieć wiele deklaracji tej samej zmiennej. Definicja może być tylko jedna. Sądzę, że Cię trochę naprowadziłem. Jeśli natomiast dalej nie kapujesz to możesz opuścić ten modyfikator. Jeśli mam być szczery to jeszcze nigdy z niego nie korzystałem. Wracając do tematu. Jest taka sytuacja. Napisałeś program. W programie znajduje się kilka obiektów globalnych. Teraz uważaj! Zdecydowałeś się na rozdzielenie programu na kilka plików. Pomyśl chwilę. Co zrobić ze zmiennymi globalnymi? Nie możesz umieścić ich definicji w każdym pliku. Kompilator zaprotestuje. Jeżeli umieścisz je w jednym pliku nie będą one znane w innych plikach. Oba sposoby odpadają. Pamiętasz jeszcze jaka jest różnica między definicją, a deklaracją? Należy z tego skorzystać. Po prostu w jednym pliku umieścić definicję wszystkich obiektów [informacja o zmiennych + rezerwacja miejsca w pamięci], a w pozostałych jedynie deklaracje [tylko informacja o obiekcie]. Robi się to za pomocą omawianego modyfikatora extern.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
plik A zawierający wszystkie definicje: int zmienna1; char zmienna2; long zmienna3; float zmienna4; plik B zawierający tylko deklaracje: extern int zmienna1; extern char zmienna2; extern long zmienna3; extern float zmienna4; plik C zawierający tylko deklaracje: extern int zmienna1; extern char zmienna2; extern long zmienna3; extern float zmienna4; extern long zmienna3; extern float zmienna4; |
Zwróć uwagę, że w pliku C umieściłem dwukrotne deklaracje zmiennej3 i zmiennej4. Wolno tak zrobić, gdyż jest to tylko deklaracja.
Autor: Kesay
[Artykuł pochodzi ze strony guidecpp.prv.pl, autor wyraził zgodę na publikację]