Clasa a V-a lecția 18 - 7 dec 2017
Tema 16 – rezolvări
Rezolvări probleme lecția 16 aici [1]
Tema 17 - rezolvări
Rezolvări probleme lecția 17 aici [2]
Lecție
<html5media height="720" width="1280">https://www.algopedia.ro/video/2017-2018/2017-12-07-lectie-info-18-720p.mp4</html5media>
Tipul caracter
Introducere
Pînă acum am văzut cum putem prelucra numere folosind calculatorul. Mai exact, am folosit numere întregi în diverse feluri:
- Am extras cifrele lor, le-am răsturnat, am aflat cîte și care cifre au două numere în comun, le-am reordonat pentru a alcătui cel mai mare număr posibil, și cîte și mai cîte.
- Am căutat divizorii numerelor, i-am numărat, am descompus numerele în factori primi, am aflat dacă sînt prime, sau aproape prime.
- Apoi am citit și prelucrat secvențe de numere. Am aflat dacă sînt crescătoare, dacă au subsecvențe de numere identice, dacă au vîrfuri și văi (vezi problema cabina).
Oare calculatorul poate, însă, să înțeleagă și să opereze și cu entități diferite de numere? Cum ar fi litere?
Știm că la baza calculatorului se află numerele. Calculatorul lucrează cu numere reprezentate în baza doi. O cifră binară în calculator se cheama bit.Dar poate el să înțeleagă ceva ce nu e număr, anume literele pe care noi le folosim zi de zi pentru a citi sau a scrie? Răspunsul este da. În continuare vom încerca să lămurim modul în care calculatoarele opereaza cu caractere.
Codul ASCII
Atunci cînd scriem un program în mediul de programare (CodeBlocks), apăsăm pe anumite taste și caracterele corespunzătoare apar pe ecran. Ele nu sînt numai cifre. Avem și litere, ba chiar si semne de punctuație, mai mic, mai mare, paranteze rotunde, acolade și așa mai departe. Din moment ce ele trec prin calculator este clar că el înțelege caractere. Dar el trebuie să facă conversia la ceea ce înțelege el nativ, și anume numere întregi. Pentru aceasta el folosește o tabelă în care fiecare caracter are un cod unic, un număr natural între 0 și 255. Această tabelă se numește ASCII (American Standard Code for Information Interchange). Iată un extras din această tabelă:
Cod ASCII | Caracter |
---|---|
32 | Spațiu |
33 | ! |
34 | " |
35 | # |
36 | $ |
65 | A |
66 | B |
67 | C |
90 | Z |
91 | [ |
92 | \ |
97 | a |
98 | b |
99 | c |
Fiecare caracter are codul lui unic. Remarcați că literele mari și literele mici au coduri distincte.
Caractere în limbajul C
În limbajul C avem variabile și expresii de tip caracter. O variabilă de tip caracter poate stoca un singur caracter și se declară astfel:
char c;
Unei astfel de variabile putem sa-i atribuim un caracter:
char c;
c = 'a';
Constantele caracter, cum ar fi 'a', se specifică între apostroafe, pentru a se deosebi de variabile. În urma instrucțiunii de mai sus variabila c va stoca valoarea 'a'. În realitate valoarea stocată va fi un număr, și anume codul ASCII al caracterului 'a', adică 97. Deoarece calculatorul cunoaște tabela codurilor de caractere el face automat conversia de la 'a' la 97, fără a fi nevoie ca noi să reținem acest număr.
Identitatea între caractere și coduri ASCII
În limbajul C nu se face distincție între un caracter și codul lui ASCII. Aceasta înseamnă ca următoarele două instrucțiuni sînt echivalente:
c = '[';
c = 91;
În primul caz atribuim caracterul '[' variabilei c. În realitate variabila c va stoca codul ASCII al acelui caracter, adică 91. În al doilea caz îi atribuim numărul 91. Dar variabila c fiind de tip caracter calculatorul va ști că 91 este de fapt codul caracterului '['. Iată un alt exemplu. Ce face codul următor?
if ( 'b' == 98 )
printf( "Sint egale" );
else
printf( "Nu sint egale" );
Din nou, mulțumită echivalenței între caractere și codurile lor ASCII programul va tipări "Sint egale". Ce va face următoarea secvență?
char c;
c = 'a';
c = c + 1;
if ( c == 'b' )
printf( "la caractere se poate aduna un numar" );
Prima instrucțiune va depune caracterul 'a' în variabila c. În realitate c va stoca codul ASCII al lui 'a', adică 97. Următoarea instrucțiune va aduna 1 la c, noua lui valoare fiind 98. Prin echivalență, aceasta este tot una cu caracterul 'b', deci programul va afișa textul din printf.
Citirea și scrierea caracterelor
Deși fscanf și fprintf pot citi/scrie caractere folosind descriptorul %c această metodă nu este recomandată. În locul fscanf/fprintf vom folosi perechea de funcții fgetc/fputc, astfel:
c = fgetc( fin ); // citeste din fisierul fin un caracter si depune-l in variabila c
fputc( c, fout ); // scrie caracterul c in fisierul fout
Pentru a citi/scrie un șir de caractere vom chema în mod repetat fgetc/fputc. Iată un scurt exemplu de program care afișează toate literele mari din alfabetul englez:
#include <stdio.h>
int main() {
FILE *fout;
char c;
fout = fopen( "litere-mari.out", "w" );
for ( c = 'A'; c <= 'Z'; c++ )
fputc( c, fout );
fclose( fout );
return 0;
}
În continuare iată un exemplu care citește n caractere și ne spune dacă toate sînt litere:
#include <stdio.h>
int main() {
FILE *fin, *fout;
int n, i;
char c;
fin = fopen( "test-litere.in", "r" );
fscanf( fin, "%d", &n );
c = fgetc( fin ); // citim caracterul sfirsit de linie '\n'
i = 0;
c = fgetc( fin );
while ( i < n && (('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z')) ) {
c = fgetc( fin );
i++;
}
fclose( fin );
fout = fopen( "test-litere.out", "w" );
if ( i < n ) // am gasit un caracter care nu e litera
fprintf( fout, "Nu sint numai litere\n" );
else // inseamna ca i == n, deci toate caracterele au fost litere
fprintf( fout, "Sint numai litere\n" );
fclose( fout );
return 0;
}
Detectarea sfîrșitului de linie
Uneori este necesar să citim caractere dintr-un fișier fără a ști în prealabil cîte caractere avem de citit. În aceste cazuri vom citi pînă la terminarea liniei. Cum putem afla că linia s-a terminat? Atunci cînd linia s-a terminat funcția fgetc returnează un caracter sfîrșit de linie, notat '\n'. În aceste situații va trebui ca în condiția din buclă să testăm dacă caracterul citit este egal cu '\n'. Iată un exemplu care citește toate caracterele dintr-un fișier și afișează numai acele caractere care sînt litere mari:
#include <stdio.h>
int main() {
FILE *fin, *fout;
char c;
fin = fopen( "filtru-litere.in", "r" );
fout = fopen( "filtru-litere.out", "w" );
c = fgetc( fin ); // citim primul caracter
while ( c != '\n' ) { // cita vreme caracterul citit nu e sfirsit de linie
if ( c >= 'A' && c <= 'Z' ) // daca este litera mare il afisam
fputc( c, fout );
c = fgetc( fin );
}
fputc( '\n', fout ); // pentru eleganta
fclose( fin );
fclose( fout );
return 0;
}
Conversia la litere mari
Una din problemele care apare des atît în realitate cît și la olimpiade este conversia unor caractere din literă mică în literă mare, sau invers. Problemă: presupunînd că variabila c conține o literă mică, care este instrucțiunea prin care în final c să conțină litera mare echivalentă?
Răspuns: observăm că codul literei 'A' este mai mic cu 32 decît codul literei 'a'. Aceeași relație se păstrează și între 'B' și 'b', 'C' și 'c', etc. Ceea ce ne duce la o primă soluție:
c = c - 32;
Simplu, nu? Problema este că trebuie să ținem minte acest număr ciudat, 32. În plus, daca vrem să facem conversia invers, de la litere mari la litere mici, va trebui sa adunăm 32 în loc să scădem. De aceea vom folosi o variantă mai elegantă:
c = 'A' + c - 'a';
Ce face această instrucțiune? Deoarece c conține o literă mică, diferența c - 'a' va fi un număr între 0 și 25 care reprezintă numărul ce trebuie adăugat la litera 'a' pentru a obține chiar litera originală din c. Cu alte cuvinte:
c == 'a' + (c - 'a')
Care este o egalitate destul de evidentă. Interpretăm această egalitate astfel: c - 'a' este cantitatea ce trebuie adăugată la prima literă a alfabetului pentru a obține litera din variabila c. Această interpretare rămîne valabilă fie că vorbim de litere mici sau litere mari. Drept pentru care, pentru a obține litera mare, vom înlocui primul 'a' cu 'A':
c = 'A' + c - 'a';
Iată un exemplu complet de program care citește un șir de caractere pîna la final de linie și scrie același șir în fișierul de ieșire, convertind literele mici la litere mari și lăsînd celelalte caractere neschimbate:
#include <stdio.h>
int main() {
FILE *fin, *fout;
char c;
fin = fopen( "upper.in", "r" );
fout = fopen( "upper.out", "w" );
c = fgetc( fin );
while ( c != '\n' ) {
if ( c >= 'a' && c <= 'z' )
c = c - 'a' + 'A';
fputc( c, fout );
c = fgetc( fin );
}
fputc( '\n', fout ); // pentru eleganta
fclose( fin );
fclose( fout );
return 0;
}
Caractere speciale (caractere escape)
Unele caractere sînt speciale deoarece nu pot fi scrise ca atare în textul unui program. Un astfel de caracter este '\n', cu care v-ați întîlnit deja. La fel este caracterul TAB, care se codează cu '\t'. Precum vedeți ele se codifică prin două caractere, din care primul este backslash. Secvența din două caractere, primul fiind \ se mai numește și secvență escape. Aceasta vine de la verbul englezesc "a scăpa", deoarece caracterul \ schimbă sensul următorului caracter, sau, cu alte cuvinte, scapă de restricțiile codificării normale a limbajului C.
Ce alte caractere speciale mai avem? Un alt caracter este chiar apostroful. Dacă am vrea să folosim caracterul apostrof și am scrie ''' (trei apostroafe) compilatorul de C ar lega primele două apostroafe, considerînd caracterul vid, cel de-al treilea apostrof fiind începutul unui nou caracter, cea ce va genera o eroare de compilare. De aceea caracterul apostrof se codifică cu '\'' (\' între două apostroafe). Un caz similar este cel în care vrem să tipărim caracterul ghilimea. Nu putem scrie
printf( "Aceasta este o ghilimea " singura.\n" );
deoarece compilatorul de C va considera că șirul se încheie la a doua ghilimea și va semnala o eroare în continuare. De aceea codificăm și acest caracter, în mod similar, cu '\"' (\" între două apostroafe).
În fine, ultimul caracter special este chiar caracterul \ (backslash). Deoarece atunci cînd îl folosim el are o semnificație specială, nu putem să îl tipărim ca atare. De aceea, atunci cînd chiar intenționăm să spunem '\' îl dublăm: '\\'.
Tema
Rezolvați următoarele probleme cu caractere:
Tema 18 să se rezolve următoarele probleme (program C trimis la vianuarena):
Rezolvări aici [3]