Capitolul 1.5. O colectie de programe utile.

Vezi subiectul anterior Vezi subiectul urmator In jos

Capitolul 1.5. O colectie de programe utile.

Mesaj  zooky la data de Mier Mar 18, 2009 12:45 am

Vom considera in cele ce urmeaza o familie de programe inrudite pentru efectuarea de operatii simple asupra datelor alcatuite din caractere. Vom vedea ca multe programe sint doar versiuni extinse ale prototipurilor pe care le vom discuta aici.

Introducere si extragere de caractere

Biblioteca standard poseda functii pentru citirea si scrierea unui caracter la un moment dat. "getchar()" aduce urmatorul caracter de intrare de fiecare data cind este apelata si returneaza acel caracter ca si valoare a ei. Adica, dupa

c=getchar()

variabila "c" contine urmatorul caracter de intrare. Caracterele vin in mod normal de la terminal, dar aceasta nu ne intereseaza pina in Capitolul 7.

Functia "putchar(c)" este complementara lui "getchar()":

putchar(c)

tipareste continutul variabilei "c" pe un mediu de iesire, in mod normal, tot pe terminal. Apelurile la "putchar" si "printf" pot fi intercalate; iesirea va apare in ordinea in care s-au facut apelurile.
Ca si in cazul lui "printf", nu exista nimic special relativ la "getchar" si "putchar". Ele nu sint parti ale limbajului C, dar sint universal disponibile.

Copiere de fisiere

Date "getchar" si "putchar", veti putea scrie o cantitate surprinzatoare de cod util, fara a sti nimic despre operatiile de I/O.
Cel mai simplu program este acela care-si copiaza intrarea in iesire, caracter cu caracter. Schitindu-l:

citeste_un_caracter
while (caracterul_nu_este_semnal_de_sfirsit_de_fisier)
tipareste_caracterul_chiar_citit
citeste_un_nou_caracter

Convertind aceasta in limbajul C, obtinem:

main() /* copy input to output; 1st version */
{
int c;
c = getchar();
while (c != EOF) {
putchar(c);
c = getchar(c);
}
}

Operatorul relational "!=" inseamna "diferit de".
Principala problema este detectarea sfirsitului de intrare. Prin conventie, "getchar" returneaza o valoare care nu este un caracter valid atunci cind intilneste sfirsitul intrarii; in acest mod, programele pot detecta cind s-au terminat intrarile. Singura complicatie, o neplacere serioasa de fapt este aceea ca exista doua conventii ce se folosesc uzual pentru valoarea sfirsitului de fisier. Noi am evitat aceasta folosind deobicei numele simbolic de EOF pentru aceasta valoare, oricare ar fi fost ea. In practica, EOF poate fi sau -1 sau 0, asa ca programul trebuie sa fie precedat de una din declaratiile de mai jos:

#define EOF -1
sau
#define EOF 0

pentru ca el sa lucreze corect. Folosind constanta simbolica EOF pentru a reprezenta valoarea pe care o ret/neaza getchar cind intilneste sfirsitul de fisier, ne asiguram ca numai un singur lucru din program depinde de valoarea numerica specificata.
De asemenea il declaram pe "c" ca fiind "int", nu "char", pentru ca el sa poata pastra valoarea pe care o returneaza "getchar". Cum vom vedea in Capitolul 2, aceasta valoare este normal un "int", deoarece ea trebuie sa fie capabila sa-l reprezinte si pe EOF in plus fata de toate char-urile posibile.
Programul de copiere ar putea fi de fapt scris mult mai concis de catre un programator experimentat in limbajul C. In C, o asignare ca

c = getchar()

poate fi folosita intr-o expresie; valoarea sa este pur si simplu valoarea ce se asigneaza partii stingi. Daca asignarea unui caracter lui c se pune in partea de test a unui "while", programul de copiat fisiere poate fi scris astfel:

main() /* copy input to output; 2nd version */
{
int c
while ((c = getchar()) != EOF)
putchar(c);
}

Programul citeste un caracter, il asigneaza lui "c" si apoi testeaza daca acesta a fost semnalul de sfirsit de fisier. Daca nu a fost, corpul buclei "while" este executat, tiparindu-se caracterul si bucla se repeta. Cind semnalul de sfirsit de fisier este atins in fine, bucla "while" se termina, termininduse totodata si programul "main".

Aceasta versiune centralizeaza intrarile - nu mai apare decit un singur apel la "getchar"- si restringe programul. Plasarea unei asignari intr-un test constituie unul din locurile unde C permite o concizie uluitoare. (Este posibil sa mergeti si mai departe, creind un cod impenetrabil, tendinta pe care noi incercam sa nu o incurajam).

Este important sa recunoastem ca parantezele ce includ asignarea sint absolut necesare. Ponderea lui "!=" este mai mare decit aceea a lui "=" ceea ce inseamna ca, in absenta parantezelor, testul relational "!=" va fi facut inaintea asignarii "=". Asa ca instructiunea

c = getchar() != EOF

este echivalenta cu

c = (getchar() != EOF)

Aceasta are un efect nedorit, prin setarea lui "c" pe 0 sau 1, dupa cum apelul lui "getchar " a intilnit sau nu sfirsitul de fisier. (Mai multe despre acestea se vor vedea in Capitolul 2).


Contorizarea caracterelor

Urmatorul program va contoriza caracterele; el este o mica elaborare a programului de copiere.

main() /* count characters in input */
{
long nc;
nc = 0;
while (getchar() != EOF)
++nc;
printf("%ld\n",nc);
}

Instructiunea

++nc;

ne introduce un nou operator "++" care inseamna, increment cu 1.
Se putea scrie si "nc = nc+1", dar "++nc" este mai concisa si adesea mai eficienta. Exista un operator corespunzator, "--" pentru decrementare cu 1. Operatorii "++" si "--" pot fi atit operatori prefix, cit si sufix ("nc++"); aceste doua forme au valori diferite in expresii asa cum se va arata in Capitolul 2, dar "++nc" si "nc++" il incrementeaza amindoi pe "nc".
Programul de contorizare de caractere acumuleaza numarul de caractere intr-o variabila "long" in loc de "int". La PDP-11, valoarea maxima pentru un intreg este 32767 si s-ar putea ca sa dam peste o depasire de contor daca-l declaram intreg; pe Honeywell si pe IBM, "long" si "int" sint sinonime si mult mai mari. Specificatorul de conversie "%ld" semnaleaza lui "printf" ca argumentul corespunzator este un intreg "long".
Pentru a face fata la numere chiar si mai mari, se poate folosi o declaratie de "double" ("float" de lungime dubla). Vom folosi, deasemenea, instructiunea "for" in locul lui "while" pentru a ilustra un alt mod in scrierea buclei.

main() /* count characters in input */
{
double nc ;
for (nc = 0; getchar() != EOF; ++nc)
;
printf("%.0f\n", nc);
}

"printf" foloseste "%f" atit pentru "float" cit si pentru
"double"; "%.0f" suprima tiparirea partii fractionare inexistente.

Corpul buclei "for" este in cazul acesta vid, deoarece toata munca este facuta in partile de test si reinitializare. Dar regulile gramaticale ale limbajului C pretind ca o instructiune "for" sa aiba un corp. Punctul si virgula ce apare izolat pe o linie, in mod tehnic o instructiune nula, este pus tocmai pentru a satisface aceasta cerere. Noi l-am pus pe o linie separata tocmai pentru a-l face mai vizibil.

Inainte de a parasi programul de contorizare caractere, sa observam ca daca intrarea nu contine nici un caracter, testul din "while" sau "for" esueaza la primul apel la getchar si deci rezultatul programului este zero, ceea ce este corect. Aceasta este o observatie importanta. Unul din lucrurile frumoase care se pot spune despre "while" si despre "for" este cela ca ele testeaza la inceputul buclei, inainte de a prelucra corpul buclei. Daca nu este nimic de facut, nimic nu se face, chiar daca aceasta inseamna ca nu se va parcurge corpul buclei niciodata. Programele vor actiona inteligent atunci cind vor minui intrari de tipul "nici un caracter". Instructiunile "while" si "for" ne asigura ca vor face lucruri rezonabile si in conditii la limita.

Contorizarea liniilor

Urmatorul program contorizeaza liniile pe care le primeste ca intrare. Liniile de intrare se presupun a fi terminate cu un caracter "linie noua" \n adaugat cu sfintenie la fiecare linie scrisa.

main() /* contorizarea liniilor in intrare */
{
int c, nl;
nl = 0;
while ((c = getchar()) != EOF)
if(c == '\n')
++nl;
printf("%d\n", nl);
}

Corpul buclei "while" consta acum dintr-un "if",care la rindul ei controleaza incrementarea ++nl. Instructiunea "if" testeaza conditia din paranteza si, daca este adevarata, se executa instructiunea (sau grupul de instructiuni dintre acolade) care urmeaza. Am aliniat iarasi, ca sa aratam ce este controlat de cine (ce).

Semnul dublu de egal "==" este in C notatia pentru "este egal cu" (ca si .EQ. din FORTRAN). Acest simbol este folosit pentru a distinge testul de egalitate de egal simplu (=) folosit pentru asignare. Deoarece asignarea este cam de doua ori mai frecventa in C decit testul de egalitate, este normal ca si operatorul de asignare sa fie jumatate din cel de egalitate, ca lungime.

Orice caracter singur poate fi scris intre apostrofuri, pentru a produce valoarea numerica a caracterului in codul de carctere al calculatorului; acesta se numeste constanta de caracter. Asa de exemplu, 'A' este o constanta de caracter; in setul de caractere ASCII, valoarea sa este 65, reprezentarea interna a caracterului A. Desigur 'A' este de preferat lui 65: semnificatia lui este evidenta si independenta de orice set particular de caractere.

Secventele escape folosite in sirurile de caractere sint si ele legale in constantele de caracter, asa ca in teste si in expresii aritmetice '\n' tine locul caracterului "linie noua".
Sa notam ca '\n' este un singur caracter si, in expresii, este echivalent cu un singur intreg; pe de alta parte, "\n" este un sir de caractere care, intimplator, contine un singur caracter. Subiectul comparatiei intre siruri si caractere este discutat mai departe in Capitolul 2.

Exercitiul 1.6. Scrieti un program care sa numere blankurile, taburile si new-line-urile.

Exercitiul 1.7. Scrieti un program care sa copieze intrarea in iesire,inlocuind fiecare sir de unul sau mai multe blankuri cu un singur blank.

Exercitiul 1.8. Scrieti un program care sa inlocuiasca fiecare tab printr-o secventa >,backspace,- care se va tipari ca "->" s fiecare backspace prin secventa similara "<-".Aceasta face taburile si backspace-urile vizibile.

Contorizarea de cuvinte

Al patrulea program din seria de programe utile va contoriza linii, cuvinte si caractere, un singur cuvint fiind definit ca orice secventa de caractere care nu contine blanc, tab sau linie noua (acesta este de fapt un schelet al programului utilitar "wc" din UNIX).

#define YES 1
#define NO 0
main() /*contorizare linii, cuvinte si caractere la intrare*/
{
int c, nl, nw, nc, inword;
inword = NO;
nl = nw = nc = 0;
while ((c = getchar()) != EOF) {
++nc;
if(c == '\n')
++nl;
if(c == ' ' || c == '\n' || c == '\t')
inword = NO;
else if (inword == NO) {
inword = YES;
++nw;
}
}
printf("%d %d %d'\n", nl, nw, nc);
}

De fiecare data cind programul intilneste primul caracter al unui cuvint, il contorizeaza. Variabila "inword" inregistreaza de cite ori programul este intr-un cuvint sau nu ; initial el "nu este intr-un cuvint " si variabilei i s-a asignat valoarea NO. Preferam constantele simbolice YES si NO valorilor literale 1 si 0 deoarece ele fac programul mai usor citibil. Desigur ca intrun program mic ca acesta diferenta este mica, dar intr-un program mai mare cresterea in claritate merita micul efort suplimenar de a-l scrie in acest mod de la inceput. Veti vedea deasemenea ca este mai usor sa efectuati modificari masive in programe in care numerele apar numai ca si constante simbolice.

Linia

nl = nw = nc = 0;

seteaza toate cele trei variabile pe zero. Acesta nu este un caz special ci doar o consecinta a faptului ca o asignare asociaza de la dreapta spre stinga. Este ca si cind am fi scris;

nc = (nl = (nw = 0));

Operatorul || inseamna SAU, asa ca linia

if(c == ' ' || c == '\n' || c == '\t');

spune ca "daca c este un blanc sau c este o linie noua sau c este un tab...". (Secventa escape \t este reprezentarea vizibila a caracterului tab).Exista un operator corespunzator && pentru SI. Expresiile conectate prin && sau || sint evaluate de la stinga la dreapta si evaluarea se opreste atunci cind se cunoaste adevarul sau falsul expresiei. Astfel daca c contine un blanc, nu mai este nevoie sa testam daca el contine o line noua sau un tab, asa ca testele acestea nu se mai fac. In particular, aceasta nu este important aici, dar este foarte semnificativ in multe situatii complicate, asa cum vom vedea in curind.

Exemplul nostru foloseste deasemenea instructiunea "else", care specifica o actiune alternativa ce trebuie executata daca partea de conditie unei instructiuni "if" este falsa.

Forma generala este:

if (expresie)
instructiune1
else
instructiune2

Una si numai una din instructiunile asociate cu if-else se executa. Daca "expresia" este adevarata, se executa "instructiunea-1"; daca nu, se executa "instructiunea-2". Fiecare "instructiune" poate fi, de fapt, mult mai complicata. In exemplul nostru instructiuea de dupa "else" este un "if" care controleaza doua instructiuni in paranteze.

Exercitiul 1.9. Cum veti testa programul de contorizare cuvinte? Care sint unele dintre limitele lui ?

Exercitiul 1.10. Scrieti un program care sa tipareasca cuvintele introduse,cite unul pe linie.

Exercitiul 1.11. Revizuiti programul de contorizare cuvinte pentru a folosi o mai buna definitie a "cuvintului", de
exemplu o secventa de litere, cifre si apostrofuri care incepe cu o litera.
avatar
zooky
Moderator
Moderator

Numarul mesajelor : 147
Data de inscriere : 15/03/2009
Varsta : 24
Localizare : Cernatesti City

Vezi profilul utilizatorului http://e-learning.forumhit.ro

Sus In jos

Vezi subiectul anterior Vezi subiectul urmator Sus


 
Permisiunile acestui forum:
Nu puteti raspunde la subiectele acestui forum