You are on page 1of 22

Jzyk ANSI C. Programowanie.

wiczenia. Wydanie II
Autorzy: Clovis L. Tondo, Scott E. Gimpel
Tumaczenie: Pawe Koronkiewicz
ISBN: 978-83-246-2591-8
Tytu oryginau: The C Answer Book, (2nd Edition)
Format: 158235, stron: 168

Ksika Jzyk ANSI C. Programowanie. Wydanie II to jedna z najlepszych dostpnych


na rynku pozycji do nauki tego jzyka, zaliczana do klasyki literatury informatycznej
i cieszca si niemalejc popularnoci. Przejrzycie opisana teoria, liczne przykady
oraz zbir wicze to atuty doceniane przez kolejne pokolenia programistw.
Niniejsza ksika zawiera rozwizania wszystkich wicze zawartych w Jzyku ANSI
C. Programowanie. Wydanie II. Oprcz dziaajcego i przetestowanego kodu znajdziesz
w niej komentarze do specyficznych konstrukcji i samego sposobu rozwizywania zada.
Poczenie teorii z praktyk pozwoli Ci byskawicznie przyswoi wiedz na temat jzyka
C, a nastpnie wykorzysta j w praktyce. Ponadto cz rozwiza z pewnoci przyda
si w codziennej pracy, dlatego te ksika ta sprawdzi si zarwno w rkach adepta
jzyka C, jak i zawodowego programisty.

Spis treci
Wstp

Rozdzia 1. Wprowadzenie

Rozdzia 2. Typy, operatory i wyraenia

37

Rozdzia 3. Sterowanie wykonywaniem programu

49

Rozdzia 4. Funkcje i struktura programu

57

Rozdzia 5. Wskaniki i tablice

77

Rozdzia 6. Struktury

121

Rozdzia 7. Wejcie i wyjcie

135

Rozdzia 8. Interfejs systemu UNIX

149

Skorowidz

161

Rozdzia 4.

Funkcje
i struktura programu
wiczenie 4.1 (str. 89)
Napisz funkcj strrindex(s,t), ktra zwraca pozycj ostatniego wystpienia t w s lub -1,
jeeli wyszukiwany cig nie zosta znaleziony.
/* strrindex: zwraca index ostatniego wystpienia t w s lub 1, jeeli nie wystpuje */
int strrindex(char s[], char t[])
{
int i, j, k, pos;
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;
}

Funkcja strrindex jest podobna do strindex, przedstawionej w podrozdziale 4.1 podrcznika K&R. Gdy funkcja strindex znajduje dopasowany podcig, zwraca jego pozycj,
ktra jest pozycj pierwszego wystpienia t w s. Funkcja strrindex nie zwraca pozycji
znalezionego podcigu, ale kontynuuje wyszukiwanie, poniewa jej zadaniem jest okrelenie pooenia ostatniego wystpienia t w s:
if (k > 0 && t[k] == '\0')
pos = i;

Jzyk ANSI C. Programowanie. wiczenia

Ten sam problem mona rozwiza take nastpujco:


#include

<string.h>

/* strrindex: zwraca index ostatniego wystpienia t w s lub 1, jeeli nie wystpuje */


int strrindex(char s[], char t[])
{
int i, j, k;
for (i = strlen(s) strlen(t); 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;
}

Jest to rozwizanie efektywniejsze. Przegldanie cigu rozpoczyna si od koca cigu


s minus dugo cigu t. Brak dopasowania powoduje przesunicie wyszukiwania o jedn
pozycj w stron pocztku cigu. Gdy tylko funkcja znajduje t w s, zwraca biec
pozycj, i. Jest to ostatnie wystpienie t w s.

wiczenie 4.2 (str. 91)


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 opcjonalnym znakiem.
#include

<ctype.h>

/* atof: konwertuje cig znakw s na liczb double */


double atof(char s[])
{
double val, power;
int exp, i, sign;
for (i = 0; isspace(s[i]); i++) /* pomi biae znaki */
;
sign = (s[i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-')
i++;
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++) {

58

Rozdzia 4. Funkcje i struktura programu

val = 10.0 * val + (s[i] - '0');


power *= 10;
}
val = sign * val / power;
if (s[i] == 'e' || s[i] == 'E') {
sign = (s[++i] == '-') ? -1 : 1;
if (s[i] == '+' || s[i] == '-')
i++;
for (exp = 0; isdigit(s[i]); i++)
exp = 10 * exp + (s[i] '0');
if (sign == 1)
while (exp-- > 0)
/* wykadnik dodatni */
val *= 10;
else
while (exp-- > 0)
/* wykadnik ujemny */
val /= 10;
}
return val;
}

Pierwsza cz funkcji to powtrzenie 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.
Druga cz funkcji odpowiada za konwersj opcjonalnego wykadnika. Jeeli ta cz
liczby nie wystpuje, funkcja zwraca warto zapisan w val. Jeeli wykadnik jest obecny,
to jego znak zostaje zapisany w zmiennej sign, po czym warto zostaje obliczona
i zapisana w zmiennej exp.
Kocowa operacja
if (sign == 1)
while (exp-- > 0)
val *= 10;
else
while (exp-- > 0)
val /= 10;

modyfikuje liczb odpowiednio do ustalonej wczeniej wartoci wykadnika. Jeeli


wykadnik jest dodatni, liczba zostaje pomnoona exp razy przez 10. Jeeli wykadnik
jest ujemny, liczba zostaje podzielona exp razy przez 10. W zmiennej val zostaje zapisany wynik, ktry jest zwracany do programu wywoujcego funkcj.
Zmienna val jest dzielona przez 10, a nie mnoona przez 0.1, poniewa liczba 0,1 nie
jest w zapisie binarnym dokadna. Na wikszoci komputerw warto 0,1 jest reprezentowana 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 powtarzanie mnoenia przez 0,1, cho utrata dokadnoci wci wystpuje.

59

Jzyk ANSI C. Programowanie. wiczenia

wiczenie 4.3 (str. 97)


W oparciu o schemat przedstawiony w przykadach program kalkulatora mona atwo
rozbudowywa . Dodaj obsug operatora modulo (%) i obsug liczb ujemnych.
#include
#include

<stdio.h>
<math.h>

#define
#define

MAXOP 100
NUMBER '0'

/* dla atof() */
/* dopuszczalny rozmiar operandu lub operatora */
/* 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 '/':
op2 = pop();
if (op2 != 0.0)
push(pop() / op2);
else
printf("error: zero divisor\n");
break;
case '%':
op2 = pop();
if (op2 != 0.0)
push(fmod(pop(), op2));
else
printf("error: zero divisor\n");

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 gwny i funkcj getop. Funkcje push i pop pozostaj niezmienione.
Operator modulo (%) jest traktowany podobnie jak operator dzielenia (/). Funkcja biblioteczna fmod oblicza reszt z dzielenia dwch elementw na wierzchoku stosu. op2 to
element pobrany z wierzchoka jako pierwszy.
Oto zmodyfikowana wersja funkcji getop:
#include
#include
#include

<stdio.h>
<string.h>
<ctype.h>

#define

NUMBER

'0'

/* sygna, e zostaa znaleziona liczba */

int getch(void);
void ungetch(int);
/* getop: pobiera nastpny operator lub operand (liczb) */
int getop(char s[])
{
int c, i;
while ((s[0] = c = getch()) == ' ' || c == '\t')
;
s[1] = '\0';
i = 0;
if (!isdigit(c) && c != '.' && c != '-')
return c;
/* nie jest liczb */
if (c == '-')
if (isdigit(c = getch()) || c == '.')
s[++i] = c;
/* liczba ujemna */
else {
if (c != EOF)
ungetch(c);
return '-';
/* znak minus */
}
if (isdigit(c))
/* pobierz cz cakowit */
while (isdigit(s[++i] = c = getch()))
;

61

Jzyk ANSI C. Programowanie. wiczenia

if (c == '.')
/* pobierz cz uamkow */
while (isdigit(s[++i] = c = getch()))
;
s[i] = '\0';
if (c != EOF)
ungetch(c);
return NUMBER;
}

Funkcja getop sprawdza nastpny znak po znaku -, aby okreli , czy dane zawieraj liczb
ujemn. Przykadowo
- 1

to znak minus i liczba. Jednak


-1.23

jest liczb ujemn.


Rozbudowany kalkulator zapewnia obsug sekwencji
1
-1
-10 3

+
%

Pierwsze wyraenie prowadzi do uzyskania wartoci 0 (1 + (-1)). Drugie wyraenie ma


warto 1.

wiczenie 4.4 (str. 97)


Utwrz polecenie wypisujce element na wierzchoku stosu bez jego usuwania ze stosu,
polecenie duplikujce element na wierzchoku stosu, polecenie zamieniajce miejscami
dwa grne elementy oraz polecenie usuwajce ca zawarto stosu.
#include
#include

<stdio.h>
<math.h>

#define
#define

MAXOP 100
NUMBER '0'

/* dla atof() */
/* dopuszczalny rozmiar operandu lub operatora */
/* sygna, e pobrano liczb */

int getop(char []);


void push(double);
double pop(void);
void clear(void);
/* kalkulator z odwrotn notacj polsk */
main()
{
int type;
double op1, op2;
char s[MAXOP];

62

Rozdzia 4. Funkcje i struktura programu

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 '/':
op2 = pop();
if (op2 != 0.0)
push(pop() / op2);
else
printf("error: zero divisor\n");
break;
case '?':
/* wypisz element z wierzchoka stosu */
op2 = pop();
printf("\t%.8g\n", op2);
push(op2);
break;
case 'c':
/* oprnij stos */
clear();
break;
case 'd':
/* duplikuj element na wierzchoku stosu */
op2 = pop();
push(op2);
push(op2);
break;
case 's':
/* zamie dwa elementy na wierzchoku */
op1 = pop();
op2 = pop();
push(op1);
push(op2);
break;
case '\n':
printf("\t%.8g\n", pop());
break;
default:
printf("error: unknown command %s\n", s);
break;
}
}
return 0;
}

63

Jzyk ANSI C. Programowanie. wiczenia

Znak nowego wiersza powoduje pobranie elementu z wierzchoka stosu i wypisanie go.
Dodalimy nowy operator, '?', ktry pobiera element z wierzchoka stosu, wypisuje
go, a nastpnie zwraca na stos. Nie usuwamy elementu z wierzchoka stosu w sposb
trway (tak jak w przypadku uycia znaku nowego wiersza), a stosujemy sekwencj
pop-printf-push. Dziki temu gwny program nie musi wiedzie o stosie ani wykorzystywanych do jego obsugi zmiennych.
Duplikowanie elementu na wierzchoku stosu polega na zdjciu go ze stosu i dwukrotnym wywoaniu funkcji umieszczajcej go na stosie ponownie.
Podobnie zamiana miejscami dwch elementw na wierzchoku jest realizowana poprzez zdjcie ich ze stosu i ponowne zapisanie na stosie.
Czyszczenie stosu jest prost operacj, sprowadzajc si do przypisania zmiennej sp
wartoci 0. Dodalimy now funkcj, uzupeniajc push i pop, ktra wykonuje wanie
tak operacj. Pozwala to zachowa zasad, e tylko funkcje operujce danymi stosu
odwouj si do niego i zwizanych z nim zmiennych.
/* clear: oprnia stos */
void clear(void)
{
sp = 0;
}

wiczenie 4.5 (str. 97)


Dodaj dostp do funkcji biblioteki, takich jak sin, exp, i pow. Patrz <math.h> w czci 4.
dodatku B.
#include
#include
#include

<stdio.h>
<string.h>
<math.h>

#define
#define
#define

MAXOP 100
NUMBER '0'
NAME
'n'

/* dla atof() */
/* dopuszczalny rozmiar operandu lub operatora */
/* sygna, e pobrano liczb */
/* sygna, e pobrano nazw */

int getop(char []);


void push(double);
double pop(void);
void mathfnc(char []);
/* kalkulator z odwrotn notacj polsk */
main()
{
int type;
double op2;
char s[MAXOP];
while ((type = getop(s)) != EOF) {

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;
}
/* mathfnc: sprawdza, czy cig s jest nazw obsugiwanej funkcji matematycznej */
void mathfnc(char s[])
{
double op2;
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

Plik rdowy zmodyfikowanej funkcji getop:


#include
#include
#include

<stdio.h>
<string.h>
<ctype.h>

#define
#define

NUMBER
NAME

'0'
'n'

/* sygna, e zostaa znaleziona liczba */


/* sygna, e pobrano nazw */

int getch(void);
void ungetch(int);
/* getop: pobiera nastpny operator, operand lub nazw funkcji */
int getop(char s[])
{
int c, i;
while ((s[0] = c = getch()) == ' ' || c == '\t')
;
s[1] = '\0';
i = 0;
if (islower(c))
/* polecenie lub nazwa */
while (islower(s[++i] = c = getch()))
;
s[i] = '\0';
if (c != EOF)
ungetch(c);
/* pobrany o jeden znak za duo */
if (strlen(s) > 1)
return NAME;
/* >1 znak, czyli nazwa */
else
return c;
/* to moe by polecenie */
}
if (!isdigit(c) && c != '.')
return c;
/* nie jest liczb */
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;
}

Zmodyfikowalimy funkcj getop, tak aby moga pobiera cig maych liter i zwraca go
jako typ NAME. Program gwny rozpoznaje NAME jako jeden z poprawnych typw i wywouje funkcj mathfnc.

66

Rozdzia 4. Funkcje i struktura programu

Funkcja mathfnc jest nowym elementem. Wykonuje ona seri instrukcji if a do znalezienia 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 elementw i wywouje t funkcj. Funkcja
zwraca warto , ktr mathfnc umieszcza na stosie.
Przykadowo funkcja sin oczekuje argumentu w radianach, a sinus PI / 2 ma warto 1.
3.14159265 2 / sin

Pierwsza operacja to dzielenie PI przez 2. Wynik zostaje umieszczony na stosie.


Funkcja sin pobiera t warto z wierzchoka stosu, oblicza wartoci sinus i zapisuje
wynik, 1, ponownie na stosie.
3.14159265 2 / sin 0 cos +

daje wynik 2, poniewa sinus PI / 2 to 1 i cosinus zera to 1.


Inny przykad,
5 2 pow 4 2 pow +

podnosi 5 do potgi 2, nastpnie 4 do potgi 2, po czym dodaje te dwie wartoci.


Funkcja getop nie zna nazw funkcji matematycznych. Zwraca ona jedynie znalezione
cigi. Zapewnia to moliwo atwego rozbudowywania funkcji mathfnc i wprowadzania
obsugi dalszych operacji.

wiczenie 4.6 (str. 98)


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

<stdio.h>
<math.h>

#define
#define

MAXOP 100
NUMBER '0'

/* dla atof() */
/* dopuszczalny rozmiar operandu lub operatora */
/* sygna, e pobrano liczb */

int getop(char []);


void push(double);
double pop(void);
/* kalkulator z odwrotn notacj polsk */
main()
{
int i, type, var = 0;
double op2, v;
char s[MAXOP];

67

Jzyk ANSI C. Programowanie. wiczenia

double variable[26];
for (i = 0; i < 26; i++)
variable[i] = 0.0;
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 '/':
op2 = pop();
if (op2 != 0.0)
push(pop() / op2);
else
printf("error: zero divisor\n");
break;
case '=':
pop();
if (var >= 'A' && var <= 'Z')
variable[var = 'A'] = pop();
else
printf("error: no variable name\n");
break;
case '\n':
v = pop();
printf("\t%.8g\n", v);
break;
default:
if (type >= 'A' && type <= 'Z')
push(variable[type 'A']);
else if (type == 'v')
push(v);
else
printf("error: unknown command %s\n", s);
break;
}
var = type;
}
return 0;
}

68

Rozdzia 4. Funkcje i struktura programu

Dodane zmienne to wielkie litery, od A do Z. Litery te su zarazem jako indeksy zmiennej tablicowej. Wprowadzilimy take zmienn v, w ktrej 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, '=', ktry przypisuje element z wierzchoka stosu
zmiennej poprzedzajcej operator. Na przykad
3 A =

przypisuje warto 3 zmiennej A. Pniejsze


2 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 +

to wynikiem jest 6: 5+1.

wiczenie 4.7 (str. 98)


Napisz procedur ungets(s), ktra zwraca do danych wejciowych cay cig znakw. Czy
funkcja ta powinna korzysta ze zmiennych buf i bufp, czy raczej tylko z funkcji ungetch?
#include

<string.h>

/* ungets: zwraca cig znakw do strumienia danych wejciowych */


void ungets(char s[])
{
int len = strlen(s);
void ungetch(int);
while (len > 0)
ungetch(s[--len]);
}

Zmienna len zawiera liczb znakw w cigu s (bez kocowego '\0'), ktra jest okrelana przy uyciu funkcji strlen (patrz podrozdzia 2.3 podrcznika K&R).
Funkcja ungets wywouje procedur ungetch (patrz koniec podrozdziau 4.3 podrcznika K&R) len razy, za kadym razem przekazujc do strumienia danych wejciowych
jeden znak cigu s. Funkcja dba o przekazywanie znakw w odwrconej kolejnoci.
Funkcja ungets nie musi zna zmiennych buf i bufp. Procedura ungetch zapewnia wykrywanie bdw i waciw prac z tymi zmiennymi.

69

Jzyk ANSI C. Programowanie. wiczenia

wiczenie 4.8 (str. 98)


Zmodyfikuj funkcje getch i ungetch, przyjwszy zaoenie, e nigdy nie bdzie wycofywany wicej ni jeden znak.
#include

<stdio.h>

char buf = 0;
/* getch: pobiera znak danych wejciowych (mg by wczeniej wycofany przez ungetch */
int getch(void)
{
int c;
if (buf != 0)
c = buf;
else
c = getchar();
buf = 0;
return c;
}
/* ungetch: wycofuje znak do strumienia danych wejciowych */
void ungetch(int c)
{
if (buf != 0)
printf("ungetch: too many characters\n");
else
buf = 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 zapisaniem znaku sprawdza, czy bufor jest pusty. Jeeli bufor nie jest pusty, wywietla
komunikat bdu.

wiczenie 4.9 (str. 98)


Nasze funkcje getch i ungetch nie obsuguj poprawnie wycofywania znaku EOF. Zastanw si, jakie powinny one mie cechy w przypadku cofania znaku EOF, po czym
zaimplementuj now koncepcj.
#include

<stdio.h>

#define BUFSIZE 100

70

Rozdzia 4. Funkcje i struktura programu

int buf[BUFSIZE];
int bufp = 0;

/* bufor dla ungetch */


/* nastpna wolna pozycja w buforze */

/* getch: pobiera znak danych wejciowych (mg by wczeniej wycofany przez ungetch */
int getch(void)
{
return (bufp > 0) ? buf[--bufp] : getchar();
}
/* ungetch: wycofuje znak do strumienia danych wejciowych */
void ungetch(int c)
{
if (bufp >= BUFSIZE)
printf("ungetch: too many characters\n");
else
buf[bufp++] = c;
}

W funkcjach getch i ungetch przedstawionych w podrczniku K&R bufor, buf, jest deklarowany jako tablica znakw:
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 niektrych komputerach, gdy lewy skrajny
bit wartoci char jest rwny 1, konwersja na int prowadzi do uzyskania wartoci ujemnej. 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.
W notacji szesnastkowej 1 to 0xFFFF (w przypadku 16 bitw). Po zapisaniu wartoci
0xFFFF w zmiennej char zmienna zawiera 0xFF. Konwersja 0xFF na int moe prowadzi
do uzyskania wartoci 0x00FF, czyli 255, lub 0xFFFF, czyli 1.
liczba ujemna (1) -> znak -> liczba int
0xFFFF
0xFF
0x00FF (255)
0xFFFF
0xFF
0xFFFF (1)

Jeeli mamy traktowa EOF (1) jak kady inny znak, zmienna buf powinna zosta zadeklarowana 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 sposb jednolity na wszystkich platformach.

71

Jzyk ANSI C. Programowanie. wiczenia

wiczenie 4.10 (str. 98)


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.
#include
#include

<stdio.h>
<ctype.h>

#define
#define

MAXLINE
NUMBER

100
'0'

/* sygna, e zostaa znaleziona liczba */

int getline(char line[], int limit);


int li = 0;
char line [MAXLINE];

/* indeks wiersza wejciowego */


/* jeden wiersz wejciowy */

/* getop: pobiera nastpny operator lub operand (liczb) */


int getop(char s[])
{
int c, i;
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. Deklarujemy 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')

nastpuje wywoanie getline w celu pobrania nowego wiersza danych.


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 zwrcenia znaku do strumienia danych wejciowych, zmniejszamy li, aby wycofa si o jeden znak.
Warto pamita , e kada funkcja ma moliwo wykorzystywania i modyfikowania
zmiennych zewntrznych stosowanych w innych funkcjach, wic zmienne li i line
mog zosta zmienione przez funkcj inn ni getop. Czasem wskazane jest zabezpieczenie programu przed takimi sytuacjami. Umoliwia to zadeklarowanie zmiennych jako static. Nie zrobilimy tego, bo zmienne static zostan omwione dopiero
w podrozdziale 4.6 podrcznika K&R.

wiczenie 4.11 (str. 102)


Zmodyfikuj funkcj getop w taki sposb, aby nie korzystaa z funkcji ungetch. Wskazwka: uyj wewntrznej zmiennej statycznej.
#include
#include

<stdio.h>
<ctype.h>

#define

NUMBER

'0'

/* sygna, e zostaa znaleziona liczba */

int getch(void);
/* getop: pobiera nastpny operator lub operand (liczb) */
int getop(char s[])
{
int c, i;
static int lastc = 0;
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

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)
lastc = c;
return NUMBER;
}

Zmodyfikowalimy funkcj getop, tak aby korzystaa ze zmiennej static, ktra pamita ostatni znak, ktry 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. Pierwsza instrukcja while ulega pewnym zmianom. Wynikaj one z tego, e getop musi
pobiera nowy znak tylko po zakoczeniu przetwarzania biecego znaku w c.

wiczenie 4.12 (str. 107)


Zaadaptuj koncepcj funkcji printd do napisania rekurencyjnej wersji funkcji itoa.
Innymi sowy, przekszta liczb cakowit na cig znakw, wywoujc procedur rekurencyjn.
#include

<math.h>

/* itoa: konwertuje liczb n na cig znakw s; wersja rekurencyjna */


void itoa(int n, char s[])
{
static int i;
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 znakw s. Jeeli wynik
dzielenia cakowitego n/10 jest rny od zera, funkcja wywouje sam siebie, przekazujc
jako argument n/10:
if (n / 10)
itoa(n / 10, s);

Gdy w jednym z kolejnych wywoa rekurencyjnych n/10 ma warto 0, mamy do czynienia z najbardziej znaczc cyfr n. Statyczna zmienna i jest indeksem tablicy s. Jeeli
liczba n jest ujemna, umieszczamy znak minus na pierwszej pozycji tablicy i zwikszamy i. Gdy itoa powraca z kolejnych wywoa rekurencyjnych, obliczane s kolejne
cyfry, od lewej do prawej strony. Zwr my uwag, e na kadym poziomie zostaje dodane
'\0' koczce cig, ktre na kolejnym poziomie zostaje zastpione nastpn cyfr liczby.
Wyjtkiem jest jedynie zakoczenie ostatniego wywoania itoa, po ktrym zapisany
znacznik koca cigu pozostaje w tablicy znakw.

wiczenie 4.13 (str. 107)


Napisz rekurencyjn wersj funkcji reverse(s), odwracajcej w miejscu cig znakw s.
#include

<string.h>

/* reverse: odwraca w miejscu cig s */


void reverse(char s[])
{
void reverser(char s[], int i, int len);
reverser(s, 0, strlen(s));
}
/* reverser: odwraca w miejscu cig s; algorytm rekurencyjny */
void reverser(char s[], int i, int len)
{
int c, j;
j = len (i + 1);
if (i < j) {
c = s[i];
s[i] = s[j];
s[j] = c;
reverser(s, ++i, len);
}
}

Musimy zachowa ten sam interfejs procedury reverse niezalenie od implementacji.


Oznacza to, e moemy przekaza do niej tylko cig znakw.
Funkcja reverse okrela dugo cigu i wywouje funkcj reverser, ktra wykonuje
waciw operacj odwrcenia 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).
Pocztkowo parametr i ma warto 0. j to indeks cigu, wskazujcy pozycj wzgldem
prawego koca tego cigu. Warto j jest obliczana jako
j = len (i + 1);

Znaki cigu s zamieniane miejscami, poczwszy od skrajnych, a do rodka cigu


najpierw zamieniane s s[0] i s[len-1], potem s[1] i s[len-2] itd. Warto indeksu
i jest zwikszana o jeden przed kadym kolejnym wywoaniem funkcji reverser:
reverser(s, ++i, len);

Zamienianie znakw 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.

wiczenie 4.14 (str. 110)


Zdefiniuj makro swap(t,x,y) wymieniajce wartoci dwch argumentw, ktrych typ
to t (pomocna bdzie struktura blokowa).
#define

swap(t, x, y)

t _z;
_z = y;
y = x;
x = _z;

\
\
\
}

Uywajc nawiasw klamrowych, definiujemy blok. Na pocztku bloku moemy zadeklarowa zmienne lokalne. _z to zmienna lokalna typu t, ktra pomaga zamieni
dwa argumenty.
Makro swap dziaa poprawnie, o ile aden z argumentw nie ma nazwy _z. Jeeli tak jest,
swap(int, _z, x);

to po rozwiniciu makra uzyskujemy


{ int _z; _z = _z; _z = x; x = _z; }

i wymiana nie nastpuje. Przyjmujemy wic zaoenie, e _z nie bdzie wykorzystywane jako nazwa zmiennej.

76

You might also like