Jezyk ANSI C Programowanie Wydanie II jansic


Język ANSI C.
Programowanie. Wydanie II
Autorzy: Brian W. Kernighan, Dennis M. Ritchie
Tłumaczenie: Paweł Koronkiewicz
ISBN: 978-83-246-2578-9
Tytuł oryginału: C Programming Language (2nd Edition)
Format: 158×235, stron: 328
Drogi Czytelniku, właSnie trzymasz w rękach nowe wydanie książki zaliczanej do klasyki
literatury informatycznej. Napisana przez autorów języka ANSI C w najlepszy możliwy
sposób przedstawia arkana tego języka. A co można powiedzieć o samym języku? To
też klasyka. To język wymagający systematycznoSci i skupienia, ale dający w zamian
wiele możliwoSci i Swietne wyniki. To najczęSciej nauczany język programowania  jego
znajomoSć stanowi znakomity fundament do poznania kolejnych, bardziej złożonych
języków. Mimo swojego zaawansowanego wieku jest on ceniony i w wielu dziedzinach
wciąż niezastąpiony.
Dzięki tej książce zdobędziesz kompletną wiedzę na temat języka C. Poznasz wszystkie
dostępne typy, operatory i wyrażenia. Nauczysz się sterować wykonywaniem programu
oraz wykorzystywać funkcje. Ponadto dogłębnie poznasz coS, co sprawia początkującym
programistom najwięcej problemów  wskaxniki. Następnie zapoznasz się także
z funkcjami wejScia i wyjScia. Dowiesz się, jak uzyskać dostęp do plików, formatować
dane wyjSciowe oraz obsługiwać błędy. Książka ta jest bogata w przykłady, a każdy
z nich został przetestowany przez autorów.  Język ANSI C. Programowanie. Wydanie II
to niezastąpiona pozycja na półce każdego studenta informatyki, pasjonata programowania
i zawodowca. Wraz z książką został wydany zeszyt zawierający rozwiązania do
wszystkich zawartych w niej ćwiczeń.
" Zmienne i wyrażenia arytmetyczne w języku C
" Kompilowanie kodu
" Wykorzystanie preprocesora języka C
" Typy i operatory
" Metody sterowania wykonywaniem programu
" Wykorzystanie funkcji
" Struktura programu
" Zasada działania wskaxników
" Struktury danych
" Operacje wejScia i wyjScia
" Zastosowanie rekurencji
Poznaj tajniki języka C!
Spis tre ci
Przedmowa 7
Przedmowa do pierwszego wydania 9
Wst p 11
Rozdzia 1. Wprowadzenie 15
1.1. Pierwsze kroki 16
1.2. Zmienne i wyra enia arytmetyczne 18
1.3. Instrukcja for 24
1.4. Sta e symboliczne 26
1.5. Znakowe operacje wej cia-wyj cia 26
1.6. Tablice 34
1.7. Funkcje 36
1.8. Argumenty  przekazywanie jako warto 40
1.9. Tablice znaków 41
1.10. Zmienne zewn trzne i zakres zmiennych 44
Rozdzia 2. Typy, operatory i wyra enia 49
2.1. Nazwy zmiennych 49
2.2. Typy danych i ich rozmiar 50
2.3. Sta e 51
2.4. Deklaracje 54
2.5. Operatory arytmetyczne 55
2.6. Operatory porównania i logiczne 56
2.7. Konwersja typów 57
2.8. Inkrementacja i dekrementacja 61
2.9. Operatory bitowe 63
2.10. Operatory i wyra enia przypisania 65
J zyk ANSI C. Programowanie
2.11. Wyra enia warunkowe 67
2.12. Priorytety operatorów i kolejno wykonywania oblicze 68
Rozdzia 3. Sterowanie wykonywaniem programu 71
3.1. Instrukcje i bloki 71
3.2. if-else 72
3.3. else-if 73
3.4. switch 75
3.5. P tle while i for 76
3.6. P tla do-while 80
3.7. break i continue 81
3.8. goto i etykiety 82
Rozdzia 4. Funkcje i struktura programu 85
4.1. Funkcje  podstawy 86
4.2. Zwracanie warto ci innych ni int 89
4.3. Zmienne zewn trzne 92
4.4. Zakres 98
4.5. Pliki nag ówkowe 100
4.6. Zmienne statyczne 101
4.7. Zmienne rejestrowe 102
4.8. Struktura blokowa 103
4.9. Inicjalizacja 104
4.10. Rekurencja 105
4.11. Preprocesor j zyka C 107
Rozdzia 5. Wska niki i tablice 113
5.1. Wska niki i adresy 113
5.2. Wska niki i argumenty funkcji 115
5.3. Wska niki i tablice 118
5.4. Arytmetyka adresów 121
5.5. Wska niki znakowe i funkcje 124
5.6. Tablice wska ników, wska niki do wska ników 128
5.7. Tablice wielowymiarowe 131
5.8. Inicjalizacja tablic wska ników 134
5.9. Wska niki a tablice wielowymiarowe 134
5.10. Argumenty wiersza polece 135
5.11. Wska niki do funkcji 140
5.12. Rozbudowane deklaracje zmiennych i funkcji 143
Rozdzia 6. Struktury 149
6.1. Struktury  podstawy 149
6.2. Struktury i funkcje 151
6.3. Tablice struktur 154
6.4. Wska niki do struktur 158
6.5. Struktury cykliczne (odwo uj ce si do siebie) 161
4
Spis tre ci
6.6. Wyszukiwanie w tabelach 166
6.7. typedef 168
6.8. union 170
6.9. Pola bitowe 172
Rozdzia 7. Wej cie i wyj cie 175
7.1. Standardowe operacje wej cia-wyj cia 175
7.2. printf  formatowanie danych wyj ciowych 178
7.3. Listy argumentów o zmiennej d ugo ci 180
7.4. scanf  formatowane dane wej ciowe 181
7.5. Dost p do plików 185
7.6. stderr i exit  obs uga b dów 188
7.7. Wierszowe operacje wej cia-wyj cia 189
7.8. Inne funkcje 191
Rozdzia 8. Interfejs systemu UNIX 195
8.1. Deskryptory plików 196
8.2. Niskopoziomowe operacje wej cia-wyj cia  odczyt i zapis 197
8.3. open, creat, close, unlink 198
8.4. lseek  dost p swobodny 201
8.5. Przyk ad  implementacja fopen i getc 202
8.6. Przyk ad  listy zawarto ci katalogów 206
8.7. Przyk ad  mechanizm alokacji pami ci 211
Dodatek A Opis j zyka C 217
A.1. Wprowadzenie 217
A.2. Konwencje leksykalne 217
A.3. Zapis sk adni 221
A.4. Identyfikatory obiektów 222
A.5. Obiekty i L-warto ci 224
A.6. Konwersje 225
A.7. Wyra enia 228
A.8. Deklaracje 241
A.9. Instrukcje 257
A.10. Deklaracje zewn trzne 261
A.11. Zakres i wi zanie 264
A.12. Przetwarzanie wst pne 266
A.13. Gramatyka 273
Dodatek B Standardowa biblioteka j zyka C 281
B.1. Wej cie i wyj cie: 282
B.2. Wykrywanie klas znaków: 291
B.3. Ci gi znakowe: 291
B.4. Funkcje matematyczne: 293
B.5. Funkcje narz dziowe: 294
B.6. Diagnostyka: 297
5
J zyk ANSI C. Programowanie
B.7. Listy argumentów o zmiennej d ugo ci: 298
B.8. Skoki odleg e: 298
B.9. Sygna y: 299
B.10. Data i godzina: 300
B.11. Ograniczenia okre lane przez implementacj : i 302
Dodatek C Podsumowanie zmian 305
Skorowidz 309
6
Rozdzia 4.
Funkcje
i struktura programu
Funkcje dziel du e zadania obliczeniowe na mniejsze oraz umo liwiaj wielokrotne
wykorzystywanie tego samego kodu. W a ciwie napisane funkcje ukrywaj szczegó y
swoich mechanizmów przed innymi cz ciami programu, dla których s one nieistotne.
Zapewnia to przejrzysto i znacznie u atwia wprowadzanie zmian.
J zyk C zosta zaprojektowany w taki sposób, aby korzystanie z funkcji by o efektywne
i atwe. Program sk ada si z regu y z du ej liczby ma ych funkcji. Du e funkcj s
stosowane rzadko. Program mo e by zapisany w jednym lub wielu plikach. Pliki
ród owe programu mog by kompilowane niezale nie i pó niej jednocze nie ado-
wanedo pami ci razem z wcze niej skompilowanymi funkcjami bibliotek. Nie b dziemy
omawia tu dok adnie tego rodzaju procedur, poniewa ró ni si one w zale no ci
od stosowanego systemu.
Deklaracja i definicja funkcji to obszar, w którym norma ANSI wprowadzi a najbardziej
rzucaj ce si w oczy zmiany w j zyku C. Jak widzieli my ju w rozdziale 1., mo na teraz
okre la w deklaracji funkcji typy jej argumentów. Sk adnia definicji funkcji równie
jest zmieniona, dzi ki czemu deklaracja i definicja maj tak sam posta . Umo liwia
to kompilatorowi wykrycie znacznie wi kszej liczby b dów ni wcze niej. Co wi cej,
w a ciwy sposób deklarowania argumentów zapewnia automatyczne konwersje typów.
Standard u ci la regu y dotycz ce zakresu nazw. W szczególno ci wymaga on, aby ka dy
obiekt zewn trzny mia tylko jedn definicj . Mechanizm inicjalizacji zosta uogólniony
 w ANSI C mo na inicjalizowa tablice i struktury automatyczne.
Preprocesor j zyka równie zosta usprawniony. Jego nowe mechanizmy obejmuj
pe niejszy zbiór dyrektyw kompilacji warunkowej, mo liwo budowania ci gów znako-
wych z argumentów makr oraz wi ksz kontrol nad procesem rozwijania makra.
J zyk ANSI C. Programowanie
4.1. Funkcje  podstawy
Na pocz tek zaprojektujemy i napiszemy program, który wypisuje ka dy wiersz danych
wej ciowych zawieraj cy okre lony wzorzec  ci g znaków (b dzie to uproszczona
wersja programu grep systemu UNIX). Przyk adowo wyszukiwanie wzorca  ould
w zbiorze wierszy
Ah Love! could you and I with Fate conspire
To grasp this sorry Scheme of Things entire,
Would not we shatter it to bits -- and then
Re-mould it nearer to the Heart's Desire!
spowoduje wypisanie
Ah Love! could you and I with Fate conspire
Would not we shatter it to bits -- and then
Re-mould it nearer to the Heart's Desire!
Tak postawione zadanie mo na podzieli w naturalny sposób na trzy cz ci:
while (jest kolejny wiersz)
if (wiersz zawiera wzorzec)
wypisz wiersz
Cho jest oczywi cie mo liwe umieszczenie ca ego kodu w funkcji main, lepszym po-
dej ciem okazuje si wykorzystanie mo liwo ci strukturalizowania kodu i zapisanie
ka dej cz ci w odr bnej funkcji. Z trzema ma ymi elementami atwiej pracowa ni
z jednym du ym  nieistotne szczegó y pozostaj ukryte w funkcjach, a prawdopo-
dobie stwo wyst pienia niepo danych interakcji jest ograniczone do minimum.
Co wi cej, gotowe elementy mog znale zastosowanie w innych programach.
 while jest kolejny wiersz to funkcja getline, któr napisali my ju w rozdziale 1.
 wypisz wiersz to funkcja printf, dost pna w standardowej bibliotece. Oznacza to,
e musimy jedynie napisa procedur okre laj c , czy wiersz zawiera wzorzec.
Mo emy rozwi za ten problem, pisz c funkcj strindex(s,t), która zwraca pozycj
(indeks) w ci gu s, od którego zaczyna si ci g t, lub -1, je eli s nie zawiera t. Poniewa
pierwsza pozycja w tablicach j zyka C ma indeks 0, indeksy b d mia y warto ci dodatnie
lub 0, a warto ujemna, taka jak -1, mo e zosta wykorzystana do sygnalizowania
nieudanego wyszukiwania. Je eli w przysz o ci b dzie potrzebny bardziej wyszukany
mechanizm wyszukiwania wzorców, b dzie mo na wymieni funkcj strindex na inn .
Reszta kodu pozostanie bez zmian (standardowa biblioteka zawiera funkcj strstr,
która jest podobna do strindex, ale zwraca wska nik zamiast indeksu).
Po takim przygotowaniu projektu napisanie w a ciwego programu jest ju czynno ci
stosunkowo prost . Poni ej przedstawiono ca o , zarówno g ówny program, jak i sto-
sowane funkcje, aby Czytelnik móg wygodnie przeanalizowa ich wspó dzia anie. W tej
wersji wyszukiwany ci g jest litera em (sta ), wi c trudno mówi o ogólno ci rozwi zania.
Do inicjalizowania tablic znakowych powrócimy ju wkrótce, natomiast w rozdziale 5.
86
Rozdzia 4. " Funkcje i struktura programu
poka emy, jak przekszta ci wzorzec w parametr przekazywany przy uruchamianiu
programu. Kod zawiera tak e nieco zmodyfikowan funkcj getline. Porównanie
jej z wersj z rozdzia u 1. mo e dostarczy warto ciowych spostrze e .
#include
#define MAXLINE 1000 /* dopuszczalna d ugo wiersza */
int getline(char line[], int max)
int strindex(char source[], char searchfor[]);
char pattern[] = "ould"; /* wzorzec do wyszukania */
/* wyszukuje wszystkie wiersze zawieraj ce wzorzec */
main()
{
char line[MAXLINE];
int found = 0;
while (getline(line, MAXLINE) > 0)
if (strindex(line, pattern) >= 0) {
printf("%s", line);
found++;
}
return found;
}
/* getline: pobiera wiersz do s, zwraca d ugo */
int getline(char s[], int lim)
{
int c, i;
i = 0;
while (--lim > 0 && (c=getchar()) != EOF && c != '\n')
s[i++] = c;
if (c == '\n')
s[i++] = c;
s[i] = '\0';
return i;
}
/* strindex: zwraca index t w s lub  1, je eli nie wyst puje */
int strindex(char s[], char t[])
{
int i, j, k;
for (i = 0; s[i] != '\0'; i++) {
for (j=i, k=0; t[k]!='\0' && s[j]==t[k]; j++, k++)
;
if (k > 0 && t[k] == '\0')
return i;
}
return -1;
}
87
J zyk ANSI C. Programowanie
Definicja funkcji ma zawsze nast puj c posta :
typ_zwracany nazwa_funkcji(deklaracje_argumentów)
{
deklaracje i instrukcje
}
Ró ne elementy mo na pomija . Absolutne minimum to
dummy () {}
czyli funkcja, która nic nie robi i nic nie zwraca. Funkcja tego rodzaju okazuje si czasem
przydatna jako tymczasowa  atrapa w trakcie pracy nad programem. Je eli zwracany typ
danych nie zosta okre lony, kompilator przyjmuje, e jest to int.
Program to po prostu zbiór definicji zmiennych i funkcji. Komunikacja mi dzy funkcjami
odbywa si za po rednictwem argumentów funkcji, warto ci zwracanych przez funkcje
i zmiennych zewn trznych. Funkcje mog by umieszczone w pliku ród owym w dowol-
nej kolejno ci, a program mo e by podzielony na wiele plików ród owych, o ile tylko
ka da funkcja znajduje si w ca o ci w jednym pliku.
Instrukcja return reprezentuje mechanizm zwracania warto ci z funkcji wywo ywanej
do funkcji lub rodowiska wywo uj cego. Po s owie return mo e znajdowa si dowolne
wyra enie:
return wyra enie;
Je eli to konieczne, warto wyra enia jest przekszta cana na typ zadeklarowany jako
zwracany przez funkcj . Wyra enie nast puj ce po s owie return ujmuje si cz sto
w nawiasy, ale nie jest to wymagane.
Funkcja wywo uj ca mo e w ka dym przypadku zignorowa zwracan warto . Co wi cej,
wyra enie po s owie return nie jest elementem wymaganym. Gdy zostanie pomini te,
funkcja nie b dzie zwraca a adnej warto ci. Sterowanie zostaje przekazane do funkcji
wywo uj cej bez zwracania warto ci tak e po doj ciu do ko cowego nawiasu klamrowego.
Jest to dopuszczalne, ale  gdy funkcja z jednego miejsca zwraca warto , a z innego
nie  sygnalizuje wyst powanie nieprawid owo ci w pracy programu. W ka dym przy-
padku, w którym funkcja nie zwraca warto ci, próba jej odczytania prowadzi do uzy-
skania przypadkowych danych ( mieci).
Program wyszukuj cy ci g zwraca z funkcji main informacj o przebiegu jego wykonywa-
nia, któr w tym przypadku jest liczba znalezionych wierszy. Warto ta mo e by wyko-
rzystywana przez rodowisko, które wywo a o program.
Mechanika kompilowania i adowania programu C, który zosta zapisany w wielu plikach
ród owych, ró ni si w zale no ci od systemu. Przyk adowo w systemie UNIX zadanie
to realizuje wspomniane w rozdziale 1. polecenie cc. Za ó my, e trzy funkcje przyk ado-
wego programu s zapisane w trzech plikach, o nazwach main.c, getline.c i strindex.c.
W takiej sytuacji polecenie
cc main.c getline.c strindex.c
88
Rozdzia 4. " Funkcje i struktura programu
kompiluje trzy wymienione pliki, umieszcza kod obiektów w plikach main.o, getline.o
i strindex.o, a nast pnie aduje je wszystkie do pliku wykonywalnego o nazwie a.out.
W przypadku wyst pienia b du, na przyk ad w main.c, plik mo e zosta skompilowany
ponownie niezale nie od innych i za adowany razem z przygotowanymi wcze niej.
Umo liwia to polecenie
cc main.c getline.o strindex.o
Polecenie cc wykorzystuje rozszerzenia .c i .o do odró niania plików ród owych od plików
wynikowych.
wiczenie 4.1. Napisz funkcj strrindex(s,t), która zwraca pozycj ostatniego wy-
st pienia t w s lub -1, je eli wyszukiwany ci g nie zosta znaleziony.
4.2. Zwracanie warto ci innych ni int
Dotychczas przyk adowe funkcje albo nie zwraca y adnej warto ci (void), albo zwraca y
liczb int. Co z funkcjami zwracaj cymi warto ci innych typów? Wiele funkcji liczbo-
wych, takich jak sqrt, sin czy cos, zwraca typ double. Inne wyspecjalizowane funkcje
zwracaj jeszcze inne typy. Aby zilustrowa prac z takimi funkcjami, napiszemy i wywo-
amy funkcj atof(s), która konwertuje ci g s na jego odpowiednik typu zmiennoprze-
cinkowego, podwójnej precyzji. Funkcja atof jest rozwini ciem funkcji atoi, której
wersje zosta y przedstawione w rozdzia ach 2. i 3. atof zapewnia obs ug opcjonalnego
znaku liczby oraz kropki dziesi tnej, a tak e sytuacji, w których nie wyst puje cz
ca kowita lub cz u amkowa warto ci. Przedstawiona tu wersja nie jest wysokiej jako ci
procedur konwersji danych wej ciowych. Taka funkcja zaj aby stanowczo zbyt wiele
miejsca. Dopracowan wersj atof zawiera standardowa biblioteka j zyka  jest ona
zdefiniowana w nag ówku .
Przede wszystkim typ zwracanej warto ci, je eli nie jest to int, musi zosta okre lony
w samej funkcji. Nazw typu umieszcza si przed nazw funkcji:
#include
/* atof: konwertuje ci g s na liczb double */
double atof(char s[])
{
double val, power;
int i, sign;
for (i = 0; isspace(s[i]); i++) /* pomi bia e znaki */
;
sign = (s[i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-')
i++;
89
J zyk ANSI C. Programowanie
for (val = 0.0; isdigit(s[i]); i++)
val = 10.0 * val + (s[i] - '0');
if (s[i] == '.')
i++;
for (power = 1.0; isdigit(s[i]); i++) {
val = 10.0 * val + (s[i] - '0');
power *= 10;
}
return sign * val / power;
}
Drug , równie wa n rzecz jest to, e procedura wywo uj ca musi wiedzie , e funkcja
atof zwraca warto inn ni int. Jedn z mo liwo ci zapewnienia tego jest jawne
zadeklarowanie atof w tej procedurze. Deklaracj tak wida w programie minimali-
stycznego kalkulatora (nadaj cego si chyba tylko do podliczania wyp at z bankomatu),
który sumuje pobieran z wej cia pojedyncz kolumn liczb. Liczby mog zawiera znak,
a po ka dej jest drukowana suma pobranych ju warto ci:
#include
#define MAXLINE 100
/* prymitywny kalkulator */
main()
{
double sum, atof(char []);
char line[MAXLINE];
int getline(char line[], int max);
sum = 0;
while (getline(line, MAXLINE) > 0)
printf("\t%g\n", sum += atof(line));
return 0;
}
Deklaracja
double sum, atof(char []);
mówi, e sum to zmienna typu double, a atof to funkcja, która pobiera jeden argument
char[] i zwraca warto double.
Deklaracja i definicja funkcji musz by zgodne. Je eli definicja funkcji i jej wywo anie
w main maj niespójnie okre lone typy, a s w tym samym pliku ród owym, kompilator
zg osi b d. Jednak gdy (co jest bardziej prawdopodobne) funkcja atof b dzie kompilowana
niezale nie, brak zgodno ci nie zostanie wykryty, a funkcja zwróci liczb double, która
w main b dzie traktowana jako int  uzyskiwane wtedy warto ci b d niemal zupe -
nie przypadkowe.
90
Rozdzia 4. " Funkcje i struktura programu
W wietle tego, co powiedzieli my o dopasowaniu deklaracji do definicji, mo e si to
wydawa zaskakuj ce. Przyczyn takiej niezgodno ci jest zasada, e gdy brak prototypu
funkcji, jej deklaracja nast puje automatycznie w chwili pierwszego u ycia w wyra-
eniu, na przyk ad
sum += atof(line)
Je eli nazwa, która nie zosta a wcze niej zadeklarowana, wyst puje w wyra eniu, a bez-
po rednio po niej jest umieszczony otwieraj cy znak nawiasu, nast puje deklaracja na
podstawie kontekstu  dana nazwa jest uznawana za nazw funkcji, która zwraca
warto int. Nie s natomiast przyjmowane adne za o enia dotycz ce jej argumentów.
Co wi cej, je eli deklaracja funkcji nie zawiera argumentów, jak w instrukcji
double atof();
to równie wstrzymuje kompilator od przyjmowania za o e dotycz cych argumentów.
Sprawdzanie poprawno ci parametrów zostaje ca kowicie wy czone. Ta szczególna
interpretacja pustej listy argumentów ma umo liwi kompilowanie starszych programów
w j zyku C przez nowsze kompilatory. Nie nale y jednak stosowa takiej sk adni w no-
wych programach. Je eli funkcja pobiera argumenty, deklarujemy je. Je eli nie po-
biera adnych, u ywamy typu void.
Je li dysponujemy (w a ciwie zadeklarowan ) funkcj atof, mo emy wykorzysta j
do utworzenia prostej funkcji atoi (konwertuj cej ci g znaków na liczb int):
/* atoi: konwertuje ci g s na liczb ca kowit przy u yciu atof */
int atoi(char s[])
{
double atof(char s[]);
return (int) atof(s);
}
Zwró my uwag na struktur deklaracji i instrukcj return. Warto wyra enia w wierszu
return wyra enie;
zostaje przekszta cona na typ warto ci zwracanej przez funkcj przed wyj ciem z tej
funkcji. Warto atof, typu double, jest konwertowana automatycznie na int po doj ciu
do tego wiersza  funkcja atoi ma zwraca liczb ca kowit . Operacja taka mo e
prowadzi do utraty cz ci danych (cz ci u amkowej liczby), wi c niektóre kompilatory
generuj po jej napotkaniu ostrze enie. Operacja (int) jest jawn informacj o tym, e
konwersja typu jest zamierzona, dzi ki czemu ostrze enie nie jest wy wietlane.
wiczenie 4.2. Dodaj do funkcji atof mo liwo obs ugi notacji wyk adniczej, postaci:
123.45e-6
gdzie po liczbie zmiennoprzecinkowej mo e wyst pi litera e lub E i wyk adnik, z opcjo-
nalnym znakiem.
91
J zyk ANSI C. Programowanie
4.3. Zmienne zewn trzne
Program w j zyku C sk ada si ze zbioru obiektów zewn trznych  zmiennych i funkcji.
Wewn trz funkcji definiowane s obiekty wewn trzne, czyli jej argumenty i zmienne
lokalne. Zmienne zewn trzne s definiowane poza funkcjami, dzi ki czemu mog by
dost pne nie w jednej, ale w wielu funkcjach. Same funkcje s zawsze obiektami ze-
wn trznymi, poniewa j zyk C nie dopuszcza definiowania funkcji wewn trz funkcji.
Standardowo zewn trzne zmienne i funkcje maj t w a ciwo , e wszystkie odwo ania
do nich, czyli takie, które u ywaj tej samej nazwy, nawet w funkcjach kompilowanych
niezale nie, pozostaj odwo aniami do tego samego obiektu (norma okre la t w a ciwo
terminem  dowi zywanie obiektów zewn trznych , ang. external linkage). Pod tym
wzgl dem zmienne zewn trzne zachowuj si tak jak bloki COMMON j zyka Fortran lub
zmienne w najbardziej zewn trznym bloku w j zyku Pascal. Wkrótce poka emy, jak
definiowa zmienne i funkcje zewn trzne, które s widoczne jedynie w obr bie po-
jedynczego pliku ród owego.
Poniewa zmienne zewn trzne s dost pne globalnie, stanowi alternatyw dla ar-
gumentów i warto ci zwracanych przez funkcje  równie umo liwiaj wymian danych
mi dzy funkcjami. Ka da funkcja mo e uzyska dost p do zmiennej zewn trznej przy
u yciu jej nazwy, o ile tylko nazwa ta zosta a wcze niej w pewien sposób zadeklarowana.
Je eli funkcje maj korzysta wspólnie z wielu ró nych zmiennych, zmienne zewn trzne
s wygodniejsze i efektywniejsze ni d ugie listy argumentów. Jak jednak pisali my
w rozdziale 1., korzystanie z tej mo liwo ci powinno wi za si z pewn ostro no ci ,
mo e mie bowiem z y wp yw na struktur programu i prowadzi do kodu z nad-
miernie z o on sieci powi za mi dzy funkcjami.
Zmienne zewn trzne znajduj tak e zastosowania wynikaj ce bezpo rednio z ich wi k-
szego zakresu i d u szego  czasu ycia . Zmienne automatyczne to wewn trzne obiekty
funkcji. Powstaj w chwili wej cia do funkcji i zostaj zlikwidowane w chwili wyj cia
z niej. Zmienne zewn trzne s trwa e, zachowuj swoj warto pomi dzy wywo aniami
ró nych funkcji. Je eli wi c dwie funkcje musz korzysta z tych samych danych, a nie
wyst puje sytuacja, w której jedna z nich wywo uje drug , zapisanie wspólnych danych
w zmiennych zewn trznych jest cz sto najwygodniejszym rozwi zaniem, pozwalaj cym
unikn wprowadzania dodatkowego mechanizmu przekazywania warto ci do i z ka dej
ze wspó dzia aj cych funkcji.
Przeanalizujmy to zagadnienie na konkretnym przyk adzie. Naszym zadaniem jest napi-
sanie programu kalkulatora, który umo liwia korzystanie z operatorów +, -, * i /. Po-
niewa jest to prostsze w implementacji, kalkulator b dzie korzysta z odwrotnej notacji
polskiej, a nie notacji infiksowej (odwrotna notacja polska jest u ywana przez niektóre
kalkulatory kieszonkowe oraz j zyki programowania, na przyk ad Forth i PostScript).
W odwrotnej notacji polskiej wszystkie operandy poprzedzaj operator. Wyra enie infik-
sowe, na przyk ad
(1  2) * (4 + 5)
92
Rozdzia 4. " Funkcje i struktura programu
jest wprowadzane jako
1 2  4 5 + *
Nawiasy nie s potrzebne. Notacja jest jednoznaczna, o ile tylko liczba operandów ka dego
operatora jest sta a.
Implementacja jest prosta. Ka dy operand zostaje umieszczony na stosie. Po pobraniu
operatora program zdejmuje ze stosu w a ciw liczb operandów (dwa w przypadku
operatorów binarnych), wykonuje operacj i zapisuje wynik ponownie na stosie. W po-
wy szym przyk adzie oznacza to umieszczenie na stosie liczb 1 i 2, nast pnie zast pienie
ich ró nic ,  1. W kolejnym kroku na stos trafiaj liczby 4 i 5, które zostaj nast pnie
zast pione sum , 9. Kolejna operacja to mno enie, wi c ze stosu zostaj pobrane warto ci
 1 i 9, które zast puje nast pnie ich iloczyn,  9. Na zako czenie, po doj ciu do ko ca
wiersza, warto ze szczytu stosu zostaje wypisana na ekranie.
Struktura programu jest wi c p tl , która wykonuje odpowiednie operacje na pobiera-
nych kolejno operatorach i operandach:
while (nast pny operator lub operand nie jest znakiem ko ca pliku)
if (liczba)
zapisz na stosie
else if (operator)
zdejmij operandy ze stosu
wykonaj operacj
zapisz wynik na stosie
else if (znak nowego wiersza)
zdejmij warto ze szczytu stosu i wypisz
else
b d
Operacje umieszczania danych na stosie i zdejmowania z niego s banalne, ale do czasu
uzupe nienia programu o wykrywanie i obs ug b dów pozostaj wystarczaj co z o one,
aby uzasadnia o to umieszczenie ich w osobnych funkcjach. Pozwoli to przede wszystkim
unikn powtarzania kodu. Równie odr bna funkcja powinna odpowiada za pobieranie
kolejnego operatora lub operandu.
G ównym za o eniem projektowym jest to, gdzie konkretnie jest stos, a w a ciwie
 które procedury maj do niego bezpo redni dost p. Jedn z mo liwo ci jest pozosta-
wienie jego obs ugi w main. Mo na przekazywa stos i bie c pozycj stosu do procedur,
które pobieraj i zapisuj warto ci. Jednak w funkcji main nie s potrzebne zmienne
steruj ce stosem. Wykonuje ona tylko operacje zapisania danych i odczytania ich. Zdecy-
dowali my wi c o przechowywaniu stosu i zwi zanych z nim informacji w zmiennych
zewn trznych, dost pnych funkcjom push i pop, ale nie main.
Zapisanie takiego projektu w postaci kodu nie jest trudne. Je eli mamy zapisa program
w jednym pliku ród owym, b dzie on wygl da tak:
#include ... /* wiersze include */
#define ... /* wiersze define */
93
J zyk ANSI C. Programowanie
deklaracje funkcji dla main
main() { ... }
zewn trzne zmienne dla push i pop
void push(double f) { ... }
double pop(void) { ... }
int getop(char s[]) { ... }
procedury wywo ywane przez getop
Zagadnieniem dzielenia programu na dwa pliki ród owe lub wi cej zajmiemy si ju
nied ugo.
Funkcja main to p tla zawieraj ca rozbudowan instrukcj switch, która rozga zia
sterowanie w zale no ci od typu operatora lub operandu. Jest to bardziej typowy przy-
k ad jej u ycia ni ten przedstawiony w podrozdziale 3.4.
#include
#include /* dla atof() */
#define MAXOP 100 /* dopuszczalny rozmiar operandu lub operatora */
#define NUMBER '0' /* sygna , e pobrano liczb */
int getop(char []);
void push(double);
double pop(void);
/* kalkulator z odwrotn notacj polsk */
main()
{
int type;
double op2;
char s[MAXOP];
while ((type = getop(s)) != EOF) {
switch (type) {
case NUMBER:
push(atof(s));
break;
case '+':
push(pop() + pop());
break;
case '*':
push(pop() * pop());
break;
case '-':
op2 = pop();
push(pop() - op2);
break;
case '/':
94
Rozdzia 4. " Funkcje i struktura programu
op2 = pop();
if (op2 != 0.0)
push(pop() / op2);
else
printf("error: zero divisor\n");
break;
case '\n':
printf("\t%.8g\n", pop());
break;
default:
printf("error: unknown command %s\n", s);
break;
}
}
return 0;
}
Poniewa + i * to operatory dzia a przemiennych, kolejno zdejmowania operandów ze
stosu nie ma znaczenia. Jednak w przypadku operatorów  i / musi istnie rozró nienie
mi dzy warto ci po lewej stronie znaku i warto ci po prawej stronie znaku. W instrukcji
push(pop()  pop()); /* B D */
kolejno obliczania warto ci wywo a pop nie jest okre lona. Aby zagwarantowa w a-
ciw , konieczne jest wcze niejsze pobranie pierwszej warto ci do zmiennej tymczasowej.
Wida to w kodzie funkcji main.
#define MAXVAL 100 /* dopuszczalna g boko stosu warto ci */
int sp = 0; /* nast pna wolna pozycja stosu */
double val[MAXVAL]; /* stos */
/* push: zapisuje f na stosie */
void push(double f)
{
if (sp < MAXVAL)
val[sp++] = f;
else
printf("error: stack full, can't push %g\n", f);
}
/* pop: zdejmuje i zwraca warto z wierzcho ka stosu */
double pop(void)
{
if (sp > 0)
return val[--sp];
else {
printf("error: stack empty\n");
return 0.0;
}
}
95
J zyk ANSI C. Programowanie
Zmienna jest zmienn zewn trzn , je eli jest zdefiniowana poza funkcj , tak wi c
wspó u ytkowane przez funkcje pop i push zmienne stosu i indeksu stosu zostaj zde-
finiowane poza tymi funkcjami. Jednak funkcja main nie odwo uje si do stosu ani jego
indeksu  reprezentacja mo e pozosta ukryta.
Przejd my teraz do implementacji getop, funkcji, która pobiera kolejny operator lub ope-
rand. Zadanie jest proste. Pomijamy spacje i tabulatory. Je eli nast pny znak nie jest
cyfr lub kropk dziesi tn , zwracamy go. W pozosta ych przypadkach pobieramy ci g
cyfr (który mo e zawiera kropk dziesi tn ) i zwracamy NUMBER, czyli warto sygnalizu-
j c , e pobrana zosta a liczba.
#include
int getch(void);
void ungetch(int);
/* getop: pobiera nast pny operator lub operand (liczb ) */
int getop(char s[])
{
int i, c;
while ((s[0] = c = getch()) == ' ' || c == '\t')
;
s[1] = '\0';
if (!isdigit(c) && c != '.')
return c; /* nie jest liczb */
i = 0;
if (isdigit(c)) /* pobierz cz ca kowit */
while (isdigit(s[++i] = c = getch()))
;
if (c == '.') /* pobierz cz u amkow */
while (isdigit(s[++i] = c = getch()))
;
s[i] = '\0';
if (c != EOF)
ungetch(c);
return NUMBER;
}
Co to za funkcje getch i ungetch? Cz sto zdarza si , e program nie mo e okre li , czy
odczyta wystarczaj c ilo danych wej ciowych a do momentu, gdy odczyta ich zbyt
du o. Takim przypadkiem jest w a nie odczytywanie znaków tworz cych liczb : do czasu
odczytania pierwszego znaku, który nie jest cyfr , pobierana liczba pozostaje niekompletna.
Jednak jest to moment, gdy program odczyta ju o jeden znak za du o, znak, na który nie
jest przygotowany.
Problem by by rozwi zany, gdyby istnia a mo liwo cofni cia operacji odczytu ostatniego
znaku danych wej ciowych. Wówczas program, który odczyta o jeden znak za du o,
móg by  odda  ten znak do strumienia, a inne elementy programu dzia a yby tak,
96
Rozdzia 4. " Funkcje i struktura programu
jak gdyby znak ten nigdy nie by odczytywany. Okazuje si , e skonstruowanie takiego
mechanizmu nie jest trudne, wystarczy para wspó pracuj cych ze sob funkcji. getch
zwraca kolejny znak danych wej ciowych. ungetch zapami tuje znaki zwrócone na
wej cie w taki sposób, aby dalsze wywo ania getch zwraca y je przed odczytaniem
nowych z rzeczywistego strumienia.
Ich wspó praca jest prosta. ungetch zapisuje wycofane znaki we wspólnym buforze
 tablicy znaków. getch odczytuje zawarto bufora, je eli nie jest on pusty. W pozo-
sta ych przypadkach wywo uje po prostu funkcj getchar. Niezb dna jest równie
zmienna indeksuj ca, która rejestruje pozycj bie cego znaku w buforze.
Poniewa bufor i indeks wykorzystuj dwie funkcje, getch i ungetch, a warto ci tych
zmiennych musz zosta zachowane mi dzy wywo aniami, konieczne jest u ycie
zmiennych zewn trznych. Obie funkcje i deklaracje zmiennych mo na zapisa tak:
#define BUFSIZE 100
char buf[BUFSIZE]; /* bufor dla ungetch */
int bufp = 0; /* nast pna wolna pozycja w buforze */
int getch(void) /* pobiera znak (mo e by znakiem wcze niej wycofanym) */
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
void ungetch(int c) /* wycofuje znak do strumienia danych wej ciowych */
{
if (bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}
Standardowa biblioteka zawiera funkcj ungetc, która umo liwia wycofanie jednego znaku.
Omówimy j w rozdziale 7. W powy szym przyk adzie u yli my tablicy, a nie poje-
dynczego znaku, aby zaprezentowa bardziej ogólne podej cie.
wiczenie 4.3. W oparciu o schemat przedstawiony w przyk adach program kalkulatora
mo na atwo rozbudowywa . Dodaj obs ug operatora modulo (%) i obs ug liczb ujemnych.
wiczenie 4.4. Utwórz polecenie wypisuj ce element na wierzcho ku stosu bez jego
usuwania ze stosu, polecenie duplikuj ce element na wierzcho ku stosu, polecenie
zamieniaj ce miejscami dwa górne elementy oraz polecenie usuwaj ce ca zawarto
stosu.
wiczenie 4.5. Dodaj dost p do funkcji biblioteki, takich jak sin, exp, i pow. Patrz
w cz ci 4. dodatku B.
97
J zyk ANSI C. Programowanie
wiczenie 4.6. Dodaj polecenia obs ugi zmiennych ( atwo jest zapewni mo liwo
korzystania z dwudziestu sze ciu zmiennych przy u yciu jednoliterowych nazw). Dodaj
zmienn przechowuj c ostatni wypisan warto .
wiczenie 4.7. Napisz procedur ungets(s), która zwraca do danych wej ciowych
ca y ci g znaków. Czy funkcja ta powinna korzysta ze zmiennych buf i bufp, czy raczej
tylko z funkcji ungetch?
wiczenie 4.8. Zmodyfikuj funkcje getch i ungetch, przyj wszy za o enie, e nigdy nie
b dzie wycofywany wi cej ni jeden znak.
wiczenie 4.9. Nasze funkcje getch i ungetch nie obs uguj poprawnie wycofywania
znaku EOF. Zastanów si , jakie powinny one mie cechy w przypadku cofania znaku EOF,
po czym zaimplementuj now koncepcj .
wiczenie 4.10. Alternatywna organizacja pracy z danymi wej ciowymi opiera si na
u yciu getline w celu pobrania ca ego wiersza. Dzi ki temu funkcje getch i ungetch nie
s potrzebne. Przekszta kalkulator, tak aby jego praca opiera a si na takim podej ciu do
danych wej ciowych.
4.4. Zakres
Funkcje i zmienne zewn trzne tworz ce program w j zyku C nie musz by kompi-
lowane jednocze nie. ród owy tekst programu mo na przechowywa w wielu plikach,
a wcze niej skompilowane procedury mog by adowane z bibliotek. Wi e si to z kil-
koma istotnymi pytaniami:
Jak zapisywa deklaracje, aby deklarowanie zmiennych w a ciwie przebiega o
w czasie kompilacji?
Jaki powinien by uk ad deklaracji, aby wszystkie elementy zosta y w a ciwie
po czone w chwili adowania programu?
Jaki uk ad deklaracji zapewnia, e nie s one powtarzane?
Jak inicjuje si zmienne zewn trzne?
Omówimy te zagadnienia na przyk adzie programu kalkulatora, który teraz podzielony
zostanie na kilka plików. Z praktycznego punktu widzenia jest to zbyt ma y program, aby
faktycznie warto by o go dzieli , jednak wystarczy on do zilustrowania problemów, które
pojawiaj si w wi kszych projektach.
Zakres (ang. scope) nazwy to cz programu, w której nazw t mo na stosowa . Dla
zmiennej automatycznej, deklarowanej na pocz tku funkcji, zakresem jest funkcja,
w której zmienna zosta a zadeklarowana. Zmienne lokalne o tej samej nazwie, ale
w ró nych funkcjach nie maj ze sob adnego zwi zku. To samo mo na powiedzie
o parametrach funkcji  s one w praktyce zmiennymi lokalnymi.
98
Rozdzia 4. " Funkcje i struktura programu
Zakres zmiennej zewn trznej lub funkcji si ga od punktu jej zadeklarowania do ko ca
kompilowanego pliku. Je eli na przyk ad main, sp, val, push i pop s zdefiniowane
w jednym pliku, w kolejno ci przedstawionej wcze niej, czyli
main() { ... }
int sp = 0;
double val[MAXVAL];
void push(double f) { ... }
double pop(void) { ... }
to zmienne sp i val mo na stosowa w funkcjach push i pop, po prostu wymieniaj c ich
nazw . Nie s wymagane dodatkowe deklaracje. Jednak nazwy te nie s widoczne w main,
podobnie jak funkcje push i pop.
Z drugiej strony, je eli odwo ania do zmiennej zewn trznej maj wyst pi przed jej
zdefiniowaniem lub zmienna ta jest definiowana w innym pliku ród owym ni ten,
w którym jest wykorzystywana, konieczne staje si u ycie deklaracji extern.
Wa ne jest, aby rozró nia deklaracj zmiennej zewn trznej od jej definicji. Deklaracja
informuje o w a ciwo ciach zmiennej (przede wszystkim jej typie). Definicja powoduje
dodatkowo przydzielenie pami ci. Je eli wiersze
int sp;
double val[MAXVAL];
pojawiaj si poza funkcjami, s to definicje zmiennych zewn trznych sp i val. Powoduj
one przydzielenie pami ci, pe ni tak e funkcje deklaracji dla kodu w pozosta ej cz ci
pliku ród owego. Z drugiej strony wiersze
extern int sp;
extern double val[];
deklaruj na potrzeby kodu w dalszej cz ci pliku, e sp ma typ int, a val to tablica liczb
double (której rozmiar jest okre lony gdzie indziej). Nie tworz one jednak zmiennych
i nie rezerwuj pami ci.
We wszystkich plikach tworz cych program ród owy mo e wyst pi tylko jedna definicja
zmiennej zewn trznej. Inne pliki mog zawiera deklaracje extern umo liwiaj ce do-
st p do tej zmiennej (deklaracje extern mog znale si tak e w pliku zawieraj cym
definicj ). Rozmiar tablicy musi zosta okre lony w definicji, a w deklaracji extern
jest opcjonalny.
Inicjalizacja zmiennej zewn trznej mo e zosta po czona tylko z jej definicj .
Cho w tym przypadku uk ad taki nie ma raczej uzasadnienia, funkcje push i pop mog
by zdefiniowane w jednym pliku, a zmienne val i sp w innym. Wówczas ich powi zanie
zostanie zapewnione przez nast puj cy uk ad definicji i deklaracji:
99
J zyk ANSI C. Programowanie
W pliku file1:
extern int sp;
extern double val[];
void push(double f) { ... }
double pop(void) { ... }
W pliku file2:
int sp = 0;
double val[MAXVAL];
Poniewa deklaracje extern w pliku file1 poprzedzaj definicje funkcji, zmienne mo na
w tych funkcjach stosowa . Jedna para deklaracji wystarczy dla zapewnienia dost p-
no ci zmiennych w ca ym pliku file1. Taki sam uk ad nale a oby zastosowa , gdyby
definicje sp i val znajdowa y si w tym samym pliku, ale po definicjach funkcji, w których
s stosowane.
4.5. Pliki nag ówkowe
Rozwa my podzielenie programu kalkulatora na kilka plików ród owych. Mog oby to
by potrzebne, gdyby poszczególne jego komponenty zosta y znacznie rozbudowane.
Przyjmijmy, e funkcja main trafia do pliku main.c, push, pop i ich zmienne do pliku
stack.c, funkcja getop do pliku getop.c, a getch i ungetch  do getch.c. Oddzielamy te
ostatnie od pozosta ych, poniewa w rzeczywistym programie by yby cz ci odr bnie
kompilowanej biblioteki.
Pozostaje jeden problem do rozwi zania  definicje i deklaracje elementów wyko-
rzystywanych w wi cej ni jednym pliku. D ymy do maksymalnej centralizacji bu-
dowanego systemu, aby ka da z jego cz ci mia a tylko jedno w a ciwe miejsce, nieule-
gaj ce zmianie w toku dalszej ewolucji kodu. Aby osi gn ten cel, umieszczamy wspólne
elementy w pliku nag ówkowym (ang. header file, najcz ciej nazywany krótko nag ów-
kiem), calc.h. Plik ten b dzie w czony do kodu plików, które korzystaj z jego zawarto ci,
dyrektyw #include. Dyrektyw t opiszemy dok adnie w podrozdziale 4.11. Program
wygl da tak:
100
Rozdzia 4. " Funkcje i struktura programu
Mamy tu do czynienia z problemem wywa enia mi dzy d eniem do tego, aby ka dy plik
mia dost p wy cznie do tych informacji, które s mu niezb dne, a prozaiczn potrzeb
codziennej praktyki  praca ze zbyt du liczb plików nag ówka jest uci liwa. Do
pewnych granic dobrym rozwi zaniem jest stosowanie jednego nag ówka dla ca ego
programu zawieraj cego wszystko, co jest u ywane przez wi cej ni jedn jego cz .
Takie rozwi zanie zastosowali my w przyk adzie. Wi ksze programy wymagaj bardziej
rozbudowanej struktury i wi kszej liczby nag ówków.
4.6. Zmienne statyczne
Zmienne sp i val w pliku stack.c oraz buf i bufp w pliku getch.c s u do prywatnego
u ytku przez funkcje znajduj ce si w tym samym pliku ród owym. adne inne nie po-
winny mie do nich dost pu. Deklaracja static zastosowana w odniesieniu do zmiennej
zewn trznej lub funkcji ogranicza zakres obiektu do pozosta ej cz ci kompilowanego
pliku ród owego. Zewn trzna deklaracja static jest wi c metod ukrywania nazw takich
jak buf i bufp  nazw, które musz by zewn trzne, bo s wspó u ytkowane przez
ró ne funkcje, ale nie powinny by widoczne dla kodu wywo uj cego te funkcje.
Statyczne przechowywanie zmiennych okre lamy, wstawiaj c na pocz tku zwyk ej
deklaracji s owo static. Je eli dwie procedury i dwie zmienne s kompilowane w tym
samym pliku, jak w przyk adzie
static char buf[BUFSIZE]; /* bufor dla ungetch */
static int bufp = 0; /* nast pna wolna pozycja w buforze */
int getch(void) { ... }
void ungetch(int c) { ... }
101
J zyk ANSI C. Programowanie
to adna inna procedura nie ma dost pu do zmiennych buf i bufp, a ich nazwy nie
wchodz w konflikt z takimi samymi nazwami w innych plikach tego samego programu.
W taki sam sposób mo na ukry zmienne wykorzystywane przez funkcje push i pop do
obs ugi stosu  deklaruj c sp i val jako static.
Zewn trzna deklaracja static jest najcz ciej stosowana w odniesieniu do zmiennych,
ale mo e by u yta tak e w odniesieniu do funkcji. Normalnie nazwy funkcji maj
charakter globalny  s widoczne w ca ym programie. Je eli jednak funkcja jest za-
deklarowana jako static, jej nazwa nie jest widoczna poza plikiem, w którym zosta a
zadeklarowana.
Deklaracji static mo na tak e u y w odniesieniu do zmiennych wewn trznych. We-
wn trzne zmienne static pozostaj zmiennymi lokalnymi funkcji, podobnie jak zmienne
automatyczne, jednak w przeciwie stwie do zmiennych automatycznych nie prze-
staj istnie w chwili wyj cia z funkcji. W efekcie wewn trzne zmienne statyczne to
prywatna pami trwa a pojedynczej funkcji.
wiczenie 4.11. Zmodyfikuj funkcj getop w taki sposób, aby nie korzysta a z funkcji
ungetch. Wskazówka: u yj wewn trznej zmiennej statycznej.
4.7. Zmienne rejestrowe
Deklaracja register zwraca uwag kompilatora na to, e dana zmienna b dzie wyj t-
kowo intensywnie wykorzystywana. Ide tej deklaracji jest wskazanie, e pewne zmienne
powinny zosta umieszczone w rejestrach komputera. Z zasady prowadzi to do szyb-
szych i mniejszych programów. Kompilator mo e, ale nie musi, dostosowa si do takie-
go zalecenia.
Oto przyk ady deklaracji register:
register int x;
register char c;
Deklaracje takie mo na stosowa wy cznie w odniesieniu do zmiennych automatycz-
nych i parametrów formalnych funkcji. W przypadku parametrów formalnych wygl da
to tak:
f(register unsigned m, register long n)
{
register int i;
...
}
W praktyce zmienne rejestrowe podlegaj pewnym ograniczeniom wynikaj cym z mo -
liwo ci wykorzystywanej platformy sprz towej. Tylko kilka zmiennych w ka dej funkcji
mo na przechowywa w rejestrach i tylko wybrane typy s dopuszczalne. Nadmiar
deklaracji register jest jednak nieszkodliwy, poniewa w przypadku zbyt du ej liczby
102
Rozdzia 4. " Funkcje i struktura programu
tak opisanych zmiennych lub niezgodno ci typów s owo register jest ignorowane. Dodat-
kowo nie mo na pobra adresu zmiennej rejestrowej (ten temat omówimy w rozdziale 5.),
niezale nie od tego, czy zosta a ona faktycznie umieszczona w rejestrze. Zakres ograni-
cze co do typów i liczby zmiennych rejestrowych jest zale ny od komputera.
4.8. Struktura blokowa
J zyk C nie jest j zykiem, w którym struktura programu opiera si na blokach, jak
jest na przyk ad w Pascalu  nie mo na definiowa funkcji wewn trz funkcji. Mimo to
struktura blokowa obowi zuje przy definiowaniu zmiennych. Deklaracje zmiennych
(i ich inicjalizacja) mog zosta umieszczone po nawiasie klamrowym otwieraj cym
dowoln instrukcj blokow , a nie tylko po nawiasie klamrowym otwieraj cym blok in-
strukcji funkcji. Zmienne deklarowane w ten sposób przes aniaj zmienne o takich samych
nazwach wyst puj ce poza blokiem, a ich  czas ycia ko czy si wraz z wyj ciem
z bloku. Na przyk ad w kodzie
if (n > 0) {
int i; /* deklaracja nowej zmiennej i */
for (i = 0; i < n; i++)
...
}
zakres zmiennej i to blok wykonywany przy warto ci warunku  prawda . Zmienna ta nie
ma adnych powi za ze zmiennymi o nazwie i poza blokiem, w którym jest zadeklaro-
wana. Zmienna automatyczna deklarowana i inicjalizowana w bloku jest deklarowana
i inicjalizowana przy ka dym wej ciu do tego bloku. Analogiczna zmienna static jest
inicjalizowana przy pierwszym wej ciu do bloku.
Zmienne automatyczne, w tym parametry formalne, równie przes aniaj zmienne
zewn trzne i funkcje o tej samej nazwie. W uk adzie deklaracji
int x;
int y;
f(double x)
{
double y;
...
}
wewn trz funkcji f wszystkie wyst pienia x odnosz si do parametru (typu double). Poza
funkcj f nazwa zmiennej x odnosi si do liczby int, zmiennej zewn trznej. To samo
mo na powiedzie o zmiennej y.
Do dobrej praktyki programowania nale y unikanie stosowania nazw zmiennych, które
przes aniaj nazwy u ywane w szerszym zakresie. Jest to bowiem najkrótsza droga do
pomy ek i b dów.
103
J zyk ANSI C. Programowanie
4.9. Inicjalizacja
O inicjalizacji wspominali my ju kilkukrotnie, ale zawsze pozostawa a ona na margi-
nesie innych tematów. W tym podrozdziale, po omówieniu ró nych klas pami ci da-
nych, mo emy przej do usystematyzowania regu tego procesu.
Gdy brak jawnej inicjalizacji, zmienne zewn trzne i statyczne maj warto 0, a zmienne
automatyczne i rejestrowe pozostaj niezdefiniowane  nie zawieraj u ytecznej warto ci.
Zmienne skalarne mo na inicjalizowa przy ich definiowaniu  wystarczy wprowadzi
po ich nazwie znak równo ci i wyra enie:
int x = 1;
char squota = '\'';
long day = 1000L * 60L * 60L * 24L; /* milisekund/dzie */
Warto inicjalizuj ca zmienne zewn trzne i statyczne musi by wyra eniem o sta ej
warto ci. Inicjalizacja jest wykonywana jednokrotnie, jeszcze przed rozpocz ciem w a-
ciwego procesu wykonywania programu. Inicjalizacja zmiennych automatycznych i reje-
strowych nast puje przy ka dym wej ciu wykonywanego programu do funkcji lub bloku.
Warto inicjalizuj ca zmienne automatyczne i rejestrowe nie musi by sta a  mo e
to by dowolne wyra enie oparte na warto ciach wcze niej zdefiniowanych, a nawet
wywo aniach funkcji. Przyk adowo inicjalizacja programu wyszukiwania binarnego
z podrozdzia u 3.3 mo e by zapisana nast puj co:
int binsearch(int x, int v[], int n)
{
int low = 0;
int high = n - 1;
int mid;
...
}
Nie jest wymagane pisanie:
int low, high, mid;
low = 0;
high = n - 1;
W efekcie inicjalizacja zmiennych automatycznych i rejestrowych to po prostu skrócona
forma cz ca instrukcje deklaracji i przypisania. Wybór jest kwesti stylu. W ksi ce
z zasady nie czymy deklaracji i przypisania, poniewa warto pocz tkowa okre lona
w bloku deklaracji jest atwa do przeoczenia, a odr bne przypisanie mo e nast pi
w miejscu, w którym zmienna jest wykorzystywana.
Tablic mo na zainicjalizowa , umieszczaj c po deklaracji list warto ci elementów
 uj t w nawiasy klamrowe i rozdzielan przecinkami. Aby na przyk ad zainicjalizowa
tablic days d ugo ciami miesi cy, piszemy:
int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
104
Rozdzia 4. " Funkcje i struktura programu
Gdy rozmiar tablicy nie jest okre lony, kompilator okre la j , zliczaj c warto ci pocz tko-
we elementów. W tym przypadku jest ich 12.
Je eli lista pocz tkowych warto ci elementów tablicy zawiera mniej elementów ni
tablica, pozosta ym przypisywana jest warto 0. Dotyczy to zmiennych zewn trznych,
statycznych i automatycznych. Podanie zbyt d ugiej listy warto ci jest b dem. Nie ma
sk adni umo liwiaj cej powtarzanie warto ci na li cie albo inicjalizowanie elementów
wewn trznych bez podania warto ci wszystkich elementów poprzedzaj cych.
Tablice znaków s traktowane w sposób szczególny. W miejsce nawiasów klamrowych
i rozdzielonej przecinkami listy mo na u y ci gu:
char pattern = "ould";
Jest to skrót d u szej, cho równowa nej konstrukcji:
char pattern[] = { 'o', 'u', 'l', 'd', '\0' };
W tym przypadku rozmiar tablicy to 5 (cztery znaki plus ko cowa sta a '\0').
4.10. Rekurencja
Funkcje j zyka C mog by wywo ywane rekurencyjnie. Oznacza to, e funkcja mo e,
bezpo rednio lub po rednio, wywo a siebie sam . Rozwa my przyk ad wypisywania
liczby jako ci gu znaków. Jak pisali my wcze niej, cyfry s wypisywane w niew a ciwej
kolejno ci  mniej znacz ce s dost pne przed bardziej znacz cymi. Kolejno ich
wypisywania musi by odwrotna.
S dwa rozwi zania tego problemu. Pierwszym jest zapisanie cyfr w tablicy i odwrócenie
kolejno ci zapisanych elementów. Tak zrobili my w przyk adowej funkcji itoa w pod-
rozdziale 3.6. Alternatyw stanowi rozwi zanie rekurencyjne, w którym funkcja printd
rozpoczyna prac od wywo ania samej siebie w celu wy wietlenia cyfr bardziej znacz -
cych ni cyfra aktualnie przetwarzana. Dopiero po powrocie z wywo anej funkcji wy-
pisywana jest cyfra bie ca. Poni ej przedstawiamy tak funkcj , ponownie w wersji
niezapewniaj cej poprawnego przetwarzania najwi kszej liczby ujemnej.
#include
/* printd: wypisuje n jako liczb dziesi tn */
void printd(int n)
{
if (n < 0) {
putchar('-');
n = -n;
}
if (n / 10)
105
J zyk ANSI C. Programowanie
printd(n / 10);
putchar(n % 10 + '0');
}
Gdy funkcja wywo uje rekurencyjnie sam siebie, ka de wywo anie otrzymuje nowy
zestaw wszystkich zmiennych automatycznych, ca kowicie niezale ny od wcze niejszego.
W efekcie po wywo aniu printd(123) pierwsza funkcja printd otrzymuje argument
n = 123. Przekazuje ona 12 do drugiej funkcji printd, która z kolei przekazuje 1 trzeciej.
Ta ostatnia wypisuje znak 1 i ko czy prac . Wówczas funkcja na drugim poziomie
wypisuje znak 2 i równie ko czy prac . Funkcja najwy szego poziomu wypisuje 3 i prze-
twarzanie pocz tkowego wywo ania printd(123) zostaje zako czone.
Innym ciekawym przyk adem rekurencji jest algorytm sortowania quicksort, opraco-
wany przez C.A.R. Hoare a w 1962 roku. Z tablicy wybierany jest jeden element, a pozo-
sta e zostaj podzielone na dwa podzbiory  elementów mniejszych oraz elementów
wi kszych lub równych. Ten sam proces jest nast pnie powtarzany rekurencyjnie dla
ka dego z podzbiorów. Gdy podzbiór ma mniej ni dwa elementy, dalsze sortowanie
nie jest potrzebne i proces rekurencji zostaje zako czony.
Nasza wersja programu sortuj cego metod quicksort nie jest najszybsza, ale za to jest
jedn z najprostszych. Podzia bazuje na rodkowym elemencie ka dej podtablicy.
/* qsort: sortuje v[left]...v[right] rosn co */
void qsort(int v[], int left, int right)
{
int i, last;
void swap(int v[], int i, int j);
if (left >= right) /* nic nie rób, je eli tablica zawiera */
return; /* mniej ni dwa elementy */
swap(v, left, (left + right)/2); /* przenie element partycji */
last = left; /* do v[0] */
for (i = left + 1; i <= right; i++) /* partycja */
if (v[i] < v[left])
swap(v, ++last, i);
swap(v, left, last); /* przywró element partycji */
qsort(v, left, last-1);
qsort(v, last+1, right);
}
Przenie li my operacj zamieniania elementów miejscami do osobnej funkcji swap
 jest przecie wywo ywana w trzech miejscach.
/* swap: zamienia miejscami v[i] i v[j] */
void swap(int v[], int i, int j)
{
int temp;
temp = v[i];
106
Rozdzia 4. " Funkcje i struktura programu
v[i] = v[j];
v[j] = temp;
}
Standardowa biblioteka zawiera wersj funkcji qsort, która potrafi sortowa obiekty
dowolnego typu.
Rekurencja nie przyczynia si do oszcz dzania pami ci  stos wykorzystywanych
przez kolejne poziomy wywo a warto ci musi by gdzie przechowywany. Nie jest te
rozwi zaniem szybszym. Jednak kod rekurencyjny jest bardziej zwarty i cz sto atwiejszy
do napisania i intuicyjnego zrozumienia ni jego nierekurencyjny odpowiednik. Rekuren-
cja jest szczególnie wygodna przy przetwarzaniu rekurencyjnie zdefiniowanych struktur
danych, takich jak drzewa. Ciekawy przyk ad znajdziemy w podrozdziale 6.5.
wiczenie 4.12. Zaadaptuj koncepcj funkcji printd do napisania rekurencyjnej wersji
funkcji itoa. Innymi s owy, przekszta liczb ca kowit na ci g znaków, wywo uj c
procedur rekurencyjn .
wiczenie 4.13. Napisz rekurencyjn wersj funkcji reverse(s), odwracaj cej  w miejscu
ci g znaków s.
4.11. Preprocesor j zyka C
J zyk C realizuje pewne mechanizmy j zykowe za po rednictwem preprocesora. Jest to
pierwszy krok wykonywany przed w a ciwym procesem kompilacji. Dwie najcz ciej
stosowane dyrektywy preprocesora to #include, w czaj ca do procesu kompilacji
zawarto innego pliku, i #define, zast puj ca nazw wskazanym ci giem znaków. W tym
podrozdziale opiszemy te inne mo liwo ci preprocesora: kompilacj warunkow i makra
z argumentami.
4.11.1. Wstawianie plików
Mechanizm wstawiania plików u atwia przede wszystkim obs ug zbiorów dyrektyw
#define i deklaracji. Ka dy wiersz postaci
#include "nazwa_pliku"
lub
#include
zostaje zast piony zawarto ci pliku nazwa_pliku. Je eli nazwa pliku jest uj ta w cudzy-
s ów, wyszukiwanie pliku rozpoczyna si najcz ciej w katalogu programu ród owego.
Je eli plik nie zostanie w nim znaleziony albo gdy zamiast cudzys owu u yto znaków
< i >, wyszukiwanie przebiega zgodnie z zasadami okre lonymi przez implementacj .
W czane dyrektyw #include pliki mog tak e zawiera wiersze #include.
107
J zyk ANSI C. Programowanie
Na pocz tku pliku ród owego znajduje si najcz ciej ca a grupa wierszy #include,
które w czaj do programu podstawowe instrukcje #define i deklaracje extern. Mog
równie zapewnia dost p do deklaracji prototypów funkcji bibliotecznych, zapisanych
w nag ówkach takich jak ( ci lej: nag ówki nie musz by plikami; zasady
dost pu do nag ówków wyznacza implementacja).
W czanie wierszem #include to podstawowa metoda czenia deklaracji w du ych
programach. Gwarantuje ona, e wszystkie pliki ród owe b d mia y dost p do tych
samych definicji i deklaracji zmiennych. Eliminuje to jeden z najbardziej uci liwych
rodzajów b dów w kodzie. Naturalnie gdy w czany plik ulega zmianie, wszystkie zale -
ne od niego pliki programu musz by kompilowane ponownie.
4.11.2. Makra
Definicja ma posta :
#define nazwa tekst_zast puj cy
Mamy tu do czynienia z najprostsz postaci makra, opart na substytucji  wszystkie
dalsze wyst pienia nazwa zostan zast pione przez tekst_zast puj cy. Nazwa w #define
ma tak sam posta jak nazwa zmiennej. Tekst zast puj cy mo e by dowolny. Nor-
malnie s to wszystkie znaki do ko ca wiersza, ale d uga definicja mo e zosta podzielona
na kilka kolejnych wierszy przez wstawienie znaku \ na ko cu ka dego wiersza, który ma
by kontynuowany. Zakres nazwy wskazanej w #define si ga od wiersza #define do ko ca
kompilowanego pliku ród owego. Definicja mo e korzysta z wcze niejszych definicji.
Substytucja nie obejmuje miejsc, w których nazwa jest cz ci d u szej nazwy i frag-
mentów uj tych w cudzys ów. Po zdefiniowaniu na przyk ad nazwy YES substytucja nie
nast pi w printf("YES") ani w YESMAN.
Zast puj cy nazw tekst mo e by dowolny. Na przyk ad
#define forever for (;;) /* p tla niesko czona */
definiuje nowe s owo, forever, które b dzie zast powane p tl niesko czon .
Mo na tak e definiowa makra z argumentami, dzi ki którym tekst zast puj cy jest
ró ny w poszczególnych wywo aniach makra. Przyk adem mo e by makro max:
#define max(A, B) ((A) > (B) ? (A) : (B))
Cho wygl da jak wywo anie funkcji, u ycie max sprowadza si do rozwini cia nazwy w kod
wstawiany wewn trz wiersza. Ka de wyst pienie parametru formalnego (tutaj A i B)
zostanie zast pione podanym argumentem. Tak wi c wiersz
x = max(p+q, r+s);
przyjmie posta
x = ((p+q) > (r+s) ? (p+q) : (r+s));
108
Rozdzia 4. " Funkcje i struktura programu
Dopóki argumenty s spójne, makro max mo e pracowa z dowolnym typem danych.
Nie ma potrzeby definiowania ró nych nazw max dla ró nych typów danych, tak jakby
to by o w przypadku zastosowania funkcji.
Gdy przyjrzymy si sposobowi rozwijania makra max, zwrócimy uwag , e wi e si
on z pewnymi pu apkami. Warto ci wyra e s obliczane dwukrotnie. Staje si to istot-
nym problemem, gdy pojawiaj si efekty uboczne wynikaj ce ze stosowania operatorów
zwi kszania i zmniejszania albo operacji wej cia-wyj cia. Przyk adowo
max(i++, j++) /* B D */
prowadzi do dwukrotnego zwi kszenia wi kszej warto ci. Cz sto warto zadba o uj cie
wyra enia w nawiasy, aby zapewni w a ciw kolejno wykonywania oblicze . Pomy l-
my, co si stanie, gdy makro
#define square(x) x * x /* B D */
zostanie wywo ane w wyra eniu square(z+1).
Makra s bardzo warto ciowym narz dziem. Jednym z praktycznych przyk adów ich
zastosowania jest w czanie do kompilacji pliku , w którym operacje getchar
i putchar s cz sto zdefiniowane jako makra. Pozwala to unikn obci enia programu
mechanizmem wywo ywania funkcji przy odczycie pojedynczych znaków. Równie
funkcje w s zazwyczaj implementowane jako makra.
Definicje nazw mo na wycofywa dyrektyw #undef. Mo liwo t wykorzystuje si
cz sto w celu uzyskania gwarancji, e dana procedura b dzie funkcj , a nie makrem:
#undef getchar
int getchar(void) { ... }
Parametry formalne nie s zast powane w ci gach znakowych otoczonych znakami
cudzys owu. Je eli jednak nazw parametru poprzedza w tek cie zast puj cym znak #,
to zostanie on zamieniony na uj ty w cudzys ów ci g znaków, w którym parametr jest
zast piony podanym argumentem faktycznym. W po czeniu z konkatenacj ci gów
pozwala to na przyk ad utworzy nast puj ce makro wy wietlaj ce warto ci potrzebne
w procesie debugowania:
#define dprint(expr) printf(#expr " = %g\n", expr)
Po jego wywo aniu, na przyk ad w instrukcji
dprint(x/y);
makro zostaje rozwini te do postaci
printf("x/y" " = &g\n", x/y);
ci gi znakowe s automatycznie czone, wi c w efekcie uzyskujemy
printf("x/y = &g\n", x/y);
109
J zyk ANSI C. Programowanie
W argumencie faktycznym ka dy znak " jest zast powany przez \", a ka dy znak \
przez \\, dzi ki czemu wynik to poprawna sta a tekstowa.
Operator preprocesora ## umo liwia konkatenowanie argumentów faktycznych w trakcie
rozwijania makr. Je eli parametr w tek cie zast puj cym s siaduje ze znakami ##, parametr
ten jest zast powany argumentem faktycznym, znaki ## i bia e znaki zostaj usuni te,
a wynik jest analizowany ponownie. Przyk adowo makro paste czy dwa argumenty:
#define paste(front, back) front ## back
wi c paste(name, 1) tworzy nazw name1.
Regu y zagnie d ania operatora ## s do z o one. Szczegó y mo na znale w dodatku A.
wiczenie 4.14. Zdefiniuj makro swap(t,x,y) wymieniaj ce warto ci dwóch argumentów,
których typ to t (pomocna b dzie struktura blokowa).
4.11.3. Warunkowe wstawianie kodu
Istnieje mo liwo sterowania prac samego preprocesora przy u yciu instrukcji wa-
runkowych, wykonywanych w trakcie jego dzia ania. Zapewnia to mo liwo wybiórcze-
go wstawiania kodu, w zale no ci od warunków, których warto ci s obliczane w czasie
kompilowania.
Wiersz #if oblicza warto sta ego wyra enia ca kowitego (które nie mo e zawiera
operatora sizeof, konwersji typów i sta ych enum). Je eli wyra enie ma warto ró n od
zera, wstawione zostaj dalsze wiersze, a do wiersza #endif, #elif lub #else (instrukcja
preprocesora #elif odpowiada else if). Wyra enie defined(nazwa) w wierszu #if ma
warto 1, je eli nazwa zosta a wcze niej zdefiniowana, a 0 w pozosta ych przypadkach.
Aby na przyk ad zapewni , e zawarto pliku hdr.h b dzie w czana do kodu tylko raz,
mo na otoczy j wierszami dyrektyw warunkowych:
#if !defined(HDR)
#define HDR
/* tu znajduje si w a ciwa tre nag ówka hdr.h */
#endif
Pierwsza operacja w czania pliku hdr.h powoduje zdefiniowanie nazwy HDR. Przy kolej-
nych próbach w czenia preprocesor stwierdza, e nazwa zosta a ju zdefiniowana, i prze-
chodzi do wiersza #endif. Podej cie takie mo na stosowa bardzo szeroko. Zachowanie
pe nej konsekwencji pozwala w ka dym nag ówku w cza do kompilacji dowolne inne
wymagane nag ówki bez ci g ego ledzenia ich wzajemnych zale no ci.
110
Rozdzia 4. " Funkcje i struktura programu
Nast puj ca sekwencja sprawdza tekst powi zany z nazw SYSTEM, aby okre li , która
wersja nag ówka ma zosta w czona do kodu:
#if SYSTEM == SYSV
#define HDR "sysv.h"
#elif SYSTEM == BSD
#define HDR "bsd.h"
#elif SYSTEM == MSDOS
#define HDR "msdos.h"
#else
#define HDR "default.h"
#endif
#include HDR
Wiersze #ifdef i #ifndef to wyspecjalizowane formy sprawdzenia, czy nazwa zosta a zde-
finiowana. Wcze niejszy przyk ad z #if mo na zapisa jako
#ifndef HDR
#define HDR
/* tu znajduje si w a ciwa tre nag ówka hdr.h */
#endif
111


Wyszukiwarka

Podobne podstrony:
Jezyk ANSI C Programowanie cwiczenia Wydanie II cwjans
Java?ektywne programowanie Wydanie II javep2
SZTUKA PERSWAZJI, czyli język wpływu i manipulacji Wydanie II
Jezyk C Szkola programowania Wydanie V
Asembler Sztuka programowania Wydanie II asesz2
Perl Zaawansowane programowanie Wydanie II perlz2
Warsztaty samodoskonalace Program walki z lekiem i depresja Wydanie II
Programowanie w jezyku C cwiczenia praktyczne Wydanie II cwprc2

więcej podobnych podstron