Dla właściwego zrozumienia języka C nieodzowne jest przyswojenie sobie pewnych ogólnych informacji.

Kompilacja: Jak działa C?

edytuj

Jak każdy język programowania, C sam w sobie jest niezrozumiały dla procesora. Został on stworzony w celu umożliwienia ludziom łatwego pisania kodu, który może zostać przetworzony na kod maszynowy. Program, który zamienia kod C na wykonywalny kod binarny, to kompilator. Jeśli pracujesz nad projektem, który wymaga kilku plików kodu źródłowego (np. pliki nagłówkowe), wtedy jest uruchamiany kolejny program - linker. Linker służy do połączenia różnych plików i stworzenia jednej aplikacji lub biblioteki (library). Biblioteka jest zestawem procedur, który sam w sobie nie jest wykonywalny, ale może być używana przez inne programy. Kompilacja i łączenie plików są ze sobą bardzo ściśle powiązane, stąd są przez wielu traktowane jako jeden proces. Jedną rzecz warto sobie uświadomić - kompilacja jest jednokierunkowa: przekształcenie kodu źródłowego C w kod maszynowy jest bardzo proste, natomiast odwrotnie - nie. Dekompilatory co prawda istnieją, ale rzadko tworzą użyteczny kod C.

Najpopularniejszym wolnym kompilatorem jest prawdopodobnie GNU Compiler Collection, dostępny na stronie gcc.gnu.org.

Co może C?

edytuj

Pewnie zaskoczy Cię to, że tak naprawdę "czysty" język C nie może zbyt wiele. Język C w grupie języków programowania wysokiego poziomu jest stosunkowo nisko. Dzięki temu kod napisany w języku C można dość łatwo przetłumaczyć na kod asemblera. Bardzo łatwo jest też łączyć ze sobą kod napisany w języku asemblera z kodem napisanym w C. Dla bardzo wielu ludzi przeszkodą jest także dość duża liczba i częsta dwuznaczność operatorów. Początkujący programista, czytający kod programu w C może odnieść bardzo nieprzyjemne wrażenie, które można opisać cytatem "ja nigdy tego nie opanuję". Wszystkie te elementy języka C, które wydają Ci się dziwne i nielogiczne w miarę, jak będziesz nabierał doświadczenia nagle okażą się całkiem przemyślanie dobrane i takie, a nie inne konstrukcje przypadną Ci do gustu. Dalsza lektura tego podręcznika oraz zaznajamianie się z funkcjami z różnych bibliotek ukażą Ci całą gamę możliwości, które daje język C doświadczonemu programiście.

Struktura blokowa

edytuj

Teraz omówimy podstawową strukturę programu napisanego w C. Jeśli miałeś styczność z językiem Pascal, to pewnie słyszałeś o nim, że jest to język programowania strukturalny. W C nie ma tak ścisłej struktury blokowej, mimo to jest bardzo ważne zrozumienie, co oznacza struktura blokowa. Blok jest grupą instrukcji, połączonych w ten sposób, że są traktowane jak jedna całość. W C, blok zawiera się pomiędzy nawiasami klamrowymi { }. Blok może także zawierać kolejne bloki.

Zawartość bloku. Generalnie, blok zawiera ciąg kolejno wykonywanych poleceń. Polecenia zawsze (z nielicznymi wyjątkami) kończą się średnikiem (;). W jednej linii może znajdować się wiele poleceń, choć dla zwiększenia czytelności kodu najczęściej pisze się pojedynczą instrukcję w każdej linii. Jest kilka rodzajów poleceń, np. instrukcje przypisania, warunkowe czy pętli. W dużej części tego podręcznika będziemy zajmować się właśnie instrukcjami.

Pomiędzy poleceniami są również odstępy - spacje, tabulacje oraz przejścia do następnej linii, przy czym dla kompilatora te trzy rodzaje odstępów mają takie samo znaczenie. Dla przykładu, poniższe trzy fragmenty kodu źródłowego, dla kompilatora są takie same:

printf("Hello world"); return 0;
printf("Hello world");
return 0;
printf("Hello world");



return 0;

W tej regule istnieje jednak jeden wyjątek. Dotyczy on stałych tekstowych. W powyższych przykładach stałą tekstową jest "Hello world". Gdy jednak rozbijemy ten napis, kompilator zasygnalizuje błąd:

printf("Hello
world");
return 0;

Należy tylko zapamiętać, że stałe tekstowe powinny zaczynać się i kończyć w tej samej linii (można ominąć to ograniczenie - więcej w rozdziale Napisy). Oprócz tego jednego przypadku dla kompilatora ma znaczenie samo istnienie odstępu, a nie jego wielkość czy rodzaj. Jednak stosowanie odstępów jest bardzo ważne, dla zwiększenia czytelności kodu - dzięki czemu możemy zaoszczędzić sporo czasu i nerwów, ponieważ znalezienie błędu (które się zdarzają każdemu) w nieczytelnym kodzie może być bardzo trudne.

Zasięg

edytuj

Pojęcie to dotyczy zmiennych (które przechowują dane przetwarzane przez program). W każdym programie (oprócz tych najprostszych) są zarówno zmienne wykorzystywane przez cały czas działania programu oraz takie, które są używane przez pojedynczy blok programu (np. funkcję). Na przykład, w pewnym programie w pewnym momencie jest wykonywane skomplikowane obliczenie, które wymaga zadeklarowania wielu zmiennych do przechowywania pośrednich wyników. Ale przez większą część tego działania te zmienne są niepotrzebne i zajmują tylko miejsce w pamięci - najlepiej gdyby to miejsce zostało zarezerwowane tuż przed wykonaniem wspomnianych obliczeń, a zaraz po ich wykonaniu zwolnione. Dlatego w C istnieją zmienne globalne oraz lokalne. Zmienne globalne mogą być używane w każdym miejscu programu, natomiast lokalne - tylko w określonym bloku czy funkcji (oraz blokach w nim zawartych). Generalnie - zmienna zadeklarowana w danym bloku jest dostępna tylko wewnątrz niego.

Funkcje

edytuj

Funkcje są ściśle związane ze strukturą blokową - funkcją jest po prostu blok instrukcji, który jest potem wywoływany w programie za pomocą pojedynczego polecenia. Zazwyczaj funkcja wykonuje pewne określone zadanie, np. we wspomnianym programie wykonującym pewne skomplikowane obliczenie.

Każda funkcja ma swoją nazwę, za pomocą której jest potem wywoływana w programie, oraz blok wykonywanych poleceń. Wiele funkcji pobiera pewne dane, czyli argumenty funkcji, wiele funkcji także zwraca pewną wartość po zakończeniu wykonywania. Dobrym nawykiem jest dzielenie dużego programu na zestaw mniejszych funkcji - dzięki temu będziesz mógł łatwiej odnaleźć błąd w programie.

Jeśli chcesz użyć jakiejś funkcji, to powinieneś wiedzieć:

  • jakie zadanie wykonuje dana funkcja,
  • jaki jest rodzaj wczytywanych argumentów i do czego są one potrzebne tej funkcji,
  • jaki jest rodzaj zwróconych danych i co one oznaczają.

W programach w języku C jedna funkcja ma szczególne znaczenie - jest to main(). Funkcję tę, zwaną funkcją główną, musi zawierać każdy program. W niej zawiera się główny kod programu i przekazywane są do niej argumenty, z którymi wywoływany jest program (jako parametry argc i argv). Więcej o funkcji main() dowiesz się później w rozdziale Funkcje.

Biblioteki standardowe

edytuj

Język C, w przeciwieństwie do innych języków programowania (np. Fortranu czy Pascala), nie posiada absolutnie żadnych słów kluczowych, które odpowiedzialne by były za obsługę wejścia i wyjścia. Może się to wydawać dziwne - język, który sam w sobie nie posiada podstawowych funkcji, musi być językiem o ograniczonym zastosowaniu. Jednak brak podstawowych funkcji wejścia-wyjścia jest jedną z największych zalet tego języka. Jego składnia opracowana jest tak, by można było bardzo łatwo przełożyć ją na kod maszynowy. To właśnie dzięki temu programy napisane w języku C są takie szybkie. Pozostaje jednak pytanie - jak umożliwić programom komunikację z użytkownikiem?

W 1983 roku, kiedy zapoczątkowano prace nad standaryzacją C, zdecydowano, że powinien być zestaw instrukcji identycznych w każdej implementacji C. Nazwano je Biblioteką Standardową (czasem nazywaną "libc"). Zawiera ona podstawowe funkcje, które umożliwiają wykonywanie takich zadań jak wczytywanie i zwracanie danych, modyfikowanie zmiennych łańcuchowych, działania matematyczne, operacje na plikach i wiele innych, jednak nie zawiera żadnych funkcji, które mogą być zależne od systemu operacyjnego czy sprzętu, jak grafika, dźwięk czy obsługa sieci. W programie "Hello World" użyto funkcji z biblioteki standardowej - puts, która wyświetla na ekranie sformatowany tekst.

Komentarze i styl

edytuj

Komentarze - to tekst włączony do kodu źródłowego, który jest pomijany przez kompilator i służy jedynie dokumentacji. W języku C, komentarze zaczynają się od

/*

a kończą

*/

Dobre komentowanie ma duże znaczenie dla rozwijania oprogramowania, nie tylko dlatego, że inni będą kiedyś potrzebowali przeczytać napisany przez ciebie kod źródłowy, ale także możesz chcieć po dłuższym czasie powrócić do swojego programu, i możesz zapomnieć, do czego służy dany blok kodu, albo dlaczego akurat użyłeś tego polecenia, a nie innego. W chwili pisania programu, to może być dla ciebie oczywiste, ale po dłuższym czasie możesz mieć problemy ze zrozumieniem własnego kodu. Jednak nie należy też wstawiać zbyt dużo komentarzy, ponieważ wtedy kod może stać się jeszcze mniej czytelny - najlepiej komentować fragmenty, które nie są oczywiste dla programisty oraz te o szczególnym znaczeniu. Ale tego nauczysz się już w praktyce.

Dobry styl pisania kodu jest o tyle ważny, że powinien on być czytelny i zrozumiały; po to w końcu wymyślono języki programowania wysokiego poziomu (w tym C), aby kod było łatwo zrozumieć ;). I tak - należy stosować wcięcia dla odróżnienia bloków kolejnego poziomu (zawartych w innym bloku; podrzędnych), nawiasy klamrowe otwierające i zamykające blok powinny mieć takie same wcięcia, staraj się, aby nazwy funkcji i zmiennych kojarzyły się z zadaniem, jakie dana funkcja czy zmienna pełni w programie. W dalszej części podręcznika możesz napotkać więcej zaleceń dotyczących stylu pisania kodu. Staraj się stosować do tych zaleceń - dzięki temu kod pisanych przez ciebie programów będzie łatwiejszy do czytania i zrozumienia.

Innym zastosowaniem komentarzy jest chwilowe usuwanie fragmentów kodu. Jeśli część programu źle działa i chcemy ją chwilowo wyłączyć, albo fragment kodu jest nam już niepotrzebny, ale mamy wątpliwości, czy w przyszłości nie będziemy chcieli go użyć - umieszczamy go po prostu wewnątrz komentarza.

Podczas obejmowania chwilowo niepotrzebnego kodu w komentarz trzeba uważać na jedną subtelność. Otóż komentarze /* * /' w języku C nie mogą być zagnieżdżone. Trzeba na to uważać, gdy chcemy objąć komentarzem obszar w którym już istnieje komentarz (należy wtedy usunąć wewnętrzny komentarz). W nowszym standardzie C dopuszcza się, aby komentarz typu /* */ zawierał w sobie komentarz // i żeby komentarz typu "//" mógł być stosowany.

Po polsku czy angielsku?

edytuj

Jak już wcześniej było wspomniane, zmiennym i funkcjom powinno się nadawać nazwy, które odpowiadają ich znaczeniu. Zdecydowanie łatwiej jest czytać kod, gdy średnią liczb przechowuje zmienna srednia niż a, a znajdowaniem maksimum w ciągu liczb zajmuje się funkcja max albo znajdz_max niż nazwana f. Często nazwy funkcji to właśnie czasowniki.

Powstaje pytanie, w jakim języku należy pisać nazwy. Jeśli chcemy, by nasz kod mogły czytać osoby nieznające polskiego - warto użyć języka angielskiego. Jeśli nie - można bez problemu użyć polskiego. Bardzo istotne jest jednak, by nie mieszać języków. Jeśli zdecydowaliśmy się używać polskiego, używajmy go od początku do końca; przeplatanie ze sobą dwóch języków robi złe wrażenie.

Warto również zdecydować się na sposób zapisywania nazw składających się z więcej niż jednego słowa. Istnieje kilka możliwości, najważniejsze z nich:

  1. oddzielanie podkreśleniem: int_to_str
  2. "konwencja pascalowska", każde słowo dużą literą: IntToStr
  3. "konwencja wielbłądzia", pierwsze słowo małą, kolejne dużą literą: intToStr

Ponownie, najlepiej stosować konsekwentnie jedną z konwencji i nie mieszać ze sobą kilku.

Notacja węgierska

edytuj

Czasem programista może zapomnieć, jakiego typu była dana zmienna. Wtedy musi znaleźć odpowiednią deklarację (co nie zawsze jest łatwe). Dlatego więc wymyślono sposób, by temu zaradzić. Pomyślano, by w nazwie zmiennej (bądź wskaźnika na zmienną) napisać, jakiego jest ona typu, np:

  • a_liczba (liczba typu int)
  • w_ll_dlugaLiczba (wskaźnik na zmienną typu long long)
  • t5x5_ch_tabliczka (tablica 5x5 elementów typu char)
  • func_i_silnia (funkcja zwracająca int)

Jest to bardzo wygodne przy bardzo zagmatwanych zmiennych:

  • w_t4_w_t2x2_s_pomieszaniec (wskaźnik na tablicę czterech wskaźników na tablice dwuwymiarowe zmiennych typu short)

Lub gdy nie pamiętamy wymiarów tablicy:

  • t4x5x6_f_powalonaKostkaRubika (od razu wiemy, że t4x5x6_f_powalonaKostkaRubika[5][4][6] jest niewłaściwe)

Taki zapis ma też swoje wady. Gdy zdecydujemy się zmienić typ zmiennej, zamiast po prostu przemienić w deklaracji int na long, musimy zmieniać nazwy w całym programie. Często takie nazwy są po prostu długie i nie chce nam się ich pisać (no cóż, programista też człowiek), więc wolimy wprowadzić pomieszaniec zamiast w_t4_w_t2x2_s_pomieszaniec. Najważniejsze to jednak trzymać się rozwiązania, które wybraliśmy na początku, bo mieszanie jest przerażające.

Preprocesor

edytuj

Nie cały napisany przez ciebie kod będzie przekształcany przez kompilator bezpośrednio na kod wykonywalny programu. W wielu przypadkach będziesz używać poleceń "skierowanych do kompilatora", tzw. dyrektyw kompilacyjnych. Na początku procesu kompilacji, specjalny podprogram, tzw. preprocesor, wyszukuje wszystkie dyrektywy kompilacyjne i wykonuje odpowiednie akcje - które polegają notabene na edycji kodu źródłowego (np. wstawieniu deklaracji funkcji, zamianie jednego ciągu znaków na inny). Właściwy kompilator, zamieniający kod C na kod wykonywalny, nie napotka już dyrektyw kompilacyjnych, ponieważ zostały one przez preprocesor usunięte, po wykonaniu odpowiednich akcji.

W C dyrektywy kompilacyjne zaczynają się od znaku hash (#). Przykładem najczęściej używanej dyrektywy, jest #include, która jest użyta nawet w tak prostym programie jak "Hello, World!". #include nakazuje preprocesorowi włączyć (ang. include) w tym miejscu zawartość podanego pliku, tzw. pliku nagłówkowego; najczęściej to będzie plik zawierający funkcje z którejś biblioteki standardowej (stdio.h - STandard Input-Output, rozszerzenie .h oznacza plik nagłówkowy C). Dzięki temu, zamiast wklejać do kodu swojego programu deklaracje kilkunastu, a nawet kilkudziesięciu funkcji, wystarczy wpisać jedną magiczną linijkę!

Nazwy zmiennych, stałych i funkcji

edytuj

Identyfikatory, czyli nazwy zmiennych, stałych i funkcji mogą składać się z liter (bez polskich znaków), cyfr i znaku podkreślenia z tym, że nazwa taka nie może zaczynać się od cyfry. Nie można używać nazw zarezerwowanych (patrz: Składnia).

Przykłady błędnych nazw:

 2liczba      (nie można zaczynać nazwy od cyfry)
 moja funkcja (nie można używać spacji)
 $i           (nie można używać znaku $)
 if           (if to słowo kluczowe)

Aby kod był bardziej czytelny, przestrzegajmy poniższych (umownych) reguł:

  • nazwy zmiennych piszemy małymi literami: i, file
  • nazwy stałych (zadeklarowanych przy pomocy #define) piszemy wielkimi literami: SIZE
  • nazwy funkcji piszemy małymi literami: print
  • wyrazy w nazwach oddzielamy jedną z konwencji:
    • oddzielanie podkreśleniem: open_file
    • konwencja pascalowska: OpenFile
    • konwencja wielbłądzia: openFile