Limbaje de programare: Laborator 8

Funcții de intrare/ieșire

Temă pentru laborator

Scrieți secvențe de cod, inclusiv verificarea citirii corecte, pentru: Scrieți un program care citește numere întregi, separate prin spații albe, până la sfârșitul intrării, și afișează suma lor.

Scrieți un program care citește cuvinte, presupuse de cel mult 50 de caractere, până la sfârșitul intrării, și le afișează, fiecare pe câte un rând.

Exerciții pregătitoare

Tipare de citire repetată În multe probleme trebuie prelucrat un șir de date de același tip pânș câd se încheie (la EOF sau când nu mai apare formatul așteptat). Pentru aceasta se pot folosi cicluri simple cu structura
while (apel citire cu rezultat corect)
  prelucrează
Exemple (fragmente de cod, se folosesc în funcția care face prelucrarea)
Prelucrarea linie cu linie (de dimensiune limitată)
#define LEN 128
char line[LEN];
while (fgets(line, LEN, stdin)) {
  // prelucrează linia
}
Prelucrarea unui șir de cuvinte (de dimensiune limitată), separate prin spații
char cuv[64];
while (scanf("%63s", cuv) == 1) {
  // prelucreaza cuvântul
}
Prelucrarea unui șir de numere, separate prin spații:
int n;
while (scanf("%d", &n) == 1) {
  // prelucrează numărul n
}
Exercițiul 1 Citiți într-un tablou de caractere două cuvinte de maxim 20 caractere (separate prin spații albe în intrare), și plasați-le în tablou unite printr-o liniuță. Exemplu: din Ana Maria în intrare construim șirul Ana-Maria în tablou.

Tabloul trebuie declarat de minim 20 + 1 (liniuța) + 20 + 1 (pentru \0) caractere. Cuvintele nu trebuie citite în tablouri separate, ci direct acolo unde trebuie plasate: după citirea primului cuvânt calculăm lungimea lui, și plasăm o liniuță la sfârșit. Apoi, citim al doilea cuvânt la adresa caracterului următor în tablou. Pentru a citi un cuvânt trebuie să dăm funcției scanf adresa la care se va citi cuvăntul. Nu trebuie ca această adresă să fie neapărat începutul unui tablou (parametrii în C se transmit prin valoare, deci o funcție nu are cum "să-și dea seama" că adresa e început de tablou sau nu), ci trebuie doar ca de la acea adresă începând să rămână suficient de mult loc pentru a citi cuvântul.

#include <stdio.h>
#include <string.h>

int main(void)
{
  char s[42];
  printf("Introduceti doua cuvinte de max. 20 caractere: ");
  if (scanf("%20s", s) == 1) {
    unsigned len = strlen(s);	// afla lungimea cuvantului citit
    s[len++] = '-';		// adauga liniuta, creste lungimea
    if (scanf("%20s", &s[len]) == 1)	// citeste unde trebuie, pune si \0
      printf("Cuvintele legate: %s\n", s);
    else printf("lipseste cuvantul 2\n");
  } else printf("nu s-au introdus cuvinte\n");
  return 0;
}
Este important să facem verificările la citire; altfel, dacă utilizatorul nu introduce cele solicitate, programul va afișa conținutul întâmplător al tabloului s (neinițializat!); sau programul poate fi abandonat forțat dacă apelul de tipărire sau de calcul al lungimii șirului nu întâlnește la timp caracterul nul și continuă accesul într-o zonă de memorie invalidă.

Exercițiul 2 Citiți o propoziție (șir de caractere care se termină cu '.') de maxim 80 de caractere (inclusiv punctul) și afișați în caz contrar un mesaj de eroare.

#include <stdio.h>

#define LEN 80

int main(void)
{
  char p[LEN+1];
  for (int i = 0; i < LEN; ++i) {	// citim direct in tablou
    int c = getchar();
    if (c == EOF) {
      printf("propozitie neterminata, EOF\n"); return 1;
    } else if (c == '.') {
      p[i++] = c; p[i] = '\0'; // termină șirul
      printf("propozitia: %s\n", p); return 0;
    } else p[i] = c;
  }
  p[LEN] = '\0';		// termină șirul
  printf("propozitie prea lunga: %s", p);
  return 2;
}
Problema e simplă, dar trebuie tratate atent diversele situații în care se poate încheia citirea: Pentru a nu depăși dimensiunea tabloului, scriem un ciclu cu număr fix de iterații (lungimea tabloului), urmând să ieșim forțat dacă întâlnim punctul sau EOF.
Citirea caracterului se face într-o variabilă de tip int și nu direct în tablou (p[i]), pentru că nu am putea distinge valoarea EOF care nu face parte din tipul char.
La apariția punctului sau EOF putem încheia întreg programul, cu return din funcția main (rezultat 0 = corect, nenul = eroare). Așa cum e scris codul, nu putem folosi break, pentru că după ieșirea din ciclu e cod care nu trebuie executat în cele două cazuri de terminare.

Întrucât șirul nu e citit cu funcții standard (fgets, scanf, etc.) trebuie să introducem terminatorul '\0' pentru a-l putea folosi ulterior (de exemplu în printf).

Problema se poate rezolva și altfel, citind max. 79 de caractere în afară de punct cu scanf(p, "%79[^.]"). Trebuie verificată citirea corectă, și dacă următorul caracter e punct, apare EOF, sau au fost citite 79 de caractere și apoi nu apare punctul (propoziție prea lungă).

Exercițiul 3. Căutarea de tipare în texte.
Scrieți un program care citește de la intrare un text și tipărește acele fragmente aflate între șirurile begin și end (nu neapărat de sine stătătoare).

Problema seamănă cu cea rezolvată la curs, în care se tipărește orice text care nu e inclus între < și >, dar textul interesant nu mai e delimitat de o pereche de caractere, ci de șiruri (cuvinte).

Pentru început, trebuie să găsim în text șirul begin. Dacă s-ar fi căutat cuvinte de sine stătătoare, s-ar putea citi textul cuvânt cu cuvânt, și compara fiecare cuvânt cu cel căutat (tratând însă și cuvintele foarte lungi care s-ar putea să fie despărțite prin citirea pe lungime limitată).

În acest caz, putem căuta întâi primul caracter, și apoi verifica dacă urmează restul șirului. Putem citi până la b cu secvența (unde nu trebuie uitat testul de EOF):

int c;
do c = getchar();
while (c != EOF && c != 'b)';

Odată găsit b s-ar putea citi 4 caractere, și verifica dacă s-a citit egin, sau altceva; în cazul din urmă, s-ar putea ca în aceste 4 caractere să apară din nou începutul lui begin. Ca să distingem acest caz, putem limita din nou citirea la caractere diferite de b:
char incep[5] = "";	 // initializat
scanf("%4[^b]", incep);  // max. 4 caractere fara 'b'
if (strcmp(incep, "egin") == 0) // ... s-a potrivit ...
Mai simplu, în formatul din scanf, caracterele obișnuite trebuie să se potrivească exact În intrare, și putem folosi formatul %n pentru a afla numărul caracterelor citite:
int matchbegin(void)
{
  int nrc = 0;
  do {
    scanf("%*[^b]");
    if (scanf("begin%n", &nrc) == EOF) return 0;
  } while (nrc == 0);
  return 1;
}
Primul apel, scanf("%*[^b]") citește fără a memora (modificatorul *) oricâte caractere din mulțimea (specificatorul [ ]) care NU include (descrierea începe cu ^) caracterul b, și se oprește fără să-l consume pe b.
Al doilea scanf va returna EOF dacă s-a ajuns la sfârșitul intrării înainte de apel (căutând un b). Dacă citește cu succes begin va stoca numărul de caractere citite (5) în variabila nrc. Altfel, citirea nu ajunge la formatul %n, variabila rămâne neatribuită și ciclul continuă.

Pentru copierea până la end procedăm similar: un ciclu de copiere caracter cu caracter pana la e, după care verificăm dacă apare nd, citind un șir de două caractere, dar fără să permitem e (o altă variantă ar fi scanf("%2[dn]", care permite doar caracterele d și n). Dacă șirul citit e chiar nd, am încheiat, altfel șirul (împreună cu 'e' inițial) e tipărit, și reluăm căutarea. Testul și ieșirea la EOF se face în ciclul care caută litera e, ciclul exterior poate fi reluat necondiționat: for (;;), sau while (1) .

void copyend(void)
{
  for (;;) {
    int c;
    while ((c = getchar()) != 'e')
      if (c == EOF) return; 
      else putchar(c);
    char w[3] = "";
    scanf("%2[^e]", w);
    if (strcmp(w, "nd") == 0) return;
    else { putchar('e'); printf("%s", w); }
  }
}
Programul principal se scrie atunci foarte simplu:
int main(void)
{
  while (matchbegin())
    copyend();
}

Probleme propuse

  1. Scrieți un program care extrage din textul citit de la intrare șiruri care corespund cu următoarele tipare: Ulterior, din șirurile găsite, selectați și afișați cele care sunt
  2. Scrieți un program care extrage din textul citit de la intrare:
  3. Scrieți un program care tipărește din textul de la intrare toate fragmentele aflate între begin și end, dacă acestea apar ca și cuvinte de sine stătătoare.
  4. Scrieți un program care numără aparițiile unui șir specificat (variantă: cuvânt de sine stătător) în textul de la intrare.
  5. Scrieți un program care citește de la intrare un șir de întregi separați prin spații și afișează subșirurile crescătoare de cel puțin două numere.
    Exemplu: din 4 5 6 2 3 1 -4 -4 va afișa
    4 5 6
    2 3
    -4 -4
  6. Scrieți un program care citește de la intrare și prelucrează un tabel în format CSV (comma-separated values), în care fiecare linie de text conține date separate prin virgule.
    Fiecare linie conține un nume de student, si apoi trei note (reali între 1 și 10) dintre care unele pot lipsi. De exemplu:
    Andrei Ionascovici,8, 7.5, 5
    Vlad Prisacescu,5  ,,6.5
    
    Eventualele spații de ambele părți ale virgulelor nu contează.
    Citiți textul și afișați fiecare student cu media lui. Dacă una din note e sub 5 sau lipsește, afișați "nepromovat".
    Atenție la citire când ultima notă de pe o linie lipsește
  7. Prelucrarea textelor structurate
    Mai multe tipuri de reprezentare structurată permit definirea de secțiuni etichetate, care pot fi încuibate pe mai multe nivele.
    a) Fișiere XML reprezintă informația structurat, asociind elementelor de date câte o etichetă care apare în forma <nume> la început, și în forma </nume> la sfârșitul valorii reprezentate, de exemplu <nota>9</nota>. Entitățile de date pot conține la rândul lor alte elemente, de exemplu:
    <student>
      <prenume>Ion</prenume>
      <nume>Iovanescovici</nume>
      <nota>9</nota>
      <nota>8</nota>
      <nota>10</nota>
    </student>
    

    b) LaTeX e un limbaj pentru descrierea documentelor în vederea tipăririi. El permite secțiuni delimitate prin comenzile \begin{nume} și \end{nume}, care pot fi la rândul lor încuibate. De exemplu, fragmentul
    \begin{center}
    \begin{verbatim}
    int main(void)
    { 
      return 0;
    }
    \end{verbatim}
    Cel mai simplu program C
    \end{center}
    
    are o astfel de secțiune (verbatim) împreună cu alt text obșnuit (dedesubt), ambele făcând parte dintr-o altă secțiune (center).
    În ambele cazuri (XML și LaTeX) secțiunile pot fi încuibate, în alta, dar nu se pot intercala (etichetele se închid în ordinea inversă în care au fost deschise).

    Scrieți un program care citește de la intrare un text cu structura respectivă și afișează doar textul propriu-zis din secțiuni, semnalând dacă apar erori de formatare (etichete nepereche).

    Soluția e recursivă, observând că textul are structura:
    secțiune ::= eticheta-inceput repetiție de text obișnuit și secțiuni eticheta-sfarșit
    La întâlnirea unei etichete de început, se apelează funcția de prelucrare a unei secțiuni, cu parametru numele etichetei. Funcția se încheie la întâlnirea etichetei de sfârșit cu același nume; ea prelucrează (tipărește) textul obișnuit, și la întâlnirea altei etichete de început, se apelează recursiv.


Marius Minea
Last modified: Tue Nov 8 20:20:00 EET 2011