160 57 2MB
Polish Pages 104 [101] Year 2015
Wszelkie prawa zastrzeżone. Nieautoryzowane rozpowszechnianie całości lub fragmentu niniejszej publikacji w jakiejkolwiek postaci jest zabronione. Wykonywanie kopii metodą kserograficzną, fotograficzną, a także kopiowanie książki na nośniku filmowym, magnetycznym lub innym powoduje naruszenie praw autorskich niniejszej publikacji. Wszystkie znaki występujące w tekście są zastrzeżonymi znakami firmowymi bądź towarowymi ich właścicieli. Autor oraz Wydawnictwo HELION dołożyli wszelkich starań, by zawarte w tej książce informacje były kompletne i rzetelne. Nie biorą jednak żadnej odpowiedzialności ani za ich wykorzystanie, ani za związane z tym ewentualne naruszenie praw patentowych lub autorskich. Autor oraz Wydawnictwo HELION nie ponoszą również żadnej odpowiedzialności za ewentualne szkody wynikłe z wykorzystania informacji zawartych w książce. Redaktor prowadzący: Michał Mrowiec Projekt okładki: Jan Paluch Fotografia na okładce została wykorzystana za zgodą Shutterstock.com Wydawnictwo HELION ul. Kościuszki 1c, 44-100 GLIWICE tel. 32 231 22 19, 32 230 98 63 e-mail: [email protected] WWW: http://helion.pl (księgarnia internetowa, katalog książek) Drogi Czytelniku! Jeżeli chcesz ocenić tę książkę, zajrzyj pod adres http://helion.pl/user/opinie/zaprcn_ebook Możesz tam wpisać swoje uwagi, spostrzeżenia, recenzję. ISBN: 978-83-283-2296-7 Copyright © Helion 2016
Poleć książkę na Facebook.com Kup w wersji papierowej Oceń książkę
Księgarnia internetowa Lubię to! » Nasza społeczność
Spis treści Rozdział 1. O co tu chodzi? — opanujesz to w tydzień :) . ................................... 5 Rozdział 2. Rozpoczęcie pracy — zainstaluj odpowiednie programy . ................... 7 Rozdział 3. Pierwszy program — już programujesz! . ......................................... 13 Rozdział 4. Biblioteki i funkcje — jak to działa? . .............................................. 19 Rozdział 5. Zmienne i stałe — niezbędne elementy . ......................................... 23 Rozdział 6. Trochę więcej o funkcjach i zmiennych — zaczynamy rozwijać skrzydła . .................................................... 27 Rozdział 7. Tablice — bardzo przydatna sprawa . .............................................. 33 Rozdział 8. Instrukcje warunkowe — ważne i proste . ....................................... 37 Rozdział 9. Operatory — tak naprawdę znasz to już od podstawówki . ................. 41 Rozdział 10. Podstawy logiki. Nuda? Wcale nie! . ............................................... 45 Rozdział 11. Pętle. Po co to? Po co to? Po co to? . ............................................ 51 Rozdział 12. Operacje wejścia i wyjścia oraz podstawowa obsługa błędów. Nareszcie! . .................................................................................... 57 Rozdział 13. enum i typedef — przydatne bajery . ............................................... 63 Rozdział 14. Struktury i unie. Liczyłem, że coś takiego wymyślono . .................... 67 Rozdział 15. Wskaźniki — pora na małe wyzwanie . ............................................ 71 Rozdział 16. malloc i free oraz stos i sterta, czyli kilka słów o pamięci operacyjnej . ........................................... 75
4
Zabawa w programowanie. Język C dla nastolatków
Rozdział 17. Operacje na plikach i parametry wejściowe programu — zawsze chciałem to zrobić . ........................................................ 79 Rozdział 18. Preprocesor, kompilator i linker — to tylko groźnie brzmi . .............. 83 Rozdział 19. Pliki nagłówkowe oraz static i extern, czyli jak dzielić program i dlaczego? . ............................................. 89 Rozdział 20. Na koniec: to dopiero początek wspaniałej przygody! :) . ................. 95 Skorowidz . .................................................................................... 97
Rozdział 1.
O co tu chodzi? — opanujesz to w tydzień :) Nie lubię lać wody i nie będę tego robił w tej książce, dlatego w prostych, żołnierskich słowach napiszę, o co tu chodzi. Jestem programistą z kilkuletnim doświadczeniem, ale zaczynałem tak jak Ty, Drogi Czytelniku. Z zerową wiedzą i książką w ręku. Na dodatek nie miałem wtedy internetu, który bardzo ułatwia naukę i pracę w dzisiejszych czasach. Niestety napotkałem jeden problem, wszystkie książki, z którymi miałem do czynienia, czytało się tak, jakby były napisane przez uczelnianych wykładowców i dla co najmniej średnio zaawansowanych studentów. I po co to wszystko, skoro przygodę z programowaniem można zacząć znacznie wcześniej? Śmiem nawet twierdzić, że w szkole podstawowej. Dlatego powstała ta książka. Ona rozwiązuje opisany wyżej problem. Stworzyłem ją na bazie moich wspomnień i doświadczeń. Po jej przeczytaniu będziesz mógł śmiało powiedzieć, że znasz podstawy programowania w języku C, a jest to jeden z najważniejszych języków programowania w informatyce. Dzięki programowaniu uczymy się logiki, konsekwencji i analitycznego sposobu myślenia, a to znacznie ułatwia życie. Ten rozdział, mimo że jest w książce pierwszy, napisałem na samym końcu, kiedy usiadłem wygodnie w fotelu i przeczytałem po kolei pozostałe rozdziały. Uważam, że udało mi się osiągnąć swój cel, czyli przekazać wiedzę we w miarę prosty i zrozumiały sposób. Czy zdarzyło Ci się, że na przykład na lekcjach matematyki lub fizyki nie rozumiałeś jakiegoś tematu, aż w końcu nadszedł ten upragniony moment „oświecenia”? Z programowaniem jest tak samo, dlatego nie wolno Ci się zniechęcać! Jeżeli będziesz robił wszystko krok po kroku, cierpliwie, aż do skutku, to za pomocą tej książki odkryjesz nowy, pasjonujący świat. Myślę, że na tym skończymy tę nudną część i zaczniemy to, co najciekawsze. Pragnę zadedykować tę książkę mojej wspaniałej żonie Ani, dzięki której spełniłem swoje marzenia i zostałem programistą.
6
Zabawa w programowanie. Język C dla nastolatków
Rozdział 2.
Rozpoczęcie pracy - zainstaluj odpowiednie programy
��
��'S'
· my najpierw nieco przygotować Aby móc rozpocząćprzygodę z program u operac!'j?ego Linu�, prawdopo?ob n�s� ko�puter. Jeśli jesteś użytkownikiem s me Jestes na tyle zaawansowany, że mo· sz oa razu przeJsc do rozdziału 3. Jeżeh na t�miast używasz środowiska Window �1edz, że i tak ?�dziemy kor�stać z s�s�emu Lmux :). Dlaczego? Bo dla prog�ow Jest dużo bardzieJ elastyczny 1 wygodmeJszy, a w informatyce spotkacie go(1))i� razy częściej, więc najlepiej zacząć się z nim oswajaćjak najszybciej. Ale be�aw! Poradzimy sobie z tym szybko i bezboleśnie. Po prostu wewnątrz system intlows utworzymy drugi G akby sztuczny) komputer, na którym zainstalujem s inux, a dokładniej jego specjalną wersję o nazwieUbuntu. Taką sztuczną mas azywamy maszyną wirtualną i w informatyce wykorzystuje się je bardzo często:4... W"_. ty celu musimy pobrać z intemetu systemUbuntu (który jest cał kowicie darm�systemem operacyjnym) oraz program, który potrafi tworzyć maszy ny wirtualne. Skorzystamy z programu Virtualbox.
�
�
�
Aby pobraćUbuntu, wejdź na stronę: http:l!www. ubuntu. com/download/desktop/ thank-you ?country=PL&versżon= 14.04.2 &archżtecture=ż386
lub pobierz dowolny system operacyjny Linux w innej wersji. Aby pobraćprogram Virtualbox, wejdź na stronę: https://www. vżrtualbox. orglwżkż/Downloads
Znajdź napis VżrtualBox XXXXfor Windows hosts i naciśnij przycisk z jego prawej strony (X.X.XX oznacza aktualną, najnowszą wersję). Po chwili powinno rozpocząćsię pobieranie.
8
Zabawa w programowanie. Język C dla nastolatków Teraz pora na instalację programu Virtualbox. Kliknij Uruchom pobrany plik i przejdź przez prosty proces instalacji. Na pulpicie powinna pojawić się ikona programu Virtual box, a program powinien uruchomić się automatycznie. Skompletowaliśmy już system operacyjny i odpowiedni program do jego ożywienia. Pora zacząć zabawę.
W programie Virtualbox klikamy przycisk New. W kolejnym oknie uzupełniamy wszyst ko zgodnie z rysunkiem 2.1 i klikamy przycisk Next.
Rysunek 2.1. Pierwsze okno w programie Virtualbox
Create
Vi rtua l Machine
INarne and operatingsystem
� �":.
Please choose a descriptive name for the ne'lll virtual e type of operating system you intend to install on it. b• ""d thmoghoot wru'IB"' to ;deotify t!_ame:
Ubuntul
and select the . e you choose 1Nill
""'
•
�u wiamy rozmiar pamięci na 512MB i ponownie klikamy Next. W kolejnym o� �bieramy opcję Create a vżrtual hard drżve naw, klikamy Create,
W następnym o
po czym w kolejnych dwóch oknach klikamy Next, a w ostatnim ponownie Create.
W tym momencie mamy utworzony wirtualny komputer (spójrz na rysunek 2.2), na któ rym musimy zainstalować system operacyjny Ubuntu. Teraz naciskamy żółty przycisk Settżngs, po czym przechodzimy do zakładki Storage -patrz rysunek 2.3. Po prawej stronie, obok napisu IDE Secondary Master, naciskamy ikonę płyty CD i wy bieramy Choose a Vżrtual CDIDVD dżsk file... . Otworzy nam się okienko, w którym musimy zaznaczyć wcześniej pobrany system operacyjny Ubuntu. Po wszystkim naci skamy przycisk OK i na samej górze, obok wcześniej wybieranego Settżng, wybiera my zielony przycisk Start. W tym momencie uruchomiliśmy nasz wirtualny komputer, który ma tak jakby włożoną płytę instalacyjną z systemem operacyjnym Ubuntu. Pozostał nam już ostatni krok-jego zainstalowanie.
Rozdział 2.
+
9
Rozpoczęcie pracy - zainstaluj odpowiednie programy
�� Name:
�bng Sysbem: rlfi
System 51.2MB
Base Memory: Boot Order: Accele a o :
\._
,�m
U buntu
Floppy, CD/DVD, Hard Disk VT-x/AMD-V, Nesbed Paging, PAE/NX
rbn
�------J
olsplay
Video Memory:
Remole Desktop Server:
\_
Preview
Ubunh.J Ubunh.J ( 32 bit)
"ideo Caph.Jre:
,
12MB
Disabled Disabled
rll2?J Storage Controller: IDE IDE S econdary Masber: Controller: SATA SATA Port 0:
[CD/DVD]
Empty
Ubunh.J.vdi (No m al, 8,00 GB)
r
Windows DirectSound
ICH AC97
Rysunek 2.2. Program Virtualbox po dodaniu sys 8
llbuntu-
Setting�
�
Genera l
iil
System
�
Display
�
Storag e
�
Audio
�
Network
t§l
Serial
{J Ll
Po·rts
USB Shared Folder>
Attributes -----CD/DVD Drive:
�
l Gl.
rJ Live CD/DVD
Information ------
�
Ubuntu.vdi
Type: Size:
Location: Attached to:
OK
Rysunek 2.3.
lIDE Secondary Master
-
--
ll
cancel
ll
Help
Ustawienia systemu operacyjnego Ubuntu w programie Virtualbox
Po pewnym czasie uruchomi się instalator systemu Ubuntu i ukaże się nam ekran zgodny z rysunkiem 2.4.
10
Zabawa w programowanie. Język C dla nastolatków
Rysunek 2.4. Ekran instalacyjny systemu operacyjnego Ubuntu
Po lewej stronie wybieramy język English, a po prawej stronie naciskamy przycisk o nazwie Install Ubuntu. W następnym oknie nie wybieramy żadnej opcji, tylko wybieramy Continue. W kolejnym oknie zaznaczamy opcję Erase disk and install Ubuntu, po czym naciskamy przycisk Install Now. Pojawi się małe okienko, w którym naciskamy Continue. W tym momencie powinna rozpocząć się instalacja systemu Ubuntu. W trakcie instalacji pojawi się okno z wyborem strefy czasowej. Zaznaczamy dowolną opcję, po czym naciskamy przycisk Continue. W następnym oknie również naciskamy Continue, po czym pojawi się najważniejsze okno instalacyjne. Wypełniamy je zgodnie z rysunkiem 2.5 i naciskamy Continue (w polach Choose a password oraz Confirm your password wpisujemy słowo user). Drogi użytkowniku. Niech nigdy w życiu nie przyjdzie Ci do głowy wpisywanie tak banalnych nazw użytkownika, komputera oraz haseł. Robimy to tutaj na potrzeby książki, żeby było szybko, łatwo i przyjemnie oraz dlatego, że jest to jedynie maszyna wirtualna. W „normalnym świecie” taka praktyka jest NAGANNA.
Teraz musimy jedynie poczekać na zakończenie instalacji systemu. Na sam koniec pojawi się małe okienko, w którym klikamy przycisk Restart now. Po chwili system może Cię poprosić o wyjęcie płyty instalacyjnej z napędu. W tym celu wejdź ponownie w Settings, a następnie w Storage i naciśnij lewym przyciskiem myszy ikonę płyty, którą wcześniej wkładałeś (zgodnie z rysunkiem 2.3). Następnie kliknij po prawej stronie
Rozdział 2. Rozpoczęcie pracy — zainstaluj odpowiednie programy
11
Rysunek 2.5. Wybór podstawowych ustawień systemu Ubuntu podczas procesu instalacji
ikonę z obrazem płyty CD i wybierz Remove disk from instalation drive. Gdybyśmy tego nie zrobili, po ponownym uruchomieniu wirtualnej maszyny doszłoby do kolejnej próby zainstalowania systemu Ubuntu. Po całej operacji naciśnij OK. Na naszym wirtualnym komputerze zainstalował się system Ubuntu. Aby go uruchomić, wystarczy teraz nacisnąć zielony przycisk Start, widoczny na rysunku 2.2. Pamiętaj, że wirtualny system operacyjny zachowuje się tak jak prawdziwy. To oznacza, że za każdym razem, kiedy kończysz na nim pracę, musisz go prawidłowo zamknąć. Jedną z metod, aby to zrobić, jest naciśnięcie w lewym górnym rogu ekranu przycisku System, a następnie wybranie opcji Shut Down. Jeśli nie zamkniesz poprawnie systemu operacyjnego, wszystko, co zrobiłeś, może ulec zniszczeniu!
Do zakończenia tego rozdziału została ostatnia rzecz. Musisz wiedzieć, jak należy poruszać się w zainstalowanym systemie operacyjnym. Do programowania będą Ci potrzebne jedynie dwa programy. Dlaczego dwa i do czego będą służyć? Wszystkie szczegóły poznasz w dalszej części książki. Na razie się tym nie przejmuj. Pierwszy program to taki, w którym będziesz wpisywał tekst. Użyjemy do tego programu gedit. Drugi program będzie nam służył do tego, aby uruchomić to, co napiszemy w pierwszym programie. Nazwa drugiego programu to Terminal.
12
Zabawa w programowanie. Język C dla nastolatków
Jeśli masz jakieś pytania dotyczące programu Virtualbox lub systemu operacyjnego Linux/Ubuntu, większość potrzebnych informacji znajdziesz w internecie lub w innych książkach. Tutaj nie jest nam to potrzebne, więc nie będę się na ten temat rozpisywał. Moim celem jest nauczyć Cię programowania i na tym będziemy się skupiać. Najnudniejsza część już za nami! Pora na samo programowanie. Robiłeś kiedyś coś tak wciągającego, że nie mogłeś się oderwać? Zobaczysz, że programowanie właśnie takie jest.
Rozdział 3.
Pierwszy program — już programujesz! Masz już wszystko, co jest potrzebne do programowania w języku C, więc pora zaczynać! Uruchom edytor tekstu: 1. Naciśnij ikonę w lewym górnym rogu ekranu, tuż pod napisem Ubuntu Desktop. Po pojawieniu się nowego okna wpisz słowo gedit, a następnie
poniżej wybierz lewym przyciskiem myszy program o nazwie Text Editor. 2. W programie gedit wybierz File, a następnie Save As... Teraz zapisz plik
w katalogu /home/user pod nazwą program031.c 3. W treści pliku wpisz kod z listingu 3.1. Listing 3.1. Plik program031.c. Pierwszy program 1. 2. 3. 4. 5. 6. 7.
#include int main() { printf("Wlasnie zostales programista!\n"); return 0; }
Nie zapominaj, aby za każdym razem, gdy w programie wprowadzisz jakiekolwiek zmiany, zapisywać plik poprzez naciśnięcie przycisku Save. Właśnie napisałeś swój własny program w języku C. Niestety komputer nie wie, co z nim zrobić. Język C jest zrozumiały dla ludzi, ale nie dla maszyn. To tak jak np. Francuz nie porozumie się z Polakiem bez znajomości choćby jednego wspólnego języka. Potrzebujemy tłumacza i takiego tłumacza posiadamy. Nazywa się on kompilatorem, a wspomagają go dodatkowo preprocesor i linker. Jeszcze nie musisz rozumieć tych nazw. Poznasz je na końcu książki. Teraz musisz jedynie wiedzieć, jak poprosić swego „tłumacza” o pomoc.
14
Zabawa w programowanie. Język C dla nastolatków
Pamiętaj, że żaden znaczek nie jest tu przypadkowy. Żadna kropka, średnik czy spacja. Komputer jest urządzeniem niezwykle precyzyjnym, więc programując, musisz być bardzo ostrożny. Zauważ również, że w kodzie programu nie używamy polskich liter, ponieważ czasami zamiast nich możemy otrzymać dziwne znaczki. Z tego powodu będziemy trzymać się tej zasady aż do końca książki. Pamiętaj również, że wszystkie programy w tej książce zostały przeze mnie przetestowane i na 100% działają. Jeżeli u Ciebie występuje jakiś błąd, to przyczyną prawdopodobnie jest niedokładnie przepisany program.
W tym celu: 1. Naciśnij ikonę w lewym górnym rogu ekranu, tuż pod napisem Ubuntu Desktop. Po pojawieniu się nowego okna wpisz słowo terminal, a następnie
poniżej wybierz lewym przyciskiem myszy program o nazwie Terminal. 2. W Terminalu wpisz poniższą komendę: cd /home/user
a następnie wciśnij Enter, aby wykonać tę komendę. 3. Wpisz w Terminalu komendę ls, naciśnij Enter i sprawdź, czy znajduje się
tam nasz plik program031.c. Jeśli go tam nie ma, upewnij się, że wszystkie wcześniejsze komendy wykonałeś poprawnie. 4. Jeśli wszystko przebiegło poprawnie, wpisz w Terminalu: gcc program31.c -o prog
i oczywiście naciśnij Enter. Pamiętaj, że możesz zmieniać program gedit na Terminal i odwrotnie za pomocą kombinacji klawiszy Alt+Tab. W tym celu naciśnij i przytrzymaj klawisz Alt, a następnie naciśnij jeden raz lub kilka razy klawisz Tab. Powinieneś również wiedzieć, że czasami system Ubuntu zablokuje ekran (jeśli będziesz zbyt długo nieaktywny) i poprosi o wpisanie hasła w celu jego odblokowania. Hasło podawaliśmy przy instalacji systemu i brzmi ono user.
Wspaniale! Udało Ci się przetłumaczyć Twój pierwszy program napisany w języku C na język komputera. Twój tłumacz o nazwie gcc przetłumaczył plik program031.c na zrozumiałą dla komputera wersję o nazwie prog. Teraz rozumiesz już znaczenie powyższej linii. Jeśli zmienisz cokolwiek w swoim programie, to znowu musisz poprosić „tłumacza”, czyli program gcc, o ponowne tłumaczenie. To tak, jakby polski kucharz wydał nową wersję swojej książki kucharskiej. Francuz, który ma starą wersję książki, nie dowie się o zmianach, chyba że tłumacz ponownie ją przetłumaczy.
Rozdział 3. Pierwszy program — już programujesz!
15
Nasz program jest gotowy do uruchomienia! Aby go odpalić, musimy wpisać w Terminalu: ./prog
i potwierdzić klawiszem Enter. W tym momencie na ekranie powinna pojawić się następująca informacja: user@computer:~$ ./prog Wlasnie zostales programista! user@computer:~$ ~$ ./prog
Jak to się stało? Za parę sekund wszystko zrozumiesz. Wytłumaczę Ci to nie jak inni programiści, lecz jak osoba, która kiedyś próbowała to zrozumieć tak jak Ty. Przeanalizujmy, jak to zrobiłeś. Spójrz na program031.c. Zaczniemy od tej linijki: printf("Wlasnie zostales programista!\n");
Słowo printf informuje komputer, że chcesz wypisać na ekranie tekst. Wybrany tekst wpisujesz w okrągłym nawiasie i w cudzysłowie, w ten sposób: printf ( "wybrany tekst" );
Na końcu stawiamy średnik. W języku C stawiamy średnik na końcu większości linii. Informuje to nasz kompilator, że w tym miejscu kończy się polecenie. Kiedy je przeanalizuje, przejdzie do kolejnych linii. Znak "\n" jest komendą dla komputera, która każe mu przejść do nowej linii. Gdybyśmy usunęli go z naszego programu, efekt byłby mało czytelny: gzp@gzp:~$ ./prog Wlasnie zostales programista!gzp@gzp:~$ ./prog
Niestety komputer nie rozumie sam z siebie słówka printf. W tym celu potrzebna jest mu „encyklopedia”, która wytłumaczy mu, co ma zrobić, kiedy wpiszesz to słowo. Taką encyklopedią w naszym programie jest linijka: #include
Informuje ona komputer, że wszystkie potrzebne tłumaczenia znajdzie w „encyklopedii” o nazwie stdio.h. Część #include informuje komputer, że wskazujemy na encyklopedię o nazwie nazwa_encyklopedii. W informatyce nie używamy jednak słowa „encyklopedia”, lecz „biblioteka”. Natomiast takie słowa jak printf nazywamy funkcjami. To znaczy, że funkcja printf wypisuje na naszym ekranie oczekiwany tekst, a informację, jak ma to zrobić, odczytuje z biblioteki o nazwie stdio.h. Wyobraź sobie, że nie masz pojęcia o mechanice samochodowej, a kolega prosi, abyś wymienił w samochodzie filtr oleju. Aby dowiedzieć się, jak to zrobić, poszedłbyś po poradę do jakiegoś doświadczonego mechanika samochodowego i wtedy poradziłbyś sobie bez problemu. Biblioteka stdio.h jest naszym doświadczonym mechanikiem, a funkcja printf to prośba kolegi.
16
Zabawa w programowanie. Język C dla nastolatków
Na koniec zostaje nam jeszcze fragment: int main() { (...) return 0; }
Teraz wystarczy, że zapamiętasz, iż w ten sposób będziemy pisali większość naszych programów. W miejscu, gdzie znajduje się (...), będziemy pisali wszystkie instrukcje, które komputer powinien wykonać. Dokładniej wytłumaczę Ci to w późniejszych rozdziałach. Myślę, że nadszedł dobry moment na parę słów na temat ogólnych zasad programowania. Są to tak zwane dobre praktyki. Zauważyłeś na pewno, że program, który przed chwilą napisałeś, dziwnie wygląda. int main oraz nawiasy klamrowe zaczynają się maksymalnie od lewej strony ekranu, a kolejne linijki zaczynają się nieco dalej. Są to tak zwane wcięcia i są one najzwyczajniej w świecie kilkoma spacjami (przeważnie czterema). Programiści piszą w ten sposób, ponieważ bardzo poprawia to czytelność kodu. To samo dotyczy wstawiania w niektórych miejscach czystych linii (w naszym przypadku tak właśnie wygląda linia druga). Jest to bardzo dobry sposób na odseparowanie poszczególnych części programu. Dla komputera wszystkie takie elementy są niewidoczne. Powyższy program moglibyśmy napisać równie dobrze w taki sposób: #include int main(){printf("Wlasnie zostales programista!\n");return 0;}
i zadziałałby on dokładnie tak samo! Musisz jednak przyznać, że jego czytanie byłoby niezbyt przyjemne, a spróbuj wyobrazić sobie program zawierający kilkaset linii kodu! Dla programisty byłaby to prawdziwa katorga! Kolejne dobre praktyki programistyczne będą pojawiać się w kolejnych rozdziałach wraz z nowymi pojęciami. Na koniec tego rozdziału chciałem wspomnieć o komentarzach w kodzie. Są to specjalne miejsca, gdzie możemy wpisywać, co nam się podoba, a kompilator nie zwróci na to uwagi. Komentarze są przydatne, gdy chcemy zapisać jakąś ważną notatkę niebędącą częścią programu. Aby do programu wstawić komentarz, należy zrobić to w następujący sposób: // tekst komentarza lub /* tekst komentarza */
Spójrz na przykład: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
#include // to jest pierwszy komentarz int main() { printf("Wlasnie zostales programista!\n"); return 0; /* Tutaj wpisuję kolejny komentarz, który zajmuje kilka linijek. */ }
Rozdział 3. Pierwszy program — już programujesz!
17
Pierwszy sposób komentowania pozwala nam na wpisanie komentarza od znaku // do końca linii. Kolejny sposób pozwala na dużo bardziej rozbudowane komentarze. Wystarczy, że na początku komentarza wpiszemy /*, a na jego końcu */. Sprawi to, że wszystko pomiędzy tymi znakami zostanie zignorowane przez kompilator.
Zadania 1. Spróbuj zmienić plik program31.c tak, aby komputer wyświetlał każde słowo
w nowej linii. Efekt powinien wyglądać tak: user@computer:~$ ./prog
Wlasnie zostales programista! gzp@gzp:~$
Podpowiedź: Znaku "\n" możesz używać dowolną liczbę razy.
18
Zabawa w programowanie. Język C dla nastolatków
Rozdział 4.
Biblioteki i funkcje — jak to działa? Po lekturze poprzedniego rozdziału wiesz już, czym są biblioteki oraz czym są funkcje. W dużym uproszczeniu możemy powiedzieć tak: funkcje to instrukcje, które ma wykonać komputer, biblioteki natomiast to zbiory funkcji. Bibliotek w języku C jest mnóstwo i są odpowiedzialne za najróżniejsze rzeczy. W poprzednim rozdziale użyliśmy biblioteki studio.h, ponieważ potrzebowaliśmy użyć znajdującej się w niej funkcji printf. Pytanie brzmi: skąd mamy wiedzieć, jakie nazwy funkcji odpowiadają za jakie zadania oraz w jakich bibliotekach się znajdują. Głównym źródłem wiedzy jest doświadczenie, ale jeśli go nie posiadacie, to pamiętajcie, że ja zaczynałem tak samo! W dzisiejszych czasach mamy mnóstwo źródeł, z których możemy zaczerpnąć takie informacje. Powiedzmy, że chcemy użyć funkcji do liczenia pierwiastka z jakiejś liczby. Poszukiwana przez nas funkcja nazywa się sqrt, a znajduje się w bibliotece math.h. Napiszemy teraz program, który przy użyciu funkcji sqrt będzie miał za zadanie wypisać w Terminalu wynik pierwiastkowania. Spójrz na listing 4.1. Listing 4.1. Plik program041.c 1. 2. 3. 4. 5. 6. 7. 8.
#include #include int main() { printf("Pierwiastek kwadratowy z liczby 64 to: %f", sqrt(64)); return 0; }
Wpisz w programie gedit powyższą treść, a następnie zapisz ten plik jako program041.c. Teraz pora skompilować program041.c. W tym celu uruchom Terminal, wpisz i potwierdź klawiszem Enter poniższą komendę: gcc program041.c -o program041
20
Zabawa w programowanie. Język C dla nastolatków
Następnie wpisz i potwierdź klawiszem Enter następujące polecenie: ./program041
W efekcie w Terminalu powinien pojawić się następujący tekst: Pierwiastek kwadratowy z liczby 64 to 8.00.
Pora omówić treść programu. Na początek linijka nr 3. Ponieważ chcemy użyć funkcji sqrt, musimy dodać do programu bibliotekę math.h. Robimy to przez polecenie #include . Kolejną nowością jest linijka nr 6. Funkcję printf już znamy, jednak nie wiemy, co oznacza %f. Oznaczenie %f nazywamy specyfikatorem, a „mówi” on funkcji printf, że chcemy na ekranie wypisać liczbę rzeczywistą. Liczba rzeczywista znajduje się natomiast po drugim cudzysłowie i znaku przecinka. Jest nią sqrt(64), czyli pierwiastek z liczby 64. Gdybyśmy chcieli wypisać dwa pierwiastki, polecenie printf wyglądałoby tak: 6.
printf("Pierwiastek kwadratowy z liczby 64 to %f a z liczby 9 to %f", sqrt(64), sqrt(9));
W książce wiele razy nie zmieści się linijka kodu, więc będę ją przenosił do nowej linii. Poznasz to w łatwy sposób dzięki temu, że w nowej linii nie będzie nowego numeru linijki, tak jak w przykładzie powyżej. Pamiętaj jednak, że w praktyce te dwie linie stanowią jedną całość.
Zauważ, że pojawił się tu drugi specyfikator %f dla drugiego pierwiastka oraz pojawiła się drugi raz funkcja sqrt, po kolejnym przecinku. Zasada wygląda tak, że kompilator „przegląda” funkcję printf od lewej strony i po znalezieniu specyfikatora %f dopasowuje do niego pierwsze wyrażenie po przecinku (znajdującym się za cudzysłowem), czyli w naszym przypadku sqrt(64). Następnie kompilator dalej „przegląda” funkcję printf i znajduje drugi raz %f, więc przypisuje do niego drugie wyrażenie po cudzysłowie, czyli w naszym przypadku sqrt(9). Ale dlaczego używamy %f? Dlatego, że wynikiem funkcji sqrt jest liczba rzeczywista, a do jej wypisania w funkcji printf służy specyfikator %f. Takich oznaczeń jest więcej. Co najważniejsze, powinieneś wiedzieć, że żeby wypisać liczbę całkowitą, należy użyć specyfikatora %d; dla litery lub innego znaku jest to %c, dla ciągu kilku liter lub innych znaków %s, dla wskaźnika (o którym napiszę dalej) %p. istnieje też kilka innych znaczników w nieco rzadszych przypadkach, którymi nie będziemy sobie teraz zawracali głowy. Zapamiętaj, że to bardzo ważne, aby zawsze używać poprawnych znaczników! Jeżeli dla liczby rzeczywistej, np. 2,5, użyjesz specyfikatora %d, kompilator wypisze Ci jedynie jej część całkowitą, czyli 2. Przy bardziej skomplikowanych programach programista może szukać błędu w zupełnie innym miejscu niż w błahej funkcji printf. Na koniec mały przykład. Załóżmy, że chcielibyśmy wypisać na ekranie wynik dodawania dwóch liczb całkowitych. Wtedy należałoby funkcję printf przerobić w następujący sposób: 6. printf("Wynik dodawania dwoch liczb calkowitych to %d", (5 + 7));
Rozdział 4. Biblioteki i funkcje — jak to działa?
21
Zauważ, iż użyliśmy tutaj specyfikatora %d, ponieważ mamy do czynienia z liczbą całkowitą. Zauważ również, że działanie dodawania wpisaliśmy w nawiasie. Nie jest to konieczne, ale bardzo poprawia czytelność programu i eliminuje ewentualne błędy. Od tej pory nie będę już pisał o sposobie kompilowania i uruchamianiu programów. Myślę, że powinieneś sobie z tym poradzić bez problemów na podstawie rozdziałów 3. i 4. Pamiętaj jedynie, że każda wprowadzona przez Ciebie zmiana w kodzie programu wymaga ponownego procesu kompilacji! Jeśli to, co przed chwilą napisałem, jest dla Ciebie niezrozumiałe, wróć do rozdziału 3. Jest to najważniejszy rozdział w książce i dzięki jego zrozumieniu wszystko okaże się dla Ciebie łatwe!
Zadania 1. Zmień program041.c tak, aby wypisywał za pomocą jednej funkcji printf
wynik odejmowania liczb 93 i 77 oraz pierwiastek kwadratowy z liczby 49. 2. Znajdź odpowiednią funkcję do liczenia potęgi i napisz z jej użyciem
program, który będzie liczył i wypisywał na ekranie liczbę 7 do potęgi 3.
22
Zabawa w programowanie. Język C dla nastolatków
Rozdział 5.
Zmienne i stałe — niezbędne elementy Pamiętasz poprzedni rozdział, w którym liczyliśmy pierwiastek liczby 64? Otóż możemy napisać go inaczej. W programowaniu bardzo często możemy rozwiązać zadanie na kilka różnych sposobów i to jest chyba najfajniejsze, ponieważ nie musisz trzymać się zawsze utartych szlaków, a możesz być kreatywny. Dzięki temu jest to niezwykle ciekawe i satysfakcjonujące zajęcie :). Pokażę Ci teraz, jak możemy przerobić program z poprzedniego rozdziału, a użyjemy do tego zmiennej. Co to jest zmienna? Za trzy minuty będziesz już to wiedział. Spójrz na listing 5.1. Listing 5.1. Plik program051.c 1. 2. 3. 4. 5. 6. 7. 8. 9.
#include #include int main() { float pierwiastek = sqrt(64); printf("Pierwiastek kwadratowy z liczby 64 to: %f", pierwiastek); return 0; }
W porównaniu z poprzednim programem zmieniły nam się tutaj dwie linijki. Zacznijmy od linii nr 6. Pojawiła się tutaj zmienna o nazwie pierwiastek. Zmienną możemy wyobrazić sobie jako pudełko, do którego wkładamy dowolne rzeczy. W naszym przypadku tworzymy „pudełko” o nazwie pierwiastek i umieszczamy w nim liczbę będącą wynikiem działania funkcji srt(64). Dlaczego nazwa brzmi pierwiastek? Bo tak sobie wymyśliłem. Nazwa zmiennej może być niemal dowolna. Teoretycznie może składać się z dużych i małych liter, z niektórych znaków, a nawet z cyfr. W informatyce ważne jest, aby zmienne nazywały się w znaczący coś sposób. Mógłbym równie dobrze wpisać abrakadabra44, ale jeśli program, który piszemy, ma tysiące linii kodu i tysiące różnych zmiennych, to niezwykle ważne jest, aby każda zmienna samą swoją nazwą podpowiadała programiście, za co jest odpowiedzialna. Wróćmy jednak do naszego programu. Przed nazwą zmiennej pojawiło się słowo float. Oznacza ono rodzaj zmiennej.
24
Zabawa w programowanie. Język C dla nastolatków
Zmienne mogą mieć najróżniejsze rodzaje. Najważniejsze z nich to użyty przez nas float, czyli liczba rzeczywista; int, czyli liczba całkowita; char, czyli zmienna znakowa (w uproszczeniu możesz na razie przyjąć, że chodzi głównie o litery), i kilka innych, o których dowiesz się z dalszej części książki. Dlaczego musimy podawać typ zmiennej? Dlatego, że dla każdej zadeklarowanej przez Ciebie zmiennej system operacyjny musi zarezerwować fragment pamięci komputera, żeby móc ją zapamiętać. Dzięki temu możesz do zmiennej pierwiastek zapisać wybraną liczbę i w każdym późniejszym momencie do niej wrócić. W poprzednim rozdziale liczyliśmy sqrt(64) bez użycia zmiennej. Gdybyśmy potrzebowali ponownie policzyć pierwiastek, niepotrzebnie obciążylibyśmy procesor po raz drugi. Przy bardziej złożonych aplikacjach jest to spory problem, dlatego lepiej użyć zmiennej, rezerwując jedynie jednorazowo mały fragment pamięci operacyjnej. Wiemy już, że wskazaliśmy kompilatorowi, że będziemy używać zmiennej o nazwie pierwiastek, która jest typu float. W dalszej części linijki nr 6 użyliśmy tak zwanego operatora przypisania, czyli znaku "=". O operatorach piszę w innym rozdziale. Na razie ważne, abyś pamiętał, że dzięki znakowi "=" do zmiennej pierwiastek wstawiliśmy wynik, który dała nam funkcja sqrt(64) (czyli jak łatwo przewidzieć liczbę 8). Potwierdza to linijka nr 7, w której wyświetlamy na ekranie zawartość zmiennej pierwiastek. Linijkę nr 6 moglibyśmy również zapisać w formie dwóch linijek w następujący sposób: 6. 7.
float pierwiastek; pierwiastek = sqrt(64);
Efekt tego jest identyczny, więc w praktyce wybieramy najwygodniejszy w danym momencie sposób. Zauważ, że w linijce nr 7 nie wpisujemy rodzaju zmiennej. Robimy to tylko raz, przy pierwszym pojawieniu się zmiennej w kodzie, co nazywa się deklarowaniem zmiennej. Możemy powiedzieć, że w linijce 6 zadeklarowaliśmy zmienną o nazwie pierwiastek jako zmienną typu float, a następnie w linijce nr 7 przypisaliśmy do niej wartość funkcji sqrt(64). Ważne jest to, że zmienna ma to do siebie, że może ulec zmianie. Przyjmijmy, że chcemy z jakiegoś powodu zmienić wartość zmiennej o nazwie pierwiastek na liczbę 100. Aby to zrobić, musielibyśmy przerobić nasz program w pokazany poniżej sposób. Spójrz na listing 5.2. Listing 5.2. Plik program052.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
#include #include int main() { float pierwiastek = sqrt(64); printf("Pierwiastek kwadratowy z liczby 64 to: %f", pierwiastek); pierwiastek = 100; printf("\nZmienna pierwiastek wynosi: %f", pierwiastek); return 0; }
Rozdział 5. Zmienne i stałe — niezbędne elementy
25
W linijce nr 8 do zmiennej pierwiastek przypisujemy liczbę 100. Od tej pory nasz program aż do końca działania będzie pamiętał, że pod zmienną pierwiastek znajduje się liczba 100. W programowaniu potrzebne są nam również stałe. Są to zmienne, którym wartość przypisywana jest w momencie deklaracji i później nie możemy już jej zmienić. Do stworzenia stałej potrzebne nam jest nowe słowo, a mianowicie const. Abym mógł to omówić i podsumować cały ten rozdział, napiszemy kolejny program. Spójrz na listing 5.3. Listing 5.3. Plik program053.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
#include #include
int main() { const float przyspieszenie = 9,81; char literka = 'g'; float masa; masa = 95; float sila = przyspieszenie * masa; printf("Sila przyciagania ziemskiego na cialo o masie 95 kg na terenie Polski wynosi %f", sila); 12. printf("\nPrzyspieszenie grawitacyjne na terenie Polski wynosi w przyblizeniu 9,81 m/s, a oznaczamy je literka: %c", literka); 13. return 0; 14. }
W linijce nr 6 zadeklarowałem zmienną przyspieszenie i przypisałem do niej wartość 9,81 (ponieważ tyle w przybliżeniu wynosi przyspieszenie grawitacyjne na terenie Polski). W linijce nr 7 zadeklarowałem zmienną typu char o nazwie literka i przypisałem do niej literę g. Zauważ, że litera g musi znajdować się w pojedynczym apostrofie (robimy tak zawsze przy zmiennych typu char). W linijce nr 8 deklarujemy zmienną masa typu float, a w linijce nr 9 przypisujemy do niej wartość 95 (tyle kilogramów przyjąłem na potrzebę tego programu). W linijce nr 10 deklaruję zmienną sila i przypisuję do niej wynik mnożenia stałej przyspieszenie i zmiennej masa. Następnie w liniach nr 11 i 12 wypisuję wszystko na ekranie. Rozumiesz już, po co nam stałe? W tym konkretnym przypadku przyspieszenie ziemskie nigdy się nie zmieni. Możemy liczyć różną siłę przyciągania w zależności od różnej masy, np. dla spadochroniarza. W zaawansowanych programach dzieje się bardzo dużo i gdybyśmy nie użyli słowa const, ryzykowalibyśmy, że zmienna przyspieszenie mogłaby niechcący ulec zmianie. Dawałoby to bezsensowne wyniki dla siły przyciągania ziemskiego. Dobre praktyki Nazwy zmiennych piszemy małymi literami. Dodatkowo nazwa zmiennej powinna zawsze sugerować, do czego zmienna służy. Na potrzeby tej książki nie zawsze trzymamy się tej reguły, ale przy skomplikowanych programach ma to ogromne znaczenie.
26
Zabawa w programowanie. Język C dla nastolatków
Zadania 1. Napisz program, w którym deklarujesz zmienną typu int, zmienną typu float, zmienną typu char i stałą typu int, po czym wyświetlasz ich wartości
na ekranie. 2. Spróbuj w tym samym programie podstawić zmienną int pod stałą int.
Zauważ, jak zachowa się kompilator.
Rozdział 6.
Trochę więcej o funkcjach i zmiennych — zaczynamy rozwijać skrzydła Zaczniemy od funkcji. W poprzednich rozdziałach używaliśmy funkcji printf i sqrt. Były one częścią odpowiednich bibliotek. Ktoś je kiedyś musiał napisać i tam umieścić. A czy wiesz, że Ty też możesz napisać własną funkcję, a później jej użyć? Pokażę Ci to na przykładzie dodawania. Spójrz na listing 6.1. Listing 6.1. Plik program061.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.
#include int dodawanie(int x, int y) { int z = x + y; return z; } int main() { int a = 3; int b = 5; int c = dodawanie(a, b); printf("Wynik dodawania to %d", c); return 0; }
Działo się tutaj kilka fascynujących rzeczy, więc spieszę z wyjaśnieniami. Linijki od 2. do 6. to nic innego jak definicja funkcji. Co to jest definicja funkcji? Można to porównać do przepisu kucharskiego. Jest to informacja, jak przygotować danie. Jednak dopóki kucharz nie weźmie książki kucharskiej do ręki, przepis jest nieużywany. Tak samo jest
28
Zabawa w programowanie. Język C dla nastolatków
w tym przypadku. Funkcję uruchamiamy dopiero w linijce nr 11. To tak, jakby klient restauracji zamówił danie. Dopiero wtedy kucharz bierze do ręki książkę i czyta instrukcję, co i jak ma zrobić. Pytanie: dlaczego funkcję definiujemy przed fragmentem main? Dlatego, że jeżeli zadeklarowalibyśmy ją po tym fragmencie, kompilator „nie wiedziałby”, co ona robi. I znowu porównanie do gotowania. Przepis musi zaczynać się od listy składników, a dopiero później powinny być informacje, co z nimi zrobić. Tak samo tutaj. Jest jednak sposób na to, aby to obejść. Spójrz na listing 6.2. Listing 6.2. Plik program062.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.
#include int dodawanie(int x, int y); int main() { int a = 3; int b = 5; int c = dodawanie(a, b); printf("Wynik dodawania to %d", c); return 0; } int dodawanie(int x, int y) { int z = x + y; return z; }
Przesunęliśmy tutaj definicję funkcji dodawanie za main(). Ten program normalnie nie skompilowałby się, ale aby temu zapobiec, dodałem linijkę nr 3. Jest to tak zwany prototyp funkcji. Informuje on kompilator, że taka funkcja istnieje i że może się do niej odnieść w późniejszym fragmencie kodu. W tym momencie nasuwa się pytanie: po co nam funkcje? Są dwa główne powody. Pierwszy i najważniejszy powód jest taki, że dzięki funkcjom unikamy powtarzania się kodu. Wyobraźcie sobie, że napiszecie program, w którym będziecie musieli kilka razy obliczać skomplikowany wzór matematyczny. Zamiast powtarzać bez sensu kilka razy ten sam kod, wystarczy, że stworzycie jedną funkcję i jedynie wywołacie ją w odpowiednich miejscach. Drugi powód to czytelność kodu. Dużo łatwiej zrozumieć programiście zasadę działania programu składającego się z krótkich funkcji niż programu obejmującego kilka tysięcy linii znajdujących się jedynie w funkcji main. Wróćmy do naszego przykładu. Spójrzmy na budowę linijki nr 3. Najpierw mamy typ funkcji int. Funkcja tak jak zmienna ma zawsze swój typ. Ten typ oznacza rodzaj wartości, jaką funkcja zwraca. Przypomnij sobie zmienne. Zapis int x = 5 informuje kompilator o zmiennej typu int o wartości równej 5. Funkcja też może posiadać swoją wartość. Jedyna różnica jest taka, że czasami najpierw musi wykonać szereg operacji, żeby ją obliczyć. Dalej w linijce nr 3 mamy otwarty nawias, a w nim zapis int x, int y.
Rozdział 6. Trochę więcej o funkcjach i zmiennych — zaczynamy rozwijać skrzydła
29
Oznacza to tylko tyle, że do funkcji możemy „włożyć” dwie zmienne, które są typu int. Takie zmienne, które przekazujemy do funkcji, nazywamy argumentami funkcji. Dzięki przekazaniu parametrów do funkcji możemy ich w niej używać. Bez tego zmienne te byłyby dla funkcji niewidoczne. Kolej na linijkę nr 9. Deklarujemy zmienną c, która jest typu int, i przypisujemy do niej funkcję dodawanie. Właśnie w tym momencie nasz kucharz bierze książkę do ręki. Dopiero teraz funkcja dodawanie zaczyna działać. Z linijki nr 3 wiemy, że funkcja dodawanie musi dostać dwie zmienne typu int, więc dajemy jej te zmienne pod postacią zmiennej a oraz zmiennej b. Przejdźmy do linijki nr 14, która jest niemal identyczna z linijką nr 3 z tą różnicą, że na jej końcu nie ma średnika. To dlatego, że dalej otwieramy nawiasy klamrowe, w których zawsze wpisujemy to, co funkcja powinna robić. Nazywa się to ciałem funkcji. W linijce nr 16 deklarujemy zmienną typu int o nazwie z i przypisujemy do niej wynik dodawania zmiennych x oraz y. Następnie w linijce nr 17 zwracamy zmienną z za pomocą słowa return. Co znaczy określenie „zwracamy”? W naszym przypadku znaczy to tyle, że w tym momencie w linijce nr 9 do zmiennej c przypisujemy zmienną z z funkcji dodawanie. Proste? Pewnie, że tak! Dodatkowo pamiętaj, że funkcję dodawanie możesz wywołać dowolną liczbę razy na przykład w celu obliczenia wyniku dodawania kolejnych liczb. W tym momencie program z rozdziału 3. powinien być dla Ciebie całkowicie jasny. Zauważ, że int main to nic innego, jak definicja funkcji main. Funkcja main jest zawsze główną funkcją, która jako pierwsza jest wywoływana przy uruchamianiu programu. W pewien obrazowy sposób możemy przyjąć, iż program w momencie rozpoczęcia działania „przelatuje” linijka po linijce przez funkcję main. main tak jak inne funkcje zwraca jakąś wartość. Zwraca ją poprzez słowo return. Dlaczego zwraca wartość 0? Przeważnie przyjęte jest w informatyce, że jeżeli jakakolwiek funkcja zwraca wartość 0, oznacza to, że zadziałała prawidłowo. Pamiętaj również, że słówko return bezpowrotnie kończy działanie funkcji! Przekonasz się o tym w następnych rozdziałach. Na koniec ważna ciekawostka. Funkcja może zwracać dowolne wartości, np. int, float, char. Ale może również nie zwracać żadnej wartości! W tym celu jej typ oznaczamy jako void. Jest to sposób dość często wykorzystywany w programowaniu, więc powinieneś się z nim zapoznać. Gdybyśmy chcieli przerobić poprzedni program tak, aby funkcja dodawanie nie zwracała żadnej wartości, wyglądałby on jak na listingu 6.3: Listing 6.3. Plik program063.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
#include void dodawanie(int x, int y); int main() { int a = 3; int b = 5; dodawanie(a, b); return 0; } void dodawanie(int x, int y) {
30
Zabawa w programowanie. Język C dla nastolatków 15. 16. 17. }
int z = x + y; printf("Wynik dodawania to %d", z);
W linijkach nr 3 oraz 13 zmieniamy typ funkcji na void. W linijce nr 9 nie przypisujemy funkcji dodawanie do żadnej zmiennej, bo nie może zwrócić żadnej wartości, lecz po prostu ją wywołujemy. Ponieważ funkcja dodawanie nie zwraca żadnej wartości do main, w linijce nr 16 dodajemy funkcję printf w celu wyświetlenia wyniku dodawania. Pora na dodatkową wzmiankę o zmiennych. Sprowadza się to do jednego zdania. Zmienne mają swój zasięg. To znaczy, że działają jedynie w pewnym obrębie programu. Przykład? Spójrz na listing 6.4. Listing 6.4. Plik program064.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17.
#include int y = 10; void wyswietl(); int main() { int x = 3; wyswietl(); return 0; } void wyswietl() { int x = 7; printf("x=%d y=%d", x, y); }
W linijce nr 3 deklaruję zmienną y i przypisuję jej wartość 10. Dlaczego deklaruję zmienną przed funkcją main? Dzięki temu będzie ona widoczna w każdym miejscu programu. Takie zmienne nazywamy zmiennymi globalnymi. Minusem jest to, że zarezerwuje ona na dłużej pamięć operacyjną komputera, więc jeśli nie jest to konieczne, należy takich zmiennych unikać. Następnie w linijce nr 4 zadeklarowałem funkcję typu void o nazwie wyswietl. Zauważ, że nie przyjmuje ona żadnych parametrów, co jest całkowicie dopuszczalne w języku C. Funkcję wyświetl wywołuję w linijce nr 9. W samej funkcji wyświetlam na ekranie wartości zmiennych x oraz y. I tutaj dzieje się najciekawsza rzecz. W linijce nr 8 zadeklarowałem zmienną x i przypisałem do niej wartość 8. W linijce nr 15 również zadeklarowałem zmienną x i przypisałem do niej wartość 7. Czy nie powinno tutaj być jakiegoś błędu? Nie! Ponieważ zmienna x z linijki nr 8 jest widoczna jedynie w funkcji main, a zmienna z linijki nr 15 jedynie w funkcji wyswietl. Oznacza to, że są to dwie różne i niezależne zmienne. Takie zmienne nazywamy zmiennymi lokalnymi. Jak nietrudno się domyślić, na ekranie pojawi się następujący wynik: x=7 y=10
Rozdział 6. Trochę więcej o funkcjach i zmiennych — zaczynamy rozwijać skrzydła
31
Dobre praktyki Nazwy funkcji podobnie jak nazwy zmiennych piszemy małą literą oraz nadajemy je tak, aby można było łatwo się domyślić, za co funkcje odpowiadają.
Zadania 1. Na początku funkcji dodawanie dodaj linijkę return 1. Zwróć uwagę, co pokaże na ekranie funkcja printf. 2. Do programu dopisz funkcję odejmowanie i wywołaj ją z funkcji main po funkcji dodawanie. 3. Napisz program, w którym zadeklarujesz dwie zmienne globalne typu float oraz jedną zmienną lokalną typu float wewnątrz funkcji main. Następnie dopisz funkcję mnozenie, w której będziesz mnożył wszystkie te trzy zmienne. Funkcja mnozenie powinna zwracać wartość typu float do main i dopiero w funkcji main powinieneś wyświetlić wynik mnożenia.
32
Zabawa w programowanie. Język C dla nastolatków
Rozdział 7.
Tablice — bardzo przydatna sprawa Poznałeś już dość dokładnie zmienne. Nie wiesz natomiast, że możemy je grupować w czymś przypominającym skrzynkę. Nazywa się to tablicą i pokażę Ci to na przykładzie. Spójrz na listing 7.1. Listing 7.1. Plik program071.c 1. #include 2. 3. int main() 4. { 6. int tablica1[3]; 7. int tablica2[] = {11, 15, 21}; 8. tablica1[0] = 4; 9. tablica1[1] = tablica1[0] + tablica2[2]; 10. tablica1[2] = tablica2[0] - tablica2[1]; 11. printf("tablica1[0]=%d tablica1[1]=%d tablica1[2]=%d", tablica1[1], tablica1[2]); 12. return 0; 13. }
Tablicę deklarujemy tak jak zmienną, z jednym wyjątkiem. Po jej nazwie wstawiamy nawias kwadratowy, a w nim liczbę. Dlaczego tak? Liczba pomiędzy nawiasami mówi, ile elementów będzie posiadać tablica, czyli na przykład ile książek wrzucimy do skrzyni. Jest to o tyle ważne, że nie możemy już później dodać do niej więcej elementów. Dlatego jeśli nie wiesz, czy będziesz potrzebować np. 8, czy 10 elementów, zadeklaruj tablicę na 10 elementów. Minusem tego rozwiązania jest to, że system operacyjny zajmie niepotrzebnie większą ilość pamięci operacyjnej komputera, jednak czasami nie ma innego wyjścia. Istnieją również tablice dynamiczne, to znaczy takie, których rozmiar może się zmieniać, ale to zupełnie inny temat. W tym momencie przyjmujemy, że istnieją jedynie tablice statyczne.
34
Zabawa w programowanie. Język C dla nastolatków
Zacznijmy od linijki nr 6. Deklarujemy tablicę typu int (to znaczy, że tablica może przechowywać JEDYNIE elementy typu int!) o rozmiarze 3. Nie podajemy natomiast na razie żadnego z tych trzech elementów. W linijce nr 7 tworzymy tablicę typu int i od razu podajemy jej konkretne elementy. Spowoduje to rezerwację pamięci operacyjnej na tablicę trójelementową. Każdy element tablicy jest tak jakby niezależną zmienną, która dodatkowo jest po prostu „włożona” do odpowiedniej skrzynki. W linijce nr 8 odwołujemy się do pierwszego elementu pierwszej tablicy. Tutaj następuje bardzo ważna rzecz. Numery kolejnych elementów tablic zaczynają się od liczby 0! To oznacza, że pierwszy element tablicy jest oznaczony cyfrą 0, drugi cyfrą 1, a trzeci cyfrą 2. Zapominanie o tym jest przyczyną wielu programistycznych błędów, musisz więc na to szczególnie uważać! W każdym razie w linijce nr 8 przypisujemy liczbę 4 do pierwszego elementu tablicy1. W linijce nr 9 do drugiego elementu tablicy (czyli elementu oznaczonego cyfrą 1) przypisujemy wynik dodawania pierwszego elementu tablicy1 i trzeciego elementu tablicy2. W linijce nr 10 do trzeciego elementu tablicy1 przypisujemy wynik odejmowania między pierwszym elementem tablicy2 oraz drugim elementem tablicy1. Po uruchomieniu programu otrzymamy następujący wynik: tablica1[0]=4 tablica1[1]=25 tablica1[2]=-4
Istnieją również tablice wielowymiarowe. Wyobrażam sobie tablicę dwuwymiarową jako tabelkę z wierszami i kolumnami. Tablicę taką deklarujemy jako: int tablica[2][3];
Możemy to rozumieć tak, że zadeklarowaliśmy tablicę z dwoma wierszami i trzema kolumnami, czyli łącznie sześcioma różnymi elementami. Do takiej tablicy odnosimy się tak samo jak w poprzednim programie, czyli jeśli chce się wyświetlić element z pierwszego wiersza i trzeciej kolumny, należy zrobić to następująco: printf("tablica[0][2]=%d, tablica[0][2]");
W rozdziale o zmiennych wspominałem o zmiennych typu char, czyli zmiennych znakowych. Do tej pory traktowałem je po macoszemu, ale to wszystko dlatego, że zmierzałem do tego rozdziału. Zmienna typu char przechowuje znak. A co, jeśli chcemy przechować całe słowo? Nic prostszego! Po prostu musimy stworzyć tablicę typu char! Pora na przykład, więc spójrz na listing 7.2. Listing 7.2. Plik program072.c 1. 2. 3. 4. 5. 6. 7. 8. 9.
#include int main() { char zdanie[] = "Ala ma kota"; printf("%s", zdanie); printf("\n%c", zdanie[0]); return 0; }
W linijce nr 5 tworzymy tablicę znakową typu char i wpisujemy do niej zdanie "Ala ma kota". Ważne, że musi się ono znajdować w podwójnym cudzysłowie. W tym momencie rezerwujemy pamięć na tablicę znakową o długości 11 elementów. Do każdej
Rozdział 7. Tablice — bardzo przydatna sprawa
35
pojedynczej komórki wpisywana jest jedna litera. Aby wyświetlić zawartość całej tablicy znakowej na ekranie, wystarczy wpisać jej nazwę. Ważne, abyś pamiętał, że do wyświetlenia całej tablicy znakowej używamy w funkcji printf specyfikatora %s! W linijce nr 7 wyświetlamy tylko pierwszy (czyli pojedynczy) znak z tablicy zdanie i wtedy używamy specyfikatora %c. Na szczęście programiści starają się ułatwiać sobie życie. Ktoś mądry wpadł kiedyś na pomysł, aby stworzyć całą bibliotekę jedynie do operacji na tablicach znakowych. Biblioteka ta nazywa się string.h i posiada całą gamę przeróżnych funkcji, np. strlen do sprawdzania, jak długa jest tablica znakowa, strcat do łączenia dwóch tablic znakowych, strcmp do porównywania dwóch tablic znakowych i wiele, wiele innych. Pokażę Ci na przykładzie, jak to wygląda w praktyce. Spójrz na listing 7.3. Listing 7.3. Plik program073.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14.
#include #include
int main() { char zdanie1[] = "Ala ma"; char zdanie2[] = "kota"; int dlugosc = strlen(zdanie1) + strlen(zdanie2); dlugosc++; char cale_zdanie[dlugosc] = {0}; strcpy(cale_zdanie, zdanie1); strcat(cale_zdanie, " "); strcat(cale_zdanie, zdanie2); printf("Dlugosc zdania to %d, a cale zdanie to %s", dlugosc, cale_zdanie); 15. return 0; 16. }
Już spieszę z objaśnieniem. W linijce nr 2 dodajemy nową bibliotekę, która zawiera potrzebne nam funkcje. W linijkach nr 6 i 7 tworzymy dwie tablice znakowe. W linijce nr 8 tworzymy zmienną typu int i przypisujemy jej wartość odpowiadającą długości pierwszego i drugiego zdania, po czym zwiększamy ją o 1 w linijce nr 9. Czemu zwiększamy? Ponieważ wstawimy dodatkowo spację między zdaniem pierwszym i zdaniem drugim, czyli 1 dodatkowy znak (spacja to też znak, który komputer musi zapamiętać). W linijce nr 10 tworzymy tablicę o nazwie cale_zdanie, która będzie połączeniem naszych dwóch mniejszych zdań. Nadajemy jej długość o wartości zmiennej dlugosc i na końcu piszemy = {0}. Po co? Otóż komputer, rezerwując pamięć operacyjną na tablicę, może zarezerwować pamięć, w której już coś jest. Jeśli jej nie wyzerujemy, to te „śmieci” z pamięci mogą nam popsuć efekt działania programu. W linijce nr 11 używamy funkcji strcat. Powoduje ona kopiowanie całego drugiego parametru do pierwszego parametru, czyli w naszym przypadku tablicy zdanie1 do cale_zdanie. W linijkach nr 12 i 13 używamy strcat, aby dokleić do tablicy cale_zdanie, spacji, a następnie tablicy zdanie2. Dlaczego nie strcpy? Ponieważ strcpy próbowałoby za każdym razem nadpisać to, co już istniało w tablicy cale_zdanie. Funkcja strcat jedynie dokleja kolejne litery.
36
Zabawa w programowanie. Język C dla nastolatków
Zadania 1. Utwórz tablicę dwuwymiarową typu float z czterema elementami i wypełnij
ją dowolnymi liczbami, a następnie dodaj te liczby do siebie i wyświetl wynik w programie Terminal. 2. Utwórz tablicę znakową z dowolnym zdaniem oraz dwie kolejne tablice z tym
samym zdaniem podzielonym na pół. Połącz je w kolejnej, czwartej tablicy i porównaj ją z tablicą pierwszą za pomocą funkcji strcmp(). Wynik wyświetl na ekranie. Zauważ, że wynikiem będzie cyfra, która oznacza powodzenie lub niepowodzenie.
Rozdział 8.
Instrukcje warunkowe — ważne i proste Czym są instrukcje warunkowe? Czymś niezwykle prostym. Wyobraź sobie, że założyłeś się z kimś, iż zaliczysz klasówkę na ocenę 5. Stawka zakładu to 50 zł. Jeżeli Ci się nie uda, zapłacisz 50 zł, natomiast jeśli Ci się uda, otrzymasz 50 zł. Przykład pokazano na listingu 8.1. Listing 8.1. Plik program081.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17.
#include int main() { int ocena = 5; float konto = 50; if (ocena == 5) { konto += 50; } else { konto -= 50; } printf("Stan konta=%3.2f\n", konto); return 0; }
W linijce nr 5 tworzymy zmienną o nazwie ocena i przypisujemy jej wartość 5. W linijce nr 6 tworzymy zmienną konto i przypisujemy jej wartość 0 (zakładamy, że tyle masz na koncie pieniędzy przed rozpoczęciem zakładu). Linijka nr 7 to pierwsza instrukcja warunkowa. Sprawdza ona, czy warunek zapisany w nawiasach (czyli w naszym przypadku ocena == 5) jest prawdziwy. Jeżeli tak, to wykonuje się kod zawarty w linijce nr 9, pomiędzy nowymi nawiasami klamrowymi. Jeżeli warunek nie byłby spełniony, wykonuje się kod w nawiasach klamrowych po słowie else. Słowo else można tłumaczyć jako „w innym wypadku”. Na koniec w linijce nr 15 wyświetlamy aktualny stan Twojego
38
Zabawa w programowanie. Język C dla nastolatków
konta, który powinien wynosić 100 zł. Spróbuj teraz zmienić w linijce nr 5 wartość zmiennej ocena, a później spróbuj zmienić w linijce 7 liczbę, do której porównujemy zmienną ocena. W zależności od tego, co wpiszesz, wykona się kod z linijki nr 9 lub kod z linijki nr 13. Nigdy obydwa naraz. Pora na drugi przykład. Wyobraź sobie, że musisz zapłacić podatek w urzędzie skarbowym. Jeżeli udało Ci się zarobić powyżej 2000 zł, podatek wynosi 20%. Jeżeli udało Ci się zarobić poniżej 2000 zł, ale nie mniej niż 1000 zł, podatek wynosi 10%, a jeżeli zarobiłeś poniżej 1000 zł, musisz zapłacić podatek w wysokości 5%. Spójrz na listing 8.2. Listing 8.2. Plik program082.c 1. #include 2. 3. int main() 4. { 5. float zarobek = 1200; 6. float kwotaDoZaplaty; 7. if (zarobek > 2000) 8. { 9. kwotaDoZaplaty = zarobek * 0.2; 10. } 11. else if (zarobek 1000) 12. { 13. kwotaDoZaplaty = zarobek * 0.1; 14. } 15. else 16. { 17. kwotaDoZaplaty = zarobek * 0.05; 18. } 19. printf("Podatek, ktory musisz zaplacic, wynosi: %f zl.\n", kwotaDoZaplaty); 20. return 0; 21. }
W linijce nr 7 ustawiamy warunek, który oznacza „jeśli zmienna o nazwie zarobek jest większa niż 2000”. W linii nr 11 pojawia się coś nowego. Sformułowanie else if oznacza tak jak słowo else „w innym wypadku”, ale po słowie if musimy dopisać konkretny warunek. Gdybyśmy wpisali tutaj samo słowo else, kod wykonywałby się w każdym przypadku, gdyby zmienna zarobek była równa wartości 2000 lub od niej niższa. My natomiast potrzebujemy określić trzy przedziały. W praktyce wygląda to przeważnie tak, że pierwszy warunek określamy jako if, kolejne (bo przecież może ich być bardzo duża liczba) jako if else, a ostatni jako else. Ciekawa rzecz znalazła się pomiędzy nawiasami. Wskazuje ona, że zmienna zarobek musi być mniejsza lub równa (znak — większe niż. = — większe lub równe.
W języku C są również operatory pozwalające na skrócony zapis. Są to operatory takie jak np. +=. Powiedzmy, że mamy zmienną x, którą chcemy zwiększyć o 5. Normalny zapis wyglądałby tak: x = x + 5. Oznacza to, że do wartości zapisanej pod zmienną x dodamy liczbę 5 i całość zapiszemy do zmiennej x, która od tej pory będzie przechowywać tę wartość. Dzięki odpowiedniemu operatorowi możemy zapisać to działanie w taki sposób: x += 5. Oznacza to dokładnie to samo i w wielu przypadkach pozwala nam zaoszczędzić czas i miejsce. To samo dotyczy innych operacji matematycznych, takich jak odejmowanie, mnożenie i dzielenie. Aby móc programować, będziesz potrzebował również operatorów logicznych. Są to: || — operator lub; kompilator sprawdza, czy któryś z warunków jest prawidłowy, np. x ==3 || x==4. && — operator i; kompilator sprawdza, czy obydwa warunki są prawidłowe, np. x > 5 && x < 10. ! — operator negacji, kompilator zamienia prawdę w fałsz i fałsz w prawdę, np. zapis 1! zwróci wartość 0.
Czy wydaje się to trudne? W ogóle się tym nie przejmuj! Omówię to dokładnie w następnym rozdziale i zobaczysz, jakie to proste. W programowaniu używane są również operatory bitowe. Wydaje mi się, że nie jest to dla Ciebie dobry moment na ich poznawanie, ponieważ aby móc ich używać, musiałbyś dokładnie zrozumieć komputerowy system binarny. W tej chwili nie jest to potrzebne, jednak dobrze, abyś wiedział, że takie zagadnienie istnieje. Zostało nam jeszcze parę innych operatorów, ale na ich poznanie przyjdzie czas w kolejnych rozdziałach. Ja już opisałem wszystko, co istotne. Kolej na Ciebie. Liczę, że rozwiążesz wszystkie zadania.
Rozdział 9. Operatory — tak naprawdę znasz to już od podstawówki
43
Dobre praktyki W programowaniu niezwykle ważnym zagadnieniem jest kolejność operatorów. Chodzi o to, że każdy z nich ma swój priorytet i zostanie wykonany w odpowiedniej kolejności. Aby uniknąć niepotrzebnych błędów, staraj się podczas łączenia różnych operatorów używać nawiasów.
Zadania 1. Napisz program, który przetestuje na różnych zmiennych wszystkie operatory
matematyczne i wyświetli ich wynik w konsoli. Nie zapomnij o operatorze modulo! 2. Napisz program, który przetestuje operatory postinkrementacji,
preinkrementacji, postdekrementacji i predekrementacji i wyświetli ich wynik w konsoli. 3. Przerób pierwszy program tak, aby wykorzystał skrócone operatory dodawania, odejmowanie, mnożenia i dzielenia (takie jak np. +=)
i wyświetlił wynik w konsoli.
44
Zabawa w programowanie. Język C dla nastolatków
Rozdział 10.
Podstawy logiki. Nuda? Wcale nie! Logika jest zagadnieniem typowo matematycznym, a bez matematyki skonstruowanie komputera byłoby niemożliwe. Komputer zawsze postępuje logicznie. Nigdy nie popełnia błędu, a jedynie wykonuje w logiczny sposób jedną instrukcję po drugiej. Aby lepiej zrozumieć pojęcie programowania, konieczne jest poznanie pewnego fragmentu logiki matematycznej. Przydatne do tego będą niektóre poprzednie rozdziały, w szczególności ten o instrukcjach warunkowych. Najważniejsze zagadnienia to tak zwane prawda i fałsz. W logice wyrażenie prawdziwe oznaczamy cyfrą 1, zaś fałszywe cyfrą 0. W języku C wygląda to podobnie, z jedną małą różnicą. Cyfra 0 tutaj również jest fałszem, natomiast wyrażeniem prawdziwym jest wszystko inne, czyli np. 1, 2, 3, –0.5, 'a', D80E980 itd. Spróbujmy pokazać to na przykładzie instrukcji warunkowej: if (x == 5)
Program sprawdza, czy zapis w nawiasach (czyli w naszym przypadku x == 5) jest prawdziwy. Co to oznacza? Oznacza to tyle, że kompilator sprawdza, czy zmienna x jest równa liczbie 5, a jeśli tak, to wstawia w tym miejscu 1 jako symbol tego, że to wyrażenie jest prawdziwe. Z punktu widzenia kompilatora wyglądałoby to tak: if (5 == 5)
a w wyniku tego następnie wyglądałoby tak: if (1)
Jeżeli zmienna x nie byłaby równa 5, program umieściłby w tym miejscu cyfrę 0. Prawda, że nie jest to trudne? 3 == 3 jest wyrażeniem prawdziwym, więc w jego miejsce wstawiamy 1, bo tak oznaczamy wyrażenia prawdziwe. 4 == 3 jest wyrażeniem fałszywym, więc w jego miejsce wstawiamy 0. Tak samo sprawa wygląda przy innych operatorach. Spójrz na przykłady:
46
Zabawa w programowanie. Język C dla nastolatków
przyjmijmy, że x = 1, a y = 5. W takim wypadku: x > y
wyrażenie prawdziwe, więc oznaczamy je cyfrą 1
x < y
wyrażenie fałszywe, więc oznaczamy je cyfrą 0
x => y
prawda
x 20) break; if (i % 2 == 0) continue; printf("Kolejna liczba nieparzysta to %d\n", i); } return 0;
Napisaliśmy właśnie program, który wypisuje na ekranie wszystkie liczby nieparzyste z przedziału 1 – 20. W linii nr 7 tworzymy pętlę while. Wpisanie w nawiasie liczby 1 oznacza, że warunek pętli jest ZAWSZE poprawny. Oznacza to, że pętla, a przez to i program będą się wykonywać w nieskończoność, chyba że pomyśleliśmy o tym dalej. W linii nr 9 zwiększamy zmienną i o 1 (ponieważ na początku wynosiła ona 0, zwiększamy ją do 1 przy pierwszym działaniu pętli, czyli tak, jak chcieliśmy). W linii nr 10 stawiamy
Rozdział 11. Pętle. Po co to? Po co to? Po co to?
55
warunek, który powoduje, że jeżeli i osiągnie wartość większą od 20 (czyli np. 21), uruchomi się komenda z linii nr 11, czyli w naszym przypadku break. Doprowadzi ona do przerwania pętli i NATYCHMIASTOWEGO z niej wyjścia. To jest właśnie nasz sposób na zamknięcie pętli. W linii nr 12 sprawdzamy, czy liczba jest parzysta. Robimy to za pomocą operatora modulo. Sprawdzamy, czy liczba i podzielona przez 2 da nam jakąś resztę z dzielenia. Jeżeli reszta będzie równa liczbie 0, oznacza to, że mamy do czynienia z liczbą parzystą i możemy przejść dalej. Zapewnia nam to słowo contiue, które spowoduje NATYCHMIASTOWE przejście do początku pętli, bez wykonania kodu, który jest poniżej. W linii nr 14 wypisujemy na ekranie kolejną liczbę nieparzystą. Gdybyśmy wzięli na przykład liczbę 1, warunek z linii nr 10 nie zadziała, warunek z linii nr 12 również nie zadziała i wykona się funkcja printf. Następnie dla liczby 2 warunek z linii nr 10 nie zadziała, ale zadziała warunek z linii 12, co nie pozwoli na wykonanie się funkcji printf, lecz natychmiast przesunie nas na początek pętli. Dla kolejnych liczb stanie się analogicznie, aż do liczby 21, przy której zadziała warunek z linii nr 10 i dojdzie do zamknięcia pętli while. Na koniec uwaga. W przypadku pętli na programistę czeka poważna pułapka. Otóż jeżeli błędnie wpiszesz warunek wykonywania pętli (tak jak na przykład w poprzednim programie), może ona działać w nieskończoność i pożerać kolejne zasoby komputera. Jest to poważny problem, szczególnie w zaawansowanych programach, dlatego musisz na to bardzo uważać. Pamiętasz rozdział o logice? Jeżeli zdarzy się sytuacja, że będziemy mieli zawsze while(1) lub for(1), będziemy w poważnych tarapatach... Dobre praktyki W przypadku pętli, tak jak w przypadku instrukcji warunkowych, używamy dodatkowego wcięcia w celu poprawy czytelności programu.
Zadania 1. Przerób drugi program tak, aby używał pętli do while. Zauważ, że pętla
wykona się jeden raz. 2. Napisz nowy program. Stwórz w nim pętlę for, która zacznie działanie od zmiennej i = 10 i z każdym kolejnym obrotem będzie ją zmniejszać o 1, a także wyświetlać w konsoli jej wartość, aż do osiągnięcia 0 (podpowiedź: zamiast postinkrementacji i++ w pętli for można użyć np. postdekrementacji i––). 3. Za pomocą pętli for stwórz dwuwymiarową tablicę typu float z 25 kolejnymi
liczbami z zakresu 1 – 25 (patrz na przykład z tabliczką mnożenia). Następnie w pojedynczej pętli while odczytuj po kolei wszystkie elementy tej tablicy i wyświetlaj na ekranie tylko te parzyste, każdy w nowej linii. Użyj słów kluczowych break i continue.
56
Zabawa w programowanie. Język C dla nastolatków
Rozdział 12.
Operacje wejścia i wyjścia oraz podstawowa obsługa błędów. Nareszcie! Operacje wejścia i wyjścia można opisać jako sposób na komunikowanie się użytkownika z komputerem. Jeśli chcemy przekazać coś komputerowi, robimy to za pomocą na przykład klawiatury i nazywamy to operacją wejścia. Kiedy z kolei komputer chce przekazać nam jakieś informacje, robi to za pomocą chociażby monitora i nazywamy to operacją wyjścia. W języku C operacje wejścia i wyjścia są realizowane za pomocą różnych funkcji. Jedną z nich już poznałeś. Mowa o funkcji printf. Z uwagi na to zaczniemy od omówienia najważniejszych funkcji, odpowiedzialnych za operacje wyjścia. Oprócz funkcji printf, którą już poznałeś, możesz równie dobrze użyć funkcji puts. Różnica polega na tym, że w środku funkcji możesz jedynie wpisać gotowy łańcuch znaków, a po jego wyświetleniu funkcja puts przejdzie do nowej linii (tak jak przy użyciu "\n" w przypadku funkcji printf). Po co więc używać funkcji puts? Dlatego, że jest prostsza od printf i szybsza, choć różnica jest raczej nieodczuwalna przy dzisiejszej mocy obliczeniowej komputerów. W języku C możesz użyć również funkcji putchar, która wyświetla na ekranie jeden znak. W konsekwencji tego zapis: for(i = 0; i < dlugoscZdania; i++) putchar(zdanie[i]);
oraz puts(char);
będzie się różnił jedynie przejściem do nowej linii w przypadku puts. Z Twojego punktu widzenia znacznie ciekawsze będą operacje wejścia. Dlaczego? Dlatego, że dają one możliwość wprowadzania z klawiatury danych do naszego programu!
58
Zabawa w programowanie. Język C dla nastolatków
Najpopularniejszą funkcją odpowiedzialną za operację wejścia jest w języku C funkcja scanf. Spójrzmy na nią na listingu 12.1. Listing 12.1. Plik program121.c 1. #include 2. 3. int main() 4. { 5. int wybor; 6. float x, y, wynik; 7. printf("Podaj pierwsza liczbe i potwierdz klawiszem Enter: "); 8. scanf("%f", &x); 9. printf("\nPodaj druga liczbe i potwierdz klawiszem Enter: "); 10. scanf("%f", &y); 11. 12. printf("\nWybierz rodzaj dzialania:\n1-dodawanie\n2-odejmowanie\ n3-mnozenie\n4-dzielenie\ni potwierdz klawiszem Enter: "); 13. scanf("%d", &wybor); 14. switch(wybor) 15. { 16. case 1: 17. wynik = x + y; 18. break; 19. case 2: 20. wynik = x - y; 21. break; 22. case 3: 23. wynik = x * y; 24. break; 25. case 4: 26. wynik = x / y; 27. break; 28. default: 29. printf("\nBledny wybor!"); 30. break; 31. } 32. printf("\nWynik dzialania wynosi: %f.", wynik); 33. return 0; 34. }
Na początku programu tworzymy jedną zmienną typu int oraz trzy zmienne typu float. Następnie w linijkach nr 7, 9 i 12 wyświetlamy użytkownikowi odpowiednie komunikaty. W linijkach nr 8, 10 i 13 uruchamiamy funkcję scanf. W nawiasie obok funkcji wpisujemy najpierw rodzaj oczekiwanego typu danych, który ma wprowadzić użytkownik. Wygląda to tak jak w funkcji printf, czyli np. dla zmiennej typu int używamy znacznika %d itd. Następnie w nawiasie stawiamy przecinek i wpisujemy nazwę zmiennej, do której zostanie przypisana wartość podana przez użytkownika. Jedyne, o czym musisz pamiętać, to znaczek & przed nazwą zmiennej. W praktyce program poprosi nas o podanie dwóch liczb z klawiatury i przypisze podane przez nas wartości do zmiennych x oraz y. Następnie użytkownik musi wybrać rodzaj działania, które go interesuje. Po tym wszystkim uruchomi się instrukcja warunkowa switch, która w zależności od wyboru użytkownika wykona odpowiednie działanie. Na koniec wynik zostanie wyświetlony.
Rozdział 12. Operacje wejścia i wyjścia oraz podstawowa obsługa błędów. Nareszcie!
59
Jak zapewne widzisz, funkcja scanf jest niezwykle prosta. W języku C są jeszcze dostępne takie funkcje wejścia jak gets i getchar. Analogicznie do wcześniej opisanych funkcji puts i putchar funkcja gets pobiera od użytkownika ciąg znaków, aż do wciśnięcia klawisza Enter, a funkcja getchar pobiera jedynie jeden znak. Dostałeś w tym momencie możliwość wprowadzania do programu danych z klawiatury. Musisz jednak wiedzieć, że najczęstszą przyczyną błędnego działania programu jest użytkownik :). Wyobraź sobie, że w linii nr 13 użytkownik wybierze inną cyfrę, niż powinien (niechcący lub celowo). Każdy dobry programista musi wziąć to pod uwagę. Przerobimy poprzedni program tak, aby był nieco bardziej odporny na tego typu przypadki. Spójrz na listing 12.2. Listing 12.2. Plik program122.c 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. 37. 38. 39. 40. 41.
#include #include int main() { char a[10] = {0}; char b[10] = {0}; char c[10] = {0}; int wybor; float x, y, wynik; printf("Podaj pierwsza liczbe i potwierdz klawiszem Enter: "); gets(a); x = atof(a); printf("\nPodaj druga liczbe i potwierdz klawiszem Enter: "); gets(b); y = atof(b); do { printf("\nWybierz rodzaj dzialania:\n1-dodawanie\n2-odejmowanie\n3mnozenie\n4-dzielenie\n: "); gets(c); wybor = atoi(c); if (wybor != 1 && wybor != 2 && wybor != 3 && wybor != 4) { printf("\n\nNie podales liczby z zakresu 1 - 4!\n\n"); continue; } else { switch(wybor) { case 1: wynik = x + y; break; case 2: wynik = x - y; break; case 3: wynik = x * y;
60
Zabawa w programowanie. Język C dla nastolatków 42. 43. 44. 45. 46. 47. 48. 49. 50. 51. 52. 53. 54. 55. 56. }
break; case 4: wynik = x / y; break; default: wynik = x + y; break; } break; } } while(1); printf("\nWynik dzialania wynosi: %f.", wynik); return 0;
Czy program wydaje Ci się długi? Nic się nie martw! Tak naprawdę za chwilę zrozumiesz jego działanie. Z nowych rzeczy przede wszystkim pojawiła się biblioteka stdlib.h. Dodaliśmy ją, ponieważ będziemy potrzebowali funkcji atof oraz atoi. W liniach nr 6 – 8 tworzymy nowe zmienne typu znakowego. Dlaczego? Dlatego, że dane wprowadzane przez użytkownika będziemy wpisywać właśnie do nich. Jak zapewne pamiętasz z wcześniejszych rozdziałów, tablice znakowe mogą zawierać dowolne znaki. Dopiero w dalszej części programu będziemy je konwertować na zmienne typu liczbowego. W liniach nr 13 i 16 pobieramy nasze liczby za pomocą funkcji gets. Powoduje ona, że liczby podane w konsoli przez użytkownika zostaną zapisane odpowiednio do tablic a oraz b. Następnie za pomocą funkcji atof przemieniamy zmienne a i b na zmienne x oraz y w linijkach nr 14 i 17. Funkcja atof zwraca wartość typu float ze zmiennej char wpisanej w nawiasie. Następnie w linii nr 19 zaczynamy pętlę do while. Robimy to dlatego, że jeżeli użytkownik nie wybierze odpowiedniego działania matematycznego, pętla wróci ponownie do początku i da mu ponownie „szansę”. W linii nr 23 pobieramy od użytkownika dane i wpisujemy je do zmiennej c, a następnie w linii nr 24 konwertujemy zmienną c do zmiennej wybor za pomocą funkcji atoi. Działa ona niemal identycznie jak funkcja atof, z tą różnicą, że przemienia zmienną znakową na zmienną typu int. W linii nr 25 sprawdzamy, czy zmienna wybor jest różna od 1, 2, 3 lub 4. Jeśli wszystkie z tych wyrażeń będą nieprawdziwe, to instrukcja warunkowa wyświetli napis o błędzie użytkownika i za pomocą słowa continue przejdzie do początku pętli. Jeżeli natomiast którykolwiek z warunków znajdujących się w nawiasie zwróci nieprawdę, wtedy wykona się kod zapisany w nawiasach klamrowych znajdujący się po słowie else, a na koniec pętla do while zostanie przerwana za pomocą słowa break znajdującego się w linii nr 50. Ta linia jest bardzo ważna! Gdyby jej nie było, funkcja do while działałaby w nieskończoność! Dobre praktyki W rozdziale poruszyłem zagadnienie obsługi błędów. Jest to NIEZBĘDNY element programowania. Programista musi uwzględnić wszystkie możliwe problemy, które mogą wyniknąć z błędnych danych wejściowych (na przykład z powodu pomyłki użytkownika). W dalszej części książki nie będziemy uwzględniać tego w naszych programach, żeby nie komplikować kodu źródłowego, ale jako przyszły programista musisz o tym pamiętać.
Rozdział 12. Operacje wejścia i wyjścia oraz podstawowa obsługa błędów. Nareszcie!
Zadania 1. Napisz program, w którym użytkownik będzie wpisywał swoje imię i nazwisko,
po czym zostanie wyświetlone na ekranie powitanie w stylu: „Imię i Nazwisko, witaj w naszym programie!”. 2. Napisz program, który będzie dodawał, odejmował, mnożył lub dzielił liczby typu float. Każde działanie będzie wykonywane w osobnej funkcji, a jego
wynik zwracany do głównego programu. (Zerknij na rozdział o funkcjach). Uwzględnij obsługę błędów użytkownika.
61
62
Zabawa w programowanie. Język C dla nastolatków
Rozdział 13.
enum i typedef — przydatne bajery enum to tak zwany typ wyliczeniowy. Oznacza to tyle, że za pomocą tego słowa może-
my stworzyć grupę stałych wartości. Po co nam to? Wyobraź sobie, że potrzebujemy jedynie zbioru trzech liczb. Zamiast używać typu int, który przypisałby nam mnóstwo niepotrzebnych liczb całkowitych, stworzymy sobie trzy stałe liczbowe, które na dodatek będą należeć do jednej grupy liczb. Spójrz na listing 13.1. Listing 13.1. Plik program131.c 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.
#include enum wynik { BLAD_KRYTYCZNY, SUKCES, OSTRZEZENIE }; enum wynik funkcja(); int main() { enum wynik x = funkcja(); printf("\nwynik dzialania funkcji to: %d.", x); return 0; } enum wynik funkcja() { int a = 100, b; printf("Podaj liczbe, przez jaka podzielisz 100: "); scanf("%d", &b); if(b == 0) return BLAD_KRYTYCZNY; else if(b < 0) return OSTRZEZENIE;
64
Zabawa w programowanie. Język C dla nastolatków 27. 28. 29. }
else return SUKCES;
W linii nr 3 za pomocą słowa enum tworzymy nowy typ wyliczeniowy o nazwie wynik. W nawiasach klamrowych wpisujemy wszystkie wartości, jakie wynik może przyjąć. W naszym przypadku wystarczą nam trzy stałe, które nazwaliśmy BLAD_KRYTYCZNY, SUKCES i OSTRZEZENIE. Ponieważ nie przypisujemy do nich żadnej wartości, kompilator automatycznie przypisze do pola BLAD_KRYTYCZNY wartość 0, do SUKCES wartość 1, a do OSTRZEZENIE wartość 2. (Można zauważyć, że każdy typ wyliczeniowy składa się jedynie z kilku liczb, z czego do każdej z nich przypisana jest stała nazwa). Całość zamykamy nawiasem klamrowym, a po nim stawiamy średnik. Jest to bardzo ważne, bo bez tego przykład nie będzie działać. W linii nr 9 tworzymy prototyp funkcji o nazwie funkcja. Zauważ, że określamy jej typ nie jako int, float, void czy jeszcze jakiś inny, a jako enum wynik. Oznacza to, że funkcja może zwrócić jedynie wartość 0, 1 lub 2. Jest to bardzo przydatne rozwiązanie, szczególnie w zaawansowanych programach. W linii nr 13 tworzymy zmienną x typu enum wynik i przypisujemy do niej to, co zwróci nam funkcja. W zależności od tego, co wpisze użytkownik, funkcja zwróci wartość 0, 1 lub 2, co dokładnie możemy zaobserwować na ekranie dzięki funkcji printf z linii nr 14. Zamiast bawić się w tworzenie typu wyliczeniowego, można by użyć zwykłego typu int, ale poza wcześniej omówionymi zaletami enum dodatkowo przy rozbudowanym oprogramowaniu znacznie zwiększa to czytelność. Gdybyśmy nasz typ nieco zmienili w ten sposób: enum wynik { BLAD_KRYTYCZNY = –1, SUKCES, OSTRZEZENIE };
spowodowałoby to automatyczne przypisanie wartości 0 do SUKCES i wartości 1 do OSTRZEZENIE. Ciekawostką jest to, co się stanie w takiej sytuacji: enum wynik {A, B, C = 11, D, E};
W tym przypadku do stałej A zostanie przypisana automatycznie wartość 0; do stałej B wartość o 1 większa, czyli 1; do stałej C wartość 11; do stałej D wartość o 1 większa od stałej C, czyli 12; i analogicznie do stałej E wartość 13. W tym rozdziale przedstawię Ci jeszcze jedno słowo kluczowe, a mianowicie słowo typedef. Pozwala ono na utworzenie własnego typu. Co to znaczy? Pamiętasz dotychczasowe typy danych, z jakimi się spotkałeś? Były to np. int, float, char itd. A co, jeśli nie mielibyśmy takiego typu danych jak np. bool w żadnej z dostępnych bibliotek? Musielibyśmy stworzyć go sobie sami, a dzięki słowu typedef nie ma nic prostszego!
Moglibyśmy zrobić to w następujący sposób: enum bool {FALSE = 0, TRUE = 1};
Rozdział 13. enum i typedef — przydatne bajery
65
Dzięki temu tworzymy typ wyliczeniowy enum o nazwie bool, który posiada jedynie dwie wartości: FALSE równą 0 i TRUE równą 1. Jest tu jednak pewna niedogodność. W każdym następnym miejscu, w którym chcielibyśmy użyć tego typu, musielibyśmy robić to w następujący sposób: enum bool x = 1;
Dużo wygodniej będzie zastosować słowo typedef w deklaracji typu wyliczeniowego. typedef enum {FALSE = 0, TRUE = 1} bool;
Dzięki takiemu zapisowi możemy od tej pory używać jedynie słowa bool: bool x = 1;
Dzięki typedef stworzyliśmy nowy typ danych o nazwie bool, który w praktyce jest typem wyliczeniowym enum, zawierającym jedynie dwie wartości: 0 i 1. Zauważ, że słowo bool piszemy na końcu, tuż przed średnikiem. Słowa typedef możemy użyć również w taki sposób: typedef int liczbyCalkowite;
Takie polecenie stworzy nowy typ o nazwie liczbyCalkowite, który tak naprawdę będzie zawierał dokładnie takie same wartości jak typ int. Podsumowując: słowo typedef pozwala nam na utworzenie nowego typu danych, a dzięki temu jest w praktyce bardzo przydatnym narzędziem. Zobaczysz to również w następnych rozdziałach. Dobre praktyki Nazwy typów wyliczeniowych piszemy z małej litery, natomiast nazwy stałych deklarowanych wewnątrz typów wyliczeniowych piszemy dużymi literami.
Zadania 1. Przerób pierwszy program z tego rozdziału tak, aby typy wyliczeniowe enum były zdefiniowane za pomocą słowa typedef. 2. Napisz program, w którym stworzysz nowy typ danych (typedef) będący jednocześnie typem wyliczeniowym (enum) zawierającym liczby od 1 do 7
i który w zależności od liczby, jaką poda użytkownik, będzie wyświetlał na ekranie jeden z dni tygodnia (dla poniedziałku 1, dla niedzieli 7).
66
Zabawa w programowanie. Język C dla nastolatków
Rozdział 14.
Struktury i unie. Liczyłem, że coś takiego wymyślono Czy pamiętasz rozdział o tablicach? Na pewno zastanawiałeś się, jak można zapisać do jednej tablicy zmienne różnych typów. Na szczęście dzięki językowi C mamy taką możliwość. Spójrz na listing 14.1. Listing 14.1. Plik program141.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20.
#include struct struktura { int a; char b; float c; };
int main() { struct struktura pierwsza; struct struktura druga; pierwsza.a = 1; pierwsza.b = 's'; pierwsza.c = 2.5; druga.a = 2; druga.b = pierwsza.b; druga.c = pierwsza.c – 2; printf("\nPierwsza struktura zawiera:a=%d b=%c c=%f", pierwsza.a, pierwsza.b, pierwsza.c); 21. printf("\nDruga struktura zawiera:a=%d b=%c c=%f", druga.a, druga.b, druga.c); 22. return 0; 23. }
68
Zabawa w programowanie. Język C dla nastolatków
Przychodzi nam tutaj z pomocą tak zwana struktura. Z praktycznego punktu widzenia jest to jakby tablica zawierająca zbiór dowolnych elementów. W linijkach od 3. do 8. deklarujemy strukturę. Informujemy w tym miejscu kompilator, jak wygląda przepis na naszą strukturę. To, czy na bazie tego przepisu faktycznie jakąś utworzymy, nie jest jeszcze wiadome. Działa to podobnie jak w przypadku deklaracji funkcji. Jest to jedynie instrukcja określająca, jak dana struktura ma wyglądać, ale jeszcze jej nie inicjalizujemy (ważne, abyś w tym miejscu nigdy nie przypisywał do zmiennych zawartych w strukturze żadnych wartości, ponieważ to jeszcze nie są żadne zmienne, a jedynie tak zwane pola struktury). Pamięć na ich przechowywanie zostanie zarezerwowana dopiero w momencie zdefiniowania (czyli faktycznego utworzenia) struktury. W linijkach nr 12 i 13 tworzymy dwie struktury o nazwie pierwsza i druga. Dopiero w tym miejscu rezerwowana jest pamięć na wszystkie zmienne tych struktur. To znaczy, że na bazie przepisu z linii nr 8 – 13 dopiero teraz faktycznie tworzymy nasze struktury. Następnie w liniach od 14. do 19. przypisujemy do nich różne wartości. Jak łatwo zauważyć, jeśli chcemy użyć zmiennej znajdującej się w strukturze, musimy zrobić to w następujący sposób: nazwa_struktury.nazwa_zmiennej
W przypadku struktur często używamy słowa kluczowego typedef z tego samego powodu co w przypadku enum (patrz poprzedni rozdział). W języku C możemy również używać unii. Na pierwszy rzut oka są one bardzo podobne do struktur, ale różnią się jedną, znaczącą rzeczą. W jednej unii można przechowywać jednorazowo TYLKO jedną zmienną. Zademonstruję to na przykładzie z listingu 14.2. Listing 14.2. Plik program142.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.
#include union unia { int a; double b; }; int main() { union unia pierwsza; pierwsza.a = 1; pierwsza.b = 2.5; printf("\nPierwsza struktura zawiera:a=%d b=%f", pierwsza.a, pierwsza.b); return 0; }
W liniach nr 3 – 7 tworzymy definicję naszej unii, a w linii nr 11 na podstawie definicji deklarujemy jedną z nich. Kompilator rezerwuje w tym momencie pamięć na jeden rodzaj zmiennej! Zawsze wybiera tę zajmującą najwięcej pamięci, żeby każdy inny element mógł się zmieścić (w naszym przypadku największa zmienna to b, ponieważ jest ona typu double). W linii nr 12 zapisujemy do tej zarezerwowanej pamięci wartość 1, po czym w linii nr 13 nadpisujemy ją wartością 2.5. Tak jak wspomniałem, unia
Rozdział 14. Struktury i unie. Liczyłem, że coś takiego wymyślono
69
może przechowywać jednorazowo jedynie jedną zmienną, więc zapamięta najpóźniej zadeklarowaną zmienną. Efekt tego możemy obejrzeć na ekranie. Pod zmienną b wyświetli się wartość 2.5, podczas gdy pod zmienną a wyświetlą się jakieś losowe dane z pamięci. W przypadku unii również pomagamy sobie za pomocą słowa typedef.
Zadania 1. Stwórz strukturę, która będzie w sobie zawierała zmienne różnego typu, w tym jedną unię. Unia z kolei ma zawierać dwie zmienne, jedną typu int, a drugą typu char. Po wprowadzeniu przez użytkownika wartości przypisz ją
do odpowiedniej zmiennej unii. 2. Za pomocą operatora sizeof zmierz rozmiary struktur oraz unii w pierwszym
programie z tego rozdziału.
70
Zabawa w programowanie. Język C dla nastolatków
Rozdział 15.
Wskaźniki — pora na małe wyzwanie W końcu dotarliśmy do kluczowego rozdziału. Wszystko dlatego, że język C jest JEDYNYM popularnym językiem programowania (obok swojego młodszego brata C++), który posiada taki mechanizm jak wskaźniki. Wskaźniki są wspaniałym elementem, który pozwala nam na operowanie bezpośrednio na komórkach pamięci. Do tej pory, tworząc zmienną dowolnego typu, system operacyjny rezerwował pamięć dla tych zmiennych, a później sam na tej pamięci operował. Dzięki wskaźnikom możemy skrócić ten proces, ponieważ sami możemy bezpośrednio odwoływać się do pamięci operacyjnej. Pora na przykład, więc spójrz na listing 15.1. Listing 15.1. Plik program151.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
#include int main() { int a = 5; int* wskaznik; wskaznik = &a; printf("a=%d,wskaznik=%d,*wskaznik=%d,&a=%d\n", a, wskaznik, *wskaznik, &a); return 0; }
W linii nr 6 tworzymy wskaźnik. Robimy to za pomocą rodzaju zmiennej (w tym przypadku int) oraz gwiazdki. W linii nr 7 do zmiennej wskaznik przypisujemy wartość &a. Znak & pozwala nam na wydobycie dokładnego adresu komórki pamięci, w której przechowywana jest zmienna a. Dzięki temu zabiegowi zmienna wskaźnikowa o nazwie *wskaznik wskazuje na liczbę 5, która jest przechowywana w adresie pamięci zapisanym w &a. Podsumowując: tworzymy zmienną a typu int i przypisujemy do niej wartość 5. Następnie tworzymy wskaźnik o nazwie *wskaznik. W kolejnym kroku przypisujemy do zmiennej wskaznik adres komórki pamięci zarezerwowanej dla zmiennej a. Efekt ten możemy zobaczyć na ekranie dzięki linijce nr 8. Pod zmienną a mamy wartość 5, która zapisana jest pod adresem &a. Zmienna wskaznik również wskazuje na adres komórki,
72
Zabawa w programowanie. Język C dla nastolatków
a za pomocą gwiazdki możemy wyłuskać wartość, która jest tam zapisana. Dzięki temu *wskaznik pokazuje nam wartość 5. Z tego powodu w języku C operator * nazywamy operatorem wyłuskania. Na pierwszy rzut oka może wyglądać to na dość skomplikowane. Po co nam w takim razie wskaźniki? Spójrzmy na to z praktycznej strony na listingu 15.2. Listing 15.2. Plik program152.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18.
#include void funkcja(int* x, int* y); int main() { int a = 1; int b = 2; funkcja(&a, &b); printf("a=%d b=%d\n", a, b); return 0; } void funkcja(int* x, int* y) { *x = 3; *y = 4; }
Tworzymy funkcję, do której przekazujemy adresy zmiennych a oraz b. Dzięki temu wewnątrz funkcji w linijkach nr 16 i 17 za pomocą operatora wyłuskania przypisujemy wartości 3 oraz 4 do zmiennych a i b. Pamiętasz rozdział o funkcjach? Bez użycia wskaźników nie byłoby to możliwe! Prawda jest taka, że największe zalety używania wskaźników poznajemy dopiero przy bardziej zaawansowanych programach. Czasami bez użycia wskaźników stworzenie działającego kodu w języku C nie byłoby możliwe. Jest to jeden z głównych powodów używania wskaźników. Używanie jednej zmiennej w kilku różnych miejscach skomplikowanego programu jest możliwe jedynie dzięki operowaniu na tych samych komórkach pamięci. Wskaźniki oznaczają szybkość, precyzję i wygodę, jednak nawet doświadczeni programiści miewają z nimi problemy. Można używać wskaźników podwójnych, potrójnych (z takimi spotkałem się w praktyce) lub jeszcze bardziej zaawansowanych. Wskaźnik potrójny zapisujemy tak: ***wskaznik. Wskazuje on na wskaźnik, który wskazuje na wskaźnik, który wskazuje na odpowiednią komórkę w pamięci operacyjnej. Brzmi to nieco abstrakcyjnie i nie będę omawiał takich przykładów w tej książce. Może kiedyś spotkasz się z nimi w praktyce i wtedy wszystko okaże się dla Ciebie bardziej oczywiste. Przy okazji wskaźników należy wspomnieć o innych związanych z nimi zagadnieniach. Pamiętasz rozdział o strukturach i uniach? One też mogą być wskaźnikami! Różnica w porównaniu ze zwykłymi strukturami polega na tym, że odnosimy się do ich elementów za pomocą operatora –> zamiast . (kropka). Spójrz na listing 15.3.
Rozdział 15. Wskaźniki — pora na małe wyzwanie
73
Listing 15.3. Plik program153.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16.
#include typedef struct { int a; float b; } struktura; int main() { struktura* pierwsza; pierwsza–>a = 1; pierwsza–>b = 2; printf("\nPierwsza struktura zawiera:a=%d b=%f", pierwsza–>a, pierwsza–>b); return 0; }
W linijce nr 11 tworzymy wskaźnik na strukturę, a w kolejnych liniach odwołujemy się do jego elementów za pomocą operatora –>. Znajomość wskaźników pozwala również na ciekawe operacje w związku z tablicami. Przykład pokazano na listingu 15.4. Listing 15.4. Plik program154.c 1. 2. 3. 4. 5. 6. 7. 8.
#include int main() { char* zdanie = "Ala ma kota"; printf("\n2 litera zdania to: %c", *(zdanie + 1)); return 0; }
W linijce nr 5 tworzymy wskaźnik typu char o nazwie *zdanie, a następnie przypisujemy do niego zdanie "Ala ma kota". Pojawia nam się ciekawa informacja na temat tablic. Każda tablica jest ciągiem komórek pamięci. W naszym przypadku początek tablicy zaczyna się pod adresem pamięci zapisanym w zmiennej zdanie i ciągnie się przez 11 komórek pamięci (w jednej komórce zapisywana jest jedna litera!). Poprzez użycie w linii nr 6 zapisu *(zdanie + 1) kierujemy się do komórki pamięci, w której zapisana jest zmienna zdanie, następnie dzięki wpisaniu +1 przeskakujemy „w prawo” o jedną komórkę, a następnie dzięki operatorowi wyłuskania (czyli gwiazdce) odczytujemy literę zawartą w tej komórce. Dobre praktyki Na wskaźniki nie ma rady. Jeśli nie rozumiesz zbyt dobrze tego rozdziału — nie przejmuj się. Niektórym właściwe poznanie tego zagadnienia zajmuje miesiące, a nawet lata. Wszystko przyjdzie w miarę nabywania praktyki, więc ćwicz. Pamiętaj, że przy deklaracji wskaźników gwiazdkę stawiasz zaraz po nazwie typu danych, czyli na przykład char* nazwa_wskaznika.
74
Zabawa w programowanie. Język C dla nastolatków
Zadania 1. Napisz program, w którym tworzysz dowolną strukturę. Następnie utwórz
wskaźnik do niej i spróbuj zmienić wartości jej elementów. Wyświetl wynik na ekranie. 2. Za pomocą wskaźników utwórz zmienną typu char* i przypisz jej dowolne
zdanie, a następnie w dowolnej pętli wyświetl co drugą literę tego zdania.
Rozdział 16.
malloc i free oraz stos i sterta, czyli kilka słów o pamięci operacyjnej Przy okazji unii wspomniałem, że system operacyjny rezerwuje pamięć na zmienną. A co, jeśli kiedyś będziemy potrzebowali sprawdzić, ile miejsca w pamięci zajmuje któryś z elementów? Możemy do tego użyć operatora sizeof. Obejrzyjmy jego działanie na podstawie prostego programu z listingu 16.1. Listing 16.1. Plik program161.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
#include int main() { int a = 5; char b = 'c'; float c = 0.5; double d = -1; printf("%d,%d,%d,%d\n", sizeof a, sizeof b, sizeof c, sizeof d); return 0; }
W linijce nr 9 wyświetlamy, ile bajtów jest zarezerwowanych na poszczególne zmienne. Możemy również użyć operatora sizeof do sprawdzenia rozmiaru typu danych, a robimy to w następujący sposób: sizeof (int)
Dzięki temu wiemy, ile miejsca w naszym systemie operacyjnym zajmuje typ danych int. Zwróć tu uwagę na nawiasy. Zapamiętaj również, że sizeof wygląda jak funkcja, ale nią nie jest. Tak jak pisałem wcześniej, sizeof jest operatorem.
76
Zabawa w programowanie. Język C dla nastolatków
Gdzie najbardziej przydaje się operator sizeof? Pamiętasz może rozdział o tablicach? Przy tworzeniu każdej z nich musimy z góry podawać, ile elementów będzie zawierać. Jednak dzięki słowu kluczowemu malloc możemy zrobić to inaczej. Spójrz na listing 16.2. Listing 16.2. Plik program162.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24.
#include #include int main() { int i; int rozmiar; int* tablica; printf("Podaj rozmiar tablicy: "); scanf("%d", &rozmiar); tablica = (int*) malloc(rozmiar * sizeof(*tablica)); for (i = 0; i < rozmiar; i++) { printf("\nPodaj %d element tablicy: ", i + 1); scanf("%d", &tablica[i]); } printf("\nNasza tablica wyglada nastepujaco: "); for (i = 0; i < rozmiar; i++) printf("%d, ", tablica[i]);
}
free(tablica); return 0;
Na początku zwróć uwagę na stdlib.h. To on właśnie zawiera funkcję malloc, której używamy w tym programie. Spójrzmy na sam kod. Zacznijmy od linii nr 8. Tworzymy w niej wskaźnik do tablicy, ale nie podajemy jeszcze, na co ten wskaźnik ma wskazywać. W kolejnych liniach prosimy użytkownika o podanie rozmiaru tablicy i w wyniku tego rezerwujemy pamięć operacyjną w linii nr 11. Na pierwszy rzut oka ta linia wygląda na skomplikowaną, ale nic bardziej mylnego! Zaczynamy od słowa kluczowego int. Jest to typ naszej tablicy. Obok niego stawiamy gwiazdkę i całość zamykamy w nawiasie. Po co to robimy? Otóż malloc domyślnie tworzy wskaźnik typu void. Ponieważ nam zależy na tym, aby wskaźnik był typu int, wykonujemy tutaj tak zwane rzutowanie, czyli narzucanie przez nas jakiejś zmiennej wybranego przez nas typu danych (innego niż ma domyślnie). Jest to praktyka, której nie powinniśmy nadużywać w języku C, dlatego nie poświęcimy jej w tej książce czasu. Jedynie w tym miejscu przy używaniu funkcji malloc musimy się nią wspomóc. Jest to w tym przypadku jak najbardziej w porządku. Następnie piszemy nazwę naszej funkcji, czyli malloc, i otwieramy nawias. W nawiasie sprawdzamy za pomocą operatora sizeof rozmiar wskaźnika *tablica. Dlaczego? Z bardzo prostego powodu. Musimy wiedzieć, jak duża ma być jedna komórka tablicy. Wynik, który zwróci nam operator sizeof, mnożymy przez zmienną rozmiar, bo dzięki temu mnożymy rozmiar jednego elementu tablicy przez liczbę elementów. W efekcie otrzymamy tablicę o liczbie elementów równej zmiennej rozmiar, gdzie każdy z elementów zajmuje rozmiar równy sizeof(*tablica). W kolejnych liniach programu umieszczamy konkretne wartości w poszczególnych komórkach tablicy, a następnie całość wyświetlamy.
Rozdział 16. malloc i free oraz stos i sterta, czyli kilka słów o pamięci operacyjnej
77
Do omówienia pozostała jeszcze linijka nr 22. Jest to ABSOLUTNIE NAJWAŻNIEJSZA LINIJKA! Powoduje ona zwolnienie pamięci zarezerwowanej wcześniej za pomocą funkcji malloc. Jeśli nie użyjemy funkcji free, może dojść do tak zwanego wycieku pamięci. Chodzi o to, że zawartość naszej tablicy pozostanie w pamięci operacyjnej i inne programy będą miały do niej dostęp. JEST TO NIEZWYKLE NIEBEZPIECZNE! Wyobraźmy sobie, że nasza tablica zawiera dane osobowe pracowników CIA. Jeśli nie wymazalibyśmy jej z pamięci, narazilibyśmy tajnych agentów na poważne niebezpieczeństwo. Jest jeszcze jeden argument przemawiający za absolutnym nakazem używania free. Chodzi oczywiście o zasoby. Jeśli my nie zwolnimy pamięci operacyjnej zajętej przez funkcję malloc, to nikt tego za nas nie zrobi. Podsumowując: zapamiętaj raz na całe życie w parze z malloc ZAWSZE idzie free! Przy okazji tego rozdziału spotykamy się z pojęciem dynamicznej alokacji pamięci. Jak zapewne już się domyśliłeś, jest to sytuacja, w której pamięć operacyjna jest rezerwowana w trakcie działania programu. W innych przypadkach, czyli kiedy pamięć jest rezerwowana przed rozpoczęciem działania programu, mamy do czynienia ze statyczną rezerwacją pamięci. W związku z tymi dwoma pojęciami powinieneś zrozumieć jeszcze jedno zagadnienie. Chodzi o zakres danych, a zaczniemy od wyjaśnienia pojęć stosu i sterty. Stos i sterta są to dwa miejsca w pamięci operacyjnej, z których korzysta uruchomiony program. Z istotnych dla nas rzeczy na stosie przechowywane są: wszystkie zmienne deklarowane statycznie, które są zmiennymi lokalnymi; wartości zwracane przez funkcję; pewna pamięć o wywołaniach różnych funkcji (nie powinieneś zaprzątać
sobie teraz tym głowy). Stos obsługiwany jest automatycznie przez system operacyjny i programista nie musi się tym w ogóle przejmować. Sprawa ma się nieco inaczej w przypadku sterty. Na stercie przechowywane są dane, dla których dynamicznie rezerwowaliśmy pamięć (na przykład za pomocą funkcji malloc). W ich przypadku powinniśmy być ostrożni. Na stercie sami umieszczamy różne dane, ale z tego powodu należy pamiętać, że zawsze je sami musimy usunąć. Dynamiczna alokacja pamięci ciągnie za sobą niebezpieczeństwo wycieku danych oraz jest wolniejsza od statycznej alokacji pamięci. Dodatkowo może powodować fragmentację pamięci, ale jest to nieco zawiłe zagadnienie, dlatego nie będziemy się nim teraz przejmować. Biorąc to wszystko pod uwagę, w praktyce w miarę możliwości powinniśmy unikać dynamicznej alokacji pamięci. Przy okazji poznawania programowania od strony pamięci operacyjnej nie sposób nie wspomnieć o pojęciu, jakim jest NULL. NULL informuje przede wszystkim o tym, że dany wskaźnik nie zawiera żadnej informacji, czyli nie wskazuje na nic. Słowo NULL może być naszym przyjacielem. Wyobraź sobie sytuację, w której zwalniasz pamięć po wskaźniku za pomocą funkcji free. W tym momencie wskaźnik staje się tak zwanym wskaźnikiem wiszącym. Oznacza to, że wskazuje na losowe miejsce w pamięci operacyjnej, które zostało już wyczyszczone. To samo dzieje się z niezaicjalizowanym lub źle zainicjalizowanym wskaźnikiem. Z tego powodu możemy niechcący „namieszać” w naszym komputerze, ponieważ będziemy operowali na nieznanej komórce pamięci.
78
Zabawa w programowanie. Język C dla nastolatków
Dzięki zapisowi wskaznik = NULL w kodzie programu unikniemy takiego ryzyka. Powinniśmy robić to dwukrotnie, przy deklaracji wskaźnika (ponieważ w trakcie działania programu może nie być do niego przypisana poprawnie komórka pamięci) oraz po funkcji free (przydatne, jeżeli wskaźnik był wcześniej różny od NULL). Jest to jedna z dobrych praktyk programowania w języku C. Zapamiętaj również, że z logicznego punktu widzenia NULL jest fałszem tak jak wartość 0. Dobre praktyki Przy tworzeniu wskaźników inicjuj je za pomocą wartości NULL. Przypisuj im również wartość NULL po zwalnianiu ich pamięci za pomocą funkcji free. Uważaj również na moment zwalniania pamięci! Pamiętaj, że po nim nie będziesz już miał dostępu do danych, na które wskazywały wskaźniki.
Zadania 1. Zmień programy z rozdziału 15. w taki sposób, aby alokować ich pamięć dynamicznie za pomocą funkcji malloc. Nie zapomnij o ich zwalnianiu i zerowaniu za pomocą free i NULL! 2. Spróbuj przerobić listing 16.2 tak, aby zerować wskaźniki za pomocą słowa NULL. 3. Zapoznaj się z funkcjami realloc, calloc, memcpy i memset.
Rozdział 17.
Operacje na plikach i parametry wejściowe programu — zawsze chciałem to zrobić Miałeś okazję poznać już sporo zagadnień z zakresu programowania. Przyszła pora, aby zerknąć na pewne zagadnienie. Wszystkie zmienne, których używamy w naszych programach, znajdują się w pamięci operacyjnej komputera. To znaczy, że po zamknięciu programu nie będziemy w stanie ich używać. A jeśli chcemy gdzieś przechować dane z uruchomionego programu? Tutaj z pomocą przychodzą nam funkcje, pozwalające operować na plikach. Pliki w komputerze są zapisane w pamięci fizycznej (czyli przeważnie na dysku twardym) i nie znikają, dopóki celowo ich nie usuniemy. Spójrz na listing 17.1. Listing 17.1. Plik program171.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
#include int main() { char zdanie[] = "Ala ma kota"; FILE *wskaznik; wskaznik=fopen("plik.txt", "w"); fprintf(wskaznik, "%s", zdanie); fclose(wskaznik); return 0; }
Jak widać powyżej, nie jest to specjalnie skomplikowane. W linii nr 6 tworzymy wskaźnik do pliku (w ten sposób „dobieramy się” w języku C do plików znajdujących się na dysku). W linii nr 7, używając funkcji fopen, wczytujemy zawartość pliku do pamięci
80
Zabawa w programowanie. Język C dla nastolatków
operacyjnej i wskazujemy na ten fragment pamięci wskaźnikiem fp (UWAGA! Plik o nazwie plik.txt musi znajdować się w tym samym katalogu co nasz program. Jeżeli znajduje się gdzie indziej, powinniśmy wtedy podać w cudzysłowie całą ścieżkę do niego, czyli np. /home/user/plik.txt). W funkcji fopen jako drugiego parametru użyliśmy litery w. Oznacza ona, że do pliku o nazwie plik.txt chcemy coś zapisać, a jeśli taki plik wcześniej nie istniał, to zostanie w tym momencie uruchomiony po raz pierwszy. Jest to niebezpieczne, bo jeżeli taki plik już istnieje, zostanie przez nas zastąpiony. Aby tego uniknąć, zamiast litery w możemy użyć litery a. Za jej pomocą do istniejącego już pliku dopisujemy nowe informacje. W funkcji fopen możemy również używać litery r służącej jedynie do odczytu oraz kilku innych opcji, których nie będziemy tutaj omawiać, ponieważ nie jest to nam w tej chwili potrzebne. Kolejną nowością jest tutaj linia nr 8, w której używamy funkcji fprintf. Funkcja ta pozwala na zapis danych do otwartego przez nas wcześniej pliku. W funkcji fprintf najpierw podajemy wskaźnik na nasz plik, czyli w naszym przypadku wskaznik, następnie wpisujemy rodzaj danych, które zapiszemy do pliku, w taki sam sposób, jak robimy to w zwykłej funkcji printf (u nas jest to %s), a na koniec podajemy treść, którą chcemy do tego pliku zapisać. W linii nr 9 za pomocą funkcji fclose zamykamy otwarty wcześniej plik. Jest to BARDZO WAŻNY element, bez którego nie usuniemy pliku z pamięci operacyjnej, a przez to niepotrzebnie zajmiemy na dłużej zasoby komputera. Co gorsza, narażamy się na wyciek danych. A jeśli chcemy wczytać zawartość pliku do wskazanej zmiennej? Nic prostszego. Spójrz na listing 17.2. Listing 17.2. Plik program172.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15.
#include #include int main() { int i = 0, n = 0; char tekst[1024]; FILE *wskaznik; wskaznik = fopen("plik.txt", "r"); fgets(tekst, sizeof tekst, wskaznik); printf("\n%s\n", tekst); fclose(wskaznik); printf("\nstrlen(tekst)=%d n=%d ", strlen(tekst), n); return 0; }
W tym programie pierwszy najważniejszy element ma miejsce w linii nr 8. Tworzymy tutaj tablicę znaków o rozmiarze 1024, do której będziemy wczytywać zawartość naszego pliku tekstowego. Bardzo ważne jest, abyśmy użyli bufora przynajmniej tak dużego, jak liczba znaków znajdujących się we wczytywanym pliku. Ja dla bezpieczeństwa użyłem liczby 1024, poza tym dla każdego informatyka jest to liczba okrągła ;). W linii nr 9 używamy przełącznika r, ponieważ chcemy jedynie odczytać zawartość pliku. Następnie używamy funkcji fgets do wczytania wszystkich znaków z pliku do ta-
Rozdział 17. Operacje na plikach i parametry wejściowe programu
81
blicy znaków o nazwie tekst. Środkowy parametr sizeof tekst określa rozmiar naszego „pojemnika”, w którym będziemy przechowywać treść. Kolejne linie programu są już oczywiste. W rozdziale należy również wspomnieć o parametrach wejściowych programu. Jest to bardzo proste zagadnienie, więc spieszę już z tłumaczeniem. Spójrzmy na przykład z listingu 17.3. Listing 17.3. Plik program173.c 1. #include 2. 3. int main(int argc, char* argv[]) 4. { 5. int ilosc = argc; 6. int wynik = 0; 7. int i; 8. 9. for(i = 1; i < ilosc; i++) 10. wynik += atoi(argv[i]); 11. 12. printf("\nWynik dodawania parametrow wejsciowych to: %d a nazwa uruchomianego programu to %s\n", wynik, argv[0]); 13. return 0; 14. }
Kluczowa rzecz dzieje się tutaj w linii nr 3. W nawiasie obok funkcji main pojawiają się dwie zmienne: argc i argv. argc oznacza liczbę parametrów wejściowych w programie, a argv[] jest tablicą zawierającą wszystkie z tych parametrów. O co tu chodzi? Odejdźmy na chwilę od naszego przykładu. Do tej pory, gdy uruchamiałeś jakiś program, robiłeś to w następujący sposób: ./nazwa_programu Dzięki zapisowi main(int argc, char* argv[]) otrzymujemy możliwość uruchamiania programów w następujący sposób: ./nazwa_programu 1 a b 3 Spowoduje to utworzenie 5-elementowej tablicy wskaźników typu char. Co to oznacza? Oznacza to tyle, że w pierwszym elemencie tablicy, czyli argv[0], zostanie zapamiętane miejsce w pamięci operacyjnej, w którym została zapisana nazwa uruchomianego programu. Następnie w drugim elemencie argv[1] zostanie zapisany parametr pierwszy (w naszym przypadku liczba 1), w argv[2] parametr drugi (u nas litera a), w argv[3] parametr trzeci (tutaj litera b) oraz w argv[4] parametr czwarty (czyli liczba 3). Pamiętaj, że jest to tablica typu char, więc parametry 1 i 3 nie są cyframi, a zmiennymi znakowymi typu char. Dochodzi nam jeszcze zmienna argc typu int. Jest to najzwyczajniej w świecie liczba parametrów przekazanych do programu — w naszym przypadku wynosi ona 5 (argv[0] również jest jednym z liczonych elementów).
82
Zabawa w programowanie. Język C dla nastolatków
Wróćmy do naszego przykładu. W linii nr 5 naszego programu tworzymy zmienną ilosc, której przypisujemy wartość argc. Nie musielibyśmy tego robić, ale stworzyliśmy ją dla zobrazowania sytuacji. W linii nr 9 tworzymy pętlę for, która przekształca wszystkie parametry programu na zmienne typu int i je sumuje w zmiennej o nazwie wynik. Kompilujemy program, po czym uruchamiamy go następującym poleceniem: ./program 5 7 2 8 -4 13 Dzięki temu wszystkie parametry zostaną zsumowane, a na ekranie pojawi się wynik 31.
Zadania 1. Utwórz samodzielnie plik tekstowy i skopiuj do niego dowolny tekst (np.
jakiś artykuł z internetu). Następnie odczytaj, ile razy występuje w nim litera a, a wynik zapisz do drugiego pliku o nazwie podanej przez użytkownika jako parametr przy uruchomieniu programu. UWAGA! Drugi plik nie może być plikiem tekstowym, a musi być plikiem binarnym (podpowiedź: do operacji na plikach binarnych w funkcji fopen należy użyć przełącznika b). 2. Dodaj do programu obsługę błędów. Niech program sprawdza,
czy użytkownik podał odpowiednią liczbę parametrów.
Rozdział 18.
Preprocesor, kompilator i linker — to tylko groźnie brzmi Nadszedł czas, abyś nieco lepiej zrozumiał, co się dzieje podczas tłumaczenia napisanego przez Ciebie programu na język zrozumiały dla komputera. Pierwszy krok po rozpoczęciu tłumaczenia to uruchomienie preprocesora. Preprocesor analizuje nasz kod linijka po linijce i jeżeli znajdzie odpowiednią informację, rozpoczyna swoją pracę. Informacje dla preprocesora zaczynają się od znaku # i w języku C nazywane są one dyrektywami preprocesora. Kiedy nasz preprocesor znajdzie już taką linię (np. #include ), wykonuje on odpowiednie działanie. W przypadku #include wstawia zamiast tej linii zawartość pliku stdio.h. Po przeanalizowaniu całego pliku preprocesor zapisuje znalezione zmiany w tymczasowym pliku źródłowym. Dopiero tak przygotowany plik jest zrozumiały dla głównego tłumacza — kompilatora. Najważniejsze dyrektywy preprocesora dostępne w języku C to: #include — już poznaliśmy, wstawia do programu zawartość pliku; #define A B — wstawia w kodzie źródłowym wyrażenie B w każdym miejscu wystąpienia wyrażenia A; #undef — wycofuje działanie define; #ifdef — instrukcja warunkowa (działa jak if()), po której możemy również użyć dyrektyw #elif, #else (działają analogicznie jak else if(), else()) oraz #endif kończącej dyrektywę #ifdef; #ifndef — odwrotność #ifdef, wyjaśniona w dalszej części rozdziału; #error — powoduje przerwanie procesu kompilacji; #warning — powoduje wyświetlenie komunikatu, używane jako ostrzeżenie
dla programisty;
84
Zabawa w programowanie. Język C dla nastolatków #pragma — dyrektywa pozwalająca na przekazanie kompilatorowi konkretnych informacji (np. #pragma once nakazuje kompilatorowi dołączenie pliku
nagłówkowego tylko jeden raz). Za pomocą wyrażenia #define tworzymy również tak zwane makra. Np. zapis: #define FUNKCJA(x) (x*2)
spowoduje, że w każdym miejscu, gdzie wystąpi zmienna x, preprocesor wstawi zapis x*2, np. zapis: printf("%d", FUNKCJA(3));
po zakończeniu pracy preprocesora będzie wyglądał następująco: printf("%d", FUNKCJA(3*2));
Duża liczba makr, szczególnie jeżeli posiadają rozbudowaną konstrukcję, znacznie obniża czytelność kodu, dlatego w praktyce nie powinniśmy ich nadużywać. Aby zobrazować działanie preprocesora, stwórzmy prosty program. Spójrz na listing 18.1. Listing 18.1. Plik program181.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10.
#include #define STALA 10 int main() { int wynik = 5 + STALA; printf("Wynik dzialania to: %d", wynik); return 0; }
Teraz w terminalu wpiszmy tę komendę: gcc –E program.c –o test
Dzięki niej uruchamiamy sam preprocesor i zapisujemy wynik jego działania do pliku test. Po wszystkim otwórz plik test w programie gedit. Zauważ, że na jego początku znajduje się dołączony plik stdio.h, a na samym końcu znajduje się właściwy kod naszego programu. Zamiast stałej o nazwie STALA preprocesor podstawił samą wartość. Jednocześnie nasz preprocesor usunął wszystkie dyrektywy zaczynające się od znaku #, ponieważ ich dalsze pozostawienie nie miałoby sensu, a sam kompilator by ich nie zrozumiał. Tak przygotowany plik jest gotowy do wykonania drugiego kroku, czyli procesu kompilacji. Kompilator tłumaczy plik przygotowany przez preprocesor na tak zwany kod maszynowy i zapisuje go w kolejnym pliku. Tutaj dzieje się najważniejsza rzecz z punktu widzenia programisty, ponieważ kompilator sprawdza nasz program pod kątem błędów, które w nim popełniliśmy! O jakich błędach mówimy? Może być ich mnóstwo. Zaczynając od błędnej składni (niechcący wpisaliśmy jakiś niechciany znak lub zapomnieliśmy o średniku), poprzez błędy typu odwoływania się do nieistniejących elementów
Rozdział 18. Preprocesor, kompilator i linker — to tylko groźnie brzmi
85
tablicy, błędy, gdy zapomnieliśmy zadeklarować zmiennej lub funkcji, a odwołujemy się do niej itd. Rodzajów błędów jest całkiem sporo i cała sztuka w programowaniu polega na tym, aby czytać je ze zrozumieniem. Niezbędna tutaj będzie znajomość pojedynczych pojęć programistycznych w języku angielskim lub tłumaczenie ich za pomocą słownika. Zademonstruję to na przykładzie z listingu 18.2. Listing 18.2. Plik programBledny.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12.
#include #define STALA 10 int main() { int wynik; x = 6; wynik = x + STALA; printf("%d", wynik); return 0 }
Po uruchomieniu w terminalu polecenia gcc programBledny.c –o programBledny wyświetli się następujący komunikat: programBledny.c: In function ‘main’: programBledny.c:8:5: error: ‘x’ undeclared (first use in this function)
Ten komunikat informuje nas słowem error o tym, że jest to błąd, oraz że zmienna x nie była nigdzie wcześniej zadeklarowana. programBledny.c:8:5: note: each undeclared identifier is reported only once for each function it appears in
W tym miejscu za pomocą słowa note kompilator jedynie przekazuje nam informację o tym, że jeśli zmienna x występowałaby również w dalszej części programu, to kompilator i tak wyświetliłby błąd o braku jej deklaracji jedynie przy pierwszym jej wystąpieniu. programBledny.c:12:1: error: expected ‘;’ before ‘}’ token }
Jest to bardzo prosty błąd; wskazuje, że zapomnieliśmy o średniku w linii 12. Bardzo ważne jest, aby komunikaty kompilatora czytać na spokojnie i ze zrozumieniem. Ważne jest również, żeby poprawiać błędy w programie w takiej kolejności, w jakiej wyświetlił je kompilator, ponieważ czasami zdarza się, że błędy znajdujące się wcześniej w programie powodują błędy znajdujące się dalej. Poznałeś już komunikaty typu note i error. Kompilator wyświetla również ostrzeżenia (warning). Są to informacje, które przeważnie mówią, że uda się przejść proces kompilacji, ale później program może nie działać tak, jak powinien. Najprostszy przykład to zapis: if (x = 1)
86
Zabawa w programowanie. Język C dla nastolatków
Z punktu widzenia kompilatora nie jest to błąd, ponieważ do zmiennej x przypisujemy wartość 1, a cały zabieg zawsze będzie prawdą, więc instrukcja w nawiasach klamrowych po if zawsze będzie się wykonywać. Z naszego punktu widzenia natomiast jest to duży błąd, ponieważ powinno tam być: if (x == 1)
czyli sprawdzenie, czy zmienna x równa jest 1. Pozostał nam do objaśnienia ostatni z elementów — linker. Linker łączy w całość wszystkie pliki stworzone przez kompilator oraz dołącza do nich zewnętrzne funkcje (takie jak na przykład printf), które nie były definiowane w naszym programie. W efekcie tego tworzony jest jeden program wykonywalny, który jest całkowicie zrozumiały dla komputera i może być na nim uruchomiony. Linker również potrafi wyświetlać błędy, a najpopularniejszy z nich to taki, kiedy w komputerze nie ma bibliotek, których użyłeś w swoim programie. Wszystkie biblioteki używane w tej książce (takie jak na przykład stdio.h) są standardowymi bibliotekami, które znajdują się w większości systemów operacyjnych. Jednak przy bardziej zaawansowanych programach często potrzebujemy używać bibliotek niestandardowych (na przykład glib, sqlite3, curl itd.). W takim przypadku, aby linker mógł ich użyć, wcześniej będzie trzeba zainstalować je w systemie operacyjnym. Jak? To już zależy od używanego systemu i jest to wiedza z zakresu systemów operacyjnych, a nie programowania, dlatego nie będziemy zgłębiać tego problemu. Cały proces przetwarzania naszego kodu źródłowego na program zrozumiały dla kompilatora możemy porównać do projektowania samochodu. Na początku projektanci siadają przy komputerach i tworzą na nim wirtualną wersję samochodu (tak jak my tworzymy kod programu). Następnie przekazują ten projekt inżynierom, aby stworzyli z niego prawdziwe auto. Najpierw szef inżynierów poprawia delikatnie projekt, tak aby kolejni inżynierowie go zrozumieli (czyli działa jak preprocesor). Następnie drugi inżynier na podstawie tego projektu tworzy w fabryce poszczególne części samochodu (jest to nasz kompilator). Do fabryki dostarczane są również części samochodu robione w innych miejscach, takie jak na przykład tapicerka (są to nasze zewnętrzne biblioteki). Ostatecznie wszystkie elementy są łączone przez trzeciego inżyniera w jedną całość (jest to nasz linker). Prawda, że nie jest to takie trudne do zrozumienia? Przy okazji procesu kompilacji należy wspomnieć o ostatniej kwestii. Rozbudowane programy zawierają wiele różnych plików i niezwykle trudno byłoby je kompilować za pomocą polecenia gcc wpisywanego w terminalu. Przychodzą nam tu z pomocą systemy do zarządzania procesami kompilacji. Jest ich bardzo wiele (np. make, cmake, autotools) i prawda jest taka, że każdy z nich jest tematem na osobną książkę. W tym momencie wiedza ta nie jest Ci potrzebna, a w miarę rozwoju umiejętności będziesz zgłębiać to zagadnienie systematycznie. Dobre praktyki Każdy programista najwięcej uczy się na błędach. Bardzo ważne jest, aby komunikaty z nich wynikające czytać po kolei i ze zrozumieniem. Zdecydowanie warto poświęcić na to więcej czasu. Pamiętaj również, że błędy mogą przytrafić się na poziomie preprocesora, kompilatora lub linkera.
Rozdział 18. Preprocesor, kompilator i linker — to tylko groźnie brzmi
Zadania 1. Spróbuj umyślnie wprowadzić różnego rodzaju błędy w kilku programach
z poprzednich rozdziałów. Zauważ, jak bardzo różnią się od siebie. Postaraj się poświęcić na to dużo czasu. W ten sposób nauczysz się czegoś, co zwiększy Twoją szansę na szybszą naprawę błędnie napisanego programu w przyszłości. 2. Spróbuj napisać za pomocą dyrektywy #define makro, które będzie obliczało
pierwiastek kwadratowy z dowolnej liczby. Użyj go w rozdziale 4. zamiast funkcji sqrt.
87
88
Zabawa w programowanie. Język C dla nastolatków
Rozdział 19.
Pliki nagłówkowe oraz static i extern, czyli jak dzielić program i dlaczego? W dotychczasowych programach, które pisaliśmy, używaliśmy różnych bibliotek, takich jak np. stdio.h. Istnieje jednak pewne ważne zagadnienie, które powinieneś poznać. Pliki z rozszerzeniem .h (czyli te, które po swojej nazwie i po kropce mają literę h) są jedynie tak zwanymi plikami nagłówkowymi. Całą, kompletną bibliotekę stanowi połączenie tego pliku z plikiem o rozszerzeniu .c (czyli takim, jakie do tej pory pisaliśmy). To znaczy, że biblioteka o nazwie stdio składa się z pliku nagłówkowego stdio.h oraz pliku źródłowego stdio.c. Zasadę działania zademonstruję na poniższym przykładzie. Będziemy teraz tworzyć trzy pliki. Najpierw napiszemy program główny. Spójrz na listing 19.1.a. Listing 19.1.a. Plik program191.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
#include #include "kalkulator.h" int main() { int x = 5; int y = 7; printf("Wynik dodawania to: %d", dodaj(x, y)); printf("Wynik odejmowania to: %d", odejmij(x, y)); return 0; }
90
Zabawa w programowanie. Język C dla nastolatków
W linii nr 2 dodajemy plik nagłówkowy kalkulator.h, a w liniach nr 8 i 9 używamy funkcji dodaj oraz odejmij, które będą zawarte w naszej bibliotece o nazwie kalkulator. Teraz czas stworzyć dwa pliki biblioteki kalkulator. Spójrz na listingi 19.1.b oraz 19.1.c. Listing 19.1.b. Plik kalkulator.h 1. 2. 3. 4. 5. 6. 7.
#ifndef KALKULATOR_H #define KALKULATOR_H int dodaj(int x, int y); int odejmij(int x, int y); #endif
Listing 19.1.c. Plik kalkulator.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
#include "kalkulator.h" int dodaj(int x, int y) { return x + y; } int odejmij(int x, int y) { return x - y; }
Zauważ, że stworzyliśmy pliki kalkulator.h i kalkulator.c będące jedną biblioteką oraz plik o nazwie program.c, który używa biblioteki kalkulator.h. Zauważ również, że nazwa naszej biblioteki jest zapisana w podwójnym cudzysłowie, a nie w nawiasach ostrych. Pamiętaj również, że w plikach kalkulator.c oraz program.c musi znajdować się linia #include "kalkulator.h". O zawartości kalkulator.h wspomnę za chwilę. Aby teraz skompilować ten program, najpierw musimy uruchomić w terminalu polecenie: gcc -o kalkulator.o -c kalkulator.c
Skompiluje nam ono plik kalkulator.c i stworzy z niego plik kalkulator.o zapisany w kodzie maszynowym. Następnie uruchamiamy kolejne polecenie w terminalu: gcc kalkulator.o program.c -o program
Spowoduje ono skompilowanie pliku program.c i przetłumaczenie go na plik program.o, a następnie linker połączy gotowe elementy kodu maszynowego, czyli kalkulator.o i program.o, i stworzy ostateczny plik wykonywalny o nazwie program. Jest to nieco zawiłe, szczególnie jeżeli będziemy mieli do czynienia z wieloma plikami w ramach jednego programu. Dlatego w praktyce wykorzystuje się do tego odpowiednie narzędzia, takie jak na przykład make, o których wspomniałem w poprzednim rozdziale. Jak należy konstruować biblioteki? W plikach nagłówkowych powinny znajdować się przede wszystkim deklaracje funkcji, deklaracje zmiennych i stałych globalnych oraz
Rozdział 19. Pliki nagłówkowe oraz static i extern
91
deklaracje struktur, unii i nowych typów. W plikach źródłowych z kolei powinny znajdować się przede wszystkim ciała funkcji oraz pozostałe elementy. Po co nam pliki nagłówkowe? Są trzy główne powody. Pierwszy z nich jest bardzo banalny — chodzi o wygodę. Wyobraźcie sobie, że tworzycie funkcję, którą później wykorzystujecie w wielu innych plikach. Zamiast ją przepisywać kilka razy, wystarczy, że stworzycie ją jeden raz, a następnie dodacie do różnych plików jedynie za pomocą polecenia #include. Drugi powód to oszczędność zasobów komputera. Zauważyłeś na pewno, że plik nagłówkowy zbudowany jest za pomocą następujących dyrektyw preprocesora: #ifndef NAZWA_PLIKU_H ... #define NAZWA_PLIKU_H #endif
Załóżmy, że mamy dwa pliki z kodem źródłowym (czyli pliki z rozszerzeniem .c) i obydwa z nich używają jednego pliku nagłówkowego .h. W procesie kompilacji pierwszego pliku .c powinno dojść również do kompilacji biblioteki i utworzenia jej wersji w kodzie maszynowym. Następnie przy kompilacji drugiego pliku .c doszłoby do ponownej kompilacji biblioteki. Nie jest to potrzebne i dzięki zastosowaniu dyrektyw #ifndef, #define i #endif możemy tego uniknąć. Zadbają one o to, aby biblioteka została skompilowana jedynie jeden raz. Ciekawostką jest to, że nazwę pliku piszemy dużymi literami, a zamiast kropki wstawiamy znak podkreślenia. Trzeci powód tworzenia plików nagłówkowych to bezpieczeństwo. Załóżmy, że przyjdzie nam pracować dla firmy nr 1, która współpracuje z firmą nr 2. Firma nr 2 tworzy jakieś oprogramowanie, które powinno współpracować z naszym oprogramowaniem, napisanym w firmie nr 1. Jest jeden problem. Nie chcemy drugiej firmie pokazywać, jak napisaliśmy nasz program. W tym momencie przychodzą nam z pomocą pliki nagłówkowe. Wystarczy, że pokażemy tylko je. Firma nr 2 otrzyma dzięki temu listę funkcji, jakie posiadamy, z informacjami, jakie typy danych one przyjmują oraz jakie typy danych zwracają, i z krótkim opisem ich funkcjonalności. To, co nasz program faktycznie robi, zapisane jest w plikach źródłowych .c, a ich znajomość nie jest firmie nr 2 potrzebna do pracy. Pozostanie to naszą słodką tajemnicą :). Dowiedziałeś się już, czym są pliki nagłówkowe. Nie da się jednak ich używać w efektywny sposób bez znajomości dwóch ważnych zagadnień. Chodzi o static i extern. Aby wyjaśnić ich działanie, stworzymy program składający się z trzech plików: program192.c, biblioteka.h i biblioteka.c. Spójrz na listingi 19.2.a, 19.2.b oraz 19.2.c. Listing 19.2.a. Program192.c 1. 2. 3. 4. 5. 6. 7. 8.
#include #include "biblioteka.h" int main() { int i; int a = 9; int b = 3;
92
Zabawa w programowanie. Język C dla nastolatków 9. 10. 11. 12. 13. 14. 15. 16.
int c = 4; int wynik = 0;
17. 18. 19. 20. 21. 22. 23. 24. }
for (i = 0; i < 5; i++) { static c = 10; c++; printf("\nWartosc zmiennej c w petli for przy przebiegu nr %d wynosi: %d", i + 1, c); wynik = funkcja(a, b); printf("\nWartosc zwracana przez funkcje w petli for przy przebiegu nr %d wynosi: %d", wynik); } printf("\nWartosc zmiennej c za petla for wynosi: %d\n", c); printf("\nhaslo1=%s\n", haslo); return 0;
Listing 19.2.b. Plik biblioteka.h 1. 2. 3. 4. 5. 6. 7. 8.
#ifndef BIBLIOTEKA_H #define BIBLIOTEKA_H extern char haslo[]; int funkcja(int x, int y); #endif
Listing 19.2.c. Plik biblioteka.c 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19.
#include #include "biblioteka.h" char haslo[] = "abc"; static int _funkcjaStatyczna(int x, int y) { return x + y; } int funkcja(int x, int y) { static int z = 3; int v = 1; z++; v++; printf("\nz=%d v=%d\n", z, v); return _funkcjaStatyczna(x, y) + z + v; }
Rozdział 19. Pliki nagłówkowe oraz static i extern
93
Zacznijmy od omówienia biblioteki. W pliku nagłówkowym biblioteka.h deklarujemy zmienną haslo. Zauważ, że nie jest to definicja! W tym miejscu jedynie przesyłamy informację, że taka zmienna będzie zadeklarowana w innym miejscu. Dzięki temu zmienna haslo jest dostępna dla każdego pliku, w którym jest dodany plik biblioteka.h, ale nie możemy odczytać jej z poziomu pliku nagłówkowego. Dzięki temu nikt, kto ma dostęp tylko do plików nagłówkowych (na przykład zewnętrzna firma), nie będzie miał wglądu w tę zmienną. Wszystko to jest możliwe dzięki zastosowaniu słowa extern. Bez niego kompilator wyrzuciłby błąd, ponieważ oczekiwałby, że zmiennej haslo zostanie nadana wartość już w pliku biblioteka.h. Można przyjąć, że wszystkie funkcje zadeklarowane w pliku nagłówkowym są domyślnie funkcjami typu extern i postawienie przed nimi tego słowa nie zmieni ich zachowania ani funkcjonalności. Zajrzyjmy teraz do pliku biblioteka.c. W linii nr 4 definiujemy zmienną haslo, o której wspomniałem wcześniej. Przejdźmy teraz do linii nr 6. Zauważ, iż stworzyliśmy w niej nową funkcję, przed którą wpisaliśmy słowo static. Słowo to powoduje, że ta funkcja będzie widoczna jedynie w obrębie pliku biblioteka.c. Robimy tak dlatego, że tak naprawdę nie będziemy jej potrzebowali nigdzie indziej, więc nie ma sensu, aby pozwalać na dostęp do niej z zewnątrz. Kolejne zastosowanie słowa static możemy zaobserwować w linii nr 13. Po uruchomieniu programu zauważysz, że dzięki niemu wartość zmiennej z zostaje zapamiętana, tak jakby zmienna z została zmienną globalną (w przeciwieństwie do zmiennej v, której wartość przy każdym kolejnym wywołaniu funkcji funkcja wynosi 1). Jako ciekawostkę zaobserwuj, że w linii nr 18 po słowie return odwołujemy się do kolejnej funkcji i czekamy na jej wykonanie. Jest to jak najbardziej dopuszczalne. Spójrzmy teraz na plik program192.c i na linie nr 9, 14 i 20. Dzięki użyciu słowa static nadajemy zmiennej c wartość 10 na czas wykonywania pętli. Przy każdym kolejny
przebiegu jej wartość jest zwiększana poprzez inkrementację o jeden. Po zakończeniu działania pętli cały proces jest jakby zapominany i zmienna c otrzymuje z powrotem swoją dawną wartość. Podsumowując: słowa kluczowe static i extern służą programistom języka C przede wszystkim do ustawiania odpowiedniego dostępu do funkcji oraz zmiennych. Jest to niezwykle przydatna funkcjonalność, szczególnie w programowaniu komercyjnym. Dobre praktyki W programowaniu rzadko używa się słowa static do oznaczania zmiennych wewnątrz funkcji. Tutaj zrobiliśmy to na potrzeby przykładu. W praktyce takie zmienne (tak jak i zmienne globalne) powinny być zerowane na koniec każdego programu. Chodzi o to, żeby wartości, które przechowują, nie wyciekły poza obszar programu. Kolejną dobrą praktyką jest to, aby w plikach nagłówkowych opisywać działanie wszystkich elementów tam występujących w postaci komentarza tuż nad nimi. Często te komentarze są główną dokumentacją kodu źródłowego, na podstawie której wdrażają się nowi pracownicy w firmie. Tym bardziej jest to ważne, jeżeli nasz program ma kilkanaście, kilkadziesiąt lub kilkaset tysięcy linii kodu. Dodatkowym plusem jest to, że udostępniając pliki nagłówkowe firmom zewnętrznym, dajemy im wraz z nimi ważne informacje o działaniu naszych funkcji, ale bez odkrywania ich implementacji.
94
Zabawa w programowanie. Język C dla nastolatków
Zadania 1. Podziel programy z rozdziału 6. na pliki źródłowe, tak aby wszystkie funkcje poza main zostały przeniesione do osobnej biblioteki. Zbuduj całość za pomocą polecenia gcc i zobacz rezultat na ekranie. 2. Do listingu 19.1 dodaj słowa static i extern do poszczególnych funkcji.
Zaobserwuj, co się dzięki temu zmieniło.
Rozdział 20.
Na koniec: to dopiero początek wspaniałej przygody! :) Gratuluję Ci! Dotrwałeś do ostatniego rozdziału. Dzięki temu masz już solidne podstawy języka C. Pamiętaj, że język programowania jest narzędziem pozwalającym na tworzenie wspaniałych i złożonych rzeczy. Możemy to porównać do malowania obrazów. To tak jak Jan Matejko posiadał narzędzia w postaci pędzli, a wybitne dzieła stworzył za ich pomocą na bazie swojego talentu i doświadczenia. Ty otrzymałeś pędzle. Przyszła pora, abyś stał się mistrzem w ich używaniu. W tym celu powinieneś rozpocząć wspaniałą przygodę, jaką jest programowanie, i zacząć tworzyć kolejne programy. Z czasem będą one coraz bardziej złożone i coraz lepiej napisane, a Twoja wiedza będzie się pogłębiać. Pamiętaj, aby nigdy się nie zniechęcać. Jan Matejko również nie namalował słynnej „Bitwy pod Grunwaldem” od razu. Każdy, kto zaczynał życie programisty, był dokładnie w takim momencie jak Ty! Wszystko musi przyjść z czasem. Mam nadzieję, że ta książka spełniła Twoje oczekiwania i okazała się wystarczająco zrozumiała i pomocna. Taki był mój cel. Po niej przyjdzie pora na bardziej zaawansowane pozycje. Po przeczytaniu niniejszej książki ich zrozumienie przyjdzie Ci znacznie łatwiej. Życzę Ci powodzenia i trzymam za Ciebie kciuki! Kto wie, czy niebawem nie staniemy ramię w ramię w szeregach tej samej firmy tworzącej oprogramowanie.
96
Zabawa w programowanie. Język C dla nastolatków
Skorowidz B bezpieczeństwo, 91 biblioteka, 19, 89 curl, 86 glib, 86 math.h, 19, 20 niestandardowa, 86 sqlite3, 86 stdbool.h, 48 stdio.h, 15 stdlib.h, 60, 76 string.h, 35 tworzenie, 90 błąd, 84, 85 obsługa, Patrz: obsługa błędów
D danych wyciek, Patrz: pamięć wyciek dekrementacja, 41 dyrektywa #define, 83, 91 #endif, 91 #error, 83 #ifdef, 83 #ifndef, 83, 91 #include, 20, 83, 91 #pragma, 84 #undef, 83 #warning, 83
atoi, 60 ciało, 29 definicja, 27 fclose, 80 fgets, 80 fopen, 80 fprintf, 80 free, 77 getchar, 59 gets, 59 ls, 14 main, 29 malloc, 76, 77 nazwa, 31 printf, 15, 20, 57 prototyp, 28 putchar, 57 puts, 57 scanf, 58, 59 sqrt, 19 strcat, 35 strcmp, 35 strlen, 35 terminal, 14 tworzenie, 27 wartość, 77
G gcc, 14 gedit, 11, 13, 14
H
F fałsz, 45, 46, 78 funkcja, 19 argument, 29 atof, 60
hasło, 10
98
Zabawa w programowanie. Język C dla nastolatków
I inkrementacja, 41 instrukcja pętli, Patrz: pętla warunkowa, 37, 40, 45, 83 if else, 37, 39 switch, 39
K komentarz, 16, 93 kompilator, 13, 84, 85 komputer wirtualny, 8
L linker, 13, 86 Linux, 7 logika, 45
M makro, 84
O obsługa błędów, 60 operacja wejścia i wyjścia, 57 operator &&, 47 *, Patrz: operator wyłuskania ., Patrz: operator kropka ||, 48 –>, 72 alternatywy, 47, 48 bitowy, 42 dekrementacji, 41 dodawania, 41 dzielenia, 41 inkrementacji, 41 kolejność, 41, 43 koniunkcji, 47 kropka, 72 logiczny, 42, 47 mnożenia, 41 modulo, 41 odejmowania, 41 porównania, 42 przypisania, 41 sizeof, 75, 76 trójargumentowy, Patrz: operator warunkowy
warunkowy, 39 wyłuskania, 72
P pamięć adres, 71 alokacja dynamiczna, 77 fragmentacja, 77 wyciek, 77, 80 zwalnianie, 77 pętla do while, 51 for, 52 przerwanie, 54 while, 52 zagnieżdżanie, 53 plik, 79 .c, 89 .h, 89 .o, 90 nagłówkowy, 89, 90, 91, 93 ścieżka, 80 polecenie, Patrz: funkcja postinkrementacja, 41 prawda, 45, 46 preinkrementacja, 41 preprocesor, 13, 83, 84 program dobre praktyki, 16, 25, 40, 43, 48, 55, 60, 65, 78, 86, 93 gcc, Patrz: gcc gedit, Patrz: gedit Terminal, Patrz: Terminal
R rzutowanie, 76
S słowo kluczowe break, 54 const, 25 continue, 54 default, 39 else, 37 else if, 38 error, 85 extern, 91, 93 main, 28 malloc, 76
Skorowidz
99
note, 85 NULL, 77 return, 29 sizeof, 75 static, 91, 93 switch, 39 typedef, 64, 65, 68 void, 29, 30 warning, 85 while, 51 specyfikator, 20 %c, 20 %d, 20, 58 %f, 20 %p, 20 %s, 20 stała, 25 standard C99, 48 sterta, 77 stos, 77 struktura, 68, 72 system autotools, 86 cmake, 86 operacyjny wirtualny, 11 zarządzania procesami kompilacji, 86
U Ubuntu instalowanie, 10 pobieranie, 7 unia, 68, 72 użytkownik nazwa, 10
V Virtualbox instalowanie, 8 pobieranie, 7
W wskaźnik, 71, 73 niezaicjalizowany, 77 NULL, 77 podwójny, 72 potrójny, 72 tworzenie, 71 typ int, 76 void, 76 wiszący, 77
T tablica, 33 deklarowanie, 33 element, 34 rozmiar, 76 typ char, 34 int, 34 znakowa, 34, 35 długość, 35 łączenie, 35 porównywanie, 35 Terminal, 11, 14 typ bool, 49 char, 24, 29, 34 definiowanie, 64 enum, 63, 64 float, 24, 29 int, 24, 29, 34 void, 29, 30 wyliczeniowy, Patrz: typ enum
Z zmienna, 23, 24 deklarowanie, 92 lokalna, 77 statyczna, 77 typ, Patrz: typ zasięg, 30 znakowa, 47 znak !, 42 !=, 42 %, 41 %c, 20 %d, 20, 58 %f, 20 %p, 20 %s, 20 &, 71 &&, 38, 42, 47 *, 41 ***, 72 */, 17
100 znak ., 72 /, 41 /*, 17 //, 17 ||, 38, 42, 48 +, 41 ++, 41 , 72 >=, 42 kropka, 72 \n, 15 średnik, 15