C/Wersja do druku
Z Wikibooks, biblioteki wolnych podręczników.
Aktualna, edytowalna wersja tego podręcznika jest dostępna w Wikibooks, bibliotece wolnych podręczników pod adresem
http://pl.wikibooks.org/wiki/C
Całość tekstu jest objęta licencją GNU Free Documentation License.
[edytuj] Spis treści
- Wstęp
- O podręczniku
- O języku C
- Czego potrzebujesz
- Używanie kompilatora
- C dla początkujących
- Pierwszy program
- Podstawowe wiadomości
- Zmienne w C
- Operatory
- Instrukcje sterujące
- Podstawowe procedury wejścia i wyjścia
- Funkcje
- Preprocesor
- Biblioteka standardowa
- Czytanie i pisanie do plików
- Ćwiczenia
- Zaawansowany C
- Tablice
- Wskaźniki
- Napisy
- Typy złożone
- Tworzenie bibliotek
- Więcej o kompilowaniu
- Zaawansowane operacje matematyczne
- Powszechne praktyki
- Przenośność programów
- Łączenie z innymi językami
- Ćwiczenia
- Dodatek A
- Składnia
- Przykłady z komentarzem
- Przypisy
- Licencja
[edytuj] O podręczniku
[edytuj] O czym mówi ten podręcznik?
Niniejszy podręcznik stanowi przewodnik dla początkujących programistów po języku programowania C.
[edytuj] Co trzeba wiedzieć, żeby skorzystać z niniejszego podręcznika?
Ten podręcznik ma nauczyć programowania w C od podstaw do poziomu zaawansowanego. Do zrozumienia rozdziału dla początkujących wymagana jest jedynie znajomość podstawowych pojęć z zakresu algebry oraz terminów komputerowych. Doświadczenie w programowaniu w innych językach bardzo pomaga, ale nie jest konieczne.
[edytuj] Czy mogę pomóc?
Oczywiście że możesz. Mało tego, będziemy zadowoleni z każdej pomocy – możesz pisać rozdziały lub tłumaczyć je z angielskiej wersji tego podręcznika. Nie musisz pytać się nikogo o zgodę - jeśli chcesz, możesz zacząć już teraz. Prosimy jedynie o zapoznanie się ze stylem podręcznika, użytymi w nim szablonami i zachowanie układu rozdziałów. Propozycje zmiany spisu treści należy zgłaszać na stronie dyskusji.
Jeśli znalazłeś jakiś błąd a nie umiesz go poprawić, koniecznie powiadom o tym fakcie autorów tego podręcznika za pomocą strony dyskusji danego modułu książki. Dzięki temu przyczyniasz się do rozwoju tego podręcznika.
[edytuj] Konwencje przyjęte w tym podręczniku
Informacje ważne oznaczamy w następujący sposób:
| Uwaga! Ważna informacja! |
Dodatkowe informacje, które odrobinę wykraczają poza zakres podręcznika, a także wyjaśniają kwestie niezwiązane bezpośrednio z językiem C oznaczamy tak:
| Wyjaśnienie |
Ponadto kod w języku C będzie prezentowany w następujący sposób:
#include <stdio.h> int main (int argc, char *argv[]) { return 0; }
Innego rodzaju przykłady, dialog użytkownika z konsolą i programem, wejście / wyjście programu, informacje teoretyczne będą wyglądały tak:
typ zmienna = wartość;
[edytuj] Autorzy
Istotny wkład w powstanie podręcznika mają:
Dodatkowo w rozwoju podręcznika pomagali między innymi:
[edytuj] Źródła
- podręcznik C Programming na anglojęzycznej wersji Wikibooks, licencja GFDL
- Brian W. Kernighan, Dennis M. Ritchie, Język ANSI C
- ISO C Committee Draft, 18 styczna 1999
- Bruce Eckel, Thinking in C++. Rozdział Język C w programie C++.
[edytuj] O języku C
C jest językiem programowania wysokiego poziomu. Jego nazwę interpretuje się jako następną literę po B (nazwa jego poprzednika), lub drugą literę języka BCPL (poprzednik języka B).
[edytuj] Historia C
W 1947 roku trzej naukowcy z Bell Telephone Laboratories - William Shockley, Walter Brattain i John Bardeen - stworzyli pierwszy tranzystor; w 1956 roku, w MIT skonstruowano pierwszy komputer oparty wyłącznie na tranzystorach: TX-O; w 1958 roku Jack Kilby z Texas Instruments skonstruował układ scalony. Ale zanim powstał pierwszy układ scalony, pierwszy język wysokiego poziomu został już napisany.
W 1954 powstał Fortran (Formula Translator), który zapoczątkował napisanie języka Fortran I (1956). Później powstały kolejno:
- Algol 58 - Algorithmic Language w 1958 r.
- Algol 60 (1960)
- CPL - Combined Programming Language (1963)
- BCPL - Basic CPL (1967)
- B (1969)
i C w oparciu o B.
B został stworzony przez Kena Thompsona z Bell Labs; był to język interpretowany, używany we wczesnych, wewnętrznych wersjach systemu operacyjnego UNIX. Inni pracownicy Bell Labs, Thompson i Dennis Richie, rozwinęli B, nazywając go NB; dalszy rozwój NB dał C - język kompilowany. Większa część UNIXa została ponownie napisana w NB, a następnie w C, co dało w efekcie bardziej przenośny system operacyjny. W 1978 roku wydana została książka pt. "The C Programming Language", która stała się pierwszym podręcznikiem do nauki języka C.
Możliwość uruchamiania UNIX-a na różnych komputerach była główną przyczyną początkowej popularności zarówno UNIX-a, jak i C; zamiast tworzyć nowy system operacyjny, programiści mogli po prostu napisać tylko te części systemu, których wymagał inny sprzęt, oraz napisać kompilator C dla nowego systemu. Odkąd większa część narzędzi systemowych była napisana w C, logiczne było pisanie kolejnych w tym samym języku.
Kilka z obecnie powszechnie stosowanych systemów operacyjnych takich jak Linux, Microsoft Windows zostały napisane w języku C.
[edytuj] Standaryzacje
W 1978 roku Ritchie i Kerninghan opublikowali pierwszą książkę nt. języka C - "The C Programming Language". Owa książka przez wiele lat była swoistym "wyznacznikiem", jak programować w języku C. Była więc to niejako pierwsza standaryzacja, nazywana od nazwisk twórców "K&R". Oto nowości, wprowadzone przez nią do języka C w stosunku do jego pierwszych wersji (pochodzących z początku lat 70.):
- możliwość tworzenia struktur (słowo struct)
- dłuższe typy danych (modyfikator long)
- liczby całkowite bez znaku (modyfikator unsigned)
- zmieniono operator "=+" na "+="
Ponadto producenci kompilatorów (zwłaszcza AT&T) wprowadzali swoje zmiany, nieobjęte standardem:
- funkcje nie zwracające wartości (void) oraz typ void*
- funkcje zwracające struktury i unie
- przypisywanie wartości strukturom
- wprowadzenie słowa kluczowego const
- utworzenie biblioteki standardowej
- wprowadzenie słowa kluczowego enum
Owe nieoficjalne rozszerzenia zagroziły spójności języka, dlatego też powstał standard, regulujący wprowadzone nowinki. Od 1983 roku trwały prace standaryzacyjne, aby w 1989 roku wydać standard C89 (poprawna nazwa to: ANSI X3.159-1989). Niektóre zmiany wprowadzono z języka C++, jednak rewolucję miał dopiero przynieść standard C99, który wprowadził m.in.:
- funkcje inline
- nowe typy danych (np. long long int)
- nowy sposób komentowania, zapożyczony od C++ (//)
- przechowywanie liczb zmiennoprzecinkowych zostało zaadaptowane do norm IEEE
- utworzono kilka nowych plików nagłówkowych (stdbool.h, inttypes.h)
Na dzień dzisiejszy normą obowiązującą jest norma C99.
[edytuj] Zastosowania języka C
Język C został opracowany jako strukturalny język programowania do celów ogólnych. Przez całą swą historię (czyli ponad 30 lat) służył do tworzenia przeróżnych programów - od systemów operacyjnych po programy nadzorujące pracę urządzeń przemysłowych. C, jako język dużo szybszy od języków interpretowanych (Perl, Python) oraz uruchamianych w maszynach wirtualnych (np. C#, Java) może bez problemu wykonywać złożone operacje nawet wtedy, gdy nałożone są dość duże limity czasu wykonywania pewnych operacji. Jest on przy tym bardzo przenośny - może działać praktycznie na każdej architekturze sprzętowej pod warunkiem opracowania odpowiedniego kompilatora. Często wykorzystywany jest także do oprogramowywania mikrokontrolerów i systemów wbudowanych. Jednak w niektórych sytuacjach język C okazuje się być mało przydatny, zwłaszcza chodzi tu o obliczenia matematyczne, wymagające dużej precyzji (w tej dziedzinie znakomicie spisuje się Fortran) lub też dużej optymalizacji dla danego sprzętu (wtedy niezastąpiony jest język asemblera).
Kolejną zaletą C jest jego dostępność - właściwie każdy system typu UNIX posiada kompilator C, w C pisane są funkcje systemowe.
Problemem w przypadku C jest zarządzanie pamięcią, które nie wybacza programiście błędów, niewygodne operowanie napisami i niestety pewna liczba "kruczków", które mogą zaskakiwać nowicjuszy. Na tle młodszych języków programowania, C jest językiem dosyć niskiego poziomu więc wiele rzeczy trzeba w nim robić ręcznie, jednak zarazem umożliwia to robienie rzeczy nieprzewidzianych w samym języku (np. implementację liczb 128 bitowych), a także łatwe łączenie C z Asemblerem.
[edytuj] Przyszłość C
Pomimo sędziwego już wieku (C ma ponad 30 lat) nadal jest on jednym z najczęściej stosowanych języków programowania. Doczekał się już swoich następców, z którymi w niektórych dziedzinach nadal udaje mu się wygrywać. Widać zatem, że pomimo pozornej prostoty i niewielkich możliwości język C nadal spełnia stawiane przed nim wymagania. Warto zatem uczyć się języka C, gdyż nadal jest on wykorzystywany (i nic nie wskazuje na to, by miało się to zmienić), a wiedza którą zdobędziesz ucząc się C na pewno się nie zmarnuje. Składnia języka C, pomimo że przez wielu uważana za nieczytelną, stała się podstawą dla takich języków jak C++, C# czy też Java.
[edytuj] Czego potrzebujesz
[edytuj] Czego potrzebujesz
Wbrew powszechnej opinii nauczenie się któregoś z języków programowania (w tym języka C) nie jest takie trudne. Do nauki wystarczą Ci:
- komputer z dowolnym systemem operacyjnym, takim jak FreeBSD, Linux, Windows;
- Język C jest bardzo przenośny, więc będzie działał właściwie na każdej platformie sprzętowej i w każdym nowoczesnym systemie operacyjnym.
- kompilator języka C
- Kompilator języka C jest programem, który tłumaczy kod źródłowy napisany przez nas do języka asembler, a następnie do postaci zrozumiałej dla komputera (maszyny cyfrowej) czyli do postaci ciągu zer i jedynek które sterują pracą poszczególnych elementów komputera. Kompilator języka C można dostać za darmo. Przykładem są: gcc pod systemy uniksowe, DJGPP pod systemy DOS, MinGW oraz lcc pod systemy typu Windows. Jako kompilator C może dobrze służyć kompilator języka C++ (różnice między tymi językami przy pisaniu prostych programów są nieistotne). Spokojnie możesz więc użyć na przykład Microsoft Visual C++® lub kompilatorów firmy Borland. Jeśli lubisz eksperymentować, wypróbuj Tiny C Compiler, bardzo szybki kompilator o ciekawych funkcjach. Możesz ponadto wypróbować interpreter języka C. Więcej informacji na Wikipedii.
- Linker
- Linker jest to program który uruchamiany jest po etapie kompilacji jednego lub kilku plików źródłowych (pliki z rozszerzeniem *.c, *.cpp lub innym) skompilowanych dowolnym kompilatorem. Taki program łączy wszystkie nasze skompilowane pliki źródłowe i inne funkcje (np. printf, scanf) które były użyte (dołączone do naszego programu poprzez użycie dyrektywy #include) w naszym programie, a nie były zdefiniowane(napisane przez nas) w naszych plikach źródłowych lub nagłówkowych. Linker jest to czasami jeden program połączony z kompilatorem. Wywoływany jest on na ogół automatycznie przez kompilator, w wyniku czego dostajemy gotowy program do uruchomienia.
- Debuger
- Debugger jest to program, który umożliwia prześledzenie(określenie wartości poszczególnych zmiennych na kolejnych etapach wykonywania programu) linijka po linijce wykonywania skompilowanego i zlinkowanego (skonsolidowanego) programu. Używa się go w celu określenia czemu nasz program nie działa po naszej myśli lub czemu program niespodziewanie kończy działanie bez powodu. Aby użyć debuggera kompilator musi dołączyć kod źródłowy do gotowego skompilowanego programu. Przykładowymi debuggerami są: gdb pod Linuksem, lub debugger firmy Borland pod Windowsa.
- edytora tekstowego;
- Systemy uniksowe oferują wiele edytorów przydatnych dla programisty, jak choćby vim i Emacs w trybie tekstowym, Kate w KDE czy gedit w GNOME. Windows ma edytor całkowicie wystarczający do pisania programów w C - nieśmiertelny Notatnik - ale z łatwością znajdziesz w Internecie wiele wygodniejszych narzędzi takich jak np. Notepad++. Odpowiednikiem Notepad++ w systemie uniksowym jest SciTE.
- dużo chęci i dobrej motywacji.
[edytuj] Zintegrowane Środowiska Programistyczne
Zamiast osobnego kompilatora i edytora, możesz wybrać Zintegrowane Środowisko Programistyczne (Integrated Development Environment, IDE). IDE jest zestawem wszystkich programów, które potrzebuje programista, najczęściej z interfejsem graficznym. IDE zawiera kompilator, linker i edytor, z reguły również debugger.
Bardzo popularny IDE to płatny Microsoft Visual C++ (MS VC++); popularne darmowe IDE to np.:
- Dev-C++ dla Windows, dostępny na stronie www.bloodshed.net,
- Code::Blocks dla Windows jak i Linux, dostępny na stronie www.codeblocks.org,
- KDevelop dla KDE
- Pelles C, www.smorgasbordet.com.
- Eclipse z wtyczką CDT (współpracuje z MinGW i GCC)
Często używanym środowiskiem jest też Borland C++ Builder (dostępny za darmo do użytku prywatnego).
[edytuj] Dodatkowe narzędzia
Wśród narzędzi, które nie są niezbędne, ale zasługują na uwagę, można wymienić Valgrinda – specjalnego rodzaju debugger. Valgrind kontroluje wykonanie programu i wykrywa nieprawidłowe operacje w pamięci oraz wycieki pamięci. Użycie Valgrinda jest proste - kompiluje się program tak, jak do debugowania i podaje jako argument Valgrindowi.
[edytuj] Używanie kompilatora
Język C jest językiem kompilowanym, co oznacza, że potrzebuje specjalnego programu - kompilatora - który tłumaczy kod źródłowy, pisany przez człowieka, na język rozkazów danego komputera. W skrócie działanie kompilatora sprowadza się do czytania tekstowego pliku z kodem programu, raportowania ewentualnych błędów i produkowania pliku wynikowego.
Kompilator uruchamiamy ze Zintegrowanego Środowiska Programistycznego lub z konsoli (linii poleceń). Przejść do konsoli można dla systemów typu UNIX w trybie graficznym użyć programów gterminal, konsole albo xterm, w Windows "Wiersz polecenia" (można go znaleźć w menu Akcesoria albo uruchomić wpisując w Start -> Uruchom... "cmd").
[edytuj] GCC
GCC jest to darmowy zestaw kompilatorów, m.in. języka C rozwijany w ramach projektu GNU. Dostępny jest on na dużą ilość platform sprzętowych, obsługiwanych przez takie systemy operacyjne jak: AIX, *BSD, Linux, Mac OS X, SunOS, Windows.
Aby skompilować kod języka C za pomocą kompilatora GCC, napisany wcześniej w dowolnym edytorze tekstu, należy uruchomić program z odpowiednimi parametrami. Podstawowym parametrem, który jest wymagany, jest nazwa pliku zawierającego kod programu który chcemy skompilować.
gcc kod.c
Rezultatem kompilacji będzie plik wykonywalny, z domyślną nazwą (w systemach Unix jest to "a.out"). Jest to metoda niezalecana ponieważ jeżeli skompilujemy w tym samym katalogu kilka plików z kodem, kolejne pliki wykonywalne zostaną nadpisane i w rezultacie otrzymamy tylko jeden (ten ostatni) skompilowany kod. Aby wymusić na GCC nazwę pliku wykonywalnego musimy skorzystać z parametru "-o <nazwa>":
gcc -o program kod.c
W rezultacie otrzymujemy plik wykonywalny o nazwie program.
Jeżeli chodzi o formę nazwy podawanego pliku z kodem (w naszym przypadku kod.c), to kompilator sam rozpoznaje język programowania wynikający z rozszerzenia pliku. Jednak jest możliwość skompilowania pliku o dowolnym rozszerzeniu, narzucając kompilatorowi dany język. Aby wymusić na GCC korzystanie z języka C, używamy parametru "-x <język>":
gcc -x c -o program kod
W rezultacie kompilator potraktuje plik "kod", jako plik z kodem źródłowym w języku C.
Pracując nad złożonym programem składającym się z kilku plików źródłowych (.c), możemy skompilować je niezależnie od siebie, tworząc tak zwane pliki typu obiekt (ang. Object File), są to pliki skompilowane ale nie poddane linkowaniu. Następnie stworzyć z nich jednolity program. Jest to bardzo wygodne i praktyczne rozwiązanie ze względu na to, iż nie jesteśmy zmuszeni kompilować wszystkich plików tworzących program za każdym razem na nowo a jedyne te w których wprowadziliśmy zmiany. Aby skompilować plik bez linkowania używamy parametru "-c <plik>":
gcc -o program1.o -c kod1.c gcc -o program2.o -c kod2.c
Otrzymujemy w ten sposób pliki typu obiekt program1.o i program2.o. A następnie tworzymy z nich program główny:
gcc -o program program1.o program2.o
Aby włączyć dokładne, rygorystyczne sprawdzanie napisanego kodu, co może być przydatne, jeśli chcemy dążyć do perfekcji, używamy przełączników:
gcc kod.c -o program -Werror -Wall -W -pedantic -ansi
Więcej informacji na temat parametrów i działania kompilatora GCC można znaleźć na:
- Strona domowa projektu GNU GCC
- Ogólny opis opcji kompilatora gcc po polsku
- Strona podręcznika systemu UNIX (man)
[edytuj] Borland
Zobacz podręcznik Borland C++ Compiler.
[edytuj] Czytanie komunikatów o błędach
Jedną z najbardziej podstawowych umiejętności, które musi posiąść początkujący programista jest umiejętność rozumienia komunikatów o różnego rodzaju błędach, które sygnalizuje kompilator. Wszystkie te informacje pomogą Ci szybko wychwycić ewentualne błędy (których na początku zawsze jest bardzo dużo). Nie martw się, że na początku dość często będziesz oglądał wydruki błędów, zasygnalizowanych przez kompilator - nawet zaawansowanym programistom się to zdarza. Kompilator ma za zadanie pomóc Ci w szybkiej poprawie ewentualnych błędów, dlatego też umiejętność analizy komunikatów o błędach jest tak ważna.
[edytuj] GCC
Kompilator jest w stanie wychwycić błędy składniowe, które z pewnością będziesz popełniał. Kompilator GCC wyświetla je w następującej formie:
nazwa_pliku.c:numer_linijki:opis błędu
Kompilator dość często podaje także nazwę funkcji, w której wystąpił błąd. Przykładowo, błąd deklaracji zmiennej w pliku test.c:
#include <stdio.h> int main () { intr r; printf ("%d\n", r); }
Spowoduje wygenerowanie następującego komunikatu o błędzie:
test.c: In function ‘main’: test.c:5: error: ‘intr’ undeclared (first use in this function) test.c:5: error: (Each undeclared identifier is reported only once test.c:5: error: for each function it appears in.) test.c:5: error: syntax error before ‘r’ test.c:6: error: ‘r’ undeclared (first use in this function)
Co widzimy w raporcie o błędach? W linii 5 użyliśmy nieznanego (undeclared) identyfikatora intr - kompilator mówi, że nie zna tego identyfikatora, jest to pierwsze użycie w danej funkcji i że więcej nie ostrzeże o użyciu tego identyfykatora w tej funkcji. Ponieważ intr nie został rozpoznany jako żaden znany typ, linijka intr r; nie została rozpoznana jako deklaracja zmiennej i kompilator zgłasza błąd składniowy (syntax error). W konsekwencji r nie zostało rozpoznane jako zmienna i kompilator zgłosi to jeszcze w następnej linijce, gdzie używamy r.
[edytuj] Pierwszy program
[edytuj] Twój pierwszy program
Przyjęło się, że pierwszy program napisany w dowolnym języku programowania, powinien wyświetlić tekst np. "Hello World!" (Witaj Świecie!). Zauważ, że sam język C nie ma żadnych mechanizmów przeznaczonych do wprowadzania i wypisywania danych - musimy zatem skorzystać ze specjalnie napisanych w tym celu funkcji - w tym przypadku printf, zawartej w standardowej bibliotece C (ang. C Standard Library) (podobnie jak w Pascalu używa się do tego procedur. Pascalowskim odpowiednikiem funkcji printf są procedury write/writeln).
W języku C deklaracje funkcji zawarte są w plikach nagłówkowych posiadających najczęściej rozszerzenie .h, choć można także spotkać rozszerzenie .hpp, przy czym to drugie zwykło się stosować w języku C++ (rozszerzenie nie ma swych "technicznych" korzeni - jest to tylko pewna konwencja). Żeby włączyć plik nagłówkowy do swojego kodu, trzeba użyć dyrektywy kompilacyjnej #include. Ta dyrektywa powoduje, że przed procesem kompilacji danego pliku źródłowego, deklaracje funkcji z pliku nagłówkowego zostają dołączone do twojego kodu celem zweryfikowania poprawności wywoływanych funkcji.
Poniżej przykład, jak użyć dyrektywy #include żeby wkleić definicję funkcji printf z pliku nagłówkowego stdio.h (Standard Input/Output.Headerfile):
#include <stdio.h>
W nawiasach trójkątnych < > umieszcza się nazwy standardowych plików nagłówkowych. Żeby włączyć inny plik nagłówkowy (np. własny), znajdujący się w katalogu z kodem programu, trzeba go wpisać w cudzysłów:
#include "mój_plik_nagłówkowy.h"
Mamy więc funkcję printf, jak i wiele innych do wprowadzania i wypisywania danych, czas na pisanie programu. [1]
Programy w C zaczyna się funkcją main, w której umieszcza się właściwy kod programu. Żeby rozpocząć tę funkcję, należy wpisać:
int main (void) {
int oznacza, że funkcja zwróci (tzn. przyjmie wartość po zakończeniu) liczbę całkowitą - w przypadku main będzie to kod wyjściowy programu; main to nazwa funkcji, w nawiasach umieszczamy parametry programu. Na tym etapie parametry programu nie będą nam potrzebne (void oznacza brak parametrów). Używa się ich do odczytywania argumentów linii polecenia, z jakimi program został uruchomiony.
Kod funkcji umieszcza się w nawiasach klamrowych { i }.
Wewnątrz funkcji należy wpisać poniższy kod:
printf("Hello World!"); return 0;
Wszystkie polecenia kończymy średnikiem. return 0; określa wartość jaką zwróci funkcja (program); Liczba zero zwracana przez funkcję main() oznacza, że program zakończył się bez błędów; błędne zakończenie często (choć nie zawsze) określane jest przez liczbę jeden[2]. Funkcję main kończymy nawiasem klamrowym zamykającym.
Twój kod powinien wyglądać jak poniżej:
#include <stdio.h> int main (void) { printf ("Hello World!"); return 0; }
Teraz wystarczy go tylko skompilować i uruchomić.
[edytuj] Rozwiązywanie problemów
Jeśli nie możesz skompilować powyższego programu, to najprawdopodobniej popełniłeś literówkę przy ręcznym przepisywaniu go. Zobacz też instrukcje na temat używania kompilatora.
Może też się zdarzyć, że program skompiluje się, uruchomi, ale jego efektu działania nie będzie widać. Dzieje się tak, ponieważ nasz pierwszy program po prostu wypisuje komunikat i od razu kończy działanie, nie czekając na reakcję użytkownika. Nie jest to problemem, gdy program jest uruchamiany z konsoli tekstowej, ale w innych przypadkach nie widzimy efektów jego działania.
Jeśli korzystasz ze Zintegrowanego Środowiska Programistycznego (ang. IDE), możesz zaznaczyć, by nie zamykało ono programu po zakończeniu jego działania. Innym sposobem jest dodanie instrukcji, które wstrzymywałyby zakończenie programu. Można to zrobić dodając przed linią z return kod (wraz z okalającymi klamrami):
{ int chr; puts("Wcisnij ENTER..."); while ((chr = getchar())!=EOF && chr!='\n'); }
Jest też prostszy (choć nieprzenośny) sposób, mianowicie wywołanie polecenia systemowego. W zależności od używanego systemu operacyjnego mamy do dyspozycji różne polecenia powodujące różne efekty. Do tego celu skorzystamy z funkcji system(), która jako parametr przyjmuje polecenie systemowe które chcemy wykonać, np:
Rodzina systemów Unix/Linux:
system("sleep 10"); /* oczekiwanie 10 s */ system("read discard"); /* oczekiwanie na wpisanie tekstu */
Rodzina systemów DOS oraz MS Windows:
system("pause"); /* oczekiwanie na wciśnięcie dowolnego klawisza */
Funkcja ta jest o wiele bardziej pomocna w systemach operacyjnych Windows w których to z reguł pracuje się w trybie okienkowym a z konsoli korzystamy tylko podczas uruchamianiu programu. Z kolei w systemach Unix/Linux jest ona praktycznie w ogóle nie używana w tym celu, ze względu na uruchamianie programu bezpośrednio z konsoli.
[edytuj] Podstawowe wiadomości
Dla właściwego zrozumienia języka C nieodzowne jest przyswojenie sobie pewnych ogólnych informacji.
[edytuj] Kompilacja: Jak działa C?
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.
[edytuj] Co może C?
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.
[edytuj] Struktura blokowa
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 lini (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.
[edytuj] Zasięg
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.
[edytuj] Funkcje
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
- rodzaj wczytywanych argumentów, i do czego są one potrzebne tej funkcji
- 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, 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.
[edytuj] Biblioteki standardowe
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 - printf, która wyświetla na ekranie sformatowany tekst.
[edytuj] Komentarze i styl
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.
| Porada Jeśli masz doświadczenia z językiem C++ pamiętaj, że w C nie powinno się stosować komentarzy zaczynających się od dwóch znaków slash: // tak nie komentujemy w C |
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 //.
[edytuj] Po polsku czy angielsku?
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.
[edytuj] Preprocesor
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ę!
[edytuj] Nazwy zmiennych, stałych i funkcji
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 znakiem podkreślenia: open_file, close_all_files
Są to tylko konwencje - żaden kompilator nie zgłosi błędu, jeśli wprowadzimy swój własny system nazewnictwa. Jednak warto pamiętać, że być może nad naszym kodem będą pracowali także inni programiści, którzy mogą mieć trudności z analizą kodu niespełniającego pewnych zasad.
[edytuj] Zmienne w C
Procesor komputera stworzony jest tak, aby przetwarzał dane, znajdujące się w pamięci komputera. Z punktu widzenia programu napisanego w języku C (który jak wiadomo jest językiem wysokiego poziomu) dane umieszczane są w postaci tzw. zmiennych. Zmienne ułatwiają programiście pisanie programu. Dzięki nim programista nie musi się przejmować gdzie w pamięci owe zmienne się znajdują, tzn. nie operuje fizycznymi adresami pamięci, jak np. 0x14613467, tylko prostą do zapamiętania nazwą zmiennej.
[edytuj] Czym są zmienne?
Zmienna jest to pewien fragment pamięci o ustalonym rozmiarze, który posiada własny identyfikator (nazwę) oraz może przechowywać pewną wartość, zależną od typu zmiennej.
[edytuj] Deklaracja zmiennych
Aby móc skorzystać ze zmiennej należy ją przed użyciem zadeklarować, to znaczy poinformować kompilator, jak zmienna będzie się nazywać i jaki typ ma mieć. Zmienne deklaruje się w sposób następujący:
typ nazwa_zmiennej;
Oto deklaracja zmiennej o nazwie "wiek" typu "int" czyli liczby całkowitej:
int wiek;
Zmiennej w momencie zadeklarowania można od razu przypisać wartość:
int wiek = 17;
| W języku C zmienne deklaruje się na samym początku bloku (czyli przed pierwszą instrukcją). |
int wiek = 17; printf("%d\n", wiek); int kopia_wieku; /* tu stary kompilator C zgłosi błąd - deklaracja występuje po instrukcji (printf). */ kopia_wieku = wiek;
Według nowszych standardów możliwe jest deklarowanie zmiennej w dowolnym miejscu programu, ale wtedy musimy pamiętać, aby zadeklarować zmienną przed jej użyciem. To znaczy, że taki kod jest niepoprawny:
printf ("Mam %d lat\n", wiek); int wiek = 17;
Należy go zapisać tak:
int wiek = 17; printf ("Mam %d lat\n", wiek);
[edytuj] Zasięg zmiennej
Zmienne mogą być dostępne dla wszystkich funkcji programu - nazywamy je wtedy zmiennymi globalnymi. Deklaruje się je przed wszystkimi funkcjami programu:
#include <stdio.h> int a,b; /* nasze zmienne globalne */ void func1 () { /* instrukcje */ a=3; /* dalsze instrukcje */ } int main () { b=3; a=2; return 0; }
Zmienne globalne, jeśli programista nie przypisze im innej wartości podczas definiowania, są inicjalizowane wartością 0.
Zmienne, które funkcja deklaruje do "własnych potrzeb" nazywamy zmiennymi lokalnymi. Nasuwa się pytanie: "czy będzie błędem nazwanie tą samą nazwą zmiennej globalnej i lokalnej?". Otóż odpowiedź może być zaskakująca: nie. Natomiast w danej funkcji da się używać tylko jej zmiennej lokalnej. Tej konstrukcji należy, z wiadomych względów, unikać.
int a=1; /* zmienna globalna */ int main() { int a=2; /* to już zmienna lokalna */ printf("%d", a); /* wypisze 2 */ }
[edytuj] Czas życia
Czas życia to czas od momentu przydzielenia dla zmiennej miejsca w pamięci (stworzenie obiektu) do momentu zwolnienia miejsca w pamięci (likwidacja obiektu).
Zakres ważności to część programu, w której nazwa znana jest kompilatorowi.
main() { int a = 10; { /* otwarcie lokalnego bloku */ int b = 10; printf("%d %d", a, b); } /* zamknięcie lokalnego bloku, zmienna b jest usuwana */ printf("%d %d", a, b); /* BŁĄD: b juz nie istnieje */ } /* tu usuwana jest zmienna a */
Zdefiniowaliśmy dwie zmienne typu int. Zarówno a i b istnieją przez cały program (czas życia). Nazwa zmiennej a jest znana kompilatorowi przez cały program. Nazwa zmiennej b jest znana tylko w lokalnym bloku, dlatego nastąpi błąd w ostatniej instrukcji.
Możemy świadomie ograniczyć ważność zmiennej do kilku linijek programu (tak jak robiliśmy wyżej) tworząc blok. Nazwa zmiennej jest znana tylko w tym bloku.
{
...
}
[edytuj] Stałe
Stała, różni się od zmiennej tylko tym, że nie można jej przypisać innej wartości w trakcie działania programu. Wartość stałej ustala się w kodzie programu i nigdy ona nie ulega zmianie. Stałą deklaruje się z użyciem słowa kluczowego const w sposób następujący:
const typ nazwa_stałej=wartość;
Dobrze jest używać stałych w programie, ponieważ unikniemy wtedy przypadkowych pomyłek a kompilator może często zoptymalizować ich użycie (np. od razu podstawiając ich wartość do kodu).
const int WARTOSC_POCZATKOWA=5; int i=WARTOSC_POCZATKOWA; WARTOSC_POCZATKOWA=4; /* tu kompilator zaprotestuje */ int j=WARTOSC_POCZATKOWA;
Przykład pokazuje dobry zwyczaj programistyczny, jakim jest zastępowanie umieszczonych na stałe w kodzie liczb stałymi. W ten sposób będziemy mieli większą kontrolę nad kodem - stałe umieszczone w jednym miejscu można łatwo modyfikować, zamiast szukać po całym kodzie liczb, które chcemy zmienić.
Nie mamy jednak pełnej gwarancji, że stała będzie miała tę samą wartość przez cały czas wykonania programu. Możliwe jest dostanie się do wartości stałej pośrednio - za pomocą wskaźników. Można zatem dojść do wniosku, że słowo kluczowe const służy tylko do poinformowania kompilatora, aby ten nie zezwalał na jawną zmianę wartości stałej. Z drugiej strony, zgodnie ze standardem, próba modyfikacji wartości stałej ma niezdefiniowane działanie (tzw. undefined behaviour) i w związku z tym może się powieść lub nie, ale może też spowodować jakieś subtelne zmiany, które w efekcie spowodują, że program będzie źle działał.
Podobnie do zdefiniowania stałej możemy użyć dyrektywy preprocesora #define (opisanej w dalszej części podręcznika). Tak zdefiniowaną stałą nazywamy stałą symboliczną. W przeciwieństwie do stałej zadeklarowanej z użyciem słowa const stała zdefiniowana przy użyciu #define jest zastępowana daną wartością w każdym miejscu, gdzie występuje, dlatego też może być używana w miejscach, gdzie "normalna" stała nie mogłaby dobrze spełnić swej roli.
W przeciwieństwie do języka C++, w C stała to cały czas zmienna, której kompilator pilnuje, by nie zmieniła się. Z tego powodu w C nie można użyć stałej do określenia wielkości tablicy[3] i należy się w takim wypadku odwołać do wcześniej wspomnianej dyrektywy #define.
[edytuj] Typy zmiennych
Każdy program w C operuje na zmiennych - wydzielonych w pamięci komputera obszarach, które mogą reprezentować obiekty nam znane, takie jak liczby, znaki, czy też bardziej złożone obiekty. Jednak dla komputera każdy obszar w pamięci jest taki sam - to ciąg zer i jedynek, w takiej postaci zupełnie nieprzydatny dla programisty i użytkownika. Podczas pisania programu musimy wskazać, w jaki sposób ten ciąg ma być interpretowany.
Typ zmiennej wskazuje właśnie sposób, w jaki pamięć, w której znajduje się zmienna będzie wykorzystywana. Określając go przekazuje się kompilatorowi informację, ile pamięci trzeba zarezerwować dla zmiennej, a także w jaki sposób wykonywać na nim operacje.
Każda zmienna musi mieć określony swój typ w miejscu deklaracji i tego typu nie może już zmienić. Lecz co jeśli mamy zmienną jednego typu, ale potrzebujemy w pewnym miejscu programu innego typu danych? W takim wypadku stosujemy konwersję (rzutowanie) jednej zmiennej na inną zmienną. Rzutowanie zostanie opisane później, w rozdziale Operatory.
Istnieją wbudowane i zdefiniowane przez użytkownika typy danych. Wbudowane typy danych to te, które zna kompilator, są one w nim bezpośrednio "zaszyte". Można też tworzyć własne typy danych, ale należy je kompilatorowi opisać. Więcej informacji znajduje się w rozdziale Typy złożone.
W języku C wyróżniamy 4 podstawowe typy zmiennych. Są to:
- char - jednobajtowe liczby całkowite, służy do przechowywania znaków;
- int- typ całkowity, o długości domyślnej dla danej architektury komputera;
- float - typ zmiennopozycyjny (4 bajty 6 miejsc po przecinku);
- double - typ zmiennopozycyjny podwójnej precyzji (8 bajtów 15 miejsc po przecinku);
Typy zmiennoprzecinkowe zostały dokładnie opisane w IEEE 754.
[edytuj] int
Ten typ przeznaczony jest do liczb całkowitych. Liczby te możemy zapisać na kilka sposobów:
- System dziesiętny
12 ; 13 ; 45 ; 35 itd
- System ósemkowy (oktalny)
010 czyli 8 016 czyli 8 + 6 = 14 019 BŁĄD
System ten operuje na cyfrach od 0 do 7. Tak wiec 9 jest niedozwolona. Jeżeli chcemy użyć takiego zapisu musimy zacząć liczbę od 0.
- System szesnastkowy (heksadycemalny)
0x10 czyli 1*16 + 0 = 16 0x12 czyli 1*16 + 2 = 18 0xff czyli 15*16 + 15 = 255
W tym systemie możliwe cyfry to 0...9 i dodatkowo a, b, c, d, e, f, które oznaczają 10, 11, 12, 13, 14, 15. Aby użyć takiego systemu musimy poprzedzić liczbę ciągiem 0x. Wielkość znaków w takich literałach nie ma znaczenia.
[edytuj] float
Ten typ oznacza liczby zmiennoprzecinkowe czyli ułamki. Istnieją dwa sposoby zapisu:
- System dziesiętny
3.14 ; 45.644 ; 23.54 ; 3.21 itd
- System "naukowy" - wykładniczy
6e2 czyli 6 * 102 czyli 600 1.5e3 czyli 1.5 * 103 czyli 1500 3.4e-3 czyli 3.4 * 10(-3) czyli 0.0034
Należy wziąć pod uwagę, że reprezentacja liczb rzeczywistych w komputerze jest niedoskonała i możemy otrzymywać wyniki o zauważalnej niedokładności.
[edytuj] double
Double - czyli "podwójny" - oznacza liczby zmiennoprzecinkowe podwójnej precyzji. Oznacza to, że liczba taka zajmuje zazwyczaj w pamięci dwa razy więcej miejsca niż float (np. 64 bity wobec 32 dla float), ale ma też dwa razy lepszą dokładność.
Domyślnie ułamki wpisane w kodzie są typu double. Możemy to zmienić dodając na końcu literę "f":
1.5f (float) 1.5 (double)
[edytuj] char
Jest to typ znakowy, umożliwiający zapis znaków ASCII. Może też być traktowany jako liczba z zakresu 0..255. Znaki zapisujemy w pojedynczych cudzysłowach, by odróżnić je od łańcuchów tekstowych (pisanych w podwójnych cudzysłowach).
'a' ; '7' ; '!' ; '$'
Cudzysłów ' zapisujemy tak: '\'' a NULL (czyli zero, które między innymi kończy napisy) tak: '\0'. Więcej znaków specjalnych.
Warto zauważyć, że typ char to zwykły typ liczbowy i można go używać tak samo jak typu int (zazwyczaj ma jednak mniejszy zakres). Co więcej literały znakowe (np. 'a') są traktowane jako liczby i w języku C są typu int (w języku C++ są typu char).
[edytuj] void
Słowa kluczowego void można w określonych sytuacjach użyć tam, gdzie oczekiwana jest nazwa typu. void nie jest właściwym typem, bo nie można utworzyć zmiennej takiego typu; jest to "pusty" typ (ang. void znaczy "pusty"). Typ void przydaje się do zaznaczania, że funkcja nie zwraca żadnej wartości lub że nie przyjmuje żadnych parametrów (więcej o tym w rozdziale Funkcje). Można też tworzyć zmienne będące typu "wskaźnik na void"
[edytuj] Specyfikatory
Specyfikatory to słowa kluczowe, które postawione przy typie danych zmieniają jego znaczenie.
[edytuj] signed i unsigned
Na początku zastanówmy się, jak komputer może przechować liczbę ujemną. Otóż w przypadku przechowywania liczb ujemnych musimy w zmiennej przechować jeszcze jej znak. Jak wiadomo, zmienna składa się z szeregu bitów. W przypadku użycia zmiennej pierwszy bit z lewej strony (nazywany także bitem najbardziej znaczącym) przechowuje znak liczby. Efektem tego jest spadek "pojemności" zmiennej, czyli zmniejszenie największej wartości, którą możemy przechować w zmiennej.
Signed oznacza liczbę ze znakiem, unsigned - bez znaku (nieujemną). Mogą być zastosowane do typów: char i int i łączone ze specyfikatorami short i long (gdy ma to sens).
Jeśli przy signed lub unsigned nie napiszemy, o jaki typ nam chodzi, kompilator przyjmie wartość domyślną czyli int.
Przykładowo dla zmiennej char(zajmującej 8 bitów zapisanej w formacie uzupełnień do dwóch) wygląda to tak:
signed char a; /* zmienna a przyjmuje wartości od -128 do 127 */ unsigned char b; /* zmienna b przyjmuje wartości od 0 do 255 */ unsigned short c; unsigned long int d;
Jeżeli nie podamy żadnego ze specyfikatora wtedy liczba jest domyślnie przyjmowana jako signed (nie dotyczy to typu char, dla którego jest to zależne od kompilatora).
signed int i = 0; // jest równoznaczne z: int i = 0;
Liczby bez znaku pozwalają nam zapisać większe liczby przy tej samej wielkości zmiennej - ale trzeba uważać, by nie zejść z nimi poniżej zera - wtedy "przewijają" się na sam koniec zakresu, co może powodować trudne do wykrycia błędy w programach.
[edytuj] short i long
Short i long są wskazówkami dla kompilatora, by zarezerwował dla danego typu mniej (odpowiednio - więcej) pamięci. Mogą być zastosowane do dwóch typów: int i double (tylko long), mając różne znaczenie.
Jeśli przy short lub long nie napiszemy, o jaki typ nam chodzi, kompilator przyjmie wartość domyślną czyli int.
Należy pamiętać, że to jedynie życzenie wobec kompilatora - w wielu kompilatorach typy int i long int mają ten sam rozmiar. Standard języka C nakłada jedynie na kompilatory następujące ograniczenia:
- int - nie może być krótszy niż 16 bitów;
- int - musi być dłuższy lub równy short a nie może być dłuższy niż long;
- short int - nie może być krótszy niż 16 bitów;
- long int - nie może być krótszy niż 32 bity;
Zazwyczaj typ int jest typem danych o długości odpowiadającej wielkości rejestrów procesora, czyli na procesorze szesnastobitowym ma 16 bitów, na trzydziestodwubitowym - 32 itd.[4]. Z tego powodu, jeśli to tylko możliwe, do reprezentacji liczb całkowitych preferowane jest użycie typu int bez żadnych specyfikatorów rozmiaru.
[edytuj] Modyfikatory
[edytuj] volatile
volatile znaczy ulotny. Oznacza to, że kompilator wyłączy dla takiej zmiennej optymalizacje typu zastąpienia przez stałą lub zawartość rejestru, za to wygeneruje kod, który będzie odwoływał się zawsze do komórek pamięci danego obiektu. Zapobiegnie to błędowi, gdy obiekt zostaje zmieniony przez część programu, która nie ma zauważalnego dla kompilatora związku z danym fragmentem kodu lub nawet przez zupełnie inny proces.
volatile float liczba1; float liczba2; { printf ("%d\n%d", liczba1, liczba2); /* instrukcje nie związane ze zmiennymi */ printf ("%d\n%d", liczba1, liczba2); }
Jeżeli zmienne liczba1 i liczba2 zmienią się niezauważalnie dla kompilatora to odczytując :
- liczba1 - nastąpi odwołanie do komórek pamięci. Kompilator pobierze nową wartość zmiennej.
- liczba2 - kompilator może wypisać poprzednią wartość, którą przechowywał w rejestrze.
Modyfikator volatile jest rzadko stosowany i przydaje się w wąskich zastosowaniach, jak współbieżność i współdzielenie zasobów oraz przerwania systemowe.
[edytuj] register
Jeżeli utworzymy zmienną, której będziemy używać w swoim programie bardzo często, możemy wykorzystać modyfikator register. Kompilator może wtedy umieścić zmienną w rejestrze, do którego ma szybki dostęp, co przyśpieszy odwołania do tej zmiennej
register int liczba ;
W nowoczesnych kompilatorach ten modyfikator praktycznie nie ma wpływu na program. Optymalizator sam decyduje czy i co należy umieścić w rejestrze. Nie mamy żadnej gwarancji, że zmienna tak zadeklarowana rzeczywiście się tam znajdzie, chociaż dostęp do niej może zostać przyspieszony w inny sposób. Raczej powinno się unikać tego typu konstrukcji w programie.
[edytuj] static
Pozwala na zdefiniowanie zmiennej statycznej. "Statyczność" polega na zachowaniu wartości pomiędzy kolejnymi definicjami tej samej zmiennej. Jest to przede wszystkim przydatne w funkcjach. Gdy zdefiniujemy zmienną w ciele funkcji, to zmienna ta będzie od nowa definiowana wraz z domyślną wartością (jeżeli taką podano). W wypadku zmiennej określonej jako statyczna, jej wartość się nie zmieni przy ponownym wywołaniu funkcji. Na przykład:
void dodaj(int liczba) { int zmienna = 0; zmienna = zmienna + liczba; printf ("Wartosc zmiennej %d\n", zmienna); }
Gdy wywołamy tę funkcję np. 3 razy w ten sposób:
dodaj(3); dodaj(5); dodaj(4);
to ujrzymy na ekranie:
Wartość zmiennej Zmienna:3 Wartość zmiennej Zmienna:5 Wartość zmiennej Zmienna:4
jeżeli jednak deklarację zmiennej zmienimy na static int zmienna = 0, to wartość zmiennej zostanie zachowana i po podobnym wykonaniu funkcji powinnyśmy ujrzeć:
Wartość zmiennej Zmienna:3 Wartość zmiennej Zmienna:8 Wartość zmiennej Zmienna:12
Zupełnie co innego oznacza static zastosowane dla zmiennej globalnej. Jest ona wtedy widoczna tylko w jednym pliku. Zobacz też: rozdział Biblioteki.
[edytuj] extern
Przez extern oznacza się zmienne globalne zadeklarowane w innych plikach - informujemy w ten sposób kompilator, żeby nie szukał jej w aktualnym pliku. Zobacz też: rozdział Biblioteki.
[edytuj] auto
Zupełnym archaizmem jest modyfikator auto, który oznacza tyle, że zmienna jest lokalna. Ponieważ zmienna zadeklarowana w dowolnym bloku zawsze jest lokalna, modyfikator ten nie ma obecnie żadnego zastosowania praktycznego. auto jest spadkiem po wcześniejszych językach programowania, na których oparty jest C (np. B).
[edytuj] Uwagi
- Język C++ pozwala na mieszanie deklaracji zmiennych z kodem. Więcej informacji w C++/Zmienne.
[edytuj] Podstawowe operacje matematyczne
[edytuj] Przypisanie
Operator przypisania ("="), jak sama nazwa wskazuje, przypisuje wartość prawego argumentu lewemu, np.:
int a = 5, b; b = a; printf("%d\n", b); /* wypisze 5 */
Operator ten ma łączność prawostronną tzn. obliczanie przypisań następuje z prawa na lewo i zwraca on przypisaną wartość, dzięki czemu może być użyty kaskadowo:
int a, b, c; a = b = c = 3; printf("%d %d %d\n", a, b, c); /* wypisze "3 3 3" */
[edytuj] Skrócony zapis
C umożliwia też skrócony zapis postaci a #= b;, gdzie # jest jednym z operatorów: +, -, *, /, &, |, ^, << lub >> (opisanych niżej). Ogólnie rzecz ujmując zapis a #= b; jest równoważny zapisowi a = a # (b);, np.:
int a = 1; a += 5; /* to samo, co a = a + 5; */ a /= a + 2; /* to samo, co a = a / (a + 2); */ a %= 2; /* to samo, co a = a % 2; */
| Początkowo skrócona notacja miała następującą składnię: a =# b, co często prowadziło do niejasności, np. i =-1 (i = -1 czy też i = i-1?). Dlatego też zdecydowano się zmienić kolejność operatorów. |
[edytuj] Rzutowanie
Zadaniem rzutowania jest konwersja danej jednego typu na daną innego typu. Konwersja może być niejawna (domyślna konwersja przyjęta przez kompilator) lub jawna (podana explicite przez programistę). Oto kilka przykładów konwersji niejawnej:
int i = 42.7; /* konwersja z double do int */ float f = i; /* konwersja z int do float */ double d = f; /* konwersja z float do double */ unsigned u = i; /* konwersja z int do unsigned int */ f = 4.2; /* konwersja z double do float */ i = d; /* konwersja z double do int */ char *str = "foo"; /* konwersja z const char* do char* [1] */ const char *cstr = str; /* konwersja z char* do const char* */ void *ptr = str; /* konwersja z char* do void* */
Podczas konwersji zmiennych zawierających większe ilości danych do typów prostszych (np. double do int) musimy liczyć się z utratą informacji, jak to miało miejsce w pierwszej linijce - zmienna int nie może przechowywać części ułamkowej toteż została ona odcięta i w rezultacie zmiennej została przypisana wartość 42.
Zaskakująca może się wydać linijka oznaczona przez [1]. Niejawna konwersja z typu const char* do typu char* nie jest dopuszczana przez standard C. Jednak literały napisowe (które są typu const char*) stanowią tutaj wyjątek. Wynika on z faktu, że były one używane na długo przed wprowadzeniem słówka const do języka i brak wspomnianego wyjątku spowodowałby, że duża część kodu zostałaby nagle zakwalifikowana jako niepoprawny kod.
Do jawnego wymuszenia konwersji służy jednoargumentowy operator rzutowania, np.:
double d = 3.14; int pi = (int)d; /* 1 */ pi = (unsigned)pi >> 4; /* 2 */
W pierwszym przypadku operator został użyty, by zwrócić uwagę na utratę precyzji. W drugim, dlatego że bez niego operator przesunięcia bitowego zachowuje się trochę inaczej.
Obie konwersje przedstawione powyżej są dopuszczane przez standard jako jawne konwersje (tj. konwersja z double do int oraz z int do unsigned int), jednak niektóre konwersje są błędne, np.:
const char *cstr = "foo"; char *str = cstr;
W takich sytuacjach można użyć operatora rzutowania by wymusić konwersję:
const char *cstr = "foo"; char *str = (char*)cstr;
Należy unikać jednak takich sytuacji i nigdy nie stosować rzutowania by uciszyć kompilator. Zanim użyjemy operatora rzutowania należy się zastanowić co tak naprawdę będzie on robił i czy nie ma innego sposobu wykonania danej operacji, który nie wymagałby podejmowania tak drastycznych kroków.
[edytuj] Operatory arytmetyczne
Język C definiuje następujące dwuargumentowe operatory arytmetyczne:
- dodawanie ("+"),
- odejmowanie ("-"),
- mnożenie ("*"),
- dzielenie ("/"),
- reszta z dzielenia ("%") określona tylko dla liczb całkowitych (tzw. dzielenie modulo).
int a=7, b=2, c; c = a % b; printf ("%d\n",c); /* wypisze "1" */
Należy pamiętać, że (w pewnym uproszczeniu) wynik operacji jest typu takiego jak największy z argumentów. Oznacza to, że operacja wykonana na dwóch liczbach całkowitych nadal ma typ całkowity nawet jeżeli wynik przypiszemy do zmiennej rzeczywistej. Dla przykładu, poniższy kod:
float a = 7 / 2; printf("%f\n", a);
wypisze (wbrew oczekiwaniu początkujących programistów) 3, a nie 3.5. Odnosi się to nie tylko do dzielenia, ale także mnożenia, np.:
float a = 1000 * 1000 * 1000 * 1000 * 1000 * 1000; printf("%f\n", a);
prawdopodobnie da o wiele mniejszy wynik niż byśmy się spodziewali. Aby wymusić obliczenia rzeczywiste należy zmienić typ jednego z argumentów na liczbę rzeczywistą po prostu zmieniając literał lub korzystając z rzutowania, np.:
float a = 7.0 / 2; float b = (float)1000 * 1000 * 1000 * 1000 * 1000 * 1000; printf("%f\n", a); printf("%f\n", b);
Operatory dodawania i odejmowania są określone również, gdy jednym z argumentów jest wskaźnik, a drugim liczba całkowita. Ten drugi jest także określony, gdy oba argumenty są wskaźnikami. O takim użyciu tych operatorów dowiesz się więcej w dalszej części książki.
[edytuj] Inkrementacja i dekrementacja
Aby skrócić zapis wprowadzono dodatkowe operatory: inkrementacji ("++") i dekrementacji ("--"), które dodatkowo mogą być pre- lub postfiksowe. W rezultacie mamy więc cztery operatory:
- pre-inkrementacja ("++i"),
- post-inkrementacja ("i++"),
- pre-dekrementacja ("--i") i
- post-dekrementacja ("i--").
Operatory inkrementacji zwiększa, a dekrementacji zmniejsza argument o jeden. Ponadto operatory pre- zwracają nową wartość argumentu, natomiast post- starą wartość argumentu.
int a, b, c; a = 3; b = a--; /* po operacji b=3 a=2 */ c = --b; /* po operacji b=2 c=2 */
Czasami (szczególnie w C++) użycie operatorów stawianych za argumentem jest nieco mniej efektywne (bo kompilator musi stworzyć nową zmienną by przechować wartość tymczasową).
[edytuj] Operacje bitowe
Oprócz operacji znanych z lekcji matematyki w podstawówce, język C został wyposażony także w operatory bitowe, zdefiniowane dla liczb całkowitych. Są to:
- negacja bitowa ("~"),
- koniunkcja bitowa ("&"),
- alternatywa bitowa ("|") i
- alternatywa rozłączna (XOR) ("^").
Działają one na poszczególnych bitach przez co mogą być szybsze od innych operacji. Działanie tych operatorów można zdefiniować za pomocą poniższych tabel:
"~" | 0 1 "&" | 0 1 "|" | 0 1 "^" | 0 1
-----+----- -----+----- -----+----- -----+-----
| 1 0 0 | 0 0 0 | 0 1 0 | 0 1
1 | 0 1 1 | 1 1 1 | 1 0
a | 0101 = 5
b | 0011 = 3
-------+------
~a | 1010 = 10
~b | 1100 = 12
a & b | 0001 = 1
a | b | 0111 = 7
a ^ b | 0110 = 6
Lub bardziej opisowo:
- negacja bitowa daje w wyniku liczbę, która ma bity równe jeden tylko na tych pozycjach, na których argument miał bity równe zero;
- koniunkcja bitowa daje w wyniku liczbę, która ma bity równe jeden tylko na tych pozycjach, na których oba argumenty miały bity równe jeden (mnemonik: 1 gdy wszystkie 1);
- alternatywa bitowa daje w wyniku liczbę, która ma bity równe jeden na wszystkich tych pozycjach, na których jeden z argumentów miał bit równy jeden (mnemonik: 1 jeśli jest 1);
- alternatywa rozłączna daje w wyniku liczbę, która ma bity równe jeden tylko na tych pozycjach, na których tylko jeden z argumentów miał bit równy jeden (mnemonik: 1 gdy różne).
Przy okazji warto zauważyć, że a ^ b ^ b to po prostu a. Właściwość ta została wykorzystana w różnych algorytmach szyfrowania oraz funkcjach haszujących. Alternatywę wyłączną stosuje się np. do szyfrowania kodu wirusów polimorficznych.
[edytuj] Przesunięcie bitowe
Dodatkowo, język C wyposażony jest w operatory przesunięcia bitowego w lewo ("<<") i prawo (">>"). Przesuwają one w danym kierunku bity lewego argumentu o liczbę pozycji podaną jako prawy argument. Brzmi to może strasznie, ale wcale takie nie jest. Rozważmy 4 bitowe liczby bez znaku (taki hipotetyczny unsigned int), wówczas:
a | a<<1 | a<<2 | a>>1 | a>>2 ------+------+------+------+------ 0001 | 0010 | 0100 | 0000 | 0000 0011 | 0110 | 1100 | 0001 | 0000 0101 | 1010 | 0100 | 0010 | 0001 1000 | 0000 | 0000 | 0100 | 0010 1111 | 1110 | 1100 | 0111 | 0011 1001 | 0010 | 0100 | 0100 | 0010
Nie jest to zatem takie straszne na jakie wygląda. Widać, że bity będące na skraju są tracone, a w "puste" miejsca wpisywane są zera.
Inaczej rzecz się ma jeżeli lewy argument jest liczbą ze znakiem. Dla przesunięcia bitowego w lewo a << b jeżeli a jest nieujemna i wartość
mieści się w zakresie liczby to jest to wynikiem operacji. W przeciwnym wypadku działanie jest niezdefiniowane[5].
Dla przesunięcia bitowego w lewo, jeżeli lewy argument jest nieujemny to operacja zachowuje się tak jak w przypadku liczb bez znaku. Jeżeli jest on ujemny to zachowanie jest zależne od implementacji.
Zazwyczaj operacja przesunięcia w lewo zachowuje się tak samo jak dla liczb bez znaku, natomiast przy przesuwaniu w prawo bit znaku nie zmienia się[6]:
a | a>>1 | a>>2 ------+------+------ 0001 | 0000 | 0000 0011 | 0001 | 0000 0101 | 0010 | 0001 1000 | 1100 | 1110 1111 | 1111 | 1111 1001 | 1100 | 1110
Przesunięcie bitowe w lewo odpowiada pomnożeniu, natomiast przesunięcie bitowe w prawo podzieleniu liczby przez dwa do potęgi jaką wyznacza prawy argument. Jeżeli prawy argument jest ujemny lub większy lub równy liczbie bitów w typie, działanie jest niezdefiniowane.
#include <stdio.h> int main () { int a = 6; printf ("6 << 2 = %d\n", a<<2); /* wypisze 24 */ printf ("6 >> 2 = %d\n", a>>2); /* wypisze 1 */ return 0; }
[edytuj] Porównanie
W języku C występują następujące operatory porównania:
- równe ("=="),
- różne ("!="),
- mniejsze ("<"),
- większe (">"),
- mniejsze lub równe ("<=") i
- większe lub równe (">=").
Wykonują one odpowiednie porównanie swoich argumentów i zwracają jedynkę jeżeli warunek jest spełniony lub zero jeżeli nie jest.
[edytuj] Częste błędy
Porównajmy ze sobą dwa warunki:
(a = 1) (a == 1)
Pierwszy z nich zawsze będzie prawdziwy, niezależnie od wartości zmiennej a! Dzieje się tak, ponieważ zostaje wykonane przypisanie do a wartości 1 a następnie jako wartość jest zwracane to, co zostało przypisane - czyli jeden. Drugi natomiast będzie prawdziwy tylko, gdy a jest równe 1.
W celu uniknięcia takich błędów niektórzy programiści zamiast pisać a == 1 piszą 1 == a, dzięki czemu pomyłka spowoduje, że kompilator zgłosi błąd.
Warto zauważyć, że kompilator GCC potrafi w pewnych sytuacjach wychwycić taki błąd. Aby zaczął to robić należy podać mu argument -Wparentheses.
Innym błędem jest użycie zwykłych operatorów porównania do sprawdzania relacji pomiędzy liczbami rzeczywistymi. Ponieważ operacje zmiennoprzecinkowe wykonywane są z pewnym przybliżeniem rzadko kiedy dwie zmienne typu float czy double są sobie równe. Dla przykładu:
#include <stdio.h> int main () { float a, b, c; a = 1e10; /* tj. 10 do potęgi 10 */ b = 1e-10; /* tj. 10 do potęgi -10 */ c = b; /* c = b */ c = c + a; /* c = b + a (teoretycznie) */ c = c - a; /* c = b + a - a = b (teoretycznie) */ printf("%d\n", c == b); /* wypisze 0 */ }
Obejściem jest porównywanie modułu różnicy liczb. Również i takie błędy kompilator GCC potrafi wykrywać - aby to robił należy podać mu argument -Wfloat-equal.
[edytuj] Operatory logiczne
Analogicznie do części