Professional Documents
Culture Documents
Jezyk ANSI C Programowanie Cwiczenia Wydanie II
Jezyk ANSI C Programowanie Cwiczenia Wydanie II
Æwiczenia. Wydanie II
Autorzy: Clovis L. Tondo, Scott E. Gimpel
T³umaczenie: Pawe³ Koronkiewicz
ISBN: 978-83-246-2591-8
Tytu³ orygina³u: The C Answer Book, (2nd Edition)
Format: 158×235, stron: 168
Wstp 5
Rozdzia 1. Wprowadzenie 7
Skorowidz 161
Rozdzia 4.
Funkcje
i struktura programu
pos = -1;
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')
pos = i;
}
return pos;
}
/* strrindex: zwraca index ostatniego wystpienia t w s lub –1, jeeli nie wystpuje */
int strrindex(char s[], char t[])
{
int i, j, k;
58
Rozdzia 4. • Funkcje i struktura programu
Pierwsza cz funkcji to powtórzenie funkcji atof z podrozdziau 4.2 podrcznika K&R.
Funkcja pomija biae znaki, zapisuje znak i oblicza liczb. Pobieranie liczby z kropk
dziesitn wymaga identycznej procedury niezalenie od tego, czy w dalszej czci
pojawi si wykadnik.
Kocowa operacja
if (sign == 1)
while (exp-- > 0)
val *= 10;
else
while (exp-- > 0)
val /= 10;
Zmienna val jest dzielona przez 10, a nie mnoona przez 0.1, poniewa liczba 0,1 nie
jest w zapisie binarnym dokadna. Na wikszoci komputerów warto 0,1 jest repre-
zentowana jako nieco mniejsza ni 0,1. W efekcie mnoenie 10.0*0.1 rzadko daje
wynik 1.0. Powtarzanie dzielenia przez 10 jest wic lepszym rozwizaniem ni po-
wtarzanie mnoenia przez 0,1, cho utrata dokadnoci wci wystpuje.
59
Jzyk ANSI C. Programowanie. wiczenia
60
Rozdzia 4. • Funkcje i struktura programu
break;
case '\n':
printf("\t%.8g\n", pop());
break;
default:
printf("error: unknown command %s\n", s);
break;
}
}
return 0;
}
Zmienilimy program gówny i funkcj getop. Funkcje push i pop pozostaj niezmienione.
Operator modulo (%) jest traktowany podobnie jak operator dzielenia (/). Funkcja bi-
blioteczna fmod oblicza reszt z dzielenia dwóch elementów na wierzchoku stosu. op2 to
element pobrany z wierzchoka jako pierwszy.
int getch(void);
void ungetch(int);
61
Jzyk ANSI C. Programowanie. wiczenia
Funkcja getop sprawdza nastpny znak po znaku -, aby okreli , czy dane zawieraj liczb
ujemn. Przykadowo
- 1
62
Rozdzia 4. • Funkcje i struktura programu
63
Jzyk ANSI C. Programowanie. wiczenia
Znak nowego wiersza powoduje pobranie elementu z wierzchoka stosu i wypisanie go.
Dodalimy nowy operator, '?', który pobiera element z wierzchoka stosu, wypisuje
go, a nastpnie zwraca na stos. Nie usuwamy elementu z wierzchoka stosu w sposób
trway (tak jak w przypadku uycia znaku nowego wiersza), a stosujemy sekwencj
pop-printf-push. Dziki temu gówny program nie musi wiedzie o stosie ani wyko-
rzystywanych do jego obsugi zmiennych.
64
Rozdzia 4. • Funkcje i struktura programu
switch (type) {
case NUMBER:
push(atof(s));
break;
case NAME:
mathfnc(s);
break;
case '+':
push(pop() + pop());
break;
case '*':
push(pop() * pop());
break;
case '-':
op2 = pop();
push(pop() - op2);
break;
case '/':
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;
}
if (strcmp(s, "sin") == 0)
push(sin(pop()));
else if (strcmp(s, "cos") == 0)
push(cos(pop()));
else if (strcmp(s, "exp") == 0)
push(exp(pop()));
else if (strcmp(s, "pow") == 0)
op2 = pop();
push(pow(pop(), op2));
} else
printf("error: %s not supported\n", s);
}
65
Jzyk ANSI C. Programowanie. wiczenia
int getch(void);
void ungetch(int);
Zmodyfikowalimy funkcj getop, tak aby moga pobiera cig maych liter i zwraca go
jako typ NAME. Program gówny rozpoznaje NAME jako jeden z poprawnych typów i wy-
wouje funkcj mathfnc.
66
Rozdzia 4. • Funkcje i struktura programu
Funkcja mathfnc jest nowym elementem. Wykonuje ona seri instrukcji if a do znale-
zienia nazwy funkcji zapisanej w cigu s. Jeeli nazwa nie zostanie znaleziona, funkcja
zgasza bd. Jeeli cig s jest nazw jednej z obsugiwanych funkcji matematycznych,
mathfnc pobiera ze stosu odpowiedni liczb elementów i wywouje t funkcj. Funkcja
zwraca warto , któr mathfnc umieszcza na stosie.
Inny przykad,
5 2 pow 4 2 pow +
Funkcja getop nie zna nazw funkcji matematycznych. Zwraca ona jedynie znalezione
cigi. Zapewnia to moliwo atwego rozbudowywania funkcji mathfnc i wprowadzania
obsugi dalszych operacji.
67
Jzyk ANSI C. Programowanie. wiczenia
double variable[26];
68
Rozdzia 4. • Funkcje i struktura programu
Dodane zmienne to wielkie litery, od A do Z. Litery te su zarazem jako indeksy zmien-
nej tablicowej. Wprowadzilimy take zmienn v, w której zapisywana jest ostatnia
wypisywana warto .
Gdy program napotyka nazw zmiennej (od A do Z lub v), zapisuje jej warto na stosie.
Dostpny jest take nowy operator, '=', który przypisuje element z wierzchoka stosu
zmiennej poprzedzajcej operator. Na przykad
3 A =
dodaje liczby 2 i 3 (warto zmiennej A). Po dojciu do znaku nowego wiersza program
wypisuje liczb 5 i przypisuje jednoczenie warto 5 zmiennej v. Jeeli nastpn
operacj jest
v 1 +
Zmienna len zawiera liczb znaków w cigu s (bez kocowego '\0'), która jest okre-
lana przy uyciu funkcji strlen (patrz podrozdzia 2.3 podrcznika K&R).
Funkcja ungets wywouje procedur ungetch (patrz koniec podrozdziau 4.3 podrcz-
nika K&R) len razy, za kadym razem przekazujc do strumienia danych wejciowych
jeden znak cigu s. Funkcja dba o przekazywanie znaków w odwróconej kolejnoci.
Funkcja ungets nie musi zna zmiennych buf i bufp. Procedura ungetch zapewnia wy-
krywanie bdów i waciw prac z tymi zmiennymi.
69
Jzyk ANSI C. Programowanie. wiczenia
char buf = 0;
/* getch: pobiera znak danych wejciowych (móg by wczeniej wycofany przez ungetch */
int getch(void)
{
int c;
if (buf != 0)
c = buf;
else
c = getchar();
buf = 0;
return c;
}
Bufor, buf, nie jest ju tablic, poniewa nigdy nie bdzie w nim przechowywany
wicej ni jeden znak.
Zmienna buf jest inicjalizowana przy adowaniu programu wartoci 0. Funkcja getch
przywraca t warto za kadym razem, gdy pobiera znak. Funkcja ungetch przed za-
pisaniem znaku sprawdza, czy bufor jest pusty. Jeeli bufor nie jest pusty, wywietla
komunikat bdu.
70
Rozdzia 4. • Funkcje i struktura programu
/* getch: pobiera znak danych wejciowych (móg by wczeniej wycofany przez ungetch */
int getch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
W funkcjach getch i ungetch przedstawionych w podrczniku K&R bufor, buf, jest dekla-
rowany jako tablica znaków:
char buf[BUFSIZE];
Jzyk C nie wymaga, aby zmienna char zostaa okrelona jako signed lub unsigned
(patrz podrozdzia 2.7 podrcznika K&R). Konwersja wartoci char na int nie moe
prowadzi do uzyskania wartoci ujemnej. Na niektórych komputerach, gdy lewy skrajny
bit wartoci char jest równy 1, konwersja na int prowadzi do uzyskania wartoci ujem-
nej. Na innych konwersja polega na dodaniu z lewej strony odpowiedniej liczby zer.
Takie przeksztacenie zawsze prowadzi do uzyskania liczby dodatniej, niezalenie od
tego, czy lewy skrajny bit mia warto 1, czy nie.
Jeeli mamy traktowa EOF (–1) jak kady inny znak, zmienna buf powinna zosta za-
deklarowana jako tablica liczb cakowitych:
int buf[BUFSIZE];
Nie s wtedy wykonywane adne operacje konwersji i warto EOF (–1), podobnie jak
kada liczba ujemna, jest obsugiwana w sposób jednolity na wszystkich platformach.
71
Jzyk ANSI C. Programowanie. wiczenia
if (line[li] == '\0')
if (getline(line, MAXLINE) == 0)
return EOF;
else
li = 0;
while ((s[0] = c = line[li++]) == ' ' || 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 = line[li++]))
;
if (c == '.') /* pobierz cz uamkow */
while (isdigit(s[++i] = c = line[li++]))
;
s[i] = '\0';
li--;
return NUMBER;
}
Zamiast getch i ungetch uywamy w getop funkcji getline. line to tablica zawierajca
jeden peny wiersz danych wejciowych. li to indeks kolejnego znaku w line. Deklaru-
jemy line i li jako zmienne wewntrzne, aby zachowyway wartoci midzy wywoaniami.
72
Rozdzia 4. • Funkcje i struktura programu
Gdy getop dochodzi do koca wiersza (lub aden wiersz nie zosta jeszcze pobrany),
if (line[li] == '\0')
W oryginalnej wersji (podrozdzia 4.3 podrcznika K&R) funkcja getop wywouje getch
za kadym razem, gdy potrzebny jest nowy znak. W tej wersji pobierany jest znak na
pozycji li w tablicy line, po czym warto li jest zwikszana. Na kocu funkcji, zamiast
wywoywa ungetch w celu zwrócenia znaku do strumienia danych wejciowych, zmniej-
szamy li, aby wycofa si o jeden znak.
int getch(void);
if (lastc == 0)
c = getch();
else {
c = lastc;
lastc = 0;
}
while ((s[0] = c) == ' ' || c == '\t')
c = getch();
s[1] = '\0';
if (!isdigit(c) && c != '.')
return c; /* nie jest liczb */
i = 0;
73
Jzyk ANSI C. Programowanie. wiczenia
Zmodyfikowalimy funkcj getop, tak aby korzystaa ze zmiennej static, która pa-
mita ostatni znak, który powinien zosta wycofany do strumienia danych wejciowych.
Poniewa nie korzystamy z funkcji ungetch, zapisujemy ten znak w zmiennej lastc.
Wywoanie funkcji getop prowadzi do sprawdzenia, czy lastc zawiera wycofany znak.
Jeeli nie, nastpuje wywoanie getch w celu pobrania nowego znaku. Jeeli lastc zawiera
wycofany znak, to funkcja getop kopiuje go do zmiennej c i zeruje warto lastc. Pierw-
sza instrukcja while ulega pewnym zmianom. Wynikaj one z tego, e getop musi
pobiera nowy znak tylko po zakoczeniu przetwarzania biecego znaku w c.
if (n / 10)
itoa(n / 10, s);
else {
i = 0;
if (n < 0)
s[i++] = '-';
}
s[i++] = abs(n) % 10 + '0';
s[i] = '\0';
}
74
Rozdzia 4. • Funkcje i struktura programu
Funkcja itoa pobiera dwa argumenty: liczb cakowit n i tablic znaków s. Jeeli wynik
dzielenia cakowitego n/10 jest róny od zera, funkcja wywouje sam siebie, przekazujc
jako argument n/10:
if (n / 10)
itoa(n / 10, s);
reverser(s, 0, strlen(s));
}
j = len – (i + 1);
if (i < j) {
c = s[i];
s[i] = s[j];
s[j] = c;
reverser(s, ++i, len);
}
}
Funkcja reverse okrela dugo cigu i wywouje funkcj reverser, która wykonuje
waciw operacj odwrócenia cigu s w miejscu.
75
Jzyk ANSI C. Programowanie. wiczenia
Funkcja reverser pobiera trzy argumenty: s jest odwracanym cigiem, i to indeks cigu
(od lewej), a len to dugo cigu (strlen(s); patrz podrozdzia 2.3 podrcznika K&R).
Zamienianie znaków jest kontynuowane do momentu, gdy dwa indeksy wskazuj ten sam
znak (i == j) albo indeks liczony od lewej strony wskazuje znak na prawo od znaku
wskazywanego przez indeks liczony od lewej strony (i > j).
Nie jest to korzystne zastosowanie rekurencji. Pewne problemy dobrze poddaj si
rozwizaniom rekurencyjnym — przykadem moe by funkcja treeprint przedstawiona
w podrozdziale 6.5 podrcznika K&R. Inne lepiej rozwizywa innymi sposobami.
Do tej ostatniej kategorii naley problem odwracania cigu.
Makro swap dziaa poprawnie, o ile aden z argumentów nie ma nazwy _z. Jeeli tak jest,
swap(int, _z, x);
76