W jednej z poprzednich lekcji przedstawiłem podstawowe typy danych występujące w języku C++. Głównym celem modyfikatorów jest w pewnym sensie zmodyfikowanie istniejących typów danych. Oprócz tej funkcji istnieją też modyfikatory mające inne cele.
Może Cię zastanawiać, po co wprowadzać modyfikatory skoro można korzystać z podstawowych typów danych. W pewnym sensie masz nawet rację. Podstawowe, poznane dotychczas typy danych w podstawowych operacjach powinny Ci wystarczyć.
Sytuacja wygląda jednak trochę inaczej, kiedy na przykład potrzebujesz przeprowadzać operacje na bardzo dużych liczbach. Może się okazać, że wbudowane typy danych nie zawsze wystarczają. Wówczas trzeba stworzyć własny typ danych, który umożliwi przeprowadzanie operacji. Nie muszę Cię chyba przekonywać, że nie jest to zbyt łatwe rozwiązanie.
Często natomiast zdarza się jednak, że tak naprawdę do takich operacji wystarczą nam podstawowe typy danych, ale odpowiednio zmodyfikowane. Wtedy właśnie użyjemy modyfikatorów.
Modyfikatory mogą okazać się również bardzo przydatne, gdy pracujemy na bardzo dużej liczbie danych, ale dane te są tak małe, że wbudowane typy danych są dla nas "za duże". W ten sposób niepotrzebnie jest zużywana tak duża ilość pamięci dla jednej danej, podczas gdy tą tak naprawdę niewykorzystaną pamięć można by wykorzystać dla kolejnych danych. Do rozwiązania tego problemu również można wykorzystać modyfikatory.
Za pomocą tych dwóch modyfikatorów możemy zmniejszyć lub powiększyć rozmiar typu danych. W ten sposób możemy albo zaoszczędzić pamięć (typ short) lub umożliwić zapisanie większej liczby (typ long).
Modyfikatorów tych nie możemy jednak zastosować do dowolnego typu danych. Tak naprawdę możliwe są jedynie następujące połączenia:
short int - "pomniejszony" typ int
long int - "powiększony" typ int
long double - "powiększony" typ double
Używając modyfikatorów, musimy zdawać sobie sprawę z konsekwencji. Stosując modyfikator short sprawiamy, że typ będzie zajmował najprawdopodobniej mniej miejsca w pamięci, jednak będziemy mogli zapisywać poprawnie mniejsze liczby niż standardowo.
Z kolei stosując modyfikator long musimy wiedzieć, że typ będzie teraz zajmował najprawdopodobniej więcej miejsca w pamięci, ale w nagrodę będziemy mogli zapisywać większe liczby.
Przy okazji warto wspomnieć o wprowadzonej skróconej notacji. Deklarując w programie zmienną typu short int wystarczy, że napiszemy tylko short. Analogicznie, zamiast pisać long int wystarczy, że napiszemy tylko long. Dla typu long double nie istnieje skrócona notacja.
Za pomocą modyfikatorów signed i unsigned możemy określić, czy będziemy używać liczb ujemnych, czy z liczb ujemnych nie będziemy korzystali. Modyfikatorów signed i unsigned używamy jedynie do typów całkowitych (int, short int, long int) oraz typu znakowego char.
W efekcie możemy uzyskać następujące kombinacje:
signed int - unsigned int
signed short int - unsigned short int
signed long int - unsigned long int
signed char - unsigned char
Musisz wiedzieć, że modyfikator signed lub unsigned zostaje zawsze ustawiony, nawet jeśli go nie jawnie nie napiszemy. W przypadku typów całkowitych automatycznie jest ustawiany modyfikator signed.
Z kolei dla typu znakowego to, jaki modyfikator zostanie ustawiony, zależy wyłącznie od komputera i od kompilatora. Przyznam się, że ja do tej pory nie spotkałem się nigdy, aby został ustawiony modyfikator unsigned, jednak może się tak zdarzyć, dlatego niekiedy dla pewności dla typu znakowego lepiej jawnie napisać modyfikator.
Jeśli jeszcze nie wiesz, kiedy modyfikatory signed i unsigned mogą się przydać, oto przykład. Załóżmy, że chcemy zapisać jak największą liczbę całkowitą. Jakiego typu użyjemy? Użyjemy typu signed long int (czyli w uproszczeniu long int), bowiem może nam chodzić zarówno o liczby dodatnie, jak i ujemne.
A co jeśli wiemy, że będziemy operować na dużych liczbach całkowitych dodatnich? Wtedy z kolei użyjemy typu unsigned long int. Podobnie, gdy będziemy chcieli korzystać jedynie z małych liczb, wówczas odpowiednie okażą się typy signed short int lub unsigned short int.
Oto grupa trzech modyfikatorów, z których tak naprawdę tylko jeden jest często wykorzystywany.
volatile - zmienna może się zmieniać bez wiedzy kompilatora
register - zależy nam, żeby dostęp do zmiennej był jak najszybszy
const - zmienna ma być stała
Podejrzewam, że z krótkich wyjaśnień obok modyfikatorów niewiele rozumiesz. Ja też niewiele bym zrozumiał. Dlatego też poniżej znajdują się dokładniejsze wyjaśnienia.
Modyfikator volatile mówi kompilatorowi, żeby nie dokonywał żadnych przyspieszeń w dostępie do zmiennej. Tak naprawdę może to być przydatne tylko wtedy, gdy wartość zmiennej jest pobierana z jakiegoś zewnętrznego urządzenia (np. czujnika). Podejrzewam, że długo z tego modyfikatora nie skorzystasz.
Z kolei modyfikator register sugeruje, aby kompilator umieścił zmienną w rejestrze. Dzięki temu dostęp do takiej zmiennej będzie nieco szybszy niż w normalnej sytuacji. Kompilator nie zawsze jednak weźmie pod uwagę użyty przez nas modyfikator. Z tego modyfikatora tak naprawdę raczej też nie będziesz korzystać, przynajmniej na początku.
Na zakończenie dodam, że modyfikatorów volatile, register i const można stosować do wszystkich poznanych do tej pory typów danych (czyli m.in. int, short, unsigned short, char, double, long double, float itd.).
W stosunku do grupy trzech modyfikatorów: volatile, register i const, modyfikator const będzie wykorzystywany przez Ciebie najczęściej. Dlatego też postanowiłem omówić ten modyfikator oddzielnie.
Przede wszystkim, czas zdradzić tajemnicę do czego służy modyfikator const. Otóż modyfikator ten służy do "powiedzenia" kompilatorowi, że zmienną z tym modyfikatorem traktujemy jako stałą i nie będziemy dokonywać na niej żadnej modyfikacji.
Czy takie podejście tak naprawdę jest konieczne? Przecież kompilator za nas zmiennych nie zmieni, a to my zdecydujemy, czy chcemy zmienić daną zmienną czy nie. Rzeczywiście, jest w tym dużo prawdy. To od nas zależy, czy daną zmienną będziemy zmieniać w trakcie działania programu czy nie.
Dlaczego zatem stosować modyfikator const? Odpowiedź jest prosta - dla własnej wygody. Załóżmy, że w programie będziesz mieć 100 zmiennych. Czy po kilku tysiącach linii programu będziesz nadal pamiętać, której zmiennej nie chcesz nigdy zmieniać? Podejrzewam, że łatwo by Ci było o tym zapomnieć i w ten sposób zmienić przypadkowo wartość jakiejś zmiennej.
Dzięki zastosowaniu modyfikatora const wprowadzasz pewne ograniczenie na siebie - mówisz, że nigdy zmiennej zmieniać nie będziesz. Skutkuje to tym, że jeśli dalej w programie usiłujesz zmienić jakąkolwiek zmienną z modyfikatorem const, kompilator zaprotestuje i program się nie skompiluje. W ten sposób udało się sprawić, że nie musisz pamiętać, których zmiennych nie należy zmieniać - jeśli spróbujesz dokonać zmiany na zmiennej z modyfikatorem const, kompilator zacznie krzyczeć, że to błąd.
Kluczową sprawą jest to, że jeśli chcesz powiedzieć, że jakiejś zmiennej nie będziesz nigdy zmieniać, musisz od razu określić jaką ma ona wartość. Dlatego też nie jest możliwe pobranie takiej wartości na przykład z klawiatury czy z pliku.
Poza tym, że wartość zmiennej musi zostać określona od razu i że wartości nie będzie można już nigdy zmienić, zmienna z modyfikatorem będzie się zachowywała tak jak każda inna zmienna.
Poniżej przedstawiam kilka fragmentów kodu z zastosowaniem modyfikatora const. Dzięki nim wszystko już będzie jasne.
const int zmienna; // blad - nie przypisalismy zadnej wartosci
const double pi=3.14; // dobrze - przypisalismy wartosc
const double pi=3.14; // dobrze - przypisalismy wartosc
pi=2; // blad - nie wolno zmieniac wartosci zmiennej z modyfikatorem const
Już wiesz, że aby tworzyć stałe w programach, możesz użyć modyfikatora const. Mimo to istnieje, jeszcze inna metoda, jednak nie polecam jej stosowania.
Otóż ta metoda polega na wykorzystaniu dyrektywy preprocesora: define O ile dobrze pamiętasz, takie dyrektywy zawsze poprzedzamy znakiem #. Zawsze musimy taką dyrektywę umieścić tam, gdzie umieszczamy dyrektywę include, czyli na początku programu (to jest wada).
Nie będę się rozwodzić nad tą metodą, bowiem jak już napisałem, odradzam Ci jej stosowania. Zauważ tylko, że nie używamy tutaj nigdzie typu zmiennej (inaczej niż w przypadku modyfikatora const). Nazwa zmiennej jest ponadto napisana dużymi literami - ale tylko dlatego, że tak się przyjęło, że stałe definiowane za pomocą dyrektywy define, pisze się dużymi literami (nie jest to obowiązkiem).
W tej lekcji przedstawiłem przede wszystkim modyfikatory, dzięki którym możemy rozbudowywać podstawowe typy danych lub nadawać im charakterystyczne właściwości. Teraz już wiesz w jaki sposób tworzyć zmienne nie przechowujące znaku (unsigned) lub tworzyć stałe zmienne (const). Dodatkowo, znasz już metodę definiowania stałych w programie za pomocą dyrektywy preprocesora #define i wiesz, że znacznie lepiej tworzyć stałe zmienne przy użyciu modyfikatora const.