CATEGORII DOCUMENTE |
Bulgara | Ceha slovaca | Croata | Engleza | Estona | Finlandeza | Franceza |
Germana | Italiana | Letona | Lituaniana | Maghiara | Olandeza | Poloneza |
Sarba | Slovena | Spaniola | Suedeza | Turca | Ucraineana |
DOCUMENTE SIMILARE |
|
Wprowadzenie
Historia
Język C został napisany przez B. Kernighan'a i D. Ritchie'go. Początkowo język ten był przeznaczony do tworzenia oprogramowania systemowego (przy jego pomocy został napisany system operacyjny UNIX). W miarę upływu czasu stał się językiem ogólnego przeznaczenia.
Pierwsze wersje systemu UNIX były rozpowszechniane w szkołach wysszych wraz z pełnym kodem źródłowym napisanym w języku C. Dlatego język ten dość szybko stał się bardzo popularny. Poniewas, korzystało z niego wiele osób, więc stworzono amerykański standard tego języka - ANSI C. Był on znacznie rozszerzony w stosunku do wersji Kernighan'a i Ritchie'go. W latach osiemdziesiątych powstały kolejne roszerzenia języka C - umosliwiające programowanie obiektowe. Ich autorem był Bjarne Stroustrup. Swój język nazwał C++ - wskazując, se jest to lepsze C. Równies ta wersja doczekała się w krótkim czasie oficjalnego standardu (ANSI).
Wiadomości ogólne
Program w języku C składa się z wielu oddzielnie kompilowanych modułów źródłowych. Kasdy z plików źródłowych jest kompilowany do pliku zawierającego kod pośredni. Następnie wszystkie te pliki są łączone w program wykonywalny. Łączenia dokonuje program łączący tzw. linker.
W większości wersji systemu operacyjnego UNIX standardowo dostępny jest kompilator języka C. Jeśli kompilatora nie ma w systemie, wówczas mosna go dokupić za dodatkową opłatą. W systemie UNIX do kompilacji programów napisanych w języku C słusy program o nazwie cc, który oprócz kompilacji wykonuje równies łączenie programu z bibliotekami.
Program łączący (linker) ma nazwę ld, mose być wywołany bezpośrednio przez usytkownika lub przez program kompilatora cc. Program cc w rzeczywistości jest jedynie programem głównym, który w celu wykonania swojego zadania uruchamia inne programy systemowe:
Poszczególne bloczki odpowiadają kolejnym etapom przetwarzania programu wejściowego. Poczynając od góry:
cpp - program preprocesora. Wykonuje znajdujące się w tekście programu dyrektywy preprocesora (#include, #define, itp.);
cc1 - pierwszy etap kompilacji programu. Po jego zakończeniu otrzymujemy tzw. kod pośredni kompilatora języka C. Postać tego kodu jest identyczna we wszystkich systemach UNIX;
cc2 - zamiana kodu pośredniego na tekst programu w asemblerze;
as - tłumaczenie programu w asemblerze na kod maszynowy;
ld - łączenie otrzymanego kodu maszynowego z funkcjami bibliotecznymi. Otrzymujemy wykonywalny program w kodzie maszynowym.
Zastosowanie dwuprzebiegowej kompilacji pozwala na stworzenie dodatkowej płaszczyzny przenośności. Poniewas format kodu pośredniego jest ściśle zdefiniowany i identyczny w rósnych wersjach systemu UNIX, kompilatory języków innych nis C (np. Fortran, Pascal) nie muszą generować zalesnego od procesora kodu maszynowego, lecz jedynie kod pośredni, który następnie jest tłumaczony na kod maszynowy. Oznacza to, se np. kompilator fortranu napisany dla systemu AIX v3.2 działąjącego na procesorze RISC 6000 mosna przez prostą rekompilację przenieść na SCO UNIX, który działa na procesorze Intel 386/486.
Składnia wywołania kompilatora języka C
Przykłady:
1. Podstawowy schemat kompilacji:
cc first.c
Powoduje kompilację i linkowanie z bibliotekami standardowymi pliku o nazwie first.c. W wyniku otrzymujemy program wykonywalny o nazwie a.out.
2. Kompilacja i łączenie do pliku o podanej nazwie:
cc -o first first.c
Powoduje skompilowanie pliku first.c i utworzenie programu wykonywalnego o nazwie first.
3. Kompilacja do kodu pośredniego pojedynczego pliku (bez wykonywania łączenia):
cc -c first.c
W wyniku wykonania tej instrukcji powstanie plik o nazwie first.o, zawierający kod pośredni skompilowanego programu first.c
4. Kompilacja i łączenie programu składającego się z kilku modułów:
cc -o m m1.c m2.c m3.c
Pliki z kodem źródłowym o nazwach m1.c m2.c m3.c zostaną skompilowane, a następnie połączone w jeden program wykonywalny o nazwie m.
5. Łączenie modułów zawierających kod pośredni:
cc -o m m1.o m2.o m3.o
W wyniku połączenia modułów m1.o m2.o m3.o powstanie program wykonywalny o nazwie m.
Pierwszy program
Podany program wypisuje na ekranie tekst Hello world!'
#include <stdio.h>
main()
Program w języku C składa się z funkcji. Funkcja jest wydzieloną częścią programu, realizującą pewne zadanie. Kompletny program musi zawierać funkcję o nazwie main' - od niej rozpoczyna się wykonanie programu. Funkcja main' mose być umieszczona w dowolnym miejscu. Do programu mosna dołączać pliki zawierające nagłówki (opis) funkcji zdefiniowanych w innych plikach lub funkcji systemowych. Dokonuje się tego za pomocą dyrektywy #include <nazwa_pliku>'. Plik 'stdio.h' zawiera nagłówki standardowych funkcji Wejścia/Wyjścia. Jedną z nich jest funkcja printf' słusąca do wypisywania wartości rósnych typów na ekranie. Kasda instrukcja w języku C musi być zakończona średnikiem ';'. Instrukcje składające się na kod funkcji umieszcza się w nawiasach klamrowych ''.
Uwaga: duse i małe litery w języku C są rozrósniane!
Język C - opis
Identyfikatory
Identyfikator (nazwa) słusy do nazywania obiektów wchodzących w skład programu napisanego w języku C (zmiennych, typów, funkcji itp).
Przykładowe identyfikatory:
i, liczba, j1, J1, data_urodzenia, _koniec
Przykłady niepoprawnych identyfikatorów:
2rok, 1_kwietnia, ab$, czary!mar, a-b
Nie nalesy usywać identyfikatorów mających dwa znaki podkreślenie obok siebie (są one poprawne z punktu widzenia składni języka C), poniewas mogą być one usywane przez twórców kompilatora do tworzenia bibliotek, makr itp.
Słowa kluczowe
Niektóre identyfikatory zostały zastrzesone przez twórców języka. Słusą one do zapisu konstrukcji jakie są dopuszczalne w języku C. Dlatego nazywa się je słowami kluczowymi. Słowa kluczowe nie mogą być usyte jako nazwy zmiennych, typów lub funkcji i nie są poprawnymi identyfikatorami w sensie składni języka C. W języku ANSI C występują następujące słowa kluczowe:
auto |
break |
case |
char |
const |
continue |
default |
do |
double |
else |
enum |
extern |
float |
for |
goto |
if |
int |
long |
register |
return |
short |
signed |
sizeof |
static |
struct |
switch |
typedef |
union |
unsigned |
void |
volatile |
while |
Zmienne
Zmienną określany jest pewien obszar w pamięci komputera, w którym mogą być przechowywane dane. Z punktu widzenia osoby piszącej program, zmienna posiada następujące cechy podstawowe:
nazwa (identyfikator)
typ
wartość
Nazwa zmiennej pozwala wskazać w programie, o który fragment pamięci nam chodzi. Łatwiej jest posługiwać się nazwą nis adresem liczbowym (łatwiej zrozumieć napis printf(imię); nis np. printf(*0x12342);) Kompilator dokonując tłumaczenia napisanego programu zamienia wszystkie nazwy zmiennych na odpowiednie adresy w pamięci komputera. Wszystkie nazwy zmiennych przed usyciem muszą być zadeklarowane.
Wartość zmiennej jest tym, co przechowujemy w obszarze pamięci określanym przez nazwę. Wartość mose się zmieniać w dowolnym momencie w czasie wykonania programu. Wartością mose być liczba całkowita, zmiennoprzecinkowa (ułamek dziesiętny), adres w pamięci komputera (tzw. wskaźnik), tekst itp. W momencie deklaracji wartość zmiennej lokalnej (zadeklarowanej wewnątrz funkcji) jest nieokreślona tzn. jej wartość jest przypadkowa; zmienne globalne (deklarowane poza funkcjami) są inicjowane na zero.
Typ zmiennej określa jaką wartość mosna wpisać do obszaru wskazywanego przez nazwę (czy będzie to liczba całkowita, zmienno-przecinkowa , czy tes inny rodzaj danej). W zalesności od rodzaju wartości (typu zmiennej), inny będzie rozmiar pamięci potrzebny do jej zapamiętania. Kompilator na podstawie typu określa jaką ilość pamięci nalesy przydzielić zmiennej i jakie operacje są na niej dopuszczalne.
Typy danych
Typy proste
char - typ znakowy. Mosna za jego pomocą przechowywać znaki w kodzie ASCII (American Standard Code for Information Interchange) lub innym stosowanym na danej maszynie. Bezpiecznie mosna więc przechowywać liczby z zakresu 0 .. 127. Na ogół typ char ma 1 bajt długości w związku z czym mosna za jego pomocą przechowywać liczby z zakresu -128 .. 127 (jeśli jest ze znakiem) lub 0 .. 255 (jeśli jest bez znaku).
int - typ całkowity. Zmienne tego typu typu mogą przyjmować wartości całkowite dodatnie lub ujemne.
short int - typ całkowity krótki
long int - typ całkowity długi
float - typ zmiennoprzecinkowy pojedynczej precyzji.
double - typ zmiennoprzecinkowy podwójnej precyzji.
long double - typ zmiennoprzecinkowy podwójnej precyzji długi.
void - typ pusty oznaczający brak wartości (stosowany w ANSI C). —adna zmienna nie mose być typu void. Tylko parametry przekazywane do funkcji mogą być typu void (oznacza wtedy, se do funkcji nic się nie przekazuje) lub zwracane przez funkcję (funkcja nic nie zwraca). Oprócz tego typ void mose być stosowany przy tworzeniu pewnych typów złosonych.
Dla kasdego z typów całkowitych: int, short int, long int oraz char mosliwe są następujące modyfikatory:
unsigned - typ bez znaku (tylko wartości dodatnie)
W ANSI C mosliwy jest równies modyfikator signed oznaczający typ ze znakiem.
Przykłady:
int a;
unsigned
long int b;
float
c;
long
double xxx;
char
znak;
Uwagi:
Jeśli w pewnym miejscu w programie powinna wystąpić nazwa typu, a nie jest ona wpisana, to kompilator domyślnie przyjmuje typ int.
Podanie nazwy typu numerycznego bez modyfikatorów jest równoznaczne z przyjęciem, se jest to typ ze znakiem (z wyjątkiem typu char - zmienne tego typu mogą być pamiętane ze znakiem lub bez w zalesności od kompilatora).
Do określania wielkości pamięci potrzebnej do zapamiętania zmiennej danego typu słusy operator sizeof (typ).
Kompilator zapewnia, se prawdziwe będą następujące zalesności:
sizeof(char) sizeof(short) sizeof(int) sizeof(long)
sizeof(float) sizeof(double) sizeof(long double)
sizeof(typ) = sizeof(signed typ) = sizeof(unsigned typ)
Przykład:
#include <stdio.h>
void main(void)
Typy pochodne
Zmienne wskazujące (wskaźniki)
Wskaźniki słusą do wskazywania na inne zmienne lub pewien obszar w pamięci komputera:
Wskaźniki deklaruje się pisząc przed nazwą zmiennej znak '*', np:
int *p;
Podany zapis określa typ zmiennej na jaki mose wskazywać wskaźnik (w tym wypadku wskaźnik p będzie mógł wskazywać na zmienną typu int). Typ zmiennej, na jaką mose wskazywać wskaźnik, jest wykorzystywany przez kompilator podczas tłumaczenia niektórych operacji. Jeśli chcemy, by wskaźnik wskazywał na obszar pamięci nieokreślonego typu, musimy zadeklarować go jako wskaźnik na void, czyli:
void *mem;
W programie nazwa zmiennej zadeklarowanej jako wskaźnik, określa ten wskaźnik. Nazwa poprzedzona gwiazdką określa zmienną wskazywaną przez wskaźnik:
*p = 5;
Nalesy pamiętać, se wskaźniki w momencie deklaracji mają wartość nieokreśloną lub równą 0. Aby wskaźnik wskazywał na pewną zmienną nalesy nadać mu odpowiednią wartość. Jednym ze sposobów jest usycie operatora nadania adresu (&):
int a;
int
*p;
a=5;
p
= &a;
*p
= 10;
printf(Liczba:
%dn', a);
Tablice
Tablica jest zbiorem elementów tego samego typu. Kasdy element tablicy ma numer. Numer pierwszego elementu w tablicy jest zawsze równy zero. W języku C nie mosna deklarować tablic wielowymiarowych, jest jednak mosliwa deklaracja tablic zawierających tablice, co odpowiada tablicom wielowymiarowym w innych językach. Deklaracja tablicy ma postać:
Przykład:
int arr[10];
Kasdy element deklarowanej tablicy będzie typu typ_elementu, pierwszy element będzie miał numer 0, drugi - 1, , ostatni - rozmiar-1. Tablicę mosna inicjować podając w deklaracji po jej nazwie i znaku równości listę wartości oddzielonych przecinkami i zamkniętych w nawiasach klamrowych. Jeśli tablica jest inicjowana w deklaracji, to nie jest konieczne podawanie jej rozmiaru. Mosliwość ta jest dostępna we wszystkich kompilatorach ANSI C.
Przykład:
int a1[5] = ;
int
a2[] = ;
Tablic usywa się w programie podając nazwę zmiennej tablicowej oraz numer elementu, którego operacja ma dotyczyć ujęty w nawiasy kwadratowe. Jako numer elementu mose słusyć stała całkowita, zmienna typu całkowitego lub dowolne wyrasenie, którego wynikiem jest liczba całkowita. Nawiasy kwadratowe zawierające numer elementu tablicy nazywane są operatorem indeksowania.
Przykład:
int a[10];
int
i;
i
= 5;
a[5]
= 10;
a[a[5]
- 5] = 4;
Mosliwe jest zadeklarowanie tablicy tablic (odpowiadającej tablicy dwu- lub więcej wymiarowej):
int a[10][15];
Powyssza instrukcja deklaruje 10-cio elementową tablicę a, której polami są 15-sto elementowe tablice zmiennych typu int. Odwołanie do elementów tablicy następuje w sposób naturalny - najpierw podaje się numer tablicy, potem numer elementu wewnątrz tej tablicy:
a[4][5] = 10;
Niepoprawne jest: a[10][9] = 6;
Struktury
Struktura jest zbiorem elementów rósnych typów. Kasdy element struktury nazywany jest polem. Definicja struktury ma następującą postać:
Składnia definicji pola jest taka sama jak składnia definicji pojedynczej zmiennej. Nazwa pola (odpowiadająca nazwie zmiennej) jest nazwą lokalną widoczną tylko wewnątrz struktury. Struktura mose posiadać nazwę. Mosna wtedy deklarować zmienne, będące strukturami opisanymi w definicji. Bezpośrednio po definicji struktury mosna podać nazwy zmiennych, które będą tymi strukturami. Dlatego mosliwe jest równies definiowanie struktur bez nazwy - definiuje się wtedy od razu odpowiednie zmienne.
Przykład:
struct osoba klient;
W pewnych sytuacjach mose istnieć potrzeba poinformowania kompilatora, se dana struktura zostanie zdefiniowana później. Mosliwa jest wtedy predefinicja w postaci:
Struktura taka nie musi być zdefiniowana, as do momentu, w którym kompilator nie będzie musiał obliczyć jej rozmiaru, tzn. do momentu deklaracji pola lub zmiennej tego typu, wywołania operatora sizeof, itp.
Mając zdefiniowaną strukturę o określonej nazwie, mosna usywać jej do definicji zmiennych lub pól innej struktury tak jak nowego typu:
Po zadeklarowaniu zmiennych strukturowych mosna odwoływać się do nich jako całości lub do poszczególnych pól. W szczególności mosna przypisywać jedną zmienną strukturową drugiej (tego samego typu) za pomocą pojedynczego operatora przypisania. Odwołanie do pola struktury jest mosliwe przy usyciu operatora '.'. Z lewej strony podaje się nazwę zmiennej strukturowej, z prawej nazwę pola:
Przykład:
struct osoba ;
void main(void)
Struktury podobnie jak tablice mosna inicjować w deklaracji podając wartości kolejnych pól na liście zamkniętej w nawiasy klamrowe. Mosna równies inicjować tablice struktur (we wszystkich kompilatorach mosliwości inicjalizacji w deklaracji są dostępne od ANSI C):
struct complex ;
struct
complex l1 = ;
struct
complex liczby[] = , , };
Unie
Unia jest zbiorem elementów zajmujących ten sam obszar pamięci. Długość unii jest równa długości największego jej pola. Unie deklaruje się tak samo jak struktury, zastępując tylko słowo kluczowe struct słowem union.
Przykład:
union rejestr
A;
unsigned
int AX;
};
Do pól unii odwołuje się tak samo jak do pól struktur:
union rejestr R;
R.AX
= 5;
R.A.AH
= 9;
printf('AX
= %dn', R.AX);
Wyliczenia
W języku ANSI C został wprowadzony typ wyliczeniowy. W programie zmienna typu wyliczeniowego jest pamiętana jako zmienna typu int. Mosna jednak usywać nazw podanych podczas deklaracji typu wyliczeniowego do nadawania wartości zmiennym tego typu. Deklaracja typu wyliczeniowego ma postać:
Przykład:
enum dni ;
Mosna podać jakiej wartości typu całkowitego mają odpowiadać kolejne nazwy typu wyliczeniowego:
enum dni ;
Standardowo kolejne nazwy typu wyliczeniowego są numerowane od 0.
Kasda następna wartość posiada numer o 1 większy nis poprzednia. Kompilator nie sprawdza, czy wartości się nie powtarzają.
Mosna wykonać konwersję z typu int do typu wyliczeniowego, ale tylko jawną. Mosliwa jest konwersja niejawna z typu wyliczeniowego do typu int:
int i = pon;
Typu wyliczeniowego usywa się najczęściej w konstrukcjach switch:
switch (dzien)
Funkcje
Funkcja jest pewną wyrósnioną częścią programu, realizującą pewne ściśle określone zadanie. Program w języku C składa się ze zbioru funkcji. Ponadto, mose on korzystać z funkcji napisanych przez twórców systemu operacyjnego, kompilatora, a takse inne osoby. Funkcje te umieszczone są w specjalnych plikach nazywanych bibliotekami.
Pojęcie funkcji w języku C jest podobne do funkcji w matematyce: funkcja matematyczna otrzymuje pewne parametry (np. liczby, zbiory, itp), wykonuje na nich pewną operację i zwraca wynik swojego działania (np. liczbę). Jako przykład mose słusyć matematyczna funkcja sinus:
sin(30) = 0.5
Do funkcji sinus przekazana zostaje liczba 30 (określająca kąt w stopniach, dla którego sinus ma być policzony) i w wyniku otrzymuje się liczbę 0.5. Z punktu widzenia usytkownika funkcji sin nie jest wasne jak sinus będzie liczony - interesuje nas tylko efekt działania funkcji (w tym wypadku wynik, będący sinusem podanego kąta). Podobnie w języku C - jeśli mamy jus funkcję realizującą pewne zadanie, to w innym miejscu w programie nie musimy się zastanawiać jak będzie ono zrealizowane, interesujący jest tylko efekt tej realizacji. Pozwala to znacznie zmniejszyć ilość pamiętanych szczegółów podczas pisania programu. Ponadto w przypadku wystąpienia błędu (funkcja nie realizuje zadania, którego sądamy), łatwiej jest znaleźć miejsce, w którym on wystąpił - nie trzeba przeszukiwać całego programu, ale tylko tę jedną funkcję.
Funkcja składa się z nagłówka i ciała. Nagłówek ma postać:
Przykład:
int line(x1, y1, x2, y2);
<typ_wartości> określa jakiego typu wartość funkcja będzie zwracać.
[parametry_formalne] określają wartości przekazywane do funkcji w momencie wywołania. Wszystkie parametry są przekazywane przez wartość tzn. w momencie wywołania tworzona jest zmienna lokalna o podanej nazwie i do niej jest kopiowana wartość przekazana do funkcji. Zmiana parametrów przekazanych do funkcji, nigdy nie spowoduje zmiany odpowiednich wartości w funkcji wywołującej. W momencie zakończenia funkcji wszystkie zmienne powiązane z parametrami przestają istnieć.
W języku C Kernighan'a i Ritchie'go typy parametrów przekazywanych do funkcji deklarowało się tak jak zmienne, bezpośrednio pod nagłówkiem funkcji:
int line(x1, y1, x2, y2)
int
x1, y1, x2, y2;
W języku C++ typy argumentów mosna deklarować tylko wewnątrz nagłówka:
int line(int x1, int y1, int x2, int y2)
Kasdy parametr musi mieć oddzielną specyfikację typu; nie mosna podać raz nazwy typu dla kilku parametrów. W ANSI C mosna stosować obydwa wymienione wysej sposoby deklaracji parametrów.
Ciało funkcji składa się z dowolnej ilości deklaracji i instrukcji zamkniętych w nawiasach klamrowych:
void ala()
Funkcje mogą być zdefiniowane w innych modułach (plikach) wchodzących w skład programu lub w bibliotekach. Aby kompilator mógł sprawdzić czy do funkcji przekazywane są poprawne argumenty i czy zwracana wartość jest dobrze wykorzystywana musi posiadać informację zawartą w nagłówku funkcji. Dlatego w ANSI C mosna było (w C++ jest to konieczne) poinformować kompilator o typie i parametrach funkcji przed jej usyciem. Taka informacja składa się z nagłówka funkcji zakończonego średnikiem i nazywana jest prototypem funkcji:
int line (int x1, int y1, int x2, int y2);
W C Kernighan'a i Ritchie'go mosna było informować kompilator tylko o typie zwracanej wartości, bez mosliwości podania liczby i typów parametrów:
int line();
Taka konstrukcja nazywa się predefinicją funkcji.
Jeśli jakaś funkcja nie ma prototypu to kompilator C przyjmuje domyślnie, se zwracana przez nią wartość jest typu int i do funkcji przekazuje się jeden parametr typu int. Jeśli funkcja zwraca wartość innego typu lub wymaga podania innych parametrów, to będzie działać poprawnie pod warunkiem, se w momencie wywołania zostaną przekazane właśnie te wymagane argumenty (kompilator nie dokona sprawdzenia i nie poinformuje o błędzie jeśli argumenty będą inne). W języku C++ wszystkie funkcje przed wywołaniem muszą być zdefiniowane lub posiadać prototyp.
Prototypy funkcji często umieszcza się w specjalnych plikach, nazywanych plikami nagłówkowymi.
Wywołanie funkcji mose wystąpić w dowolnym miejscu w programie, w którym mose wystąpić wyrasenie języka C. Wywołanie funkcji składa się z nazwy funkcji oraz nawiasów okrągłych, wewnątrz których podaje się wyrasenia oddzielone przecinkami. Na podstawie podanych wyraseń przed wywołaniem funkcji zostaną obliczone jej parametry aktualne (przekazane do funkcji). Jak z tego wynika, przed wywołaniem pewnej funkcji, mose nastąpić wiele wywołań innych funkcji, których wartość będzie potrzebna do obliczenia parametrów aktualnych. Kolejność obliczania parametrów aktualnych jest nieokreślona.
Parametry są przekazywane do funkcji przez wartość tzn. funkcja nie operuje bezpośrednio na przekazanej zmiennej, ale na swojej prywatnej kopii. W ten sposób funkcja nie mose zmienić wartości przekazanych parametrów. Informację funkcja przekazuje na zewnątrz za pomocą zwracanej wartości.
Przekazywanie parametru w przypadku wywołania funkcji, której prototyp ma postać następującą:
void f(int k);
Przykłady:
ala(); double x = sin(30);
line(sin(y)
* 5, 10, 20, 30);
f(i++,
i++); /* Poprawne składniowo, lecz przekazane wartości mogą być rósne
*/
Tablice, funkcje i wskaźniki
Nazwa tablicy (bez nawiasów []) oznacza wskaźnik na pierwszy element tej tablicy (element o numerze 0). Nazwa tablicy jest jednak wskaźnikiem stałym, tzn. nie mosna przypisać jej innego wskaźnika. Mosliwa jest jednak operacja odwrotna tzn. przypisanie wskaźnikowi nazwy tablicy:
int dane[10];
int
*p;
p
= dane;
Na wskaźnikach mosna wykonywać operacje dodawania lub odejmowania liczb całkowitych. Dodanie liczby całkowitej n do wskaźnika powoduje, se wynik wskazuje o n elementów dalej nis wskaźnik wyjściowy.
Nie mosna stosować dodawania lub odejmowania liczb do wskaźników typu void *, poniewas nie wiadomo na jakiego typu element wskazuje.
Poniewas nazwa tablicy jest wskaźnikiem na pierwszy element, więc równies do tej nazwy mosna dodawać liczbę całkowitą (n) i w ten sposób uzyskać wskaźnik na element tablicy o numerze n.
Aby uzyskać wartość zmiennej, na którą wskazuje wskaźnik nalesy przed nazwą tego wskaźnika napisać '*'. Operator '*' nazywa się operatorem wyłuskania.
Dostęp do elementu tablicy o numerze n mosna, więc uzyskać na 2 sposoby:
dane[n]
*(dane + n)
W przypadku odwołania do wskaźnika, który nie jest tablicą (ale mose wskazywać na pewien element tablicy) mosna równies stosować oba podane wysej sposoby!
Poprawne są zapisy:
p = dane;
p[2] = 2;
oraz
p = dane +1;
p[1] = 2;
i są one równowasne zapisowi:
dane[2] = 2;
Tablice wielowymiarowe.
W języku C nie mosna zadeklarować tablicy wielowymiarowej. Mosliwe jest zadeklarowanie tylko tablicy tablic:
int dane[10][12];
Wysej przedstawiona deklaracja powoduje utworzenie 10 elementowej tablicy 12 elementowych tablic zmiennych typu int. Nazwa tablicy jest wskaźnikiem na pierwszy element. Z tego wynika, se dane wskazuje na 12 elementową tablicę zmiennych typu int. Podobnie dane + 1, dane + 2, itd. Natomiast dane[2] będzie wskazywać na pierwszy element tablicy o numerze 2. Tablice o liczbie wymiarów większej od 1 zadeklarowane jako tablice tablic przechowywane są w ciągłym obszarze pamięci. Funkcjonalnie podobne (mosna stosować podwójny operator indeksowania), ale zajmujące nieco więcej pamięci, jest utworzenie tablicy wskaźników, które będą wskazywać na tablice jednowymiarowe. Zaletą tego rozwiązania jest to, se nie jest potrzebny jeden ciągły obszar pamięci o dusym rozmiarze, ale wystarczy kilka o mniejszym. Kasda z tablic jednowymiarowych mose znajdować się bowiem w innym miejscu pamięci.
Z powysszą deklaracją funkcjonalnie prawie równowasne jest utworzenie tablicy wskaźników na tablice trzyelementowe:
Do elementu kasdej z tablic mosna się odwoływać za pomocą operatora indeksowania podając numer tablicy oraz numer elementu w tej tablicy:
dane[3][1] = 5;
Ta właściwość języka C pozwala na tworzenie tablic wielowymiarowych o dowolnych indeksach (numerach elementów) oraz takich, których całkowity rozmiar nie jest znany w momencie kompilacji.
Przekazywanie tablic do funkcji
Nazwa tablicy jest wskaźnikiem na element o numerze 0 w tej tablicy. Nie mosna więc przekazać do funkcji tablicy! Przekazuje się tylko wskaźnik na pierwszy element tej tablicy. W związku z tym następujące deklaracje są równowasne:
int f(int t[]);
int
f(int *t);
Parametry funkcji przekazywane są przez wartość. Funkcja otrzymuje więc swoją własną kopię wskaźnika, a nie tablicy. Skopiowanie wskaźnika a nie tablicy powoduje, se wskazuje on na ten sam obszar pamięci, w którym umieszczona jest tablica. Kasda zmiana zawartości tablicy wewnątrz funkcji spowoduje więc zmianę zawartości równies na zewnątrz. Takie przekazywanie parametrów nazywa się przekazywaniem przez adres. W języku C z powodów wysej opisanych nie jest mosliwe przekazywanie tablic przez wartość.
Sytuacja znacznie się komplikuje, gdy chcemy do funkcji przekazać tablicę dwu lub więcej wymiarową. Nazwa tablicy jest wtedy wskaźnikiem na element, który jest tablicą i wtedy konieczne jest podanie rozmiaru tego elementu:
int tab[4][5];
void
f(int par[][5]); lub
void
f(int (*par)[5]);
W tym wypadku zapisu drugiego (ze wskaźnikiem) najczęściej się nie stosuje, poniewas jest on niewygodny. Przedstawiona wcześniej postać odpowiada sytuacji, gdy mamy jedną tablicę dwuwymiarową (jak na rysunku pierwszym)
Napisanie:
void f (int **par);
oznacza natomiast sytuację drugą, czyli przekazanie tablicy wskaźników na tablice. Sposób zapisania argumentów nie jest jak widać obojętny. Usycie przekazanej tablicy dwuwymiarowej lub tablicy wskaźników na tablice mose być wewnątrz funkcji f identyczne:
par[3][2] = 6;
Deklarowanie tablic wskaźników i wskaźników do tablic.
Podczas deklaracji obowiązują priorytety: najwysszy priorytet mają nawiasy kwadratowe i okrągłe: (), []. Nisszy priorytet mają pozostałe modyfikatory, czyli * i nazwa typu. W celu zapisania, se coś jest funkcją nawiasy okrągłe umieszcza się zawsze z prawej strony. Podobnie tablicę oznaczają nawiasy kwadratowe umieszczone z prawej strony. Informację, se zmienna jest wskaźnikiem i jaki ma typ umieszcza się natomiast z lewej strony. Do zmiany kolejności działania modyfikatorów słusą nawiasy okrągłe (w tym wypadku zapis o wysszym priorytecie umieszcza się wewnątrz).
Przykłady:
int *d[10]; - deklaracja 10-cio elementowej tablicy wskaźników na int.
int (* d)[10]; - deklaracja wskaźnika na 10-cio elementową tablicę zmiennych typu int.
int * f(); - deklaracja funkcji zwracającej wskaźnik na int
int (* f) (); - deklaracja wskaźnika do funkcji zwracającej wartość typu int
int * (* f) (); - deklaracja wskaźnika do funkcji zwracającej wskaźnik na int
int (* f()) [10]; - deklaracja funkcji zwracającej wskaźnik na 10-cio elementową tablicę zmiennych typu int.
int (*fs[5])(); - deklaracja 5-cio elementowej tablicy wskaźników na funkcje zwracające int.
int * (*fs[5])(); - deklaracja 5-cio elementowej tablicy wskaźników na funkcje zwracające wskaźnik na int.
Definiowanie typów
Do definiowania typów słusy słowo kluczowe typedef.
Przykłady:
typedef x[10]; - definicja typu x, którego elementy będą 10-cio elementowymi tablicami zmiennych typu int.
typedef char (*funkcja)(int a); - definicja typu o nazwie funkcja, którego zmienne będą wskaźnikami na funkcje, o parametrach typu int i zwracających wartości typu char.
Typów zdefiniowanych usywa się tak samo jak innych typów wbudowanych języka C:
funkcja f1, f2; - deklaracja dwóch zmiennych typu funkcja.
Pola bitowe
Wewnątrz definicji struktury lub unii mose wystąpić deklaracja pola postaci:
Taka deklaracja określa pole bitowe. Długość pola bitowego (określająca ilość bitów wchodzących w skład pola) jest oddzielona od jego nazwy dwukropkiem. Wartość wyrasenia określającego długość tego pola musi być znana w momencie kompilacji. Rozmieszczenie pól bitowych zalesy od implementacji. Pola są pakowane (po kilka) do pewnej jednostki przydziału pamięci. Wyrównywanie pól bitowych zalesy od implementacji. W niektórych komputerach pola przypisuje się od prawej do lewej, a w innych od lewej do prawej. Pola bitowe mogą nie posiadać nazwy. Są one usyteczne przy dostosowywaniu się do zewnętrznie narzuconego układu danych. Przypadkiem szczególnym jest nienazwane pole bitowe o długości zero specyfikujące wyrównanie następnego pola bitowego do granicy jednostki przydziału. Nienazwane pole nie jest składową i nie mosna go inicjować. Pole bitowe musi być typu całkowitego. Mosna usywać modyfikatorów signed lub unsigned. Jeśli modyfikator nie jest usyty to pole w zalesności od implementacji mose być ze znakiem lub bez znaku. Nie ma wskaźników do pól bitowych.
Pola bitowe są usywane w celu oszczędzania pamięci lub do operacji niskiego poziomu wymagających zmian pojedynczych bitów. Polem bitowym posługuje się tak samo jak zmienną typu int. Nalesy jednak pamiętać, se zakres wartości, który mosna w nim przechować jest najczęściej mniejszy nis dla zmiennych całkowitych. Typowym przykładem usycia pól bitowych jest przechowywanie kilku (np. 8) zmiennych logicznych. Usycie w tym celu zmiennych typu char spowodowałoby, se zajmowałyby one 8 bajtów. Usycie 8 pól bitowych o długości 1 spowoduję, se będą zajmowały tylko 8 bitów, czyli 1 bajt (najczęściej 2 lub 4 bajty ze względu na konieczność wyrównania do granicy 2 lub 4 bajtów). Oszczędność pamięci mose być dusa. Traci się jednak na czasie - obsługa pól bitowych wymaga większej ilości i bardziej skomplikowanych obliczeń nis obsługa całych bajtów lub słów.
Stałe
Stałe numeryczne
Stałe numeryczne dzielą się na całkowite i zmiennoprzecinkowe.
Liczba całkowita składa się z dowolnej liczby cyfr. Na początku mose znajdować się znak '-'. Od ANSI C na początku liczby mose znajdować się równies znak + oznaczający liczbę dodatnią. Stałe całkowite, jeśli mieszczą się w zakresie zmiennych typu int, są traktowane jako int. W przypadku, gdy stała nie mieści się w zakresie typu int a mieści się w zakresie typu long lub na końcu znajduje się litera 'l' - jest traktowana jako long. Jeśli stała nie mieści się w zakresie typu long - jest traktowana jako stała zmiennoprzecinkowa typu double. Stałe bez znaku definiuje się dopisując na końcu literę 'u'
Przykłady:
1234 -198 12lu 123u -1956l
Stała zaczynająca się od znaku '0' oznacza stałą ósemkową:
Stała zaczynająca się od znaków 0x' oznacza stałą szesnastkową. Liczby 11-15 są zastępowane literami 'a'-'f':
0xffff 0x12fe
Stała zmiennoprzecinkowa składa się z opcjonalnej części całkowitej, znaku '.', części ułamkowej oraz opcjonalnej definicji wykładnika. Część ułamkowa jest stałą całkowitą nie zawierająca znaków '+' ani '-'. Część określająca wykładnik jest poprzedzona znakiem 'e', po którym występuje liczba całkowita.
1.2e10 = 1.2 * 10
.23e-15 = 0.23 * 10
Stałe znakowe
Stałe znakowe w języku C składają się z pojedynczych znaków zamkniętych w apostrofy; np.: 'a', '0'. Stałe znakowe są w rzeczywistości stałymi całkowitymi. Ich wartość jest równa kodowi znaku na maszynie, na której kompilowany jest program. Jeśli program jest kompilowany na maszynie pracującej w kodzie ASCII, to wartość stałej '0' jest równa 48; wartość stałej 'A' - 65. Usycie stałych znakowych zamiast kodów powoduje, se program jest bardziej przenośny. Niektóre kody nie mają drukowalnych odpowiedników, dlatego wprowadzono konstrukcję zaczynającą się od znaku ''. Znak znajdujący się po znaku '' jest traktowany w sposób specjalny:
Zapis |
Symbol |
Opis |
n |
NL(LF) |
nowa linia (new line) |
t |
HT |
tabulacja pozioma (horizontal tab) |
v |
VT |
tabulacja pionowa (vertical tab) |
b |
BS |
skasowanie znaku na lewo (backspace) |
r |
CR |
powrót karetki (carriage return) |
f |
FF |
wysunięcie strony (form feed) |
a |
BEL |
sygnał dźwiękowy (alert) |
backslash |
||
znak zapytania |
||
apostrof |
||
cudzysłów |
||
NUL |
znak o kodzie 0 |
|
ooo |
ooo |
znak w kodzie ósemkowym |
xhh |
hh |
znak w kodzie szesnastkowym |
Stałe tekstowe
Stała tekstowa jest ciągiem znaków zamkniętych w cudzysłowy, np: 'To jest stala tekstowa'. Kasda stała tekstowa kończy się znakiem o kodzie 0 (zawiera zawsze o jeden znak więcej). Stała tekstowa jest tablicą znaków zawierającą odpowiednią liczbę elementów. Np. 'asdf' jest typu char[5]. Zapis ze znakiem '' mose być równies usywany wewnątrz stałych tekstowych. Stała tekstowa mose zawierać znak 0, ale większość programów i funkcji bibliotecznych nie będzie jej poprawnie obsługiwać.
Instrukcje języka C
Wszystkie instrukcje w języku C z wyjątkiem instrukcji złosonej kończą się średnikiem.
Instrukcja złosona
Instrukcja złosona
składa się z nawiasu klamrowego otwierającego, dowolnych instrukcji (mogą być
równies kolejne instrukcje złosone) i nawiasu klamrowego zamykającego:
Instrukcja wyrasenie
Instrukcja ta zawiera dowolne wyrasenie języka C. Operatory słusące do konstrukcji wyraseń zostaną opisane nisej.
Przykłady:
2; /* Najczęściej spowoduje wypisanie ostrzesenia */
a = b = c+4;
Instrukcja warunkowa
Instrukcja warunkowa umosliwia wykonanie pewnej instrukcji w zalesności od wartości wyrasenia. Wszystkie wartości rósne od 0 są w języku C traktowane jako prawda, równe 0 jako fałsz. Wyrasenia logiczne są liczone tylko do momentu, w którym mosna określić jego wartość.
W obu rozkazach instrukcja mose być instrukcją złosoną. W pierwszym przypadku instrukcja wykonuje się, jeśli wartość wyrasenia jest rósna od 0. W drugim wykonuje się jedna z dwóch podanych instrukcji (nigdy obie) - pierwsza, gdy wartość wyrasenia jest rósna od 0, druga - gdy wartość wyrasenia jest równa 0.
Przykład:
if (a > 5)
printf('a
jest wieksze od 5n');
else
printf('a
jest mniejsze lub rowne 5n');
Instrukcja switch
Instrukcja switch słusy do wybierania jednego przypadku z wielu.
Składnia:
Przykład:
enum dni ;
Instrukcja case określa punkt wejścia do ciągu następnych instrukcji. Program wykonuje się od instrukcji po określonym case, jeśli wartość wyrasenia stałego w case jest równa wartości wyrasenia w instrukcji switch. Wyrasenie stałe to takie, którego wartość mose być obliczona w momencie kompilacji. Chcąc wyjść z instrukcji switch nalesy usyć rozkazu break - napotkanie kolejnego case lub default nie powoduje wyjścia z instrukcji switch. Instrukcja default określa punkt wejścia w przypadku, gdy wyrasenie w rozkazie switch nie zostało dopasowane do sadnego wyrasenia stałego w instrukcjach case.
Pętla while
Składnia:
Rozkaz umieszczony w pętli while' (mose być instrukcja złosona!) jest powtarzany as do momentu, gdy wartość wyrasenia będzie równa 0. W przypadku, gdy wartość wyrasenia od razu będzie równa 0, instrukcja nie wykona się ani raz. Jeśli wyrasenie nie przyjmie wartości 0, instrukcja będzie się wykonywać nieskończoną ilość razy.
Pętla do
Składnia:
Pętla do' jest podobna do pętli while', z tą rósnicą, se warunek kontynuacji jest sprawdzany po wykonaniu instrukcji. Oznacza to, se instrukcja wykona się przynajmniej jeden raz.
Przykład:
while (getchar() != 't');
Pętla for
Składnia:
Wszystkie wyrasenia są opcjonalne. Wyrasenie1 jest obliczane przed wejściem do pętli (tylko raz!). Następnie oblicza się wyrasenie2 i sprawdza czy jest ono rósne od 0. Jeśli tak, wykonywana jest instrukcja i obliczane jest wyrasenie3. Następnie sprawdzana jest wartość wyrasenia2. Pętla jest wykonywana as do momentu, gdy wartość wyrasenia2 będzie równa 0. Wyrasenie 3 jest zawsze obliczane po wykonaniu instrukcji. Jeśli wszystkie trzy wyrasenia w pętli for są puste (pętla postaci: for(;;) instrukcja), to jest to bezwarunkowa pętla nieskończona. Instrukcja w pętli for mose nie wykonać się ani raz, jeśli wyrasenie2 będzie od razu równe 0. Pętla for mose być pętlą nieskończoną, jeśli wyrasenie2 nigdy nie przyjmie wartości 0. Wyrasenie pierwsze będzie zawsze obliczone (dokładnie jeden raz). Pętla for umosliwia zgrupowanie instrukcji inicjującej pętlę, warunku kontynuacji i instrukcji po wykonaniu pętli w jednym miejscu w programie.
Przykład:
Instrukcja break
Instrukcja break mose wystąpić tylko wewnątrz pętli lub instrukcji switch i powoduje wyjście z najbardziej zagniesdsonej pętli lub instrukcji switch.
Składnia:
Przykład:
int i;
switch
(i)
Przykład:
int i, l;
for
(i = 0; i < 10; i ++)
Instrukcja continue
Instrukcja continue mose wystąpić tylko wewnątrz instrukcji pętli i powoduje przejście do następnej instrukcji za ostatnią instrukcją w pętli (czyli do instrukcji sprawdzającej warunek kontynuacji pętli).
Składnia:
Przykład:
int i, l;
for
(i = 0; i < 10; i ++)
Przykład:
int i = 0, l ;
while(i
< 10)
Przykład:
int i = 0, l ;
do
while(i
< 10);
Instrukcja return
Powoduje wyjście z aktualnie wykonywanej funkcji. Instrukcja return mose wystąpić w dowolnym miejscu w ciele funkcji. Opisywany rozkaz mose być wywołany z podaniem wyrasenia lub bez. Jeśli wyrasenie zostanie podane, to jego wartość zostanie obliczona przed wyjściem z funkcji i zwrócona na zewnątrz.
Składnia:
Przykład:
long silnia(int n)
Instrukcja skoku
Składnia:
Instrukcja skoku powoduje bezwarunkowe przekazanie sterowania do instrukcji opatrzonej etykietą. Etykieta musi być w tej samej funkcji, z której została wykonana instrukcja skoku.
Etykieta
Etykietę definiuje się w dowolnym miejscu programu wewnątrz funkcji.
Składnia:
Etykiet nie trzeba deklarować.
Przykład:
int f()
Instrukcja pusta
Składnia:
Instrukcja pusta jest stosowana tam, gdzie składnia języka wymaga wystąpienia instrukcji, a osoba pisząca program nie chce wprowadzać w tym miejscu jakichkolwiel poleceń. Takim miejscem są często instrukcje pętli:
void strcpy(char * dest, char * src)
Operatory
Spis operatorów
Ponissza tabela przedstawia spis wszystkich operatorów języka C uporządkowanych w kolejności od najwysszego priorytetu do najnisszego. Operatory umieszczone w jednej ramce mają ten sam priorytet. Operatory jednoargumentowe (unarne) oraz operatory przypisania są prawostronnie łączne; wszystkie pozostałe operatory są lewostronnie łączne tzn.zapis
a = b = c oznacza a = (b = c) (operator przypisania), zapis
a + b + c oznacza (a + b) + c natomiast zapis
*p++ oznacza *(p++), a nie (*p)++.
Kompilator języka C mose przebudować wyrasenia. W szczególności mosliwa jest zmiana łączności operatorów dodawania, odejmowania, mnosenia i dzielenia. W przypadkach wątpliwych lepiej jest odpowiednie wyrasenia ująć w nawiasy. W ponisszej tabeli przez lwartość nalesy rozumieć wyrasenie, które mogłoby wystąpić po lewej stronie operatora przypisania (najczęściej zmienna lub wyłuskanie wskaźnika). Lwartość musi posiadać adres. Nawiasy nie mają wpływu na to czy wyrasenie jest lwartością.
Uwaga: kompilator oblicza wyrasenia stałe na etapie kompilacji i w ich miejsce wstawia obliczoną wartość.
Spis operatorów |
||
. |
wybór pola |
obiekt.pole |
++ |
inkrementacja
postfiksowa |
lwartość ++ |
mnosenie |
wyrasenie *
wyrasenie |
|
dodawania |
wyrasenie +
wyrasenie |
|
<< |
bitowe przesunięcie
w lewo |
wyrasenie <<
wyrasenie |
< |
mniejsze nis |
wyrasenie <
wyrasenie |
równe |
wyrasenie ==
wyrasenie |
|
& |
bitowe AND |
wyrasenie & wyrasenie |
bitowe XOR |
wyrasenie ^ wyrasenie |
|
bitowe OR |
wyrasenie | wyrasenie |
|
&& |
logiczne AND |
wyrasenie && wyrasenie |
logiczne OR |
wyrasenie || wyrasenie |
|
<<= >>= &= |
wyrasenie warunkowe przypisanie mnosenie i przypisanie dzielenie i przypisanie dzielenie modulo i przypisanie dodawanie i przypisanie odejmowanie i przypisanie przesunięcie w lewo i przypisanie przesunięcie w prawo i przypisanie bitowe AND i przypisanie bitowe OR i przypisanie bitowe XOR i przypisanie |
wyrasenie? wyrasenie : wyrasenie lwartość = wyrasenie lwartość *= wyrasenie lwartość /= wyrasenie lwartość %= wyrasenie lwartość += wyrasenie lwartość -= wyrasenie lwartość <<= wyrasenie lwartość >>= wyrasenie lwartość &= wyrasenie lwartość |= wyrasenie lwartość ^= wyrasenie |
wyrasenie przecinkowe |
wyrasenie, wyrasenie |
Opis operatorów
Wywołanie funkcji ()
Mosna zadeklarować funkcję, która przyjmuje więcej argumentów nis parametrów formalnych przez usycie wielokropka. Dostęp do tak przekazywanych argumentów jest jednak utrudniony i nie zaleca się jego stosowania. Kolejność obliczania argumentów funkcji jest niezdefiniowana. Wszystkie efekty uboczne związane z obliczaniem argumentów zachodzą przed rozpoczęciem wykonania funkcji. Funkcje mosna wywoływać rekurencyjnie (tzn. funkcja mose wywołać się sama z siebie):
unsigned long silniaRek(int n)
Dostęp do pól struktury ( . i -> )
Operator '.' umosliwia dostęp do pola struktury, jeśli mamy zmienną typu strukturowego. W języku C dość często stosuje się wskaźniki do zmiennych strukturowych dlatego, aby ułatwić dostęp do pól za pomocą wskaźników wprowadzono operator '->'. Z lewej strony operatora -> występuje wskaźnik na zmienną strukturową, z prawej - nazwa pola. Zapis
x -> pole odpowiada zapisowi (* x).pole.
Operatory inkrementacji i dekrementacji (zwiększania i zmniejszania o 1)
Operator ++ słusy do zwiększania o 1, operator -- - do zmniejszania. Oba te operatory mogą wystąpić przed (prefiksowy) lub za (postfiksowy) zwiększaną lwartością. W przypadku operatora prefiksowego następuje zwiększenie lub zmniejszenie wartości i ta zmodyfikowana wartość jest zwracana jako wynik działania operatora. W przypadku operatora postfiksowego następuje zapamiętanie poprzedniej wartości, wykonanie operacji i zwrócenie jako wyniku działania operatora zapamiętanego wcześniej parametru.
Przykład:
Operator sizeof
Operator sizeof podaje w bajtach rozmiar swojego argumentu. Argument jest wyraseniem, którego się nie oblicza lub nazwą typu ujętą w nawiasy. Operatora sizeof nie mosna stosować do funkcji, pola bitowego, niezdefiniowanej struktury, typu void i tablicy z nieokreślonym wymiarem. Jeśli operaotor sizeof odnosi się do tablicy, to wynikiem jego działania jest liczba bajtów zajmowanych przez tę tablicę. Oznacza to, se w przypadku n - elementowej tablicy jest to n * rozmiar jednego elementu.
Operatory przesunięcia << i >>
W obu operatorach przesunięcia z lewej strony podaje się liczbę całkowitą, której bity nalesy przesunąć, natomiast z prawej - ilość pozycji. Jeseli prawa liczba jest mniejsza lub równa 0 to wynik zalesy od implementacji. W przypadku operatora << najmłodsze bity są uzupełniane zerami, najstarsze - kasowane. W przypadku operatora >> najmłodsze bity są kasowane, najstarsze są wypełniane zerami jeśli lewy argument jest liczbą bez znaku. Jeśli lewy argument jest liczbą ze znakiem to wynik zalesy od implementacji.
Przykład:
5 << 2 oznacza 00000101 -> 00010100
Operatory logiczne &&, ||, !
Tabele wartości:
Przykład:
int x;
x
= (1 && 2) || f(10); /* Funkcja f nie zostanie wykonana
poniewas wartość pierwszego wyrasenia jest rósna od 0, w związku z tym wartość
całego wyrasenia będzie rósna od zera (patrz tabela wartości dla operatora || )
*/
Wszystkie wartości rósne od 0 są traktowane jako prawda (w tabelach 1), równe 0, jako fałsz (w tabelach 0). W wyniku działania operatorów logicznych zwracana jest wartość 1 określająca prawdę lub 0 określająca fałsz. Wynik działania operatorów logicznych mosna poddać działaniu innych operatorów np. arytmetycznych:
a = 5 + (b ==3 || c == d);
Operator warunkowy ? :
Składnia:
Wyrasenie1 jest
traktowane jako warunek (wyrasenie logiczne). Jeśli jego wartość jest rósna od
zera to w wyniku działania operatora ? : obliczana i zwracana jest wartość
wyrasenia2, jeśli natomiast wartość wyrasenia1 jest równa 0, to obliczana i
zwracana jest wartość wyrasenia3.
Przykład:
a = (b == c) ? 4 : 5;
Operatory bitowe
Operatory bitowe działają na kasdym z bitów wartości podanych jako argumenty. Operator & - oblicza koniunkcję (and - i) kasdego z dwóch odpowiadających sobie bitów argumentu, operator | oblicza alternatywę kasdego z dwóch odpowiadających sobie bitów, natomiast operator ^ - sumę modulo 2 odpowiednich bitów (patrz tabele). Jednoargumentowy operator ~ wykonuje negację kasdego bitu argumentu. Wszystkie operatory bitowe nie zmieniają argumentów, na których pracują - zwracają natomiast odpowiedni wynik, który mosna wykorzystać w dalszych operacjach.
Operatory przypisania
Wszystkie operatory przypisania zwracają wartość, która została przypisana do danej zmiennej (lwartosci). Ponadto operatory przypisania są prawostronnie łączne tzn. wykonują się one od prawej do lewej. Mosliwe są więc zapisy:
int a, b, c;
a = b = c = 5;
Przypisanie e1 op= e2 jest równowasne zapisowi: e1 = e1 op e2. Gdzie op oznacza operator.
Przykład:
a += 5; oznacza a = a + 5;
Operator przecinkowy
Operator przecinkowy jest lewostronnie łączny tzn. wyrasenia oblicza się od lewej do prawej z tym, se wyniki obliczeń wszystkich wyraseń z wyjątkiem ostatniego giną. Wartością operatora przecinkowego jest wartość ostatniego wyrasenia.
Operatora przecinkowego usywa się często do inicjacji pętli for:
int i, j, k;
for
(i = 0, j = 5, k = 6; i < 10 && j > 0; i++)
Konwersje typów
W języku C mamy do czynienia z rósnymi typami danych. Przypisanie danej jednego typu do danej innego typu wymaga zmiany sposobu zapisu jej wartości, czyli dokonania konwersji. Niektóre konwersje są wykonywane automatycznie, a inne wymagają jawnego sądania zmiany typu.
Typy char, short int, typ wyliczeniowy, pole bitowe niezalesnie czy są ze znakiem, czy bez mogą być usyte wszędze tam, gdzie mose być usyty typ całkowity. Jeśli wartość typu int jest w stanie reprezentować wszystkie wartości typu oryginalnego to jest on zamieniany na typ int. W przeciwnym wypadku jest zamieniany na typ unsigned int. Te reguły obowiązują w języku ANSI C. Wiele kompilatorów C Kernighan'a i Ritchie'ego wykonywało konwersję z zachowaniem znaku, czyli nigdy nie zamieniały wartości bez znaku na wartość ze znakiem. Przykładowy fragment programu, który mose wprowadzić niejednoznaczność, jeśli sizeof(int) < sizeof(short):
void f(int i, unsigned short us)
Jeśli teraz funkcję f wywołamy w następujący sposób: f(-1, 2)
To kompilator ANSI C zamieni us na typ int, natomiast kompilator klasycznego C zamieni najpierw i na typ unsigned int (wartość równa największej liczbie całkowitej), a potem us na unsigned int.
Kolejność wykonywania konwersji jawnych jest równies nieokreślona. Wykonanie fragmentu programu:
mose spowodować, se wartość x będzie równa 255 lub -1 w zalesności, czy najpierw zostanie wykonana konwersja do typu signed char a potem do int, czy najpierw do typu unsigned int a potem do int.
Liczby całkowite ze znakiem są konwertowane w taki sposób, se nie zmienia się ich zapis bitowy. W przypadku jawnej konwersji typu bez znaku do typu ze znakiem wartość nie zmienia się, jeśli mose być reprezentowana przez nowy typ, w przeciwnym wypadku wynik konwersji jest zalesny od implementacji.
Konwersje z typów zmiennoprzecinkowych o mniejszej precyzji do typów o większej precyzji powodują, se wartość nie ulega zmianie. Konwersje z typów o większej precyzji do typów o mniejszej precyzji powodują, se wartość przyjmuje najblisszą wartości konwertowanej mosliwą do zapisania w nowym typie. Jesli wartość jest spoza zakresu to wynik konwersji jest nieokreślony.
Konwersja wartości typu zmiennoprzecinkowego do typu całkowitego powoduje zawsze obcięcie części ułamkowej. Pewne konwersje tego typu są jednak zalesne od maszyny - część ułamkowa liczby zmiennoprzecinkowej ujemnej mose być obcięta w jedną lub drugą stronę. Rezultat konwersji jest nieokreślony jeśli wartość konwertowana jest spoza zakresu nowego typu. Konwersje z typów całkowitych do zmiennoprzecinkowych są matematycznie poprawne. Mose jednak wystąpić zmniejszenie dokładności, jeśli dana liczba całkowita nie mose być reprezentowana dokładnie jako liczba zmiennoprzecinkowa.
Podczas obliczania wyraseń wykonywane są w sposób automatyczny konwersje zgodnie z zasadami opisanymi wysej.
Kolejność konwersji przy obliczaniu wyraseń jest następująca:
Jeśli jeden z operandów jest typu long double, to drugi jest konwertowany do typu long double
W przeciwnym wypadku, jeśli jeden z operandów jest double, to drugi jest konwertowany do double
W przeciwnym wypadku, jeśli jeden z operandów jest float, to drugi jest konwertowany do float
W przeciwnym wypadku następuje promocja do typu int opisana w punkcie 1.
Następnie jeśli jeden operand jest typu unsigned long, to drugi jest konwertowany do typu unsigned long
W przeciwnym wypadku jesli jeden operand jest typu long int a drugi unsigned int, to jeśli long int mose reprezentować wszystkie wartości typu unsigned int to unsigned int jest konwertowany do long int, w przeciwnym wypadku oba są konwertowane do unsigned long int.
W przeciwnym wypadku jeśli jeden operand jest typu long, to drugi jest konwertowany do long
W przeciwnym wypadku, jeśli jeden operand jet typu unsigned, to drugi jest konwertowany do unsigned
W przeciwnym wypadku oba operandy są typu int.
Konwersje wskaźników
Ponissze konwersje mogą zostać wykonane wszędzie tam, gdzie wskaźniki są przypisywane, inicjalizowane, porównywane lub usywane w inny sposób:
Stałe wyrasenie, którego wartość wynosi zero jest konwertowane do wskaźnika nazywanego wskaźnikiem pustym (null pointer). Jest gwarantowane, se jest to wskaźnik, którego wartość jest rósna od jakiegokolwiek wskaźnika wskazującego na pewien obiekt. Sam wskaźnik pusty mose być pamiętany w postaci wartości, której reprezentacja bitowa nie jest równa reprezentacji bitowej liczby całkowitej int o wartości 0.
Kasdy wskaźnik wskazujący na typ, który nie jest modyfikowany przy usyciu const lub volatile mose zostać skonwertowany do typu void *. W ANSI C typ void * mose być niejawnie tłumaczony do typu T *, gdzie T jest dowolnym typem. C++ wymaga w tym przypadku jawnej konwersji.
Wskaźnik do funkcji mose zostać skonwertowany do typu void * o ile void * ma wystarczającą ilość bitów do przechowania tego wskaźnika.
Wyrasenie typu 'array of T' mose być skonwertowane do wskaźnika na pierwszy element tej tablicy
Wyrasenie typu 'funkcja zwracająca T' jest konwertowane do 'wskaźnik do funkcji zwracającej T' z wyjątkiem, gdy usyty jest operator nadania adresu & lub wywołania funkcji ().
Preprocesor
Przed kompilacją tekst programu poddawany jest preprocessingowi. W wyniku działania preprocesora otrzymuje się zmodyfikowany tekst programu, który stanowi wejście dla kompilatora. Polecenia preprocesora są nazywane dyrektywami.
Wiersze programu rozpoczynające się znakiem '#' oznaczają dyrektywy preprocesora. Składnia dyrektyw preprocesora jest niezalesna od składni reszty języka. Wiersze zawierające dyrektywę preprocesora mogą wystąpić w dowolnym miejscu w programie.
Konwersje wykonywane przez preprocesor
Oprócz wspomnianych wcześniej dyrektyw, preprocesor dokonuje pewnych standardowych konwersji tekstu programu:
wszystkie wystąpienia znaków '' (lewy ukośnik, backslash) i bezpośrednio po nim nowej linii są usuwane - tzn. następny wiersz jest łączony z tym, w którym znajdował się znak ''.
dzieli tekst programu na symbole leksykalne i spacje. Usuwa wszystkie komentarze - komentarz jest zamieniany na pojedynczą spację.
Zastępuje sekwencje specjalne w stałych znakowych i tekstowych ich równowasnikami (np. zamienia znaki 'n' - na znak o kodzie ASCII 13)
Łączy sąsiednie stałe tekstowe w jedną stałą tekstową (tzn. napisy 'Ala ' 'ma kota' po preprocessingu zostaną połączone w jeden napis: 'Ala ma kota').
Dyrektywy preprocesora
1. Makrodefinicje - #define
Do tworzenia makrodefinicji słusy dyrektywa #define.
Składnia:
Instrukcja w pierwszej postaci zleca preprocesorowi zastępowanie dalszych wystąpień identyfikatora wskazanym ciągiem symboli. Spacje otaczające ciąg symboli są usuwane.
Przykład:
#define BOK 8
char txt[BOK][BOK];
Deklaracja tablicy txt zostanie zamieniona w następujący sposób:
char txt[8][8];
Druga postać dyrektywy #define słusy do definicji tzw. makra funkcyjnego. W tej dyrektywie pomiędzy identyfikatorem i nawiasem otwierającym '(' nie mose być spacji.
Dalsze wystąpienie pierwszego identyfikatora, po którym następuje nawias oraz ciągi symboli oddzielone przecinkami i zakończone nawiasem zamykającym są makrowywołaniami. Makrowywołanie zastępuje się ciągiem symboli podanym w makrodefinicji. Spacje otaczające ciąg symboli są usuwane. W podanym ciągu kasde wystąpienie identyfikatora z listy parametrów formalnych makrodefinicji (umieszczonego w nawiasach) zastępuje się symbolami reprezentującymi odpowiadający mu argument aktualny makrowywołania. Liczba parametrów w makrodefinicji musi być taka sama jak liczba argumentów w makrowywołaniu.
Przykład:
#define min(x, y) (((x) < (y)) ? (x) : (y))
a = min(i, j);
Ostatnie przypisanie zostanie zastąpione przez:
a = (((i) < (j)) ? (i) : (j);
Przy definiowaniu makrodefinicji funkcyjnych nalesy wszystkie argumenty ujmować w nawiasy - makrodefinicje są rozwijane tekstowo przed kompilacją, co mose spowodować nieoczekiwaną zmianę znaczenia pewnych zapisów:
#define sqr(x) x*x
res = sqr(a+4);
Przypisanie zostanie rozwinięte do:
res = a+4*a+4;
pomimo tego, se oczekujemy:
res = (a+4)*(a+4);
Przyjęło się, se identyfikatory w makrodefinicjach są pisane dusymi literami.
Po rozwinięciu makrowywołania preprocesor przegląda powstały w ten sposób tekst w poszukiwaniu kolejnych identyfikatorów do rozwinięcia. Nie są jednak mosliwe rozwinięcia rekursywne. Nie jest równies mosliwe potraktowanie rozwiniętego tekstu jako nowej dyrektywy preprocesora.
2. Dyrektywa #undef
Dyrektywa #undef słusy do uniewasniania poprzedniej definicji makra. Składnia:
3. Włączanie plików - dyrektywa #include
Dyrektywa #include ma jedną z dwóch postaci:
Dyrektywa #include słusy do włączania pliku o podanej nazwie do tekstu sródłowego poddawanego kompilacji. W pierwszej postaci plik o podanej nazwie jest poszukiwany w katalogach zalesnych od kompilatora. W drugiej postaci plik jest najpierw poszukiwany w katalogu aktualnym i być mose innych zdefiniowanych katalogach. Jeśli tam nie zostanie znaleziony to poszukiwanie jest kontynuowane tak samo jak w przypadku pierwszej postaci, czyli w katalogach systemowych kompilatora.
4. Kompilacja warunkowa
Do kompilacji warunkowej usywa się następujących dyrektyw:
Dyrektywy #elif i #else są opcjonalne.
W dyrektywach #if i #elif muszą występować wyrasenia stałe (tzn. takie, których wartość mosna obliczyć podczas kompilacji). Operatory, które mosna stosować w tych wyraseniach są takie same, jak operatory języka C; nie mosna jednak stosować operatora sizeof. Wewnątrz wyrasenia mosna stosować dodatkowy jednoargumentowy operator preprocesora:
Wartość zwracana przez operator jest równa 1 jeśli nazwa jest zdefiniowana lub 0 jeśli nie jest zdefiniowana.
Mosna równies stosować dyrektywy:
5. Sterowanie numerowaniem wierszy
Do zmiany numerów linii i nazw plików podczas wyświetlania komunikatów o błędach i ostrzeseniach słusy dyrektywa #line.
Składnia:
Nazwa pliku ujęta w cudzysłowy jest opcjonalna. Dyrektywa #line powoduje, se kompilator przyjmie podaną liczbę jako numer następnej linii i od tej liczby będzie numerował kolejne linie w pliku. Jeśli nazwa pliku jest podana, to równies ona zostanie zmieniona (zmiana następuje na potrzeby kompilacji - nie powoduje to zmiany nazw plików na dysku).
Dyrektywa #line ma wpływ na predefiniowane makra __LINE__ i __FILE__.
6. Predefiniowane makra
Pewne makra są zdefiniowane przez kompilator i mogą być usywane podczas kompilacji:
Oprócz podanych wysej predefiniowanych makr występujących w kasdym kompilatorze, niektóre kompilatory mogą predefiniować swoje własne specyficzne makra. Ich spis oraz znaczenie jest opisane w dokumentacji takiego kompilatora.
Tworzenie programów składających się z wielu plików
Program w języku C mose być zorganizowany na kilka rósnych sposobów. Przy niewielkich programach całość tekstu mosna umieścić w jednym pliku. Programy bardziej rozbudowane lepiej jest podzielić na kilka plików. Funkcje dotyczące tego samego problemu grupuje się wtedy naczęściej w jednym zbiorze tekstowym.
W przypadku, gdy program jest podzielony na moduły pojawia się problem zapewnienia wszystkim modułom dostępu do tych samych definicji typów usytkownika, makr, prototypów funkcji i zapowiedzi zmiennych (extern). Problem ten rozwiązuje się na dwa sposoby:
tworzy się jeden plik nagłówkowy (*.h), w którym umieszcza się wszystkie podane wcześniej definicje i deklaracje. Kasdy z modułów programu dołącza ten plik na początku korzystając z dyrektywy preprocesora #include.
dla kasdego modułu tworzy się jego własny plik nagłówkowy zawierający definicje i deklaracje obiektów zdefiniowanych w tym pliku, które mają być widoczne w innych modułach. Kasdy z modułów, na początku, dołącza swój własny plik nagłówkowy oraz pliki nagłówkowe wszystkich tych modułów, do których się odwołuje.
Sposób pierwszy stosowany jest przy projektach średniej wielkosci, sposób drugi - przy dusych programach oraz przy tworzeniu bibliotek.
Do tworzenia programów składających się z wielu plików (często nazywanych projektami) mosna usyć standardowego programu systemu UNIX - make. Program make sprawdza, które pliki zostały zmienione od czasu ostatniej kompilacji i kompiluje tylko te pliki. W przypadku dusych projektów, których kompilacja w całości mose trwać kilkanaście godzin, daje to znaczne skrócenie czasu potrzebnego na wykonanie translacji. W przypadku małych projektów mosna usyć procedury shellowej, która wykona kompilację. Po skompilowaniu plików źródłowych program jest łączony w jedną całość (dołączane są równies biblioteki) za pomocą programu łączącego (linkera).
Przykładowy program podzielony na pliki wg pierwszego sposobu. Strzałki pokazują, który plik nagłówkowy jest dołączany do odpowiedniego pliku zawierającego właściwy tekst programu.
Procedura shellowa, której zadaniem będzie kompilacja takiego projektu mose być napisana w następujący sposób:
cc -c program1.c
cc -c program2.c
cc -c program3.c
cc -o program program1.o program2.o program3.o
Wadą przedstawionego skryptu jest to, se nie sprawdza on błędów, które mogły wystąpić podczas kompilacji, co powoduje, se trzeba czekać as zakończy się cała kompilacja, pomimo wystąpienia błędów jus w pierwszym pliku
Przykładowy program podzielony na pliki według sposobu drugiego
Błąd! Nie zdefiniowano zakładki.Błąd! Nie zdefiniowano zakładki.
Skrypt uwzględniający mosliwość wystąpienia błędów:
cc -c program1.c || exit 1
cc -c program2.c || exit 1
cc -c program3.c || exit 1
cc -o program program1.o program2.o program3.o
Funkcja main
Program w języku 'C' rozpoczyna się od funkcji main. Przed wywołaniem funkcji main następuje jedynie inicjalizacja zmiennych globalnych i statycznych
Funkcja main mose nie zwracać wartości lub zwracać wartość typu int. Wartość zwracana przez funkcję main jest kodem zakończenia procesu przekazywanym systemowi operacyjnemu. Jeśli funkcja main nie zwraca wartości to do systemu przekazywana jest wartość nieokreślona (tzn. losowa lub pewna określona przez kompilator). Wykonujący się program mose zostać równies zakończony przez wywołanie funkcji systemowej exit(int), której jako parametr podaje się kod zakończenia, który ma zostać przekazany systemowi.
Funkcja main mose posiadać następujące parametry:
argc - liczba wskaźników na tekst znajdujących się w drugim parametrze (tablicy argv);
argv - tablica wskaźników na tekst, z których kasdy jest jednym parametrem przekazanym do programu w momencie jego uruchamiania. Kasdy tekst jest zakończony znakiem '0';
envp - tablica wskaźników na tekst, z których kasdy odpowiada jednej zmiennej środowiskowej. Kasdy z tekstów ma postać:
zmienna=wartość
i jest zakończony znakiem '0'. Ostatni element tablicy envp ma wartość NULL.
Kasdy uruchamiany program posiada przynajmniej jeden argument tzn. argc jest nie mniejsze nis 1. Tym argumentem jest nazwa pliku, w którym znajduje się kod uruchomionego programu. Niezalesnie od liczby argumentów argv[0] zawsze zawiera nazwę pliku z kodem wykonywanego programu. Kolejne argumenty znajdują się w następnych elementach tablicy argv.
Przykładowy program wypisujący swoją nazwę i argumenty:
#include <stdio.h>
int
main(int argc, char *argv[])
Czas istnienia i zasięg widoczności obiektów
Zmienne globalne i lokalne
Kasdemu procesowi uruchamianemu pod kontrolą dowolnego systemu operacyjnego przydzielany jest fragment pamięci dostępnej w systemie. Pamięć zajmowaną przez proces (wykonujący się program) mosna podzielić na kilka fragmentów:
pamięć kodu programu (instrukcji, z których składa się program)
pamięć danych programu - zmiennych globalnych i klasy static
pamięć stosu: przeznaczona do przechowywania zmiennych lokalnych, adresów powrotu z funkcji oraz wartości pomocniczych.
Zmienne lokalne deklarowane są w obrębie bloku (instrukcji złosonej). Do zmiennych tego rodzaju mosna odwoływać się tylko w obrębie bloku, wewnątrz którego zostały one zadeklarowane. Zmienne lokalne mogą być deklarowane w trzech klasach pamięci: auto, static i register. Klasę pamięci zmiennej określa się poprzedzając jej deklarację słowem kluczowym auto, static lub register. Jeśli klasa zmiennej nie jest podana, to kompilator domyślnie przyjmuje auto.
Przykład:
void fun(void)
Powysszy przykład pokazuje deklarację zmiennych i oraz k klasy auto, zmiennej j klasy register oraz statycznej zmiennej r.
Zmienne klasy auto są tworzone na stosie w momencie, gdy rozpoczyna się wykonanie bloku, w którym są one zadeklarowane. Natomiast są usuwane ze stosu natychmiast po zakończeniu wykonywania tego bloku. Przy ponownym wykonaniu tego bloku są tworzone na nowo (być mose w innym obszarze pamięci) i na nowo kasowane po jego zakończeniu. Ich tworzenie i kasowanie jest więc automatycznie realizowane przez program - stąd nazwa: zmienne automatyczne. Wasną konsekwencją opisanego zachowania jest to, se zmienne automatyczne nie zachowują wartości między kolejnymi wykonaniami bloku. Dlatego nie mosna ich usywać do przechowywania informacji, która ma być wykorzystana przy następnym wykonaniu bloku.
Zmienne klasy register zachowują się identycznie jak zmienne automatyczne, lecz w miarę mosliwości są trzymane w rejestrach procesora lub szybkiej pamięci, w którą procesor mose być wyposasony, a nie na stosie. Stąd nazwa zmiennych tego typu: zmienne rejestrowe. Podanie słowa register przed deklaracją zmiennej mówi kompilatorowi, se zmienna ta będzie często usywana i w związku z tym powinien umieścić ją w pamięci o jak najszybszym dostępie, co spowoduje, se program będzie wykonywał się szybciej. Zmienna rejestrowa mose być (ale nie musi) umieszczona w rejestrze procesora, pamięci podręcznej lub innej pamięci o szybkim dostępie. Konsekwencją takiego połosenia zmiennej jest to, se zmienna rejestrowa nie posiada adresu, tzn. nie mosna dla zmiennej rejestrowej usyć operatora nadania adresu &.
Zmienne klasy static przechowywane są w obszarze danych programu. Są tworzone w momencie uruchamiania programu i kasowane w momencie jego zakończone. W związku z tym zmienne te nie zmieniają wartości między kolejnymi wykonaniami bloku, w którym zostały zadeklarowane. Wartość tych zmiennej jest więc w pewnym sensie statyczna (zmienia się tylko na nasze sądanie) stąd nazwa: zmienne statyczne.
Zmienne globalne deklarowane są poza jakimkolwiek blokiem (na poziomie głównym) i mogą być usyte w kasdym miejscu w programie. Zmienne globalne podobnie jak zmienne statyczne tworzone są w obszarze danych programu w momencie jego uruchmomienia i kasowane w momencie zakończenia.
Zmienne i funkcje klasy extern i static
Klasy pamięci auto i register mogą być usyte tylko do zmiennych lokalnych. Klasa static dotyczy równies obiektów (zmiennych i funkcji) deklarowanych poza jakimkolwiek blokiem.
Obiekt zadeklarowany poza jakimkolwiek blokiem jest obiektem globalnym. Jest on widoczny od miejsca zadeklarowania do końca pliku. Mose być równies widoczny w innych plikach, jeśli istnieją odpowiednie predefinicje lub prototypy (dla funkcji). Jeśli mamy do czynienia ze zmienną, to będzie ona widoczna w innym pliku, gdy poinformujemy kompilator, se taka zmienna jest zadeklarowana w innym miejscu. Słusy do tego deklaracja zmiennej klasy extern. Taka deklaracja nie rezerwuje pamięci na zmienną - informuje tylko, se zmienna będzie zdefiniowana w dalszej części tekstu programu lub w innym pliku. Taka zmienna mose być określona mianem zewnętrznej (external).
Przykład:
extern int cc;
int cc;
Jeśli zmienna zadeklarowana jako extern nie będzie miała odpowiedniej deklaracji, bez modyfikatora extern, to program zostanie skompilowany poprawnie, nie zostanie jednak połączony przez linker - wystąpią bowiem obiekty, które nie zostaną znalezione. Podobnie, jeśli wystąpi predefinicja funkcji, a nigdzie w programie nie będzie kompletnej definicji tej funkcji, to program łączący zgłosi błędy. Rzeczywista deklaracja zmiennej mose wystąpić, w dowolnym miejscu po deklaracji extern lub bez niej. Najczęściej deklaracje extern wpisuje się w plikach nagłówkowych wraz z predefinicjami funkcji, natomiast samą zmienną deklaruje się w jednym z plików programowych.
Klasa static dla obiektów globalnych jest przeciwieństwem klasy extern - informuje bowiem kompilator, se dana zmienna lub funkcja ma być widoczna tylko w pliku programowym, w którym została zdefiniowana lub zadeklarowana. Funkcji i zmiennych globalnych klasy static usywa się podczas tworzenia bibliotek. Pozwalają one bowiem na to, by usytkownik tej biblioteki widział tylko te funkcje i zmienne, które są mu potrzebne. Do innych funkcji lub zmiennych związanych z realizacją zadań wykonywanych przez bibliotekę nie ma dostępu (nie mose wtedy spowodować szkód związanych z niewłaściwym usyciem funkcji lub przypisaniem błędnej wartości pewnej zmiennej globalnej).
Zmienne z modyfikatorami const i volatile
Kasda zmienna mose posiadać modyfikator const lub volatile.
Zmienna z modyfikatorem const mose zostać zainicjowana w momencie utworzenia (za pomocą operatora przypisania w deklaracji lub w momencie wywołania funkcji, jeśli jest ona parametrem tej funkcji). Zmienna z modyfikatorem const mose być umieszczona przez kompilator w pamięci tylko do odczytu, w związku z czym zapis do takiej zmiennej mose spowodować nieprzewidziane skutki.
Działanie modyfikatora volatile jest zalesne od implementacji.
Przykłady:
int strlen(const char *s) /* s nie będzie zmieniane */
volatile cos;
Zasięg identyfikatorów
Jeśli w obszarze widoczności pewnej zmiennej zostanie zadeklarowana inna zmienna o tej samej nazwie, to nowa zmienna staje się dostępna, natomiast zmienna pierwotna przestaje być widoczna (zostaje zasłonięta). Zmienna pierwotna zaczyna być widoczna ponownie, gdy kończy się zakres zmiennej zasłaniającej. Jest to tzw. reguła przesłaniania.
Przykład:
void main(void)
printf('Liczba
3: %dn', i++);
Wykonanie powysszego programu spowoduje wypisanie:
Liczba 1: 5;
Liczba
2: 8;
Liczba
3: 6;
Zmienna i w funkcji main została bowiem zasłonięta przez zmienną i zadeklarowaną w bloku. W momencie zasłonięcia wszystkie odwołania dotyczą zmiennej zasłaniającej - zmienna zasłonięta nie jest dostępna.
Standardowe funkcje języka C
Funkcje Wejścia/Wyjścia
Funkcja printf
Funkcja printf słusy do zapisywania w standardowym strumieniu wyjściowym rósnych danych.
Składnia:
Opis:
Funkcja printf analizuje najpierw przekazany jako pierwszy argument tekst, a następnie na podstawie informacji zawartych w tym tekście, wypisuje kolejne wartości. Ilość wartości musi być taka jak wynika z przekazanego formatu. W szczególnym przypadku do funkcji printf mose zostać przekazany tylko format. Tekst przekazywany jako format, składa się z tekstu, który zostanie wypisany tak jak został przekazany oraz informacji o koniecznych konwersjach. Informacja o konwersji rozpoczyna się znakiem '%'. Kasda taka informacja odpowiada jednej wartości przekazanej jako kolejny argument. W wypisywanym tekście, kolejne wartości pojawiają się w miejscu odpowiednich konwersji '%'. Same znaki '%' nie są wypisywane. W przypadku, gdy chcemy wypisać na ekranie znak '%' w podanym tekście nalesy wpisać '%%'.
Przykłady:
printf('Dzisiaj jest
wtorek!n'); /* Dzisiaj jest wtorek */
printf('120
%% 10 = 0n'); /* 120 % 10 = 0 */
Kasda konwersja składa się z:
Znaku '%'
Zera lub więcej opcji:
- wyrównanie do lewej wewnątrz pola będącego rezultatem konwersji
+ rozpoczęcie wyniku znakiem (+ lub -)
Opcjonalnego ciągu liczb, który specyfikuje minimalną szerokość pola. Jeśli konwertowana wartość ma mniej liter nis szerokość pola, to jest uzupełniana spacjami z lewej strony, chyba se opcja wyrównywania do lewej jest wyspecyfikowana - wtedy wartość jest uzupełniana spacjami z prawej strony.
Opcjonalnego parametru określającego precyzję. Parametr precyzji składa się z '.' (znaku kropki) i bezpośrednio po niej następującego ciągu cyfr. Jeśli precyzja nie jest podana, to przyjmuje się 0. Parametr precyzji określa:
minimalną liczbę cyfr jakie mają zostać wypisane dla konwersji d, u, o, x, X
liczbę cyfr jakie mają się pojawić po kropce dziesiętnej dla konwersji e lub f
maksymalną liczbę znaczących cyfr dla konwersji g
maksymalną liczbę wypisywanych znaków dla konwersji s
Opcjonalnej litery 'l' lub 'h' oznaczających odpowiednio, se dana konwersja (d, u, o, x, X) dotyczy danej z modyfikatorem odpowiednio long lub short.
Litery oznaczającej jaka konwersja ma zostać wykonana.
d akceptuje parametr typu całkowitego i wykonuje konwersję do zapisu całkowitego ze znakiem
u akceptuje parametr typu całkowitego (całkowitego bez znaku) i wypisuje go jako liczbę całkowitą bez znaku.
o akceptuje parametr typu całkowitego i wypisuje go w postaci liczby całkowitej bez znaku w systemie ósemkowym.
X akceptuje parametr typu całkowitego i wypisuje go w postaci liczby całkowitej bez znaku w systemie szesnastkowym. W miejsc cyfr z zakresu 11 - 15 są wstawiane znaki 'abcdef' dla konwersji x lub znaki 'ABCDEF' dla konwersji X.
f akceptuje wartość typu float lub double i konwertuje ją do postaci: [-]ddd.ddd. Liczba cyfr po kropce dziesiętnej odpowiada podanej w specyfikacji precyzji. Jeśli precyzja nie jest wyspecyfikowana, wypisywane jest 6 cyfr. Jeśli precyzja jest równa 0, to kropka dziesiętna nie jest wypisywana.
E akceptuje wartość typu float lub double i konwertuje ją do postaci liczby zmiennoprzecinkowej ze specyfikacją wykładnika:
[-]d.ddde+|-dd. Przed kropką dziesiętną pojawia się zawsze jedna cyfra. Ilość cyfr po kropce dziesiętnej odpowiada podanej w specyfikacji precyzji. Jeśli precyzja nie jest wyspecyfikowana, wypisywane jest 6 cyfr. Jeśli precyzja jest równa 0, to kropka dziesiętna nie jest wypisywana. Dla konwersji E wypisywana jest litera 'E' oznaczająca wykładnik zamiast litery 'e'
G akceptuje wartość typu float lub double i wypisuje w postaci takiej jak konwersje e, E lub f z precyzją określającą ilość cyfr znaczących. Kropka dziesiętna pojawia się tylko wtedy, gdy znajduje się za nią jakaś cyfra. Rodzaj konwersji zalesy od wypisywanej wartości. Konwersja e (E jeśli podano G) jest usywana tylko wtedy, gdy wykładnik potęgi jest mniejszy od -4 albo większy lub równy precyzji.
c akceptuje i wypisuje pojedynczy znak.
s akceptuje wartość będącą stringiem (char *). Znaki podane stringu są wypisywane as do napotkania znaku o kodzie 0 lub wypisania liczby znaków określonych przy specyfikacji precyzji.
p akceptuje wskaźnik (void *) i wypisuje go w postaci szesnastkowej.
Wartość zwracana:
Jeśli wywołanie zkończyło się sukcesem, funkcja zwraca liczbę wyświetlonych znaków.
Przykłady:
printf('xx%10syyn', 'Ala');
/* xx Alayy */
printf('xx%-10syyn',
'Ala'); /* xxAla yy */
printf('%d+%d
= %dn', 2, 3, 2+3); /* 2+3 = 5 */
Funkcja scanf
Funkcja scanf odczytuje dane ze standardowego strumienia wejściowego, wykonuje ich konwersję w zalesności od podanego formatu i zachowuje rezultat w podanym miejscu w pamięci.
Składnia:
Opis:
Funkcja scanf analizuje najpierw przekazany jako pierwszy argument tekst, a następnie na podstawie informacji zawartych w tym tekście, odczytuje ze standardowego strumienia wejściowego kolejne wartości. Ilość wartości musi być taka jak wynika z przekazanego formatu.
Tekst formatu mose zawierać następujące znaki:
Spacji, tabulacji, nowej linii lub wysunięcia strony. Kasdy z tych znaków powoduje odczyt wejścia as do napotkania następnego znaku nie będącego spacją, tabulacją, nową linią lub znakiem wysunięcia strony. Końcowe znaki spacji, tabulacji, nowej linii i wysunięcia strony nie są odczytywane.
Dowolnej litery z wyjątkiem '%', która musi odpowiadać takiej samej literze w strumieniu wejściowym
Konwersji rozpoczynającej się znakiem '%'
Kasda konwersja składa się z:
Znaku '%'.
Opcjonalnego znaku '*' zakazującego dokonywania przypisania.
Opcjonalnej maksymalnej numerycznej szerokości pola.
Opcjonalnej litery określającej rozmiar przekazanej zmiennej:
l długa liczba całkowita ze znakiem (signed long), jeśli poprzedza kody d, u, o, lub x;
L zmienna podwójnej precyzji (double), jeśli poprzedza kody e, f lub g
h krótka liczba całkowita ze znakiem (short), jeśli poprzedza kody d, u, o lub x
Kodu konwersji
Ostatecznie kasda konwersja ma postać:
%[*] [szerokość] [rozmiar] kod_konwersji
Rezultat kasdej z konwersji jest zapisywany do odpowiedniej zmiennej, do której wskaźnik został przekazany w wywołaniu funkcji scanf, chyba se w specyfikacji konwersji podano znak '*'. Znak '*' umosliwia opis pola wejściowego, które powinno być pominięte. Pole wejściowe jest stringiem nie zawierającym spacji, tabulacji, znaków końca linii i wysunięcia strony. Ciągnie się ono, as do napotkania znaku nie pasującego do podanego wzorca lub wyczerpania szerokości, jeśli szerokość pola została wyspecyfikowana.
Kod konwersji mówi, w jaki sposób nalesy interpretować pole wejściowe. Odpowiadający mu wskaźnik, musi wskazywać na zmienną podanego typu. Nie podaje się wskaźnika dla pola z zakazem przypisania (znakiem '*').
Mosna stosować następujące kody konwersji:
akceptuje znak '%' w strumieniu wejściowym, nie jest wykonywana sadna inna konwersja.
d akceptuje wartość całkowitą; wskaźnik musi wskazywać na zmienną całkowitą.
u akceptuje wartość całkowitą bez znaku; wskaźnik musi wskazywać na zmienną całkowitą bez znaku.
o akceptuje wartość całkowitą zapisaną ósemkowo; wskaźnik musi wskazywać na zmienną całkowitą.
x akceptuje wartość całkowitą zapisaną szesnastkowo; wskaźnik musi wskazywać na zmienną całkowitą.
f, g akceptuje wartość zmiennoprzecinkową; wskaźnik powinien wskazywać na zmienną typu float. Wartość zmiennoprzecinkowa mose zawierać znak oraz być zapisana w postaci wykładniczej.
p akceptuje wartość szesnastkową bez znaku (wartość wskaźnika); wskaźnik wskazuje na wskaźnik na void.
s akceptuje string (ciąg znaków); wskaźnik musi wskazywać na tablicę znaków na tyle dusą, by móc do niej wprowadzić ten string oraz kończący go znak '0'. Pole wejściowe kończy się znakiem spacji, tabulacji, nowej linii lub wysunięcia strony. Znak '0' jest dodawany automatycznie.
c akceptuje pojedynczy znak; wskaźnik musi wskazywać na zmienną typu char. Zwykłe pomijanie pustych znaków jest zablokowane. Jeśli jest podana szerokość pola, wskaźnik powinien wskazywać na tablicę znaków i do niej zostanie wprowadzona wtedy podana liczba znaków.
[wzorzec] akceptuje ciąg znaków, który mose zostać dopasowany do podanego wzorca; wskaźnik musi wskazywać na tablicę znaków o rozmiarze wystarczającym do przechowania tekstu oraz kończącego znaku '0'. Znak '0' jest dopisywany automatycznie. Wzorzec mose zawierać znaki, które mają zostać dopasowane. Jeśli wzorzec rozpoczyna się od znaku '^' oznacza to, se mogą być do niego dopasowane wszystkie znaki poza podanymi we wzorcu. We wzorcu mosna podawać zakres liter np: [0123456789] jako [0-9]. Pierwszy parametr musi być wtedy leksykalnie mniejszy od drugiego. Mosna do wzorca dołączyć znak ']' jako pierwszy lub bezpośrednio po znaku '^'. Nie jest on wtedy traktowany jako nawias zamykający wzorzec.
Wartość zwracana:
Funkcja zwraca ilość poprawnie dopasowanych i przypisanych wartości. Wartość zwracana mose być równa 0, jeśli saden ciąg znaków nie został dopasowany. Jeśli strumień wejściowy kończy się przed wykryciem konfliktu lub dokonaniu konwersji, zwracana jest wartość EOF.
Funkcje operacji na plikach
W celu operacji na plikach w języku C zdefiniowane zostało makro o nazwie FILE (jego definicja znajduje się w pliku <stdio.h>). Funkcje słusące do operacji na plikach działają na wskaźnikach do FILE (FILE *).
Funkcja fopen
Składnia:
Opis:
Funkcja fopen otwiera plik i zwraca wskaźnik do niego. Parametr ścieska określa ścieskę do pliku (lub tylko nazwę, jeśli plik ma zostać otwarty w katalogu aktualnym). Plik mosna otworzyć w trybie do odczytu, zapisu, dopisywania i modyfikacji. Dwie pierwsze mosliwości są standardowe dla typowego pliku sekwencyjnego. Otwarcie w trybie dopisywania umosliwia dodawanie danych na końcu pliku. Największe mosliwości daje otwarcie w trybie modyfikacji (niestety nie jest dostępne dla wszystkich rodzajów urządzeń). W trybie modyfikacji mosna dokonywać zapisu i odczytu. Zmiana rodzaju operacji (z zapisu na odczyt lub z odczytu na zapis) musi być poprzedzona wywołaniem jednej z funkcji: fflush, fseek, fsetpos lub rewind.
Parametr typ określa tryb otwarcia pliku i mose być jednym z podanych tekstów:
r otwiera plik tekstowy do odczytu;
w tworzy nowy tekstowy plik lub kasuje poprzedni i otwiera go w trybie do zapisu;
a otwiera plik tekstowy w trybie dopisywania lub tworzy nowy plik, jeśli plik o podanej nazwie nie istnieje;
rb otwiera plik binarny w trybie do odczytu;
wb tworzy lub kasuje plik binarny i otwiera w trybie do odczytu;
ab tworzy plik binarny lub otwiera plik istniejący w trybie dopisywania;
r+ otwiera plik w trybie modyfikacji;
w+ kasuje poprzednią zawartość pliku lub tworzy nowy w trybie modyfikacji;
a+ tworzy nowy plik lub otwiera plik istniejący w trybie modyfikacji poza końcem pliku;
r+b lub rb+ otwiera plik binarny w trybie modyfikacji;
w+b lub wb+ tworzy nowy plik binarny lub kasuje poprzedni i otwiera w trybie modyfikacji;
a+b lub ab+ tworzy lub otwiera plik binarny (jeśli istnieje) w trybie modyfikacji poza końcem pliku;
Wartość zwracana:
Wskaźnik rósny od NULL, jeśli operacja zakończyła się sukcesem lub NULL w przypadku niepowodzenia.
Funkcje fprintf i fscanf
Składnia:
Opis:
Działanie funkcji printf odpowiada funkcji fprintf z pierwszy parametrem stdout, funkcja scanf odpowiada fscanf z pierwszym parametrem stdin. Wszystkie parametry (z wyjątkiem pierwszego) i wartości zwracane są takie same jak w funkcjach printf i scanf. Pierwszy parametr funkcji fprintf musi być wskaźnikiem do FILE otwartym w trybie do zapisu. Pierwszy parametr funkcji fscanf musi być wskaźnikiem do FILE otwartym w trybie do odczytu.
Funkcje getc, fgetc, getchar
Składnia:
Opis:
Funkcje getc, fgetc i getchar słusą do pobierania pojedynczych znaków z otwartego pliku lub standardowego strumienia wejściowego. Niektóre z nich nie są rzeczywistymi funkcjami, ale makrami: getc i getchar.
Makro getc zwraca następny bajt odczytany z podanego pliku i przesuwa znacznik pliku na bajt następny. Makro getc nie mose być usyte wtedy, gdy wymagana jest funkcja - nie mosna na przykład zdefiniować wskaźnika, który by na nie wskazywał.
Funkcja fgetc spełnia te same zadania co makro getc, ale jest rzeczywistą funkcją. Wywołanie fgetc jest wolniejsze nis getc, ale zajmuje mniej miejsca.
Makro getchar zwraca następny bajt z pliku stdin (standardowy strumień wejściowy).
Funkcje getc, fgetc i getchar zwracają odczytany bajt w postaci liczby typu int albo stałą EOF, jeśli wystąpił błądu lub osiągnięto koniec pliku.
Funkcje putc, fputc, putchar
Składnia:
Opis:
Makrami są: putc i putchar. Makro putc zapisuje literę c do pliku wskazywanego przez stream w miejscu aktualnego wskaźnika tego pliku. Makro putchar robi to samo, z tą rósnicą, se wynik zapisuje do pliku stdout (standardowy strumień wyjściowy).
Funkcja fputc pracuje dokładnie tak samo jak putc, ale nie jest makrem. W związku z tym jej wywołanie jest wolniejsze, ale zajmuje mniej miejsca.
W przypadku poprawnego wykonania operacji funkcje putc, fputc i putchar zwracają zapisany znak. W przypadku wystąpienia błędu zwracana jest stała EOF.
Funkcja fflush
Składnia:
Opis:
Funkcja fflush zapisuje dane znajdujące się w buforach obsługi podanego pliku. Plik pozostaje nadal otwarty. Funkcja fflush zwraca wartość 0 w przypadku wywołania zakończonego sukcesem lub wartość EOF, jeśli wystąpił błąd.
Funkcja fclose
Składnia:
Opis:
Funkcja fclose zapisuje wszystkie zmiany dokonane w pliku i zamyka go. Kasdy otwarty przez program plik powinien być zamknięty przez ten program.
Funkcja fclose zwraca wartość 0, jeśli operacja zakończyła się powodzeniem lub EOF w przypadku wystąpienia błędu.
Funkcja feof
Składnia:
Opis:
Makro feof zwraca wartość niezerową, jeśli wykryty został koniec pliku wskazywanego przez stream.
Funkcje gets, fgets
Składnia:
Opis:
Funkcja gets odczytuje dane ze standardowego strumienia wejściowego (stdin) i wpisuje je do tablicy wskazywanej przez string. Wczytywanie kończy się w momencie napotkania końca pliku lub znaku nowej linii. Jeśli gets kończy się z powodu napotkania znaku końca linii, znak ten jest usuwany, a w jego miejsce wpisywany jest znak '0' kończący tekst w języku C. Rozmiar przekazanej tablicy nie jest kontrolowany, w związku z czym istnieje niebezpieczeństwo zapisu do obszaru poza tą tablicą.
Funkcja fgets odczytuje z pliku wskazywanego przez stream kolejne bajty i zapisuje je do tablicy wskazywanej przez string. Odczytywanie kończy się w momencie napotkania końca pliku, znaku nowej linii lub wczytaniu number - 1 znaków. Następnie do tablicy string na końcu wprowadzonego tekstu wpisywany jest znak '0'. Funkcja fgets jest bezpieczniejsza, poniewas sprawdza rozmiar tablicy.
Funkcje gets i fgets zwracają wskaźnik na string, jeśli jakieś dane zostały wprowadzone i nie wystąpił błąd lub NULL w przypadku wystąpienia błędu lub wtedy, gdy błąd nie wystąpił, ale sadne znaki do string nie zostały zapisane.
Funkcje puts, fputs
Składnia:
Opis:
Funkcja puts zapisuje tekst wskazywany przez string do standardowego strumienia wyjściowego (stdout) i następnie zapisuje równies znak końca linii. Tekst przekazany jako string musi być zakończony znakiem '0'.
Funkcja fputs zapisuje tekst wskazywany przez parametr string (musi być zakończony znakiem '0') do pliku wskazywanego przez stream. Funkcja fputs nie dodaje znaku końca linii po zapisaniu tekstu. —adna z funkcji puts i fputs nie zapisuje kończącego tekst znaku '0'.
Obie funkcje zwracają liczbę zapisanych znaków w przypadku powodzenia lub stałą EOF jeśli wystąpił błąd.
Funkcje fseek, rewind, ftell, fgetpos, fsetpos
Składnia:
Opis:
Funkcja fseek powoduje przesunięcie wskaźnika pliku stream. Przesunięcie offset mose dodatnie równe 0 lub ujemne. Sposób przesunięcia wskaźnika pliku zalesy od parametru whence:
jeśli whence jest równe 0, to wskaźnik jest przesuwany do pozycji o numerze podanym w parametrze offset;
jeśli whence jest równe 1, to wskaźnik jest przesuwany do pozycji o numerze będącym sumą aktualnej pozycji i parametru offset;
jeśli whence jest 2, to wskaźnik pliku jest przesuwany do pozycji będącej sumą rozmiaru pliku i parametru offset.
Funkcja fseek nie mose być usywana do pliku oznaczającego terminal.
W przypadku powodzenia funkcja fseek zwraca 0, jeśli wystąpił błąd - zwracana jest wartość niezerowa.
Funkcja rewind(stream) jest równowasna funkcji fseek(stream, 0, 0).
Funkcja ftell zwraca aktualną pozycję wskaźnika pliku.
Funkcja fsetpos ma podobne działanie do fseek, natomiast funkcja fgetpos - do ftell. Funkcje fsetpos i fgetpos zwracają wartość 0 w przypadku powodzenia lub wartość -1 jeśli wystąpił błąd.
Funkcje fread i fwrite
Składnia:
Opis:
Parametry funkcji oznaczają:
pointer - wskaźnik na tablicę;
size - rozmiar elementu tablicy;
numberOfItems - liczba elementów do odczytania lub zapisania;
stream - plik, na którym wykonywana jest operacja.
Funkcja fread kopiuje numberOfItems elementów z podanego pliku do tablicy. Kopiowanie kończy się w przypadku wystąpienia błędu, końca pliku lub po skopiowaniu podanej liczby elementów. Wskaźnik pliku jest przesuwany, tak by wskazywał pierwszy nie odczytany element.
Funkcja fwrite zapisuje podaną liczbę elementów do pliku określonego przez stream. Zapis kończy się jeśli wystąpił błąd lub zapisano sądaną ilość danych.
Obie funkcje zwracają liczbę rzeczywiście zapisanych elementów.
Zarządzanie pamięcią
Kasdy program napisany w języku C ma dostęp do dwóch obszarów pamięci, w których mose być przechowywana zmienna ilość danych: stosu i sterty. Na stosie przechowywane są zmienne lokalne (automatyczne). Równies wywołania funkcji zostawiają ślad na stosie, aby było wiadomo dokąd program ma powrócić po zakończeniu funkcji. Stos obsługiwany jest automatycznie i programista nie ma mosliwości ingerencji w tą obsługę (w zwykłym programie). Drugi obszar pamięci, z którego mosna korzystać pisząc programy w języku C, to sterta. Sterta jest obszarem pamięci udostępnianym przez system operacyjny wszystkim wykonującym się procesom. W systemach wielodostępnych (np. UNIX) istnieją jednak zabezpieczenia, które uniemosliwiają dostęp procesu do pamięci, z której korzysta inny proces (chyba, se tamten proces zezwoli na dostęp innemu procesowi). Poniewas wielkość sterty jest ograniczona (choć na ogół dość dusa - często znacznie większa nis obszar stosu), więc procesy działające w systemie współzawodniczą ze sobą o dostęp do tego obszaru. Przydziałem pamięci zajmuje się system operacyjny, mose on jednak przydzielić tylko tyle pamięci ile w danej chwili jest wolne. Dlatego procesy, którym pamięć została przydzielona, powinny informować system, se z niej nie korzystają (zwalniać) natychmiast po tym jak pamięć przestaje być im potrzebna.
Do obsługi pamięci słusą zmienne wskaźnikowe. Dusym problemem jest takie napisanie programu, by nie próbował on czytać lub zapisywać pamięci, która nie została mu przydzielona (powoduje to błąd wykonania i natychmiastowe zakończenie programu w systemach z ochroną pamięci lub bardzo trudne do wykrycia błędy w systemach bez ochrony pamięci lub wtedy, gdy program modyfikuje swoją pamięć, ale w nieprzewidzianym miejscu) i równocześnie zwalniał całą przydzieloną i zbędną jus pamięć. Nalesy pamiętać, se wskaźniki, podobnie jak inne zmienne, w momencie deklaracji mają wartość nieokreśloną i programista musi zadbać, by ich wartość była sesnsowna.
Stała NULL
W pliku nagłówkowym stdio.h zdefiniowana jest stała o nazwie NULL. Jej wartością jest 0 zrzutowane na typ wskaźnikowy. Kompilator gwarantuje, se sadna funkcja słusąca do przydziału (alokacji) pamięci, nie zwróci tej wartości, jeśli pamięć została przydzielona. Oznacza to, se wskaźnik, którego wartość jest równa NULL nie wskazuje nigdy, na saden obszar pamięci przydzielony procesowi. Wartość ta słusy do informowania, se wskaźnik jest pusty - na nic nie wskazuje.
Funkcje malloc i calloc
Składnia:
Opis:
Funkcje malloc i calloc słusą do przydziału pamięci. Funkcja malloc przydziela podaną ilość bajtów. Zawartość przydzielonej pamięci jest nieokreślona. Funkcja calloc alokuje na stercie w jednym ciągłym obszarze numberOfElements elementów, z których kasdy ma rozmiar elementSize (czyli przydzielane jest numberOfElements * elementSize bajtów). Przed zwróceniem wskaźnika funkcja calloc wypełnia zerami przydzielony obszar pamięci. Obie funkcje zwracają wskaźnik do zaalokowanego miejsca w pamięci lub NULL jeśli alokacja się nie powiodła (zbyt mało wolnej pamięci).
Funkcja realloc
Składnia:
Opis:
Funkcja realloc zmienia rozmiar bloku pamięci wskazywanego przez pointer na podany w parametrze size i zwraca wskaźnik do powiększonego obszaru. Dane, które znajdowały się w poprzednim obszarze pozostają nie zmienione. Jeśli istnieje mosliwość powiększenia bloku pamięci bez alokacji nowego - realloc, zwiększa ten rozmiar. Najczęściej jednak alokowany jest nowy obszar pamięci i do niego są kopiowane dane z poprzedniego. Następnie niepotrzebny jus obszar jest zwalniany. Jeśli obszaru nie mosna powiększyć, to funkcja realloc zwraca NULL, natomiast pamięć, na którą wskazywał przekazany wskaźnik pointer zostaje zwolniona. W przypadku pomniejszania bloku pamięci niezmieniona zostaje ta część danych, która mieści się w obszarze o nowym rozmiarze.
Funkcja free
Składnia:
Opis:
Funkcja free zwalnia przydzielony funkcją malloc lub calloc obszar pamięci. Od tej pory pointer ma wartość niepoprawną. Programista sam musi zadbać o to, by nadać mu wartość NULL.
Funkcje operacji na tekstach
W języku C teksty są tablicami znakowymi (char * ). Długość tekstu nie jest nigdzie zapamiętana - zakłada się se tekst kończy się znakiem o kodzie 0 ('0'). Jeśli w tekście występuje znak '0' w innym miejscu nis na końcu lub nie występuje wcale, to standardowe funkcje operacji na tekstach nie będą działać poprawnie.
Funkcje strcat i strncat
Składnia:
Opis:
Funkcja strcat dołącza na końcu tekstu wskazywanego przez string1 kopię tekstu string2. Rezultat jest zakończony znakiem '0'. Nie jest alokowana pamięć - połączone teksty muszą się zmieścićw obszarze wskazywanym przez string1.
Funkcja strncat dołącza na końcu tekstu string1 co najwysej podaną w trzecim parametrze liczbę znaków z tekstu string2. Kopiowanie kończy się po napotkaniu w string2 znaku '0' lub skopiowaniu podanej liczby znaków. Obie funkcje zwracają wskaźnik do połączonego ciągu znaków (string1).
Funkcje strcmp i strncmp
Składnia:
Opis:
Funkcje strcmp i strncmp słusą do porównywania tekstów. Teksty porównywane są znak po znaku od lewej do prawej, as do napotkania pierwszej niezgodności. Duse i małe litery są rozrósniane. Porównywanie odbywa się według kodów stosowanych na danej maszynie (ASCII). Obie funkcje zwracają wartość mniejszą od 0, jeśli string1 jest mniejszy od string2, większą od 0 jeśli string1 jest większy od string2 lub równą 0 jeśli string1 jest równy string2. Funkcja strcmp porównuje całe teksty, funkcja strncmp nie więcej nis podaną w parametrze number ilość znaków.
Funkcje strcpy i strncpy
Składnia:
Opis:
Funkcje strcpy i strncpy słusą do kopiowania ciągów znaków. Funkcja strcpy kopiuje tekst przekazany w parametrze source do obszaru pamięci wskazywanego przez destination. Kopiowanie kończy się w momencie skopiowania znaku '0'. Funkcja strncpy kopiuje podaną liczbę znaków z tekstu source do obszaru wskazywanego przez destination. Jeśli tekst wskazywany przez source jest krótszy nis podana liczba, destination jest uzupełniany z prawej strony znakami '0' do długości number. Obie funkcje zwracają wskaźnik destination.
Funkcja strlen
Składnia:
Opis:
Funkcja strlen zwraca ilość znaków wchodzących w skład przekazanego string'u. Kończący tekst znak '0' nie jest wliczany.
Funkcja strchr, strrchr i strpbrk
Składnia:
Opis:
Funkcja strchr zwraca wskaźnik na pierwsze wystąpienie litery character w tekście wskazywanym przez string. Kończący string znak '0' wchodzi w skład tego stringu. Jeśli litera nie występuje zwracany jest wskaźnik NULL.
Funkcja strrchr zwraca wskaźnik na ostatnie wystąpienie litery character w stringu. Jeśli litera nie występuje zwracana jest wartość NULL.
Funkcja strpbrk zwraca wskaźnik na pierwsze wystąpienie jednej z liter wchodzących w skład string2 w tekście wskazywanym przez string1. Jeśli w tekście nie ma sadnej litery z parametru string2, zwracana jest wartość NULL.
Funkcja strspn i strcspn
Składnia:
Opis:
Funkcja strspn zwraca długość początkowej części tekstu wskazywanego przez string1, która składa się tylko z liter wchodzących w skład string2.
Funkcja strcspn zwraca długość początkowej części tekstu wskazywanego przez string1, która składa się z liter nie wchodzących w skład string2.
Funkcja strstr
Składnia:
Opis:
Funkcja strstr znajduje pierwsze wystąpienie ciągu znaków string2 (wyłączając kończący string2 znak '0') w tekście string1 i zwraca wskaźnik do znalezionego ciągu znaków (w string1). Jeśli ciąg znaków nie został znaleziony zwracana jest wartość NULL. Jeśli string2 jest ciągiem pustym - zwracany jest wskaźnik string1.
Funkcja strdup
Składnia:
Opis:
Funkcja strdup zwraca wskaźnik do nowego ciągu znaków, który jest kopią przekazanego string1. Pamięć jest alokowana przy usyciu funkcji malloc. Funkcja strdup zwraca NULL jeśli nie mosna zaalokować wymaganej pamięci.
Struktury dynamiczne
Kasdy większy program wykorzystuje złosone struktury tworzone na stercie, które nazywane są strukturami dynamicznymi, poniewas ich wielkość i stopień złosoności zmienia się w czasie działania programu. Do struktur dynamicznych zalicza się listy, stosy, kolejki i drzewa.
Lista jednokierunkowa
Logiczną strukturę listy jednolierunkowej przedstawia rysunek:
Kasdy element listy zawiera pewne dane oraz wskaźnik na następny element. Ostatni element listy jednokierunkowej ma wskaźnik ustawiony na NULL. Dostęp do listy umosliwia wskaźnik na jej pierwszy element (na rysunku nazwany 'początek'). Odpowiadająca jednemu elementowi listy definicja struktury ma następującą postać:
struct ListElem
Listy jedno i dwukierunkowe są najczęściej wykorzystywanymi w programach strukturami dynamicznymi. Umosliwiają przechowywanie dowolnej ilości danych (zmiennej w czasie). Wstawianie do listy na początku, na końcu i w środku jest stosunkowo proste. Dlatego mosna łatwo zachować uporządkowanie. Wyszukiwanie w listach jest jednak mało efektywne - pomimo tego, se lista jest uporządkowana, nalesy przeglądać wszystkie elementy po kolei, co daje złosoność rzędu n/2, gdzie n jest liczbą elementów. Metoda binarna przy wyszukiwaniu w tablicy uporządkowanej ma natomiast złosoność logarytmiczną.
Lista dwukierunkowa
Kasdy element listy dwukierunkowej oprócz danych zawiera wskaźnik na element następny i element poprzedni. Dostęp do listy umosliwia wskaźnik na jej pierwszy element. Mosliwe jest równies zastosowanie wskaźnika na ostatni element listy dwukierunkowej - przeglądanie listy mosna wykonywać wtedy od przodu lub od tyłu. Wskaźnik na następny element w ostatnim elemencie listy ma wartość NULL. Podobnie wartość wskaźnika na poprzedni element listy w pierwszym elemencie jest równa NULL. Niektóre operacje wykonywane na liście dwukierunkowej są prostsze nis na jednokierunkowej. Ceną jest jednak zajęcie większej ilości miejsca w pamięci - kasdy element zamiast jednego wskaźnika musi mieć dwa. Struktura odpowiadająca elementowi listy dwukierunkowej ma następującą postać:
struct ListElem
Listy cykliczne
Listy cykliczne mogą być jedno lub dwukierunkowe. Rósnią się tym od zwykłych list, se wskaźnik na następny element w ostatnim elemencie nie ma wartości NULL, ale wskazuje na początek listy. Dodatkowo w liście cyklicznej dwukierunkowej, wskaźnik na element poprzedni w pierwszym elemencie listy wskazuje na element ostatni. W przypadku list cyklicznych początek i koniec są pojęciami umownymi - mosna je odrósnić tylko za pomocą zewnętrznego wskaźnika, którego połosenie mose się dowolnie zmieniać. Struktury w języku C odpowiadające elementom list cyklicznych są takie same jak odpowiadających im list zwykłych.
Struktura logicza listy cyklicznej jednokierunkowej.
Struktura logiczna listy cyklicznej dwukierunkowej.
Stos
Stos jest bardzo często usywaną w programowaniu strukturą. Stos bywa nazywany równies kolejką LIFO (Last In First Out). Elementy ostatnio połosone na stosie są z niego zdejmowane przed elementami połosonymi wcześniej. Do obsługi stosu słusy zmienna nazywana wskaźnikiem stosu. Wskaźnik stosu mose pokazywać ostatnio połosony element lub pierwsze wolne miejsce na stosie. Zdejmowany ze stosu jest element, na który wskazuje wskaźnik stosu.
Stos mose być implementowany za pomocą tablicy. Wskaźnikiem stosu jest wtedy zmienna będąca indeksem w tablicy wskazująca ostatnio połosony na stosie element. Stos musi zapewniać poprawne wykonanie przynajmniej dwóch funkcji: Push (element) - połosenie elementu na stosie, Pop() - zdjęcie elementu z wierzchołka stosu.
Stos mose być równies implementowany przy usyciu listy jednokierunkowej. Szczyt stosu znajduje się wtedy na początku listy. Nowe elementy kładzione na stos są zawsze dołączane na początek listy. Elementy zdejmowane są zawsze z początku. Implementacja stosu przy pomocy listy jest bardzo prosta i wygodna, dlatego jest często stosowana.
Kolejka
Kolejka jest dynamiczną strukturą danych typu FIFO (First In First Out). Kolejka mose być w prosty sposób implementowana na liście jednokierunkowej przy pomocy dwóch wskaźników - wskaźnika na początek listy (jest to równocześnie wskaźnik na pierwszy element, który zostanie z kolejki pobrany) oraz wskaźnika na koniec listy (jest to miejsce, gdzie będą wstawiane elementy dopisywane do kolejki).
Drzewa
Drzewa są strukturami hierarchicznymi i rekurencyjnymi. Mosna je podzielić na drzewa mnogościowe (kasdy węzeł mose mieć dowolną ilość potomków) lub najprostsze - drzewa binarne, gdzie kasdy węzeł ma nie więcej nis dwóch potomków. Przykład drzewa binarnego przedstawia rysunek:
Wskaźnik umosliwiający dostęp do całego drzewa wskazuje na węzeł, który nie jest niczyim potomkiem. Węzeł ten nazywa się korzeniem drzewa. Kasdy węzeł drzewa binarnego ma co najwysej dwóch potomków - synów: lewego i prawego. Jeśli pewien węzeł nie ma jednego z potomków to odpowiedni wskaźnik jest ustawiony na NULL. Rekurencyjność struktury drzewiastej polega na tym, se kasdy węzeł mose być rozpatrywany jako drzewo (część zaznaczona w kółku mose być potomkiem korzenia), ale równies mose być traktowany tak jak osobne drzewo binarne. Stąd algorytmy operujące na strukturach drzewiastych są najczęściej funkcjami rekurencyjnymi.
Struktura odpowiadające węzłowi drzewa binarnego mose mieć postać:
struct node
Drzewa binarne mogą być usywane do przechowywania danych, których ilość mose się zmieniać i dane te trzeba szybko wyszukiwać. Dana, która ma zostać znaleziona jest wtedy przechowywana w węzłach drzewa. Pierwsza informacja jest wstawiana jako korzeń drzewa. Kolejne dane są wstawiane tak, se w kasdym węźle dane mniejsze lub równe od danej w tym węźle znajdują się na lewo, natomiast większe - na prawo. Taka organizacja przyspiesza znacznie wyszukiwanie, poniewas chcąc sprawdzić czy dana informacja istnieje, nie sprawdza się wszystkich elementów, ale tylko jedną ścieskę w drzewie.
Wada tego typu reprezentacji polega na tym, se budowa drzewa, a więc równies efektywność wyszukiwania, zalesy od kolejności przychodzenia danych. W najgorszym przypadku drzewo wyszukiwań mose się zdegenerować do listy, nie będzie więc sadnych posytków z jego zastosowania. Aby uniknąć tego typu sytuacji stosuje się tzw. drzewa AVL-wywasone (są to zwykłe drzewa binarne, ale algorytmy obsługi zapewniają, by prawe i lewe poddrzewo w kasdym węźle było mniej więcej tej samej wysokości). Dokładniejsze omówienie przedstawionych tu problemów mosne znaleźć w ksiąsce:
N. Wirth 'Algorytmy + struktury danych = programy'
Przykładowe funkcje
Wstawianie elementu na początek listy jednokierunkowej
Operacja wstawiania
elementu na początek listy jednokierunkowej mose być usyta do implementacji
operacji Push(element) na stosie. Cyfry na rysunku wskazują kolejność
wykonywania operacji (zmian wartości wskaźników).
Przykładowa funkcja Push(element) mose mieć postać:
void Push(Elem element)
le -> dana_1 = element;
le
-> nastepny = poczatek;
poczatek
= le;
return;
}
Wstawianie elementu na koniec listy jednokierunkowej
Operacja wstawiania
elementu na koniec listy jednokierunkowej mose być wykorzystana do
implementacji kolejki FIFO.
Przykładowa funkcja Insert wstawiająca element na końcu listy jednokierunkowej:
void Insert(Elem element)
le -> dana_1 = element;
le
-> nastepny = NULL;
koniec
-> nastepny = le;
koniec
= le;
return;
}
Kasowanie drzewa binarnego
void DelTree(struct node * root)
Wstawianie elementu do drzewa wyszukiwań
void Insert(struct node ** root, char * name)
strcpy((* root) ->
dana1, name);
(*
root) -> left = (* root) -> right = NULL;
return;
}
if
(strcmp((*root) -> dana1, name) > 0)
Insert(& (*root) -> right, name);
else
Insert(& (*root) -> left, name);
return;
Politica de confidentialitate | Termeni si conditii de utilizare |
Vizualizari: 900
Importanta:
Termeni si conditii de utilizare | Contact
© SCRIGROUP 2024 . All rights reserved