Jezyk ANSI C Programowanie Wydanie II jansic

background image

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³aœnie 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 systematycznoœci i skupienia, ale daj¹cy w zamian
wiele mo¿liwoœci i œwietne wyniki. To najczêœciej nauczany jêzyk programowania – jego
znajomoœæ 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 coœ, co sprawia pocz¹tkuj¹cym
programistom najwiêcej problemów – wskaŸniki. Nastêpnie zapoznasz siê tak¿e
z funkcjami wejœcia i wyjœcia. Dowiesz siê, jak uzyskaæ dostêp do plików, formatowaæ
dane wyjœciowe 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 wskaŸników
• Struktury danych
• Operacje wejœcia i wyjœcia
• Zastosowanie rekurencji

Poznaj tajniki jêzyka C!

background image

Spis treci

Przedmowa

7

Przedmowa do pierwszego wydania

9

Wstp

11

Rozdzia 1. Wprowadzenie

15

1.1.

Pierwsze kroki

16

1.2.

Zmienne i wyraenia arytmetyczne

18

1.3.

Instrukcja for

24

1.4.

Stae symboliczne

26

1.5.

Znakowe operacje wejcia-wyjcia

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 zewntrzne i zakres zmiennych

44

Rozdzia 2. Typy, operatory i wyraenia

49

2.1.

Nazwy zmiennych

49

2.2.

Typy danych i ich rozmiar

50

2.3.

Stae

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 wyraenia przypisania

65

background image

Jzyk ANSI C. Programowanie

4

2.11.

Wyraenia 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.

Ptle while i for

76

3.6.

Ptla 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 wartoci innych ni int

89

4.3.

Zmienne zewntrzne

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 jzyka C

107

Rozdzia 5. Wskaniki i tablice

113

5.1.

Wskaniki i adresy

113

5.2.

Wskaniki i argumenty funkcji

115

5.3.

Wskaniki i tablice

118

5.4.

Arytmetyka adresów

121

5.5.

Wskaniki znakowe i funkcje

124

5.6.

Tablice wskaników, wskaniki do wskaników

128

5.7.

Tablice wielowymiarowe

131

5.8.

Inicjalizacja tablic wskaników

134

5.9.

Wskaniki a tablice wielowymiarowe

134

5.10.

Argumenty wiersza polece

135

5.11.

Wskaniki 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.

Wskaniki do struktur

158

6.5.

Struktury cykliczne (odwouj ce si do siebie)

161

background image

Spis treci

5

6.6.

Wyszukiwanie w tabelach

166

6.7.

typedef

168

6.8.

union

170

6.9.

Pola bitowe

172

Rozdzia 7. Wejcie i wyjcie

175

7.1.

Standardowe operacje wejcia-wyjcia

175

7.2.

printf — formatowanie danych wyjciowych

178

7.3.

Listy argumentów o zmiennej dugoci

180

7.4.

scanf — formatowane dane wejciowe

181

7.5.

Dostp do plików

185

7.6.

stderr i exit — obsuga bdów

188

7.7.

Wierszowe operacje wejcia-wyjcia

189

7.8.

Inne funkcje

191

Rozdzia 8. Interfejs systemu UNIX

195

8.1.

Deskryptory plików

196

8.2.

Niskopoziomowe operacje wejcia-wyjcia — odczyt i zapis

197

8.3.

open, creat, close, unlink

198

8.4.

lseek — dostp swobodny

201

8.5.

Przykad — implementacja fopen i getc

202

8.6.

Przykad — listy zawartoci katalogów

206

8.7.

Przykad — mechanizm alokacji pamici

211

Dodatek A Opis jzyka C

217

A.1.

Wprowadzenie

217

A.2.

Konwencje leksykalne

217

A.3.

Zapis skadni

221

A.4.

Identyfikatory obiektów

222

A.5.

Obiekty i L-wartoci

224

A.6.

Konwersje

225

A.7.

Wyraenia

228

A.8.

Deklaracje

241

A.9.

Instrukcje

257

A.10.

Deklaracje zewntrzne

261

A.11.

Zakres i wi zanie

264

A.12.

Przetwarzanie wstpne

266

A.13.

Gramatyka

273

Dodatek B Standardowa biblioteka jzyka C

281

B.1.

Wejcie i wyjcie: <stdio.h>

282

B.2.

Wykrywanie klas znaków: <ctype.h>

291

B.3.

Ci gi znakowe: <string.h>

291

B.4.

Funkcje matematyczne: <math.h>

293

B.5.

Funkcje narzdziowe: <stdlib.h>

294

B.6.

Diagnostyka: <assert.h>

297

background image

Jzyk ANSI C. Programowanie

6

B.7.

Listy argumentów o zmiennej dugoci: <stdarg.h>

298

B.8.

Skoki odlege: <setjmp.h>

298

B.9.

Sygnay: <signal.h>

299

B.10.

Data i godzina: <time.h>

300

B.11.

Ograniczenia okrelane przez implementacj: <limits.h> i <float.h>

302

Dodatek C Podsumowanie zmian

305

Skorowidz

309

background image

Rozdzia 4.

Funkcje

i struktura programu

Funkcje dziel due zadania obliczeniowe na mniejsze oraz umoliwiaj wielokrotne
wykorzystywanie tego samego kodu. Waciwie napisane funkcje ukrywaj szczegóy
swoich mechanizmów przed innymi czciami programu, dla których s one nieistotne.
Zapewnia to przejrzysto i znacznie uatwia wprowadzanie zmian.

Jzyk C zosta zaprojektowany w taki sposób, aby korzystanie z funkcji byo efektywne
i atwe. Program skada si z reguy z duej liczby maych funkcji. Due funkcj s
stosowane rzadko. Program moe by zapisany w jednym lub wielu plikach. Pliki
ródowe programu mog by kompilowane niezalenie i póniej jednoczenie ado-
wanedo pamici razem z wczeniej skompilowanymi funkcjami bibliotek. Nie bdziemy
omawia tu dokadnie tego rodzaju procedur, poniewa róni si one w zalenoci
od stosowanego systemu.

Deklaracja i definicja funkcji to obszar, w którym norma ANSI wprowadzia najbardziej
rzucajce si w oczy zmiany w jzyku C. Jak widzielimy ju w rozdziale 1., mona teraz
okrela w deklaracji funkcji typy jej argumentów. Skadnia definicji funkcji równie
jest zmieniona, dziki czemu deklaracja i definicja maj tak sam posta . Umoliwia
to kompilatorowi wykrycie znacznie wikszej liczby bdów ni wczeniej. Co wicej,
waciwy sposób deklarowania argumentów zapewnia automatyczne konwersje typów.

Standard ucila reguy dotyczce zakresu nazw. W szczególnoci wymaga on, aby kady
obiekt zewntrzny mia tylko jedn definicj. Mechanizm inicjalizacji zosta uogólniony
— w ANSI C mona inicjalizowa tablice i struktury automatyczne.

Preprocesor jzyka równie zosta usprawniony. Jego nowe mechanizmy obejmuj
peniejszy zbiór dyrektyw kompilacji warunkowej, moliwo budowania cigów znako-
wych z argumentów makr oraz wiksz kontrol nad procesem rozwijania makra.

background image

Jzyk ANSI C. Programowanie

86

4.1. Funkcje — podstawy

Na pocztek zaprojektujemy i napiszemy program, który wypisuje kady wiersz danych
wejciowych zawierajcy okrelony wzorzec — cig znaków (bdzie to uproszczona
wersja programu

grep

systemu UNIX). Przykadowo 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 mona podzieli w naturalny sposób na trzy czci:

while (jest kolejny wiersz)
if (wiersz zawiera wzorzec)
wypisz wiersz

Cho jest oczywicie moliwe umieszczenie caego kodu w funkcji

main

, lepszym po-

dejciem okazuje si wykorzystanie moliwoci strukturalizowania kodu i zapisanie
kadej czci w odrbnej funkcji. Z trzema maymi elementami atwiej pracowa ni
z jednym duym — nieistotne szczegóy pozostaj ukryte w funkcjach, a prawdopo-
dobiestwo wystpienia niepodanych interakcji jest ograniczone do minimum.
Co wicej, gotowe elementy mog znale zastosowanie w innych programach.

„while jest kolejny wiersz” to funkcja

getline

, któr napisalimy ju w rozdziale 1.

wypisz wiersz” to funkcja

printf

, dostpna w standardowej bibliotece. Oznacza to,

e musimy jedynie napisa procedur okrelajc, czy wiersz zawiera wzorzec.

Moemy rozwiza ten problem, piszc funkcj

strindex(s,t)

, która zwraca pozycj

(indeks) w cigu

s

, od którego zaczyna si cig

t

, lub

-1

, jeeli

s

nie zawiera

t

. Poniewa

pierwsza pozycja w tablicach jzyka C ma indeks 0, indeksy bd miay wartoci dodatnie
lub 0, a warto ujemna, taka jak

-1

, moe zosta wykorzystana do sygnalizowania

nieudanego wyszukiwania. Jeeli w przyszoci bdzie potrzebny bardziej wyszukany
mechanizm wyszukiwania wzorców, bdzie mona wymieni funkcj

strindex

na inn.

Reszta kodu pozostanie bez zmian (standardowa biblioteka zawiera funkcj

strstr

,

która jest podobna do

strindex

, ale zwraca wskanik zamiast indeksu).

Po takim przygotowaniu projektu napisanie waciwego programu jest ju czynnoci
stosunkowo prost. Poniej przedstawiono cao , zarówno gówny program, jak i sto-
sowane funkcje, aby Czytelnik móg wygodnie przeanalizowa ich wspódziaanie. W tej
wersji wyszukiwany cig jest literaem (sta), wic trudno mówi o ogólnoci rozwizania.
Do inicjalizowania tablic znakowych powrócimy ju wkrótce, natomiast w rozdziale 5.

background image

Rozdzia 4. • Funkcje i struktura programu

87

pokaemy, jak przeksztaci wzorzec w parametr przekazywany przy uruchamianiu
programu. Kod zawiera take nieco zmodyfikowan funkcj

getline

. Porównanie

jej z wersj z rozdziau 1. moe dostarczy wartociowych spostrzee.

#include <stdio.h>
#define MAXLINE 1000

/* dopuszczalna dugo wiersza */

int getline(char line[], int max)
int strindex(char source[], char searchfor[]);

char pattern[] = "ould";

/* wzorzec do wyszukania */

/* wyszukuje wszystkie wiersze zawierajce 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 dugo */
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, jeeli nie wystpuje */
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;
}

background image

Jzyk ANSI C. Programowanie

88

Definicja funkcji ma zawsze nastpujc posta :

typ_zwracany nazwa_funkcji(deklaracje_argumentów)
{
deklaracje i instrukcje
}

Róne elementy mona 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. Jeeli zwracany typ
danych nie zosta okrelony, kompilator przyjmuje, e jest to

int

.

Program to po prostu zbiór definicji zmiennych i funkcji. Komunikacja midzy funkcjami
odbywa si za porednictwem argumentów funkcji, wartoci zwracanych przez funkcje
i zmiennych zewntrznych. Funkcje mog by umieszczone w pliku ródowym w dowol-
nej kolejnoci, a program moe by podzielony na wiele plików ródowych, o ile tylko
kada funkcja znajduje si w caoci w jednym pliku.

Instrukcja

return

reprezentuje mechanizm zwracania wartoci z funkcji wywoywanej

do funkcji lub rodowiska wywoujcego. Po sowie

return

moe znajdowa si dowolne

wyraenie:

return wyraenie;

Jeeli to konieczne, warto wyraenia jest przeksztacana na typ zadeklarowany jako
zwracany przez funkcj. Wyraenie nastpujce po sowie

return

ujmuje si czsto

w nawiasy, ale nie jest to wymagane.

Funkcja wywoujca moe w kadym przypadku zignorowa zwracan warto . Co wicej,
wyraenie po sowie

return

nie jest elementem wymaganym. Gdy zostanie pominite,

funkcja nie bdzie zwracaa adnej wartoci. Sterowanie zostaje przekazane do funkcji
wywoujcej bez zwracania wartoci take po dojciu do kocowego nawiasu klamrowego.
Jest to dopuszczalne, ale — gdy funkcja z jednego miejsca zwraca warto , a z innego
nie — sygnalizuje wystpowanie nieprawidowoci w pracy programu. W kadym przy-
padku, w którym funkcja nie zwraca wartoci, próba jej odczytania prowadzi do uzy-
skania przypadkowych danych (mieci).

Program wyszukujcy cig zwraca z funkcji

main

informacj o przebiegu jego wykonywa-

nia, któr w tym przypadku jest liczba znalezionych wierszy. Warto ta moe by wyko-
rzystywana przez rodowisko, które wywoao program.

Mechanika kompilowania i adowania programu C, który zosta zapisany w wielu plikach
ródowych, róni si w zalenoci od systemu. Przykadowo w systemie UNIX zadanie
to realizuje wspomniane w rozdziale 1. polecenie

cc

. Zaómy, e trzy funkcje przykado-

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

background image

Rozdzia 4. • Funkcje i struktura programu

89

kompiluje trzy wymienione pliki, umieszcza kod obiektów w plikach main.o, getline.o
i strindex.o, a nastpnie aduje je wszystkie do pliku wykonywalnego o nazwie a.out.
W przypadku wystpienia bdu, na przykad w main.c, plik moe zosta skompilowany
ponownie niezalenie od innych i zaadowany razem z przygotowanymi wczeniej.
Umoliwia to polecenie

cc main.c getline.o strindex.o

Polecenie

cc

wykorzystuje rozszerzenia .c i .o do odróniania plików ródowych od plików

wynikowych.

wiczenie 4.1. Napisz funkcj

strrindex(s,t)

, która zwraca pozycj ostatniego wy-

stpienia

t

w

s

lub

-1

, jeeli wyszukiwany cig nie zosta znaleziony.

4.2. Zwracanie wartoci innych ni int

Dotychczas przykadowe funkcje albo nie zwracay adnej wartoci (

void

), albo zwracay

liczb

int

. Co z funkcjami zwracajcymi wartoci 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 cig

s

na jego odpowiednik typu zmiennoprze-

cinkowego, podwójnej precyzji. Funkcja

atof

jest rozwiniciem funkcji

atoi

, której

wersje zostay przedstawione w rozdziaach 2. i 3.

atof

zapewnia obsug opcjonalnego

znaku liczby oraz kropki dziesitnej, a take sytuacji, w których nie wystpuje cz
cakowita lub cz uamkowa wartoci. Przedstawiona tu wersja nie jest wysokiej jakoci
procedur konwersji danych wejciowych. Taka funkcja zajaby stanowczo zbyt wiele
miejsca. Dopracowan wersj

atof

zawiera standardowa biblioteka jzyka — jest ona

zdefiniowana w nagówku

<stdlib.h>

.

Przede wszystkim typ zwracanej wartoci, jeeli nie jest to

int

, musi zosta okrelony

w samej funkcji. Nazw typu umieszcza si przed nazw funkcji:

#include <ctype.h>

/* atof: konwertuje cig s na liczb double */
double atof(char s[])
{
double val, power;
int i, sign;

for (i = 0; isspace(s[i]); i++)

/* pomi biae znaki */

;
sign = (s[i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-')
i++;

background image

Jzyk ANSI C. Programowanie

90

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 wan rzecz jest to, e procedura wywoujca musi wiedzie , e funkcja

atof

zwraca warto inn ni

int

. Jedn z moliwoci zapewnienia tego jest jawne

zadeklarowanie

atof

w tej procedurze. Deklaracj tak wida w programie minimali-

stycznego kalkulatora (nadajcego si chyba tylko do podliczania wypat z bankomatu),
który sumuje pobieran z wejcia pojedyncz kolumn liczb. Liczby mog zawiera znak,
a po kadej jest drukowana suma pobranych ju wartoci:

#include <stdio.h>

#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. Jeeli definicja funkcji i jej wywoanie
w

main

maj niespójnie okrelone typy, a s w tym samym pliku ródowym, kompilator

zgosi bd. Jednak gdy (co jest bardziej prawdopodobne) funkcja

atof

bdzie kompilowana

niezalenie, brak zgodnoci nie zostanie wykryty, a funkcja zwróci liczb

double

, która

w

main

bdzie traktowana jako

int

— uzyskiwane wtedy wartoci bd niemal zupe-

nie przypadkowe.

background image

Rozdzia 4. • Funkcje i struktura programu

91

W wietle tego, co powiedzielimy o dopasowaniu deklaracji do definicji, moe si to
wydawa zaskakujce. Przyczyn takiej niezgodnoci jest zasada, e gdy brak prototypu
funkcji, jej deklaracja nastpuje automatycznie w chwili pierwszego uycia w wyra-
eniu, na przykad

sum += atof(line)

Jeeli nazwa, która nie zostaa wczeniej zadeklarowana, wystpuje w wyraeniu, a bez-
porednio po niej jest umieszczony otwierajcy znak nawiasu, nastpuje deklaracja na
podstawie kontekstu — dana nazwa jest uznawana za nazw funkcji, która zwraca
warto

int

. Nie s natomiast przyjmowane adne zaoenia dotyczce jej argumentów.

Co wicej, jeeli deklaracja funkcji nie zawiera argumentów, jak w instrukcji

double atof();

to równie wstrzymuje kompilator od przyjmowania zaoe dotyczcych argumentów.
Sprawdzanie poprawnoci parametrów zostaje cakowicie wyczone. Ta szczególna
interpretacja pustej listy argumentów ma umoliwi kompilowanie starszych programów
w jzyku C przez nowsze kompilatory. Nie naley jednak stosowa takiej skadni w no-
wych programach. Jeeli funkcja pobiera argumenty, deklarujemy je. Jeeli nie po-
biera adnych, uywamy typu

void

.

Jeli dysponujemy (waciwie zadeklarowan) funkcj

atof

, moemy wykorzysta j

do utworzenia prostej funkcji

atoi

(konwertujcej cig znaków na liczb

int

):

/* atoi: konwertuje cig s na liczb cakowit przy uyciu atof */
int atoi(char s[])
{
double atof(char s[]);

return (int) atof(s);
}

Zwró my uwag na struktur deklaracji i instrukcj

return

. Warto wyraenia w wierszu

return wyraenie;

zostaje przeksztacona na typ wartoci zwracanej przez funkcj przed wyjciem z tej
funkcji. Warto

atof

, typu

double

, jest konwertowana automatycznie na

int

po dojciu

do tego wiersza — funkcja

atoi

ma zwraca liczb cakowit. Operacja taka moe

prowadzi do utraty czci danych (czci uamkowej liczby), wic niektóre kompilatory
generuj po jej napotkaniu ostrzeenie. Operacja

(int)

jest jawn informacj o tym, e

konwersja typu jest zamierzona, dziki czemu ostrzeenie nie jest wywietlane.

wiczenie 4.2. Dodaj do funkcji

atof

moliwo obsugi notacji wykadniczej, postaci:

123.45e-6

gdzie po liczbie zmiennoprzecinkowej moe wystpi litera

e

lub

E

i wykadnik, z opcjo-

nalnym znakiem.

background image

Jzyk ANSI C. Programowanie

92

4.3. Zmienne zewntrzne

Program w jzyku C skada si ze zbioru obiektów zewntrznych — zmiennych i funkcji.
Wewntrz funkcji definiowane s obiekty wewntrzne, czyli jej argumenty i zmienne
lokalne. Zmienne zewntrzne s definiowane poza funkcjami, dziki czemu mog by
dostpne nie w jednej, ale w wielu funkcjach. Same funkcje s zawsze obiektami ze-
wntrznymi, poniewa jzyk C nie dopuszcza definiowania funkcji wewntrz funkcji.
Standardowo zewntrzne zmienne i funkcje maj t waciwo , e wszystkie odwoania
do nich, czyli takie, które uywaj tej samej nazwy, nawet w funkcjach kompilowanych
niezalenie, pozostaj odwoaniami do tego samego obiektu (norma okrela t waciwo
terminem „dowizywanie obiektów zewntrznych”, ang. external linkage). Pod tym
wzgldem zmienne zewntrzne zachowuj si tak jak bloki

COMMON

jzyka Fortran lub

zmienne w najbardziej zewntrznym bloku w jzyku Pascal. Wkrótce pokaemy, jak
definiowa zmienne i funkcje zewntrzne, które s widoczne jedynie w obrbie po-
jedynczego pliku ródowego.

Poniewa zmienne zewntrzne s dostpne globalnie, stanowi alternatyw dla ar-
gumentów i wartoci zwracanych przez funkcje — równie umoliwiaj wymian danych
midzy funkcjami. Kada funkcja moe uzyska dostp do zmiennej zewntrznej przy
uyciu jej nazwy, o ile tylko nazwa ta zostaa wczeniej w pewien sposób zadeklarowana.

Jeeli funkcje maj korzysta wspólnie z wielu rónych zmiennych, zmienne zewntrzne
s wygodniejsze i efektywniejsze ni dugie listy argumentów. Jak jednak pisalimy
w rozdziale 1., korzystanie z tej moliwoci powinno wiza si z pewn ostronoci,
moe mie bowiem zy wpyw na struktur programu i prowadzi do kodu z nad-
miernie zoon sieci powiza midzy funkcjami.

Zmienne zewntrzne znajduj take zastosowania wynikajce bezporednio z ich wik-
szego zakresu i duszego „czasu ycia”. Zmienne automatyczne to wewntrzne obiekty
funkcji. Powstaj w chwili wejcia do funkcji i zostaj zlikwidowane w chwili wyjcia
z niej. Zmienne zewntrzne s trwae, zachowuj swoj warto pomidzy wywoaniami
rónych funkcji. Jeeli wic dwie funkcje musz korzysta z tych samych danych, a nie
wystpuje sytuacja, w której jedna z nich wywouje drug, zapisanie wspólnych danych
w zmiennych zewntrznych jest czsto najwygodniejszym rozwizaniem, pozwalajcym
unikn wprowadzania dodatkowego mechanizmu przekazywania wartoci do i z kadej
ze wspódziaajcych funkcji.

Przeanalizujmy to zagadnienie na konkretnym przykadzie. Naszym zadaniem jest napi-
sanie programu kalkulatora, który umoliwia korzystanie z operatorów

+

,

-

,

*

i

/

. Po-

niewa jest to prostsze w implementacji, kalkulator bdzie korzysta z odwrotnej notacji
polskiej, a nie notacji infiksowej (odwrotna notacja polska jest uywana przez niektóre
kalkulatory kieszonkowe oraz jzyki programowania, na przykad Forth i PostScript).

W odwrotnej notacji polskiej wszystkie operandy poprzedzaj operator. Wyraenie infik-
sowe, na przykad

(1 – 2) * (4 + 5)

background image

Rozdzia 4. • Funkcje i struktura programu

93

jest wprowadzane jako

1 2 – 4 5 + *

Nawiasy nie s potrzebne. Notacja jest jednoznaczna, o ile tylko liczba operandów kadego
operatora jest staa.

Implementacja jest prosta. Kady operand zostaje umieszczony na stosie. Po pobraniu
operatora program zdejmuje ze stosu waciw liczb operandów (dwa w przypadku
operatorów binarnych), wykonuje operacj i zapisuje wynik ponownie na stosie. W po-
wyszym przykadzie oznacza to umieszczenie na stosie liczb 1 i 2, nastpnie zastpienie
ich rónic, –1. W kolejnym kroku na stos trafiaj liczby 4 i 5, które zostaj nastpnie
zastpione sum, 9. Kolejna operacja to mnoenie, wic ze stosu zostaj pobrane wartoci
–1 i 9, które zastpuje nastpnie ich iloczyn, –9. Na zakoczenie, po dojciu do koca
wiersza, warto ze szczytu stosu zostaje wypisana na ekranie.

Struktura programu jest wic ptl, która wykonuje odpowiednie operacje na pobiera-
nych kolejno operatorach i operandach:

while (nastpny operator lub operand nie jest znakiem koca 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
bd

Operacje umieszczania danych na stosie i zdejmowania z niego s banalne, ale do czasu
uzupenienia programu o wykrywanie i obsug bdów pozostaj wystarczajco zoone,
aby uzasadniao to umieszczenie ich w osobnych funkcjach. Pozwoli to przede wszystkim
unikn powtarzania kodu. Równie odrbna funkcja powinna odpowiada za pobieranie
kolejnego operatora lub operandu.

Gównym zaoeniem projektowym jest to, gdzie konkretnie jest stos, a waciwie
— które procedury maj do niego bezporedni dostp. Jedn z moliwoci jest pozosta-
wienie jego obsugi w

main

. Mona przekazywa stos i biec pozycj stosu do procedur,

które pobieraj i zapisuj wartoci. Jednak w funkcji

main

nie s potrzebne zmienne

sterujce stosem. Wykonuje ona tylko operacje zapisania danych i odczytania ich. Zdecy-
dowalimy wic o przechowywaniu stosu i zwizanych z nim informacji w zmiennych
zewntrznych, dostpnych funkcjom

push

i

pop

, ale nie

main

.

Zapisanie takiego projektu w postaci kodu nie jest trudne. Jeeli mamy zapisa program
w jednym pliku ródowym, bdzie on wyglda tak:

#include ...

/* wiersze include */

#define ...

/* wiersze define */

background image

Jzyk ANSI C. Programowanie

94

deklaracje funkcji dla main
main() { ... }

zewntrzne zmienne dla push i pop
void push(double f) { ... }
double pop(void) { ... }

int getop(char s[]) { ... }

procedury wywoywane przez getop

Zagadnieniem dzielenia programu na dwa pliki ródowe lub wicej zajmiemy si ju
niedugo.

Funkcja

main

to ptla zawierajca rozbudowan instrukcj

switch

, która rozgazia

sterowanie w zalenoci od typu operatora lub operandu. Jest to bardziej typowy przy-
kad jej uycia ni ten przedstawiony w podrozdziale 3.4.

#include <stdio.h>
#include <stdlib.h>

/* 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 '/':

background image

Rozdzia 4. • Funkcje i struktura programu

95

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 dziaa przemiennych, kolejno zdejmowania operandów ze

stosu nie ma znaczenia. Jednak w przypadku operatorów

i

/

musi istnie rozrónienie

midzy wartoci po lewej stronie znaku i wartoci po prawej stronie znaku. W instrukcji

push(pop() – pop());

/* B D */

kolejno obliczania wartoci wywoa

pop

nie jest okrelona. Aby zagwarantowa wa-

ciw, konieczne jest wczeniejsze pobranie pierwszej wartoci do zmiennej tymczasowej.
Wida to w kodzie funkcji

main

.

#define MAXVAL 100

/* dopuszczalna gboko stosu wartoci */

int sp = 0;

/* nastpna 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 wierzchoka stosu */
double pop(void)
{
if (sp > 0)
return val[--sp];
else {
printf("error: stack empty\n");
return 0.0;
}
}

background image

Jzyk ANSI C. Programowanie

96

Zmienna jest zmienn zewntrzn, jeeli jest zdefiniowana poza funkcj, tak wic
wspóuytkowane przez funkcje

pop

i

push

zmienne stosu i indeksu stosu zostaj zde-

finiowane poza tymi funkcjami. Jednak funkcja

main

nie odwouje si do stosu ani jego

indeksu — reprezentacja moe pozosta ukryta.

Przejdmy teraz do implementacji

getop

, funkcji, która pobiera kolejny operator lub ope-

rand. Zadanie jest proste. Pomijamy spacje i tabulatory. Jeeli nastpny znak nie jest
cyfr lub kropk dziesitn, zwracamy go. W pozostaych przypadkach pobieramy cig
cyfr (który moe zawiera kropk dziesitn) i zwracamy

NUMBER

, czyli warto sygnalizu-

jc, e pobrana zostaa liczba.

#include <ctype.h>

int getch(void);
void ungetch(int);

/* getop: pobiera nastpny 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 cakowit */

while (isdigit(s[++i] = c = getch()))
;
if (c == '.')

/* pobierz cz uamkow */

while (isdigit(s[++i] = c = getch()))
;
s[i] = '\0';
if (c != EOF)
ungetch(c);
return NUMBER;
}

Co to za funkcje

getch

i

ungetch

? Czsto zdarza si, e program nie moe okreli , czy

odczyta wystarczajc ilo danych wejciowych a do momentu, gdy odczyta ich zbyt
duo. Takim przypadkiem jest wanie odczytywanie znaków tworzcych 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 duo, znak, na który nie
jest przygotowany.

Problem byby rozwizany, gdyby istniaa moliwo cofnicia operacji odczytu ostatniego
znaku danych wejciowych. Wówczas program, który odczyta o jeden znak za duo,
mógby „odda ” ten znak do strumienia, a inne elementy programu dziaayby tak,

background image

Rozdzia 4. • Funkcje i struktura programu

97

jak gdyby znak ten nigdy nie by odczytywany. Okazuje si, e skonstruowanie takiego
mechanizmu nie jest trudne, wystarczy para wspópracujcych ze sob funkcji.

getch

zwraca kolejny znak danych wejciowych.

ungetch

zapamituje znaki zwrócone na

wejcie w taki sposób, aby dalsze wywoania

getch

zwracay 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, jeeli nie jest on pusty. W pozo-

staych przypadkach wywouje po prostu funkcj

getchar

. Niezbdna jest równie

zmienna indeksujca, która rejestruje pozycj biecego znaku w buforze.

Poniewa bufor i indeks wykorzystuj dwie funkcje,

getch

i

ungetch

, a wartoci tych

zmiennych musz zosta zachowane midzy wywoaniami, konieczne jest uycie
zmiennych zewntrznych. Obie funkcje i deklaracje zmiennych mona zapisa tak:

#define BUFSIZE 100

char buf[BUFSIZE];

/* bufor dla ungetch */

int bufp = 0;

/* nastpna wolna pozycja w buforze */

int getch(void)

/* pobiera znak (moe by znakiem wczeniej wycofanym) */

{
return (bufp > 0) ? buf[--bufp] : getchar();
}

void ungetch(int c)

/* wycofuje znak do strumienia danych wejciowych */

{
if (bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}

Standardowa biblioteka zawiera funkcj

ungetc

, która umoliwia wycofanie jednego znaku.

Omówimy j w rozdziale 7. W powyszym przykadzie uylimy tablicy, a nie poje-
dynczego znaku, aby zaprezentowa bardziej ogólne podejcie.

wiczenie 4.3. W oparciu o schemat przedstawiony w przykadach program kalkulatora
mona atwo rozbudowywa . Dodaj obsug operatora modulo (

%

) i obsug liczb ujemnych.

wiczenie 4.4. Utwórz polecenie wypisujce element na wierzchoku stosu bez jego
usuwania ze stosu, polecenie duplikujce element na wierzchoku stosu, polecenie
zamieniajce miejscami dwa górne elementy oraz polecenie usuwajce ca zawarto
stosu.

wiczenie 4.5. Dodaj dostp do funkcji biblioteki, takich jak

sin

,

exp

, i

pow

. Patrz

<math.h>

w czci 4. dodatku B.

background image

Jzyk ANSI C. Programowanie

98

wiczenie 4.6. Dodaj polecenia obsugi zmiennych (atwo jest zapewni moliwo
korzystania z dwudziestu szeciu zmiennych przy uyciu jednoliterowych nazw). Dodaj
zmienn przechowujc ostatni wypisan warto .

wiczenie 4.7. Napisz procedur

ungets(s)

, która zwraca do danych wejciowych

cay cig 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

, przyjwszy zaoenie, e nigdy nie

bdzie wycofywany wicej ni jeden znak.

wiczenie 4.9. Nasze funkcje

getch

i

ungetch

nie obsuguj 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 wejciowymi opiera si na
uyciu

getline

w celu pobrania caego wiersza. Dziki temu funkcje

getch

i

ungetch

nie

s potrzebne. Przekszta kalkulator, tak aby jego praca opieraa si na takim podejciu do
danych wejciowych.

4.4. Zakres

Funkcje i zmienne zewntrzne tworzce program w jzyku C nie musz by kompi-
lowane jednoczenie. ródowy tekst programu mona przechowywa w wielu plikach,
a wczeniej skompilowane procedury mog by adowane z bibliotek. Wie si to z kil-
koma istotnymi pytaniami:

Q

Jak zapisywa deklaracje, aby deklarowanie zmiennych waciwie przebiegao
w czasie kompilacji?

Q

Jaki powinien by ukad deklaracji, aby wszystkie elementy zostay waciwie
poczone w chwili adowania programu?

Q

Jaki ukad deklaracji zapewnia, e nie s one powtarzane?

Q

Jak inicjuje si zmienne zewntrzne?

Omówimy te zagadnienia na przykadzie programu kalkulatora, który teraz podzielony
zostanie na kilka plików. Z praktycznego punktu widzenia jest to zbyt may program, aby
faktycznie warto byo go dzieli , jednak wystarczy on do zilustrowania problemów, które
pojawiaj si w wikszych projektach.

Zakres (ang. scope) nazwy to cz programu, w której nazw t mona stosowa . Dla
zmiennej automatycznej, deklarowanej na pocztku funkcji, zakresem jest funkcja,
w której zmienna zostaa zadeklarowana. Zmienne lokalne o tej samej nazwie, ale
w rónych funkcjach nie maj ze sob adnego zwizku. To samo mona powiedzie
o parametrach funkcji — s one w praktyce zmiennymi lokalnymi.

background image

Rozdzia 4. • Funkcje i struktura programu

99

Zakres zmiennej zewntrznej lub funkcji siga od punktu jej zadeklarowania do koca
kompilowanego pliku. Jeeli na przykad

main

,

sp

,

val

,

push

i

pop

s zdefiniowane

w jednym pliku, w kolejnoci przedstawionej wczeniej, czyli

main() { ... }

int sp = 0;
double val[MAXVAL];

void push(double f) { ... }

double pop(void) { ... }

to zmienne

sp

i

val

mona stosowa w funkcjach

push

i

pop

, po prostu wymieniajc ich

nazw. Nie s wymagane dodatkowe deklaracje. Jednak nazwy te nie s widoczne w

main

,

podobnie jak funkcje

push

i

pop

.

Z drugiej strony, jeeli odwoania do zmiennej zewntrznej maj wystpi przed jej
zdefiniowaniem lub zmienna ta jest definiowana w innym pliku ródowym ni ten,
w którym jest wykorzystywana, konieczne staje si uycie deklaracji

extern

.

Wane jest, aby rozrónia deklaracj zmiennej zewntrznej od jej definicji. Deklaracja
informuje o waciwociach zmiennej (przede wszystkim jej typie). Definicja powoduje
dodatkowo przydzielenie pamici. Jeeli wiersze

int sp;
double val[MAXVAL];

pojawiaj si poza funkcjami, s to definicje zmiennych zewntrznych

sp

i

val

. Powoduj

one przydzielenie pamici, peni take funkcje deklaracji dla kodu w pozostaej czci
pliku ródowego. Z drugiej strony wiersze

extern int sp;
extern double val[];

deklaruj na potrzeby kodu w dalszej czci pliku, e

sp

ma typ

int

, a

val

to tablica liczb

double

(której rozmiar jest okrelony gdzie indziej). Nie tworz one jednak zmiennych

i nie rezerwuj pamici.

We wszystkich plikach tworzcych program ródowy moe wystpi tylko jedna definicja
zmiennej zewntrznej. Inne pliki mog zawiera deklaracje

extern

umoliwiajce do-

stp do tej zmiennej (deklaracje

extern

mog znale si take w pliku zawierajcym

definicj). Rozmiar tablicy musi zosta okrelony w definicji, a w deklaracji

extern

jest opcjonalny.

Inicjalizacja zmiennej zewntrznej moe zosta poczona tylko z jej definicj.

Cho w tym przypadku ukad 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 powizanie

zostanie zapewnione przez nastpujcy ukad definicji i deklaracji:

background image

Jzyk ANSI C. Programowanie

100

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 mona

w tych funkcjach stosowa . Jedna para deklaracji wystarczy dla zapewnienia dostp-
noci zmiennych w caym pliku file1. Taki sam ukad naleaoby zastosowa , gdyby
definicje

sp

i

val

znajdoway si w tym samym pliku, ale po definicjach funkcji, w których

s stosowane.

4.5. Pliki nagówkowe

Rozwamy podzielenie programu kalkulatora na kilka plików ródowych. Mogoby to
by potrzebne, gdyby poszczególne jego komponenty zostay 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 pozostaych, poniewa w rzeczywistym programie byyby czci odrbnie
kompilowanej biblioteki.

Pozostaje jeden problem do rozwizania — definicje i deklaracje elementów wyko-
rzystywanych w wicej ni jednym pliku. Dymy do maksymalnej centralizacji bu-
dowanego systemu, aby kada z jego czci miaa tylko jedno waciwe miejsce, nieule-
gajce zmianie w toku dalszej ewolucji kodu. Aby osign ten cel, umieszczamy wspólne
elementy w pliku nagówkowym (ang. header file, najczciej nazywany krótko nagów-
kiem), calc.h. Plik ten bdzie wczony do kodu plików, które korzystaj z jego zawartoci,
dyrektyw

#include

. Dyrektyw t opiszemy dokadnie w podrozdziale 4.11. Program

wyglda tak:

background image

Rozdzia 4. • Funkcje i struktura programu

101

Mamy tu do czynienia z problemem wywaenia midzy deniem do tego, aby kady plik
mia dostp wycznie do tych informacji, które s mu niezbdne, a prozaiczn potrzeb
codziennej praktyki — praca ze zbyt du liczb plików nagówka jest uciliwa. Do
pewnych granic dobrym rozwizaniem jest stosowanie jednego nagówka dla caego
programu zawierajcego wszystko, co jest uywane przez wicej ni jedn jego cz .
Takie rozwizanie zastosowalimy w przykadzie. Wiksze programy wymagaj bardziej
rozbudowanej struktury i wikszej liczby nagówków.

4.6. Zmienne statyczne

Zmienne

sp

i

val

w pliku stack.c oraz

buf

i

bufp

w pliku getch.c su do prywatnego

uytku przez funkcje znajdujce si w tym samym pliku ródowym. adne inne nie po-
winny mie do nich dostpu. Deklaracja

static

zastosowana w odniesieniu do zmiennej

zewntrznej lub funkcji ogranicza zakres obiektu do pozostaej czci kompilowanego
pliku ródowego. Zewntrzna deklaracja

static

jest wic metod ukrywania nazw takich

jak

buf

i

bufp

— nazw, które musz by zewntrzne, bo s wspóuytkowane przez

róne funkcje, ale nie powinny by widoczne dla kodu wywoujcego te funkcje.

Statyczne przechowywanie zmiennych okrelamy, wstawiajc na pocztku zwykej
deklaracji sowo

static

. Jeeli dwie procedury i dwie zmienne s kompilowane w tym

samym pliku, jak w przykadzie

static char buf[BUFSIZE];

/* bufor dla ungetch */

static int bufp = 0;

/* nastpna wolna pozycja w buforze */

int getch(void) { ... }

void ungetch(int c) { ... }

background image

Jzyk ANSI C. Programowanie

102

to adna inna procedura nie ma dostpu 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 mona ukry zmienne wykorzystywane przez funkcje

push

i

pop

do

obsugi stosu — deklarujc

sp

i

val

jako

static

.

Zewntrzna deklaracja

static

jest najczciej stosowana w odniesieniu do zmiennych,

ale moe by uyta take w odniesieniu do funkcji. Normalnie nazwy funkcji maj
charakter globalny — s widoczne w caym programie. Jeeli jednak funkcja jest za-
deklarowana jako

static

, jej nazwa nie jest widoczna poza plikiem, w którym zostaa

zadeklarowana.

Deklaracji

static

mona take uy w odniesieniu do zmiennych wewntrznych. We-

wntrzne zmienne

static

pozostaj zmiennymi lokalnymi funkcji, podobnie jak zmienne

automatyczne, jednak w przeciwiestwie do zmiennych automatycznych nie prze-
staj istnie w chwili wyjcia z funkcji. W efekcie wewntrzne zmienne statyczne to
prywatna pami trwaa pojedynczej funkcji.

wiczenie 4.11. Zmodyfikuj funkcj

getop

w taki sposób, aby nie korzystaa z funkcji

ungetch

. Wskazówka: uyj wewntrznej zmiennej statycznej.

4.7. Zmienne rejestrowe

Deklaracja

register

zwraca uwag kompilatora na to, e dana zmienna bdzie wyjt-

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 moe, ale nie musi, dostosowa si do takie-
go zalecenia.

Oto przykady deklaracji

register

:

register int x;
register char c;

Deklaracje takie mona stosowa wycznie w odniesieniu do zmiennych automatycz-
nych i parametrów formalnych funkcji. W przypadku parametrów formalnych wyglda
to tak:

f(register unsigned m, register long n)
{
register int i;
...
}

W praktyce zmienne rejestrowe podlegaj pewnym ograniczeniom wynikajcym z mo-
liwoci wykorzystywanej platformy sprztowej. Tylko kilka zmiennych w kadej funkcji
mona przechowywa w rejestrach i tylko wybrane typy s dopuszczalne. Nadmiar
deklaracji

register

jest jednak nieszkodliwy, poniewa w przypadku zbyt duej liczby

background image

Rozdzia 4. • Funkcje i struktura programu

103

tak opisanych zmiennych lub niezgodnoci typów sowo

register

jest ignorowane. Dodat-

kowo nie mona pobra adresu zmiennej rejestrowej (ten temat omówimy w rozdziale 5.),
niezalenie od tego, czy zostaa ona faktycznie umieszczona w rejestrze. Zakres ograni-
cze co do typów i liczby zmiennych rejestrowych jest zaleny od komputera.

4.8. Struktura blokowa

Jzyk C nie jest jzykiem, w którym struktura programu opiera si na blokach, jak
jest na przykad w Pascalu — nie mona definiowa funkcji wewntrz funkcji. Mimo to
struktura blokowa obowizuje przy definiowaniu zmiennych. Deklaracje zmiennych
(i ich inicjalizacja) mog zosta umieszczone po nawiasie klamrowym otwierajcym
dowoln instrukcj blokow, a nie tylko po nawiasie klamrowym otwierajcym blok in-
strukcji funkcji. Zmienne deklarowane w ten sposób przesaniaj zmienne o takich samych
nazwach wystpujce poza blokiem, a ich „czas ycia” koczy si wraz z wyjciem
z bloku. Na przykad w kodzie

if (n > 0) {
int i;

/* deklaracja nowej zmiennej i */

for (i = 0; i < n; i++)
...
}

zakres zmiennej

i

to blok wykonywany przy wartoci warunku „prawda”. Zmienna ta nie

ma adnych powiza 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 kadym wejciu do tego bloku. Analogiczna zmienna

static

jest

inicjalizowana przy pierwszym wejciu do bloku.

Zmienne automatyczne, w tym parametry formalne, równie przesaniaj zmienne
zewntrzne i funkcje o tej samej nazwie. W ukadzie deklaracji

int x;
int y;

f(double x)
{
double y;
...
}

wewntrz funkcji

f

wszystkie wystpienia

x

odnosz si do parametru (typu

double

). Poza

funkcj

f

nazwa zmiennej

x

odnosi si do liczby

int

, zmiennej zewntrznej. To samo

mona powiedzie o zmiennej

y

.

Do dobrej praktyki programowania naley unikanie stosowania nazw zmiennych, które
przesaniaj nazwy uywane w szerszym zakresie. Jest to bowiem najkrótsza droga do
pomyek i bdów.

background image

Jzyk ANSI C. Programowanie

104

4.9. Inicjalizacja

O inicjalizacji wspominalimy ju kilkukrotnie, ale zawsze pozostawaa ona na margi-
nesie innych tematów. W tym podrozdziale, po omówieniu rónych klas pamici da-
nych, moemy przej do usystematyzowania regu tego procesu.

Gdy brak jawnej inicjalizacji, zmienne zewntrzne i statyczne maj warto 0, a zmienne
automatyczne i rejestrowe pozostaj niezdefiniowane — nie zawieraj uytecznej wartoci.

Zmienne skalarne mona inicjalizowa przy ich definiowaniu — wystarczy wprowadzi
po ich nazwie znak równoci i wyraenie:

int x = 1;
char squota = '\'';
long day = 1000L * 60L * 60L * 24L;

/* milisekund/dzie */

Warto inicjalizujca zmienne zewntrzne i statyczne musi by wyraeniem o staej
wartoci. Inicjalizacja jest wykonywana jednokrotnie, jeszcze przed rozpoczciem wa-
ciwego procesu wykonywania programu. Inicjalizacja zmiennych automatycznych i reje-
strowych nastpuje przy kadym wejciu wykonywanego programu do funkcji lub bloku.

Warto inicjalizujca zmienne automatyczne i rejestrowe nie musi by staa — moe
to by dowolne wyraenie oparte na wartociach wczeniej zdefiniowanych, a nawet
wywoaniach funkcji. Przykadowo inicjalizacja programu wyszukiwania binarnego
z podrozdziau 3.3 moe by zapisana nastpujco:

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 czca instrukcje deklaracji i przypisania. Wybór jest kwesti stylu. W ksice
z zasady nie czymy deklaracji i przypisania, poniewa warto pocztkowa okrelona
w bloku deklaracji jest atwa do przeoczenia, a odrbne przypisanie moe nastpi
w miejscu, w którym zmienna jest wykorzystywana.

Tablic mona zainicjalizowa , umieszczajc po deklaracji list wartoci elementów
— ujt w nawiasy klamrowe i rozdzielan przecinkami. Aby na przykad zainicjalizowa
tablic

days

dugociami miesicy, piszemy:

int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }

background image

Rozdzia 4. • Funkcje i struktura programu

105

Gdy rozmiar tablicy nie jest okrelony, kompilator okrela j, zliczajc wartoci pocztko-
we elementów. W tym przypadku jest ich 12.

Jeeli lista pocztkowych wartoci elementów tablicy zawiera mniej elementów ni
tablica, pozostaym przypisywana jest warto 0. Dotyczy to zmiennych zewntrznych,
statycznych i automatycznych. Podanie zbyt dugiej listy wartoci jest bdem. Nie ma
skadni umoliwiajcej powtarzanie wartoci na licie albo inicjalizowanie elementów
wewntrznych bez podania wartoci wszystkich elementów poprzedzajcych.

Tablice znaków s traktowane w sposób szczególny. W miejsce nawiasów klamrowych
i rozdzielonej przecinkami listy mona uy cigu:

char pattern = "ould";

Jest to skrót duszej, cho równowanej konstrukcji:

char pattern[] = { 'o', 'u', 'l', 'd', '\0' };

W tym przypadku rozmiar tablicy to 5 (cztery znaki plus kocowa staa

'\0'

).

4.10. Rekurencja

Funkcje jzyka C mog by wywoywane rekurencyjnie. Oznacza to, e funkcja moe,
bezporednio lub porednio, wywoa siebie sam. Rozwamy przykad wypisywania
liczby jako cigu znaków. Jak pisalimy wczeniej, cyfry s wypisywane w niewaciwej
kolejnoci — mniej znaczce s dostpne przed bardziej znaczcymi. Kolejno ich
wypisywania musi by odwrotna.

S dwa rozwizania tego problemu. Pierwszym jest zapisanie cyfr w tablicy i odwrócenie
kolejnoci zapisanych elementów. Tak zrobilimy w przykadowej funkcji

itoa

w pod-

rozdziale 3.6. Alternatyw stanowi rozwizanie rekurencyjne, w którym funkcja

printd

rozpoczyna prac od wywoania samej siebie w celu wywietlenia cyfr bardziej znacz-
cych ni cyfra aktualnie przetwarzana. Dopiero po powrocie z wywoanej funkcji wy-
pisywana jest cyfra bieca. Poniej przedstawiamy tak funkcj, ponownie w wersji
niezapewniajcej poprawnego przetwarzania najwikszej liczby ujemnej.

#include <stdio.h>

/* printd: wypisuje n jako liczb dziesitn */
void printd(int n)
{
if (n < 0) {
putchar('-');
n = -n;
}
if (n / 10)

background image

Jzyk ANSI C. Programowanie

106

printd(n / 10);
putchar(n % 10 + '0');
}

Gdy funkcja wywouje rekurencyjnie sam siebie, kade wywoanie otrzymuje nowy
zestaw wszystkich zmiennych automatycznych, cakowicie niezaleny od wczeniejszego.
W efekcie po wywoaniu

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 koczy prac. Wówczas funkcja na drugim poziomie

wypisuje znak

2

i równie koczy prac. Funkcja najwyszego poziomu wypisuje

3

i prze-

twarzanie pocztkowego wywoania

printd(123)

zostaje zakoczone.

Innym ciekawym przykadem rekurencji jest algorytm sortowania quicksort, opraco-
wany przez C.A.R. Hoare’a w 1962 roku. Z tablicy wybierany jest jeden element, a pozo-
stae zostaj podzielone na dwa podzbiory — elementów mniejszych oraz elementów
wikszych lub równych. Ten sam proces jest nastpnie powtarzany rekurencyjnie dla
kadego z podzbiorów. Gdy podzbiór ma mniej ni dwa elementy, dalsze sortowanie
nie jest potrzebne i proces rekurencji zostaje zakoczony.

Nasza wersja programu sortujcego metod quicksort nie jest najszybsza, ale za to jest
jedn z najprostszych. Podzia bazuje na rodkowym elemencie kadej podtablicy.

/* qsort: sortuje v[left]...v[right] rosnco */
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, jeeli 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);
}

Przenielimy operacj zamieniania elementów miejscami do osobnej funkcji

swap

— jest przecie wywoywana w trzech miejscach.

/* swap: zamienia miejscami v[i] i v[j] */
void swap(int v[], int i, int j)
{
int temp;

temp = v[i];

background image

Rozdzia 4. • Funkcje i struktura programu

107

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 oszczdzania pamici — stos wykorzystywanych
przez kolejne poziomy wywoa wartoci musi by gdzie przechowywany. Nie jest te
rozwizaniem szybszym. Jednak kod rekurencyjny jest bardziej zwarty i czsto 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 przykad znajdziemy w podrozdziale 6.5.

wiczenie 4.12. Zaadaptuj koncepcj funkcji

printd

do napisania rekurencyjnej wersji

funkcji

itoa

. Innymi sowy, przekszta liczb cakowit na cig znaków, wywoujc

procedur rekurencyjn.

wiczenie 4.13. Napisz rekurencyjn wersj funkcji

reverse(s)

, odwracajcej „w miejscu”

cig znaków

s

.

4.11. Preprocesor jzyka C

Jzyk C realizuje pewne mechanizmy jzykowe za porednictwem preprocesora. Jest to
pierwszy krok wykonywany przed waciwym procesem kompilacji. Dwie najczciej
stosowane dyrektywy preprocesora to

#include

, wczajca do procesu kompilacji

zawarto innego pliku, i

#define

, zastpujca nazw wskazanym cigiem znaków. W tym

podrozdziale opiszemy te inne moliwoci preprocesora: kompilacj warunkow i makra
z argumentami.

4.11.1. Wstawianie plików

Mechanizm wstawiania plików uatwia przede wszystkim obsug zbiorów dyrektyw

#define

i deklaracji. Kady wiersz postaci

#include "nazwa_pliku"

lub

#include <nazwa_pliku>

zostaje zastpiony zawartoci pliku nazwa_pliku. Jeeli nazwa pliku jest ujta w cudzy-
sów, wyszukiwanie pliku rozpoczyna si najczciej w katalogu programu ródowego.
Jeeli plik nie zostanie w nim znaleziony albo gdy zamiast cudzysowu uyto znaków

<

i

>

, wyszukiwanie przebiega zgodnie z zasadami okrelonymi przez implementacj.

Wczane dyrektyw

#include

pliki mog take zawiera wiersze

#include

.

background image

Jzyk ANSI C. Programowanie

108

Na pocztku pliku ródowego znajduje si najczciej caa grupa wierszy

#include

,

które wczaj do programu podstawowe instrukcje

#define

i deklaracje

extern

. Mog

równie zapewnia dostp do deklaracji prototypów funkcji bibliotecznych, zapisanych
w nagówkach takich jak

<stdio.h>

(cilej: nagówki nie musz by plikami; zasady

dostpu do nagówków wyznacza implementacja).

Wczanie wierszem

#include

to podstawowa metoda czenia deklaracji w duych

programach. Gwarantuje ona, e wszystkie pliki ródowe bd miay dostp do tych
samych definicji i deklaracji zmiennych. Eliminuje to jeden z najbardziej uciliwych
rodzajów bdów w kodzie. Naturalnie gdy wczany plik ulega zmianie, wszystkie zale-
ne od niego pliki programu musz by kompilowane ponownie.

4.11.2. Makra

Definicja ma posta :

#define nazwa tekst_zastpujcy

Mamy tu do czynienia z najprostsz postaci makra, opart na substytucji — wszystkie
dalsze wystpienia

nazwa

zostan zastpione przez

tekst_zastpujcy

. Nazwa w

#define

ma tak sam posta jak nazwa zmiennej. Tekst zastpujcy moe by dowolny. Nor-
malnie s to wszystkie znaki do koca wiersza, ale duga definicja moe zosta podzielona
na kilka kolejnych wierszy przez wstawienie znaku

\

na kocu kadego wiersza, który ma

by kontynuowany. Zakres nazwy wskazanej w

#define

siga od wiersza

#define

do koca

kompilowanego pliku ródowego. Definicja moe korzysta z wczeniejszych definicji.
Substytucja nie obejmuje miejsc, w których nazwa jest czci duszej nazwy i frag-
mentów ujtych w cudzysów. Po zdefiniowaniu na przykad nazwy

YES

substytucja nie

nastpi w

printf("YES")

ani w

YESMAN

.

Zastpujcy nazw tekst moe by dowolny. Na przykad

#define forever for (;;)

/* ptla niesko czona */

definiuje nowe sowo,

forever

, które bdzie zastpowane ptl nieskoczon.

Mona take definiowa makra z argumentami, dziki którym tekst zastpujcy jest
róny w poszczególnych wywoaniach makra. Przykadem moe by makro

max

:

#define max(A, B) ((A) > (B) ? (A) : (B))

Cho wyglda jak wywoanie funkcji, uycie

max

sprowadza si do rozwinicia nazwy w kod

wstawiany wewntrz wiersza. Kade wystpienie parametru formalnego (tutaj

A

i

B

)

zostanie zastpione podanym argumentem. Tak wic wiersz

x = max(p+q, r+s);

przyjmie posta

x = ((p+q) > (r+s) ? (p+q) : (r+s));

background image

Rozdzia 4. • Funkcje i struktura programu

109

Dopóki argumenty s spójne, makro

max

moe pracowa z dowolnym typem danych.

Nie ma potrzeby definiowania rónych nazw

max

dla rónych typów danych, tak jakby

to byo w przypadku zastosowania funkcji.

Gdy przyjrzymy si sposobowi rozwijania makra

max

, zwrócimy uwag, e wie si

on z pewnymi puapkami. Wartoci wyrae s obliczane dwukrotnie. Staje si to istot-
nym problemem, gdy pojawiaj si efekty uboczne wynikajce ze stosowania operatorów
zwikszania i zmniejszania albo operacji wejcia-wyjcia. Przykadowo

max(i++, j++)

/* B D */

prowadzi do dwukrotnego zwikszenia wikszej wartoci. Czsto warto zadba o ujcie
wyraenia w nawiasy, aby zapewni waciw kolejno wykonywania oblicze. Pomyl-
my, co si stanie, gdy makro

#define square(x) x * x

/* B D */

zostanie wywoane w wyraeniu

square(z+1)

.

Makra s bardzo wartociowym narzdziem. Jednym z praktycznych przykadów ich
zastosowania jest wczanie do kompilacji pliku

<stdio.h>

, w którym operacje

getchar

i

putchar

s czsto zdefiniowane jako makra. Pozwala to unikn obcienia programu

mechanizmem wywoywania funkcji przy odczycie pojedynczych znaków. Równie
funkcje w

<ctype.h>

s zazwyczaj implementowane jako makra.

Definicje nazw mona wycofywa dyrektyw

#undef

. Moliwo t wykorzystuje si

czsto w celu uzyskania gwarancji, e dana procedura bdzie funkcj, a nie makrem:

#undef getchar

int getchar(void) { ... }

Parametry formalne nie s zastpowane w cigach znakowych otoczonych znakami
cudzysowu. Jeeli jednak nazw parametru poprzedza w tekcie zastpujcym znak

#

,

to zostanie on zamieniony na ujty w cudzysów cig znaków, w którym parametr jest
zastpiony podanym argumentem faktycznym. W poczeniu z konkatenacj cigów
pozwala to na przykad utworzy nastpujce makro wywietlajce wartoci potrzebne
w procesie debugowania:

#define dprint(expr) printf(#expr " = %g\n", expr)

Po jego wywoaniu, na przykad w instrukcji

dprint(x/y);

makro zostaje rozwinite do postaci

printf("x/y" " = &g\n", x/y);

cigi znakowe s automatycznie czone, wic w efekcie uzyskujemy

printf("x/y = &g\n", x/y);

background image

Jzyk ANSI C. Programowanie

110

W argumencie faktycznym kady znak

"

jest zastpowany przez

\"

, a kady znak

\

przez

\\

, dziki czemu wynik to poprawna staa tekstowa.

Operator preprocesora

##

umoliwia konkatenowanie argumentów faktycznych w trakcie

rozwijania makr. Jeeli parametr w tekcie zastpujcym ssiaduje ze znakami

##

, parametr

ten jest zastpowany argumentem faktycznym, znaki

##

i biae znaki zostaj usunite,

a wynik jest analizowany ponownie. Przykadowo makro

paste

czy dwa argumenty:

#define paste(front, back) front ## back

wic

paste(name, 1)

tworzy nazw

name1

.

Reguy zagniedania operatora

##

s do zoone. Szczegóy mona znale w dodatku A.

wiczenie 4.14. Zdefiniuj makro

swap(t,x,y)

wymieniajce wartoci dwóch argumentów,

których typ to

t

(pomocna bdzie struktura blokowa).

4.11.3. Warunkowe wstawianie kodu

Istnieje moliwo sterowania prac samego preprocesora przy uyciu instrukcji wa-
runkowych, wykonywanych w trakcie jego dziaania. Zapewnia to moliwo wybiórcze-
go wstawiania kodu, w zalenoci od warunków, których wartoci s obliczane w czasie
kompilowania.

Wiersz

#if

oblicza warto staego wyraenia cakowitego (które nie moe zawiera

operatora

sizeof

, konwersji typów i staych

enum

). Jeeli wyraenie ma warto rón od

zera, wstawione zostaj dalsze wiersze, a do wiersza

#endif

,

#elif

lub

#else

(instrukcja

preprocesora

#elif

odpowiada

else if

). Wyraenie

defined(nazwa)

w wierszu

#if

ma

warto 1, jeeli

nazwa

zostaa wczeniej zdefiniowana, a 0 w pozostaych przypadkach.

Aby na przykad zapewni , e zawarto pliku

hdr.h

bdzie wczana do kodu tylko raz,

mona otoczy j wierszami dyrektyw warunkowych:

#if !defined(HDR)
#define HDR

/* tu znajduje si waciwa tre nagówka hdr.h */

#endif

Pierwsza operacja wczania pliku hdr.h powoduje zdefiniowanie nazwy

HDR

. Przy kolej-

nych próbach wczenia preprocesor stwierdza, e nazwa zostaa ju zdefiniowana, i prze-
chodzi do wiersza

#endif

. Podejcie takie mona stosowa bardzo szeroko. Zachowanie

penej konsekwencji pozwala w kadym nagówku wcza do kompilacji dowolne inne
wymagane nagówki bez cigego ledzenia ich wzajemnych zalenoci.

background image

Rozdzia 4. • Funkcje i struktura programu

111

Nastpujca sekwencja sprawdza tekst powizany z nazw

SYSTEM

, aby okreli , która

wersja nagówka ma zosta wczona 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 zostaa zde-

finiowana. Wczeniejszy przykad z

#if

mona zapisa jako

#ifndef HDR
#define HDR

/* tu znajduje si waciwa tre nagówka hdr.h */

#endif


Wyszukiwarka

Podobne podstrony:
Jezyk C Nowoczesne programowanie Wydanie II jcnpr2
Jezyk C Nowoczesne programowanie Wydanie II jcnpr2
Jezyk C Nowoczesne programowanie Wydanie II
Jezyk C Nowoczesne programowanie Wydanie II 2
Jezyk ANSI C Programowanie cwiczenia Wydanie II cwjans
Perl Zaawansowane programowanie Wydanie II perlz2
Asembler Sztuka programowania Wydanie II asesz2
Perl Zaawansow programowanie Wydanie II
Jezyk C Szkola programowania Wydanie V
Jezyk C Szkola programowania Wydanie V jcszpr

więcej podobnych podstron