Cyfroteka.pl

klikaj i czytaj online

Cyfro
Czytomierz
00224 005815 13604959 na godz. na dobę w sumie
Język ANSI C. Programowanie. Ćwiczenia. Wydanie II - książka
Język ANSI C. Programowanie. Ćwiczenia. Wydanie II - książka
Autor: , Liczba stron: 168
Wydawca: Helion Język publikacji: polski
ISBN: 978-83-246-2591-8 Data wydania:
Lektor:
Kategoria: ebooki >> komputery i informatyka >> programowanie >> c - programowanie
Porównaj ceny (książka, ebook, audiobook).

Zobacz też podręcznik do ćwiczeń Język ANSI C. Programowanie. Wydanie II>>

Książka 'Język ANSI C. Programowanie. Wydanie II' to jedna z najlepszych dostępnych na rynku pozycji do nauki tego języka, zaliczana do klasyki literatury informatycznej i ciesząca się niemalejącą popularnością. Przejrzyście opisana teoria, liczne przykłady oraz zbiór ćwiczeń to atuty doceniane przez kolejne pokolenia programistów.

Niniejsza książka zawiera rozwiązania wszystkich ćwiczeń zawartych w 'Języku ANSI C. Programowanie. Wydanie II'. Oprócz działającego i przetestowanego kodu znajdziesz w niej komentarze do specyficznych konstrukcji i samego sposobu rozwiązywania zadań. Połączenie teorii z praktyką pozwoli Ci błyskawicznie przyswoić wiedzę na temat języka C, a następnie wykorzystać ją w praktyce. Ponadto część rozwiązań z pewnością przyda się w codziennej pracy, dlatego też książka ta sprawdzi się zarówno w rękach adepta języka C, jak i zawodowego programisty.

Znajdź podobne książki Ostatnio czytane w tej kategorii

Darmowy fragment publikacji:

Jêzyk ANSI C. Programowanie. Æ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 Ksi¹¿ka „Jêzyk ANSI C. Programowanie. Wydanie II” to jedna z najlepszych dostêpnych na rynku pozycji do nauki tego jêzyka, zaliczana do klasyki literatury informatycznej i ciesz¹ca siê niemalej¹c¹ popularności¹. Przejrzyście opisana teoria, liczne przyk³ady oraz zbiór æwiczeñ to atuty doceniane przez kolejne pokolenia programistów. Niniejsza ksi¹¿ka zawiera rozwi¹zania wszystkich æwiczeñ zawartych w „Jêzyku ANSI C. Programowanie. Wydanie II”. Oprócz dzia³aj¹cego i przetestowanego kodu znajdziesz w niej komentarze do specyficznych konstrukcji i samego sposobu rozwi¹zywania zadañ. Po³¹czenie teorii z praktyk¹ pozwoli Ci b³yskawicznie przyswoiæ wiedzê na temat jêzyka C, a nastêpnie wykorzystaæ j¹ w praktyce. Ponadto czêśæ rozwi¹zañ z pewności¹ przyda siê w codziennej pracy, dlatego te¿ ksi¹¿ka ta sprawdzi siê zarówno w rêkach adepta jêzyka C, jak i zawodowego programisty. Spis treĂci WstÚp Rozdziaï 1. Wprowadzenie Rozdziaï 2. Typy, operatory i wyraĝenia Rozdziaï 3. Sterowanie wykonywaniem programu Rozdziaï 4. Funkcje i struktura programu Rozdziaï 5. Wskaěniki i tablice Rozdziaï 6. Struktury Rozdziaï 7. WejĂcie i wyjĂcie Rozdziaï 8. Interfejs systemu UNIX Skorowidz 5 7 37 49 57 77 121 135 149 161 Rozdziaï 4. Funkcje i struktura programu mwiczenie 4.1 (str. 89) Napisz funkcjÚ strrindex(s,t), która zwraca pozycjÚ ostatniego wystÈpienia t w s lub -1, jeĝeli wyszukiwany ciÈg nie zostaï znaleziony. /* strrindex: zwraca index ostatniego wystąpienia t w s lub –1, jeĪeli nie wystĊpuje */ 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 pod- rÚcznika K R. Gdy funkcja strindex znajduje dopasowany podciÈg, zwraca jego pozycjÚ, która jest pozycjÈ pierwszego wystÈpienia t w s. Funkcja strrindex nie zwraca pozycji znalezionego podciÈgu, ale kontynuuje wyszukiwanie, poniewaĝ jej zadaniem jest okre- Ălenie poïoĝenia ostatniego wystÈpienia t w s: if (k 0 t[k] == \0 ) pos = i; JĊzyk ANSI C. Programowanie. ûwiczenia Ten sam problem moĝna rozwiÈzaÊ takĝe nastÚpujÈco: #include string.h /* strrindex: zwraca index ostatniego wystąpienia t w s lub –1, jeĪeli nie wystĊpuje */ 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 rozwiÈzanie efektywniejsze. PrzeglÈdanie ciÈgu rozpoczyna siÚ od koñca ciÈgu s minus dïugoĂÊ ciÈgu t. Brak dopasowania powoduje przesuniÚcie wyszukiwania o jednÈ pozycjÚ w stronÚ poczÈtku ciÈgu. Gdy tylko funkcja znajduje t w s, zwraca bieĝÈcÈ pozycjÚ, i. Jest to ostatnie wystÈpienie t w s. mwiczenie 4.2 (str. 91) Dodaj do funkcji atof moĝliwoĂÊ obsïugi notacji wykïadniczej, postaci: 123.45e-6 gdzie po liczbie zmiennoprzecinkowej moĝe wystÈpiÊ litera e lub E i wykïadnik, z opcjo- nalnym znakiem. #include ctype.h /* atof: konwertuje ciąg znaków s na liczbĊ double */ double atof(char s[]) { double val, power; int exp, i, sign; for (i = 0; isspace(s[i]); i++) /* pomiĔ biaáe 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) /* wykáadnik dodatni */ val *= 10; else while (exp-- 0) /* wykáadnik ujemny */ val /= 10; } return val; } Pierwsza czÚĂÊ funkcji to powtórzenie funkcji atof z podrozdziaïu 4.2 podrÚcznika K R. Funkcja pomija biaïe znaki, zapisuje znak i oblicza liczbÚ. Pobieranie liczby z kropkÈ dziesiÚtnÈ wymaga identycznej procedury niezaleĝnie od tego, czy w dalszej czÚĂci pojawi siÚ wykïadnik. Druga czÚĂÊ funkcji odpowiada za konwersjÚ opcjonalnego wykïadnika. Jeĝeli ta czÚĂÊ liczby nie wystÚpuje, funkcja zwraca wartoĂÊ zapisanÈ w val. Jeĝeli wykïadnik jest obecny, to jego znak zostaje zapisany w zmiennej sign, po czym wartoĂÊ zostaje obliczona i zapisana w zmiennej exp. Koñcowa operacja if (sign == 1) while (exp-- 0) val *= 10; else while (exp-- 0) val /= 10; modyfikuje liczbÚ odpowiednio do ustalonej wczeĂniej wartoĂci wykïadnika. Jeĝeli wykïadnik jest dodatni, liczba zostaje pomnoĝona exp razy przez 10. Jeĝeli wykïadnik jest ujemny, liczba zostaje podzielona exp razy przez 10. W zmiennej val zostaje za- pisany wynik, który jest zwracany do programu wywoïujÈcego funkcjÚ. Zmienna val jest dzielona przez 10, a nie mnoĝona przez 0.1, poniewaĝ liczba 0,1 nie jest w zapisie binarnym dokïadna. Na wiÚkszoĂci komputerów wartoĂÊ 0,1 jest repre- zentowana jako nieco mniejsza niĝ 0,1. W efekcie mnoĝenie 10.0*0.1 rzadko daje wynik 1.0. Powtarzanie dzielenia przez 10 jest wiÚc lepszym rozwiÈzaniem niĝ po- wtarzanie mnoĝenia przez 0,1, choÊ utrata dokïadnoĂci wciÈĝ wystÚpuje. 59 JĊzyk ANSI C. Programowanie. ûwiczenia mwiczenie 4.3 (str. 97) W oparciu o schemat przedstawiony w przykïadach program kalkulatora moĝna ïatwo rozbudowywaÊ. Dodaj obsïugÚ operatora modulo ( ) i obsïugÚ liczb ujemnych. #include stdio.h #include math.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 / : 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; } ZmieniliĂmy 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 wierzchoïku stosu. op2 to element pobrany z wierzchoïka jako pierwszy. Oto zmodyfikowana wersja funkcji getop: #include stdio.h #include string.h #include ctype.h #define NUMBER 0 /* sygnaá, Īe zostaáa znaleziona liczba */ int getch(void); void ungetch(int); /* getop: pobiera nastĊpny 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ĊĞü caákowitą */ while (isdigit(s[++i] = c = getch())) ; 61 JĊzyk ANSI C. Programowanie. ûwiczenia if (c == . ) /* pobierz czĊĞü uáamkową */ while (isdigit(s[++i] = c = getch())) ; s[i] = \0 ; if (c != EOF) ungetch(c); return NUMBER; } Funkcja getop sprawdza nastÚpny znak po znaku -, aby okreĂliÊ, czy dane zawierajÈ liczbÚ ujemnÈ. Przykïadowo - 1 to znak minus i liczba. Jednak -1.23 jest liczbÈ ujemnÈ. Rozbudowany kalkulator zapewnia obsïugÚ sekwencji 1 -1 + -10 3 Pierwsze wyraĝenie prowadzi do uzyskania wartoĂci 0 (1 + (-1)). Drugie wyraĝenie ma wartoĂÊ –1. mwiczenie 4.4 (str. 97) Utwórz polecenie wypisujÈce element na wierzchoïku stosu bez jego usuwania ze stosu, polecenie duplikujÈce element na wierzchoïku stosu, polecenie zamieniajÈce miejscami dwa górne elementy oraz polecenie usuwajÈce caïÈ zawartoĂÊ stosu. #include stdio.h #include math.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); 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 wierzchoáka stosu */ op2 = pop(); printf( \t .8g\n , op2); push(op2); break; case c : /* opróĪnij stos */ clear(); break; case d : /* duplikuj element na wierzchoáku stosu */ op2 = pop(); push(op2); push(op2); break; case s : /* zamieĔ dwa elementy na wierzchoáku */ 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 JĊzyk ANSI C. Programowanie. ûwiczenia Znak nowego wiersza powoduje pobranie elementu z wierzchoïka stosu i wypisanie go. DodaliĂmy nowy operator, ? , który pobiera element z wierzchoïka stosu, wypisuje go, a nastÚpnie zwraca na stos. Nie usuwamy elementu z wierzchoïka stosu w sposób trwaïy (tak jak w przypadku uĝycia znaku nowego wiersza), a stosujemy sekwencjÚ pop-printf-push. DziÚki temu gïówny program nie musi wiedzieÊ o stosie ani wyko- rzystywanych do jego obsïugi zmiennych. Duplikowanie elementu na wierzchoïku stosu polega na zdjÚciu go ze stosu i dwukrot- nym wywoïaniu funkcji umieszczajÈcej go na stosie ponownie. Podobnie zamiana miejscami dwóch elementów na wierzchoïku jest realizowana po- przez zdjÚcie ich ze stosu i ponowne zapisanie na stosie. Czyszczenie stosu jest prostÈ operacjÈ, sprowadzajÈcÈ siÚ do przypisania zmiennej sp wartoĂci 0. DodaliĂmy nowÈ funkcjÚ, uzupeïniajÈcÈ push i pop, która wykonuje wïaĂnie takÈ operacjÚ. Pozwala to zachowaÊ zasadÚ, ĝe tylko funkcje operujÈce danymi stosu odwoïujÈ siÚ do niego i zwiÈzanych z nim zmiennych. /* clear: opróĪnia stos */ void clear(void) { sp = 0; } mwiczenie 4.5 (str. 97) Dodaj dostÚp do funkcji biblioteki, takich jak sin, exp, i pow. Patrz math.h w czÚĂci 4. dodatku B. #include stdio.h #include string.h #include math.h /* dla atof() */ #define MAXOP 100 /* dopuszczalny rozmiar operandu lub operatora */ #define NUMBER 0 /* sygnaá, Īe pobrano liczbĊ */ #define NAME n /* 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 ciąg s jest nazwą obsáugiwanej 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 JĊzyk ANSI C. Programowanie. ûwiczenia Plik ěródïowy zmodyfikowanej funkcji getop: #include stdio.h #include string.h #include ctype.h #define NUMBER 0 /* sygnaá, Īe zostaáa znaleziona liczba */ #define NAME n /* sygnaá, Īe pobrano nazwĊ */ int getch(void); void ungetch(int); /* getop: pobiera nastĊpny 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 duĪo */ if (strlen(s) 1) return NAME; /* 1 znak, czyli nazwa */ else return c; /* to moĪe byü polecenie */ } if (!isdigit(c) c != . ) return c; /* nie jest liczbą */ if (isdigit(c)) /* pobierz czĊĞü caákowitą */ while (isdigit(s[++i] = c = getch())) ; if (c == . ) /* pobierz czĊĞü uáamkową */ while (isdigit(s[++i] = c = getch())) ; s[i] = \0 ; if (c != EOF) ungetch(c); return NUMBER; } ZmodyfikowaliĂmy funkcjÚ getop, tak aby mogïa pobieraÊ ciÈg maïych liter i zwracaÊ go jako typ NAME. Program gïówny rozpoznaje NAME jako jeden z poprawnych typów i wy- woïuje 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 ciÈgu s. Jeĝeli nazwa nie zostanie znaleziona, funkcja zgïasza bïÈd. Jeĝeli ciÈg s jest nazwÈ jednej z obsïugiwanych funkcji matematycznych, mathfnc pobiera ze stosu odpowiedniÈ liczbÚ elementów i wywoïuje tÚ funkcjÚ. Funkcja zwraca wartoĂÊ, którÈ mathfnc umieszcza na stosie. Przykïadowo 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 wierzchoïka stosu, oblicza wartoĂci 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 przykïad, 5 2 pow 4 2 pow + podnosi 5 do potÚgi 2, nastÚpnie 4 do potÚgi 2, po czym dodaje te dwie wartoĂci. Funkcja getop nie zna nazw funkcji matematycznych. Zwraca ona jedynie znalezione ciÈgi. Zapewnia to moĝliwoĂÊ ïatwego rozbudowywania funkcji mathfnc i wprowadzania obsïugi dalszych operacji. mwiczenie 4.6 (str. 98) Dodaj polecenia obsïugi zmiennych (ïatwo jest zapewniÊ moĝliwoĂÊ korzystania z dwu- dziestu szeĂciu zmiennych przy uĝyciu jednoliterowych nazw). Dodaj zmiennÈ prze- chowujÈcÈ ostatniÈ wypisanÈ wartoĂÊ. #include stdio.h #include math.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 i, type, var = 0; double op2, v; char s[MAXOP]; 67 JĊzyk 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 sïuĝÈ zarazem jako indeksy zmien- nej tablicowej. WprowadziliĂmy takĝe 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. DostÚpny jest takĝe nowy operator, = , który przypisuje element z wierzchoïka stosu zmiennej poprzedzajÈcej operator. Na przykïad 3 A = przypisuje wartoĂÊ 3 zmiennej A. Póěniejsze 2 A + dodaje liczby 2 i 3 (wartoĂÊ zmiennej A). Po dojĂciu do znaku nowego wiersza program wypisuje liczbÚ 5 i przypisuje jednoczeĂnie wartoĂÊ 5 zmiennej v. Jeĝeli nastÚpnÈ operacjÈ jest v 1 + to wynikiem jest 6: 5+1. mwiczenie 4.7 (str. 98) Napisz procedurÚ ungets(s), która zwraca do danych wejĂciowych caïy ciÈg znaków. Czy funkcja ta powinna korzystaÊ ze zmiennych buf i bufp, czy raczej tylko z funkcji ungetch? #include string.h /* ungets: zwraca ciąg znaków do strumienia danych wejĞciowych */ void ungets(char s[]) { int len = strlen(s); void ungetch(int); while (len 0) ungetch(s[--len]); } Zmienna len zawiera liczbÚ znaków w ciÈgu s (bez koñcowego \0 ), która jest okre- Ălana przy uĝyciu funkcji strlen (patrz podrozdziaï 2.3 podrÚcznika K R). Funkcja ungets wywoïuje procedurÚ ungetch (patrz koniec podrozdziaïu 4.3 podrÚcz- nika K R) len razy, za kaĝdym razem przekazujÈc do strumienia danych wejĂciowych jeden znak ciÈgu s. Funkcja dba o przekazywanie znaków w odwróconej kolejnoĂci. Funkcja ungets nie musi znaÊ zmiennych buf i bufp. Procedura ungetch zapewnia wy- krywanie bïÚdów i wïaĂciwÈ pracÚ z tymi zmiennymi. 69 JĊzyk ANSI C. Programowanie. ûwiczenia mwiczenie 4.8 (str. 98) Zmodyfikuj funkcje getch i ungetch, przyjÈwszy zaïoĝenie, ĝe nigdy nie bÚdzie wycofy- wany wiÚcej niĝ jeden znak. #include stdio.h char buf = 0; /* getch: pobiera znak danych wejĞciowych (mógá byü wczeĞniej 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 wejĞciowych */ 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 bÚdzie w nim przechowywany wiÚcej niĝ jeden znak. Zmienna buf jest inicjalizowana przy ïadowaniu programu wartoĂciÈ 0. Funkcja getch przywraca tÚ wartoĂÊ za kaĝdym razem, gdy pobiera znak. Funkcja ungetch przed za- pisaniem znaku sprawdza, czy bufor jest pusty. Jeĝeli bufor nie jest pusty, wyĂwietla komunikat bïÚdu. mwiczenie 4.9 (str. 98) Nasze funkcje getch i ungetch nie obsïugujÈ poprawnie wycofywania znaku EOF. Za- stanów 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]; /* bufor dla ungetch */ int bufp = 0; /* nastĊpna wolna pozycja w buforze */ /* getch: pobiera znak danych wejĞciowych (mógá byü wczeĞniej wycofany przez ungetch */ int getch(void) { return (bufp 0) ? buf[--bufp] : getchar(); } /* ungetch: wycofuje znak do strumienia danych wejĞciowych */ void ungetch(int c) { if (bufp = BUFSIZE) printf( ungetch: too many characters\n ); else buf[bufp++] = c; } W funkcjach getch i ungetch przedstawionych w podrÚczniku K R bufor, buf, jest dekla- rowany jako tablica znaków: char buf[BUFSIZE]; JÚzyk C nie wymaga, aby zmienna char zostaïa okreĂlona jako signed lub unsigned (patrz podrozdziaï 2.7 podrÚcznika K R). Konwersja wartoĂci char na int nie moĝe prowadziÊ do uzyskania wartoĂci ujemnej. Na niektórych komputerach, gdy lewy skrajny bit wartoĂci char jest równy 1, konwersja na int prowadzi do uzyskania wartoĂci ujem- nej. Na innych konwersja polega na dodaniu z lewej strony odpowiedniej liczby zer. Takie przeksztaïcenie zawsze prowadzi do uzyskania liczby dodatniej, niezaleĝnie od tego, czy lewy skrajny bit miaï wartoĂÊ 1, czy nie. W notacji szesnastkowej –1 to 0xFFFF (w przypadku 16 bitów). Po zapisaniu wartoĂci 0xFFFF w zmiennej char zmienna zawiera 0xFF. Konwersja 0xFF na int moĝe prowadziÊ do uzyskania wartoĂci 0x00FF, czyli 255, lub 0xFFFF, czyli –1. liczba ujemna (–1) - znak - liczba int 0xFFFF 0xFF 0x00FF (255) 0xFFFF 0xFF 0xFFFF (–1) Jeĝeli mamy traktowaÊ EOF (–1) jak kaĝdy inny znak, zmienna buf powinna zostaÊ za- deklarowana jako tablica liczb caïkowitych: int buf[BUFSIZE]; Nie sÈ wtedy wykonywane ĝadne operacje konwersji i wartoĂÊ EOF (–1), podobnie jak kaĝda liczba ujemna, jest obsïugiwana w sposób jednolity na wszystkich platformach. 71 JĊzyk ANSI C. Programowanie. ûwiczenia mwiczenie 4.10 (str. 98) Alternatywna organizacja pracy z danymi wejĂciowymi opiera siÚ na uĝyciu getline w celu pobrania caïego wiersza. DziÚki temu funkcje getch i ungetch nie sÈ potrzebne. PrzeksztaïÊ kalkulator, tak aby jego praca opieraïa siÚ na takim podejĂciu do danych wejĂciowych. #include stdio.h #include ctype.h #define MAXLINE 100 #define NUMBER 0 /* sygnaá, Īe zostaáa znaleziona liczba */ int getline(char line[], int limit); int li = 0; /* indeks wiersza wejĞciowego */ char line [MAXLINE]; /* jeden wiersz wejĞciowy */ /* getop: pobiera nastĊpny 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ĊĞü caákowitą */ while (isdigit(s[++i] = c = line[li++])) ; if (c == . ) /* pobierz czĊĞü uáamkową */ while (isdigit(s[++i] = c = line[li++])) ; s[i] = \0 ; li--; return NUMBER; } Zamiast getch i ungetch uĝywamy w getop funkcji getline. line to tablica zawierajÈca jeden peïny wiersz danych wejĂciowych. li to indeks kolejnego znaku w line. Deklaru- jemy line i li jako zmienne wewnÚtrzne, aby zachowywaïy wartoĂci miÚdzy wywoïaniami. 72 Rozdziaá 4. • Funkcje i struktura programu Gdy getop dochodzi do koñca wiersza (lub ĝaden wiersz nie zostaï jeszcze pobrany), if (line[li] == \0 ) nastÚpuje wywoïanie getline w celu pobrania nowego wiersza danych. W oryginalnej wersji (podrozdziaï 4.3 podrÚcznika K R) funkcja getop wywoïuje getch za kaĝdym razem, gdy potrzebny jest nowy znak. W tej wersji pobierany jest znak na pozycji li w tablicy line, po czym wartoĂÊ li jest zwiÚkszana. Na koñcu funkcji, zamiast wywoïywaÊ ungetch w celu zwrócenia znaku do strumienia danych wejĂciowych, zmniej- szamy li, aby wycofaÊ siÚ o jeden znak. Warto pamiÚtaÊ, ĝe kaĝda funkcja ma moĝliwoĂÊ wykorzystywania i modyfikowania zmiennych zewnÚtrznych stosowanych w innych funkcjach, wiÚc zmienne li i line mogÈ zostaÊ zmienione przez funkcjÚ innÈ niĝ getop. Czasem wskazane jest zabez- pieczenie programu przed takimi sytuacjami. Umoĝliwia to zadeklarowanie zmien- nych jako static. Nie zrobiliĂmy tego, bo zmienne static zostanÈ omówione dopiero w podrozdziale 4.6 podrÚcznika K R. mwiczenie 4.11 (str. 102) Zmodyfikuj funkcjÚ getop w taki sposób, aby nie korzystaïa z funkcji ungetch. Wska- zówka: uĝyj wewnÚtrznej zmiennej statycznej. #include stdio.h #include ctype.h #define NUMBER 0 /* sygnaá, Īe zostaáa znaleziona liczba */ int getch(void); /* getop: pobiera nastĊpny 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 JĊzyk ANSI C. Programowanie. ûwiczenia if (isdigit(c)) /* pobierz czĊĞü caákowitą */ while (isdigit(s[++i] = c = getch())) ; if (c == . ) /* pobierz czĊĞü uáamkową */ while (isdigit(s[++i] = c = getch())) ; s[i] = \0 ; if (c != EOF) lastc = c; return NUMBER; } ZmodyfikowaliĂmy funkcjÚ getop, tak aby korzystaïa ze zmiennej static, która pa- miÚta ostatni znak, który powinien zostaÊ wycofany do strumienia danych wejĂciowych. Poniewaĝ nie korzystamy z funkcji ungetch, zapisujemy ten znak w zmiennej lastc. Wywoïanie funkcji getop prowadzi do sprawdzenia, czy lastc zawiera wycofany znak. Jeĝeli nie, nastÚpuje wywoïanie getch w celu pobrania nowego znaku. Jeĝeli lastc zawiera wycofany znak, to funkcja getop kopiuje go do zmiennej c i zeruje wartoĂÊ lastc. Pierw- sza instrukcja while ulegïa pewnym zmianom. WynikajÈ one z tego, ĝe getop musi pobieraÊ nowy znak tylko po zakoñczeniu przetwarzania bieĝÈcego znaku w c. mwiczenie 4.12 (str. 107) Zaadaptuj koncepcjÚ funkcji printd do napisania rekurencyjnej wersji funkcji itoa. Innymi sïowy, przeksztaïÊ liczbÚ caïkowitÈ na ciÈg znaków, wywoïujÈc procedurÚ re- kurencyjnÈ. #include math.h /* itoa: konwertuje liczbĊ n na ciąg znaków 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Ú caïkowitÈ n i tablicÚ znaków s. Jeĝeli wynik dzielenia caïkowitego n/10 jest róĝny od zera, funkcja wywoïuje samÈ siebie, przekazujÈc jako argument n/10: if (n / 10) itoa(n / 10, s); Gdy w jednym z kolejnych wywoïañ rekurencyjnych n/10 ma wartoĂÊ 0, mamy do czy- nienia z najbardziej znaczÈcÈ cyfrÈ n. Statyczna zmienna i jest indeksem tablicy s. Jeĝeli liczba n jest ujemna, umieszczamy znak minus na pierwszej pozycji tablicy i zwiÚk- szamy i. Gdy itoa powraca z kolejnych wywoïañ rekurencyjnych, obliczane sÈ kolejne cyfry, od lewej do prawej strony. ZwróÊmy uwagÚ, ĝe na kaĝdym poziomie zostaje dodane \0 koñczÈce ciÈg, które na kolejnym poziomie zostaje zastÈpione nastÚpnÈ cyfrÈ liczby. WyjÈtkiem jest jedynie zakoñczenie ostatniego wywoïania itoa, po którym zapisany znacznik koñca ciÈgu pozostaje w tablicy znaków. mwiczenie 4.13 (str. 107) Napisz rekurencyjnÈ wersjÚ funkcji reverse(s), odwracajÈcej „w miejscu” ciÈg znaków s. #include string.h /* reverse: odwraca w miejscu ciąg s */ void reverse(char s[]) { void reverser(char s[], int i, int len); reverser(s, 0, strlen(s)); } /* reverser: odwraca w miejscu ciąg 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 niezaleĝnie od implementacji. Oznacza to, ĝe moĝemy przekazaÊ do niej tylko ciÈg znaków. Funkcja reverse okreĂla dïugoĂÊ ciÈgu i wywoïuje funkcjÚ reverser, która wykonuje wïaĂciwÈ operacjÚ odwrócenia ciÈgu s w miejscu. 75 JĊzyk ANSI C. Programowanie. ûwiczenia Funkcja reverser pobiera trzy argumenty: s jest odwracanym ciÈgiem, i to indeks ciÈgu (od lewej), a len to dïugoĂÊ ciÈgu (strlen(s); patrz podrozdziaï 2.3 podrÚcznika K R). PoczÈtkowo parametr i ma wartoĂÊ 0. j to indeks ciÈgu, wskazujÈcy pozycjÚ wzglÚdem prawego koñca tego ciÈgu. WartoĂÊ j jest obliczana jako j = len – (i + 1); Znaki ciÈgu sÈ zamieniane miejscami, poczÈwszy od skrajnych, aĝ do Ărodka ciÈgu — najpierw zamieniane sÈ s[0] i s[len-1], potem s[1] i s[len-2] itd. WartoĂÊ indeksu i jest zwiÚkszana o jeden przed kaĝdym kolejnym wywoïaniem funkcji reverser: reverser(s, ++i, len); 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Ú rozwiÈzaniom rekurencyjnym — przykïadem moĝe byÊ funkcja treeprint przedstawiona w podrozdziale 6.5 podrÚcznika K R. Inne lepiej rozwiÈzywaÊ innymi sposobami. Do tej ostatniej kategorii naleĝy problem odwracania ciÈgu. mwiczenie 4.14 (str. 110) Zdefiniuj makro swap(t,x,y) wymieniajÈce wartoĂci dwóch argumentów, których typ to t (pomocna bÚdzie struktura blokowa). #define swap(t, x, y) { t _z; \ _z = y; \ y = x; \ x = _z; } UĝywajÈc nawiasów klamrowych, definiujemy blok. Na poczÈtku bloku moĝemy za- deklarowaÊ zmienne lokalne. _z to zmienna lokalna typu t, która pomaga zamieniÊ dwa argumenty. Makro swap dziaïa poprawnie, o ile ĝaden z argumentów nie ma nazwy _z. Jeĝeli tak jest, swap(int, _z, x); to po rozwiniÚciu makra uzyskujemy { int _z; _z = _z; _z = x; x = _z; } i wymiana nie nastÚpuje. Przyjmujemy wiÚc zaïoĝenie, ĝe _z nie bÚdzie wykorzysty- wane jako nazwa zmiennej. 76
Pobierz darmowy fragment (pdf)

Gdzie kupić całą publikację:

Język ANSI C. Programowanie. Ćwiczenia. Wydanie II
Autor:
,

Opinie na temat publikacji:


Inne popularne pozycje z tej kategorii:


Czytaj również:


Prowadzisz stronę lub blog? Wstaw link do fragmentu tej książki i współpracuj z Cyfroteką: