Algorithms and programming languages

Page 1

Florin POSTOLACHE

Programarea calculatoarelor Ĺ&#x;i limbaje de programare prin exemple practice


Colecţia „Matematică”


Florin POSTOLACHE

Programarea calculatoarelor şi limbaje de programare prin exemple practice

Editura Academiei Navale „Mircea cel Bătrân” Constanţa, 2018


Referenţi ştiinţifici:

Conf. univ. Dr. Florentina-Loredana DRAGOMIR Conf. univ. Dr. Viorel ARITON

Copyright © 2018 Editura Academiei Navale „Mircea cel Bătrân” Toate drepturile rezervate Editare computerizată: Mirela CLEIANU Copertă: Mirela CLEIANU Editura Academiei Navale „Mircea cel Bătrân” Str. Fulgerului nr. 1, 900218, Constanţa Tel. 0241/626200/174, fax 0241/643096 Email: editura@anmb.ro

ISBN 978-606-642-167-6


Cuprins BAZELE INFORMATICII ...............................................................................................15 NOȚIUNI INTRODUCTIVE ...................................................................................................15 INFORMAȚII ȘI PRELUCRĂRI ..............................................................................................16 Date .............................................................................................................................16 Informație ....................................................................................................................18 Cunoștințe....................................................................................................................19 Sistemul informațional.................................................................................................19 Sistemul de calcul ........................................................................................................20 REPREZENTAREA ȘI STRUCTURAREA INFORMAȚIEI ......................................22 BIT, OCTET ȘI MULTIPLII ACESTORA ..................................................................................22 TIPURI DE DATE SIMPLE ....................................................................................................24 SISTEME DE NUMERAȚIE ...................................................................................................24 Sistemul de numerație zecimal (baza 10) ....................................................................25 Sistemul de numerație binar (baza 2) ..........................................................................26 Sistemul de numerație octal (baza 8)...........................................................................27 Sistemul de numerație hexazecimal (baza 16) .............................................................27 Interdependența sistemelor de numerație ....................................................................28 Conversia binar – octal - hexazecimal și invers ..........................................................29 Conversia binar – zecimal și invers ............................................................................30 REPREZENTAREA NUMERELOR ÎNTREGI ............................................................................31 PROBLEME PROPUSE: ........................................................................................................33 ALGORITM .......................................................................................................................34 REPREZENTAREA ALGORITMILOR .....................................................................................34 Scheme logice ..............................................................................................................36 Pseudocod ...................................................................................................................37 ELABORAREA ALGORITMILOR ..........................................................................................39 Analiza și complexitatea algoritmilor .........................................................................40 Complexitatea algoritmului .........................................................................................40 PRELUCRAREA INFORMAȚIEI ..................................................................................42 IDENTIFICATOR, VARIABILĂ, CONSTANTĂ, LITERAL .........................................................42 EXPRESII ...........................................................................................................................43 OPERATORI .......................................................................................................................44 TIPURI DE EXPRESII ...........................................................................................................45 INSTRUCȚIUNI ................................................................................................................47 INSTRUCȚIUNI SIMPLE (SECVENȚIALE)..............................................................................48 INSTRUCȚIUNI STRUCTURATE (STRUCTURI DE CONTROL) .................................................49 Instrucțiuni decizionale (de selecție) ...........................................................................50


Algoritmică Ĺ&#x;i programare InstrucČ›iuni repetitive (bucle sau cicluri) ....................................................................53 PROGRAME Č˜I SUBPROGRAME .................................................................................56 SUBPROGRAME .................................................................................................................57 PROGRAMUL PRINCIPAL....................................................................................................58 REALIZAREA PROGRAMELOR Č˜I PROGRAME SUPORT .........................................................59 PROBLEMATICA PROGRAMÄ‚RII .........................................................................................60 Etape ĂŽn ciclul de viaČ›Äƒ ale unui produs program .......................................................61 Limbaje de programare ...............................................................................................68 MEDIUL DE PROGRAMARE RAPTOR .......................................................................88 INTERFAČšA GRAFICÄ‚ UTILIZATOR .....................................................................................88 STRUCTURA UNUI PROGRAM.............................................................................................89 OperaČ›ii de intrare(input) ............................................................................................91 OperaČ›ii de prelucrare procesare ................................................................................92 OperaČ›ii de ieČ™ire (output). ..........................................................................................93 ExecuČ›ia algoritmului/programului .............................................................................93 SCHEME LOGICE. APLICAČšII PRACTICE UTILIZĂ‚ND RAPTOR ...........................................95 Algoritm pentru calculul Sumei a două numere ..........................................................95 Algoritm pentru calculul ariei si perimetrului unui dreptunghi ..................................95 Algoritm pentru rezolvarea ecuaČ›iei de gradul I: a*x+b=0, unde a Č™i b sunt numere reale inserate (citite) de la tastatură ...........................................................................97 Algoritm pentru rezolvarea ecuaČ›iei de gradul 2: ax2+bx+c=0 ..................................97 Algoritm pentru Suma Gauss (suma primelor n numere naturale) .............................99 Algoritm pentru Suma pătratelor/cuburilor primelor n numere naturale .................100 Algoritm pentru calculul đ?’Œ = đ?&#x;?đ?’? đ?’Œđ?’Œ + đ?&#x;? .................................................................100 Algoritm pentru calculul đ?’Œ = đ?&#x;?đ?’?đ?’Œđ?’Œ + đ?&#x;? .................................................................101 Algoritm pentru calculul đ?’Œ = đ?&#x;?đ?’?đ?’Œđ?&#x;? + đ?&#x;?đ?’Œ + đ?&#x;?đ?’Œđ?&#x;? + đ?&#x;?đ?’Œ + đ?&#x;? ..................................101 Schema logică pentru calcului factorialului (n!).......................................................102 Algoritm pentru calculul đ?‘Şđ?’?đ?’Œ ..................................................................................102 Algoritm pentru calculul đ?‘¨đ?’?đ?’Œ ..................................................................................103 Algoritm pentru numere prime ..................................................................................103 SCHEME LOGICE. SUBSCHEME SI PROCEDURI ..................................................................104 Schemă logică pentru citirea unui număr natural nenul, determinarea Č™i afiČ™area cifrelor sale................................................................................................................105 Schemă logică pentru citirea unui număr natural nenul Č™i afiČ™area inversului numărului ..................................................................................................................106 Test palindrom pentru un număr natural nenul, citit de la tastatura ........................106 MulČ›imea de palindromuri pana la n .........................................................................107 MulČ›imea de palindromuri dintr-un interval .............................................................107 Schema logică pentru citirea unui număr natural nenul, determinarea Č™i afiČ™area sumei/produsului cifrelor sale ...................................................................................108 Schema logică pentru afiČ™area produsului cifrelor unui număr natural, utilizând o subschemă logică ......................................................................................................108 Schema logică pentru afiČ™area produsului cifrelor unui număr natural, utilizând o procedură ..................................................................................................................109 39T

39T

39T

39T

39T

ii


Algoritmică şi programare Schema logică și pseudocod pentru calculul sumei/produsului cifrelor unui număr natural nenul utilizând proceduri. .............................................................................109 Aranjamente și combinări utilizând o subschemă logică ..........................................110 Sisteme de numerație. Conversia unui număr din binar (baza 2) în zecimal (baza 10) ...................................................................................................................................110 Conversia unui număr din zecimal (baza 10)în binar (baza 2) .................................111 SCHEME LOGICE. TABLOURI ...........................................................................................111 Algoritm pentru scrierea si citirea elementelor unui vector ......................................111 Algoritm pentru: ........................................................................................................112 Algoritm pentru elementele comune a doi vectori .....................................................113 Algoritm pentru sortarea elementelor vectorului ......................................................113 Matrice. Algoritm (pseudocod si schema logică) pentru scrierea si citirea elementelor ...................................................................................................................................114 Schemă logică și pseudocod pentru citirea/scrierea elementelor matricei, suma elementelor diagonalei principale si secundare ........................................................114 Algoritm sortare elemente matrice pe linia k ............................................................117 LIMBAJUL DE PROGRAMARE C/C++ .....................................................................118 NOȚIUNI INTRODUCTIVE .................................................................................................118 STRUCTURA UNUI PROGRAM C/C++ ...............................................................................119 UNITĂȚI LEXICALE C/C++ ..............................................................................................120 Mulțimea caracterelor ...............................................................................................120 Comentariul ...............................................................................................................121 Tipuri de date primare...............................................................................................122 Constante ...................................................................................................................124 Constante de tip caracter ..........................................................................................124 Constante de tip șir de caractere ...............................................................................125 Constante de tip întreg ..............................................................................................125 Constante de tip real .................................................................................................125 Cuvinte cheie .............................................................................................................125 Separatori ..................................................................................................................126 Variabile ....................................................................................................................126 EXPRESII. OPERANZI. OPERATORI ..................................................................................127 Expresii......................................................................................................................127 Operanzi ....................................................................................................................127 Conversii implicite de tip...........................................................................................127 Operatori ...................................................................................................................127 Operatori de adresare ...............................................................................................129 Operatori unari .........................................................................................................129 Operatori multiplicativi .............................................................................................130 Operatori aditivi ........................................................................................................131 Operatori pentru deplasare .......................................................................................131 Operatori relaționali .................................................................................................131 Operatori de egalitate ...............................................................................................132 Operatori logici .........................................................................................................132 Operatorul condițional ..............................................................................................132

iii


Algoritmică şi programare Operatori de atribuire ...............................................................................................132 Operatorul virgulă ....................................................................................................133 STRUCTURI ALGORITMICE. INSTRUCȚIUNI ......................................................134 INSTRUCȚIUNI SIMPLE.....................................................................................................135 INSTRUCȚIUNI COMPUSE .................................................................................................135 Blocul ........................................................................................................................135 Structuri decizionale(ramificate) ...............................................................................136 Structuri repetitive (bucle sau cicluri) .......................................................................139 Descriptori de format ................................................................................................143 APLICAȚII PRACTICE .......................................................................................................143 MASIVE DE DATE .........................................................................................................149 MASIVE DE DATE UNIDIMENSIONALE..............................................................................149 APLICAȚII PRACTICE .......................................................................................................150 Algoritm pentru citirea elementelor unui vector .......................................................150 Algoritm pentru scrierea elementelor unui vector.....................................................150 Algoritm pentru citirea și scrierea elementelor unui vector ......................................150 Suma elementelor unui vector ...................................................................................151 Suma elementelor cu valoare para, produsul elementelor cu valoare impara ale unui vector .........................................................................................................................152 Sortarea elementelor unui vector in ordine crescătoare ...........................................152 Determinarea valorii minime și poziția sa într-un vector de numere reale...............153 Produsul scalar a doi vectori ....................................................................................154 MASIVE DE DATE BIDIMENSIONALE ................................................................................155 APLICAȚII PRACTICE .......................................................................................................155 Algoritm pentru citirea/scrierea elementelor unei matrice .......................................155 Suma elementelor matricei situate pe diagonala principală si secundară ................156 Suma elemente matrice pe coloana k ........................................................................157 Suma elemente matrice de pe linia k .........................................................................158 MASIVE DE DATE N-DIMENSIONALE................................................................................158 FUNCȚII ALE LIMBAJULUI DE PROGRAMARE C/C++ ......................................160 DECLARAȚII ȘI DEFINIȚII DE FUNCȚII ..............................................................................161 Aplicații practice .......................................................................................................162 ITERAȚIA SI RECURSIVITATEA .........................................................................................166 Aplicații practice .......................................................................................................168 DEFINIREA ȘI TRANSMITEREA PARAMETRILOR. PARAMETRI FORMALI ȘI ACTUALI .........178 ADRESE DE MEMORIE. POINTERI ..........................................................................186 Operatorii de adresare si dereferenţiere ...................................................................189 Pointeri la funcții.......................................................................................................191 Aplicații practice .......................................................................................................192 STRUCTURI ȘI UNIUNI ................................................................................................199 STRUCTURI .....................................................................................................................199 Pointeri către structuri ..............................................................................................203 iv


Algoritmică şi programare Typedef. Crearea unor noi tipuri de date ..................................................................204 UNIUNI ...........................................................................................................................205 STRUCTURI ȘI UNIUNI. DIFERENȚE ..................................................................................208 DIRECTIVE PRE-PROCESOR .....................................................................................210 MACRO-DEFINIȚII ...........................................................................................................210 Directiva #define .......................................................................................................211 Comparație între macro-definiții și funcții C/C++ ...................................................212 Directiva #undef ........................................................................................................212 Directive de compilare condiționată .........................................................................213 Directivele #if, #else, #elif și #endif ..........................................................................213 Directivele #ifdef şi #ifnde.........................................................................................215 Directiva #include .....................................................................................................216 Directiva #error ........................................................................................................217 FUNCȚII DE INTRARE/IEȘIRE ..................................................................................218 FUNCȚII DE INTRARE/IEȘIRE PENTRU CARACTERE ...........................................................218 FUNCȚII DE INTRARE/IEȘIRE CU FORMAT ........................................................................219 FUNCȚII CU FORMATARE PENTRU ȘIRURI DE CARACTERE................................................220 FUNCȚII PENTRU GESTIUNEA TIMPULUI ..........................................................................220 Aplicații practice: ......................................................................................................223 FUNCȚII PENTRU OPERAȚII MATEMATICE ........................................................................226 Constante simbolice...................................................................................................226 Funcții trigonometrice ...............................................................................................226 Funcții putere și radical ............................................................................................227 Funcții exponențiale, logaritmice și hiperbolice .......................................................227 Funcții de conversie ..................................................................................................228 Funcții parte întreagă, de rotunjire și de trunchiere .................................................229 Funcții modul ............................................................................................................229 Funcții pentru generarea numerelor aleatoare .........................................................229 Aplicații practice .......................................................................................................230 FUNCȚII PENTRU OPERAȚII CU ȘIRURI DE CARACTERE .....................................................232 Aplicații practice .......................................................................................................236 FUNCȚII PENTRU CLASIFICAREA CARACTERELOR ...........................................................245 Aplicații practice .......................................................................................................247 ALOCAREA DINAMICĂ A MEMORIEI ....................................................................254 Funcțiile standard pentru gestiunea dinamică a memoriei .......................................256 Alocarea dinamică a memoriei..................................................................................257 Alocarea dinamică a memoriei utilizând pointeri .....................................................258 Aplicații practice .......................................................................................................258 Funcții pentru operații cu blocuri de memorie..........................................................264 Aplicații practice .......................................................................................................264 FIȘIERE I/O. FUNCȚII PENTRU OPERAȚII ASUPRA FIȘIERELOR ..................272 FIȘIERE DE DATE. INTRODUCERE ....................................................................................272 DESCHIDEREA ȘI/SAU CREAREA UNUI FIȘIER ...................................................................274 v


Algoritmică şi programare Deschiderea și/sau crearea unui fișier text ...............................................................275 Deschiderea și/sau crearea unui fișier binar ............................................................275 Poziționarea indicatorului de citire/scriere ..............................................................279 Funcții pentru tratarea erorilor ................................................................................281 Funcții de citire/scriere fără format ..........................................................................282 Funcții de citire/scriere șir de caractere ...................................................................284 Aplicații practice .......................................................................................................285 METODE DE PROGRAMARE .....................................................................................288 METODA “DIVIDE ET IMPERA” .......................................................................................288 Pașii metodei .............................................................................................................289 Aplicații practice .......................................................................................................290 METODA “BACKTRACKING” ...........................................................................................298 Backtracking nerecursiv ............................................................................................299 Backtracking recursiv ................................................................................................300 Pașii metodei .............................................................................................................302 Aplicații practice .......................................................................................................303 BIBLIOGRAFIE ..............................................................................................................309

vi


Algoritmică şi programare

Lista figurilor FIG. 1 TRANSMITEREA INFORMAȚIEI .....................................................................................18 FIG. 2 TRANZIȚIA DE LA DATE LA CUNOȘTINȚE .....................................................................19 FIG. 3 SISTEMUL DE CALCUL .................................................................................................21 FIG. 4 REPREZENTARE TEHNICĂ A VALORILOR LOGICE 0 ȘI 1 PENTRU UN BIT........................23 FIG. 5 SIMBOLURI GRAFICE (BLOCURI STANDARD) ................................................................36 FIG. 6 SCHEMA LOGICA PENTRU REZOLVAREA ECUAȚIEI DE GRADUL I .................................36 FIG. 7 REPREZENTAREA ALGORITMULUI PRIN SCHEMĂ LOGICĂ ............................................38 FIG. 8 PRECEDENȚA OPERATORILOR .....................................................................................45 FIG. 9 INSTRUCȚIUNI REPETITIVE CU TEST (INIȚIAL SAU FINAL) ............................................54 FIG. 10 FAZE ȘI ETAPE ÎN EXISTENȚA UNUI PRODUS PROGRAM ..............................................61 FIG. 11 CICLUL DE REALIZARE A UNUI PRODUS PROGRAM (APLICAȚIE SAU SISTEM INFORMATIC) ................................................................................................................ 62 FIG. 12 FAZELE DE REALIZARE ALE UNUI PROGRAM..............................................................66 FIG. 13 ARHITECTURA CLIENT-SERVER 2-TIER (CU DOUĂ PĂRȚI) .........................................83 FIG. 14 ARHITECTURA CLIENT-SERVER 3-TIER ȘI N-TIER ......................................................84 FIG. 15 INTERFAȚA GRAFICA UTILIZATOR RAPTOR .............................................................88 FIG. 16 CALCULUL ARIEI CERCULUI IN RAPTOR .................................................................90 FIG. 17 INSERAREA SIMBOLURILOR .......................................................................................91 FIG. 18 EDITARE SIMBOL INPUT ............................................................................................92 FIG. 19 EDITARE SIMBOL DE ATRIBUIRE ................................................................................92 FIG. 20 EDITARE SIMBOL OUTPUT .........................................................................................93 FIG. 21 EXECUȚIA PROGRAMULUI/ALGORITMULUI................................................................94 FIG. 22 ALGORITMI PENTRU CALCULUL ARIEI CERCULUI ......................................................94 FIG. 23 SUNA A DOUĂ NUMERE .............................................................................................95 FIG. 24 ARIA ȘI PERIMETRUL DREPTUNGHIULUI ....................................................................96 FIG. 25 ARIA ȘI PERIMETRUL DREPTUNGHIULUI - ALGORITMI OPTIMIZAȚI ............................96 FIG. 26 ALGORITM PENTRU REZOLVAREA ECUAȚIEI DE GRADUL I ........................................97 FIG. 27 SCHEMA LOGICA 1 PENTRU REZOLVAREA ECUAȚIEI AX2+BX+C=0) ..........................98 FIG. 28 SCHEMA LOGICA 2 PENTRU REZOLVAREA ECUAȚIEI AX2+BX+C=0) ..........................99 FIG. 29 ALGORITM PENTRU SUMA GAUSS .............................................................................99 FIG. 30 SUMA PĂTRATELOR/CUBURILOR PRIMELOR N NUMERE NATURALE .........................100 FIG. 31 CALCULUL SUMEI K(K+1) .......................................................................................100 FIG. 32 CALCULUL SUMEI K/(K+1) ......................................................................................101 FIG. 33 CALCULUL SUMEI ((K^2+2K+1)/(K^2+2K+2)) .......................................................101 FIG. 34 CALCULUL N!..........................................................................................................102 FIG. 35 CALCUL COMBINĂRI ...............................................................................................102 FIG. 36 CALCUL ARAMJAMENTE .........................................................................................103 FIG. 37 ALGORITM NUMERE PRIME .....................................................................................103 FIG. 38 CALCULUL FACTORIALULUI UTILIZÂND SUBSCHEME LOGICE..................................104 FIG. 39 CALCULUL FACTORIALULUI UTILIZÂND O PROCEDURĂ ...........................................104 FIG. 40 CITIREA UNUI NUMĂR NATURAL NENUL, DETERMINAREA ȘI AFIȘAREA CIFRELOR SALE ...................................................................................................................................105 FIG. 41 DETERMINAREA ȘI AFIȘAREA INVERSULUI UNUI NUMĂR .........................................106 FIG. 42 TEST PALINDROM ....................................................................................................106

vii


Algoritmică şi programare FIG. 43 MULȚIMEA PALINDROMURILOR PANA LA N.............................................................107 FIG. 44 MULȚIMEA DE PALINDROMURI DINTR-UN INTERVAL ..............................................107 FIG. 45 SUMA/PRODUSUL CIFRELOR UNUI NUMĂR NATURAL NENUL, CITIT DE LA TASTATURĂ ...................................................................................................................................108 FIG. 46 PRODUSUL CIFRELOR UNUI NUMĂR NATURAL, UTILIZÂND O SUBSCHEMĂ LOGICĂ ..108 FIG. 47 PRODUSUL CIFRELOR UNUI NUMĂR NATURAL, UTILIZÂND O PROCEDURĂ ...............109 FIG. 48 PRODUSUL/SUMA CIFRELOR UNUI NUMĂR NATURAL, UTILIZÂND PROCEDURI .........109 FIG. 49 ARANJAMENTE ȘI COMBINĂRI UTILIZÂND O SUBSCHEMĂ LOGICĂ ...........................110 FIG. 50 CONVERSIA UNUI NUMĂR DIN BAZA 2 ÎN BAZA 10 ..................................................110 FIG. 51 CONVERSIA UNUI NUMĂR DIN BAZA 10 ÎN BAZA 2 ..................................................111 FIG. 52 CITEȘTE N ...............................................................................................................111 FIG. 54 ALGORITM PENTRU CITIREA ȘI SCRIEREA ELEMENTELOR VECTORULUI ...................112 FIG. 53 VECTOR. CITIRE ȘI SCRIERE ELEMENTE...................................................................112 FIG. 55 ALGORITM PENTRU CALCULUL SUMEI/PRODUSULUI ELEMENTELOR UNUI VECTOR .113 FIG. 56 ELEMENTELE COMUNE A DOI VECTORI ....................................................................113 FIG. 57 SORTARE ELEMENTE VECTOR (CRESCĂTOARE) .......................................................113 FIG. 58 SCRIERE SI CITIRE ELEMENTE MATRICE ...................................................................114 FIG. 59 CITIRE /SCRIERE ELEMENTE MATRICE, SUMA ELEMENTELOR DIAGONALEI PRINCIPALE SI SECUNDARE ............................................................................................................ 116 FIG. 60 SORTARE ELEMENTE MATRICE PE LINIA K ...............................................................117 FIG. 61 NIVELURI DE PRECEDENȚĂ .....................................................................................128 FIG. 62 STRUCTURA ............................................................................................................199 FIG. 63 AFIȘARE UNIUNE .....................................................................................................207 FIG. 64 CONSTANTE SIMBOLICE[1] .....................................................................................226 FIG. 65 FUNCȚII TRIGONOMETRICE[1] .................................................................................227 FIG. 66 FUNCȚII PUTERE ȘI RADICAL[1]...............................................................................227 FIG. 67 FUNCȚII EXPONENȚIALE, LOGARITMICE ȘI HIPERBOLICE[1] ....................................228 FIG. 68 FUNCȚII PARTE ÎNTREAGĂ, DE ROTUNJIRE ȘI DE TRUNCHIERE[1] ............................229 FIG. 69 FUNCȚII MODUL[1] .................................................................................................229 FIG. 70 METODA “DIVIDE ET IMPERA”................................................................................289 FIG. 71 SUMA GAUSS ..........................................................................................................290 FIG. 72 N FACTORIAL...........................................................................................................292 FIG. 73 MAXIMUL DINTR-UN VECTOR DE NUMERE REALE ...................................................293

viii


Algoritmică şi programare

Lista tabelelor TABEL 1 MULTIPLI DE BIT ȘI OCTET ......................................................................................23 TABEL 2 PUTERILE LUI 2 .......................................................................................................27 TABEL 3 INTERDEPENDENȚA SISTEMELOR DE NUMERAȚIE ....................................................29 TABEL 4 CLASIFICAREA LIMBAJELOR DE PROGRAMARE DUPĂ NIVEL ....................................72 TABEL 5 CARACTERE SPECIALE ..........................................................................................121 TABEL 6 CARACTERE DE SPAȚIERE .....................................................................................121 TABEL 7 TIPURI DE DATE PRIMARE .....................................................................................124 TABEL 8 CONSTANTE DE TIP CARACTER..............................................................................124 TABEL 9 CUVINTE CHEIE .....................................................................................................126 TABEL 10 STRUCTURI .........................................................................................................202 TABEL 11 UNIUNI ...............................................................................................................206 TABEL 12 FUNCȚII DE INTRARE/IEȘIRE PENTRU CARACTERE[1] ..........................................218 TABEL 13 FUNCȚII DE INTRARE/IEȘIRE CU FORMAT[1] ........................................................219 TABEL 14 FUNCȚII CU FORMATARE PENTRU ȘIRURI DE CARACTERE[1] ...............................220 TABEL 15 FUNCȚII PENTRU GESTIUNEA TIMPULUI[1] ..........................................................222 TABEL 16 SPECIFICATORII DE FORMAT PENTRU AFIȘAREA TIMPULUI ..................................223 TABEL 17 FUNCȚII DE CONVERSIE[1] ..................................................................................228 TABEL 18 FUNCȚII PENTRU GENERAREA NUMERELOR ALEATOARE[1] ................................229 TABEL 19 FUNCȚII PENTRU CONCATENARE[1] ....................................................................234 TABEL 20 FUNCȚII PENTRU COPIEREA ȘIRURILOR DE CARACTERE[1] ..................................235 TABEL 21 FUNCȚII PENTRU COMPARAREA A DOUĂ ȘIRURI DE CARACTERE[1] .....................235 TABEL 22 FUNCȚII DE CĂUTARE[1] .....................................................................................236 TABEL 23 FUNCȚII DE SETARE[1] ........................................................................................236 TABEL 24 FUNCȚII DE APARTENENȚĂ[1] .............................................................................246 TABEL 25 FUNCȚII PENTRU CONVERSIA CARACTERELOR[1]................................................246 TABEL 26 FUNCȚII STANDARD PENTRU GESTIUNEA DINAMICĂ A MEMORIEI[1] ...................257 TABEL 27 FUNCȚII PENTRU OPERAȚII CU BLOCURI DE MEMORIE[1].....................................264 TABEL 28. DESCHIDEREA ȘI/SAU CREAREA UNUI FIȘIER TEXT[1] ........................................275 TABEL 29 DESCHIDEREA ȘI/SAU CREAREA UNUI FIȘIER BINAR [1].......................................276 TABEL 30 DESCHIDEREA ȘI/SAU CREAREA UNUI FIȘIER .......................................................276

ix


Algoritmică Ĺ&#x;i programare

x


Algoritmică şi programare

Bazele informaticii Noțiuni introductive Informatica (cf. DEX) reprezintă știința care se ocupă cu studiul prelucrării informației cu ajutorul mijloacelor automatice de calcul. Provenind din informație și automatică, pentru prima dată, termenul informatik a fost introdus în 1957 de Karl Steinbuch în eseul “Informatica: prelucrarea automată a informației” și a fost adoptat oficial de Academia Franceză în 1967 (Informatique) și răspândit în alte limbi europene. În engleză: Computer Science. În sens restrâns, informatica este o ramură a matematicii, care studiază aspecte abstracte, formale ale prelucrării informației. În sens larg, informatica este ansamblul științelor și tehnicilor care se ocupă de procesarea (prelucrarea) informației. Remarcăm, deci, că informatica nu se ocupă de studierea calculatorului sau altor echipamente de calcul, ci a modalităților de tratare a informației cu ajutorul calculatorului. Știința informației (engleză: information science; franceză: science de l'information) este o știință interdisciplinară, care se ocupă cu studierea problemelor privind culegerea, clasificarea, depozitarea, regăsirea și diseminarea informației. A apărut cu mult timp înaintea informaticii, deoarece se referă la utilizarea bibliotecilor, arhivelor și a altor mijloace de manipulare și conservare a informației, atât tradiționale, cât și moderne. Domeniul informaticii este în ultimul timp legat de domeniul comunicațiilor, atât datorită tehnicilor digitale folosite în ambele domenii cât și datorită tendinței de integrare a producerii, transportului și consumului de informație. Astfel, a apărut „Tehnologia Informației și Comunicațiilor” TIC (în engleză „Information and Communication Technologies” ICT), care unifică cele trei categorii de informații de bază (date, sunete și imagini) și le tratează unitar după digitizare (adică după transformarea din semnal în succesiuni de numere binare), iar prelucrarea se face cu același tip de echipamente IT(calculatoare numerice), folosind programe adecvate. Tehnologia informației (TI) numită și tehnologia informației și comunicațiilor (TIC) este domeniul științific și tehnic care se ocupă cu metodele și procedeele de achiziție, prelucrare, stocare, transport și prezentare a informațiilor, prin echipamente și rețele („hardware”) și aplicații („software”) create în acest scop cât și administrarea sistemelor respective. Cuprinde tehnologiile aplicate la utilizarea calculatoarelor electronice și rețelelor de calculatoare pentru culegerea, conversia, stocarea, procesarea, transmiterea, protejarea și diseminarea informației.

15


Algoritmică şi programare

Conceptele utilizate în metodele și procedeele amintite în definiția de mai sus provin, în general, din alte domenii de activitate umană, de exemplu din editare și tipărire de carte (tipuri de caractere, spațieri, etc.), din artă și design (grafică, proporții, combinații de culori, etc.), tehnică și economie (în aplicații ce vizează aceste domenii). Prin implicarea sa în toate activitățile umane domeniul TIC a devenit foarte vast, antrenând specialiști din cele mai diverse profesiuni; de aceea, uneori, delimitarea metodelor specifice TIC și a cunoștințelor necesare celor ce se ocupă în acest domeniu nu prezintă frontiere clare și nici stabile. În esența sa TIC este un domeniu multi și inter-disciplinar, axat pe conceptualizarea informațiilor de orice natură, cu reprezentarea și prelucrarea unitară a acestora. Cu toată această diversitate, utilizarea TIC presupune un set relativ restrâns de metode de uz general și de „obișnuințe” de operare (lucru cu calculatorul) pentru un registru larg de utilizatori, deoarece chiar extinderea TIC a impus două caracteristici - ca cerințe de bază: • atributul deschis (în engleză „open”) – prin care modificări în structura și complexitatea unui sistem de calcul (ca echipamente și programe) nu trebuie să afecteze modul de lucru al utilizatorului; • atributul prietenos (în engleză „friendly”) – prin care operarea nu trebuie să necesite cunoștințe speciale (de exemplu nume de comenzi, sintaxă, etc.), manevrele trebuie să fie simple, iar sistemul să ofere sugestii și ajutor în lucrul utilizatorului. În vederea lucrului comod și uniform al utilizatorului în rețele de calculatoare eterogene (adică cele care interconectează echipamente și programe de la diferiți producători), s-a impus o a treia caracteristică deziderat, anume: • transparența pentru utilizator a tipului de echipament sau program care oferă un anumit serviciu. Urmare a acestui atribut, utilizatorul nu trebuie să fie interesat de tipul și locul echipamentului sau programului pentru consumarea unui serviciu ci să opereze la fel de oriunde în rețea. Astfel, au apărut standarde internaționale care stabilesc modul cum trebuie să funcționeze și chiar cum să se prezinte către utilizator o aplicație de uz general (de exemplu, aplicațiile CLOUD).

Informații și prelucrări Date Fără a intra în studiul cunoașterii, se poate conchide din considerațiile anterioare că, pentru manipularea pieselor de cunoaștere prin intermediul Tehnologiei Informației și a Comunicațiilor (TIC), acestea trebuie să se refere la o formă: materială (substanță sau câmp), ori conceptuală (cuvânt). 16


Algoritmică şi programare

Constituie informație orice text, număr, imagine, sunet, dar și miros, rugozitate, căldură, sesizabile și utile omului la un moment dat. În cazul său cel mai simplu, informația se referă la apariția (sau neapariția) unui eveniment, informația căpătată fiind de tip binar: Adevărat/Fals, Da/Nu. Efectiv, informația apare după consumarea evenimentului, adică atunci când piesa de cunoaștere este sigură și clară; spunem că informația s-a instanţiat – în sensul că a căpătat o valoare pentru o mărime legată de eveniment. Omul manipulează însă și informații care nu sunt sigure (sunt probabile sau sunt posibile) precum și informații care nu sunt clare (sunt vagi). Totuși, acestea pot fi prelucrate și utilizate folosind tehnica de calcul, prin metode stochastice (probabilități) sau metode posibiliste, respectiv prin metode de inteligență artificială cum sunt tehnicile „Fuzzy”. În final, forma – ca purtător al informației, trebuie să capete o reprezentare ce permite memorarea ei în dispozitivele electronice (magnetice, optice, SSD, etc.) ale unui sistem de calcul, devenind astfel „dată”. Datele sunt informații într-o reprezentare adecvată stocării lor pe un suport (magnetic, optic, etc.), în scopul prelucrării electronice sau transferului electromagnetic. Cu alte cuvinte, ”Datele” reprezintă “materia brută” din care este constituită informația. Datele nu au semnificație prin ele însele, ci numai într-un anumit context. Reprezentarea datelor în calculatoarele numerice se face prin discretizare. Acest procedeu indică o separare în piese (discrete) a elementelor ce compun informația. De exemplu, o imagine este discretizată prin puncte minuscule (pixeli) - fiecare cu o culoare și o intensitate luminoasă, care prin ansamblul lor refac imaginea vizată. Acestor puncte le corespund coduri ce reprezintă culoarea și intensitatea luminoasă și sunt aranjate pe linii și coloane, ca într-o matrice, fiind memorată apoi pe suport extern, în memoria calculatorului sau prezentată pe ecran. Toate informațiile în calculatoarele numerice sunt reprezentate prin coduri (numerice), pentru a fi ulterior stocate și prelucrate după cerințe. Ca semnificație, discretizarea se referă la separarea unor momente de timp egale în evoluția unui fenomen real (și care prezintă de fapt o curgere continuă), în scopul cunoașterii sau modelării acestuia; prin extensie, se consideră discretizare și descompunerea în piese spațiale a unui obiect (de exemplu o imagine). Descompunerea în piese de mărimi egale ale unei măsuri (distanță, tensiune electrică, etc.) se numește cuantificare; acesta este procedeul prin care măsurările efectuate în lumea reală sunt separate în piese discrete, spre a fi stocate și prelucrate cu tehnica de calcul numerică.

17


Algoritmică şi programare

Informație Informația este o piesă de cunoaștere legată de o utilitate umană, așteptată și/sau obținută în urma unui eveniment, apoi stocată sau transmisă de la sursă/emițător către receptor printr-un canal de comunicație (materie sau câmp). Pentru domeniul TIC, informația este și „materia primă” și „produsul final”. Informația nu poate fi separată de o utilitate, o finalitate umană și este în legătură cu procesul de cunoaștere. Varietatea formelor, situațiilor și utilităților în care este implicată cunoașterea umană, face ca informația să nu poată fi definită fără echivoc. În vorbirea curentă, informația este o comunicare, o știre care pune pe cineva la curent cu o anumită situație sau cu un anumit eveniment. Este important caracterul de noutate: pentru a fi informație, mesajul trebuie să conțină elemente pe care primitorul/receptorul nu le cunoștea anterior. În Știința informației, informația este conținută în documente, în format tradițional sau electronic. În biologie, informația constă în semnalele (vizuale, auditive, tactile etc.) pe care organismul le primește din mediul exterior. Există, de asemenea, informația genetică (înregistrată în gene). În tehnică, informația constă din semnalele primite de la senzori sau transmise prin căi de comunicație. Orice cunoștință umană este legată de informații, iar acestea din urmă pot apare ca imagini, sunete (surse directe) sau numere, texte (surse deja prelucrate).

Fig. 1 Transmiterea informației

În general, o cunoștință umană surprinde o legătură cauză-efect din lumea înconjurătoare, iar procesul de obținere a cunoștințelor este relativ complex, adică: a) se preiau informații din mediu și se asociază cu evenimentele de interes (de obicei o mulțime de evenimente legate de o utilitate anume);

18


Algoritmică şi programare

b) se constată repetarea apariției unui anume eveniment așteptat (numit efect) în corelație cu unul sau mai multe evenimente observate (numite cauze) și se stabilește o relație de cauzalitate; c) se procedează la generalizarea relației de cauzalitate formulând o „lege” – eliminând, eventual, evenimente ce nu intră în mod relevant în relația de cauzalitate (având caracter întâmplător sau legături slabe cu evenimentul efect). De ce se vorbește însă, de o tehnologie a informației? După cum prelucrările pe care le suferă materiile prime într-o uzină spre a se obține un anume produs se bazează pe o tehnologie – ca succesiune bine stabilită de transformări efectuate cu un anumite instalații special proiectate, asemănător, informația din mediu este prelucrată într-o succesiune bine definită de operații cu echipamente IT dedicate. Pe această similitudine se poate concepe o paralelă între o tehnologie industrială și tehnologia informațiilor, care poate fi sintetizată astfel: materie primă – pregătire – transformare - ambalare a produsului finit, respectiv informație – reprezentare – prelucrare – prezentare a rezultatelor către utilizator.

Cunoștințe Pe scurt, la diferite niveluri de reprezentare, în informatică se operează cu date, informație și cunoștințe. Datele nu au semnificație prin ele însele, reprezintă “materia brută” din care este constituită informația. Cu alte cuvinte, datele doar când capătă o semnificație devin informație. Informația astfel înțeleasă se transformă în cunoștințe.

Fig. 2 Tranziția de la date la cunoștințe

Cunoștințele sunt informații înțelese, acumulate și sistematizate, care pot fi utilizate pentru a obține anumite rezultate (pentru a lua decizii, pentru a acționa etc.). Cunoștințele acumulate de un om constituie un ansamblu sistematizat de concepte și de reguli de acțiune. Înțelegerea unei informații constă tocmai în încadrarea ei în sistemul de cunoștințe deja acumulate.

Sistemul informațional Se numește sistem un ansamblu de elemente care interacționează atât între ele și care poate fi privit ca un întreg. Interacțiunile dintre elementele sistemului, sau dintre sistem și mediu, se pot realiza prin schimb de substanță, de energie sau de informație. În informatică interesează, desigur, sistemele în care are loc transfer și prelucrare de informație. 19


Algoritmică şi programare

Sistemul informațional (engleză: information system, franceză: système d'information) este un ansamblu organizat de elemente, implicate în procesul de colectare, transmisie, stocare, prelucrare și diseminare a informației. Interacțiunea dintre elementele sistemului informațional se realizează prin transmiterea de informație și nu este obligatoriu ca operațiile enumerate anterior să se efectueze cu echipamente de calcul. De altfel, sistemele informaționale au existat cu mult înaintea apariției calculatoarelor. Sistemul informațional este compus din: • totalitatea informațiilor cu care operează; • echipamentele folosite pentru operații asupra informației; • personalul implicat în funcționarea și administrarea sistemului; • procedurile aplicate pentru funcționarea sistemului.

Sistemul de calcul Tehnologia Informației și Comunicațiilor (TIC) este privită ca domeniu de „înaltă tehnologie” (High Technology), atât prin subtilitatea și complexitatea metodelor și aparaturii electronice implicate cât și prin aplicațiile cu impact și eficiență deosebite pentru toate domeniile de activitate. Utilizarea Tehnologiei Informației și Comunicațiilor are cea mai largă extindere întâlnită la orice altă tehnologie cunoscută, datorită materiei prime pe care o prelucrează - informația. Cum nu există domeniu și activitate umană care să nu folosească informații, TIC vine să sprijine activitățile din acel domeniu, prin metode și mijloace pentru prelucrarea informațiilor, unele generale, altele specializate. Se numește sistem de calcul complexul de echipamente fizice interconectate, cu programele și datele legate intrinsec de funcționarea și utilizarea lor de către om. Un sistem de calcul conține „partea tare” – hardware, echipamentele, precum și „partea moale” – software, programele și datele. Echipamentele (engleză: hardware) sunt realizate practic sub formă de aparate sau dispozitive electronice, electrice, mecanice sau optice. Programele (engl.: software) sunt seturi de instrucțiuni , scrise într-un limbaj de programare , pe care calculatorul le executa pentru a îndeplini o anumita sarcina (prelucrarea informației). Sistemul de operare constituie interfața dintre partea hardware si utilizator si gestionează resursele fizice si logice ale sistemului de calcul.

20


Algoritmică şi programare

Fig. 3 Sistemul de calcul

Cuvântul calculator are, în limba română, mai multe semnificații. În ceea ce ne privește, vom avea în vedere calculatorul electronic numeric universal, numit și computer ori PC (cuvânt preluat din limba engleză) sau ordinator (în franceză: ordinateur). Acesta efectuează cu ajutorul softwareului diferite prelucrări de date numerice și nenumerice. Se numește calculator numeric (engl.: digital computer), deoarece în interiorul lui toată informația este reprezentată prin numere. Se numește universal deoarece funcționează pe bază de program înregistrat în memorie. Prin schimbarea programului se poate modifica funcționarea acestuia (deci succesiunea de operații pe care le execută), fără a face modificări în construcția calculatorului. Programul este și el codificat numeric.

21


Algoritmică şi programare

Reprezentarea și structurarea informației Informațiile vehiculate de om, în diferite situații, sunt legate de mijloacele prin care acesta comunică cu mediul înconjurător și cu ceilalți oameni. În primul caz, informațiile apar sub formă de imagini și sunete, iar în al doilea caz informațiile apar în plus sub formă de texte (cărți, reclame, etichete de produs) și sub formă de numere (sume de bani, vârste). În scopul manipulării acestor tipuri de informație prin intermediul tehnicii de calcul, fiecare din categoriile de mai sus capătă o reprezentare anume, pentru a fi „inteligibilă” calculatorului numeric. Reprezentarea informației în creierul uman nu este încă elucidată datorită complexității structurilor din creier și datorită mecanismelor, încă necunoscute, ce realizează stocarea informațiilor. În calculatoarele electronice, dispozitivele de stocare au doar două stări și sunt astfel simplu de realizat tehnologic: condensatoare cu stările încărcat/descărcat sau dispozitive cu două stări stabile (numite bi-stabile). In alte variante tehnologice ele sunt de asemenea, ușor de realizat: mecanic, chimic și chiar la nivel atomic. Calculatoarele actuale sunt denumite calculatoare numerice (sau digitale – de la cuvântul englezesc „digit” - cifră) pentru că ele transformă toate informațiile pe care le stochează și prelucrează în reprezentări discrete. Au existat și calculatoare analogice, în care informația păstra reprezentarea continuă, firească, a semnalelor din lumea înconjurătoare; aceste calculatoare prelucrau semnalele continui cu ajutorul „dispozitivelor electronice analogice” de tip amplificator de semnal, integrator cu condensator electric, etc. Limitările calculatoarelor analogice privind aplicațiile și modalitatea de stocare a datelor au dus la înlocuirea lor cu dispozitive numerice, care prin discretizare și cuantificare pot reprezenta și prelucra orice semnal analogic (cu o precizie mai mare sau mai mică).

Bit, octet și multiplii acestora Anterior, definiția informației a fost însoțită de exemplificări relative la tipuri de informații – cum sunt de exemplu imagini, sunete, numere, text. Informațiile logice, de tip Adevărat/Fals sau Da/Nu, sunt informații abstracte, putând fi considerate piese elementare de informație fiindcă au doar două valori posibile și reprezintă situații simple sau generale – de exemplu un eveniment oarecare se produce (Da) sau nu se produce (Nu). Piesa de informație ce poate avea doar două valori posibile se numește bit. Bit-ul reprezintă unitatea elementară de informație, corespunzând unei situații fizice în care un comutator este deschis sau închis, situații

22


Algoritmică şi programare

reprezentate de oameni, în afara calculatorului prin cifrele 0 și 1, deci, cifrele sistemului binar.

0

1

Fig. 4 Reprezentare tehnică a valorilor logice 0 și 1 pentru un bit

În calculatoarele numerice, un bit se consideră că poate lua ori valoarea 0 ori valoarea 1, fiecare din acestea corespunzând de fapt unei situații fizice în care un comutator este deschis – respectiv închis (vezi fig.4), sau un condensator electric este încărcat cu sarcină electrică – respectiv este descărcat. Valorile 0 sau 1 reprezintă deci „valori logice” (Adevărat/Fals) dar totodată, fiind cifre, permit crearea de numere. Memoriile semiconductoare ale calculatoarelor numerice pot stoca biți în miliarde de condensatoare minuscule, numărul acestor biți reprezentând capacitatea de memorie a respectivului calculator. Asemenea numere uriașe nu sunt comod de utilizat, de aceea se utilizează multipli de bit. Octet (sau Byte) este un grup de opt biți reprezentând, tradițional, o celulă de memorie. Evident ca evaluarea capacităților de memorie actuale, folosind ca unitate de măsură octetul, nu rezolvă problema numerelor foarte mari vehiculate. De aceea se folosesc multipli de octet, cu denumiri uzuale ale multiplilor unităților de măsură, dar cu semnificație diferită - conform tabelului de mai jos. Nume multiplu Kilo Mega Giga Tera

Semnificație

Magnitudine

2

10

× 1.024

2

20

× 1.048.576

2

30

× 1.073.741.824

2

40

× 1.099.511.627.776

Tabel 1 Multipli de bit și octet

După cum se observă din Tabel 1 semnificația multiplilor de bit și octet în informatică diferă de cea tradițională (în care Kilo reprezintă 103 de exemplu), dar valoarea numerică a fiecărei categorii de multiplu este oarecum apropiată de cea tradițională (Kilo apropiat de mie, Mega de

23


Algoritmică şi programare

milion, etc.). Rațiunea acestor valori pentru multiplii utilizați în informatică este reprezentarea în calculator a numerelor folosind baza 2 de numerație.

Tipuri de date simple Varietatea informațiilor utilizate de om pare foarte mare, însă ele pot fi clasificate în categorii generice care apoi să primească reprezentări generale ca tipuri de date. Se poate face o analiză sumară din care rezultă câteva categorii de asemenea informații simple: • numere singulare – utilizate a desemna sume de bani, distanțe, zile din luna calendaristică, cu care se pot face calcule matematice. Se deosebesc două categorii de numere – numere întregi și numere reale (cu zecimale). • simboluri grafice - litere, cifre, semne de punctuație, spatii libere, utilizate în texte. Aceste simboluri sunt elemente ale unui set finit de simboluri de scriere (alfabetul, cifrele de la 0 la 9, semnele de punctuație); ele sunt denumite generic caractere alfanumerice. • Informații de tip Adevărat sau Fals – pentru a desemna o situație sau un eveniment funcție de care se ia o decizie (de exemplu, dacă un eveniment a avut loc se execută o anumită acțiune, altfel altă acțiune), denumite informații logice (booleene). Reprezentarea interna a datelor se face binar utilizându-se o codificare. Aceasta codificare poate fi: • ASCII – cea mai utilizată tabelă de cod, provenind din standardul american (American Standard Code for Information Interchange); • Unicode – pe 16 biți, care este o altă extensie a tabelei ASCII și care are un număr mare de coduri disponibile, pentru reprezentarea nu numai a caracterelor latine cu diverse diacritice dar și a caracterelor diferite de cel latin (chirilic, arab, etc.)

Sisteme de numerație Sistemul de numerație reprezintă o modalitate de notare matematică și se definește ca fiind totalitatea regulilor folosite în vederea scrierii numerelor cu ajutorul simbolurilor de scriere. Un sistem de numerație este nepozițional atunci când simbolurile de scriere prin care sunt reprezentate cantitățile (numerele/ dimensiunea) nu au o pondere în funcție de poziția ocupată în cadrul șirului de simboluri ce desemnează (semnifică) cantitatea globală (totală). Exemplu cel mai elocvent îl reprezintă sistemul de numerație roman. Un sistem de numerație este pozițional atunci când

24


Algoritmică Ĺ&#x;i programare

aportul unui simbol de scriere (cifră sau literă) ĂŽn valoarea totală a unui număr depinde atât de valoarea simbolului de scriere, cât Č™i de locul ocupat de acesta ĂŽn reprezentarea numărului respectiv. Baza unui sistem de numeraČ›ie poziČ›ional reprezintă numărul de semne distincte necesare scrierii unui număr sau totalitatea unitÄƒČ›ilor de acelaČ™i ordin de mărime care formează o unitate de ordin imediat superior.

đ?‘›đ?‘˘đ?‘šÄƒđ?‘&#x;(đ?‘?đ?‘Žđ?‘§Äƒ) = [đ?‘‘đ?‘› ‌ đ?‘‘2 đ?‘‘1 đ?‘‘0 ](đ?‘?) = ∑đ?‘›đ?‘–=0 đ?‘‘đ?‘– đ?‘? đ?‘– = đ?‘‘0 đ?‘? 0 + đ?‘‘1 đ?‘?1 + â‹Ż + đ?‘‘đ?‘› đ?‘? đ?‘› ;

b - baza sistemului de numeraČ›ie; đ?‘‘đ?‘– – numărul/simbolul de scriere aflat pe poziČ›ia i ; n- număr (pozitiv sau negativ) ce indica poziČ›ia sau rangul; n+1=numărul de simboluri de scriere;

Sisteme de numeraČ›ie: • sisteme ĂŽn baza 1 (unary numeral system sau bijective base system) – Sistemul de vot/pontaj • sisteme ĂŽn baza 2 (binary numeral system) – Digital computing • sisteme ĂŽn baza 5 (quinary numeral system) - Sistemul Roman • sisteme ĂŽn baza 8 (octal numeral system) - Informatică • sisteme ĂŽn baza 10(decimal numeral system) - Sistemul Arab • sisteme ĂŽn baza 16(hexazecimal numeral system) Informatică • sisteme ĂŽn baza 20 (vigesimal numeral system) - Sistemul MayaČ™ • sisteme ĂŽn baza 60 (sexazecimal numeral system) – Sistemul Babilonian.

Sistemul de numeraČ›ie zecimal (baza 10) Numerele ĂŽntrebuinČ›ate frecvent pentru calcule (Č™i nu numai), cu cea mai mare arie de răspândire Č™i aplicabilitate, sunt formulate ĂŽn aČ™a-numita „scriere arabăâ€?, ĂŽn care numărul 10 este considerat „bază de numeraČ›ieâ€? iar cifrele sunt plasate pe ranguri corespunzătoare puterilor cifrei 10. ĂŽn reprezentarea zecimală a numerelor, rangul conČ›ine cifre de la 0 la 9, astfel, pornind de la stânga către dreapta, unitÄƒČ›ile au rangul 0, zecile au rangul 1, sutele au rangul 2, etc. Numărul simbolurilor utilizate pentru reprezentarea numerică este 10 (exact cât baza de numeraČ›ie), respectiv cifrele de la 0 la 9. ĂŽn concluzie, un număr este reprezentat ĂŽn sistemul de

25


Algoritmică Ĺ&#x;i programare

numeraČ›ie zecimal ca fiind suma dintre produsul fiecărui simbol de scriere Č™i numărul 10 ridicat la puterea rangului respectivului simbol de scriere. đ?‘›đ?‘˘đ?‘šÄƒđ?‘&#x;(đ?‘?đ?‘Žđ?‘§Äƒ 10) = [đ?‘‘đ?‘› ‌ đ?‘‘2 đ?‘‘1 đ?‘‘0 ](10) = ∑đ?‘›đ?‘–=0 đ?‘‘đ?‘– ∗ 10đ?‘– = đ?‘‘0 ∗ 100 + đ?‘‘1 ∗ 101 + â‹Ż + đ?‘‘đ?‘› ∗ 10đ?‘› ;

unde, đ?‘‘đ?‘– este numărul/simbolul de scriere aflat pe poziČ›ia i. ...

Mii

Sute

Zeci

UnitÄƒČ›i

...

103

102

101

100

...

5

9

9

9

5999 (10) = 9â‹…100 + 9â‹…101 + 9â‹…102 + 5â‹…103 =9 + 90 + 900 + 5000 n

10

...

8

7

10

6

10

10

5

10

4

10

3

10

2

1

10 5

10 4

0

10 3

543 (10) = 5Ă—102+4Ă—101+3Ă—100 = 500+40+3

Sistemul de numeraČ›ie binar (baza 2) Calculatoarele numerice sunt dispozitive realizate tehnologic ĂŽn cel mai simplu mod, prin faptul că folosesc cea mai simplă reprezentare a informaČ›iei: Adevărat / Fals sau Da / Nu. Sistemul de numeraČ›ie binar utilizează doar 2 cifre ĂŽn modalitatea de reprezentare a numerelor, respectiv 0 Č™i 1 astfel, asemănător modalitÄƒČ›ii de lucru ĂŽn baza de numeraČ›ie 10, numărul reprezentat ĂŽn baza 2 (de numeraČ›ie) va avea rangurile drept puteri ale bazei de numeraČ›ie. đ?‘›đ?‘˘đ?‘šÄƒđ?‘&#x;(đ?‘?đ?‘Žđ?‘§Äƒ 2) = [đ?‘‘đ?‘› ‌ đ?‘‘2 đ?‘‘1 đ?‘‘0 ](2) đ?‘›

= ďż˝ đ?‘‘đ?‘– ∗ 2đ?‘– = đ?‘‘0 ∗ 20 + đ?‘‘1 ∗ 21 + â‹Ż + đ?‘‘đ?‘› ∗ 2đ?‘› đ?‘–=0

unde, đ?‘‘đ?‘– este numărul/simbolul de scriere aflat pe poziČ›ia i. ...

rang 3

rang 2

rang 1

rang 0

...

23

22

21

20

...

1

0

1

1

1011 (2) = 1â‹…20+1â‹…21+0â‹…22+1â‹…23 = 11 (10)

26


Algoritmică Ĺ&#x;i programare n

128

64

32

16

7

6

5

4

n

2

2

2

2 1

8

4 3

2 0

2 2

2 0

1 1

2 0

0

2 1

2 1

100011 (2) = 1Ă—25+0Ă—24+0Ă—23+0Ă—22+1Ă—21+1Ă—20 =32+2+1= 35 (10) n 2đ?‘›

0 1

1 2

2 4

3 8

4 16

5 32

6 64

7 128

8 256

9 512

10 1024

11 2048

12 4096

Tabel 2 Puterile lui 2

Sistemul de numeraČ›ie octal (baza 8) Sistemul de numeraČ›ie octal utilizează cifre cu valori de la 0 la 7 ĂŽn reprezentarea codificată a numerelor reale dar Č™i de reprezentare a numerelor raČ›ionale Č™i iraČ›ionale. Este utilizat in Informatică deoarece reuČ™eČ™te să comprime Č™iruri lungi de biČ›i din reprezentarea unui număr ĂŽn baza 2. Totodată, conversia din baza 2 ĂŽn baza 8 Č™i invers se face foarte uČ™or, fără calcule laborioase: đ?‘›đ?‘˘đ?‘šÄƒđ?‘&#x;(đ?‘?đ?‘Žđ?‘§Äƒ 8) = [đ?‘‘đ?‘› ‌ đ?‘‘2 đ?‘‘1 đ?‘‘0 ](8) đ?‘›

= ďż˝ đ?‘‘đ?‘– ∗ 8đ?‘– = đ?‘‘0 ∗ 80 + đ?‘‘1 ∗ 81 + â‹Ż + đ?‘‘đ?‘› ∗ 8đ?‘› đ?‘–=0 7

8

6

8

5

8

4

8

3

8 4

2

8 3

1

8 0

0

8 7

4307 (8) = 4Ă—83+3Ă—82+0Ă—81+7Ă—80= 2247 (10) Alte exemple: 27 (8) = 2Ă—81+7Ă—80 = 16+7 = 23 (10) 31 (8) = 3Ă—81+1Ă—80 = 24+1=25 (10) 55 (8) = 5Ă—81+5Ă—80 = 40+5=45 (10)

Sistemul de numeraČ›ie hexazecimal (baza 16) Sistemul de numeraČ›ie hexazecimal este utilizat cu preponderenČ›Äƒ ĂŽn matematică Č™i informatică deoarece, similar sistemului de numeraČ›ie octal, reuČ™eČ™te să comprime Č™iruri lungi de biČ›i, prin gruparea lor câte 4, asigurând astfel o reprezentare mult mai apropiata de ĂŽnČ›elegerea umană a unui număr reprezentat binar. Sistemul hexazecimal, pentru a reprezenta cele 16 cifre utilizează 16 simboluri de scriere distincte: primele 10 dintre acestea coincid cu cifrele sistemului de numeraČ›ie zecimal, iar pentru restul de 6 utilizează primele litere ale alfabetului latin (A,B,C,D,E,F).

27


Algoritmică Ĺ&#x;i programare

đ?‘›đ?‘˘đ?‘šÄƒđ?‘&#x;(đ?‘?đ?‘Žđ?‘§Äƒ 16) = [đ?‘‘đ?‘› ‌ đ?‘‘2 đ?‘‘1 đ?‘‘0 ](16) đ?‘›

= ďż˝ đ?‘‘đ?‘– ∗ 16đ?‘– = đ?‘‘0 ∗ 160 + đ?‘‘1 ∗ 161 + â‹Ż + đ?‘‘đ?‘› ∗ 16đ?‘› đ?‘–=0 7

16

6

16

5

16

4

16

3

2

16 B

16 C

1

16 1

0

16 2

BC12 (16) = 11Ă—163+12Ă—162+1Ă—161+2Ă—160= 48146 (10) Alte exemple: 28 (16) = 2Ă—161+8Ă—160 = 40 (10) 2F (16) = 2FH = 2Ă—161+15Ă—160 = 47 (10)

InterdependenČ›a sistemelor de numeraČ›ie Sistemul de numeraČ›ie binar este utilizat pentru stocarea si prelucrarea informaČ›iei ĂŽn Informatică. Pentru om, dificultatea redării ĂŽn sistem binar, fie scris sau oral, creČ™te direct proporČ›ional odată cu numărul de biČ›i. De aceea, uzual, se folosesc ĂŽn mod curent sisteme de numeraČ›ie cu baze mai mari (indicat ca baza de numeraČ›ie sa fie egala cu rezultatul lui 2 la o putere naturală) care permit trecerea rapidă ĂŽn binar. Din acest punct de vedere, cele mai ĂŽntâlnite sisteme de numeraČ›ie sunt sistemul octal (baza 8) Č™i sistemul hexazecimal (baza 16). Astfel, orice simbol de scriere din baza 8 se poate reprezenta printr-o combinaČ›ie de 3 cifre binare, respectiv orice simbol de scriere din baza 16 (hexazecimal), printr-o combinaČ›ie de 4 cifre binare deoarece 8 = 23 Č™i 16 = 24. n 2đ?‘›

0 1

1 2

2 4

Baza

2 0 1

3 0 1 2

+ 0 1

0 0 1

1 1 10

* 28

0

1

3 8

4 16 4 0 1 2 3

5 32 5 0 1 2 3 4

6 64

7 128

6 0 1 2 3 4 5

7 0 1 2 3 4 5 6

8 256

9 512 8 0 1 2 3 4 5 6 7

10 1024 9 0 1 2 3 4 5 6 7 8

11 2048 10 0 1 2 3 4 5 6 7 8 9

12 4096 16 0 1 2 3 4 5 6 7 8 9 A B C D

(10) (11) (12) (13)


Algoritmică şi programare Baza 0 1

X(

2 0 0

3 0 1

4

5

6

7

8

9

10

16 E (14) F (15)

0 1 2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

0 1 2

3

4

5

6

7

8

9

A

B

C

D

E

F

10

0 1 2

3

4

5

6

7

10

11

12

13

14

15

16

17

20

0 1 1 0

1 1

1 0 0

1 0 1

1 1 0

1 1 1

10 00

10 01

10 10

10 11

11 00

11 01

11 10

11 11

10 00 0

10)

X( 16)

X( 8)

X( 2)

Tabel 3 Interdependența sistemelor de numerație

Conversia binar – octal - hexazecimal și invers După cum am amintit anterior, orice simbol de scriere din baza 8 se poate reprezenta printr-o combinație de 3 cifre binare, respectiv orice simbol de scriere din baza 16 (hexazecimal), printr-o combinație de 4 cifre binare deoarece 8 = 23 și 16 = 24. Exemple: 4307 8 = 100011000111 2 = 2247 10

2F 16 = 2×161+15×160 = 47 10 2F 16 = 57 8 =5×81+7×80 = 40+70= 47 10 101111 2 = 1×25+1×23+1×22+1×21+1×20 = 47 10

29


Algoritmică şi programare

BC12 16 = 11×163+12×162+1×161+2×160= 48146 10 BC12 16 = 1011110000010010 2 BC12 16 = 136022 8

Conversia binar – zecimal și invers Trecerea unui număr din sistemul zecimal în binar se poate face prin diferite metode. Cele mai uzuale sunt: prin împărțirea succesiva a numărului zecimal la 2 sau prin diferențe succesive dintre numărul zecimal (respectiv restul) și puterile lui doi. 35 10 =? 2

35 10 =? 2

30


Algoritmică şi programare

Verificare: 35 (10) =32+2+1=25+21+20 100011 (2) = 1×25+1×21+1×20 =32+2+1= 35 (10) 73 10 =? 2

73 10 =? 2

Verificare: 1×26+1×23+1×20 = 64+8+1 = 73 10

Reprezentarea numerelor întregi Numerele întregi pot fi: • Pozitive (fără semn) • Pozitive (cu semn) • Negative (cu semn) Numerele întregi pot fi codificate prin trei metode: • cod direct (semn și valoare absolută) Numerele întregi se reprezintă prin mărime (valoare absolută) și semn. Pentru numerele negative, bitul cel mai 31


Algoritmică şi programare

semnificativ (de semn) este 1, iar ceilalți n-1 biți servesc pentru reprezentarea valorii absolute a numărului. Ex.: N = -5 pe 8 biți => 10000101 cod invers (complement față de 1) Pentru numerele negative, bitul de semn este 1, ceilalți n-1 biți servind pentru reprezentarea valorii absolute negate a numărului. Negarea se realizează la nivel de bit: biții 0 devin 1 și biții 1 devin 0. Ex.: N = -5 pe 8 biți => 11111010 cod complementar (complement față de 2) Se obține din complementul față de 1, la care se adaugă 1 Ex.: N = -5 pe 8 biți => 11111010+1=11111011 a=5 => N=28-a => 256-5=25110 = 11111011 2

Conversia unui număr dintr-o anumită bază de numerație (α) în altă bază de numerație (β). Exemplu: conversia numărului 121,45 din zecimal în binar se face în doua etape: se convertește partea întreagă in binary apoi se convertește partea fracționară. Diferența dintre conversia părții întregi și a celei fracționare este următoarea: partea întreagă se împarte succesiv la 2 pe când partea fracționară se înmulțește succesiv cu 2 și se reține doar partea întreagă. Astfel, din conversia părții întregi rezultă că 121 10 = 1111001 2 .dar pentru partea fracționară, respective 0,45 conversia se face în felul următor: 0,45 * 2 = 0,9; se reține 0 0,9 * 2 = 1,8; se reține 1 0,8 * 2 = 1,6; se reține 1 0,6 * 2 = 1,2; se reține 1 Algoritmul se oprește în momentul în care partea fracționară devine 0 sau s-a atins precizia dorită (numărul de cifre pentru reprezentarea rezultatului). 0,45 10 = 0,01110011 2 În final, obținem conversia următoare: 121,45 10 = 1111001,01110011 2

32


Algoritmică şi programare

Probleme propuse: 1. Reprezentați numărul 121 în baza 2 pe 8 biți; 2. Reprezentați numărul 0.121 în baza 2 pe 8 biți; 3. Efectuați în baza 2 suma și produsul numerelor a=1011 și b=1101; 4. Reprezentați numărul 121.122 în baza 2 pe 16 biți (câte 8 pentru partea întreagă și 8 pentru partea fracționară); 5. Reprezentați în cod complementar numărul 112 pe 8 biți; 6. Reprezentați în cod complementar numărul -112 pe 8 biți; 7. Reprezentați numărul 121 în baza 8; 8. Reprezentați numărul 121 în baza 16; 9. Efectuați conversia numărului 2309 din baza 10 în baza 2, 8 și 16; 10. Efectuați conversia numărului 1011011 din baza 2 în baza 8, 10 și 16;

33


Algoritmică şi programare

Algoritm Modalitatea în care are loc prelucrarea efectivă a unor date se exprimă prin algoritm – ca pași succesivi, repetiții și ramificații prin care se execută operațiile vizate. Rezolvarea oricărei probleme presupune o metodă și o execuție în pași a acesteia, spre soluția dorită. Pe scurt, algoritmul reprezintă o succesiune ordonată de operații (număr finit de pași) care trebuie efectuate pentru a realiza un anumit scop. Totodată, algoritmii sunt modalități prin care se exprimă succesiunea de operații prin care un program pe calculator poate ajunge, pornind de la datele de intrare furnizate, la rezultatele dorite. Trebuie remarcat că un algoritm nu exprimă doar operații cu numere ci orice fel de prelucrări (cu texte, imagini, etc.), având scopul de a ajunge la un rezultat. Un algoritmul trebuie să dețină următoarele caracteristici: • caracter discret - este format din mai mulți pași; • caracter finit - numărul pașilor este limitat; • caracter determinist – se ajunge la un rezultat; • caracter realizabil - fiecare operație prevăzută este realizabilă efectiv; • caracter universal - nu se aplică unui caz izolat, ci unui număr de cazuri, care diferă prin datele de intrare.

Reprezentarea algoritmilor În general, descrierea prelucrărilor prin care se obține soluția unei probleme date se face fără a se specifica tipurile de date utilizate, ca și cum acestea ar fi subînțelese. Un algoritm este o înșiruire de operațiuni în care se utilizează variabile și (eventual) alte prelucrări, indicate prin identificatori inventariați într-un dicționar sau nomenclator ce specifică rolul variabilelor sau prelucrărilor respective. Există diferite metode de reprezentare a algoritmilor: • într-un limbaj natural (de exemplu, în limba română); • sub formă de schemă logică (reprezentare grafică). • în pseudocod. Istoric, prima modalitate de exprimare a algoritmilor au constituit-o organigramele – cu avantajul descrierii grafice, intuitive și ușor de urmărit. Evoluția către programarea și proiectarea structurată a impus exprimarea prin pseudocod – cu avantajele descrierii concise, modularizate și apropiate limbajelor de programare. De exemplu, rezolvarea unei ecuații de gradul I de forma ax + b = 0 presupune pașii: 34


Algoritmică şi programare

i) se verifică dacă a = 0; dacă da – ecuația este degenerată; ii) se verifică dacă b = 0; dacă da x = 0; iii) altfel, x = -b/a; Pentru prelucrări mai complexe, trebuie găsită o reprezentare explicită, în amănunt, a operațiunilor și pașilor efectuați pentru a rezolva problema. De exemplu, în cazul de mai sus, al ecuației de gradul I, nu s-a specificat nimic privind introducerea datelor (valorile pentru a și b) dar și privind afișarea soluției x (care pentru un calculator nu sunt subînțelese – așa cum sunt ele pentru un om obișnuit cu rezolvarea problemelor de matematică). Programul principal: #1 citește valorile pentru a și b; #2 soluționarea ecuației de gradul I; #3 afișează soluția ecuației. Detalierea pașilor #1, #2, #3: #1:

#2:

#3:

afișează pe rând nou: „Introduceți valoarea variabilei a: ”; citește de la tastatură: a; afișează pe rând nou: „Introduceți valoarea variabilei b: ”; citește de la tastatură: b. Dacă a=0 atunci afișează pe rând nou: „Ecuație degenerată”; altfel x = - b/a. afișează pe rând nou: „Soluția ecuației este x = ”, x; afișează pe rând nou: „Pentru reluare lansați din nou programul”.

Exprimarea acțiunilor prin pseudocod este o modalitate eficientă de a descrie operațiunile ce urmează a fi implementate într-un program dar poate fi folosită și pentru a descrie orice acțiune complexă specifică activității umane.

35


Algoritmică şi programare

Scheme logice Succesiunea etapelor rezolvării unei probleme poate fi descrisă grafic utilizând simboluri (blocuri standard).

Fig. 5 Simboluri grafice (blocuri standard)

Chiar dacă are mai mult o valoare istorică, schema logică este un bun start în specificarea unui algoritm, fiind intuitivă și ușor de constituit. Blocurile grafice generice sunt prezentate în Figura 5, cu simbolurile și semnificațiile lor: • terminatorii (START) și (END) indică începutul și sfârșitul prelucrărilor – delimitând astfel algoritmul descris; • blocurile (ASSIGN) și (CALL) indică efectiv prelucrările vizate prin algoritm – descrise prin formule sau printr-un subprogram predefinit ; • blocurile (READ) și (WRITE) indică o intrare sau o ieșire de date (citire sau afișare); • blocul (SELECTION) este necesar pentru specificarea ramificațiilor de decizie; • blocul (LOOP) – numit ciclu Fig. 6 Schema logica pentru sau buclă - implică operațiuni rezolvarea ecuației de gradul I repetate și este necesar pentru specificarea operațiilor repetitive.

36


Algoritmică şi programare

Succesiunea operațiilor se indică prin linii și săgeți care leagă blocurile grafice prezentate, astfel constituind așa-numita schemă logică. Ca exemplu, se prezintă în Figura 6 organigrama (schema logică) pentru rezolvarea ecuației de gradul întâi. Schema logică este clară și intuitivă, indicând riguros atât operațiile cât și succesiunea lor. Astfel, pentru un număr mare de operații, cu ajutorul schemei logice, algoritmul devine dificil de reprezentat și de urmărit.

Pseudocod Atunci când se dorește nu doar descrierea algoritmului ci și structurarea etapelor de rezolvare a problemei (prin modularizare), este indicată utilizarea unui limbaj codificat care exprimă operațiunile de executat și cele de control al fluxului de comenzi (cum sunt decizia, repetiția). Modularizarea (adică separarea operațiunilor pe secțiuni, fiecare cu un scop restrâns și specific) este singura modalitate de abordare a rezolvării problemelor complexe și cu soluție puțin sau deloc cunoscută. Pe de altă parte, descrierea algoritmilor prin pseudocod este mult mai compactă decât schema logică (care necesită mult spațiu pe foaia de scris), este apropiat de un limbaj de programare, fiind o replică exprimată în limba maternă a programului structurat. Ca exemplu, vom considera algoritmul de calculare a ariei și perimetrului unui dreptunghi cu laturile b (baza) și i (înălțimea). În limbaj natural, se poate descrie astfel: Pasul 1: se introduc datele b și i; Pasul 2: dacă oricare din b sau i este număr negativ sau nul, atunci: Pasul 2.1: scrie “Date de intrare incorecte”. Pasul 2.2: stop. Pasul 3: Calculează A=b*i; Pasul 4: Calculează p=2*(b+i); Pasul 5: Scrie “Aria dreptunghiului este”, A Pasul 6: Scrie “Perimetrul dreptunghiului este”, p Pasul 7: Stop Același algoritm, reprezentat prin schemă logică. Operațiile sunt reprezentate prin simboluri grafice, iar succesiunea lor prin linii sau săgeți.

37


Algoritmică şi programare

Fig. 7 Reprezentarea algoritmului prin schemă logică

Același algoritm, reprezentat în pseudocod (un limbaj special de reprezentare a algoritmilor): start citește b, i; dacă (b<=0 sau i<=0) atunci scrie “Date de intrare incorecte” altfel A=b*i P=2*(b+i) scrie “Aria este “,A, “perimetrul este ”,P stop

38


Algoritmică şi programare

Remarcăm că, în toate cazurile, algoritmul indică ce operații se execută și în ce ordine, fără să indice de ce se execută (în ce scop). Limbajul natural nu este suficient de riguros. Schema logică după cum am menționat anterior, este clară și intuitivă, indicând riguros atât operațiile cât și succesiunea lor. Totuși, pentru număr mare de operații, devine dificil de reprezentat și de urmărit. Pseudocodul este riguros și nu necesită desene. Operațiile sunt specificate prin cuvinte cheie (de ex. citește, scrie, daca, altfel, cât timp, stop, etc.).

Elaborarea algoritmilor Rezolvarea diferitor probleme din lumea reală cu ajutorul calculatorului se poate face doar dacă soluția se cunoaște dar trebuie căutată într-un set existent sau dacă există o metodă de găsire a soluției. Algoritmul este o asemenea metodă, dar elaborarea sa este de cele mai multe ori dificilă, considerată uneori (îndeosebi la începuturile programării) o artă. Este evident că folosirea unor metode sistematice de elaborare a algoritmilor este mai eficientă decât o căutare „la ureche” sau prin încercări; aceste metode sunt totodată algoritmi generici ce vor fi concretizați apoi pentru problema reală dată. Între metodele de elaborare a algoritmilor se amintesc cele mai importante (numele fiind indicat în engleză spre a fi ușor de recunoscut), cu o scurtă descriere a specificului lor: • Greedy – pentru crearea de submulțimi optimale cu elementele preluate dintr-o mulțime dată și cu respectarea unor restricții impuse individual elementelor. • Backtracking – pentru crearea de submulțimi optimale cu elementele preluate dintr-o mulțime dată, cu respectarea unor restricții impuse elementelor dar și setului (există relații între elementele submulțimii soluție). • Divide et Impera – pentru probleme ce conțin secvențe sau piese discrete ordonate, care pot fi divizate în subprobleme care se rezolvă separat și apoi se obține soluția prin combinarea soluțiilor parțiale. • Branch and Bound – pentru probleme în care soluțiile se pot reprezenta ca noduri în arbore iar căutarea soluției se face prin parcurgerea arborelui urmărind totodată o funcție de cost pentru a limita adâncimea de căutare. • Programare dinamica • Metode euristice

39


Algoritmică şi programare

Algoritmii generali pot sta la baza elaborării de algoritmi particulari, pentru rezolvarea problemelor concrete, după stabilirea celei mai adecvate metode pentru situația impusă.

Analiza și complexitatea algoritmilor După cum bine știm, rezolvarea unei probleme se poate face prin mai multe căi – deci prin algoritmi diferiți, iar în cele mai multe cazuri este utilă, chiar necesară uneori, compararea acestor căi pentru a răspunde la următoarele întrebări: • Soluția obținută este corectă? Dacă da este ea optimă? • Rezolvarea problemei este inteligibilă și ușor de modificat? • Execuția algoritmului este eficientă - în sensul timpului de calcul și a resurselor necesare? Pe lângă aceste probleme fundamentale din punct de vedere practic apar și chestiuni teoretice prin care algoritmii se pot analiza și compara: • Durata de execuție este predictibilă? • Cât de complexă este rezolvarea problemei relativ la timpul de calcul necesar?

Complexitatea algoritmului În acest paragraf se vor stabile mărimi de evaluare a complexității algoritmilor. Analiza algoritmilor privește în principal timpul de execuție, dar acesta depinde puternic de sistemul de calcul și de contextul de rezolvare. O modalitate mai generală este calculul numărului de operații executate de algoritm pentru rezolvarea problemei – aceasta fiind dependentă doar de metoda de rezolvare și de numărul n al pieselor de date de intrare. În acest sens, compararea programelor se poate face urmărind în principal numărul de operațiuni costisitoare ca timp de execuție, cum sunt: apeluri de funcții, înmulțiri și împărțiri, atribuiri, comparații, etc. Pentru un număr n de piese de date la intrare, ne interesează: • complexitatea temporală – notată T(n), care reprezintă numărul de operații executate de algoritm asupra setului de intrare (considerând o operație în unitatea de timp); • complexitatea spațială – notată S(n), care reprezintă numărul de locații de memorie folosite de algoritm (memoria de date, stiva, regiștrii); unde T(n) și S(n) nu sunt funcții ci relații, fiindcă pot avea pentru același n rezultate diferite (dependente de modul în care algoritmi diferiți rezolvă o problemă).

40


Algoritmică şi programare

Analiza unui algoritm se face pentru un număr generic de n date în cele trei cazuri de interes: • cel mai defavorabil – indicând maximul numărului de operații efectuate, • mediu – indicând media numărului de operații efectuate, • cel mai favorabil - indicând minimul numărului de operații efectuate. Uzual, se analizează cazul cel mai defavorabil, apreciat prin Ordinul algoritmului, notat O (O mare), acesta corespunzând timpului cel mai lung (sau spațiului cel mai mare) necesar algoritmului pentru a prelucra toate piesele n de date. Complexitatea algoritmului este indicată de ordinul său. În urma analizei unui algoritm rezultă un ordin al acestuia, care poate fi încadrat în una din clasele de complexitate de mai jos în scopul comparării la un nivel mai general a algoritmilor: • Clasa P pentru probleme polinomiale, ce se pot rezolva într-un număr de operații exprimabil printr-un polinom de n – numărul de piese de date, într-un mod determinist (adică cunoscut și stabilit perfect privind operațiile și rezultatul lor). • Clasa NP pentru probleme nedeterminist polinomiale, ce se pot rezolva într-un timp polinomial într-un mod nedeterminist (de exemplu prin „ghicirea” soluției folosind algoritmi genetici, apoi se verifică dacă soluția este corectă. În clasa NP există o subclasă de probleme numite NP-complete care nu se pot rezolva în timp polinomial dar măcar se poate verifica în timp polinomial o eventuală soluție. Soluția propriu-zisă necesită un timp de rezolvare cuprins între unul exprimat polinomial și unul exprimat exponențial.

41


Algoritmică şi programare

Prelucrarea informației În această secțiune trecem în revistă noțiuni privind operațiunile efectuate asupra datelor și modul în care se specifică desfășurarea acestora – conform prelucrărilor vizate. Abordarea sistematică a operațiunilor și desfășurării lor este impusă de exprimarea concisă și completă a acestora printr-un limbaj de programare; întrucât limbajele de programare moderne reprezintă de fapt codificări optime ale operațiunilor necesare oricăror categorii de prelucrări, se utilizează pe parcursul modulului formalismul specific acestor codificări.

Identificator, variabilă, constantă, literal Datele sunt memorate ca succesiuni de biți în memoria de lucru (RAM) sau pe suport extern (disc magnetic sau optic, etc.), iar ele pot fi piese simple sau pot fi colecții de piese, cu o semnificație anume (litere, numere, texte, sunete, imagini). Pentru a fi manipulate, datele trebuie referite, adică apelate după un nume sau prin adresa acestora în memorie. Fie prelucrarea algebrică următoare: a = b + pi + 15 + sin(c) unde a, b, c și pi, sunt date denumite prin litere, sin() este numele funcției trigonometrice sinus iar 15 este o valoare imediată. Pe acest exemplu simplu se vor enunța noțiunile ce urmează. Identificatorul este numele atașat unei piese sau colecții de date, dar și a unei prelucrări anume, în general aflate în memoria de lucru. Numele este un șir de caractere (litere, cifre și alte simboluri de scriere) care trebuie să satisfacă anumite criterii, funcție de locul de utilizare a identificatorului. Astfel, în limbajele de programare identificatorii nu pot conține spatii (constituie un singur cuvânt). În expresia de mai sus identificatori pentru date sunt a, b, c, pi, iar pentru prelucrări este sin (calcului sinusului de unghi c). Dacă datele sunt piese sau colecții care se stochează în memoria internă (RAM) atunci identificatorii au semnificația unor adrese – prin care se pot manipula variabile și constante. Variabila este o locație de memorie referită printr-un identificator și poate conține diverse valori ale unei date de tip specificat. Valorile pe care le poate lua variabila aparțin domeniului specific tipului de date respectiv. De exemplu, datele de tip logic pot lua doar două valori - Adevărat sau Fals, iar datele de tip întreg pe un octet pot lua valori numere între 0 și 255. Datele vehiculate într-un program sunt de fapt variabile, care au primit fiecare o valoare chiar la momentul declarației (prin inițializare) –sau ca rezultat al unei expresii (prin atribuire).

42


Algoritmică şi programare

Constanta este o locație de memorie referită printr-un identificator și poate conține doar o singură valoare a unei date de tip specificat. Constantele sunt utile pentru anumite valori speciale (celebre) cum sunt numărul π sau numărul lui Neper e, constante universale sau doar valori care nu se doresc a fi modificate în cadrul unor prelucrări (voit sau din eroare). Constanta este de fapt o variabilă cu restricție de modificare a valorii, valoarea însăși fiind „ascunsă” după identificator. Identificatorii constantelor sunt în general nume rezervate - într-un limbaj de programare (de exemplu pi). Literalul este o valoare concretă pentru un tip de date, indicată prin simboluri specifice tipului respectiv. Inițializarea variabilelor (adică atribuirea unei valori inițiale în tipul de date propriu variabilei) sau specificarea unei valori imediate într-o expresie se face utilizând literali. În expresia de mai sus apare banal 15 - ca literal numeric, dar pentru alte tipuri de date specificarea este mai puțin comună; de exemplu un literal caracter (litera a) se indică ‘a’.

Expresii Termenul calculator ne îndreaptă gândul către calcule – posibile fiind prelucrările pe care acest instrument le poate executa dar, așa cum s-a arătat anterior, posibilitățile de prelucrare ale calculatoarelor sunt mult mai largi. Expresia este descrierea formală a unui set de acțiuni efectuate cu un anumit tip de date. Expresia este o structură sintactică formată din variabile, literali, operatori și invocări de funcții, care în procesul de calcul este evaluată pentru a se obține o valoare. Trebuie avut în vedere că, în programare: • Expresia este de fapt o secvență de program, în care se indică ordinea în care trebuie efectuată o succesiune de operații asupra unor date, în scopul de a obține o valoare. • Operațiile se fac întotdeauna asupra valorilor. • Operațiile sunt indicate prin operatori sau/și prin funcții, iar datele sunt indicate prin variabile și literali. Este, deci, foarte important să știm nu numai ce operații se fac, ci și în ce ordine se execută ele. Semnul = are rolul de a „încărca” în variabila din stânga sa, valoarea rezultatului obținut în urma evaluării expresiei din dreapta sa. Astfel, semnul = poate fi privit ca un operator de atribuire, iar întreaga construcție E = m*c2 ca o expresie cu o singură valoare – anume valoarea obținută pentru energia E.

43


Algoritmică şi programare

Operatori Operatorii sunt simboluri ale unor acțiuni cu semnificație stabilită prin tipul de date operat. Am folosit deja simbolul + ca operator de concatenare (deci simbol al operației de concatenare). Operatorul poate fi format din unul sau mai multe caractere. Entitatea asupra căreia se aplică operatorul se numește operand. După numărul de operanzi, operatorii pot fi: • operatori unari, care se aplica unui singur operand, de ex. operatorul ”-” in expresia ”-x”; utilizarea operatorului unar se face, de regula, sub forma "prefix" operator operand (operatorul este pus în fața operandului) iar uneori sub forma "postfix" operand operator, în care operatorul este pus după operand; • operatori binari, care se aplică asupra a doi operanzi, fiind situat între aceștia; de exemplu operatorul de concatenare ”+” in expresia "acesta este"+" un exemplu" sau operatorul de adunare a doua numere ”+” în expresia 17+28. Operatorii binari se folosesc sub forma "infix" operand1 operator operand2 - în care operatorul este situat între cei doi operanzi; • operatori ternari, care se aplica asupra a trei operanzi; de exemplu în limbajele C și Java există operatorul ternar (? : ) folosit în expresiile condiționale. Operatorul ternar se scrie sub forma (operand1 ? operand2 : operand3 ) în care operand1 are o valoare logica (true sau false), iar ceilalți doi operanzi reprezintă acțiuni. Pentru o privire completă asupra domeniului, se prezintă în continuare categorii de operatori, cu simboluri uzuale pentru limbajele C și Java. Operatori aritmetici sunt operatorii utilizați pentru calculele numerice și care se aplică funcție de tipul operanzilor (întregi sau reali). Există o ordine de precedență a operatorilor aritmetici: primii se aplică operatorii unari apoi cei de înmulțire și împărțire, ultimii de sumare algebrică; această ordine poate fi schimbată prin grupări între paranteze ‘(’ și ‘)’ ale operanzilor și operațiilor în expresie. Operatori relaționali sunt operatori (binari) pentru comparație sau incluziune: <, >, = =, != (simbol pentru ≠), <= și >= (simboluri pentru ≤ și ≥), in (pentru apartenență∈). Operatori logici sunt operatorii care produc un rezultat logic („adevărat ” sau „fals”). Ordinea de precedență este: primul negarea ! (NU), apoi conjuncția && (ŞI), ultimul disjuncția || (SAU); ordinea de precedență se poate schimba prin grupare cu paranteze.

44


Algoritmică şi programare

Tipuri de expresii Prin rolul său în program, o expresie este o linie de cod care poate fi redusă la o singură valoare. Se prezintă în continuare tipuri de expresii, prin exemple, pentru cazuri de operatori și precedență care prezintă interes. Expresii matematice Fie declarațiile de variabile: int a=3, b=7, c=11, d; care inițializate cu valorile de mai sus, pot fi utilizate în expresii similare celei de mai jos: d=(a + c) * b - (a + b) Expresii logice Aceste expresii au rezultat logic (adevărat - true, sau fals - false). Operatorii relaționali dau ca rezultat o valoare de tip logic fiindcă în urma relației (comparației) rezultatul poate fi adevărat sau fals. Comparațiile realizate cu operatori relaționali precum și agregările de operatori logici sunt privite ca întrebări „este adevărat că … ?” Evaluarea expresiilor Evaluarea expresiei constă în aplicarea succesivă a operatorilor asupra operanzilor corespunzători, într-o ordine care depinde de precedența operatorilor.

Fig. 8 Precedența operatorilor

45


Algoritmică şi programare

Se pot da reguli riguroase de evaluare a expresiilor, dar sunt destul de complicate. Preferăm să indicăm numai că expresia se parcurge de la stânga la dreapta, aplicând operatorii în ordinea lor de precedență și luând în considerație parantezele. Operațiile se fac asupra valorilor operanzilor. Variabilele se înlocuiesc prin valorile lor. Imediat ce s-a aplicat un operator, subexpresia respectivă (formată din operator și operandul sau operanzii corespunzători) se înlocuiește cu valoarea rezultată din calcul. Se continuă astfel, până când întreaga expresie este înlocuită printr-o singură valoare. Precedența poate fi modificată prin utilizarea de paranteze, cu convenția că operatorii din interiorul unei paranteze se aplică înaintea celor din exterior. De exemplu, în expresia „a + b*c” operatorul „*” se va aplica înaintea operatorului „+”, deoarece are precedența superioară. În consecință, aceasta expresie se va calcula ca și când ar fi scrisă sub forma a + (b * c).

46


Algoritmică şi programare

Instrucțiuni Pentru prelucrările simple – de genul calculelor matematice, este suficient un calculator științific, de buzunar. Dacă aceste calcule sunt combinate pentru a determina un rezultat al unei probleme care implică un anumit grad de dificultate – de exemplu soluționarea generală a unei ecuații de gradul întâi, atunci sunt necesare acțiuni de decizie: de exemplu „este coeficientul necunoscutei nul?” – atunci ecuația este degenerată; asemenea acțiuni de control a desfășurării prelucrărilor propriu-zise trebuie realizate printr-un program de calculator, pe baza unor comenzi specifice limbajului de programare ales. De fapt, pe lângă prelucrările variabilelor (care se realizează prin acțiuni secvențiale), mai sunt necesare două tipuri generice de acțiuni: de decizie și de repetiție. Rezultă că un limbaj de programare trebuie să asigure comenzi de: a) Atribuire –obținerea unui rezultat pentru o expresie și încărcarea valorii acestui rezultat în variabila destinație (din stânga semnului =). b) Decizie – pentru alegerea unei secvențe de comenzi din mai multe variante (uzual două) urmare a rezultatului unei expresii logice – condiția de decizie. c) Repetiție – pentru repetarea unei secvențe de comenzi de mai multe ori, până la îndeplinirea unei condiții de oprire. Între aceste acțiuni doar atribuirea este cea care privește formularea expresiilor, rezolvarea și păstrarea rezultatelor. Decizia și repetiția modifică doar cursul de execuție a atribuirilor. Tabloul comenzilor posibile se completează cu: d) Salturi și reveniri – pentru întreruperea unei secvențe de comenzi și preluarea controlului de către altă secvență de comenzi în cadrul aceluiași bloc program sau într-un subprogram. e) Operații de intrare/ieșire – pentru interacțiunea cu exteriorul, prin periferice de intrare / ieșire (către om sau instalații, pentru stocarea sau transferul datelor). Comenzile din categoriile a) și e) modifică valoarea sau suportul datelor, iar comenzile din categoriile b) c) și d) modifică controlul asupra fluxul de comenzi. Uzual, operațiile de intrare / ieșire nu fac parte din limbajul de programare ci din biblioteci de funcții atașate acestuia. Cuvintele cheie care exprimă categoriile de acțiuni de mai sus precum și modul corect de exprimare al lor (adică sintaxa) constituie setul de

47


Algoritmică şi programare

instrucțiuni a unui limbaj de programare. Trebuie remarcat că, în limbajele de programare moderne, operațiile de intrare - ieșire nu constituie instrucțiuni de limbaj ci sunt realizate prin subprograme specializate – în afara limbajului. Un program este un text ce cuprinde o secvență de instrucțiuni din categoriile enumerate mai sus, care sunt executate în ordinea în care apar în text. Procesorul asigură preluarea și executarea comenzilor în secvență, aceasta fiind ideea centrală de funcționare a mașinilor von Neumann. Sintetizând cele enunțate anterior, putem afirma că programul este un ansamblu de instrucțiuni, scrise într-un limbaj de programare, pe care calculatorul le execută pentru a îndeplini o anumită sarcină. După cum se poate imagina, comparativ cu instrucțiunile secvențiale, instrucțiunile de decizie și de repetiție au o exprimare mai complexă – de exemplu pentru decizie trebuie indicate acțiunile pentru ramura în care condiția de decizie are rezultat „fals” sau acțiunile pentru ramura cu rezultat „adevărat”. Instrucțiunile pentru decizii și repetiții sunt mai complexe și au nevoie de mai multe linii de text pentru a le descrie, pe când pentru instrucțiunile de atribuire este suficientă o linie de text ce descrie expresia vizată. De aceea instrucțiunile din prima categorie se consideră „instrucțiuni simple”, iar cele din ultimele două categorii „instrucțiuni structurate”.

Instrucțiuni simple (secvențiale) Într-un program acțiunile sunt descrise prin linii de cod/text, care apoi pot fi interpretate și executate de către calculator – linie cu linie sau pe loturi de linii. Descrierea unei acțiuni complete se încheie cu simbolul”;”(punct și virgulă) indicând „sfârșitul acțiunii”. Declarațiile de variabile sunt instrucțiuni în care se declară tipul și, eventual, valoarea inițială a unor variabile (inițializare). Instrucțiunile expresie sunt expresii de atribuire, de incrementare/ decrementare sau de invocare de metodă, care se termină prin simbolul ';' (punct și virgulă), numită și instrucțiunea vidă. Instrucțiunea de atribuire Prelucrarea efectivă a datelor are loc în cadrul evaluării instrucțiunii de atribuire: una sau mai multe valori intră într-o expresie al cărei rezultat se atribuie variabilei stânga, simbolul de atribuire = (în limbajele C și Java) sau := (în limbajul Pascal). Pentru exemplul de calcul al forței: F=m*a; // în C, respectiv F:=m*a; /* în Pascal În expresie intervin numai variabile ce au primit valori, iar variabila stânga trebuie să aibă tipul de date reprezentând rezultatul expresiei.

48


Algoritmică şi programare

Instrucțiuni de salt Întreruperea forțată a executării unei secvențe de instrucțiuni (și saltul la începutul altei secvențe) se poate face condiționat (dacă a fost îndeplinită o condiție logică) sau necondiționat. a) Saltul necondiționat – provoacă părăsirea execuției secvențiale a comenzilor. „Ruperea” secvențelor de comenzi din program prin salturi necondiționate, vădesc o proiectare defectuoasă, nesistematizată, a descrierii prelucrărilor; ele fac programul greu inteligibil și dificil de depanat sau dezvoltat și de aceea, acestea trebuie eliminate. În limbajele Pascal și C saltul necondiționat există doar pentru caracterul său istoric: GOTO eticheta; Secvența curentă este întreruptă și execuția se continuă de la eticheta. Există instrucțiuni de salt care provoacă părăsirea secvenței de comenzi doar în condiții bine precizate, impuse direct de prelucrări. b) Saltul de revenire din subprogram la programul apelant: return [expresie]; unde expresie produce ca rezultat o valoare care este „întoarsă” (returnată) programului apelant de către subprogram, fiind folosită direct în expresii. Parantezele drepte [ și ] indică o parte opțională – adică expresie poate lipsi în situația când nu este necesară o valoare returnată. Saltul de la programul apelant la programul apelat se face prin însăși numele subprogramului sau funcției. Salturile de terminare abruptă a instrucțiunilor structurate sunt impuse de prelucrări (în cazul repetițiilor sau deciziilor multiple): c) Întrerupere – încheie instrucțiunea curentă și trece la următoarea: break [eticheta]; d) Continuare – reia secvența curentă de la începutul ei: continue [eticheta]; Cazuri de utilizare sunt la întreruperea unei bucle (și saltul în afara sa, la secvența ce începe de la eticheta), respectiv reluarea unei bucle de la început, cu/fără parcurgerea întregii secvențe din buclă.

Instrucțiuni structurate (structuri de control) Structurile de control se mai numesc și instrucțiuni compuse și au rolul de a indica succesiunea în care se execută instrucțiunile programului. Distingem următoarele structuri de control: blocul, instrucțiunile de ramificare (decizia/selecția), instrucțiunile repetitive (bucle sau ciclurile) și structura de tratare a excepțiilor. Blocul este o succesiune de instrucțiuni cuprinsă între acolade. Instrucțiunile din cadrul blocului pot fi simple sau compuse. În particular, 49


Algoritmică şi programare

un bloc poate conține alte blocuri. Variabilele declarate într-un bloc sunt valabile numai în blocul respectiv, din locul declarării ei până la sfârșitul blocului, inclusiv în blocurile interioare. Structurile ramificate (decizia/selecția) sunt structuri de control în care fluxul programului conține două sau mai multe ramuri paralele. În limbajele Java si C/C++, structurile ramificate sunt realizate prin instrucțiunile if, if-else și switch. Structurile repetitive, numite și bucle sau cicluri, sunt structuri de control în care o anumită instrucțiune simplă sau compusă, de regulă un bloc, se execută în mod repetat. În limbajele Java si C/C++, ciclurile se realizează prin instrucțiunile while, do-while și for. Controlul fluxului de instrucțiuni este necesar pentru soluționarea unei probleme complexe, iar algoritmul este de fapt o rețetă de control al fluxului de instrucțiuni prin care se caută soluția la problema dată.

Instrucțiuni decizionale (de selecție) Instrucțiunea decizională (if) Instrucțiunea if este o instrucțiune condițională, care are forma generală: if(condiție) instrucțiune în care: condiție - este o expresie logică instrucțiune - este o instrucțiune simplă sau compusă, care se execută dacă și numai dacă expresia condiție are valoarea true. Instrucțiunea decizională binară (if-else) Pentru indicarea a două alternative în desfășurarea acțiunilor unui program se folosește decizia sau ramificația binară „dacă .. atunci”. Instrucțiunea if-else este o instrucțiune condițională, care are forma generală: if (expresie) instrucţiune1; else instrucţiune2; unde instrucţiune1 se execută doar atunci când condiția expresie are valoare logică „adevărat” (sau în limbajul C/C++, are o valoare diferită de

50


Algoritmică şi programare

0), altfel (când condiția este falsă sau 0) se execută instrucțiune 2. Când pe una din ramuri sunt mai multe instrucțiuni de executat, atunci se folosește pe acea ramură instrucțiunea compusă. Se observă că, prin modul de scriere a textului, ies în evidentă prin „indentare” (adâncire spre dreapta) ce se execută pe ramura „adevărat” (imediat după if) și ce se execută pe ramura „fals” (imediat după else). În cazul în care nu există o instrucțiune 2, atunci ramura else lipsește și se continuă cu secvența ce urmează după instrucțiunea vidă (simbolul de sfârșit ;). În diferite limbaje de programare (inclusiv în limbaje „script”) se folosește pentru decizia binară construcția de mai sus, cu diferențe minore de scriere (sintaxă): lipsesc parantezele după condiție și apare cuvântul then (ca în Pascal) sau în loc de instrucțiunea vidă(;) este folosit endif. Fie exemplul banal de „algoritm pentru rucsacul de vacanță”: 1) dacă (este vacanța de vară) atunci 2) repetă adaugă în rucsac tricou; 3) repetă adaugă în rucsac obiect de plajă; 1’) altfel 4) repetă adaugă în rucsac pulover; 5) repetă adaugă în rucsac obiect pentru schi; 6) închide rucsacul; În acest exemplu se observă „decizia” 1) – 1’) și „repetiția” 2), 3) sau 4), 5). Acestea se vor regăsi ca instrucțiuni într-un limbaj de programare ca instrucțiuni structurate – remarcați structura deciziei dacă .. atunci .. altfel, subliniată prin numerotarea cu apostrof. Cuvintele cu litere înclinate (cursive) indică modul cum se vor efectua operațiunile (sunt „instrucțiuni”) iar cuvintele scrise normal sunt operațiunile înseși (de fapt instrucțiuni simple). Astfel, la pasul 2) se repetă așezarea în valiză a tricourilor – unul la un moment dat (cel roșu, apoi cel verde, etc.), iar la pasul 3) similar, așezarea fiecărui obiect de plajă (umbrelă, ulei de plajă, etc.). Omul execută operațiunile înscrise mai sus una după alta, fără a fi necesară indicarea ordinii lor prin numere – în acest exemplu au fost numerotate doar pentru a fi referite. Similar, procesorul (construit după principiile enunțate de von Neumann) execută operațiile una după alta, în ordinea apariției lor în textul programului; în exemplul nostru, după încheierea repetiției 3) se execută direct 6) – în cazul vacanței de vară, similar în cazul vacanței de iarnă.

51


Algoritmică şi programare

Instrucțiunea de decizie (selecție) multiplă Decizia binară – prezentată mai sus, privește situații simple, cu două alternative: Alb/Negru, Da/Nu, Adevărat/Fals. Pentru situații în care decizia urmărește mai mult de două alternative, este dificil de aplicat mai multe instrucțiuni de decizie binară. O asemenea situație apare când expresia de selecție nu are valori binare ci multiple – cum ar fi cazul selecției opțiunilor unui meniu; unde, fiecare opțiune devine un caz selectat printr-un număr sau prin poziția indicatorului pe ecran („mouse”). Ca exemplu, se prezintă instrucțiunea switch în limbajul C/C++, într-o secvență de program în care se alege o opțiune din trei posibile la alegerile prezidențiale, prin numărul acesteia – furnizat prin NumarOptiune. Instrucțiunea switch este o structură de control, care are forma: switch (NumarOptiune) { case ‘1’: { “Candidat de stânga”}; break; case ‘2’: { “Candidat de centru”}; break; case ‘3’: { “Candidat de dreapta”}; break; default: { „Exprimați-vă opțiunea”}; } Se observă că pentru un alegător indecis (care alege NumarOptiune diferit de 1, 2 sau 3) există posibilitatea de a fi atenționat că doar aceste opțiuni sunt disponibile – prin secțiunea default (care înseamnă „implicit” în engleză); textele dintre {} la fiecare caz, apar în loc de blocuri program prin care se afișează – de exemplu, sigla și numele candidatului. Instrucțiunile de salt break, sunt utilizate pentru a încheia posibilitatea de selecție după ce s-a exprimat o opțiune, în scopul continuării cu secvența program care urmează instrucțiunii switch, adică după }.

52


Algoritmică şi programare

Instrucțiuni repetitive (bucle sau cicluri) Instrucțiunea repetitivă cu număr cunoscut (finit) de pași Deseori, prelucrările pentru care este util calculatorul sunt cele în care se repetă anumite operații de foarte multe ori; omul ar obosi (apoi greși) la repetiții îndelungate ale acelorași operații, dar echipamentul electronic le execută precis, rapid și fără complicații sociale. Instrucțiunea for, întâlnită și sub denumirea de ciclul cu inițializare, test și reinițializare, se folosește în special atunci când numărul de repetări al corpului ciclului este dinainte cunoscut. Această instrucțiune are forma: for(iniţializare opt ; condiţieContinuare opt ; incrementare opt ) instrucţiune opt în care: inițializare: listă de expresii de inițializare, separate prin virgule; condiţieContinuare: expresie logică; incrementare: listă de expresii, care se execută după executarea corpului ciclului; instrucțiune: instrucțiune simplă sau compusă (de preferință un bloc) care constituie corpul ciclului. Toate componentele marcate cu opt sunt opționale. Executarea instrucțiunii for decurge astfel: • se execută mai întâi secvența de expresii inițializare; • se intră în ciclu, executându-se în mod repetat instrucțiune urmată de secvența de expresii incrementare, cât timp expresia condiţieContinuare are valoarea true; • când expresia de condiţieContinuare are valoarea false, se iese din ciclu. Atunci când se cunoaște numărul de iterații (cuvânt ce indică „repetiții numerotate”), este utilă instrucțiunea for – ce apare în diverse limbaje, dar se va prezenta cu sintaxa uzuală în limbajul C/C++. Trebuie remarcat că, pentru a efectua numărul de iterații dorit, este necesar un contor pentru care se indică o valoare de start, o valoare de final și o modalitate de avans a contorului (în această ordine) prin expresiile ce apar între () și sunt separate cu ; ca mai jos. 53


Algoritmică şi programare

for (NrStud=1,NrBilet=35;NrStud<=30;NrStud++,NrBilet--) { „Prezintă legitimație și primește bilet de examen” }; Exemplul privește verificarea legitimației de student și primirea de către acesta a biletului de examen, la o grupă de 30 studenți. Contorul NrStud reține câți studenți au intrat în sala de examen iar contorul NrBilet reține câte bilete au mai rămas examinatorului – din totalul de 35; expresiile NrStud++ şi NrBilet-- adună și respectiv scad o unitate din contoarele respective (aceasta înseamnă ++ și respectiv --). Instrucțiuni repetitive cu test (inițial sau final) Atunci când nu se cunoaște numărul de iterații, terminarea repetițiilor poate fi indicată de o condiție cunoscută – care se aplică înainte sau după efectuarea operației, în bucla de repetiție. Ca urmare, se folosesc una din cele două tipuri de instrucțiuni: a) ciclul cu test inițial (while): „cât timp (condiție) adevărată – repetă {operație}” – prin care este posibil ca operația să nu se execute nici măcar o dată, dacă de la început condiția nu este îndeplinită; b) ciclu cu test final (do-while): „repetă {operație} – cât timp (condiție) adevărată” – prin care operația se execută cel puțin o dată, indiferent dacă este adevărată condiția la intrarea în buclă.

Fig. 9 Instrucțiuni repetitive cu test (inițial sau final)

Instrucțiunea while realizează structura de control repetitivă numită ciclu cu test inițial și are următoarea formă: while(condiție) instrucțiune

54


Algoritmică şi programare

în care: condiție - este o expresie logică; instrucțiune - este o instrucțiune simplă sau compusă (de regulă un bloc), care se repetă cât timp expresia condiție are valoarea true. Instrucțiunea do-while realizează structura de control repetitivă numită ciclu cu test final și are următoarea formă: do bloc while(condiție); în care: bloc - este o structură de control sub formă de bloc; condiție - este o expresie logică. Executarea blocului se repetă și în acest caz cât timp este satisfăcută condiția dar, spre deosebire de instrucțiunea while, în acest caz testarea condiției se face după ce a fost executat corpul ciclului. Pentru exemplul de mai sus reluat, dar în care nu se cunoaște numărul de studenți ce se prezintă la examen, descrierea în limbajul C/C++ a celor două cazuri ar fi: (a) while (NrStud>0) { „Prezintă legitimație și primește bilet de examen” }; (b) do

{ „Prezintă legitimație și primește bilet de examen” }; while (NrStud>0);

Lăsăm cititorul să decidă care dintre formele (a) sau (b) este adecvată exemplului ales. (Indicație: este posibil ca la un examen să nu se prezinte nici un student). Dacă există cazuri în care bucla de repetiție trebuie întreruptă în desfășurarea ei (de exemplu în cazul când unele operații nu se execută dacă nu este îndeplinită o condiție), atunci apar în interiorul buclei instrucțiuni de salt, de tipul break (întrerupe repetiția și părăsește bucla) sau continue (reia bucla de la început fără executarea operațiunilor care urmează acestei instrucțiuni).

55


Algoritmică şi programare

Programe și subprograme Secvențele de instrucțiuni sunt organizate în programe și subprograme, fiecare având un nume care – în principiu, indică rolul prelucrărilor acestuia. Program este denumirea generică a unei înșiruiri de comenzi care execută prelucrări într-un scop dat; comenzile pot fi exprimate prin cuvinte cheie specifice unui limbaj de programare (în programarea „clasică”) sau poate fi o structură de reprezentări grafice ale comenzilor, plasate pe o suprafață de lucru (în programarea „vizuală”). Pe scurt, Program= date + algoritm. Programul reprezintă un ansamblu de instrucțiuni, scrise într-un limbaj de programare, pe care calculatorul le execută cu scopul de a îndeplini o anumita sarcina. Programul conține: • descrierea datelor de intrare, de ieșire și intermediare cu care se operează; • descrierea operațiilor efectuate asupra datelor (a algoritmului de prelucrare). În cazul programelor scrise într-un limbaj de programare, structura textului depinde de modul de programare (structurată sau obiectuală), în principiu, pentru un program fiind specificate: nume_program (lista parametri) { declarații variabile corpul programului } unde nume_program este un identificator al programului (prin care poate fi apelat spre a executa acțiunile înscrise în el), lista parametri este setul de date care se furnizează programului (date de intrare), declarații variabile indică variabilele (locale) necesare prelucrării/stocării rezultatelor intermediare, corpul programului este secvența efectivă de comenzi pentru acțiunile vizate, iar acoladele {} încadrează corpul programului și delimitează programul propriu-zis.

56


Algoritmică şi programare

Subprograme În cazul unor prelucrări complexe, care sunt supuse procesului de ciclare, este indicată folosirea subprogramelor; acestea sunt secțiuni de cod scrise o singură dată și folosite de mai multe ori, prin apelarea lor ori de câte ori este nevoie, separat sau în cadrul expresiilor. Apelarea subprogramului se face prin intermediul identificatorului său (numele), similar cu referirea unei variabile. Dacă un subprogram este inclus unei biblioteci de subprograme el poate fi apelat de către oricare alt program scris în limbajul respectiv sau în alte limbaje (dacă la apelare se fac precizările de rigoare). Subprogramul este o prelucrare care se poate efectua asupra unui set de date ale căror valori sunt specificate la „apelarea” subprogramului. Subprogramul este un program apelat (prin nume) în cadrul altui program (programul apelant) pentru a executa acțiunile sale specifice. Deci subprogramele nu sunt „de sine stătătoare”, adică nu pot fi lansate direct de către utilizator din sistemul de operare. Subprogramul care, după execuția acțiunilor sale, revine în programul apelant cu o singură valoare (valoare „returnată”) ce poate fi folosită direct în calculul unei expresii; se numește funcție. Un exemplu clasic este funcția sin(x), care poate fi apelată într-o expresie – de exemplu a+b+sin(x), sumele fiind realizate doar după furnizarea calculului sinusului pentru valoarea x. Structura textului de definire a unei funcții adaugă, la structura de principiu, specificarea tipului valorii returnate și comanda de revenire în programul apelant: tip_valoare_returnata nume_functie (lista parametri) { declarații variabile corpul programului return expresie; //nu apare obligatoriu la final } unde tip_valoare_returnata specifică tipul de date al valorii rezultate în urma calculelor din expresie și care va fi adusă la revenirea în programul apelant. Execuția programelor se face de către procesor, care poate funcționa doar secvențial și pentru o singură sarcină (un singur program) la un moment dat. La apelul unui subprogram de către programul apelant, acesta din urmă trebuie „părăsit” de către procesor iar contextul de lucru (adică setul de rezultate parțiale) din memoria locală a procesorului trebuie salvat în memoria internă – RAM. La revenirea din subprogram, acest context se 57


Algoritmică şi programare

încarcă din memoria RAM în memoria locală a procesorului iar programul apelant reia lucrul – exact din punctul în care a fost întrerupt pentru execuția subprogramului, folosind valoarea „întoarsă” ca rezultat de către subprogram (vezi instrucțiunea return), și valoarea rezultat pentru expresie.

Programul principal Execuția acțiunilor înscrise într-un program trebuie inițiată la comanda utilizatorului. Pentru aceasta sistemul de operare interacționează cu omul, primește comanda (care de obicei este chiar numele programului) și „lansează” execuția acestuia. Partea din textul unui program care poate fi lansată nemijlocit de sistemul de operare (deci care poate funcționa de sine stătător) se numește program principal. Acesta este, în general, o succesiune de acțiuni grupate în corpul programului pe trei secțiuni: i) Introducerea datelor ii) Prelucrarea datelor iii) Afișarea rezultatelor Pentru execuția acestora, se face apel la subprograme din biblioteca de subprograme sau din setul subprogramelor declarate în textul sursă al programului ca ansamblu; în acest ultim caz, programul va fi specificat astfel: nume_program_principal (lista parametri) { declarații variabile declarații subprograme definire subprograme { corpul programului principal } } unde declarații subprograme reprezintă secțiunea în care se inventariază numele și lista parametrilor, specifice tuturor subprogramelor apelate în textul programul. Secțiunea definire subprograme (declarația forward) reia declararea subprogramelor, de această dată cu descrierea acțiunilor din fiecare subprogram.

58


Algoritmică şi programare

Realizarea programelor și programe suport Funcționarea unui sistem de calcul presupune existenta pe suportul fizic (echipament) a secvenței de comenzi (program) care indică ce prelucrări suportă valorile de interes (date). Anterior am nuanțat cele trei entități, privind: (I) structura constructivă și funcțională a echipamentului de calcul; (II) reprezentarea datelor referitoare la diverse tipuri de informații; (III) comenzile elementare (instrucțiuni) și modalitățile de soluționare generică a problemelor (algoritmi). Totuși, acestea sunt doar instrumente puse la dispoziția omului de Tehnologia Informației și Comunicațiilor dar, rezolvarea completă a unei probleme noi (necunoscute) presupune abordarea tuturor aspectelor ce apar în situații reale, ba chiar mai mult, elaborarea unei soluții generice pentru toate (sau cât mai multe din) problemele similare. Sunt necesare: (IV) analiza problemei (sau clasei de probleme) și elaborarea unei soluții generale adecvate (analiza și soluționarea problemei); (V) înscrierea comenzilor de găsire a soluției cu prevederea tuturor situațiilor ce pot apare, apoi furnizarea acestora calculatorului (proiectarea și realizarea programului); (VI) validarea soluției și utilizarea ei pentru situații concrete din clasa de probleme . În acest modul se vor aborda chestiuni ce privesc acțiunile (IV), (V) și (VI), cu metodele sistematice ce stau la baza lor, cu mijloacele prin care aceste acțiuni se pot realiza eficient (sau automat) și cu chestiuni legate indirect de utilizarea soluției – cum sunt documentarea, întreținerea sistemului, dezvoltarea sa. În principal, scopul tuturor acestor acțiuni este realizarea programului de calculator, adică programarea. Programul este o secvență de comenzi exprimată într-un mod codificat și care poate fi interpretat și executat de către calculator, având ca scop prelucrarea informației. După cum s-a arătat, un program nu prezintă doar secvențe (succesiuni) de comenzi ci și ramificații sau repetiții iar secvența este esențială fiindcă reflectă modalitatea în care omul concepe rezolvarea unei probleme – pași succesivi, cu o singură operație la un moment dat. Chiar și procesorul este astfel realizat constructiv (indiferent cât de perfecționat ar fi) pentru a executa o singură comandă al un moment dat sau, mai multe comenzi în serie (secvență). Un sistem de calcul poate executa mai multe 59


Algoritmică şi programare

comenzi simultan doar dacă are mai multe procesoare (sistem multiprocesor, multi-core sau mașină paralelă). Pe de altă parte, la rezolvarea unei probleme cu ajutorul calculatorului apar situații complexe, colaterale problemei de bază, ce trebuie și acestea rezolvate, cum sunt: condiții limitate de către contextul de lucru sau de valorile de intrare, greșeli posibile pe care operatorul uman le poate face (din necunoaștere sau neatenție); apoi prezentarea rezultatelor este importantă: tabele, grafice, sunete, etc. Se constată deci că rezolvarea unei probleme (chiar foarte simplă) implică de fapt rezolvarea altor multiple probleme privind utilizarea de către om a soluției. Aplicația este un set de programe reunite și interdependente, care se prezintă într-un mod unitar și oferă soluții pentru o clasă de probleme date.

Problematica programării Programarea este un proces de exprimare a instrucțiunilor prin care se rezolvă o problemă și care pot fi convertite mecanic în acțiuni ale unui sistem de calcul. Programarea este dependentă de limbajul de codificare a instrucțiunilor, de cunoștințele programatorului privind regulile structurale (sintaxa) și semnificația elementelor de limbaj (semantica), de modul cum se execută (clar, comentat și documentat) și de modul general de gândire privind soluționarea problemelor (metode de analiză și implementare). De aceea, o lungă perioadă, programarea a fost considerată o „artă”, unde programatorul s-a putut exprima pe sine (ca mod de gândire, eleganță în exprimare, eficientă a soluției) prin produsul program realizat. Întrucât informatica a devenit o industrie, programarea este privită astăzi prin prisma eficienței muncii și a soluțiilor, adică vizează productivitatea muncii de programare și o standardizare a utilizării produselor. Rezultatul programării este un produs program. Ca orice produs, el are un ciclu de fabricație, o valoare de utilizare (și de aici un preț), precum și un proprietar. Spre deosebire de alte produse, un program produs dezvoltat de o firmă este proprietatea acesteia și nu a cumpărătorului programului; cumpărătorul achiziționând doar dreptul de utilizare a programului și ca atare nu îl poate modifica sau revinde. Un program reprezintă implementarea unei idei (deci presupune o „tehnologie” de implementare a ideii) și, conform reglementărilor privind dreptul de proprietate intelectuală (pentru orice creație), firma sau persoana care l-a elaborat are drepturi de autor asupra lui, însă nu poate emite pretenții asupra ideii la care se referă programul. Produsele program intră astfel pe piață, alături de alte produse, cu specificul lor – nu pot deveni proprietatea cumpărătorului iar multiplicarea lor neautorizată este ilegală. 60


Algoritmică şi programare

Etape în ciclul de viață ale unui produs program Programarea propriu-zisă este doar o etapă în realizarea și existenta unui program. Ciclul de viață al unui produs program cuprinde acțiuni desfășurate de la lansarea ideii programului până la înlăturarea lui (fiind perimat sau inutil). Etapele din viața unui program sunt grupate în faze ca în Figura 10 iar evoluția ciclică a fazelor este ilustrată în În continuare sunt descrise etapele din ciclul de viață, indicând metodologii sau abordări sistematice care cresc eficienta muncii în fiecare etapă. O metodologie în domeniul analizei și proiectării unei aplicații (sau a unui sistem informatic) se referă la următoarele aspecte: • modele – ca viziuni conceptuale asupra obiectelor și activităților întrun domeniu dat, • metode – ca acțiuni ce determină obiecte și activități concrete din problema de rezolvat, • instrumente – ca mijloace de lucru software care ajută analiza și/sau proiectarea aplicației.

Fig. 10 Faze și etape în existența unui produs program

61


Algoritmică şi programare

Formularea cerințelor În această etapă se enunță de fapt problema ce se dorește rezolvată cu ajutorul calculatorului și constituie motivul pentru care este necesară aplicația. Se enunță o soluție de principiu, care rezultă chiar din formularea cerințelor noului program sau sistem informatic. Adesea, beneficiarul aplicației nu știe exact ce dorește de fapt și nici posibilitățile pe care un program i le poate oferi. De aceea, ciclul de viață se poate relua, chiar cu reformularea cerințelor – spre a fi în acord cu realitatea sau cu disponibilul financiar pentru produsul program.

Fig. 11 Ciclul de realizare a unui produs program (aplicație sau sistem informatic)

Analiza problemei Această etapă începe cu „Studiu și elaborarea soluției problemei”, prin care se evaluează situația existentă, se parcurg metode sau soluții deja aplicate în situații similare, apoi se stabilesc acele metode (eventual și algoritmii) care permit rezolvarea conceptuală a problemei. Studiul efectuat descrie problema (sau sistemul țintă) din următoarele puncte de vedere: i) Viziunea externă (specificația) – asupra scopurilor aplicației; ii) Viziunea organizațională (structurală) – asupra modului de realizare a aplicației; iii) Viziunea comportamentală (temporală) – asupra evoluției dinamice a aplicației; iv) Viziunea asupra resurselor – hardware (echipamente de prelucrare și transfer), software (alte programe necesare

62


Algoritmică şi programare

aplicației), resurse umane (implicate în operarea și utilizarea aplicației), resurse financiare (sume estimate pentru realizarea aplicației). Pentru realizarea unei analize precise și complete, urmată de elaborarea sistematică a soluției, se recomandă respectarea unei metodologii de analiză, care este, de obicei, specifică modului de proiectare și dezvoltare a aplicației. La baza celor mai multe metodologii stă conceptul de diagramă „Entitate-Relație”, care constă într-o reprezentare grafică, intuitivă, a obiectelor și legăturilor dintre ele în problema reală dată. De asemenea, abordările de analiză pot evolua „de la mic la mare” (bottom-up, de jos în sus, de la amănunt la general) sau „de la mare la mic” (top-down, de sus în jos, de la general la amănunt). Fiecare metodologie deține instrumente software adecvate, pentru asistarea experților umani în modelarea soluției și a programului sau sistemului de informatizare. Prin aceste instrumente se pot elabora sistematic structuri de obiecte conceptuale, care se reprezintă ca diagrame și scheme bloc fizice și funcționale ale viitoarei aplicații. Persoanele implicate în această etapă: analiști în domeniul problemei (adică specialiști cu experiență și suficiente cunoștințe pentru a elabora o soluție viabilă și în detaliu), analiști de sistem (adică informaticieni cu experiență în tipul de probleme din care face parte problema de rezolvat), beneficiarul și utilizatori obișnuiți pentru sistemul existent (care dau detalii asupra situației existente și problemei de rezolvat, pretind un mod de funcționare a aplicației și un anume mod de prezentare a rezultatelor). Documentele care rezultă din această etapă sunt „Specificația de proiectare” (descrierea de principiu a informațiilor și prelucrărilor) și „Strategia de testare” (care prevede modurile în care se vor testa modulele și întreg ansamblul, precum și datele de test – cu rezultatele așteptate). Proiectarea aplicației Etapa de proiectare („design”) se referă la structurarea efectivă a blocurilor software cu indicarea rolurilor, interacțiunilor și resurselor fiecăruia. Activitatea de proiectare implică abstractizarea faptelor ce au rezultat în etapa de analiză, pentru modelarea informațiilor și acțiunilor necesare rezolvării problemei date. Procedura de abstractizare elimină faptele irelevante și le accentuează pe cele esențiale, iar procedura de modelare reprezintă informații și acțiuni într-un mod specific. Modelul obținut privește doar aspectele care se doresc rezolvate ale problemei (nu toate aspectele acesteia) și va conține obiecte cu (și asupra cărora) se acționează. Pentru un produs informatic, modelul poate fi formal (adică exprimat prin simboluri, de exemplu prin formule) sau procedural (adică exprimat prin cuvinte ca o rețetă de bucătărie).

63


Algoritmică şi programare

Metodologia aplicată la etapa de proiectare este puternic dependentă de modalitatea de programare. De aceea, etapele de proiectare și implementare sunt strâns legate, uneori chiar suprapuse iar această legătură provine din modul cum este gândită, chiar de la etapa de proiectare, realizarea efectivă (implementarea) aplicației pe întregul ei și pe fiecare program în parte. Între metodologii se amintesc două mai importante: proiectarea obiectuală (pentru aplicații în care se pot discrimina obiecte din lumea reală ce sunt manipulate de aplicație) – cu utilizare mai frecventă în domenii tehnice și care simulează realitatea și proiectarea cu baze de date (pentru aplicații de gestiune a resurselor de orice fel) – cu utilizare frecventă în economie și administrație. Pe lângă partea software, la această etapă se proiectează și structura de echipament, privind: structura de calculatoare și configurația fiecăruia, structura de comunicație (rețea locală, echipamente de rețea, conectarea la Internet) și structura de periferice partajate (adică folosite în comun) de mai mulți utilizatori (imprimante sau mese de desen, interfețe de proces pentru culegerea datelor sau comanda din/către instalații). Se proiectează tipul și configurația sistemelor de operare - strâns legat de structura de echipamente și de scopurile aplicației. Persoanele implicate în această etapă sunt: analiști de sistem (informaticieni cu pregătire specială în folosirea unui instrument de proiectare și implementare a programelor), ingineri hardware și ingineri de sistem (care proiectează structura de echipamente și programe), conducători de proiect (specialiști în domeniul țintă sau în informatică, care cunosc modul de organizare a activităților complexe precum și domeniul țintă). Documentele elaborate la finalul etapei sunt „Specificația de programare” (indică structura de module și acțiuni apoi datele necesare fiecărui program), „Planificarea lucrărilor de implementare ”și „Inventarul resurselor necesare” (financiare, umane și materiale) pentru realizarea noului program sau sistem de informatizare, Implementarea și testarea aplicației Activitatea esențială a acestei etape este programarea. Se vorbește adesea de „programarea calculatoarelor” subînțelegând toate activitățile implicate de aceasta, poate fiindcă programarea este activitatea prin care efectiv echipamentul de calcul devine funcțional (fără programe este „doar fier”). În sine, programarea constă în codificarea operațiunilor pe care calculatorul trebuie să le execute către atingerea unui scop dat (calcul matematic, retușarea și afișarea unei imagini, sau mișcarea brațului unui robot). După cum se constată, programarea este doar partea de realizare efectivă a programului, care însă necesită multe alte activități anterioare și posterioare.

64


Algoritmică şi programare

Fazele realizării unui program sunt: (I) Scrierea programului sursă – prin care se descriu acțiuni (folosind un limbaj de programare) într-un text scris cu un editor de texte. Atât limbajul cât și modul de realizare a programului sursă sunt apropiate obișnuințelor umane (cum spre exemplu, o rețetă de bucătărie este înscrisă ca text, într-o formă simplificată uneori chiar codificată). (II) Compilarea – prin care textul sursă este „tradus” din limbajul de programare (exprimat prin cuvinte – ) în limbajul mașinii (exprimat prin coduri binare). Traducerea este realizată de un program special pentru limbajul de programare ales – numit compilator, iar rezultatul este codul obiect al programului. (III) Editarea legăturilor – prin care în codul obiect se inserează subprograme, preluate din biblioteci de subprograme, ce descriu prelucrări uzuale, pe care programatorul le folosește fără a mai scrie cod (fără a scrie programul ci doar a-l apela din bibliotecă). Astfel, prelucrări care au fost doar amintite în programul sursă se înscriu efectiv în codul obiect. Rezultatul fazei este codul executabil al programului, adică forma binară ce poate fi încărcată direct în memoria de lucru și poate executa operațiunile programate. Cuvintele, ce exprimă comenzi, se combină în limbajul de programare respectând o sintaxă strictă (ca reguli gramaticale); programatorul poate greși (din neatenție, din necunoaștere), astfel că textul sursă fiind greșit, este posibil să nu poată fi interpretat de calculator. În acest caz, este necesară: (IV) depanarea programului – care constă în modificarea textului sursă spre a fi eliminate erorile. Identificarea erorilor și apoi verificarea programului se realizează cu ajutorul unui depanator (program de asistare a programatorului în activitatea de verificare a corectitudinii programului). Corectarea efectivă a erorilor constă în înscrierea corectă a cuvintelor sau a combinațiilor de cuvinte în textul sursă. Depanatorul localizează erorile din program şi face chiar sugestii de corectură, însă aceste erori sunt legate doar de „modul de exprimare” în limbajul dat, nu de modul cum a fost rezolvată problema (dacă soluția este corectă sau nu); eliminarea erorilor de soluționare a problemei se poate face doar prin executarea de teste pe date și în situații reale, urmată de compararea rezultatelor cu cele așteptate și apoi modificarea algoritmilor de prelucrare. În general, editarea, compilarea și depanarea programului se realizează folosind un mediu integrat (un program complex cu toate aceste instrumente), spre a spori eficiența muncii de programare. Astfel de instrumente sunt „mediile de programare” sau „instrumentele de inginerie 65


Algoritmică şi programare

software”. Scrierea efectivă a programului se numește codificare. Această operațiune complexă nu se realizează doar înscriind textul în limbajul de programare ales ci se includ date, obiecte sau prelucrări „prefabricate” din biblioteci ale mediilor de programare utilizate pentru scrierea aplicației. Aplicația se implementează modular – fiecare subprogram rezultat la proiectare (și înscris în „Specificația de programare”) este codificat și testat separat. La realizarea programelor se respectă principii de inginerie a programării, în scopul depanării facile și apoi a dezvoltării coerente a fiecărui modul și aplicație. După ce modulele sunt verificate, se face integrarea aplicației, adică se instalează toate piesele software și hardware ale aplicației. Se face testarea ansamblul în condiții de laborator și se emit documentele de conformitate cu cerințele (dacă sunt respectate sau nu, care din cerințe nu au fost satisfăcute și de ce).

Fig. 12 Fazele de realizare ale unui program

În situația în care funcțiile aplicației sau utilizarea acesteia nu sunt conforme cerințelor, se decide dacă, și pentru care din cerințe, se reiau fazele de analiză, proiectare și apoi cele de implementare cu testare. Similar fazei de proiectare, pentru structura de echipamente se procedează la achiziționarea, instalarea și testarea fiecărui echipament și a întregului sistem, format din calculatoare, rețea și echipamente de interconectare, periferice în rețea, alimentare cu energie electrică, spații de securizare a echipamentelor sensibile și stocare a suporturilor de date. Persoanele implicate în aceste faze sunt: analiști programatori (elaborează structurile conceptuale de module sau obiecte și stabilesc datele și prelucrările pentru fiecare din ele), programatori (realizează codificarea programelor), ingineri electroniști, electricieni, alți tehnicieni (pentru instalarea echipamentelor și, eventual, amenajarea spațiilor), ingineri de sistem sau ingineri DevOps (pentru instalarea sistemelor de operare și 66


Algoritmică şi programare

integrarea aplicațiilor), precum și beneficiari sau utilizatori (pentru testarea utilizării aplicațiilor și certificarea conformității cu cerințele). Documentele elaborate în finalul acestei etape sunt: „Programe sursă” ale aplicației și fișierelor de comandă, „Documentația aplicației” (care descrie structura de module, funcționarea și configurarea aplicației), „Manualul de utilizare”, „Fișe de testare” (care constată conformitatea utilizării cu cerințele). Darea în exploatare a aplicației se face după o testare la utilizator (de obicei de 72 de ore), și dacă aceasta a decurs cu succes se încheie un „Proces verbal de recepție”. Acest document încheie ciclul de realizare al aplicației; oricare alte modificări solicitate și realizate după acest moment se consideră lucrări separate, pentru care se parcurg din nou etapele (de la analiză până la implementare și testare). Exploatarea și dezvoltarea aplicației După ce aplicația a fost testată și recepționată de către beneficiar ea intră în exploatare, adică este utilizată pentru scopul pentru care a fost realizată. „Darea în exploatare” este faza în care personalul utilizator urmează cursuri de pregătire pentru utilizarea aplicației și, eventual, conducerea asigură cadrul organizatoric (personal specializat, spații și regulamente de lucru) pentru aplicația sau sistemul în cauză. Pe durata exploatării aplicației (sau a sistemului de informatizare) pot apare diverse probleme, care trebuie rezolvate spre a se asigura o funcționare corectă. Dintre probleme amintim: actualizarea unor date ale aplicației de tip parametric (de exemplu la modificarea legislației legată de o aplicație de contabilitate), configurare periodică, administrarea resurselor sistemului (imprimante, discuri, conturi utilizator), rezolvarea unor incidente de operare sau ce apar după un accident (defect) sau după modificări în echipamente. Acțiunile de rezolvare a unor asemenea probleme se pot reuni în activitatea de întreținere a aplicației. Persoanele implicate în această activitate sunt: ingineri DevOps, ingineri de sistem (asigură configurarea corectă a sistemului de operare și a programelor de aplicație), administratori de baze de date (asigură configurarea și asistă utilizatorii în utilizarea corectă a bazelor de date) administratori de rețea (asigură configurarea și menținerea bunei funcționări a echipamentelor și programelor de comunicație), ingineri și/sau tehnicieni de întreținere echipament, utilizatori obișnuiți și utilizatori „privilegiați” – ultimii având de fapt sarcini speciale, de exemplu gestiunea resurselor grupului de lucru, servicii de configurare specifică grupului de lucru; calificativul de „privilegiat” se referă la drepturile (și răspunderile) extinse pe care le au privind accesul la date și programe. Exploatarea aplicației – în forma în care a fost achiziționată, are loc până la apariția unei versiuni perfecționate (adică o dezvoltare - în engleză

67


Algoritmică şi programare

„upgrade”) sau până la inutilitatea ei (datorită apariției pe piață a unor noi produse sau prin dispariția scopului aplicației). În măsura în care prin modificarea aplicației se pot obține caracteristici mai performante, se poate intra într-o etapă de dezvoltare a aplicației, în care se repetă toate etapele – de la analiză la implementare, parcurse la realizarea aplicației. Evident, cea mai marea parte a programelor din aplicația curentă nu ar trebui să sufere modificări ci doar cele care nu mai sunt de actualitate sau necesită perfecționări.

Limbaje de programare În modulele anterioare s-au amintit unele limbaje de programare în contextul declarării tipurilor de date dar și a tipurilor de comenzi necesare descrierii prelucrărilor cu ajutorul sistemelor de calcul. De fapt, acestea sunt cele două aspecte generice prin care omul comunică mașinii ce are de făcut: cu ce (datele) și cum (comenzile). Un limbaj de programare apropiat omului oferă independentă programatorilor fată de tipul mașinii de calcul, permițând acestuia să se concentreze asupra rezolvării problemei, nu asupra mașinii. Un limbaj de programare este un set de cuvinte cu semnificații precise, care se pot combina după reguli stricte pentru a exprima comenzi și a descrie date necesare unui tip de prelucrare. Limbajele de programare sunt limbaje artificiale, în care se scriu programele pentru calculator. Limbajul trebuie să poată fi înțeles de către om (care îl concepe), dar trebuie să poată fi executat de calculator. Din această cauză, trebuie să respecte un formalism riguros. Din punctul de vedere al comodității de utilizare de către om, există mai multe niveluri de limbaje de programare: cod mașină, limbaje de asamblare, limbaje de nivel superior. În limbajul mașină, numit și cod mașină, instrucțiunile sunt scrise binar (prin numere), așa cum sunt ele în memoria calculatorului în momentul executării programului. O instrucțiune mașină tipică are forma <cod operație> <adresa operandului>, unde ambele părți ale instrucțiunii sunt numere. Codul mașină este executat direct de calculator, fiind foarte greu de scris și înțeles de către om. Cu toate că s-au făcut diverse încercări de elaborare a unui limbaj de programare universal, care să satisfacă cerințe spre orice scop, diversitatea limbajelor de programare a crescut datorită pe de o parte tendinței de specializare resimțită în lumea modernă, pe de altă parte datorită unor interese comerciale ale firmelor producătoare de software. Unele limbaje necesită cunoștințe profunde asupra structurii și funcționării sistemului de calcul și sistemelor de operare, altele pot fi 68


Algoritmică şi programare

folosite de începători, unele limbaje se folosesc la programarea microcontrolerelor (care comandă aparate moderne de jos nivel, în orice domeniu – de la dispozitive în instalații industriale până la aparatură de uz casnic și domestic), altele se folosesc la programarea super-calculatoarelor (cu mii de procesoare funcționând în paralel utilizate în instituții cu regim special, pentru calcul intensiv în mecanica fluidelor sau în controlul traficului de zbor, etc.). Având o abordare pragmatică, un limbaj va fi regăsit în diverse clase ce indică astfel caracteristicile sale. Limbaje de programare uzuale Anterior s-a făcut trimitere către anumite limbaje de programare C, Pascal și Java pentru a exemplifica cele două aspecte importante: descrierea datelor și structurilor de date precum și exprimarea comenzilor elementare (instrucțiuni). Se prezintă mai jos, în fiecare paragraf, câte un limbaj de programare, ordonate după gradul de extindere și frecvența de utilizare de către programatori. 1. C (pronunțat ca în engleză „si”) este dezvoltat de Kernigan şi Ritchie la Bell Laboratories în anul 1972, fiind ulterior limbajului denumit B. C a fost dezvoltat pentru crearea de programe apropiate de mașină (sisteme de operare, drivere) fiind legat de sistemul de operare UNIX. Popularitatea sa cât și standardizarea de către ANSI l-au impus ca limbajul cel mai larg acceptat de programatori. C este un limbaj compilat, cu funcții pentru diferite prelucrări (intrare/ieșire, matematice, etc.) conținute în fișiere biblioteci („library”), ce pot fi accesate din program. Programele C sunt compuse dintr-una sau mai multe funcții definite de programator, C fiind considerat un limbaj structurat. Varianta C++ (dezvoltată de Bjarne Stroustrup) este un limbaj de programare orientat pe obiecte, fiind extins cu directive pentru crearea și manipularea obiectelor. Există alte diverse variante îmbunătățite, ca Visual C++ (cu mecanisme de creare a interfețelor grafice și lucrul în rețea), C# (pronunțat „si șarp”, cu servicii pe Internet în categoria .net – „dot net”). 2. Java (pronunțat „giava”), dezvoltat de firma SUN Microsystems în scopul declarat de a realiza aplicații pentru Internet prin compilator și biblioteci gratuite, este similar limbajului C++ (orientat pe obiecte și instrucțiuni identice) însă compilarea produce un „cod de octeți” (nu cod executabil). Este extins pentru lucrul cu fire de execuție (secțiuni de program ce pot rula independent), tratarea excepțiilor (erori, sau întreruperi), securitate (execuția se face prin intermediul „Java Virtual Machine” care interpretează și controlează „codul de octeți” spre a nu permite acțiuni ostile – de ex. prin acces direct la

69


Algoritmică şi programare

3.

4.

5.

6.

7.

8.

70

resursele sistemului), portabilitate (poate rula pe orice calculator care prezintă „Java Virtual Machine”). JavaScript este un limbaj scriptural care adaugă paginilor web facilități de animație și de interacțiune cu utilizatorul (diferit și folosit mult mai des decât Java pentru programarea părții client web). Este standardizat sub numele de ECMAscript. HTML (Hyper Text Markup Language) este un limbaj scriptural de descriere a documentelor, folosind marcaje ce specifică acțiuni de formatare înscrise chiar în textul (conținutul) documentului. Paginile Web sunt descrise prin acest limbaj, permit în plus legături cu alte pagini distribuite spațial pe alte mașini (site-uri); astfel se poate considera că textul documentului nu mai este înscris pe o foaie cu două dimensiuni ci prezintă și adâncime – către alte texte (este hipertext). Informațiile din pagină, conținând text și imagini, sunt descărcate („download”) și afișate pe mașina utilizatorului. În aceste pagini, acțiunile de formatare privesc modul de scriere a textului (litere îngroșate, cursive, etc.), structura documentului (denumiri de modul, paragrafe, tabele) și interacțiunea cu utilizatorul (de exemplu prin formulare ce permit transferul de date către site – „upload”). XML (eXtensible Markup Language) este o extensie a limbajelor din familia SGML, în care marcajele nu mai sunt predefinite și stricte ci pot fi definite chiar de programator pentru a descrie diverse tipuri de date dar și prelucrări. Este utilizat în principal pentru partajarea informațiilor și textelor structurate în Internet. SQL (Structured Query Language) este creat de firma IBM și adoptat ca standard de ISO (International Standards Organization). SQL (pronunțat „sicuăl”) este un limbaj de programare declarativ pentru baze de date relaționale, oferind exprimări simple și intuitive de interogare precum și de manipulare a tabelelor, formularelor și rapoartelor în aplicații cu baze de date. PERL (Practical Extraction and Report Language) este un limbaj scriptural creat de Larry Wall ca o combinație între limbaj de comandă (v. Bourne Shell) și C, pentru extragerea informației din texte și generarea de rapoarte. Este folosit în special pentru generare pagini HTML, pentru programe de lucru pe servere Web (programe CGI), pentru animație sau interacțiune cu utilizatorul prin formulare în pagini web. PHP este un limbaj scriptural similar cu PERL, ce poate fi înglobat în codul HTML al paginilor Web. Este folosit pentru aplicațiile pe partea server web, pentru construirea paginilor web dintr-o bază de date SQL (Oracle, MySQL). Prezintă una din cele mai mari


Algoritmică şi programare

biblioteci „open-source” (cod public și liber de a fi utilizat, modificat sau distribuit). 9. Asamblare este limbajul mașinii într-o notație inteligibilă omului. La compilarea unui program scris în limbaj de asamblare, conversia se face direct în coduri binare, fiindcă instrucțiunile limbajului se referă la acțiuni elementare ale procesorului (încărcare de regiștrii, salturi condiționate, operații pe bit). Este utilizat pentru scrierea programelor de control direct al perifericelor sau pentru operații legate de echipament. 10. Bourne Shell este un limbaj scriptural care permite crearea de fișiere de comenzi (în loturi - „batch”) pentru sistemul de operare UNIX (alte limbaje similare sunt sh, bash, ksh, csh). În general, orice sistem de operare prezintă limbajele de comandă (limbaje shell), necesare lucrului imediat cu calculatorul, comenzile fiind executate de un „interpretor de comenzi” furnizat cu sistemul de operare. Utilizarea unui limbaj de programare depinde de scopul și tipul programării (indicate succint în inventarul de mai sus) dar și de obișnuința sau preferințele programatorului. Clasificări ale limbajelor de programare O primă clasificare a limbajelor se poate face după paradigma de programare – adică după ideea generală de soluționare a problemei. Astfel, se deosebesc: a. Programarea procedurală – privește datele și prelucrările ca entități distincte (declarate separat) și folosește conceptele de modul pentru prelucrare și orizont de vizibilitate pentru variabile. Un modul este format din unul sau mai multe subprograme iar o variabilă (dată declarată de anumit tip) este vizibilă (accesibilă) în cadrul unui modul dar nu și din afara sa; modulele pot fi preluate din biblioteci de subprograme predefinite (prefabricate de producătorul compilatorului). Programarea procedurală este realizată prin limbajele C, Pascal, Delphi, FORTRAN, COBOL. b. Programarea imperativă – în care se furnizează calculatorului o listă de instrucțiuni și un context de memorie (considerat drept starea programului la un moment dat) care este modificat printr-o comandă în alt context (altă stare, cu alte valori de variabile în memorie). Funcționarea procesorului este de fapt imperativă, el urmărind pas cu pas lista de instrucțiuni din program. Limbajele de programare uzuale sunt imperative (FORTRAN, C, Perl), cele obiectuale (C++, Java) adăugând doar noi facilități de lucru.

71


Algoritmică şi programare

c. Programarea declarativă – diferă de cea imperativă prin faptul că în program se descrie pentru calculator un set de condiții, lăsând apoi calculatorului sarcina să le satisfacă. În programarea declarativă, se descriu relații între variabile în termeni de „reguli de inferență” (reguli de obținere a unor noi valori din cele existente). Calculatorul (înțelegând aici un program complex de tipul unui motor de inferență sau unui mediu de baze de date ce rulează pe calculator) aplică un algoritm fixat și interpretează relațiile spre a produce un rezultat. Limbaje uzuale din categoria declarativă sunt Prolog și SQL. d. Programarea funcțională – consideră prelucrarea drept evaluarea funcțiilor matematice. Expresiile, în această paradigmă, sunt formate din funcții ce combină valori iar execuția nu implică o secvență de comenzi fiindcă programul definește CE și nu CUM se prelucrează datele. De fapt limbajele de programare nu pot fi pur funcționale pentru că rulează pe o mașină în paradigmă imperativă; se poate aminti LISP ca limbaj funcțional, în care prelucrările sunt structurate similar structurării datelor (de obicei în liste). O clasificare a limbajelor de programare des întâlnită, consideră nivelul limbajului, relativ apropiat (sau depărtat) de formularea limbajului mașinii (mai precis al procesorului, ca dispozitiv electronic, binar). Nivelul limbajului jos

mediu

înalt – compilat

înalt - scriptural

foarte înalt

Caracteristici ale limbajului Instrucțiunile sunt apropiate de limbajul mașinii, fiind translatate direct în instrucțiuni mașină de către asamblor (un compilator simplu) Instrucțiunile sunt transpuse în limbaj de asamblare prin compilator, oferind în plus biblioteci și servicii de configurare a resurselor mașinii la execuție Instrucțiunile sunt transpuse într-un cod intermediar folosind un interpretor, permițând astfel controlul codului și portabilitatea sa pe orice mașină Instrucțiunile pe linii de program sunt interpretate și executate fiecare în parte; liniile se pot grupa în loturi și executate ca „fișier (text) de comandă” Structura limbajului este apropiată limbajului uman, descrie o metodă de implementare; sunt limbaje declarative sau pentru Inteligență Artificială

Exemple uzuale Asamblare

C, BASIC, Pascal, COBOL

Java, Visual Basic, Visual C

Tabel 4 Clasificarea limbajelor de programare după nivel

72

Bourne Shell, HTML, Perl,

SQL


Algoritmică şi programare

În Tabel 4 se prezintă succint clasificarea după nivel a limbajelor, care însă trebuie înțeles ca fiind din ce în ce mai apropiat de nivelul uman, deci mai simplu pentru om, nu mai dificil. O a treia clasificare se poate face după modul de declarare a tipurilor de date, în limbaje cu: • tipuri statice de date – tipurile declarate sunt stricte și verificate de compilator (exemple C, C++, Java, Pascal), • tipuri dinamice de date – în care datele de tipuri diferite pot fi interschimbate, interpretorul nesemnalând eroare la o dată nouă cu tip nedeclarat (exemple Lisp, JavaScript, Prolog). Compilatoare și interpretoare Programele în limbaj înalt sunt relativ ușor de înțeles de către om, dar nu pot fi executate direct de către calculator. Există două modalități principale în care un astfel de program poate fi pus în execuție: prin compilare sau interpretare. Astfel, limbajele de programare permit realizarea codului ce va fi executat după traducerea sa în limbajul mașină prin: • Compilare –traducerea are loc pentru întregul set de comenzi (descrise ca un tot unitar, într-un „program”). Limbaje din această categorie sunt C, C++, Java, Pascal, BASIC, FORTRAN, COBOL. • Interpretare – traducerea are loc linie cu linie (câte o comandă la un moment dat), de la prima până la ultima din setul dat. Limbaje din această categorie se numesc limbaje scripturale, iar textul cu comenzile se numește script, „fișier de comenzi” sau „lot de comenzi” (în engleză „batch”). Exemple de limbaje script sunt HTML, Perl, PHP, limbaje de comandă ale sistemului de operare („shell” – Bourne Shell, bash, csh). Compilarea este traducerea (translatarea) unui program în limbaj de nivel înalt, numit program sursă, într-un program în cod mașină, numit program obiect.

Compilatorul este un program care se execută pe calculator. El primește ca date de intrare programul sursă și produce ca date de ieșire

73


Algoritmică şi programare

programul obiect, în cod mașină (binar). Acesta se stochează pe un suport extern (pe disc) și poate fi ulterior executat. Interpretorul este un program care execută direct programul sursă scris în limbaj de nivel înalt. Executarea se face instrucțiune cu instrucțiune. Se citește o instrucțiune din programul sursă, aceasta este translatată în cod mașină care este transmis spre execuție procesorului, apoi se trece la instrucțiunea următoare din programul sursă și se repetă ciclul. Dezavantaj: execuția este mai lentă decât la programele compilate, deoarece se consumă timp pentru interpretare. Un avantaj important al programelor scrise în limbaj înalt este că ele pot fi executate, cel puțin în principiu, pe orice tip de calculator. Întrucât, codul mașină diferă de la un tip de calculator la altul, este necesar câte un compilator sau un interpretor al limbajului respectiv pentru fiecare tip de calculator pe care se intenționează să fie executat programul. Se spune că programul sursă este portabil, dar nu și programul obiect. Unele limbaje de programare, cum sunt Fortran, Cobol, Pascal, C, C++ și altele, sunt compilate. Avantaj: executarea programului obiect se face cu viteza mare, respectiv cu viteza de lucru a procesorului calculatorului pe care se execută. Alte limbaje, cum sunt Basic, Python, Pearl, Ruby și altele sunt interpretate. Avantajul este că pot fi utilizate în regim interactiv: imediat ce utilizatorul a introdus o instrucțiune de la consolă, ea poate fi executată de către interpretor. Există limbaje care pot fi executate în ambele moduri dar, de obicei, au specific doar unul din ele. Limbajele script pot coexista pe aceeași mașină; astfel, pentru selecția tipului de limbaj efectiv utilizat, prima linie din lotul de comenzi script conține o directivă a sistemului de operare ce indică în clar limbajul la care se referă fișierul de comenzi. Avantajul compilatoarelor este acela că programul în cod executabil poate fi rulat direct pe mașină, imediat după încărcarea lui în memorie de pe suportul extern (disc). Un caz special îl constituie limbajul Java, care este interpretat după ce este „compilat” în așa-numitul „cod de octeți” numit bytecode, care este apoi interpretat pe orice mașină ce prezintă mașina virtuală Java („Java Virtual Machine”). Acest program este executat de către un interpretor, care convertește instrucțiunile mașinii virtuale Java în instrucțiuni ale procesorului calculatorului pe care se execută programul. Avantaj: bytecode-ul poate fi executat pe orice calculator pe care există JVM (portabilitate).

74


Algoritmică şi programare

Sintaxa si semantica limbajelor de programare Fiecare limbaj de programare, ca orice alt limbaj, se caracterizează prin anumite reguli de scriere corectă, numite reguli de sintaxă. Orice instrucțiune a limbajului are o formă și o semnificație. Sintaxa se referă numai la forma instrucțiunii, in timp ce semnificația este de domeniul semanticii. Semantica se referă la modul in care trebuie interpretată instrucțiunea respectivă. Este evident că, atunci când se concepe un program, este necesar să se acorde atenție atât sintacticii, cât si semanticii. Dacă într-un program nu sunt respectate regulile de sintaxă, compilatorul sau interpretorul sesizează anomaliile și le semnalează sub forma unor mesaje de erori de sintaxă. În astfel de situații, codul de octeți al programului respectiv nu va mai fi generat. Programatorul trebuie sa remedieze erorile de sintaxă semnalate și să ceară repetarea compilării sau, în cazul regimului interactiv, să reintroducă instrucțiunea pentru interpretare. Acest procedeu se repetă, până când nu vor mai fi constatate erori de sintaxă. Insistăm asupra faptului că la compilare sau interpretare sunt semnalate numai erorile de sintaxă. Dacă un program este corect sintactic, dar este conceput greșit, va fi executat de către calculator, dar rezultatul obținut nu va fi cel scontat. Corectitudinea conceperii programului este în întregime responsabilitatea programatorului. Ingineria programării Termenul „inginerie” duce cu gândul imediat la tehnică și industrie. Așa cum termenul „tehnologie” din acronimul TIC nu se referă la tehnologii în industria metalurgică ci doar la suma de tehnici și mijloace din informatică, termenul „inginerie a programării” se referă la tehnică drept abordare sistematică, de producție eficientă a programelor pe calculator (în mod industrial), nu la un domeniu ingineresc (metalurgie spre exemplu). De fapt, etapele de realizare a programelor (prezentate) constituie esența acestei abordări sistematice. Ca și la alte produse, calitatea produselor program (a software-ului) nu este doar un deziderat de piață ci și un scop impus de toleranțele în care produsul trebuie să-și realizeze utilitățile. Astfel, în timp ce pentru o mașină de spălat automată toleranța de 2% în alimentarea cu detergent este admisă (și considerată foarte bună), un sistem de contabilitate care are erori de 2% este inacceptabil. De aceea, sunt necesare și pentru software modalități de măsurare (metrici) și evaluare a performanțelor și de aici a calității produselor program. Ingineria programării este abordarea sistematică, disciplinată și cuantificată a dezvoltării, operării și întreținerii produselor program.

75


Algoritmică şi programare

Definiția de mai sus subliniază că această abordare este o aplicare a ingineriei în programare (de aici și numele): aplicând tehnicile și practicile de inginerie a programării se creează programe de bună calitate, cu mare productivitate, cu întreținere și dezvoltare facile. Dezideratul ingineriei programării este satisfacerea cerințelor clientului produsului software, respectând restricții de calitate, cost și durată, prin: i) utilizarea unei metodologii clare începând cu faza de analiză, apoi cea de proiectare și de implementare a programelor; ii) conducerea activităților, desfășurate pe parcursul proiectul, conform unor principii de management stricte („project management”); iii) analiza și evaluarea performanțelor codului, a structurii de module, funcționării și utilizării produsului; iv) documentarea clară și completă a funcționării și utilizării produsului software; v) urmărirea produsului livrat la beneficiari și pe întreaga durată a ciclului său de viață, pentru actualizarea cu versiunile noi și îmbunătățite. Factori de performanta ale produselor software sunt: funcționalitatea (în ce măsură produsul îndeplinește funcțiile propuse), ușurința în utilizare (simplitatea de învățare și operare), fiabilitatea (funcționarea corectă și robustă – tolerantă la operare sau date greșite), eficienta (privind resurse utilizate – memorie putina, viteză de execuție), flexibilitate (ușurința de adaptare și modificare după cerințe), portabilitate (posibilitatea de transfer pe alte mașini și sisteme de operare, respectarea standardelor), întreținerea comodă (acces la cod sursă și compilare, modificare ușoară) Ingineria programării oferă metrici (adică sisteme de măsurare) de evaluare pentru: • produs – privind proiectarea, codul sursă și cazurile de test ale programului; • procesul de dezvoltare – privind activitățile de analiză, proiectare și programare; • persoanele implicate în proiect – privind eficienta individuală a proiectanților, programatorilor și testărilor. Pentru aplicații mari, organizarea proiectului implică un șef de proiect (contribuie la proiectare și dezvoltare în proporție de 30%, distribuie sarcini și coordonează coechipierii), adjunct (planifică și coordonează programarea și testele, asigură calitatea produsului), secretar de proiect (execută sarcini

76


Algoritmică şi programare

administrative privind protocoale cu beneficiarul, biblioteci, gestiunea termenelor și costurilor), programatori / dezvoltatori (specialiști în medii și limbaje de programare sau în instrumente de dezvoltare software). Analiza, proiectarea și implementarea respectă o metodologie specifică (uzual din categoriile „structurată” sau „obiectuală”), iar testarea se execută atât în condiții de laborator cât și pe cazuri reale. Întreținerea aplicației este foarte importantă, așa cum reiese din proporția uzuală a costurilor pentru software: Analiza – 10%, Proiectarea – 10%, Implementarea – 10%, Testarea – 20%, Întreținerea – 50%. O importantă deosebită o are documentația, care trebuie întocmită pe parcursul proiectului la fiecare etapă. Documentația se adresează dezvoltatorilor, utilizatorilor și personalului de întreținere. Documentația ce trebuie întocmită a fost deja amintită la fazele ciclului de viață ale programului. De remarcat că programele trebuie amplu documentate (comentate) chiar în codul sursă, unde algoritmul trebuie explicat. Tehnici și instrumente de realizare a programelor Istoric, în evoluția proiectării aplicațiilor și a programării, au existat următoarele abordări – din care au rezultat metodologii specifice: i) Proiectarea/programarea nestructurată – în care aplicația nu este gândită și realizată modular ci ca un tot, cu salturi interne greu de controlat și urmărit. Dezvoltarea și întreținerea programelor este extrem de ineficientă și greoaie. Această abordare se poate compara cu generația televizoarelor cu tuburi electronice („lămpi”) și componente interconectate prin sârme (formând uneori „ghemuri”). ii) Proiectarea/programarea structurată – în care aplicația este divizată în module specializate pentru anumite operații, asamblate apoi (mai precis apelate) de către aplicația „principală”. Abordarea se poate compara cu generația televizoarelor realizate pe module specializate (modul alimentare, selectare canale, modul sunet, etc.) montate pe placa de bază. Avantajele provin din faptul că pentru depanarea sau modificarea unui modul se lucrează numai cu acesta, nu cu întreg aparatul (respectiv întreaga aplicație). În programare se elimină salturile necondiționate, deoarece, chiar din etapa de proiectare, prelucrările sunt ierarhizate astfel încât un modul să „apeleze” un alt modul specializat pentru o acțiune anume. La aceste abordări datele sunt analizate separat de prelucrări. În cazul ii) modulele sunt realizate prin subprograme (funcții) iar datele sunt declarate separat de prelucrări. Declararea datelor se face prin exprimări

77


Algoritmică şi programare

conform celor menționate anterior, acestea fiind considerate „date de intrare” pentru funcțiile care le prelucrează. Proiectarea se bazează pe o parcurgere „top-down” (de la mare la mic) a problemei, pornind de la ansamblu și apoi trecând la părți, module; pentru fiecare din acestea se discriminează datele și prelucrările corespunzătoare. iii) Proiectarea/programarea orientată obiect – în care aplicația se construiește din obiecte care încapsulează proprietăți și metode – adică informații (date) și prelucrări (operații asupra datelor). În acest mod, în etapele de analiză și proiectare se concep clasele de obiecte similare celor din lumea reală a problemei de rezolvat, cu „modul lor de utilizare”, iar rezolvarea problemei se face prin manipularea obiectelor create la implementare. Abordarea se poate compara cu viziunea utilizatorului de televizoare, în care clasa de obiecte „televizor” trebuie să dețină ecran și legătură prin cablu (ca informații - date) și butoane de acționare pornit/oprit, reglaj volum, comutare canale, etc. Evident, rămâne în sarcina producătorului să realizeze linia de fabricație a clasei de televizoare (acesta este programatorul) și apoi magazinului să vândă utilizatorului un televizor anume (acesta este declararea unui obiect a de tip „televizor”, similar cu declararea unui tip de dată inițializată). iv) Proiectarea/programarea cu componente – în care aplicația se construiește prin componente gata fabricate. Abordarea este similară construirii televizorului din circuite integrate specializate, care doar se asamblează în modul dorit spre a produce un televizor CRT, TFT sau LED, SMART sau clasic, etc. Similar cu primirea componentelor fizice vin de la fabrică, componentele software vin de la producători/ dezvoltatori de software și sunt utilizate de programatori pentru a crea produsul dorit. La ultimele două abordări, proiectarea decurge „bottom-up” (de la mic la mare) adică se face inventarul de obiecte sau componente aflate la dispoziție și apoi se „construiesc” aplicații prin asamblarea acestora. Datele și programele nu mai au o delimitare evidentă: pentru programator datele pot deveni programe și programele – date. Continuând similitudinea cu producția de televizoare, generația iii) reprezintă crearea de televizoare artizanale – fiecare producător realizează televizoare în tehnologie proprie, pe când cu iv) producătorii folosesc componente standard pentru părți de televizor, oferind înfățișare și performante speciale produselor proprii fată de ale altor producători. Ultimele două generații permit și stimulează industria software, fiindcă proiectantul și programatorul nu mai sunt 78


Algoritmică şi programare

implicați în atâtea amănunte de lucru la fiecare program în parte (amănunte pe care nu le pot stăpâni perfect și nici nu au productivitate dacă sunt multe sau necesită mulți coechipieri) ci se pot orienta pe producție, pe nevoile clienților și/sau pe cererea pieței. Proiectantul și programatorul au „în spate” o industrie de componente pe care trebuie doar să știe să le asambleze pentru a face un produs la comandă. v) Proiectarea/programarea cu agenți – în care aplicația (ca agent) evoluează de sine stătător și comunică cu alte aplicații (alți agenți) pentru a-și îndeplini misiunea. Spre exemplu, există programe agent de căutare care pot „naviga” prin Internet, se pot ”stabili” la anumite site-uri și comunica cu alți agenți, scopul lor fiind acela de a găsi și transmite informațiile pentru care au fost creați și lansați în spațiul cibernetic. În timp ce pentru primele patru generații aplicația se lansează și se executa prin operarea programului direct de către om– adică inițiativa aparține omului, generația v) introduce programe cu inițiativă, adică programe care după lansare au existență și acțiuni independente de omul în contul căruia execută prelucrările. Programul-agent se poate multiplica, comunică cu alți agenți, precum și cu „baza”, formează grupuri și chiar stabilește relații sociale și limbaje de comunicare între agenți; se poate spune că din „obiecte” informatice aceste tipuri de date devin „ființe” informatice. La inventarul abordărilor prezentate mai sus pentru proiectarea aplicațiilor, este necesar să se adauge încă două – care nu aduc metodologii conceptual noi ci doar specifice unor instrumente frecvent folosite în realizarea aplicațiilor: vi) Proiectarea aplicațiilor cu Baze de Date – în care datele se structurează în tabele, adică mulțimi de articole. De fapt, un articol (ca linie din tabel) reprezintă un obiect iar tabelul colecția de obiecte de același fel din problema de rezolvat – adică tabelul reprezintă o entitate (o categorie conceptuală de obiecte). Structura de entități cu relații între ele formează modelul problemei de rezolvat, iar aplicația manipulează datele din tabele cu comenzi specifice acestei reprezentări. Metodologiile de proiectare a aplicațiilor cu baze de date urmează etapei de analiză, iar proiectarea datelor și prelucrărilor se realizează separat – datele ca tabele iar prelucrările ca operații cu acestea, vizând direct instrumentele software cu care se vor realiza aplicațiile. vii) Proiectarea aplicațiilor Web (servicii Internet) – în care datele sunt, de obicei, pagini cu informații ce trebuie 79


Algoritmică şi programare

vizionate de utilizatori prin Internet sau datele provin de la utilizatori prin formulare completate de către aceștia, iar prelucrările sunt operații de navigare, afișare și actualizare a datelor (stocate adesea în baze de date). Proiectarea aplicațiilor Web se bazează pe arhitectura Client-Server, pe principii de marketing, impact estetic și emoțional, precum și pe utilizarea sistematică a instrumentelor de proiectare și editare de pagini web (cu imagini, formulare, animații și hiper-legături) Ultimele două abordări de proiectare sunt preferate de către multi producători de produse software (firme, profesioniști în informatică sau chiar practicieni în informatică proveniți din alte domenii) pentru că cele mai multe aplicații privesc două activități umane foarte frecvente: • gestiunea de resurse – fie ele bunuri de consum sau industriale, resurse umane sau financiare, mijloace de transport sau documente, care se pot stoca și manipula folosind baze de date; • comunicarea informațiilor și interacțiunea prin Internet între persoane, între firme și persoane (B2C – „Business to Client”) sau între firme (B2B – „Business to Business”) – pentru activități economice, sociale, administrative sau educaționale. Din aceste abordări au rezultat tehnici și instrumente specifice de programare și de realizare a aplicațiilor, cu influențe în toate etapele ciclului de viață ale unui program. Caracteristici ale programării orientate obiect Fiindcă cea mai mare parte a aplicațiilor actuale folosesc conceptul de obiect și sunt realizate obiectual, se vor prezenta în continuare caracteristici ale programării obiectuale, pentru familiarizarea cu termenii și abordările întâlnite în submodulele următoare. Programarea orientată obiect (POO) se bazează pe clase, ca abstractizări ce reunesc date și prelucrări posibile asupra lor. Un obiect realizat (instanţiat) într-o clasă dată, prezintă anumite valori pentru date (identificate ca proprietăți ale obiectului) și o anume comportare (identificată prin metode de modificare a proprietăților). POO vizează, în principal, următoarele aspecte: • Crearea și manipularea de obiecte – prin care se modularizează acțiunile programului încă din faza de analiză, atunci când se identifică obiectele în problema reală; • Refolosirea codului – prin care obiecte odată codificate se pot reutiliza ori de câte ori este necesar, fiind grupate în colecții (denumite biblioteci sau pachete).

80


Algoritmică şi programare

Aceste deziderate se obțin ca urmare a caracteristicilor programării obiectuale, cele mai importante fiind: a. Abstractizarea – prin care un obiect devine modelul unui „actor” ce prezintă o stare (și o poate modifica), execută acțiuni sau comunică cu alte obiecte din sistem. b. Încapsularea – prin care accesul la proprietățile obiectului se poate face numai prin metodele definite. Obiectul prezintă o interfață către alte obiecte, prin care se specifică modalitățile sale de manipulare. c. Moștenirea – prin care o clasă de obiecte poate fi baza altor clase (denumite clase derivate), proprietăți și metode esențiale ale primei fiind preluate în întregime de celelalte. Se realizează astfel specializarea claselor (și obiectelor). d. Polimorfismul – prin care o metodă a unui obiect din clasă derivată produce o comportare diferită față de cea a clasei de bază. Tipuri și structuri de aplicații Structura unui program a fost prezentată, indicând părțile specifice ale programului principal, pentru un limbaj de programare comun (procedural și cu tipuri statice de date) cum sunt C, Pascal sau Java. În sine, o aplicație cuprinde un program principal care are rol de „dispecer” pentru prelucrările efective ale aplicației. Lansarea aplicației se realizează la inițiativa utilizatorului, care înscrie o comandă (în forma text – linie comandă) sau accesează o pictogramă într-o interfață grafică. Execuția aplicației începe, în general, cu prezentarea unei interfețe de interacțiune cu omul (printr-un interpretor de comenzi sau meniu). De obicei, o aplicație este un program compilat și stocat în forma executabilă, fiind lansat (adică încărcat în memoria de lucru și executat pe întreg lotul de instrucțiuni) la inițiativa utilizatorului. Structura generică a unei aplicații cuprinde două părți generice: colecțiile de date („data” - adică valori cu care se lucrează) și logica de prelucrare („business logic” - adică acțiunile asupra datelor). De exemplu, o aplicație bancară conține o parte privitoare la conturi și valorile lor, o parte privitoare la operațiuni de transfer între conturi. Aplicații de Baze de Date O categorie specială de aplicații sunt create și funcționează prin intermediul Sistemelor de Gestiune a Bazelor de Date. Aceste aplicații organizează datele în structuri de tip articol care apoi sunt grupate în „tabele” ce pot fi stocate ca fișiere (pe suport extern – disc). Pentru prelucrarea datelor, se folosesc programe scrise în limbaje proprii SGBD

81


Algoritmică şi programare

sau în limbajul standard SQL. Conceptual, o aplicație cu baze de date manipulează „obiecte” de tipurile: • Tabel – structura pe linii (articole) și coloane (câmpuri ale articolelor) ce stochează datele, cu mijloace de modificare și control a valorilor acestora; • Interogare – exprimarea cererii de extragere, după condiții impuse, a unui set de date din unul sau mai multe tabele, folosind un limbaj (de ex. SQL) sau o grilă (un tabel generic prin care cererea este ilustrată cu un exemplu); • Raport – document text generat automat, conținând date sintetice și analitice obținute din tabele sau interogări, prezentate în mod profesionist (cu antet, subsol, etc.); • Formular – machetă de încărcare și consultare a datelor, pentru interacțiunea comodă cu utilizatorul (în mod grafic, prin câmpuri și indicații pe loc); • Modul – secțiune de program (scris în limbajul de manipulare a datelor) care realizează prelucrări complexe, eventual combinații de interogări, formulare și rapoarte generate conform logicii de prelucrare a aplicației Programele sunt interpretate sau compilate în cod intermediar, astfel că execuția lor nu poate avea loc decât în prezenta mediului de baze de date (SGBD) care este rezident în memorie și coordonează toate acțiunile din program. O bază de date este constituită din mai multe tabele, cu legături între ele, pe lângă care se prevăd module de prelucrare specifice aplicației vizate. Cele mai uzuale aplicații cu baze de date privesc gestiunea resurselor într-un domeniu dat: contabil, financiar-bancar, mijloace fixe, stocuri (magazii sau magazine), sisteme de vânzări, resurse umane. Multe aplicații în Internet au în fundal un server de baze de date (mașinile de căutare, magazine virtuale, etc.). Aplicații client-server Există aplicații în rețele de calculatoare care trebuie să asigure transferul de date și prelucrarea acestora la distanță. Datele sunt stocate la un punct central, pe un calculator care le gestionează printr-un program denumit server pentru că oferă servicii (de acces și prelucrare date) la distanță, rulând în permanență și așteptând cereri de la utilizatori. Pe mașina locală a fiecărui utilizator, există un program denumit client prin intermediul căruia utilizatorul poate solicita servicii serverului distant; datele „se descarcă” („download”) de la server pe mașina utilizatorului, unde sunt prelucrate local și afișate (prin interfața utilizator) de către programul client.

82


Algoritmică şi programare

Structura de aplicație cu două entități este denumită, în jargonul informatic, 2-tier.

Fig. 13 Arhitectura Client-Server 2-tier (cu două părți)

Primele aplicații în rețea realizau toate prelucrările pe o mașină centrală (numită gazdă) iar mașina locală era folosită doar pentru afișare text și preluarea datelor de la utilizator (terminal), adică aveau „o parte” 1tier. Avantajele modului de lucru 2-tier provin din faptul că datele pot fi gestionate și asigurate mai bine într-un singur punct (nu distribuite în mai multe puncte) dar prelucrările nu încarcă doar mașina centrală ci și mașinile locale (ele fiind mai multe și încărcate temporar). În această structură, partea client conține programele de prelucrare locală și de prezentare a rezultatelor către utilizator, complementar părții server – care asigură prelucrări de acces și transfer a datelor centralizate. Cele două piese software, server și client, conlucrează și comunică prin intermediul unui protocol (ca un limbaj cu set de reguli pentru formularea și servirea cererilor), iar comunicația fizică se realizează prin infrastructura de comunicație (rețea de calculatoare). Un exemplu uzual de aplicație client-server este WWW, în care partea server găzduiește paginile web (și programele de interacțiune cu utilizatorul) iar partea client o constituie navigatorul Internet („browser” ca MS Internet Explorer, Mozilla Firefox, Safari sau Google Chrome). Protocolul de comunicație între cele două piese software server și client este HTTP (HyperText Transfer Protocol) sau HTTPS (HyperText Transfer Protocol Secure) și apare indicat chiar în adresa de acces (URL) a serverului. Varianta în care partea server conține pe aceeași mașină si logica de prelucrare și datele este ilustrată în figura de mai sus și are două părți (denumită de aceea arhitectură 2-tier). Un dezavantaj al acestei arhitecturi este siguranța precară a datelor în cazul când un utilizator rău intenționat, având acces la mașina server, poate depăși mijloacele de verificare efectuate de logica de prelucrare centrală (programul server) și are acces direct la datele stocate pe această mașină. Pentru creșterea siguranței accesului la date, este indicat ca partea server să fie separată în alte două părți: una care să conțină logica de prelucrare (SERVER-P) și este legată direct cu clienții, alta să conțină datele (SERVER-D) și la care are acces doar logica de prelucrare centrală – așa

83


Algoritmică şi programare

cum indică figura de mai jos. În acest fel, după accesul utilizatorului la mașina SERVER-P, logica de prelucrare centrală verifică autenticitatea și drepturile utilizatorului, apoi – doar prin intermediul programului server, oferă acces la datele aflate pe una sau mai multe servere cu date (SERVERD 1 , ... SERVER-D n-2 ). Arhitecturile, în acest caz, sunt de tip 3-tier (dacă există doar un singur server SERVER-D 1 ), respectiv n-tier (dacă există mai multe servere SERVER-D 1 ... SERVER-D n-2 ). Aplicații de baze de date pot funcționa în arhitectura client-server 2-tier. În forma 3-tier funcționează aplicații distribuite – cum ar fi de exemplu o aplicație de vânzări de acțiuni, în care datele despre cursul acțiunilor, apoi știrile despre acționari sau companii, respectiv tranzacțiile se găsesc pe mașini diferite.

Fig. 14 Arhitectura Client-Server 3-tier și n-tier

Aplicațiile în arhitectura client-server sunt cele mai răspândite în Internet astăzi, prezentând și forme în care cele două părți sunt simetrice: serverul poate fi client și reciproc, în scopul comunicării bidirecționale de date stocate pe cele două (sau mai multe) mașini (v. peer-to-peer). Miniaplicaţii O categorie specială de program o constituia miniaplicația („applet”) – ca program ce nu există de sine stătător ci doar în cadrul unei (alte) aplicații – de exemplu o aplicație web cu pagini conținând text și imagini. Miniaplicația „applet” poate rula doar când este lansată de un eveniment extern – click pe o imagine, producând spre exemplu animația unui obiect pe ecran. Miniaplicația „applet”, ca program, este descărcată de pe mașina server pe mașina client și rulează (de obicei în mod interpretat) pe clientul web (navigatorul Internet). Miniaplicația „applet” este un program script (adică scris într-un limbaj scriptural) și interpretat linie cu line din textul 84


Algoritmică şi programare

sursă, acțiunile sale fiind astfel permanent controlate; din motive de securitate. Miniaplicația „applet” nu are acces la sistemul de fișiere al mașinii client iar comunicația o poate realiza doar cu mașina server de pe care provine. Complementar miniaplicațiilor „applet” există „servlet”, ce rulează pe mașina server spre a asigura: acces securizat la baze de date, facilități pe mașini de căutare în Internet, generarea de pagini web dinamice. Și acest tip de program se scrie în limbaj scriptural și rulează interpretat, în cadrul unei aplicații gazdă (de exemplu serverul web). Instrumente software de dezvoltare a aplicațiilor În accepția comună, clasică, programarea constă în scrierea textului sursă într-un limbaj de programare ales, prin care se descrie un algoritm de prelucrarea a datelor. Această activitate este foarte laborioasă și necesită dese reveniri în scrierea textului (pentru corectura sintactică, pentru optimizare sau chiar refacerea codului). Ingineria programării indică metode și pași sistematici prin care se reduc la minim erorile în soluția problemei și în realizarea codului. Totuși, pentru a obține o productivitate rezonabilă în munca de programare sunt necesare instrumente software de asistare a programatorului. Cele mai importante categorii de instrumente sunt prezentate în cele ce urmează. Interfețe, biblioteci și pachete de programare Limbajele de programare actuale tind să fie cât mai concise – privind cuvintele cheie și comenzile de prelucrare sau control de flux (atribuire respectiv repetiție și decizie). O mare parte din prelucrări sunt implementate ca subprograme sau pachete de funcții și formează biblioteci. Programatorul trebuie să cunoască aceste biblioteci, cu inventarul lor și modul de utilizare a subprogramelor aferente, pe care le apelează în programul sursă; la faza de editare a legăturilor („linking”) ele se includ în codul obiect al programului apelant într-una din modalitățile: • Legătură statică – codul din bibliotecă este inclus în program la compilare și formează codul executabil, care poate fi apoi rulat independent de bibliotecă; • Legătură dinamică – codul din bibliotecă face parte din sistemul de operare și este inclus în codul executabil la încărcarea sa în memorie („loadtime” - adică la lansarea programului) sau chiar în timpul execuției („runtime”). Legarea statică a subprogramelor din biblioteci este folosită curent de către mediile de programare C/C++ și Java, în vederea realizării programului executabil. Fișierele bibliotecă conțin module precompilate și sunt parte a compilatorului sau mediului de programare a limbajului,

85


Algoritmică şi programare

furnizat de producător. Bibliotecile sunt denumite la limbajul C „headers”, sunt specializate pe tipuri de date și prelucrări (standard, pentru șiruri de caractere, pentru funcții matematice, etc.) și se includ în programul obiect dacă au fost apelate cu directivă preprocesor #include <…> la începutul programului sursă. În limbajul Java, subprogramele sunt de fapt clase predefinite, grupate în pachete (package) cu o structură arborescentă și incluse în programul sursă prin directiva import. Un tip de bibliotecă foarte utilizat este Interfața de Programare a Aplicațiilor (API), care conține seturi de funcții, proceduri, variabile și structuri de date ce pot fi folosite de către programator în aplicații ca obiecte precompilate, apelate standard. Există API care fac parte din sistemul de operare sau livrate ca pachet independent, API poate fi proprietară (exemplu cea pentru PlayStation a firmei Sony) sau poate fi distribuită gratuit (cum este cea de la Microsoft Windows). Sistemele de operare precum Windows și Linux folosesc legătura dinamică la încărcarea programului executabil („loadtime”). Biblioteca de legătură dinamică este uzual denumită prin acronimul DLL (Dinamic Link Library) la Windows și bibliotecă partajată („shared library”, ELF) la sisteme UNIX. Biblioteca conține module executabile pre-compilate și este stocată pe disc ca fișier separat. Atunci când mai multe aplicații utilizează aceeași bibliotecă dinamică, sistemul de operare o poate încărca pentru fiecare în parte. Un mare dezavantaj al legăturii dinamice este faptul că programele executabile depind de existența fișierelor bibliotecă pe sistemul pe care rulează, execuția aplicației întrerupându-se când aceste fișiere lipsesc, sunt redenumite sau au altă versiune. La unele sisteme de operare legătura dinamică are loc pe durata execuției programului („runtime”), astfel: programul executabil apelează o Interfață de Programare a Aplicațiilor (API „Application Programming Interface”) furnizând numele fișierului bibliotecă, un număr de funcție și parametrii acesteia; sistemul de operare rezolvă importul de fișier (îl încarcă în memorie) și face apelul funcției solicitate, în contul aplicației. Datorită operațiilor suplimentare executate la fiecare apel de funcție, se poate ca execuția programului să fie foarte lentă, deci să afecteze performanța aplicației. De aceea legătura dinamică la momentul execuției nu este foarte utilizată în sistemele de operare actuale. Există diverse alte tipuri de biblioteci, cum sunt: • Biblioteci pentru testare – care includ date de test dezvoltate specific pentru un tip de aplicație anume, cu care se poate verifica comportarea aplicației în situații diverse – înainte de a fi dată în exploatare.

86


Algoritmică şi programare

Biblioteci grafice – care conțin diferite obiecte grafice gata de utilizat la proiecte din diferite domenii. • Biblioteci de programe pentru diferite limbaje (Perl, Java) și diferite utilizări (animații în pagini web, clase și prelucrări uzuale). Trebuie făcută o deosebire clară între „pachete de programe” (sau clase) – ca biblioteci și „pachete software” sau „pachete de aplicații” – ca modalități de distribuție și instalare software achiziționat de la producători, de exemplu pachete pentru aplicații de birou – MS Office al firmei Microsoft, sau Apache OpenOffice care provine din StarOffice al firmei SUN. Dezvoltarea unui program scris într-un limbaj de programare ales, decurge mai ușor dacă toate instrumentele necesare sunt integrate într-unul singur, adică: editorul de text sursă, compilatorul, editorul de legături și chiar depanatorul. Un Mediu de Dezvoltare Integrat (IDE – „Integrated Development Environment”) este un instrument software care oferă aceste facilități, fiind utilizat atât la proiectarea cât și la implementarea aplicației. Întrucât interfața grafică utilizator (GUI – „Graphical User Interface”) este partea de program care necesită cel mai mare efort de programare, acestor medii li s-a adăugat modalitatea de proiectare și implementare vizuală a interfeței grafice. Prin programarea vizuală, se plasează obiecte (de tipul fereastră, buton grafic, casetă de text, etc.) pe o suprafață de lucru – care la final va deveni suprafața de lucru a utilizatorului, în fundal, mediul de programare producând cod prin care rezolvă partea de program ce va construi obiectele interfeței. •

87


Algoritmică şi programare

Mediul de programare RAPTOR RAPTOR (Rapid Algorithmic Prototyping Tool for Ordered Reasoning) este un instrument rapid de prototipizare algoritmică pentru raționament ordonat, dezvoltat de către Martin C. Carlisle, Terry Wilson, Jeff Humphries and Jason Moore, destinat în special pentru programarea vizuală a algoritmilor. Astfel, se pot implementa diverși algoritmi utilizând simbolurile grafice de reprezentare puse la dispoziție de RAPTOR, ca ulterior, mediul de programare vizual să genereze codul algoritmului implementat în diferite limbaje de programare, precum C/C++, Java, etc. Un program dezvoltat în RAPTOR constă așadar dintr-o multitudine de simboluri interconectate prin săgeți unidirecționale, simbolurile reprezentând acțiuni ce trebuie executate iar săgețile stabilind ordinea în care sunt interpretate simbolurile.

Interfața grafică utilizator Interacțiunea om-mașină are atât aspecte tehnice cât și psihologice. Aceste aspecte trebuie luate în considerare pentru a asigura lucrul eficient in cadrul utilizării aplicațiilor pe calculator. Interfața cu utilizatorul reprezintă totalitatea mijloacelor prin care omul interacționează cu mașina, uzual reprezentate de perifericele de intrare – ecran tactil, tastatură și indicator pe ecran (mouse, TrackBall, Touchpad) cu perifericul de ieșire - ecran și imaginile/rezultatele afișate/proiectate.

Fig. 15 Interfața grafica utilizator RAPTOR

88


Algoritmică şi programare

După cum bine știm, orice program se deschide într-o fereastră. O fereastră aplicație este o reprezentare standard a instrumentelor uzuale de lucru cu aplicația. Aceste instrumente sunt reprezentate grafic în diverse moduri și permit acționarea lor cu ajutorul mouse-ului sau al tastaturii. Elementele generice ale mediului de programare vizual RAPTOR, precum și rolul fiecăreia sunt: •

Bara de titlu 1 prezintă numele aplicației, precum și numele documentului;

Bara de meniu 2 cuprinde categorii (opțiuni) accesibile în aplicația dată, din care trei sunt regăsite la orice aplicație: File (manipularea datelor specifice aplicației), Edit (copiere, căutare, ștergere date) și Help (ajutor imediat - structurat pe conținut, index și căutare);

Bara de unelte 3 cuprinde comenzi uzuale ilustrate prin desene intuitive, acționate ca butoane grafice. Comenzi de editare, lucru cu fișiere și cu ferestre au o replică în bara de unelte. Aici găsim totodată, comenzile pentru executarea programului, stabilirea vitezei cu care fie evaluate simbolurile precum și modalitatea de mărire/micșorare a algoritmului din zona de lucru (Zoom);

Panoul de simboluri 4 aflat în partea stânga, imediat sub bara de unelte, conține 6 tipuri de simboluri, fiecare reprezentând un tip unic de instrucțiune. Acestea sunt: Assignment (instrucțiunea de atribuire), Call (apelul de procedura), input , output, Selection (instrucțiunea decizională) și Loop (instrucțiunea repetitivă).

Watch window 5 reprezintă fereastra de evenimente în evaluarea simbolurilor, unde sunt afișate variabilele si valorile lor in timpul rulării programului/algoritmului.

Work space 6 reprezintă zona de lucru unde, cu ajutorul simbolurilor, dezvoltam vizual algoritmul.

Consola Master 7 , reprezintă interfața grafică unde sunt afișate rezultatele (ieșirile din program) precum și numărul de simboluri evaluate.

Structura unui program Conform descrieri anterioare, programul constă dintr-o multitudine de simboluri interconectate prin săgeți unidirecționale, unde: • săgețile determină ordinea în care sunt interpretate simbolurile; • simbolurile reprezentă acțiuni ce trebuie executate;

89


Algoritmică şi programare • • •

lansarea în execuție a unui program începe cu evaluarea simbolului START; execuția programului constă în evaluarea simbolurilor, urmând calea indicată prin săgeți, pornind de la START până la END, inclusiv; execuția unui program se termină atunci când se ajunge la simbolul END.

Fig. 16 Calculul ariei cercului in RAPTOR

Astfel, fiecare algoritm dezvoltat în RAPTOR poate fi vizualizat și urmărit pe tot parcursul executării lui, iar ulterior, convertit în alt limbaj de programare (Ada, C#, C++, etc.) utilizând opțiunea Generate. În dezvoltarea unui algoritm trebuie sa parcurgem trei etape: input, prelucrare/procesare și output. Trebuie specificată clar diferența dintre programator și utilizator în conceperea, implicit utilizarea unui produs program. De aceea, ținând cont de faptul că la execuția programului, utilizatorul introduce doar datele de intrare pentru a vizualiza rezultatele dorite, programatorul trebuie să dezvolte programul de așa manieră încât săi afișeze utilizatorului într-un mod clar si neinterpretabil ce operații trebuie să efectueze, pe tot parcursul execuției programului pentru a preîntâmpina eventualele probleme de percepție/înțelegere ale utilizatorului. Totodată, pentru exemplificare, vom dezvolta algoritmul de calcul pentru aria cercului, ținând cont de etapele descrise mai sus. 90


Algoritmică şi programare

Operații de intrare(input) Pentru problema propusă, avem nevoie de raza cercului, implicit o singură dată de intrare. În informatică exista cel puțin 3 posibilități de a efectua aceeași operație, astfel, pentru inserarea simbolurilor avem următoarele modalități: o drag and drop o

selectarea simbolului din Panoul de simboluri click în Work space inserat simbolul.

o

6

4

, apoi

, pe ramura unde se dorește

în Work space 6 , dreapta click pe ramura unde se dorește inserat simbolul. Din lista derulantă se alege opțiunea dorită.

Fig. 17 Inserarea simbolurilor

Orice simbol inserat în Work space 6 , trebuie editat de așa manieră încât să respecte întocmai algoritmul de rezolvare a problemei, în cazul nostru, calculul ariei cercului.

91


Algoritmică şi programare

Fig. 18 Editare simbol input

În cazul nostru, input-ul este raza cercului. Editarea oricărui simbol se face prin două modalități, dublu click pe simbol sau dreapta click pe simbol si alegerea opțiunii de editare (Edit). Operația de input presupune 2 procese, enunțarea cererii către utilizator, formulată cat mai clar și concis, în zona PROMPT, respectiv declararea variabilei (în câmpul de text variable) care va stoca ceea ce se preia de la tastatură (ce tastează utilizatorul). În cazul nostru îi vom cere utilizatorului să introducă o valoare pentru raza cercului (Enter radius) pe care o salvăm în variabila radius.

Operații de prelucrare procesare Reprezintă operațiile efectuate cu simbolurile Assignment (atribuire), Call (apel procedura/subschemă logică), Selection (decizia) și Loop (repetiția). Aceste operații vor fi detaliate pe larg în secțiunile următoare. Pentru exemplul enunțat anterior, calculam aria cercului, funcție de input, cu ajutorul simbolului de atribuire.

Fig. 19 Editare simbol de atribuire

92


Algoritmică şi programare

Astfel, atribuim variabilei area (câmpul de text SET) valoarea pe care o va avea expresia pi*radius^2 sau pi*radius*radius. Pe măsură ce datele sunt completate, se observă faptul că în zona Auto-complete apar sugestii privind variabilele sau funcțiile existente.

Operații de ieșire (output). Afișarea rezultatelor este o operație destul de laborioasă deoarece implică operații de concatenare ale șirurilor de caractere precum și transformări ale tipurilor de dată.

Fig. 20 Editare simbol output

Funcție de versiunea utilizată, există 2 opțiuni pentru output: text și expression. Opțiunea text afișează șirul de caractere exact cum este tastat în aria de text. Opțiunea expression implică, după cum am enunțat, operații de concatenare pentru șiruri de caractere și variabile utilizate in elaborarea algoritmului de rezolvare a problemei. În exemplul nostru, pentru a afișa aria cercului, trebuie sa concatenam șirul de caractere The area is cu valoarea variabilei area. Pentru concatenare utilizam semnul matematic + aferent operației de adunare. Suplimentar, ținând cont de faptul că sirurile de caractere se scriu intre ghilimele, vom insera in aria de text, aferenta simbolului de output, următoarele: ”The area is ” + area.

Execuția algoritmului/programului Execuția programului se poate face gradual (pas cu pas, prin evaluarea unui singur simbol în cadrul unui pas) sau integral, pornind de la START până la END.

93


Algoritmică şi programare

Fig. 21 Execuția programului/algoritmului

În fiecare moment al execuției programului știm cu exactitate ce simbol este evaluat, lucru indicat de starea simbolului, respectiv culoarea verde deschis. Totodată, în Watch window 5 (fereastra de evenimente în evaluarea simbolurilor) devin vizibile variabilele disponibile. Indiscutabil, algoritmul nostru de calcul a ariei unui cerc poate fi îmbunătățit, totul depinde de performanța si rafinarea dorită. În figura de mai jos, putem observa aceeași problema, rezolvată cu algoritmi diferiți.

Fig. 22 Algoritmi pentru calculul ariei cercului

În capitolul ce urmează vom elabora diverși algoritmi pentru diferite probleme utilizând mediul de programare vizual RAPTOR.

94


Algoritmică şi programare

Scheme logice. Aplicații practice utilizând RAPTOR Algoritm pentru calculul Sumei a două numere Date de intrare: a, b – numere reale; Condiții: cu/fără restricții ( a, b !=0); Date de ieșire: Suma;

Fig. 23 Suna a două numere

Algoritm pentru calculul ariei si perimetrului unui dreptunghi Date de intrare: b, i – numere reale; Condiții: cu/fără restricții ( b,i !=0); Date de ieșire: A (aria), P (perimetrul) – numere reale;

95


Algoritmică şi programare

Fig. 24 Aria și perimetrul dreptunghiului

Performanta (optimizarea) algoritmului de calcul

Fig. 25 Aria și perimetrul dreptunghiului - algoritmi optimizați 96


Algoritmică şi programare

Algoritm pentru rezolvarea ecuației de gradul I: a*x+b=0, unde a și b sunt numere reale inserate (citite) de la tastatură Date de intrare: a, b – numere reale; Condiții: a, b !=0; Date de ieșire: A (aria), P (perimetrul) – numere reale;

Fig. 26 Algoritm pentru rezolvarea ecuației de gradul I

Algoritm pentru rezolvarea ecuației de gradul 2: ax2+bx+c=0 Din enunțul problemei extragem următoarele cerințe: o date de intrare: a, b, c – numere reale o Condiții: a!=0 si d>=0 o date de ieșire: x1, x2 – numere reale o x1 = (-b - sqrt(d)) / (2*a) o x2 = (- b + sqrt(d)) / (2*a)  sqrt reprezintă funcția radical  d = b * b – 4 * a * c; d reprezintă delta Pseudocod (rezolvarea ecuației ax2+bx+c=0) o Start o citește a, b, c 97


Algoritmică şi programare

o o o o o o o o o o o o o

dacă a=0 atunci scrie "Ecuația nu este de gradul 2" sfârșit altfel d=b*b–4*a*c dacă d<0 atunci scrie "Ecuația are soluții complexe" sfârșit altfel x1 = (-b - sqrt(d)) / (2 * a) x2 = (-b + sqrt(d)) / (2 * a) scrie x1, x2 sfârșit

Schema logică 1 (rezolvarea ecuației ax2+bx+c=0)

Fig. 27 Schema logica 1 pentru rezolvarea ecuației ax2+bx+c=0)

98


Algoritmică şi programare

Schema logică 2 (rezolvarea ecuației ax2+bx+c=0)

Fig. 28 Schema logica 2 pentru rezolvarea ecuației ax2+bx+c=0)

Algoritm pentru Suma Gauss (suma primelor n numere naturale)

Fig. 29 Algoritm pentru Suma Gauss

99


Algoritmică Ĺ&#x;i programare

Algoritm pentru Suma pătratelor/cuburilor primelor n numere naturale

Fig. 30 Suma pătratelor/cuburilor primelor n numere naturale

Algoritm pentru calculul ∑đ?’?đ?’Œ=đ?&#x;? đ?’Œ(đ?’Œ + đ?&#x;?)

Fig. 31 Calculul sumei k(k+1)

100


Algoritmică Ĺ&#x;i programare đ?’Œ

Algoritm pentru calculul ∑đ?’?đ?’Œ=đ?&#x;? ďż˝đ?’Œ+đ?&#x;?ďż˝

Fig. 32 Calculul sumei k/(k+1)

Algoritm pentru calculul ∑đ?’?đ?’Œ=đ?&#x;? ďż˝

đ?’Œđ?&#x;? +đ?&#x;?đ?’Œ+đ?&#x;?

đ?’Œđ?&#x;? +đ?&#x;?đ?’Œ+đ?&#x;?

ďż˝

Fig. 33 Calculul sumei ((k^2+2k+1)/(k^2+2k+2))

101


Algoritmică Ĺ&#x;i programare

Schema logică pentru calcului factorialului (n!)

Fig. 34 Calculul n!

Algoritm pentru calculul đ?‘Şđ?’Œđ?’?

Fig. 35 Calcul Combinări

102


Algoritmică Ĺ&#x;i programare

Algoritm pentru calculul đ?‘¨đ?’Œđ?’?

Fig. 36 Calcul Aramjamente

Algoritm pentru numere prime Schemă logică pentru testul numărului prim, determinarea Ĺ&#x;i afiĹ&#x;area divizorilor Č™i a sumei divizorilor unui număr natural n nenul.

Fig. 37 Algoritm numere prime 103


Algoritmică şi programare

Scheme logice. Subscheme si proceduri Schema logică pentru citirea unui număr natural nenul, determinarea și afișarea factorialului numărului utilizând o subschema logică.

Fig. 38 Calculul factorialului utilizând subscheme logice

Schema logică pentru citirea unui număr natural nenul, determinarea și afișarea factorialului numărului utilizând o procedură.

Fig. 39 Calculul factorialului utilizând o procedură

104


Algoritmică şi programare

Schemă logică pentru citirea unui număr natural nenul, determinarea și afișarea cifrelor sale Soluție: Fie n numărul citit. Se determină ultima cifră a numărului n calculând restul împărțirii lui n la 10. Apoi se atribuie numărului valoarea câtului împărțirii lui n la 10. Se repeta pașii de mai sus, până când n are valoarea 0.

Fig. 40 Citirea unui număr natural nenul, determinarea și afișarea cifrelor sale

105


Algoritmică şi programare

Schemă logică pentru citirea unui număr natural nenul și afișarea inversului numărului

Fig. 41 Determinarea și afișarea inversului unui număr

Test palindrom pentru un număr natural nenul, citit de la tastatura

Fig. 42 Test palindrom

106


Algoritmică şi programare

Mulțimea de palindromuri pana la n

Fig. 43 Mulțimea palindromurilor pana la n

Mulțimea de palindromuri dintr-un interval

Fig. 44 Mulțimea de palindromuri dintr-un interval

107


Algoritmică şi programare

Schema logică pentru citirea unui număr natural nenul, determinarea și afișarea sumei/produsului cifrelor sale

Fig. 45 Suma/produsul cifrelor unui număr natural nenul, citit de la tastatură

Schema logică pentru afișarea produsului cifrelor unui număr natural, utilizând o subschemă logică

Fig. 46 Produsul cifrelor unui număr natural, utilizând o subschemă logică

108


Algoritmică şi programare

Schema logică pentru afișarea produsului cifrelor unui număr natural, utilizând o procedură

Fig. 47 Produsul cifrelor unui număr natural, utilizând o procedură

Schema logică și pseudocod pentru calculul sumei/produsului cifrelor unui număr natural nenul utilizând proceduri.

Fig. 48 Produsul/suma cifrelor unui număr natural, utilizând proceduri

109


Algoritmică şi programare

Aranjamente și combinări utilizând o subschemă logică

Fig. 49 Aranjamente și combinări utilizând o subschemă logică

Sisteme de numerație. Conversia unui număr din binar (baza 2) în zecimal (baza 10)

Fig. 50 Conversia unui număr din baza 2 în baza 10

110


Algoritmică şi programare

Conversia unui număr din zecimal (baza 10)în binar (baza 2)

Fig. 51 Conversia unui număr din baza 10 în baza 2

Scheme logice. Tablouri Algoritm pentru scrierea si citirea elementelor unui vector Pas1. Stabilirea numărului de elemente ale vectorului. Algoritm: citește n /* nr de elemente*/ start do read n while n<=1 enddo /* nr de elemente*/ start read n while n<=1 read n endwhile

Fig. 52 Citește n

111


Algoritmică şi programare

Pas2. inițializarea și afișarea elementelor vectorului. Algoritm: citire si scriere elemente vector /* citirea elementelor*/ for (i=1; i<=n; i++) read a[i] endfor /* scrierea elementelor*/ for (i=1; i<=n; i++) write a[i] endfor

Fig. 53 Vector. Citire și scriere elemente

Pas 3. Algoritm pentru scrierea și citirea elementelor vectorului

Fig. 54 Algoritm pentru citirea și scrierea elementelor vectorului

Algoritm pentru: • • • •

112

Suma elementelor vectorului aflate pe poziții pare; Produsul elementelor vectorului aflate pe poziții impare; Suma elementelor vectorului cu valoare para ; Produsul elementelor vectorului cu valoare impara;


Algoritmică şi programare

Fig. 55 Algoritm pentru calculul sumei/produsului elementelor unui vector

Algoritm pentru elementele comune a doi vectori

Fig. 56 Elementele comune a doi vectori

Algoritm pentru sortarea elementelor vectorului

Fig. 57 Sortare elemente vector (crescătoare)

113


Algoritmică şi programare

Matrice. Algoritm (pseudocod si schema logică) pentru scrierea si citirea elementelor

Fig. 58 Scriere si citire elemente matrice

Într-o matrice pătratică, numărul de linii= numărul de coloane (n=m). Diagonala principala: elementele a[i][i], cu i=1,n sau a[i][i], cu i=0,n-1 Diagonala secundara: elementele a[i][n-i+1], cu i=1,n sau a[i][n-i-1], cu i=0,n-1 Zonele determinate de diagonale: I. Pe diagonala principala: i=j Sub diagonala principala: i>j Deasupra diagonalei principale: i<j II. Pe diagonala secundară: j=n-i+1 Sub diagonala secundara: j>n-i+1 Deasupra diagonalei secundare: <n-i+1

Schemă logică și pseudocod pentru citirea/scrierea elementelor matricei, suma elementelor diagonalei principale si secundare Pas1. Stabilirea numărului de linii si coloane ale matricei Start do read n while (n<=1) enddo

114


Algoritmică Ĺ&#x;i programare

Pas2. Citirea/scrierea elementelor matricei. //citire elem. matrice for (i=1; i<=n; i++) for (j=1; j<=n; j++) read a[i,j] endfor endfor //scriere elem. matrice for (i=1; i<n=; i++) for (j=1; j<=n; j++) write a[i,j] endfor endfor Pas3. Suma elementelor DP si DS //Suma elem. DP matrice for (i=1; i<=n; i++) sp+= a[i,i] //sau sp = sp + a[i,i] write sp endfor

//Suma elem. DS matrice for (i=1; i<=n; i++) ss+=a[i,n+1-i] ] //sau ss = ss + a[i, n+1-i] write ss endfor Pseudocod citire/scriere elemente matrice, suma elementelor diagonalei principale si secundare Start do //stabilire nr linii si coloane matrice read n assign n<- abs(n) while (n<=1) 115


Algoritmică Ĺ&#x;i programare

enddo for (i=1; i<=n; i++) //citire elem. matrice for (j=1; j<=n; j++) read a[i,j] endfor endfor for (i=1; i<=n; i++) //scriere elem. matrice for (j=1; j<=n; j++) write a[i,j] endfor endfor for (i=1; i<=n; i++) //Suma elem. DP matrice sp+= a[i,i] //sau sp = sp + a[i,i] write s endfor for (i=1; i<=n; i++) //Suma elem. DS matrice ss+=a[i,n+1-i] //sau ss = ss + a[i, n+1-i] write ss endfor End

Fig. 59 Citire /scriere elemente matrice, suma elementelor diagonalei principale si secundare

116


Algoritmică Ĺ&#x;i programare

Algoritm sortare elemente matrice pe linia k

Fig. 60 Sortare elemente matrice pe linia k

117


Algoritmică şi programare

Limbajul de programare C/C++ Noțiuni introductive Dezvoltat la începutul anilor 1970 de Ken Thompson și Dennis Ritchie pentru a scrie nucleul sistemului de operare UNIX, Limbajul de programare C reprezintă un limbaj standardizat care aparține clasei limbajelor de nivel scăzut sau de nivel mediu. Popularitatea Limbajului de programare C se datorează portabilității și eficienței codului obiect pe care îl poate genera, fiind unul din cele mai cunoscute limbaje de programare utilizate pentru scrierea de software de system, dar și pentru faptul că este implementat pe majoritatea platformelor de calcul existente. Ulterior apariției limbajului C au fost lansate diferite versiuni și extinderi ale limbajului: C++, C#, Java, Javascript, etc. Spre deosebire de majoritatea limbajelor de programare, limbajul C este asimilat deseori unui "asamblor portabil" datorită relației strânse dintre inter-operabilitate și echipamentul hardware, astfel, programul sursă C poate fi compilat și executat pe aproape orice tip de calculator, similar altor limbaje de programare, în timp ce limbajele de asamblare presupun un anumit tip de mașină de calcul. Limbajul C, prin prisma paradigmei programării procedurale, a fost conceput pentru realizarea cât mai facilă a programelor de dimensiuni mari sau foarte mari, într-un mod structurat. Caracteristici ale limbajului C: • Orientat spre paradigma programării procedurale; • Utilizează un limbaj preprocesor pentru lucrul cu macrouri și fișiere sursă; • Limbaj facil, cu funcționalități de baza: biblioteci, pointeri, funcții predefinite sau utilizator, pointeri la funcții și fișiere; • Întrebuințează un set simplu de tipuri de date; • Variabilele trebuie declarate (local sau global); • Permite lucrul cu adrese de memorie datorită pointerilor; • Permite folosirea parametrilor și pointerilor în cadrul funcțiilor; • Structuri și uniuni, definite de utilizator. Brian Kernighan și Dennis Ritchie, în prima lor carte, a dat un exemplu standard de program introductiv, utilizat

118


Algoritmică şi programare

apoi în majoritatea cărților de programare, indiferent de limbajul de programare. Programul afișează „Hello, World!“ la ieșirea standard (terminal sau monitor). Programul prezentat va fi compilat fără erori de marea majoritate a compilatoarelor moderne. Totuși, dacă va fi compilat de un compilator ce respectă standardul ANSI C, va produce unele mesaje de avertizare. Mai mult, în urma compilării programului sursă, vom avea mesaje de eroare, dacă se respectă standardele C99, deoarece variabila de ieșire, de tip int, nu va putea fi dedusă dacă nu a fost specificată în codul sursă (tipul de data returnat de funcția main). Aceste mesaje de avertizare sau eroare sunt eliminate operând schimbări minore în program. //directiva preprocesor //definirea funcției main() fără argument, care returnează o valoare de tip întreg. //executarea codului pentru funcția de afișare printf //terminarea execuției funcției main, returnând o valoare întreagă, interpretată de către sistem ca un cod de ieșire pentru o execuție efectuată cu succes.

Structura unui program C/C++ #include<...>

Directivele preprocesor (#include<stdio.h, conio.h>, etc) - conțin antetele funcțiilor din program Orice program C conține din una sau mai multe funcții care realizează operațiile de calcul sau de intrare /ieșire În corpul unei funcții întâlnim declarații, apeluri ale altor funcții și instrucțiuni.

main() • { declarații, apeluri de • funcții, instrucțiuni, } Orice program C/C++ utilizează biblioteci 1, care permit utilizarea funcțiilor pe care le conțin. 1

funcții biblioteca (printf, scanf, getch) 119


Algoritmică şi programare

De exemplu, funcția de bază printf, care afișează pe ecran, este definită în fișierul antet (header) stdio.h Pentru a putea executa comanda printf în programul nostru, trebuie să adăugăm următoarea directiva preprocesor #include <stdio.h>, care formează prima noastră linie de cod. Orice program C/C++ are în componență cel puțin o funcție (funcția main) și variabile. Funcția main () este aparte deoarece execuția programului începe întotdeauna cu această funcție. Orice funcție conține parametri și expresii (zero sau mai mulți/multe). O expresie este construită din operanzi conectați prin operatori conform anumitor reguli sintactice. Definiția unei funcții include următoarele componente: un tip de retur, un nume, parametri (între paranteze rotunde) și un corp al funcției numit și bloc (cuprins între acolade). Dintre funcțiile de intrare/ieșire cel mai des utilizate sunt printf și scanf. • Printf - este o funcție de scriere pe terminal. • Scanf - citește date de la tastatură și le scrie în memoria interna.

Unități lexicale C/C++ Unitățile lexicale ale unui program C/C++ sunt alcătuite din caractere, fiecare caracter având un anume înțeles pentru compilatorul C/C++, folosirea incorectă a acestora fiind semnalată prin mesaje de eroare. Limbajul de programare C nu prevede tipul da dată Boolean. El poate fi definit sub forma: #define BOOL char #define FALSE 0 #define TRUE 1

Mulțimea caracterelor Mulțimea caracterelor ce pot fi folosite în programe C/C++ este împărțită în următoarele submulțimi: • caractere alfanumerice: literele mici și mari din alfabetul englez precum și cifrele; • caractere speciale • caractere de spațiere • alte caractere: celelalte caractere tipăribile din setul de caractere ASCII

120


Algoritmică şi programare

Caracter , : ? ' ( [ { < ^ + * % | _ &

Denumire Caracter Denumire virgulă punct . două puncte punct și virgulă ; semnul întrebării semnul exclamării ! apostrof ghilimele " paranteză rotundă paranteză rotundă ) stânga dreapta paranteză dreaptă paranteză dreaptă ] stânga dreapta acoladă stânga acoladă dreapta } mai mic mai mare > săgeată sus egal = plus minus asterisc slash / procent backslash \ bară verticală tildă ~ underscore diez # ampersand Tabel 5 Caractere speciale

Denumire

Semnificație

11

spațiu Tab line feed carriage return tab vertical

un spațiu grup de spații (tabulator orizontal) trecere la linia următoare (new line) revenire la începutul liniei curente (folosit în special la imprimante) tabulator vertical (folosit în special la imprimante)

12

form feed

trecere la pagina următoare (folosit în special la imprimante)

Cod ASCII 32 9 10 13

Tabel 6 Caractere de spațiere

Comentariul Un comentariu este o porțiune de text care este ignorată de către compilatorul C/C++. Un comentariu pe o singură linie începe după o secvență dublu-slash // și se termină la finalul liniei curente. Un comentariu pe una sau mai multe linii începe după o secvență slash-star /* și se termină la prima secvență star-slash */. Pentru o mai 121


Algoritmică şi programare

ușoară înțelegere/depanare a programelor se recomandă folosirea comentariilor cât mai detaliate. De exemplu, o secvență de program care calculează suma a două numere poate fi comentată astfel: aria= b*i; // calculăm aria dreptunghiului sau aria= b*i; /* calculăm aria dreptunghiului */

Tipuri de date primare Un tip de date reprezintă o informație asociată unei expresii pe baza căreia compilatorul limbajului C/C++ determină în ce context poate fi folosită expresia respectivă. (char – înseamnă ca se pot efectua operații de concatenare). Orice expresie validă are asociată un anumit tip de date. Limbajul C/C++ definește patru tipuri de date fundamentale: caracter (char), întreg (int), real (sau flotant - float) și flotant dublu (double), unele dintre acestea având mai multe variante (denumite subtipuri). Tipurile char și int reprezintă valori cu sau fără semn, dar programatorul poate să folosească scurt sau lung, dar programatorul poate să folosească modificatorii short și long pentru a selecta subtipul dorit. În anumite situații (foarte rare), tipul flotant dublu (double) este echivalent cu flotant simplu (float). Limbajul C/C++ dispune la bază de patru tipuri de date : char, int, float, double. • int modificatorii signed și unsigned pentru a selecta un anume subtip. De asemenea, implicit tipul int poate fi o Rezervă 2 octeți. o Se folosește pentru a declara variabile întregi pozitive sau negative. o Valoarea minimă este 32768, valoarea maximă 32767. • char o Rezervă un singur octet. o Se folosește pentru a reține caractere din setul local de caractere (coduri ASCII). o Valorile minime și maxime care pot fi reținute într‑o variabilă de tip caracter sunt ‑127 și 127. • float o Pentru o variabilă simplă precizie se alocă 4 octeți. o Se folosește pentru a declara variabilele reale (în virgulă mobilă). Variabilele în virgulă flotantă pot fi în simplă precizie sau dublă precizie.

122


Algoritmică şi programare

o Valorile minime și maxime pe care le poate lua variabila: 3.4E 38 și 3.4E+38. • double o Se folosește pentru a declara variabile în dublă precizie. Rezerva 8 octeți. o Valorile minime și maxime sunt: 1.7E-308 și 1.7E+308. La tipurile de date prezentate mai sus se pot aplica calificatorii: short, long, unsigned; • short o Calificatorul short se aplică numai la int. El are semnificația de întreg scurt adică lungimea minimă pe care se poate reprezenta un întreg. In anumite variante de C, tipul de data short are aceeași semnificație ca și int. • long o se aplică și pentru tipul int și pentru tipul float sau double. Pentru tipul int, long va reprezenta un întreg pe 4 octeți (Valorile unui long int: valoarea maximă 2147483647). o Pentru float, long float este de fapt double iar long double se va reprezenta pe 10 octeți. • unsigned o (fără semn) are semnificația de număr pozitiv sau nul. o un întreg (int) declarat unsigned va avea valori cuprinse între 0 și 65535 iar un char declarat unsigned va avea valori cuprinse între 0 și 255. Tip/subtip Mărime (octeţi) 1 unsigned char 1 signed char 1 Char 2 short signed int 2 short unsigned int 2 short int 4 long signed int 4 long unsigned int 4 long int

Domeniu de valori 0…255 -128…127 depinde (cu sau fără semn) -32768…32767 0…65535 depinde (cu sau fără semn) -2147483648…2147483647 0… 4294967296 depinde (cu sau fără semn)

123


Algoritmică şi programare

Tip/subtip signed int unsigned int int float double

Mărime (octeţi) 2 sau 4 2 sau 4 2 sau 4 4 8

Domeniu de valori depinde (cu semn) depinde (fără semn) depinde (cu sau fără semn) -3,40×1038...3,4×1038 -1,79×10308...1,79×10308

Tabel 7 Tipuri de date primare

Constante Constantele reprezintă cel mai simplu tip de operanzi, mai exact o valoare care nu poate fi modificată şi care are asociat un tip (stabilit în funcție de modul de scriere al valorii constantei).

Constante de tip caracter O constantă de tip caracter se scrie în program între apostrofuri (e.g. ’C’, ’0’, ’.’, ’!’). Tipul constantelor caracter este întotdeauna char. Pentru a scrie constantele caracter apostrof, backslash (\) sau un caracter care nu apare pe tastatură se folosesc secvențe escape. O secvență escape este compusă din caracterul backslash (\) urmat de un caracter normal sau un număr (în baza 8 sau 16). În acest context, semnificația caracterului sau a numărului de după backslash devine una specială. Secvenţă Semnificaţie escape alert – produce un sunt în difuzorul calculatorului \a new line – trecerea la rândul următor \n carriage return – revenirea la capătul rândului \r horizontal tab – deplasarea pe orizontală cu un tab \t vertical tab – deplasarea pe verticală cu un tab (la imprimantă) \v form feed – trecerea la pagina următoare (la imprimantă) \f backspace – deplasarea înapoi a cursorului cu un caracter \b backslash – caracterul backslash \\ single quote – caracterul apostrof \’ double quote – caracterul ghilimele \” octal – caracterul cu codul ASCII ooo (unde ooo este un număr \ooo în baza 8, vezi Anexa 2 – Setul de caractere ASCII hexazecimal – caracterul cu codul ASCII hh (unde hh este un \xhh număr în baza 16, vezi Anexa 2 Tabel 8 Constante de tip caracter

124


Algoritmică şi programare

Constante de tip șir de caractere O constantă de tip șir de caractere este un grup de caractere și secvențe escape scrise între ghilimele. “Acesta este un sir de caractere” Tipul constantelor șir de caractere este char[], adică un vector de caractere.

Constante de tip întreg O constantă de tip întreg este un număr întreg scris în baza 8, 10 sau 16. Dacă numărul este scris în baza 8, atunci trebuie să fie prefixat de cifra zero (012). Dacă numărul este scris în baza 16, atunci trebuie să fie prefixat de zero urmat de litera x (0xA ). Subtipul constantei este ales astfel încât domeniu acestuia să conțină valoarea constantei. Programatorul poate scrie sufixele u (10u - de la unsigned) și/sau l (10l - de la long) pentru a specifica un subtip anume. Dacă valoarea constantei nu poate fi memorată ca subtip long signed int sau long unsigned int, atunci constanta se consideră constantă de tip flotant (float).

Constante de tip real O constantă de tip flotant este un număr real scris în baza 10, cu sau fără zecimale, cu sau fără exponent. Exponentul este alcătuit din litera e urmată de un număr întreg n, cu semnificația că valoarea constantei este numărul dinaintea exponentului înmulțit cu 10n. Separatorul pentru zecimale este punctul. Dacă numărul nu are zecimale sau exponent, atunci trebuie urmat de un punct pentru a fi considerat real. Tipul implicit al constantei este tipul double, dar dacă aceasta este urmată de litera f (de la float), tipul implicit va fi float. De exemplu, constanta cu valoarea -12,5 și tip double se poate scrie: 12.5 sau 1.25e1. Dacă scriem 12.5f, atunci tipul este float.

Cuvinte cheie Un cuvânt cheie este o secvență de caractere alfanumerice, cu o anumită semantică predefinită în limbajul C/C++. Cuvintele cheie sunt scrise cu litere mici. Cuvintele rezervate în limbajul C/C++ sunt în număr de 32, prezintă o semnificație predefinită și nu pot fi folosite în alte scopuri într-un program. Pentru controlul fluxului dispunem de instrucțiunile decizionale if, if-else și switch și de instrucțiunile repetitive do, while și for.

125


Algoritmică şi programare

auto const double float int short struct unsigned

break continue else for Long Signed switch void

case default enum go to register sizeof typedef volatile

char do extern if return static union while

Tabel 9 Cuvinte cheie

Separatori Delimitarea unităților lexicale dintr-un program se face cu ajutorul separatorilor. Separatorii acceptați de către limbajul C/C++: • ( ) - Parantezele rotunde specifică, în cadrul unei funcții, lista de parametrii sau, precizează ordinea de efectuare a operațiilor pentru evaluarea unei expresii. • { } - Acoladele delimitează instrucțiunile compuse, numite și blocuri. • [ ] - Parantezele drepte specifică dimensiunile tablourilor. • " " - Ghilimelele delimitează șirurile de caractere • ' ' - Apostroful încadrează un singur caracter • ; - instrucțiunea vida. Fiecare instrucțiune se încheie cu ; • /* */ sau //- Comentariul sau instrucțiunea de documentare.

Variabile Domeniul de aplicare reprezintă o regiune a programului în care o variabilă definită poate exista iar dincolo de aceasta regiune variabila nu poate fi accesata. In orice limbaj de programare, exista 3 zone unde variabilele pot fi declarate: • În afara tuturor funcțiilor (variabile globale) • În interiorul unei funcții sau a unui bloc (variabile locale) • În definiția parametrii funcțiilor(parametri formali) O variabila globala poate fi accesata de orice funcție și doar in interiorul unei funcții sau unui bloc (unde este declarată).

126


Algoritmică şi programare

Expresii. Operanzi. Operatori Expresii O expresie este o secvență de operanzi și operatori. Un operand poate fi o constantă, o variabilă sau o altă expresie. O expresie este caracterizată de o valoare și un tip. Valoarea și tipul expresiei sunt determinate de operatorii și operanzii care formează expresia. În limbajul C/C++, o expresie are valoarea logică de adevăr „adevărat” dacă are valoare nenulă (!=) și are valoarea logică de adevăr „fals” dacă are valoare nulă.

Operanzi Un operand poate fi: o constantă, o constantă simbolică, un identificator de variabilă simplă, un identificator de tablou, un element de tablou, un identificator de structură, un membru al unei structuri, numele unei funcții, un apel de funcție sau o expresie.

Conversii implicite de tip În timpul evaluării unei expresii, tipul unei subexpresii poate să fie schimbat automat în alt tip cu domeniul de valori mai mari. Această operație se numește conversie implicită de tip și realizează astfel: • orice operand de tipul char se convertește la tipul int; • orice operand de tipul short se convertește la tipul int. În plus, unii operatori prelucrează informațiile stocate în doi operanzi. Dacă operanzii au același tip, atunci rezultatul operatorului are același tip cu al operanzilor. Altfel, unul dintre operanzi se convertește la tipul celuilalt, conform următoarelor reguli: • dacă un operand este de tipul double atunci și celălalt operand se convertește la tipul double; • dacă un operand este de tipul float atunci și celălalt operand se convertește la tipul float; • dacă un operand este de tipul unsigned long atunci și celălalt operand se convertește la tipul unsigned long; • dacă un operand este de tipul long atunci și celălalt operand se convertește la tipul long.

Operatori Limbajul C/C++ dispune de un set puternic de operatori. Operatorii se pot clasifica după aritate, după asociere sau după prioritatea la evaluare întro expresie. Din punct de vedere al arității (adică numărul de operanzi), operatorii sunt unari, binari și ternari. 127


Algoritmică şi programare

Din punct de vedere al asociativității, operatorii pot fi asociativi de la stânga la dreapta sau de la dreapta la stânga. Cu excepția operatorilor de atribuire, operatorului condițional și operatorilor unari, restul operatorilor au asociativitate de la stânga la dreapta. În limbajul C/C++, sunt definite 15 niveluri de precedență; nivelul 1 este cel mai prioritar, iar nivelul 15 este cel mai puțin prioritar. Operatorii aflați pe un anumit nivel de prioritate au aceeași prioritate.

Fig. 61 Niveluri de precedență

128


Algoritmică şi programare

Operatori de adresare Operatorul de apelare „( )” este folosit ca separator al listei de parametri a funcțiilor sau ca modificator de prioritate în evaluarea expresiilor. Operatorul de indexare „[ ]” este folosit pentru accesarea valorilor din tablouri de date și va fi discutat în capitolul dedicat tablourilor. Operatorii de selecție directă „.” și de selecție indirectă „->” se folosesc în gestiunea structurilor și uniunilor. De exemplu, instrucțiunea: printf("Elementul 4 din vectorul v este %d", v[3]); folosește funcția printf pentru a afișa un mesaj.

Operatori unari Operatorul de negare logică are rezultatul 1 dacă valoarea operandului este nulă și 0 dacă valoarea operandului este nenulă. Exemplu, fie secvența: int a = 21,b; b=!a; // b este 0 deoarece a e diferit de 0 b=!b; // b devine 1 deoarece b este 0 Operatorul de negare la nivel de bit schimbă toți biții operandului din 0 în 1 și din 1 în 0. Operatorul de semn plus are ca rezultat valoarea operandului, iar operatorul de semn minus are ca rezultat valoarea cu semn schimbat a operandului. De exemplu, int a = 33, b, c; b = -a; c = +b; Operatorii de adresă și de adresare indirectă, având sintaxa &operand, respectiv, *operand, sunt folosiți pentru operații cu adrese de memorie. Operatorii de incrementare și decrementare au fiecare două forme: prefixată ++operand și postfixată operand++, respectiv, –operand și operand--. În forma prefixată valoarea operandului se mărește cu 1 (adică se incrementează), respectiv se micșorează cu 1 (adică se decrementează), iar rezultatul expresiei este noua valoare. În forma postfixată, valoarea operandului se incrementează, respectiv decrementează, iar rezultatul expresiei este vechea valoare. Operatorul sizeof are ca rezultat dimensiunea în octeți a operandului (adică numărul de octeți necesari pentru memorarea unei informații de acest tip). Operandul poate să fie o expresie sau un tip de dată. Dacă operandul este o expresie, atunci se evaluează expresia și sizeof calculează dimensiunea în octeți pentru tipul rezultatului.

129


Algoritmică şi programare

De exemplu, long b; char c; b=sizeof(c); // b = 1 (un caracter ocupa 1 octet) b=sizeof(-b);// b = 4 (un long int ocupa 4 octeți) Operatorul de conversie explicită (denumit operator cast) este folosit atunci când este necesară conversia unei valori la un anumit tip. Sintaxa operatorului este: (tip)expresie, unde tip este tipul spre care se convertește valoarea expresiei. De exemplu, operatorul de împărțire aplicat la doi operanzi întregi efectuează împărțirea întreagă (fără zecimale); pentru a calcula valoarea reală unul dintre operanzi trebuie transformat în tipul float sau double: int a = 6, b = 10; float f; f=a/b; // f=6/10 = 0 (împărțire întreagă) f=(float)a/b;// f=6.0/10 = 0.6 (împărțire reala)

Operatori multiplicativi Operatorul de înmulțire are sintaxa operand1*operand2 și rezultatul este produsul valorilor celor doi operanzi, cu eventuale conversii de tip. Operatorul de împărțire are sintaxa operand1/operand2 și rezultatul este câtul împărțirii primului operand la cel de-al doilea operand, cu eventuale conversii de tip. Operatorul modulo are sintaxa operand1%operand2, se aplică numai operanzilor de tip întreg și rezultatul este restul împărțirii primului operand la cel de-al doilea operand. Operandul 2 trebuie să fie nenul. int a = 3, b = 6; double c = 8.1, d = 1.1; printf("%d\n", a * b);// Valoarea 18 de tip întreg printf("%f\n", a * d);// Valoarea 6.6 de tip real printf("%f\n", c * d);// Valoarea 8.91 de tip real printf("%d\n", b / a);// Valoarea 2 de tip întreg printf("%f\n", c / a);// Valoarea 2.7 de tip real printf("%f\n", c / d);// Valoarea 7.36 de tip real printf("%d\n", a % b);// Valoarea 3 de tip întreg printf("%d\n", b % a);// Valoarea 0 de tip întreg

130


Algoritmică şi programare

Operatori aditivi Operatorul de adunare are sintaxa operand1+operand2 și rezultatul este suma valorilor celor doi operanzi, cu eventuale conversii de tip. Operatorul de scădere are sintaxa operand1–operand2 și rezultatul este diferența dintre valoarea primului și a celui de-al doilea operand, cu eventuale conversii de tip. Pentru exemplificare considerăm secvența următoare: int a = 3, b = 5; double c = 8.12, d = 1.1; printf("%d\n", a + b);// Valoarea 8 de tip întreg printf("%d\n", a – b);// Valoarea -2 de tip întreg printf("%f\n", c – b);// Valoarea 3.12 de tip real printf("%f\n", a + c);// Valoarea 11.12 de tip real printf("%f\n", c + d);// Valoarea 9.21 de tip real printf("%f\n", c – d);// Valoarea 7.02 de tip real

Operatori pentru deplasare Operatorii de deplasare se aplică operanzilor de tip întreg, rezultatul fiind de tip întreg. Operatorul de deplasare stânga are sintaxa: operand1<<operand2 și rezultatul se obține prin deplasarea la stânga a configurației binare a primului operand, cu numărul de biți dat de valoarea celui de-al doilea operand. Pozițiile binare rămase libere în dreapta se completează cu zerouri. Operatorul de deplasare dreapta are sintaxa: operand1>>operand2 și rezultatul se obține prin deplasarea la dreapta a configurației binare a primului operand, cu numărul de biți dat de valoarea celui de-al doilea operand. Pozițiile binare rămase libere în stânga se completează cu bitul de semn pentru subtipurile cu semn (e.g. int, short, etc.), sau cu 0 pentru subtipurile fără semn (e.g. unsigned int, unsigned short, etc.).

Operatori relaționali Operatorii relaționali sunt folosiți pentru tipurile aritmetice de date și pentru datele de tip pointer, având sintaxa operand1 operator operand2. Valoarea expresiei se obține astfel: se evaluează cei doi operanzi; dacă valoarea primului operand este în relația dată de operator, cu al doilea operand, rezultatul expresiei este 1(true); altfel rezultatul expresiei este 0(false). De exemplu: int a = 20, b = 13, c; c = a < b; // c este 0 c = a <= b; // c este 0

131


Algoritmică şi programare

c = a > b; c = a >= b;

// c este 1 // c este 1

Operatori de egalitate Operatorii relaționali sunt folosiți pentru tipurile aritmetice de date și pentru datele de tip pointer, având sintaxa operand1 operator operand2. Valoarea expresiei se obține astfel: se evaluează cei doi operanzi; dacă valoarea primului operand este în relația dată de operator, cu al doilea operand, rezultatul expresiei este 1 (true); altfel rezultatul expresiei este 0. De exemplu, fie secvența: int a = 20, b = 13, c; c = a == b; // c este 0 c = a != b; // c este 1

Operatori logici Operatori logici binari se aplică valorilor operanzilor conform funcției descrise anterior. Evaluarea se realizează prin scurt-circuit, adică evaluarea operanzilor se oprește atunci când valoarea expresiei a fost stabilită și nu mai poate fi schimbată de restul expresiei. De exemplu: short int a = 20, b = 0, c, d; c = a && b; // c este 0 (deoarece b este 0) d = a || b; // d este 1 (deoarece a este 20)

Operatorul condițional Operatorul condițional este singurul operator ternar al limbajului C/C++. Sintaxa sa este: operand1?operand2:operand3. Valoarea expresiei se obține astfel: se evaluează expresia operand1; dacă valoarea acesteia este nenulă (true) rezultatul expresiei este valoarea lui operand2, iar operand3 nu se mai evaluează; altfel, dacă valoarea expresiei operand1 este nulă (false) rezultatul expresiei este valoarea lui operand3, iar operand2 nu se mai evaluează. (max=a > b ? a : b;)

Operatori de atribuire Operatorii de atribuire sunt de două tipuri: simpli și compuși. Operatorul de atribuire simplă are sintaxa var=expr unde var este o variabilă sau un element dintr-un tablou, iar expr este o expresie. De exemplu, considerăm secvența: int a, b; char c; double d; a = 5; // a este 5 132


Algoritmică şi programare

d = 12.21; // c este 12.21 b = a + 3.5; // b este 8 (se face conversie la int) b = b+d; // b este 20 (se face conversie la int)

Operatorul virgulă Operatorul virgulă este folosit pentru scrierea unei expresii formată dintr-o listă de expresii, având sintaxa: operand1, operand2, ..., operandn. Valoarea expresiei se calculează astfel: se evaluează succesiv valorile lui operand1, operand2, ..., operandn. Tipul și valoarea întregii expresii este tipul și valoarea expresiei operandn. De exemplu, int a, b, c; a = 2, b = 5, c = b % 2; Întâi se atribuie variabilei a valoarea 2, apoi se atribuie variabilei b valoarea 5 și în final variabilei c restul împărțirii valorii lui b la 2, adică se atribuie variabilei c valoarea 1. Valoarea expresiei a = 2, b = 5, c = b % a este 1 (valoarea expresiei c=b % a), iar tipul ei este int (tipul expresiei c=b % a).

133


Algoritmică şi programare

Structuri algoritmice. Instrucțiuni Prelucrările realizate de un program corespund algoritmului implementat, utilizând instrucțiuni. Instrucțiunea se încheie cu ajutorul separatorului punct și virgulă numit și instrucțiunea vidă (;). Instrucțiunile limbajului C/C++ pot fi împărțite în următoarele categorii: • Expresii, • Blocuri, • Selecții, • Iterații, • Salturi. Algoritmul, conform teoremei Bohm-Jacopini, poate fi conceput cu ajutorul a trei structuri fundamentale de calcul: structura secvențială (secvența), structura alternativă decizională (decizia sau selecția) și structura repetitivă (bucla sau ciclul). Clasa structurilor secvențiale (enunțuri): start, end, read v, write v, assign v  e. Clasa structurilor alternative: • structura alternativă cu două alternative • structura alternativă cu n alternative. Clasa structurilor repetitive: • ciclul cu test inițial, • ciclul cu test final, • ciclul cu inițializare, test și reinițializare. Limbajul C/C++ are relativ puține instrucțiuni în comparație cu alte limbaje de nivel înalt. Astfel, instrucțiunile READ, WRITE, OPEN, CLOSE etc. prezente în majoritatea limbajelor, nu există în limbajul C/C++, aceste operații de intrare/ieșire trebuie realizate explicit prin funcții utilizator sau funcții de bibliotecă. Limbajul C/C++ permite scrierea programelor după descrierea unui algoritm prin scheme logice bine structurate sau prin limbaj pseudocod datorită instrucțiunilor de selecție (if‑else, switch), repetitive (while, do, for) și posibilităților de grupare a instrucțiunilor în blocuri.

134


Algoritmică şi programare

Instrucțiuni simple Instrucțiunile simple pot fi declarații de variabile sau instrucțiuni expresie. Fiecare instrucțiune simplă se termină obligatoriu prin simbolul ';' (punct și virgulă). Declarațiile de variabile sunt instrucțiuni în care se declară tipul și, eventual, valoarea inițială a unor variabile. Instrucțiunile expresie sunt construcțiile (terminate cu punct și virgulă) în care identificatorii, constantele și apelurile de funcție sunt legate prin operatori Instrucțiunile expresie sunt expresiile de atribuire, de incrementare /decrementare sau de invocare de metodă, care, la rândul lor, se termină prin simbolul ';' (punct și virgulă). expresie; Exemplu : a = b = 0; n ++; gets (t); r = ((d = b * b – 4 * a * c) > 0 ? sqrt (d) : sqrt( d));

Instrucțiuni compuse Structurile de control (structurate) se mai numesc și instrucțiuni compuse. Acestea au rolul de a indica succesiunea în care se execută instrucțiunile programului. Conform celor afirmate anterior, dispunem de următoarele structuri de control: blocul, instrucțiunile de ramificare (decizia), instrucțiunile repetitive (bucla sau ciclul) și structura de tratare a excepțiilor.

Blocul Blocul reprezintă o succesiune de instrucțiuni simple sau compuse, cuprinse între acolade, ({}). În particular, un bloc poate conține alte blocuri. Variabilele declarate într-un bloc sunt valabile numai în blocul respectiv, din locul declarării variabilei până la sfârșitul blocului, inclusiv în blocurile interioare. Din punct de vedere sintactic o instrucțiune compusă este echivalentă cu o singură instrucțiune și de aceea la descrierea sintaxei instrucțiunilor while, if, do etc. unde apare instrucțiunea, se va subînțelege că este o instrucțiune simplă sau un bloc de instrucțiuni. Obs: Nu se pune terminatorul de instrucțiune „;“ după acolada dreaptă de la sfârșitul blocului.

135


Algoritmică şi programare

Structuri decizionale(ramificate) Structurile decizionale sunt acele structuri de control în care fluxul programului înglobează două sau mai multe ramificații paralele. În majoritatea limbajelor de programare, structurile ramificate sunt realizate prin instrucțiunile decizionale (de selecție) if, if-else și switch. Instrucțiunea if Instrucțiunea if este o instrucțiune condițională, de forma: if(condiție) instrucțiune în care

condiție - este o expresie logicăș instrucțiune - este o instrucțiune simplă sau compusă, care se execută dacă și numai dacă expresia condiție are valoarea true. Instrucțiunea funcționează astfel: • se determină valoarea de adevăr pentru expresie; dacă este diferită de zero este adevărată, iar dacă este zero este falsă; • dacă expresie este adevărată, atunci se execută instrucțiune; • dacă expresie este falsă, atunci nu se execută nimic. Deoarece o expresie este considerată adevărată dacă este diferită de zero în limbajul C/C++ în loc de: if (expresie! = 0) este suficient să scriem: if (expresie) Instrucțiunea if-else Instrucțiunea if este o instrucțiune condițională, de forma: if(condiție) instrucţiune1 else instrucţiune2 în care

condiție - este o expresie logică instrucţiune1 - este o instrucțiune simplă sau compusă, care se execută dacă și numai dacă expresia condiție are valoarea de adevăr true (!=0);

136


Algoritmică şi programare

instrucţiune2 - este o instrucțiune simplă sau compusă, care se execută dacă și numai dacă expresia condiție are valoarea false. Instrucțiunea funcționează astfel: • se determină valoarea de adevăr pentru expresie; dacă este diferită de zero este adevărată, iar dacă este zero este falsă; • dacă expresie este adevărată, atunci se execută instrucțiune1; • dacă expresie este falsă, atunci se execută instrucțiune2. Exemplul 1: maximul a trei numere a, b, c Primul exemplu este greșit deoarece else se asociază cu al doilea if nu cu primul așa cum este corect și cum indică și alinierea. Această ambiguitate este rezolvată în secvența din partea dreaptă utilizând blocul (acoladele) și astfel asociem corect else la primul if. Exemplul 2: determină ce fel de triunghi este: echilateral, isoscel sau scalen (oarecare).

137


Algoritmică şi programare

Instrucțiunea switch Instrucțiunea switch este o structură de control, cu următoarea formă: switch (selector) { case c 1 : secvență 1 case c 2 : secvență 2 ............................. case c n : secvență n default: secvență (n+1) } în care: selector - o expresie a cărei valoare are tip întreg sau tip char sau tip enumerare; c i - o constantă, sau o expresie a cărei valoare este o constantă, având același tip cu cel al selectorului; secvență i - o secvență de instrucțiuni simple sau compuse (poate fi și vidă). Instrucțiunea switch este o instrucțiune de decizie multiplă care ramifică execuția programului către o anumită secvență de instrucțiuni după cum o expresie este egală cu o anumită valoare constantă. Efectul instrucțiunii switch este următorul: 1. se evaluează expresia și se compară cu valoarea cazurilor prezente în etichetele case; 2. dacă valoarea expresiei este egală cu constanta de la unul din cazuri, execuția continuă de la secvența de instrucțiuni etichetată cu acel case în jos. Dacă secvența respectivă se termină cu instrucțiunea break, se execută doar acele instrucțiuni, astfel execuția continuă cu următoarea secvență până se întâlnește un break sau sfârșitul instrucțiunii switch. Această curgere în jos la următoarea secvență poate fi în avantajul programatorului, dar poate fi și cauza unor erori; 3. dacă valoarea expresiei nu este egală cu nici o constantă de la nici unul din cazuri, execuția continuă cu secvența de instrucțiuni etichetată cu default, iar dacă default lipsește atunci nu se execută nimic; Instrucțiunea switch se mai poate folosi cu succes și la ramificări multiple, de exemplu ramificările cerute de un meniu.

138


Algoritmică şi programare

Exemplu: # include "stdio.h" void main ()  int k; .............. printf(" M E N U \n"); printf(" 1 = creare fişier \n"); printf(" 2 = Modificare articol \n"); printf(" 3 = Ştergere articol \n"); printf(" 4 = Adăugare în coadă \n"); printf(" Opţiunea dvs."); scanf (" %d, &k); switch (k)  case 1: creare (.....); break; case 2: modif (.....); break; case 3: şterg (.....); break; case 4: adaug (.....); break; default: exit (1);  ................................ ............................... 

Structuri repetitive (bucle sau cicluri) Structurile repetitive, numite bucle sau cicluri, sunt structuri de control în care o anumită instrucțiune simplă sau compusă, de regulă un bloc, se execută în mod repetat. În limbajele de programare, ciclurile se realizează prin instrucțiunile while, do-while și for. Instrucțiunea while Instrucțiunea repetitivă while, numită și ciclu cu test inițial, are următoarea formă: while (condiție) instrucțiune în care: condiție - este o expresie logică; instrucțiune - este o instrucțiune simplă sau compusă (de regulă un bloc), care se repetă cât timp expresia condiție are valoarea true.

139


Algoritmică şi programare

Exemplu: int n = 0; while (n < 10) { n ++; }

while (1) { /* executa la infinit instrucțiunea*/ }

int n = 0; while (1) { n ++; if (n == 10) { break; } }

Corpul buclei while se execută cât timp (while cât timp) expresia este diferită de zero (adevărată) sau să nu se execute niciodată dacă la prima evaluare expresia este nulă. De asemenea, while poate defini și o buclă infinită, de exemplu: while (1) instrucțiune urmând ca ieșirea din ciclare să se facă din corpul instrucțiunii folosind: break, return sau exit(). Instrucțiunea do-while Instrucțiunea do-while ,numită și ciclu cu test final, are următoarea formă: do bloc/instrucțiune while(condiție); în care: bloc - este o structură de control sub formă de bloc; condiție - este o expresie logică. Executarea blocului se repetă și în acest caz cât timp este satisfăcută condiția dar, spre deosebire de instrucțiunea while, în acest caz testarea condiției se face după ce a fost executat corpul ciclului. Se execută corpul buclei, după care se evaluează expresie și dacă este diferită de zero (adevărată) atunci se reia execuția corpului instrucțiunii, astfel dacă expresie este zero (falsă) bucla ia sfârșit și se trece la instrucțiunea următoare de după do. Din modul de executare a instrucțiunii do se observă că instrucțiune (corpul buclei) se execută cel puțin o data.

140


Algoritmică şi programare

Exemplu:

Instrucțiunea for Instrucțiunea for se folosește în special atunci când numărul de repetări al corpului ciclului este dinainte cunoscut. Această instrucțiune are următoarea formă: for(inițializare opt ; condiţieContinuare opt ; incrementare opt ) instrucțiune opt în care: inițializare: listă de expresii de inițializare, separate prin virgule; condițieContinuare: expresie logică; incrementare: listă de expresii, care se execută după executarea corpului ciclului; instrucțiune: instrucțiune simplă sau compusă (de preferință un bloc) care constituie corpul ciclului. Toate componentele marcate cu opt sunt opționale. Executarea instrucțiunii for decurge astfel: • se execută mai întâi secvența de expresii inițializare; • se intră în ciclu, executându-se în mod repetat instrucțiune urmată de secvența de expresii incrementare, cât timp expresia condiţieContinuare are valoarea true;

141


Algoritmică şi programare

când expresia de condiţieContinuare are valoarea false, se iese din ciclu. Exemplu: •

int i; i = 0; for (; i < 10;) { i++; printf("%d\n", i); }

int i; for (i = 0; i < 10;) { i++; printf("%d\n", i); }

int i; for (i = 0; i < 10; i++) { printf("%d\n", i); }

Instrucțiunea for extinsa Instrucțiunea for extinsă este o variantă a instrucțiunii for, care are forma: for(tip e:a) instrucțiune în care: a - o structură de date iterabilă, respectiv un tablou, o enumerare sau o colecție; e - o variabilă, al cărei tip este același cu tipul elementelor structurii a; tip - tipul variabilei e; instrucțiune - o instrucțiune simplă sau compusă (preferabil un bloc). Semnificația instrucțiunii for extinse este următoarea: pentru fiecare element e al structurii a se execută instrucțiune. Se permite ca tipul variabilei e să fie declarat chiar în interiorul parantezei instrucțiunii for. Utilizarea in cicluri a instrucțiunilor break si continue Instrucțiunile break și continue sunt instrucțiuni de control, care produc salt către sfârșitul corpului ciclului. Ele pot fi simple sau cu etichetă. Instrucțiunea break simplă, produce ieșirea definitivă din ciclul în corpul căreia se găsește. Instrucțiunea break etichetă se folosește când există mai multe cicluri imbricate și provoacă ieșirea definitivă din ciclul marcat cu etichetă. Instrucțiunea continue provoacă întreruperea executării restului instrucțiunilor din corpul ciclului la iterația curentă și trecerea la executarea iterației următoare a ciclului. Instrucțiunea continue etichetă se folosește când există mai multe cicluri imbricate. Ea are un efect similar cu continue, dar se referă la corpul ciclului marcat cu etichetă. Instrucțiunile break și continue pot fi folosite în toate instrucțiunile repetitive: while, do-while și for.

142


Algoritmică şi programare

Descriptori de format Printf("%[-]lungime.precizie cod_format", nume variabila); unde:

• • • • • • • • •

[-] - aliniere la dreapta lungime.precizie – (nr. cifre) partea intreagă.partea fractionara cod_format: d, i - valori întregi; f - reale in simpla precizie 4 octeți (float); lf - reale in dubla precizie - long float (double); e, E - afișare valori in format științific cu exponent, cu o singura cifra la partea întreagă; c – caracter; s – string (sir de caractere); p - afișare pointer, adrese de memorie; x, X - afișare valori hexazecimale; o - afișare valori întregi octale; Exemplu: int a, b = 1, c = 2, d = 3, e = 4, x, y; a = b - c + d * e; /* afișam rezultatul operației: 1-2+3*4 = 11 */ printf("a=%d", a); =>se va afișa pe terminal a=11

Aplicații practice Citirea (de la tastatura) si scrierea variabilelor

Dupa compilare si executie, se vor afisa: Valorile variabilelor a si b.

143


Algoritmică şi programare

Suma a 2 numere

După compilare si execuție, se va afișa: Suma numerelor a si b Calculați aria cercului, cerând de la tastatura valoarea razei cercului.

Rezolvare utilizând instrucțiunea decizionala if:

144


Algoritmică şi programare

Ecuația de gradul I

145


Algoritmică şi programare

Aria si perimetrul dreptunghiului

Ecuația de gradul II

146


Algoritmică şi programare

Suma primelor n numere (Gauss) si factorialul

Conversie temperatura. Problema: Afișați corespondentul temperaturii din grade Celsius in grade Kelvin, Fahrenheit, Rankine si Réaumur Formule de conversie: K = C + 273.15;//Kelvin F = C * 1.8 + 32;//Fahrenheit Ra = C * 1.8 + 32 + 459.67;//Rankine Re = C * 0.8;//Réaumur

147


Algoritmică şi programare

Divizorii unui număr

CMMDC

148


Algoritmică şi programare

Masive de date Masivele de date sunt tablouri de date omogene( de același tip, standard sau definit de utilizator), reunite sub un singur nume si dispuse contiguu 2 într-un bloc de memorie. Elementele pot fi accesate individual prin indici. Toate elementele au un predecesor (excepție primul) si un succesor (excepție ultimul). Tabloul ne permite să programăm/efectuam mai ușor operații asupra grupurilor de valori de același tip. Nu se pot defini tablouri cu componente de tipuri diferite. Un tablou poate avea una sau mai multe dimensiuni. Numărul dimensiunilor tabloului este limitat doar de memoria calculatorului pe care rulează programul care folosește masive de date. Dacă numărul de valori folosite la inițializare depășește numărul de elemente din masiv atunci compilatorul generează un mesaj de eroare. Dacă numărul de valori folosite la inițializare este mai mic decât numărul de elemente din masiv, atunci restul valorilor sunt inițializate cu 0.

Masive de date unidimensionale Un masiv unidimensional se numește vector. Un vector se declară conform sintaxei: tip identificator[n]; tip este tipul (predefinit sau utilizator) comun al tuturor elementelor vectorului; identificator este numele vectorului; n este un număr natural nenul care precizează numărul de componente ale vectorului. Numărul de elemente/componente se declară între []. Fiecare componentă a unui tablou poate fi tratată exact ca o variabilă simplă (identificator[i]). Referirea la componenta i a vectorului se face cu identificatorul[i]. Această componentă are valoarea memorată la adresa adr+i*sizeof(tip). Declarația: int vector[1000];

Care se leagă, se înrudește, se unește cu ceva, care are elemente apropiate, comune cu altceva

2

149


Algoritmică şi programare

• • •

creează un tablou cu 1000 de elemente/componente, toate de tip int. primul element are indicele 0, al doilea are indicele 1, iar ultimul are indicele 999. se rezervă o zonă contiguă de memorie de dimensiune sizeof(int)*1000=4*1000=4000 octeți.

Aplicații practice Algoritm pentru citirea elementelor unui vector for(i = 0; i < n; i ++) { printf("a[%d]=",i); scanf("%d",&a[i]); }

i=0; while (i < n) { printf("a[%d]=",i); scanf("%d",&a[i]); i ++; }

Algoritm pentru scrierea elementelor unui vector for(i = 0; i < n; i ++) { printf("Elementul a[%d]=%d\n",i,a[i]); }

i=0; while (i < n) { printf(“Elementul a[%d]=%d\n",i,a[i]); i ++; }

Algoritm pentru citirea și scrierea elementelor unui vector #include <stdio.h> #include<conio.h> main() { int i, n, a[25]; printf("Tastați numărul elementelor vectorului:"); scanf("%d", &n); for(i = 0;i < n;i ++) // citire/inițializare elemente vector { printf("a[%d]=",i); scanf("%d", &a[i]); } printf("Elementele vectorului sunt:\n"); for(i=0;i<n;i++)// afișare elemente vector

150


Algoritmică şi programare

printf("a[%d]=%d\n",i,a[i]); system("pause"); return 0; }

Suma elementelor unui vector #include <stdio.h> #include<conio.h> int s=0,i,n,a[25]; main() {printf("Stabiliți numărul de elemente ale vectorului, n<=25:"); scanf("%d", &n); for(i = 0;i < n;i ++)// inițializare elemente vector { printf("a[%d]=",i); scanf("%d",&a[i]); } printf("Elementele vectorului sunt:\n"); for(i=0;i<n;i++)// afisare elemente vector printf("a[%d]=%d\n",i,a[i]); //suma elemente vector for(i=0;i<n;i++) s=s+a[i]; printf("Suma elementelor vectorului=%d\n",s); //system("pause"); return 0;} sau #include <stdio.h> #include<conio.h> int s=0,i,n,a[25]; main() {printf("Stabiliți numărul de elemente ale vectorului, n<=25:"); scanf("%d",&n); // inițializare elemente vector for(i=0;i<n;i++) { printf("a[%d]=",i); scanf("%d",&a[i]); s=s+a[i]; //suma elemente vector } 151


Algoritmică şi programare

printf("Suma elementelor vectorului=%d\n",s); system("pause"); return 0;}

Suma elementelor cu valoare para, produsul elementelor cu valoare impara ale unui vector #include <stdio.h> #include <conio.h> int main() { int v[100]; // Declararea vectorului v int s,p,i,n; // s-suma, p-produsul, i-index in vector, n-număr de componente printf(" Introduceți numărul de componente ale vectorului "); scanf("%d",&n); printf(" Tastați componentele vectorului \n"); for(s=0,p=1,i=0;i<n;i++) { printf(" v[%d] = ",i); scanf("%d",&v[i]); if(v[i]%2) // Elementul v[i] impar p*=v[i]; else // Elementul v[i] par s+=v[i]; } printf(" Suma componentelor pare este %d \n",s); printf(" Produsul componentelor impare este %d \n",p); getch(); return 0; }

Sortarea elementelor unui vector in ordine crescătoare #include <stdio.h> #include <conio.h> int main() { int i,j,n,pmin; float v[100], aux; printf("Introduceți numărul de termeni:"); scanf("%d",&n); printf("Introduceți componentele:\n"); for(i=0; i<n; i++) 152


Algoritmică şi programare

{ printf("v[%d]=",i); scanf("%f",&v[i]); } for(i=0;i<n;i++) for(j=i+1;j<n;j++) if(v[i]>v[j]) //sortare in ordine crescătoare { aux=v[i]; v[i]=v[j]; v[j]=aux; } printf("Vectorul sortat crescător este:\n"); for(i=0;i<n;i++) printf("a[%d] = %.2f\n",i,v[i]); getch(); //system("pause"); return 0; }

Determinarea valorii minime și poziția sa într-un vector de numere reale #include <stdio.h> #include <conio.h> int main() { int i,n,pmin; float v[100]; printf("Introduceți numărul de termeni:"); scanf("%d",&n); printf("Introduceți componentele:\n"); for(i=0; i<n; i++) { printf("v[%d]=", i); scanf("%f",&v[i]); } for(pmin=0,i=1; i<n; i++) if(v[pmin]>v[i]) pmin = i; printf("Minimul este v[%d]=%f.2\n", pmin, v[pmin]); getch(); }

153


Algoritmică şi programare

Produsul scalar a doi vectori #include <stdio.h> #include <conio.h> int main() { int i,j,n; float v1[100], v2[100],ps=0; printf("Introduceți numărul de termeni ai vectorilor:"); scanf("%d",&n); printf("Introduceți componentele vectorului 1:\n"); for(i=0; i<n; i++) { printf("v1[%d]=",i); scanf("%f",&v1[i]); } printf("Introduceți componentele vectorului 2:\n"); for(i=0; i<n; i++) { printf("v2[%d]=",i); scanf("%f",&v2[i]); } for(i=0;i<n;i++) ps+=v1[i]*v2[i]; printf("Produsul scalar al vectorului =%.2f\n",ps); getch(); //system("pause"); return 0; } sau #include <stdio.h> #include <conio.h> int main() { int i,j,n; float v1[100], v2[100],ps=0; printf("Introduceți numărul de termeni ai vectorilor:"); scanf("%d",&n); printf("Introduceți componentele vectorilor:\n"); 154


Algoritmică şi programare

for(i=0; i<n; i++) { printf("v1[%d]=",i); scanf("%f",&v1[i]); printf("v2[%d]=",i); scanf("%f",&v2[i]); ps+=v1[i]*v2[i]; } printf("Produsul scalar al vectorului =%.2f\n",ps); getch(); //system("pause"); return 0; }

Masive de date bidimensionale Un masiv bidimensional se numește matrice si se declară conform sintaxei: tip identificator [m][n]; tip este tipul (predefinit sau utilizator) comun al tuturor elementelor matricei; identificator este numele matricei; m, n este un număr natural nenul care precizează numărul de linii si coloane ale matricei Liniile se numerotează cu 0,1, ...,(m-1). Coloanele se numerotează cu 0,1, ...,(n-1). Fiecare element din matrice are același tip, dat de tipul matricei. Nu se pot defini matrice cu elemente de tipuri diferite. Referirea la elementul de pe linia i și coloana j din matrice se face cu identificator[i][j]. Declarația: int matrice[10][10]; • creează o matrice pătratică cu 10 linii si 10 coloane (100 elemente), toate de tip int. • se rezervă o zonă contiguă de memorie de dimensiune m*n*sizeof(int)=100*4=400 octeți.

Aplicații practice Algoritm pentru citirea/scrierea elementelor unei matrice for(i=0;i<n;i++) for(j=0;j<n;j++) {

for(i=0;i<n;i++) for(j=0;j<n;j++) { 155


Algoritmică şi programare

printf("a[%d][%d]=",i,j); scanf("%d",&a[i][j]); }

printf("%d=", a[i][j]); }

Într-o matrice pătratică, numărul de linii= numărul de coloane (n=m). Diagonala principala: • elementele a[i][i], cu i=1,n sau a[i][i], cu i=0,n-1 Diagonala secundara: • elementele a[i][n-i+1], cu i=1,n sau a[i][n-i-1], cu i=0,n-1 Zonele determinate de diagonale: • Pe diagonala principala i=j o Sub diagonala principala: i>j o Deasupra diagonalei principale: i<j • Pe diagonala secundară j=n-i+1 o Sub diagonala secundara: j>n-i+1 o Deasupra diagonalei secundare: j<n-i+1

Suma elementelor matricei situate pe diagonala principală si secundară #include <stdio.h> #include<conio.h> int sp=0,ss=0,i,j,n,a[30][30]; main() { printf("Numărul de linii si coloane ale matricei:"); scanf("%d",&n); for(i=0;i<n;i++) for(j=0;j<n;j++) { printf("a[%d][%d]=",i,j); scanf("%d",&a[i][j]); } for(i=0;i<n;i++) for(j=0;j<n;j++) { if(i==j) sp=sp+a[i][j]; } printf("Suma elementelor de pe diagonala principală=%d\n",sp); for(i=0;i<n;i++) for(j=0;j<n;j++) { 156


Algoritmică şi programare

if(i+j==n-1) ss=ss+a[i][j]; } printf("Suma elementelor de pe diagonala secundară=%d\n",ss); //system("pause"); return 0; }

Suma elemente matrice pe coloana k #include "stdio.h" int sum(int a[][20], int n,int k) { int i,j,s; s=0; for(i=0;i<n;i++) for(j=0;j<n;j++) if(j==k) s+=a[i][j]; return s; } int main() { int n,i,j,a[20][20],k; printf(" Dimensiune matrice –nr. linii/coloane: "); scanf("%d",&n); printf(" Elementele matricei \n"); for(i=0;i<n;i++) for(j=0;j<n;j++) { printf(" a [ %d ][ %d ] = ",i,j); scanf("%d",&a[i][j]); } printf(" Coloana pe care se calculează suma: "); scanf("%d",&k); if(k>=0 && k<=n-1) printf(" Suma elementelor de pe coloana %d este %d\n",k,sum(a,n,k)); else printf(" Coloana %d inexistenta \n",k); getch(); }

157


Algoritmică şi programare

Suma elemente matrice de pe linia k #include "stdio.h" #include "conio.h" int sum(int a[][20], int n,int k) { int i,j,s; s=0; for(i=0;i<n;i++) for(j=0;j<n;j++) if(i==k) s+=a[i][j]; return s; } int main() { int n,i,j,a[20][20],k; printf(" Dimensiune matrice: "); scanf("%d",&n); printf(" Elementele matricei \n"); for(i=0;i<n;i++) for(j=0;j<n;j++) { printf(" a [ %d ][ %d ] = ",i,j); scanf("%d",&a[i][j]); } printf(" Linia pe care se calculează suma: "); scanf("%d",&k); if(k>=0 && k<=n-1) printf(" Suma elementelor de pe linia %d este %d\n",k,sum(a,n,k)); else printf(" Linia %d inexistenta \n",k); getch(); }

Masive de date n-dimensionale Un masiv n-dimensional este limitat doar de memoria fizică a calculatorului pe care rulează programul si se declară conform sintaxei: tip identificator [dim1][dim2]…[dimn]; tip este tipul (predefinit sau utilizator) comun al tuturor elementelor tabloului; identificator este numele tabloului;

158


Algoritmică şi programare

dim1, dim2, dimn sunt expresii constante a căror valori reprezintă numărul de componente pentru fiecare dimensiune. Numărul total de elemente ale unui masiv cu n dimensiuni este egal cu dim1*dim2*...*dimn. Pentru memorarea unui masiv cu tipul tip cu n dimensiuni sunt necesari sizeof(tip)*dim1*dim2*...*dimn octeți. Accesarea unui element al masivului se face cu construcția nume[i1][i2]…[in], unde i1, i2, …, in sunt coordonatele elementului pe fiecare dintre dimensiuni (0 ≤ ij < dimj, 1≤j≤n). Declarația: int masiv[3][3][3]; •

creează masivul tridimensional cu 3x3x3 elemente de tip real, toate de tip int. • se rezervă o zonă contiguă de memorie de dimensiune sizeof(int)*27=4*27=108 octeți.

159


Algoritmică şi programare

Funcții ale limbajului de programare C/C++ Funcția reprezintă unitatea fundamentală de execuție a programelor C/C++ prin care se obține o divizare logică (modularizare) a programelor mari și complexe. Funcțiile, numite si subrutine, cuprind o serie de instrucțiuni care efectuează un calcul, realizează o acțiune, implementează un algoritm, etc, îndeplinind totodată rolul de variabila globală a programului. Funcțiile împart sarcinile complexe în porțiuni mici de program cu scopul de a fi mai ușor de înțeles și de programat. Sunt utile întrucât ascund detaliile de funcționare ale anumitor porțiuni ale programului, îmbunătățind concomitent modul de lucru al acestuia. Utilizarea funcțiilor împiedică scrierea repetata a aceluiași cod în procesul de dezvoltare a unui program. În procesul de concepere a funcțiilor trebuie să ținem cont de următoarele principii: claritate, lizibilitate, ușurință în întreținere/depanare și de reutilizare/reapelare a lor. Împărțirea programelor în funcții este arbitrară și depinde de modul de gândire a programatorului. Funcțiile pot fi refolosite de cate ori este nevoie, fără sa fie rescrise de la zero. O funcție în Limbajul C/C++ realizează o anumită acțiune și constituie o construcție independentă. Funcțiile conțin declarații și instrucțiuni, încapsulând prelucrări precizate corespunzător și care pot fi refolosite în diverse programe. Practic, aproape orice program apelează atât funcții din bibliotecile existente cât și funcții definite în cadrul programului respectiv. Cu alte cuvinte, programul (aplicația) este de fapt o colecție de funcții (subprograme). În utilizarea unei funcții sunt implicate 2 entități: apelantul (care apelează/cheamă funcția) și funcția apelată. Modul de funcționare al unei funcții: Apelantul (reprezentând tot o funcție) transmite parametri (argumente) către funcția apelată. Funcția apelată primește parametrii transmiși de către apelant, efectuează operațiile din corpul funcției și returnează rezultatul /rezultatele înapoi funcției apelante. Pentru a construi și utiliza o funcție trebuie să cunoaștem trei elemente care sunt implicate în utilizarea funcțiilor: declararea, apelul și prototipul (declarație forward) funcției. 1. Declararea funcției tip nume_funcţie (lista parametri) { declarații locale instrucțiuni

160

1. Declararea funcției float suma_numere (float a, float b) { float sum;


Algoritmică şi programare

return expresie } 2. Apelul funcției nume_functie(p1, p2, . . . ,pn); 3. Prototipul funcției tip nume_funcţie(lista declarații parametri);

suma = a + b; return suma; } 2. Apelul funcției suma_numere(a,b); 3. Prototipul funcției float suma_numere (float a, float b); sau float suma_numere (float, float); Avantajele utilizării funcțiilor sunt multiple: • Evită duplicarea codului sursă ținând cont de faptul că este greu să menții și să sincronizezi toate duplicările de cod; • Utilizarea funcțiilor permite dezvoltarea modulară a unui program de dimensiuni considerabile, sau progresivă, fie de jos în sus (bottom up), fie de sus în jos (top down), fie combinat. De aceea, un program complex poate fi mai ușor de scris, înțeles și depanat dacă este dezvoltat modular; • Funcțiile pot fi reutilizate de mai multe ori in cadrul unei aplicații sau de către alte aplicații dacă sunt incluse într-o bibliotecă de funcții, conducând la diminuarea efortului de programare în cazul dezvoltării unei noi aplicații; • Funcțiile pot fi scrise și testate separat de orice aplicație, conducând la îmbunătățirea timpului de dezvoltare al unei aplicații ample întrucât erorile apar între module sau funcții); • Depanarea unei aplicații este sistematizată, deoarece intervențiile se fac numai în cadrul anumitor module sau funcții;

Declarații și definiții de funcții În limbajul C/C++ orice identificator trebuie declarat înainte de a fi folosit. Funcțiile, la rândul lor, trebuie să respecte si ele această regulă. Limbajul C/C++ utilizează două tipuri de funcții: funcții void și funcții care întorc o valoare. O declarație de funcție înfățișează compilatorul despre numele funcției, tipul de data al valorii returnate (poate fi și void) și tipurile datelor folosite în lista de parametri. Pentru a satisface cerința enunțată anterior, prototipul unei funcții se plasează chiar înaintea definiției funcției main, la începutul programului. Prototipul unei funcții este întâlnit în unele limbaje sub denumirea de declarație forward. Prototipul nu este însoțit de corpul funcției, lista de

161


Algoritmică şi programare

parametri formali este opțională iar declarația prototipului unei funcții se încheie cu ;

Aplicații practice Suma a două numere utilizând o funcție #include "stdio.h" //----- funcția suma ---float suma_numere (float a, float b) { float sum; sum=a+b; return sum; } //----- funcția main ---int main() { float a,b, sum; printf("Inserați valoarea variabilei a:"); scanf("%f",&a); printf("Inserați valoarea variabilei b:"); scanf("%f",&b); sum=suma_numere(a,b); printf("Suma numerelor=%.2f\n", sum); //system ("PAUSE"); return 0; } sau #include "stdio.h" //----- funcția suma ---float suma_numere (float a, float b) { float sum; sum=a+b; return sum; } //----- funcția main ---int main() { float a,b; printf("Inserați valoarea variabilei a:"); 162


Algoritmică şi programare

scanf("%f",&a); printf("Inserați valoarea variabilei b:"); scanf("%f",&b); printf("Suma numerelor=%.2f\n", suma_numere(a,b)); //system ("PAUSE"); return 0; } sau #include "stdio.h" float suma_numere (float, float); //----- prototipul funcției suma ---//----- funcția main ---int main() { float a,b; printf("Inserați valoarea variabilei a:"); scanf("%f",&a); printf("Inserați valoarea variabilei b:"); scanf("%f",&b); printf("Suma numerelor=%.2f\n", suma_numere(a,b)); //system ("PAUSE"); return 0; } //----- funcția suma ---float suma_numere (float a, float b) { float sum; sum=a+b; return sum; } Suma Gauss și factorialul #include "stdio.h" #include<windows.h> //--------funcția factorial-----int factorial(int a) { int i,fact=1; for (i=1;i<=a;i++) fact*=i; return fact;

163


Algoritmică şi programare

} //--------funcția Gauss-----int Gauss(int a) { int i,gauss=0; for (i=1;i<=a;i++) gauss+=i; return gauss; } //--------funcția main-----int main(){ int n; printf("Inserați valoarea variabilei n:"); scanf("%d",&n); printf("n!=%d\n",factorial(n)); printf("Suma Gauss=%d\n",Gauss(n)); //system ("PAUSE"); return 0; } sau #include "stdio.h" #include<windows.h> int factorial(int a); //--------prototipul funcției factorial-----int Gauss(int a); //--------prototipul funcției Gauss-----//---------------------------------int main() { int n; printf("Inserați valoarea variabilei n:"); scanf("%d",&n); printf("n!=%d\n",factorial(n)); printf("Suma Gauss=%d\n",Gauss(n)); //system ("PAUSE"); return 0; } //--------funcția factorial-----int factorial(int a) { int i,fact=1; 164


Algoritmică şi programare

for (i=1;i<=a;i++) fact*=i; return fact; } //--------funcția Gauss-----int Gauss(int a) { int i,gauss=0; for (i=1;i<=a;i++) gauss+=i; return gauss; } Aranjamente si combinări #include "stdio.h" #include<windows.h> //--------functia factorial-----int factorial(int a) { int i,fact=1; for (i=1;i<=a;i++) fact*=i; return fact; } //--------funcția main ---------int main() { int n,k,a,c; printf("Inserați valoarea variabilei n:"); scanf("%d",&n); printf("Inserați valoarea variabilei k:"); scanf("%d",&k); printf("n!=%d\n",factorial(n)); printf("k!=%d\n",factorial(k)); printf("(n-k)!=%d\n",factorial(n-k)); a=factorial(n)/factorial(n-k); c=factorial(n)/(factorial(k)*factorial(n-k)); printf("Aranjamente=%d, Combinări=%d\n", a, c); //system ("PAUSE"); return 0; }

165


Algoritmică şi programare

Iterația si recursivitatea Ambele tehnici se bazează pe câte o structură de control. Iterația utilizează o structură repetitivă, iar recursivitatea o structură de selecție. Recursivitatea implementează repetiția prin apelurile repetate ale aceleiași funcții. Orice algoritm recursiv se poate rezolva si iterativ. Iterația reprezintă execuția periodică (ciclică) a unei porțiuni de program, până la satisfacerea condiției (pentru instrucțiunile repetitive while, do-while, for). Este structurata sub forma unor funcții care apelează alte funcții într-o manieră ierarhică. Recursivitatea este un mecanism care dă posibilitatea unui subprogram sa se auto-apeleze. Se spune că un obiect sau un fenomen este definit în mod recursiv dacă în definiția sa există cel puțin o referire la el însuși. O funcție recursiva este o funcție care se apelează pe ea însăși. Limbajul C/C++ permite recursivitatea, adică admite ca o funcție să se autoapeleze fie direct, fie indirect prin intermediul altei funcții. La fiecare apel recursiv se alocă pe stivă într-o zonă distinctă față de apelul precedent sau următor, următoarele informații: • valorile parametrilor; • variabilele locale funcției; • adresa de revenire. Pe scurt, recursivitatea: • reprezintă execuția repetată a unui modul; • verifică o condiție în cursul execuției modulului; • nesatisfacerea condiției implică repetarea execuției modulului de la început, chiar dacă execuția curentă nu s-a incheiat (terminat); • în momentul satisfacerii condiției se revine în ordine inversă în lanțul de apeluri, reluându-se și încheindu-se apelurile suspendate. Recursivitatea este o tehnică de programare frecvent utilizată în implementarea funcțiilor. Tehnica poate fi folosită în cazul problemelor cu natură recursivă și simplifică scrierea programelor prin scrierea directă a formulelor recursive. Funcțiile C/C++: • Iterative • Recursive o Direct recursive o Indirect recursive

166


Algoritmică şi programare

Funcția direct recursiva este o funcție P care conține o referință la ea însăși. Exemplu: P=M(Si,P), unde M este mulțimea ce conține instrucțiunile Si pe P însuși. Se deosebesc două tipuri de funcții recursive directe: • Funcții monoapel, cu un singur apel recursiv. Acestea se pot scrise rapid sub formă iterativă (nerecursivă). • Funcții multiapel, cu mai mult de un apel recursiv. Forma iterativă este greoaie, folosește o stivă pentru memorarea rezultatelor intermediare. In recursivitatea indirectă există două procese care se apelează reciproc astfel: funcția A apelează funcția B iar funcția B apelează funcția A, ceea ce înseamnă că subprogramele sunt indirect recursive. În această situație este obligatoriu să declarăm funcțiile prin prototipul lor. Exemplu: Fie șirurile de numere reale AN și BN care au primii termeni AN(0) si BN(0) egali cu doua valori a și b de același semn. Celelalte elemente ale șirurilor se determina astfel: AN(n)=(AN(n-1)+BN(n))/2; BN(n)=sqrt(AN(n)+BN(n-1)). O funcție recursivă se numește direct recursivă, dacă în definiția ei există cel puțin un autoapel al ei. O funcție recursivă se numește indirect recursivă dacă se autoapelează prin intermediul unei alte funcții, care la rândul ei se auto-apelează prin intermediul primei funcții. În orice funcție recursivă trebuie să existe cel puțin o instrucțiune if prin care este verificată condiția de terminare a procesului recursiv, fiind astfel evitată o execuție infinită. În cazul recursivității indirecte, funcțiile care se auto-apelează indirect trebuie făcute cunoscute compilatorului prin prototipurile lor, scrise înaintea funcției main, respectiv, definirii lor. Utilitatea recursivității ne asigură posibilitatea de a preciza un set infinit de obiecte printr-un set finit de relații (una sau mai multe). De exemplu, funcția factorial prezentată anterior, poate fi implementată recursiv în limbajul C/C++ astfel: int factorial (int n) { if(n == 0) return 1; else

167


Algoritmică şi programare

return n * factorial(n - 1); } Observație: tipul funcției factorial devine long pentru n>15. Orice funcție recursivă trebuie să conțină (cel puțin) o instrucțiune if (de obicei la începutul funcției), prin care se verifică necesitatea unui apel recursiv sau se pîrîsește funcția! Absența instrucțiunii decizionale IF conduce la un ciclu fără condiție de terminare (buclă infinită). Pentru funcțiile diferite de tipul void, apelul recursiv se face cu ajutorul instrucțiunii return, prin preluarea rezultatului apelului anterior. Funcțiile implementate recursiv nu conțin instrucțiuni repetitive explicite, repetarea operațiilor fiind obținută prin apel recursiv. Algoritmii recursivi sunt folosiți pentru rezolvarea problemelor care implementează calcule recursive și pentru prelucrarea structurilor de date definite recursiv (liste, arbori). Iterația este preferată (uneori) datorită vitezei mai mari de execuție și folosirii unei cantități mici de memorie utilizată în executarea lor.

Aplicații practice Factorial iterativ si recursiv #include<stdio.h> int fact_r1 (int n) //factorialul recursiv 1 { if (n == 1 || n == 0 ) return 1; return n * fact_r1(n - 1); } int fact_r2 (int n) { return (n >= 1) ? n * fact_r2(n - 1) : 1; //factorialul recursiv 2 } int fact_it (int n) //Varianta, iterativă { int i, f; for( i = f = 1; i <= n; i ++ ) f * = i; return f; } int main() { int n; printf("Tastati n: "); scanf("%d",&n); 168


Algoritmică şi programare

printf("n! recursiv1= %d\nn! recursiv2= %d\nn! iterativ= %d\n", fact_r1(n),fact_r2(n),fact_it(n)); fflush(stdin); return 0; } sau #include<stdio.h> //------------declarații forward----------------int fact_r1 (int ); //factorialul recursiv 1 int fact_r2 (int ); //factorialul recursiv 2 int fact_it (int ); //Varianta, iterativă //------------funcția main----------------int main() { int n; printf("Tastati n: "); scanf("%d",&n); printf("n! recursiv1= %d\nn! recursiv2= %d\nn! iterativ= %d\n", fact_r1(n),fact_r2(n),fact_it(n)); fflush(stdin); //getchar(); return 0; } //------------funcții factorial----------------int fact_r1 (int n) //factorialul recursiv 1 { if (n == 1 || n == 0 ) return 1; return n*fact_r1(n-1); } int fact_r2 (int n) //factorialul recursiv 2 { return (n >= 1) ? n * fact_r2(n - 1) : 1; } int fact_it (int n) //Varianta, iterativă { int i, f; for( i = f = 1; i <= n; i ++ ) f * = i; // sau f=f*i return f; }

169


Algoritmică Ĺ&#x;i programare

CMMDC recursiv #include "stdio.h" #include "conio.h" int cmmdc(int m, int n) { if(n==0) return m; else return cmmdc(n,m%n); } int main() { int m,n; printf("Tastati m = "); scanf("%d",&m); printf("Tastati n = "); scanf("%d",&n); if(cmmdc(m,n)==1) printf(" Numerele tastate, %d si %d sunt prime intre ele\n",m,n); else printf(" Numerele tastate, %d si %d nu sunt prime intre ele \n",m,n); printf(" CMMDC( %d , %d ) = %d\n",m,n,cmmdc(m,n)); getch(); } CMMDC n numere #include <stdio.h> #include <conio.h> // declaratii forward functii CMMDC unsigned int cmmdc_2(unsigned int , unsigned int ); unsigned int cmmdc_n(unsigned int x[], int n); int main() { unsigned int x[20]; int n,i; printf("Tastati n: "); scanf("%d",&n); for(i = 0; i < n; i ++) { printf("Inserati elementul %d= ",i+1); scanf("%u",&x[i]); } if (n==1) printf("\n Cmmdc-ul numerelor este: %u",x[0]);

170


Algoritmică Ĺ&#x;i programare

else printf("\nCmmdc-ul numerelor este: %u",cmmdc_n(x,n)); getch(); } unsigned int cmmdc_2(unsigned int a, unsigned int b) { if(a==b) return a; if(a>b) return cmmdc_2(a-b,b); else return cmmdc_2(a,b-a); } unsigned int cmmdc_n(unsigned int x[], int n) { if (n==2) return cmmdc_2(x[0],x[1]); else return cmmdc_2(cmmdc_n(x,n-1),x[n-1]); } Problema turnurilor din Hanoi #include <stdio.h> #include <conio.h> void hanoi(int n, char a, char b, char c) { if (n==1) { printf("Se muta discul %d de pe %c pe %c\n", n, a, b); } else { hanoi(n-1,a,c,b); printf("Se muta discul %d de pe %c pe %c\n", n, a, b); hanoi(n-1,c,b,a); } } int main() { int n; printf("Problema turnurilor din Hanoi\n"); printf("Tastati numarul de discuri: "); scanf("%d",&n); hanoi(n,'A','B','C'); getch(); }

171


Algoritmică şi programare

Suma Gauss calculată recursiv și iterativ #include<stdio.h> //fara declaratii forward int suma_r1(int n) //Gauss recursiv 1 { if (n==0) return 0; else return (n + suma_r1(n-1)); } int suma_r2 (int n) //Gauss recursiv 2 { return (n > 0) ? n + suma_r2(n - 1) : 0; } int suma_it (int n) //Varianta, iterativa { int i, s=0; for( i=1; i<=n; i++ ) s+= i; return s; } int main() { int n; printf("Introduceti n: "); scanf("%d", &n); printf("Suma Gauss calculata recursiv1= %d\n",suma_r1(n)); printf("Suma Gauss calculata recursiv2= %d\n",suma_r2(n)); printf("Suma Gauss calculata iterativ = %d\n",suma_it(n)); getchar(); } sau #include<stdio.h> int suma_r1(int n); //Gauss recursiv 1 int suma_r2 (int n); //Gauss recursiv 2 int suma_it (int n); //Varianta, iterativa int main() { int n; printf("Introduceti n: "); scanf("%d", &n); printf("Suma Gauss calculata recursiv1= %d\n",suma_r1(n)); 172


Algoritmică Ĺ&#x;i programare

printf("Suma Gauss calculata recursiv2= %d\n",suma_r2(n)); printf("Suma Gauss calculata iterativ = %d\n",suma_it(n)); getchar(); } int suma_r1(int n) //Gauss recursiv 1 { if (n==0) return 0; else return (n + suma_r1(n-1)); } int suma_r2 (int n) //Gauss recursiv 2 { return (n > 0) ? n + suma_r2(n - 1) : 0; } int suma_it (int n) //Varianta, iterativa { int i, s=0; for( i=1; i<=n; i++ ) s+= i; return s; } Suma elementelor unui vector - calcul recursiv #include <stdio.h> #include <conio.h> int suma (int n, int a[10]); int main() { int a[10], n; // Citire date de intrare printf("Introduceti nr de elemente: "); scanf("%d", &n); for (int i=1; i<=n; i++) { printf("Elementul [%d] = ", i); scanf("%d", &a[i]); } // Afisarea rezultatelor printf("Suma = %d", suma(n,a)); getch(); } int Suma (int n, int a[10]) {

173


Algoritmică Ĺ&#x;i programare

return (n >0) ? a[n]+suma(n-1,a) : 0; } Fibonacci (1) #include<stdio.h> int fibo_it (int n) { int f1=1,f2=1,fn=1, i; if (n==0 || n==1) return 1; for (i=2; i<=n; i++) { fn=f1+f2; f2=f1; f1=fn; } return fn; } int fibo_r (int n) { if ( n==0 || n==1 ) return 1; return fibo_r (n-2) + fibo_r (n-1); } int main () { int n; printf("Inserati n:"); scanf ("%d", &n); printf ("Calcul iterativ\n%d \n", fibo_it(n)); printf ("Calcul recursiv\n%d \n", fibo_r(n)); fflush (stdin); getchar (); return 0; } Fibonacci (2) #include<stdio.h> int fibo_it (int); int fibo_r (int); int main (){ int n; printf("Inserati n:"); scanf ("%d", &n); printf ("Calcul iterativ\n%d \n", fibo_it(n));

174


Algoritmică Ĺ&#x;i programare

printf ("Calcul recursiv\n%d \n", fibo_r(n)); fflush (stdin); getchar (); return 0; } int fibo_it (int n) { int f1=1,f2=1,fn=1, i; if (n==0 || n==1) return 1; for (i=2; i<=n; i++) { fn=f1+f2; f2=f1; f1=fn; } return fn; } int fibo_r (int n) { if ( n==0 || n==1 ) return 1; return fibo_r (n-2) + fibo_r (n-1); } Suma cifrelor unui număr natural #include<stdio.h> #include<conio.h> int suma(int ); //prototipul functiei int main() { int n; printf("Introduceti numarul: "); scanf("%d", &n); printf("Suma cifrelor numarului este: %d", suma(n)); getch(); } int suma(int n) { if(!n) return 0; //!n=daca nu exista n else return n%10+suma(n/10); }

175


Algoritmică şi programare

Conversia unui număr din baza 10 în baza k #include<stdio.h> #include<conio.h> void schimbbaza(int, int); //prototipul functiei int main() { int n,b; printf("Tastati numarul:"); scanf("%d",&n); printf("Tastati baza:"); scanf("%d",&b); schimbbaza (n,b); getch(); } void schimbbaza (int n,int b) { int rest=n%b; if (n>=b) schimbbaza (n/b,b); printf("%d",rest); } Vector. Funcții recursive pentru: a. citirea componentelor șirului b. afișarea elementelor din sir c. suma componentelor d. produsul componentelor e. numărul componentelor negative f. produsul componentelor pozitive g. media aritmetica a elementelor #include<stdio.h> #include<conio.h> typedef int vector[20]; void citire(vector x,int n) ; void afisare(vector x,int n) ; int suma(vector x,int n); int produs(vector x,int n); int numar_negative(vector x,int n) ; int produs_pozitive(vector x,int n) ; float media(vector x, int m, int n) ; int main() { vector x; int n; // numar componente

176


Algoritmică Ĺ&#x;i programare

printf("Tastati numarul de elemente: "); scanf("%d",&n); printf("Tastati elementele sirului:\n"); citire(x,n); printf("Vectorul are in componenta elementele: "); afisare(x,n); printf("\nSuma elementelor: %d",suma(x,n-1)); printf("\nProdusul elementelor: %d",produs(x,n-1)); printf("\nNumarul elementelor negative: %d",numar_negative(x,n1)); printf("\nProdusul elementelor pozitive: %d",produs_pozitive(x,n1)); printf("\nMedia componentelor din sir: %.2f",media(x,n-1,n)); getch(); } void citire(vector x,int n) //n este dimensiunea sirului { printf("\telementul %d: ",n); scanf("%d",&x[n-1]); if(n>=2) citire(x,n-1); //apelul recursiv } void afisare(vector x,int n) { printf("%d ",x[n-1]); if(n>=2) afisare(x,n-1); //apelul recursiv } int suma(vector x,int n) //adunarea cifrelor { if(n==-1) return 0; else return x[n]+suma(x,n-1); } int produs(vector x,int n) //produsul cifrelor { if(n==-1) return 1; else return x[n]*produs(x,n-1); } int numar_negative(vector x,int n) //numarul elemente negative { if(n==0) return (x[n]<0); else return (x[n]<0)+numar_negative(x,n-1); 177


Algoritmică şi programare

} int produs_pozitive(vector x,int n) //produsul componentelor pozitive { if(n==0) return (x[n]>0?x[n]:1); else return (x[n]>0?x[n]:1)*produs_pozitive(x,n-1); } float media(vector x, int m, int n) //media aritmetica a componentelor sirului { return (float)x[m]/n + ((m!=0)?media(x,m-1,n):0); }

Definirea și transmiterea parametrilor. Parametri formali și actuali Limbajul C/C++ pune la dispoziția utilizatorului diverse funcții predefinite, numite funcții standard. Utilizatorul poate să definească și să utilizeze propriile sale funcții, numite funcții utilizator. Există o funcție standard, principală, funcția main(), apelată de sistemul de operare la începutul execuției oricărui program. Funcția main gestionează toate funcțiile utilizator din program. Funcțiile apelate pot comunica cu funcțiile apelante prin intermediul parametrilor. Pentru a fi utilizată într‑un program C/C++, o funcție trebuie întâi declarată (și ulterior definită). O funcție se definește specificând tipul și numele ei, tipul și numele argumentelor sale, precum și corpul funcției (compus din declarații și instrucțiuni). Argumentele unei funcții precizate la definirea ei se numesc parametri formali, în timp ce argumentele precizate la apelul ei se numesc parametri actuali. Sintaxa declarației unei funcții este: tip identificator(lista parametri formali) { declarații și instrucțiuni } unde:

tip identificator(lista parametri formali) se numește antet al funcției, iar restul reprezintă corpul funcției. Elementul tip reprezintă tipul valorii întoarse prin apelul funcției, iar identificator este numele funcției.

178


Algoritmică şi programare

Pentru funcțiile care nu returnează nici o valoare, tipul funcției este void. Pentru fiecare parametru din lista parametri formali trebuie specificat tipul și numele. Lista parametrilor formali poate să fie şi vidă, caz în care poate fi înlocuită cu cuvântul cheie void. Parametrii funcțiilor se pot transmite prin valoare, prin referință și prin intermediul variabilelor globale. La transmiterea parametrilor prin valoare, valorile din funcția apelantă sunt copiate în parametrii actuali ai funcției, și nu pot fi modificate de către funcția apelată deoarece aceasta lucrează cu copii ale lor. În cazul transmiterii parametrilor prin referință (sau prin pointeri) funcția apelantă furnizează funcției apelate adresa zonei de memorie unde sunt păstrate datele. Funcția apelată poate modifica aceste date accesând direct zona respectivă de memorie. O altă metodă de transfer al parametrilor este transmiterea acestora prin variabile globale. Această metodă se bazează pe faptul că variabilele globale sunt accesibile din orice funcție. În limbajul de programare C/C++ există două posibilități de transfer a datelor între funcția apelantă și cea apelată: prin parametri și prin variabile globale. Prin utilizarea variabilelor globale nu se face un transfer propriuzis, ci se folosesc în comun anumite zone de memorie partajate între toate funcțiile din program. În limbajul C/C++ transferul prin parametri se poate realiza prin valoare și prin referință. În cazul transferului prin valoare, valoarea parametrului este copiată, iar funcția apelată lucrează cu această copie. Deci operațiile efectuate asupra unui parametru formal transmis prin valoare (adică orice parametru care are tip fundamental), nu modifică, la ieșirea din funcție, parametrul actual corespunzător. În plus, transferul valorii este însoțit de eventuale conversii de tip, realizate pe baza informațiilor despre funcție de care dispune compilatorul. În concluzie, parametrii transmiși prin valoare se pot modifica în corpul funcției, dar după terminarea apelului funcției apelate în funcția apelantă aceștia au aceleași valori pe care le-au avut înaintea apelului. Din acest motiv, transferul prin valoare este folosit pentru transmiterea datelor de intrare. Folosind transferul prin valoare se pot transmite funcției numai parametri de intrare. Exemplu: #include <stdio.h> #include <conio.h> int suma(int ); // prototip functie 179


Algoritmică şi programare

int main() { int n; printf(" Valoarea lui n = "); scanf("%d",&n); printf("Suma primelor %d numere naturale este %d\n", n, suma(n)); getch(); } int suma(int k) // parametrul transmis prin valoare { int s = 0,i; for(i=1;i<=k;i++) s+=i; return s; } Pentru transmiterea parametrilor de ieșire se folosește transferul prin referință (adică prin pointeri), caz în care se transmit funcției adrese de variabile. Antetul unei funcții care folosește transferul de parametri de tip referință este: tip identificator(…, tip *variabila,…) Parametrii transmiși prin referință se pot modifica în corpul funcției, iar după terminarea apelului funcției aceștia au valorile primite în timpul execuției funcției apelate. În acest caz, funcția primește ca parametru adresa zonei de memorie unde se găsesc datele și poate să le actualizeze pe acestea modificând conținutul zonei de memorie. Reluăm exemplul anterior. Acum, funcția suma are un parametru formal de intrare k și un parametru formal de ieșire s, care este un pointer la tipul int. #include <stdio.h> #include <conio.h> int suma(int k,int *s); int main() { int n,sum; printf("Valoarea lui n = "); scanf("%d",&n); // Apelul functiei 180


Algoritmică şi programare

suma(n,&sum); // &sum inseamna adresa lui sum printf("Suma primelor %d numere naturale este %d\n", n, sum); getch(); } // Parametrul n se transmite prin valoare // Parametrul s se transmite prin adresa int suma(int k,int *s) { int sum = 0,i; for(i = 1; i <= k;i ++) sum+=i; *s=sum; Pentru parametrii de tip masiv de date, transferul prin referință se face în mod implicit, deoarece numele masivului este pointer către zona de memorie unde este stocat masivul. Astfel următoarele prototipuri sunt echivalente: tip identificator1(int masiv[]); tip identificator2(int *masiv); Următorul exemplu prezintă o funcție pentru calculul sumei componentelor unui vector. Funcția suma are doi parametri de intrare. Citirea vectorului se realizează cu funcția citvector. Parametrul vector este transferat prin referință deoarece este un masiv de întregi. #include <stdio.h> #include <conio.h> void citvector(int k,int vector[]) { int i; for(i=0;i<k;i++) { printf("v [ %d ] = ",i); scanf("%d",&vector[i]); } } int suma(int k,int vector[]) { int s = 0,i; for(i=0; i<k; i++) s+=vector[i]; 181


Algoritmică şi programare

return s; } int main() { int n,v[100],i; printf("Numar de componente n = "); scanf("%d",&n); citvector(n, v); printf("Suma componentelor este: %d\n", suma(n,v)); getch(); } Variabilele globale se declară în afara funcțiilor, inclusiv în afara funcției main() și pot fi referite din orice alte funcții, inclusiv din funcția main(). Astfel, transferul de parametri între funcția apelantă și cea apelată se poate face și prin variabile globale. Pentru exemplificare, reluăm unul dintre exemplele anterioare. Parametrii de intrare ai funcției suma sunt declarați ca variabile globale. Funcția suma are lista parametrilor formali vidă. #include <stdio.h> #include <conio.h> int n, vector[100]; // variabile globale void citvector() { int i; for(i=0;i<n;i++) { printf(" v [ %d ] = ",i); scanf("%d", &vector[i]); } } int suma() { int s = 0,i; for(i=0;i<n;i++) s+=vector[i]; return s; } int main() { 182


Algoritmică şi programare

int i; printf(" Numar de componente n = "); scanf("%d",&n); citvector(); printf("Suma componentelor este: %d\n",suma()); getch(); } Cele două metode de transmitere a parametrilor (prin variabile globale și prin parametri) pot fi folosite împreună. De exemplu, declarăm numai vectorul vector variabilă globală. Funcția suma are un singur parametru formal k. #include <stdio.h> #include <conio.h> int vector[100]; // Declararea variabilei globale vector void citvector(int k) { int i; for(i=0;i<k;i++) { printf(" v [ %d ] = ",i); scanf("%d", &vector[i]); } } int suma(int k) //parametru format { int s = 0,i; for(i=0;i<k;i++) s+=vector[i]; return s; } int main() { int n; printf(" Numar de componente n = "); scanf("%d",&n); citvector(n); printf("Suma componentelor: %d\n", suma(n)); getch(); }

183


Algoritmică şi programare

Descompunerea Goldbach #include <stdio.h> #include <conio.h> int prim(int); int main() // Definitia functiei principale { int n,i,dif,p; printf(" n = "); scanf("%d",&n); if(n%2==0) { i=2; while(i<n-1) { if(prim(i)) { p=i; i++; } else i++; } dif=n-p; printf(" %d = %d + %d \n",n,dif,p); } else printf(" %d nu este numar par \n",n); getch(); } int prim(int n) // Functia prim testează dacă numarul n este prim { int i,p; p=1; for(i=2;i<=n/2;i++) if(n%i==0) p=0; return p; }

184


Algoritmică Ĺ&#x;i programare

Proprietatea Goldbach pe mulțimea{3,4,...,k} #include <stdio.h> #include <conio.h> int prim(int); int main() // Definitia functiei principale { int k,n,i,dif,p; printf(" k = "); scanf("%d",&k); for(n=4;n<=k;n++) { if(n%2==0) { i=2; while(i<n-1) { if(prim(i)) { p=i; i++; } else i++; } dif=n-p; printf(" %d = %d + %d \n",n,dif,p); } } getch(); } int prim(int n) //n este prim ? { int i,p; p=1; for(i=2;i<=n/2;i++) if(n%i==0) p=0; return p; }

185


Algoritmică şi programare

Adrese de memorie. Pointeri Orice variabilă are două elemente caracteristice: • Valoarea pe care o are variabila la un moment dat (int a=10;) • valoarea adresei locației de memorie (&a). O zonă de memorie este o succesiune 1, 2, 4, 8 sau mai multe locații consecutive de memorie (octeți). O variabilă se memorează într o zonă de memorie de o anumită lungime, funcție de tipul ei: o variabilă de tip char se memorează pe 1 octet, o variabila de tip int sau float pe 4 octeți iar un double pe 8 octeți. • printf("Dimensiunea int=%d\n", sizeof (int)); • printf("Dimensiunea float=%d\n", sizeof (float)); • printf("Dimensiunea char=%d\n", sizeof (char)); • printf("Dimensiunea double=%d\n", sizeof (double)); • printf("Dimensiunea u int=%d", sizeof (unsigned int)); Adresa locației de memorie în care este stocată valoarea unei variabile se poate obține aplicând operatorul de adresă (operatorul &) înaintea numelui variabilei (operație întâlnită frecvent în cazul apelării funcției scanf). Operatorul de adresă poate fi utilizat pentru obținerea valorii adresei oricărei variabile. Exemplu: int a=2; printf("Adresa lui a=%d\n",&a); Operatorul &, numit "de adresa", furnizează adresa zonei de memorie unde este memorata valoarea variabilei a. Manipularea datelor se face cu ajutorul: • identificatorilor (nume de variabile); • adreselor de memorie ale variabilelor si conținutul zonei de memorie de la adresa respectiva (pointerilor) Mecanismul transmiterii parametrilor prin intermediul pointerilor este o extensie a transmiterii prin referință. Un pointer este o variabilă care are ca valoare adresa unei zone de memorie a unui obiect (o variabila sau o funcție). Pointerul este o variabilă care poate memora numai adrese de memorie. Fie T = un tip de data standard(int, double, char, etc.)

186


Algoritmică şi programare

Fie T* = un nou tip de data de tip pointer (o variabila de tip pointer are ca valoare adresele variabilelor de tip pointer). int *p; //variabila de tip pointer si poate sa memoreze numai adrese de memorie de variabile ale căror valori sunt de tip întreg float *f //variabila de tip pointer si poate sa memoreze numai adrese de memorie de variabile ale căror valori sunt de tip float T*p //p este o variabila pointer si va conține adrese de memorie alocate unor date de tip T // * semnifica faptul ca variabila p este pointer la tipul de data T Pointerii se utilizează pentru a face referire (a avea acces) la valoarea unei variabile atunci când se cunoaște adresa ei. Astfel, un pointer nu reprezintă numai adresa unei variabile ci si: • adresa unei zone de memorie; • tipul variabilei (int, char, double etc.) care este memorată în acea zonă de memorie. int a=2,*p; printf("a=%d, Adresa lui a=%d\n",a,&a); p=&a; //operatorul & furnizează adresa zonei de memorie unde este memorata valoarea variabilei a printf("p=%d, Adresa lui a=%d\n",p,&a); printf("p=%d, Adresa lui a=%d\n",p,p); În general, un pointer poate fi inițializat ca orice altă variabilă deși, în mod normal, singurele valori care au sens sunt zero sau o expresie care implică adresele unor date definite anterior. Valorile si formatul adreselor de memorie depind de arhitectura calculatorului si de sistemul de operare sub care rulează. Din motive de portabilitate, la declararea unei variabile care conține o adresă de memorie se va utiliza un specificator de format special: %p, pentru tipărirea valorilor reprezentând adrese de memorie. Sunt doua mari categorii de pointeri: • pointeri către variabile • pointeri către funcții. Avantajele utilizării pointerilor sunt: • oferă posibilitatea de a modifica argumentele de apelare a funcțiilor, • permite alocarea dinamică a memoriei, • permit exprimarea unor operații la un nivel mai scăzut, • conduc la un cod mai compact si mai eficient,

187


Algoritmică şi programare

îmbunătățesc eficienta anumitor rutine.

Pentru a obține valoarea obiectului indicat de un pointer se utilizează operatorul de dereferenţiere sau indirectare (operatorul *). int a=2,*p; /*definesc o variabila de tip întreg si o variabila p pointer la întreg care va conține adrese de memorie in care se memorează valori întregi*/ p=&a; //operatorul & furnizează adresa zonei de memorie unde este memorata valoarea variabilei a printf("a=%d, Adresa lui a=%d\n",a,&a); printf("a=%d, Adresa lui a=%d\n",a,p); printf("a=%d, Adresa lui a=%d\n",*p,&a); printf("a=%d, Adresa lui a=%d\n",*p,p); //operatorul unar *, numit de "dereferenţiere", sau "indirectare" *p=20; //reinițializare variabila a (a=20) printf("a=%d, Adresa lui a=%d\n",a,&a); printf("a=%d, Adresa lui a=%d\n",a,p); printf("a=%d, Adresa lui a=%d\n",*p,p); Se pot declara si pointeri generici, de tip void* numit pointerul vid (tipul datelor indicate de ei nu este cunoscut). Un pointer de tip void reprezintă doar o adresă de memorie a unui obiect oarecare: • dimensiunea zonei de memorie indicate si interpretarea informației conținute, nu sunt definite; • poate apare în atribuiri, în ambele sensuri, cu pointeri de orice alt tip; • folosind o conversie explicită de tip cu operatorul cast:(tip *), poate fi convertit la orice alt tip de pointer (pe același calculator, toate adresele au aceeași lungime) void*p //se definește un pointer către tipul vid si care poate sa memoreze adrese de tipuri diferite (float, int, etc.) float a; p=&a; p=*(float*)a; se memorează valoarea adresei variabilei a de tip real (pentru modificarea valorii conținutului pointerului)

188


Algoritmică şi programare

Exemplu:

Operatorii de adresare si dereferenţiere Ca si în cazul variabilelor de orice tip, variabilele pointer declarate si neinițializate conțin valori aliatoare. Pentru a atribui variabilei p valoarea adresei variabilei a, se folosește operatorul de adresă într-o expresie de atribuire de forma: 189


Algoritmică şi programare

p=&a; În urma acestei operații, se poate spune că pointerul p indică spre variabila a. Pentru a obține valoarea obiectului indicat de un pointer se utilizează operatorul de dereferenţiere sau indirectare (operatorul *). Dacă p este o variabilă de tip pointer care are ca valoare adresa locației de memorie a variabilei întregi a (p indică spre a) atunci expresia *p reprezintă o valoare întreagă (valoarea variabilei a). int a=5, *p; p=&a; *p=6; // variabila a ia valoarea 6 Când se utilizează operatorul de dereferenţiere (indirectare) trebuie avut grijă ca variabila pointer asupra căreia se aplică să fi fost inițializată, adică să conțină adresa unei locații de memorie valide. Secvența următoare este incorectă si poate genera erori la execuție: greșit

corect

int a, *p; p=&a; *p=6;

int a=5, *p; p=&a; *p=6;

Variabilele pointer pot fi utilizate în expresii si direct, fără indirectare. int a=5, *p, *q; p=&a; q=p; // atribuire de pointeri - se copiază conținutul lui p in q, rezulta ca q indica spre același obiect ca p Pe scurt: • • •

Un nume de variabilă referă direct o valoare; Un pointer referă indirect o valoare; Referirea unei valori printr-un pointer se numește indirectare.

Toate operațiile de manipulare a pointerilor iau în considerare în mod automat dimensiunea obiectului spre care se indică. Pointerii pot fi folosiți în expresii aritmetice, asignări și comparații, însă nu toți operatorii pot avea pointeri ca operanzi. Asupra pointerilor pot fi realizate operații de: • incrementare (++) 190


Algoritmică şi programare

• decrementare (--) • adăugare a unui întreg (+ sau +=) • scădere a unui întreg (- sau -=) • scădere a unui pointer din alt pointer Operațiile permise cu pointeri sunt: • atribuirea pointerilor de același tip; • adunarea unui pointer cu un întreg sau scăderea unui întreg; • scăderea sau compararea a doi pointeri care indică spre elementele aceluiași tablou; • atribuirea valorii zero (NULL) sau compararea cu aceasta. Un pointer poate fi asignat altui pointer doar dacă cei doi au același tip. În caz contrar, trebuie aplicată o operație de conversie pentru ca pointerul din dreapta asignării să fie adus la tipul pointerului din stânga. Excepție de la această regulă în face pointerul void* care este un tip generic și poate reprezenta orice tip de pointer fără a mai fi nevoie de cast. Pe de altă parte, pointerul void* nu poate fi nepreferențiat pentru că numărul de byți corespunzător lui nu poate fi determinat de compilator Sunt ilegale următoarele operații aritmetice asupra pointerilor: • adunarea a doi pointeri; • înmulțirea sau împărțirea pointerilor; • deplasarea sau aplicarea unor măști; • adunarea cu valori de tip float sau double ; • atribuirea a doi pointeri de tipuri diferite fără a folosi operatorul cast (cu excepția tipului void *).

Pointeri la funcții Deoarece funcțiile sunt considerate variabile globale într-un program scris în limbajul de programare C/C++ are sens noțiunea de pointer la o funcție. Numele unei funcții este un pointer care indică adresa de memorie unde începe codul executabil al funcției. Aceasta permite transmiterea funcțiilor ca parametri în alte funcții, precum și lucrul cu tabele de funcții. Considerăm dată definiția unei funcții: tip nume(tip 1 pf 1 , tip 2 pf 2 ,..., tip n pf n ) { corp funcție }

191


Algoritmică şi programare

în care: tip este tipul valorii returnată de funcție, nume este identificatorul (numele) funcției, tip 1 este tipul parametrului formal pf 1 , tip 2 este tipul parametrului formal pf 2 , ..., tip n este tipul parametrului formal pf n . Un pointer notat cu p la funcţia astfel definită se declară astfel: tip (*p)( tip1, tip2,..., tipn); în care: tip este tipul valorii returnată de funcţie, p este pointerul la funcţie, tip 1 , tip 2 ,..., tip n sunt tipurile parametrilor formali ai funcţiei. Iniţializarea pointerului se realizează cu sintaxa: tip (*p)( tip1, tip2,..., tipn)=nume; în care nume este numele funcţiei. Unui pointer la o funcţie i se poate atribui numai numele oricărei funcţii care are prototip identic cu prototipul funcţiei spre care pointează: tip numep( tip1, tip2,..., tipn); în care nume p este numele unei funcţii oarecare cu acelaşi prototip. Atribuirea unei adrese de funcţie unui pointer la o funcţie se face cu sintaxa p=nume; Lansarea în execuţie a funcţiei respective prin intermediul pointerului p la funcţia respectivă se face cu sintaxa: (*p)(pa1, pa2, ..., pan); în care pa 1 , pa 2 , ..., pa n sunt parametrii actuali de apel ai funcţiei.

Aplicații practice Suma elementelor unui vector fără pointeri #include <stdio.h> #include<conio.h> int s=0,i,n,a[25]; main() {printf("Stabiliti numarul de elemente ale vectorului, n<=25:"); scanf("%d",&n); // initializare elemente vector for(i=0;i<n;i++) { printf("a[%d]=",i); 192


Algoritmică Ĺ&#x;i programare

scanf("%d",&a[i]); } // afisare elemente vector printf("Elementele vectorului sunt:\n"); for(i=0;i<n;i++) printf("a[%d]=%d\n",i,a[i]); //suma elemente vector for(i=0;i<n;i++) s=s+a[i]; printf("Suma elementelor vectorului=%d\n",s); //system("pause"); return 0; } Suma elementelor unui vector cu pointeri #include <stdio.h> #include<conio.h> int s=0,i,n,a[25],*p; main() {printf("Stabiliti numarul de elemente ale vectorului, n<=25:"); scanf("%d",&n); // initializare elemente vector for(i=0;i<n;i++) { printf("a[%d]=",i); scanf("%d",&a[i]); } // afisare elemente vector p=&a[0]; printf("Elementele vectorului sunt:\n"); for(i=0;i<n;i++) printf("a[%d]=%d\n",i,*p+i); //suma elemente vector for(i=0;i<n;i++) s=s+*(p+i); printf("Suma elementelor vectorului=%d\n",s); //system("pause"); return 0; }

193


Algoritmică şi programare

Suma elementelor unui vector utilizând pointeri la o funcție #include <stdio.h> #include<conio.h> int suma_vector(int i, int n, int *p)//suma elemente vector { int s=0; for(i=0;i<n;) { s+=(*p+i); i++; } printf("Suma elemente vector=%d\n",s); return s; } //----------------------main() { int i,n,a[25],*p; p=&a[0]; printf("Stabiliti numarul de elemente ale vectorului, n<=25:"); scanf("%d",&n); printf("Inserati elementele vectorului:\n"); // initializare elemente vector for(i=0;i<n;i++) { printf("a[%d]=",i); scanf("%d",&a[i]); } suma_vector(i,n,a); //system("pause"); return 0; } Test palindrom #include<stdio.h> #include<stdlib.h> #include<conio.h> int main() { int n, i, pal=1; int *vector; printf("Dimensiunea vectorului n:");

194


Algoritmică Ĺ&#x;i programare

scanf("%i", &n); vector = (int*) malloc(sizeof(int) * n); for(i=0; i<n; ++i) { printf("Introduceti v[%d]:", i); scanf("%i", &vector[i]); } for(i=0; i<n/2 && pal == 1; ++i) if(vector[i] != vector[n-i-1]) pal = 0; if(pal == 1) printf("Vectorul este palindrom.\n"); else printf("Vectorul nu este palindrom.\n"); free(vector); getch(); } Implementare operații aritmetice elementare // Operatiile aritmetice +,-,*,% cu intregi #include "stdio.h" #include "conio.h" int suma(int, int); // prototipul functiei suma int dif(int, int); // prototipul functiei dif int prod(int, int); // prototipul functiei prod int mod(int, int); // prototipul functiei mod void afis(int, int, int, char); // prototipul functiei afis // Functie care returneaza suma argumentelor a si b int suma(int a, int b) { return a+b; } // Functie care returneaza diferenta argumentelor a si b int dif(int a ,int b) { return a-b; } // Functie care returneaza produsul argumentelor a si b int prod(int a, int b) { return a*b; } // Functie care returneaza restul impartirii intregi a argumentelor a si b

195


Algoritmică Ĺ&#x;i programare

// Daca b==0 functia returneaza valoarea -1 int mod(int a, int b) { if(b) return a%b; else return -1; } // Functie pentru afisarea rezultatului rez al operatiei op intre argumentele a si b void afis(int a, int b, int rez, char op) { printf(" %d %c %d = %d \n",a,op,b,rez); } int main() { char op[4]={'+','-','*','%'}; // Lista operatorilor int i,rez,a,b; printf(" Inserati a: "); scanf("%d",&a); printf(" Inserati b: "); scanf("%d",&b); printf("\n Rezultatele operatiilor:\n\n"); rez=suma(a,b); // calcul suma afis(a,b,rez,op[0]); // afisare rezultat suma rez=dif(a,b); // calcul diferenta afis(a,b,rez,op[1]); // afisare rezultat diferenta rez=prod(a,b); // calcul produs afis(a,b,rez,op[2]); // afisare rezultat produs rez=mod(a,b); // calcul modulo if(rez != -1) // daca b != 0 afis(a,b,rez,op[3]); // afisare rezultat modulo else { printf(" Operatia %d %c %d este nedefinita \n",a,op[3],b); printf(" Impartire la zero \n"); } getch(); }

196


Algoritmică şi programare

Implementare operații aritmetice elementare folosind pointeri la funcţii int main() { int (*ps)(int,int)=suma; // ps pointer la functia suma int (*pd)(int,int)=dif; // pd pointer la functia dif int (*pp)(int,int)=prod; // pp pointer la functia prod int (*pm)(int,int)=mod; // pm pointer la functia mod void (*pa)(int,int,int,char)=afis; // pa pointer la functia afis char op[4]={'+','-','*','%'}; // lista operatorilor int i,rez,a,b; printf(" Inserati a: "); scanf("%d",&a); printf(" Inserati b: "); scanf("%d",&b); printf("\n Rezultatele operatiilor:\n\n"); rez=(*ps)(a,b); // calcul suma (*pa)(a,b,rez,op[0]); // afisare rezultat suma rez=(*pd)(a,b); // calcul diferenta (*pa)(a,b,rez,op[1]); // afisare rezultat diferenta rez=(*pp)(a,b); // calcul produs (*pa)(a,b,rez,op[2]); // afisare rezultat produs rez=(*pm)(a,b); // calcul modulo if(rez != -1) // daca b != 0 (*pa)(a,b,rez,op[3]); // afisare rezultat modulo else { printf(" Operatia %d %c %d este nedefinita \n",a,op[3],b); printf(" Impartire la zero \n"); } getch(); } int main() { int (*p)(int,int); // p pointer la o functie cu prototipul int ... (int, int); // neinitializat void (*pa)(int,int,int,char)=afis; // pa pointer la o functie cu prototipul // void ... (int, int, int, char); // initializat cu functia afis char op[4]={'+','-','*','%'}; // lista operatorilor int i,rez,a,b; printf(" Inserati a: "); scanf("%d",&a); printf(" Inserati b: "); scanf("%d",&b); printf("\n Rezultatele operatiilor:\n\n"); 197


Algoritmică Ĺ&#x;i programare

p=suma; // p pointeaza functia suma rez=(*p)(a,b); // calcul suma (*pa)(a,b,rez,op[0]); // afisare rezultat suma p=dif; // p pointeaza functia dif rez=(*p)(a,b); // calcul diferenta (*pa)(a,b,rez,op[1]); // afisare rezultat diferenta p=prod; // p pointeaza functia prod rez=(*p)(a,b); // calcul produs (*pa)(a,b,rez,op[2]); // afisare rezultat produs p=mod; // p pointeaza functia mod rez=(*p)(a,b); // calcul modulo if(rez != -1) // daca b != 0 (*pa)(a,b,rez,op[3]); // afisare rezultat modulo else { printf(" Operatia %d %c %d este nedefinita \n",a,op[3],b); printf(" Impartire la zero \n"); } getch(); }

198


Algoritmică şi programare

Structuri și uniuni Structuri La nivel conceptual structurile sunt șabloane formate din date de diferite tipuri numite câmpuri, la care accesul se realizează printr-un nume simbolic, care se numește selector. Structura (înregistrare în alte limbaje) este o selecție de variabile de diferite tipuri. Rezultă că sub un nume comun vom putea grupa variabile de diferite tipuri. Structura se declara înaintea funcției main(). Sintaxa unei structuri este următoarea: struct [nume] { tip 1 var 1 ; tip 2 va r2 ; ……………. tip n var n ; }; struct: cuvânt cheie care identifică ceea ce urmează ca fiind o definiție de structură. nume: parte opțională a definiției de structură.

Fig. 62 Structura

199


Algoritmică şi programare

Aceasta permite declararea unor variabile cu aceeași structură în diferite segmente de program. Între acolade se poate declara un set de variabile, având tipuri diferite de date. Structurile pot fi folosite independent sau în combinație cu alte tipuri de date. Se pot enumera: • structuri simple; • tablouri de structuri; • structuri conținând tablouri; • pointeri la structuri; • structuri auto-referite (liste). Exemplu: #include <stdio.h> #include <conio.h> int main() { struct student { char nume[20]; char prenume[20]; int nota; } student[25], aux; int i,j,n; printf("Numar studenti:"); scanf("%d",&n); for(i=0;i<n;i++) { printf("Nume si prenume student %d:", i+1); scanf("%s%s",student[i].nume, student[i].prenume); printf("Nota studentului %s %s:", student[i].nume, student[i].prenume); scanf("%d",&student[i].nota); } for(i=0;i<n;i++) for(j=i+1;j<n;j++) if(student[i].nota < student[j].nota) { aux=student[i]; student[i]=student[j]; student[j]=aux; } printf("Rezultatele grupei:\n");

200


Algoritmică Ĺ&#x;i programare

for(i=0;i<n;i++) printf("%s %s\t%d\n", student[i].nota); getch(); }

student[i].nume,

student[i].prenume,

sau #include <stdio.h> #include <stdlib.h> int main() { struct student{ char nume[20]; int mate,fiz,chim; float med; }student[30],aux; int i,j,n; printf("Numar studenti:"); scanf("%d",&n); for(i=0;i<n;i++) { printf("Numele studentului %d:",i+1); scanf("%s",&student[i].nume); printf("Notele studentului %s la disciplina matematica :",student[i].nume); scanf("%d",&student[i].mate); printf("Notele studentului %s la disciplina fizica :",student[i].nume); scanf("%d",&student[i].fiz); printf("Notele studentului %s la disciplina chimie :",student[i].nume); scanf("%d",&student[i].chim); student[i].med=(student[i].mate+student[i].fiz+student[i].chim)/3; } for(i=0;i<n;i++) for(j=i+1;j<n;j++) if(student[i].med>student[j].med) 201


Algoritmică şi programare

{aux=student[i]; student[i]=student[j]; student[j]=aux; } printf("Rezultatele studentilor in ordine descrescatoare : \n"); for(i=0; i<n; i++) printf("%s are media %.2f\n",student[i].nume,student[i].med); getch(); } Tip

Variabile

Pointeri

Sintaxa

struct tag_name { data type var_name1; data type var_name2; data type var_name3; };

struct tag_name { data type var_name1; data type var_name2; data type var_name3; };

Ex:

struct student { int nr_matricol; char nume[10]; float medie; };

struct student { int nr_matricol; char nume[10]; float medie; };

Declarare variabile structura

struct student raport;

struct student *raport, rap;

Initializare variabile struct

struct student raport = {100, “Ion”, 9.5};

struct student rap = {100, “Ion”, 9.5};raport = &rap;

Scriere membri struct

raport.nr_matricol raport.nume raport.medie

raport>nr_matricol raport->nume raport->medie

(*raport).nr_matricol (*raport).nume (*raport).medie

Tabel 10 Structuri

Structurile pot participa la formarea unor tipuri de date, in cazul de mai jos, un tablou unidimensional care are componentele de tip structuri.

202


Algoritmică Ĺ&#x;i programare

#define Marime_nume 20 #define Marime_adresa 20 #define Nrpers 20 #include <stdio.h> main() { struct data {int zi; int luna; int an; }; struct persoana {char nume [Marime_nume]; char adresa[Marime_adresa]; struct data data_nastere; char nrtelefon; }baza_personal[Nrpers]; }

Pointeri către structuri Exemplu: # include "stdio.h" struct Student { char nume[20]; char prenume[20]; int varsta; char sex; float m_bac; }; main() { struct Student *stud, p; stud=&p; printf("Inserati Nume:"); printf("Inserati Prenume:");

gets((*stud).nume); gets((*stud).prenume);

printf("\n\nNumele studentului este:%s\n", (*stud).nume); printf("Prenumele studentului este:%s\n", (*stud).prenume); }

203


Algoritmică şi programare

Typedef. Crearea unor noi tipuri de date Programatorul, cu ajutorul construcției typedef, poate atribui un anumit nume unui tip de data indiferent de faptul că acesta este un tip predefinit sau definit propriu de utilizator. Formatul este următorul: typedef tip identificator; tip – poate fi numele tipului predefinit, o declarație de tip structura sau tablou. identificator – numele care se atribuie tipului respectiv. Exemplu: #include "stdio.h" main() { typedef int intreg; intreg an_nastere; an_nastere=1999; printf("Sunt nascut in anul %d", an_nastere); } Typedef de tip Structura # include "stdio.h" typedef struct Student { char nume[20]; char prenume[20]; int varsta; char sex; float m_bac; }Student; main() { Student stud; printf("Inserati Nume:"); gets(stud.nume); printf("Inserati Prenume:"); gets(stud.prenume); printf("Inserati varsta:"); scanf("%d", &stud.varsta); fflush(stdin); //curatarea bufferului printf("Inserati sexul (M/F):"); scanf("%c", &stud.sex); printf("Inserati media obtinuta la BAC:"); scanf("%f", &stud.m_bac);

204


Algoritmică şi programare

printf("\n\nNumele studentului este:%s\n", stud.nume); printf("Prenumele studentului este:%s\n", stud.prenume); printf("Varsta studentului este:%d\n", stud.varsta); printf("Sexul studentului este:%c\n", stud.sex); printf("Media obtinuta la BAC este:%.2f\n", stud.m_bac); }

Uniuni Uniunile sunt asemănătoare structurilor, utilizează o singura locație de memorie pentru a stoca mai mult de o singura variabila, adică o colecție de date de tipuri și mărimi diferite. O uniune se declară printr‑o construcție asemănătoare declarației de structură. Fiecare element al uniunii se numește membru. union [nume] { tip 1 var 1 ; tip 2 var 2 ; ……………. tip n var n ; }; union: cuvânt cheie care identifică ceea ce urmează ca fiind o definiție de tip union. nume: parte opțională a definiției uniunii. Între acolade se poate declara un set de variabile, având tipuri diferite. Exemplu: union Student { char nume[20]; char prenume[20]; int varsta; char sex; float m_bac; }; Tip Sintaxa

Variabile union tag_name { data type var_name1;

Pointeri union tag_name { data type var_name1; data type var_name2;

205


Algoritmică şi programare

Tip

Variabile

Pointeri

data type var_name2; data type var_name3; };

data type var_name3; };

Ex:

union student { int nr_matricol; char nume[10]; float medie; };

union student { int nr_matricol; char nume[10]; float medie; };

Declarare variabile uniune

union student raport;

union student *raport, rap;

Initializare variabile uniune

union student raport = {100, “Ion”, 9.5};

union student rap = {100, “Ion”, 9.5}; raport = &rap;

Scriere membri uniune

raport.nr_matricol raport.nume raport.medie

raport->nr_matricol raport->nume raport->medie

Tabel 11 Uniuni

Exemplu: # include "stdio.h" union Student { char nume[20]; char prenume[20]; int varsta; char sex; float m_bac; }; main() { union Student stud; printf("Inserati Nume:"); gets(stud.nume); printf("\n\nNumele studentului este:%s\n", stud.nume); printf("Inserati Prenume:"); gets(stud.prenume); printf("Prenumele studentului este:%s\n", stud.prenume); 206


Algoritmică şi programare

printf("Inserati varsta:"); scanf("%d", &stud.varsta); printf("Varsta studentului este:%d\n", stud.varsta); fflush(stdin); //curatarea bufferului printf("Inserati sexul (M/F):"); scanf("%c", &stud.sex); printf("Sexul studentului este:%c\n", stud.sex); printf("Inserati media obtinuta la BAC:"); scanf("%f", &stud.m_bac); printf("Media obtinuta la BAC este:%.2f\n", stud.m_bac); } Afișarea in acest caz este corecta deoarece de fiecare data este utilizat doar un membru al uniunii.

Fig. 63 Afișare uniune

Exemplu: #include <stdio.h> #include <conio.h> #include <string.h> union student { char nume[20]; char disciplina[20]; float media; }; int main() { union student inregistrare1; union student inregistrare2; // inserare valori in inregistrare 1 – se observa alterarea datelor strcpy(inregistrare1.nume, "Radu"); strcpy(inregistrare1.disciplina, "Matematica"); inregistrare1.media = 8.50; 207


Algoritmică şi programare

printf("Union inregistrare1 values example\n"); printf(" nume : %s \n", inregistrare1.nume); printf(" disciplina : %s \n", inregistrare1.disciplina); printf(" media : %f \n\n", inregistrare1.media); // inserare valori in inregistrare2 printf("Union inregistrare2 values example\n"); strcpy(inregistrare2.nume, "Manuel"); printf(" nume : %s \n", inregistrare2.nume); strcpy(inregistrare2.disciplina, "Fizica"); printf(" disciplina : %s \n", inregistrare2.disciplina); inregistrare2.media = 9.50; printf(" media : %f \n", inregistrare2.media); return 0; }

Structuri și uniuni. Diferențe Se înlocuiește cuvântul cheie struct cu union. Din exemplele anterioare rezultă că aceeași zonă de memorie poate fi interpretată în mod diferit. Nr.crt

Structuri

Uniuni

1

Se aloca spațiu separate pentru toți membrii structurii

Se aloca un spațiu comun de memorie egala cu cea mai mare dimensiune a unui membru

2

Structura ocupa mai mult spațiu (=suma dimensiunilor tuturor membrilor)

Uniunile ocupa mai puțin spațiu

3

Se pot accesa toți membrii structurii simultan

Se poate accesa cate1 membru

4

struct student { int nr_matricol; char nume[10]; float medie; };

union student { int nr_matricol; char nume[10]; float medie; };

5

Spațiul total ocupat de structura= 4+10+4=18 octeți (Bytes)

Spațiul total ocupat de uniune= max(4,10,4)=10 octeți (Bytes)

208


Algoritmică Ĺ&#x;i programare

Exemplu: #include <stdio.h> #include <string.h> union Data1 { int i; float f; char sir1[20]; }; struct Data2 { int j; float k; char sir2[20]; }; int main( ) { union Data1 u1; printf( "Memory size occupied by union : %d\n", sizeof(u1)); struct Data2 u2; printf( "Memory size occupied by struct : %d\n", sizeof(u2)); return 0; }

209


Algoritmică şi programare

Directive pre-procesor Limbajul C/C++ prezintă o particularitate în raport cu alte limbaje compilate, prin aceea că dispune de un pre procesor înglobat în sistemul său de compilare. Înainte de începerea traducerii, are loc o prelucrare inițială a programului sursă, prin care sunt analizate eventuale opțiuni de lucru transmise compilatorului. Există un număr de operații care pot să fie realizate în această fază de prelucrare inițială și care sunt formulate prin directive pre-procesor. Anterior au fost deja întâlnite directivele #define (prin care s a dat un nume unei constante) și #include (pentru introducerea unor fișiere sursă suplimentare). Pre-procesorul permite realizarea următoarelor operații: • înlocuirea unor denumiri prescurtate prin șiruri de caractere, operație denumită macro-substituție: directiva #define; • includerea unor fișiere suplimentare în textul sursă: directiva #include; • compilarea selectivă a unor părți din programul sursă. Directivele pre-procesor pot să apară oriunde în cadrul textului programului: la începutul textului sursă, la sfârșitul textului, sau intercalat printre instrucțiuni. O directivă este valabilă de la locul apariției, până la sfârșitul fișierului. Din exemplul anterior am reținut faptul că în limbajul C, nu exista tipul Boolean, el fiind definit sub forma: #define BOOL char #define FALSE 0 #define TRUE 1

Macro-definiții Macro definițiile sunt similare funcțiilor, prin aceea că permit reprezentarea unor operații simple printr-un nume simbolic. Folosirea macro-definițiilor este de obicei, un compromis între timpul de execuție și spațiul de memorie ocupat. Principalele avantaje ale macro-definițiilor sunt: • Macro-definiția este executată mai rapid decât o funcție; • nu există nici o restricție în ceea ce privește tipul parametrilor transmiși unei macro-definiții, astfel că aceeași macro-definiție poate fi folosită pentru mai multe tipuri de date.

210


Algoritmică şi programare

Dezavantajele folosirii macro-definițiilor: • de fiecare dată când se apelează o macro-definiție, aceasta este expandată conducând la un program de dimensiuni mai mari decât un program care folosește funcții corespunzătoare în locul macro-definițiilor; • corpul unei funcții este compilat o singură dată și toate apelurile acelei funcții folosesc aceeași instrucțiune mașină, fără a le repeta la fiecare apel, așa cum se întâmplă în cazul macro-definițiilor; • este mai greu să se corecteze un program care conține macrodefiniții, deoarece macro expandările introduc un nivel suplimentar la traducerea programului sursă, iar codul obiect obținut este diferit de programul sursă.

Directiva #define Numele simbolic ales ca prescurtare a șirului de caractere reprezintă „NUMELE MACRO-DEFINIŢIEI“, în timp ce textul asociat se numește „corpul macro-definiției“. #define IDENTIFICATOR sir_caractere Prin convenție, numele unei macro-definiții se scrie cu litere mari, pentru a putea deosebi un nume de macro-definiție de numele variabilelor, care se scriu cu caractere mici. Șirul de caractere înlocuiește toate aparițiile identificatorului găsite în cadrul programului. Exemplu: #define DIMENSIUNE 100 char sir[DIMENSIUNE]; va fi tradusă, în etapa de prelucrare inițială a textului, prin: char sir[100]; Cea mai frecventă utilizare a macro definițiilor este pentru înlocuirea unor valori numerice prin nume simbolice. Directiva #define poate să ia și o altă formă, care permite asocierea unor argumente numelui macro definiției, într un mod asemănător funcțiilor C/C++. #define identificator(lista parametri) corp_macro_definitie

211


Algoritmică şi programare

Exemplu: #define PATRAT(a) ((a)*(a)) Instrucțiunea: k=PATRAT(5) este tradusă în faza de prelucrare inițială în: k=((5)*(5)). Parametrul 5, a înlocuit parametrul a la expandarea macrodefiniției.

Comparație între macro-definiții și funcții C/C++ Din punct de vedere al efectului obținut, o macro-definiție nu se deosebește cu nimic de o funcție, macro-definiția introdusă anterior PATRAT(a) conduce la același rezultat ca și următoarea funcție: int pătrat (int a) { return a*a } Argumentele unei macro-definiții nu sunt variabile și nu au nici un tip asociat. În timp ce funcția pătrat(), așa cum a fost definită anterior, trebuie să primească un parametru de tip întreg și transmite un rezultat de tip întreg, la expandarea unei macro-definiții se pot furniza argumente de orice tip.

Directiva #undef O macro-definiție introdusă printr-o directivă #define își păstrează semnificația până la sfârșitul fișierului sursă, sau până când aceasta este invalidată în mod explicit cu ajutorul directivei #undef. Forma generală a directivei este: #undef nume_macro_definitie Directiva #undef se folosește, în scopul de a anula o macro-definiție, pentru a o putea redefini. Exemplu: #define DIMENS1 10 #define DIMENS2 20 char matrice[DIMENS1][DIMENS2]; ………. #undef DIMENS1 #undef DIMENS2

212


Algoritmică şi programare

Directive de compilare condiționată Este posibilă compilarea selectivă a unor anumite părți din programul sursă și omiterea celorlalte părți ale programului, folosind directive de compilare condiționată. Aceste directive sunt similare instrucțiunilor if-else din limbajul C/C++. Compilarea selectivă este folosită pe scară largă în cazul unor programe de aplicații care urmează să fie adaptate diferitelor cerințe ale utilizatorilor. Se pot elimina anumite părți din program și/sau reintroduce prin simpla modificare a unei macro-definiții. Aceste directivele sunt: #if, #else, #elif și #endif

Directivele #if, #else, #elif și #endif Directiva #if realizează compilarea unei succesiuni de instrucțiuni din textul sursă, în cazul în care condiția indicată prin directiva if este adevărată; în caz contrar, instrucțiunile nu sunt luate în considerare de către compilator. Sfârșitul blocului de instrucțiuni care urmează să fie compilat condiționat este indicat printr-o directivă #endif. Forma generală a directivei este: #if expresie_constanta ………../*instrucțiuni C/C++*/ #endif Exemplu: #include <stdio.h> #define DIMENSIUNE 20 int main(void) { #if DIMENSIUNE > 10 printf (“Compilarea s-a făcut pentru DIMENSIUNE 10 \n); #endif } Directiva #else oferă o alternativă de compilare atunci când condiția menționată în directiva #if nu este îndeplinită. Structura obținută este similară instrucțiunii if else din limbajul C/C++. Exemplul anterior poate să fie completat astfel: #include <stdio.h> #define DIMENSIUNE 5

213


Algoritmică şi programare

int main(void) { #if DIMENSIUNE>10 printf (“Compilarea s-a facut pentru DIMENSIUNE>10 \n"); #else printf (“Compilarea s-a facut pentru DIMENSIUNE<10 \n"); #endif } Fiecărei directive #if i se asociază o singură directivă #endif. În situația în care este nevoie să se compileze un bloc de instrucțiuni, din mai multe alternative existente, se va folosi directiva #elif, al cărei nume este o prescurtare de la else-if. Directiva #elif conține și ea o condiție sub forma unei expresii constante. Dacă rezultatul expresiei este adevărat, se va compila blocul de instrucțiuni corespunzător acelei condiții și se vor neglija celelalte alternative. În cazul în care condiția este falsă, se analizează următoarea condiție. Forma generală a directivei este: #if expresie ……… /*instrucțiuni C/C++*/ #elif expresie 1 ……… /*instrucțiuni C/C++*/ #elif expresie 2 ……… /*instrucțiuni C/C++*/ ………. #elif expresie N ……… /*instrucțiuni C/C++*/ #endif Exemplu: #define SUA 0 #define ANGLIA 1 #define FRANTA 2 #define ROMANIA 3 #define TARA ROMANIA #if TARA ==SUA char moneda[]=”dolar”; #elif TARA==ANGLIA char moneda[]=”lire sterline”; 214


Algoritmică şi programare

#elif TARA==FRANTA char moneda[]=”franci”; #elif TARA==ROMANIA char moneda[]=”lei”; #endif

/*sau, #else*/

Unei directive #if i se pot asocia oricâte directive #elif dorim, așa cum se vede în exemplul anterior, dar numai o singură directivă #else.

Directivele #ifdef şi #ifnde Directivele #if și #elif permit compilarea succesivă a unor părți din programul sursă, pe baza valorii unei expresii constante. Există și o altă posibilitate de a realiza o compilare condiționată și anume, în raport cu existenta sau lipsa unei macro-definiții. În acest caz se folosesc directivele #ifdef (care este prescurtarea de la „if defined“) și #ifndef (care este prescurtarea de la „if not defined“). Forma generală a directivei #ifdef este: #ifdef nume_macro_definitie ……… /*instrucțiuni C/C++*/ #endif Dacă macro-definiția cu numele menționat a fost definită anterior printr-o directivă #define, succesiunea de instrucțiuni cuprinsă între #ifdef și #endif este compilată. În mod similar, directiva #ifndef realizează compilarea unei secvențe de instrucțiuni atunci când o macro-definiție cu numele indicat nu a fost definită anterior: #ifndef nume_macro_definitie ……….. /*instructiuni C/C++*/ #endif Atât directiva #ifdef cât și directiva #ifndef pot să fie combinate cu o directivă #else. Exemplu: #ifdef TEST printf(“TEST a fost definit. \n”); #else printf(“TEST nu a fost definit. \n”); #endif

215


Algoritmică şi programare

Dacă există o macro-definiție cu numele TEST, se compilează prima operație printf(); în caz contrar, este compilată cea de a doua operație de scriere. Indicarea sfârșitului blocurilor de instrucțiuni care urmează să fie compilate, se face ca și în cazul directivei #if cu ajutorul directivei #endif.

Directiva #include Directiva #include arată unde să se intercaleze conținutul unui fișier, în textul sursă în care apare directiva. Directiva are una din următoarele două forme: #include <nume_fisier> sau #include " nume_fisier “ Dacă fișierul este indicat între paranteze unghiulare, acesta va fi căutat într o listă de adrese (directoare) care sunt definite la instalarea compilatorului. Atunci când numele fișierului este indicat între apostrofuri duble, căutarea fișierului se face conform regulilor de căutare ale sistemului de operare: se începe cu directorul curent de lucru; dacă fișierul nu este găsit aici, se continuă căutarea cu directorii impliciți ce conțin fișierele de includere (header). Biblioteca de programe executabile a limbajului C/C++ cuprinde o serie de fișiere care conțin definiții ale funcțiilor din bibliotecă, numite fișiere „header“ (întrucât se plasează la începutul textului sursă). Atunci când un program folosește funcții standard, este nevoie să se includă în textul sursă și fișiere „header“ corespunzătoare, așa cum s-a procedat în toate programele anterioare. Programatorul poate să creeze fișiere „header“ proprii, conținând informații comune mai multor fișiere sursă, cum ar fi: definiții de structuri de date, macro-definiții, variabile globale necesare pentru comunicarea între fișiere. Fișierele „header“ folosesc în mod tradițional extensia .h. Ele asigură plasarea informațiilor comune într-un singur loc, pentru a elimina repetarea lor în fiecare modul sursă. Aceasta contribuie la simplificarea programării și facilitează modificările ulterioare. Un fișier care este inclus printr-o directivă #include poate să conțină în interiorul său alte directive #include. Se obțin astfel mai multe nivele de includere succesive. O astfel de situație este corectă din punct de vedere sintactic.

216


Algoritmică şi programare

Directiva #error Pentru etapa de punere la punct a unui program, utilizatorul are la dispoziție directiva #error. La întâlnirea unei directive #error compilarea se oprește și se afișează mesajul de eroare menționat prin directivă. Forma generală a directivei este: #error mesaj_eroare Mesajul de eroare conținut în directivă nu se pune între apostrofuri duble. Un exemplu de folosire a directivei pentru verificarea unor valori incorecte la compilarea condiționată, este: #if DIMENSIUNE_INTREG<16 #error DIMENSIUNE_INTREG prea mica #endif

217


Algoritmică şi programare

Funcții de intrare/ieșire Deși limbajul C/C++ nu conține nici o instrucțiune pentru citirea datelor (intrare) de la utilizator sau afișarea rezultatelor (ieșire) pe ecran, există un număr mare de funcții standard pentru operații de intrare/ieșire. Unele dintre aceste funcții se găsesc în biblioteca stdio.h (stdio este un acronim pentru STanDard Input Output) iar altele în conio.h (conio este un acronim pentru CONsole Input Output). Totodată, în fișierul stdio.h sunt definite constantele simbolice EOF (End Of File) și NULL. Aceste valori sunt returnate de diverse funcții când prelucrarea datelor s-a finalizat (datorită terminării datelor sau datorită unei erori).

Funcții de intrare/ieșire pentru caractere Funcțiile de intrare/ieșire pentru caractere au o utilizare limitată, fiind folosite pentru citirea și afișarea informației caracter cu caracter, fără nici o prelucrare în prealabil. Funcția int putchar (int c); int getchar();

int getch();

int getche();

int puts *s);

(char

char*gets(char*s )

Descriere Funcția afișează caracterul cu codul ASCII c pe ecran. Funcția returnează prin numele ei valoarea c sau EOF în caz de eroare. Funcția are prototipul în fișierul stdio.h. Funcția citește de la tastatură un singur caracter. Funcția getchar așteaptă apăsarea tastei Enter înainte de a returna primul caracter introdus. La următoarele apeluri, funcția returnează restul caracterelor tastate înainte de apăsarea tastei Enter. Funcția returnează prin numele ei codul ASCII al caracterului citit sau EOF dacă s-au terminat datele sau a apărut o eroare. Funcția are prototipul în fișierul stdio.h. Funcția citește fără ecou (fără a afișa pe ecran) un caracter de la tastatură. Funcția poate fi folosită pentru suspendarea temporară a execuției unui program. Funcția returnează codul ASCII al caracterului citit sau EOF în caz de eroare. Funcția are prototipul în fișierul conio.h. Funcția citește cu ecou (cu afișare pe ecran) un caracter de la tastatură. Funcția returnează codul ASCII al caracterului citit sau EOF în caz de eroare. Funcția are prototipul în fișierul conio.h. Funcția afișează pe ecran șirul de caractere memorat la adresa s și trece pe linia următoare. Funcția returnează prin numele său un număr nenegativ în caz de succes sau EOF în caz de eroare. Funcția citește un șir de caractere de la tastatura până la apăsarea tastei Enter și îl transferă în memorie la adresa s. Funcția returnează adresa s a șirului citit sau NULL dacă s-au terminat datele sau a apărut o eroare. Tabel 12 Funcții de intrare/ieșire pentru caractere[1]

218


Algoritmică şi programare

Exemplu: char c; puts("Tastati un caracter si apasati Enter"); c = getchar(); putchar(c);

Funcții de intrare/ieșire cu format Funcțiile cu formatare permit citirea și afișarea informației după o prelucrare în prealabil a acesteia. Prelucrarea se realizează conform unor coduri de format (descriptori de format) scrise de programator într-un șir de caractere. Funcția int printf (char *frmt, var);

int scanf (char *frmt, var);

Descriere Funcția scrie pe ecran șirul frmt în care codurile de format sunt înlocuite cu valorile expresiilor marcate prin var. Funcția returnează numărul de caractere scrise sau EOF în caz de eroare. Funcția citește de la tastatură date conform șirului frmt și le transferă în memorie la adresele marcate prin var. Funcția returnează numărul de coduri de format prelucrate corect sau EOF în caz de eroare.

Tabel 13 Funcții de intrare/ieșire cu format[1]

Exemplu: int n; printf("Introduceti n:"); scanf("%i", &n); // &n este adresa variabilei n printf("Valoarea lui n este: %i\n", n); Exemplu: Afișare tabela coduri ASCII. Programul listează pe fiecare linie, în ordine crescătoare coduri ASCII în baza 10, 8 și 16, și caracterul asociat acestora. După 22 de linii, execuția se suspendă până la apăsarea unei tastei. #include <stdio.h> #include <conio.h> int main() { int i; puts("Cod 10\tCod 8\tCod 16\tCaracter"); for(i=0;i<=255;i++) { printf("%3i\t%#4o\t%#4X\t%c\n", i, i, i, i);

219


Algoritmică şi programare

if(i % 22 == 21) { getch(); puts("Cod

10\tCod

8\tCod

16\t

Caracter"); } } }

Funcții cu formatare pentru șiruri de caractere Funcția sprintf funcționează similar cu funcția printf, cu excepția că scrierea datelor se va face într-un șir de caractere și nu pe ecran. În mod similar, funcția sscanf funcționează ca funcția scanf, cu excepția că citirea datelor se va face dintr-un șir de caractere și nu de la tastatură. Funcția int sprintf (char* dest, char *frmt, ...);

int sscanf (char* srs, char *frmt, ...);

Descriere Funcția scrie în șirul dest șirul frmt în care codurile de format sunt înlocuite cu valorile expresiilor marcate prin trei puncte. Funcția returnează prin numele său numărul de caractere scrise sau EOF în caz de eroare. Funcția citește din șirul srs date conform șirului frmt și le transferă în memorie la adresele marcate prin trei puncte. Funcția returnează prin numele său numărul de coduri de format prelucrate corect sau EOF în caz de eroare.

Tabel 14 Funcții cu formatare pentru șiruri de caractere[1]

Funcții pentru gestiunea timpului Funcțiile predefinite pentru gestiunea timpului folosesc tipul de dată tm definit prin structura tm definită în <time.h>. Aceste funcții lucrează cu tipul de dată tm și cu sinonimele pentru tipul long clock_t, time_t şi size_t. Structura tm are membri care definesc complet un moment de timp: struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year;

220

// secunda 0-59 // minut 0-59 // ora 0-23 // ziua din luna 1-31 // luna din an 0-11 // an incepand cu anul 1900


Algoritmică şi programare

int tm_wday; cu duminica int tm_yday; int tm_isdst; informatie indisponibila }; Funcția int clock (void);

double difftime (time_t time2, time_t time1); time_t time (time_t * timer);

tm * localtime (const time_t * timer); time_t mktime (struct tm * time_pointer);

// numarul zilei din saptamana 0-6 incepand // numarul zilei din an 0-365 // 1=se modifica ora (iarna/vara), 0= nu, <0

Descriere Funcția returnează prin numele său numărul de milisecunde care au trecut de la lansarea în execuție a unui program. Pentru a măsura durata de execuție a unor secvențe de program se folosesc două apeluri ale funcției și se calculează diferența dintre timpul returnat la apelul 2 și timpul returnat la apelul 1. În cazul unei erori funcția returnează valoarea -1. Funcția returnează un număr real care reprezintă diferența dintre două măsurători ale timpului time2time1. Funcţia citeşte data curentă şi o converteşte în număr de secunde care au trecut de la 1 ianuarie 1900 până la momentul curent. Altfel spus, funcţia converteşte data şi ora curentă la tipul time_t. Dacă funcţia se apelează cu parametrul NULL ea returnează numărul de secunde de mai sus. Funcţia actualizează structura tm folosind ora curentă.

tm * gmtime ( const time_t * timer );

Funcţia converteşte conţinutul pointerului time_pointer la structura tm la tipul time_t. Funcţia returnează timpul echivalent care a trecut de la 1 ianuarie 1900 măsurat în secunde până la momentul precizat de structura tm. În caz de eroare funcţia returnează valoarea -1. Funcția convertește conținutul pointerului time_pointer la structura tm în formatul extern de afişare a datei calendaristice şi anume şirul de caractere cu formatul zi luna data. Funcția convertește conținutul pointerului la tipul time_t în formatul extern de afişare a datei calendaristice şi anume şirul de caractere cu formatul zi luna data. Funcţia are acelaşi efect cu al funcţiei asctime. Deosebirea dintre cele două funcţii constă în faptul că asctime foloseşte structura tm iar ctime foloseşte tipul time_t. Funcția folosește tipul time_t și actualizează structura tm cu data curentă corelată cu meridianul curent.

size_t strftime ( char * ptr, size_t maxsize, const char * format, const struct tm * time );

Funcţia construieşte un şir de caractere care poate să conţină un mesaj şi diverşi specificatori de format de afişare a datei şi orei curente în diverse formate de

char * asctime (const struct tm * time_pointer);

char * ctime (const time_t * timer);

221


Algoritmică şi programare Funcția

Descriere afişare. Funcția returnează valoarea maxsize sau valoarea -1 în caz de eroare. Parametrii funcției sunt: • ptr pointer la tipul char - memorează șirul specificatorilor de format; • maxsize numărul maxim de caractere copiate în șirul pointat de ptr; • format şir de caractere - definește formatul de afișare al datei curente; • time pointer la tipul tm. Tabel 15 Funcții pentru gestiunea timpului[1] Semnificaţie

Specificator 1

2

3

%a

Numele prescurtat al zilei din săptămână *

Thu

%A

Numele complet al zilei din săptămână *

Thursday

%b

Numele prescurtat al lunii *

Aug

%B

Numele complet al lunii *

August

%c

Afişare dată şi oră *

Thu Aug 23 14:55:02 2001

%d

Afişare număr zi din lună (01-31)

23

%H

Afişare oră în format de 24 de ore (00-23)

14

%I

Afişare oră în format de 12 de ore (01-12)

02

%j

Afişare număr de zi din an (001-366)

235

%m

Afişare lună în format numeric (01-12)

08

%M

Afişare minut (00-59)

55

%p

Afişare AM sau PM

PM

%S

Afişare secundă (00-61)

02

%U

Numărul săptămânii din an în care prima zi de 33 duminică este prima zi din săptămână (00-53)

%w %W

222

Exemplu

Ziua din săptămână în care ziua de duminică este 4 ziua 0 (0-6) Numărul săptămânii în care ziua de luni este prima 34 zi din săptămână (00-53)

%x

Format numeric de afişare a datei *

08/23/01

%X

Format digital de afişare a orei *

14:55:02

%y

Afişare an prescurtat cu două cifre (00-99)

01

%Y

Afişare completă a anului

2001

%Z

Afişare zonă orară

CDT


Algoritmică şi programare Semnificaţie

Specificator %%

Afişare character %

Exemplu %

Tabel 16 Specificatorii de format pentru afișarea timpului

Exemplu: #include <stdio.h> #include <string.h> int main() { int day, year; char zi[20], luna[20], data[100]; strcpy( data, "Luni Martie 24 2018" ); sscanf( data, "%s %s %d %d", zi, luna, &day, &year ); printf("%d %s %d = %s\n", day, luna, year, zi ); day=29; year=2020; sprintf(data, "%d %d",day, year); printf("%d %s %d = %s\n", day, luna, year, zi ); return(0); }

Aplicații practice: utilizarea funcției clock Funcția măsoară timpul exprimat în milisecunde pentru durata execuției secvenței. #include <stdio.h> #include <conio.h> #include <time.h> int main() { int i,ts,tf; //ts timp de start, tf timp final ts=clock(); printf(" ts = %d \n",ts); for(i=0;i<10000000;i++); tf=clock(); printf(" tf = %d \n",tf); printf(" Timp de executie in milisecunde = %d\n",tf-ts); getch(); }

223


Algoritmică şi programare

utilizarea funcției difftime Se citește un șir de caractere și se măsoară în secunde timpul de tastare. #include <stdio.h> #include <conio.h> #include <time.h> int main () { time_t start,end; char buf [256]; double dif; // dif timp de executie time (&start); printf (" Tastati un sir de caractere "); gets (buf); time (&end); dif = difftime (end,start); // dif=end-start printf (" Ati tastat sirul %s \n",buf); printf (" Timp de tastare %.2lf secunde \n", dif ); getch(); } utilizarea funcției localtime #include <stdio.h> #include <conio.h> #include <time.h> int main() { char *zile[]={"Duminica","Luni","Marti","Miercuri","Joi","Vineri","Sambata"}; char *luna[]={"Ianuarie","Februarie","Martie","Aprilie","Mai","Iunie","Iulie", "August","Septembrie","Octombrie","Noiembrie","Decembrie"}; time_t t; tm *timp; time(&t); // Citire data curenta timp=localtime(&t); // Actualizare structura tm printf(" Anul curent \t %d \n",timp->tm_year+1900); printf(" Luna curenta \t %s luna numarul %d \n",luna[timp>tm_mon],timp->tm_mon+1);

224


Algoritmică şi programare

printf(" Ziua curenta \t %s ziua numarul %d \n",zile[timp>tm_wday],timp->tm_mday); printf(" Ora curenta \t %d \n",timp->tm_hour); printf(" Minutul curent \t %d \n",timp->tm_min); printf(" Secunda curenta \t %d \n",timp->tm_sec); getch(); } utilizarea funcției asctime #include <stdio.h> #include <time.h> #include <conio.h> int main () { time_t t; tm * timp; time (&t); timp=localtime(&t); // Citirea datei si orei curente din calculator printf ( " Data si ora curenta: %s", asctime(timp)); // Afisare data si ora curenta getch(); } utilizarea funcției strftime #include <stdio.h> #include <conio.h> #include <time.h> int main () { time_t t; struct tm * timp; char sir[80]; time(&t); timp=localtime(&t); // citire ora locala // Afiare ora in format de 24 ore, minut, secunda //strftime (sir,80,"Acum este ora %H:%M:%S",timp); strftime (sir,80,"Astazi este %d.%m.%y ora %H:%M:%S",timp); puts (sir); getch(); }

225


Algoritmică şi programare

Funcții pentru operații matematice Biblioteca limbajului C/C++ pune la dispoziția utilizatorului un număr de funcții specializate pentru operații matematice, grupate pe diverse categorii. Aceste funcții au prototipurile în fișierul antet math.h, stdlib.h și / sau complex.h. Orice program care folosește astfel de funcții trebuie să conțină următoarele directive preprocesor: #include <math.h> #include <stdlib.h> #include <complex.h> Pe lângă funcțiile specializate incluse in bibliotecile C/C++, dispunem și de valorile unor constante remarcabile din matematică, definite prin constante simbolice.

Constante simbolice

Fig. 64 Constante simbolice[1]

Funcții trigonometrice Funcțiile din această categorie au unul sau doi parametri de intrare și returnează prin numele lor valoarea funcției matematice corespunzătoare.

226


Algoritmică şi programare

Fig. 65 Funcții trigonometrice[1]

Funcții putere și radical Funcțiile din această categorie au unul sau doi parametri de intrare și returnează prin numele lor valoarea funcției matematice corespunzătoare.

Fig. 66 Funcții putere și radical[1]

Funcții exponențiale, logaritmice și hiperbolice Funcțiile din această categorie au un singur parametru de intrare și returnează prin numele lor valoarea funcției matematice corespunzătoare.

227


Algoritmică şi programare

Fig. 67 Funcții exponențiale, logaritmice și hiperbolice[1]

Funcții de conversie Funcțiile de conversie au un singur parametru care constituie adresa de memorie a unui șir de caractere. Funcțiile transformă șirul ASCII în valoarea numerică corespunzătoare și returnează prin numele lor această valoare. Funcția int atoi (const char* c) double atof char* c)

(const

Descriere Convertește șirul de caractere de la adresa c la valoarea întreagă a acestuia. Convertește șirul de caractere de la adresa c la valoarea reală simplă precizie a acestuia. Tabel 17 Funcții de conversie[1]

228


Algoritmică şi programare

Funcții parte întreagă, de rotunjire și de trunchiere Funcțiile din această categorie au un singur parametru de intrare și returnează prin numele lor valoarea funcției matematice corespunzătoare.

Fig. 68 Funcții parte întreagă, de rotunjire și de trunchiere[1]

Funcții modul Funcțiile din această categorie au un singur parametru de intrare și returnează prin numele lor valoarea funcției matematice corespunzătoare.

Fig. 69 Funcții modul[1]

Funcții pentru generarea numerelor aleatoare Funcțiile din această categorie returnează prin numele lor valori aleatoare. Funcția int rand () void srand (unsigned s)

Descriere Funcția returnează la fiecare apel un număr natural aleatoriu. Funcția inițializează valoarea de start a secvenței de numere aleatoare generate cu funcția rand.

Tabel 18 Funcții pentru generarea numerelor aleatoare[1]

229


Algoritmică şi programare

Aplicații practice Calculul valorilor unei funcții reale(cu o singură variabilă reală) în n puncte, într-un interval dat Fie funcția f: R → R #include <stdio.h> #include <math.h> #include <stdlib.h> #include <conio.h> double f(double x) { if(x<-1) return x*exp(1-pow(x,2)); if(x>=-1 && x<=1) return pow(x+1,1.0/3)* log(1+pow(x,2)); if(x>1) return sin(x)*sinh(x); } int main() { double a,b,h,x; int n,k; printf("Extremitatile intervalului [a,b] \n"); printf(" a = "); scanf(“%lf”, &a); printf(" b = "); scanf(“%lf”, &b); printf("Numarul de puncte din intervalul (%f, %f) in care se calculeaza f(x)=", a, b); scanf("%d",&n); h=(b-a)/n; for(k=0,x=a; k<=n+1; x=a+k*h,k++) printf("%d. f ( %f ) = %f \n",k+1,x,f(x)); getch(); } Calculul ariei și perimetrului unui triunghi oarecare #include <stdio.h> #include <conio.h> #include <math.h> #include <stdlib.h>

230


Algoritmică şi programare

float p(float,float,float); float arie(float,float,float);

// prototipul functiei p // prototipul functiei arie

int main() { float a,b,c; // lungimile laturilor triunghiului printf(" a = "); scanf("%f",&a); printf(" b = "); scanf("%f",&b); printf(" c = "); scanf("%f",&c); if(a+b>c && a+c>b && b+c>a) // validare date de intrare { printf(" Perimetrul = %f \n",2*p(a,b,c)); printf(" Aria = %f \n",arie(a,b,c)); } else printf(" Eroare in date \n"); getch(); } float p(float a, float b, float c) // calculul semiperimetrului { return (a+b+c)/2.; } float arie(float a, float b, float c) // calculul ariei – formula lui Heron { float semip; // semiperimetrul triunghiului semip=p(a,b,c); return sqrt(semip*(semip-a)*(semip-b)*(semip-c)); } Ghicirea unui număr aleator dintr-un interval din n încercări #include <stdio.h> #include <conio.h> #include <stdlib.h> int main() { int n,i,nr,nra,terminat=0, max; printf(" Ghiceste numarul dintr-un interval stabilit\n"); printf(" Stabiliti valoarea maxima a intervalului [0,m]; m= "); scanf("%d",&max); printf("Numar de incercari: ");

231


Algoritmică şi programare

scanf("%d",&n); nra=rand()%max;//generare numar for(i=1;i<=n && terminat==0;i++) { printf(" Tasteaza un numar natural intre 0 si %d : ", max); scanf("%d",&nr); if(nr<nra) printf(" Prea mic \n"); if(nr>nra) printf(" Prea mare \n"); if(nr==nra) { printf(" OK \n"); terminat=1; } } if(terminat) printf(" Ai castigat ! \n"); else printf(" Ai pierdut ! Numarul corect era: %d\n",nra); getch(); }

Funcții pentru operații cu șiruri de caractere În limbajul C/C++, un șir de caractere este de fapt un vector de caractere (masive de date unidimensionale de tip char), ultimul caracter fiind caracterul NUL, care are codul ASCII 0. În general, șirurile de caractere se scriu între ghilimele sub formă de secvențe de caractere normale și secvențe escape (e.g. "acesta este un sir"). Tipul șirurilor de caractere este char[], adică vector de caractere, dar având în vedere relația între tablouri și pointer, putem scrie char*, adică pointer la date de tip caracter. Declararea masivelor de date unidimensionale (șirurilor de caractere) de tip char Char nume_sir[n]; nume_sir este declarat ca un șir de caractere (tablou cu valori de tip char) care poate să stocheze (n-1) caractere + terminatorul de șir (caracterul NULL) '\0'.

232


Algoritmică şi programare

De exemplu, cf. tabelului cu codurile ASCII, șirul de caractere "Limbajul C/C++" este stocat în memoria calculatorului astfel: 76 105 109 98 97 106 117 108 32 67 47 67 43 43 0 0 #include <stdio.h> #include <conio.h> int main() { int i; char sir[] = "Limbajul C/C++"; for(i=0; i<=15; ++i) printf("%d ", sir[i]); getch(); } Dacă dorim ca șirurile de caractere pe care le afișăm să fie create dinamic, în funcție de anumite condiții, sau să fie preluate de la utilizator, sau să efectuăm anumite operații cu ele, atunci trebuie să folosim funcții specializate pentru aceste lucruri. Funcțiile standard pentru prelucrarea șirurilor de caractere se găsesc în biblioteca string.h. În prototipurile acestor funcții, parametrul de tip int reprezintă de fapt un caracter (sau un octet). Exemplu: Citirea si scrierea elementelor unui vector de tip char #include <stdio.h> #include <conio.h> int main() { char s[50]; // Declararea sirului de caractere s puts(" Inserati sirul:"); gets(s); printf(" Sirul inserat este: %s\n",s); system("pause"); return 0; } Se numește lungime a unui șir de caractere numărul caracterelor din șir. Terminatorul de șir nu intră în calculul lungimii șirului de caractere. Funcția strlen Funcția strlen are prototipul int strlen(char *s);

233


Algoritmică şi programare

Funcția primește la intrare adresa unui șir de caractere și returnează prin numele său lungimea șirului. Dacă în memorie șirul s este “limbaj de programare”, atunci prin apelul strlen(s) funcția strlen returnează valoarea 20. Funcția strtok Funcția este folosită pentru determinarea cuvintelor dintr-un șir de caractere. Un cuvânt este un șir de caractere delimitat de unul sau doi separatori anterior definiți. Funcția strtok are prototipul: char * strtok(char * s1, char *s2); în care s1 este adresa de memorie a șirului care va fi descompus în cuvinte, iar s2 este adresa de memorie a șirului care conține separatorii. Descompunerea are loc după mai multe apeluri ale funcției. La primul apel parametrul s1 precizează șirul care va fi descompus. La următoarele apeluri primul parametru este NULL. La fiecare apel funcția returnează prin numele ei adresa de început (un pointer) a următorului cuvânt din șirul s1 sau pointerul NULL dacă nu a mai fost găsit nici un cuvânt. Funcții pentru concatenare Funcțiile pentru concatenarea șirurilor de caractere ca parametri tot șiruri de caractere. Prin operația de concatenare a șirului s1 cu șirul s2 se obține un nou șir s=s1s2 obținut prin alipirea lui s2 la s1, după s1, concomitent cu ștergerea din memorie a terminatorului șirului s1. Funcția char* strcat (char *dest, char *src) char* strncat (char *dest, char *src, unsigned n)

Descriere Funcția concatenează șirul destinație, notat dest, cu șirul sursă, notat src. Funcția returnează prin numele său adresa șirului modificat (adică dest). Funcția adaugă cel mult n caractere din șirul sursă, src, la sfârșitul șirului destinație, dest. Funcția returnează prin numele său adresa șirului modificat (adică dest).

Tabel 19 Funcții pentru concatenare[1]

Funcții pentru copierea șirurilor de caractere Funcția char* strcpy (char *dest, char *src) char* strncpy (char *dest, char *src, unsigned n)

char* strdup (char *src)

234

Descriere Funcția copiază șirul sursă de la adresa src, la adresa dest. Funcția returnează prin numele său adresa șirului destinație, dest. Funcția copiază cel mult n caractere din șirul sursă de la adresa src, la adresa dest. Funcția returnează prin numele său adresa șirului destinație. Numărul de caractere copiate este minimul dintre n și strlen(src). Funcția returnează prin numele său adresa șirului destinație, dest. Funcția copiază șirul src într-o zonă de memorie alocată


Algoritmică şi programare Funcția

Descriere dinamic cu funcția malloc; programatorul trebuie să elibereze cu funcția free zona de memorie când nu o mai folosește. Funcția returnează prin numele său adresa zonei de memorie alocată.

Tabel 20 Funcții pentru copierea șirurilor de caractere[1]

Funcții pentru compararea a două șiruri de caractere Compararea a două șiruri memorate la adresele s1 și s2 se face astfel: se compară primul caracter din șirul s1 cu primul caracter din șirul s2. Dacă primul caracter din s1 are cod ASCII mai mic decât primul caracter din s2 compararea se termină cu concluzia că s1 < s2. Dacă primul caracter din s1 are cod ASCII mai mare decât primul caracter din s2 compararea se termină cu concluzia că s1 > s2. Dacă primul caracter din s1 are același cod ASCII ca primul caracter din s2, la compararea următoarelor două caractere din cele două șiruri. Se continuă procesul până la determinarea a două caractere aflate pe poziții identice cu coduri ASCII diferite, caz în care s1 < s2 sau s1 > s2, sau până la determinarea sfârșitului celor două șiruri, caz în care cele două șiruri sunt identice. Funcția int strcmp (char *s1, char *s2)

int stricmp (char *s1, char *s2)

int strncmp (char *s1, char *s2, unsigned n)

int strnicmp (char *s1, char *s2, unsigned n)

Descriere Funcția compară caracterele din șirurile de la adresele s1 și s2. Funcția returnează prin numele său valoarea întreagă 0 dacă șirurile s1 și s2 sunt identice; o valoare întreagă negativă dacă s1 < s2, o valoare întreagă pozitivă dacă s1 > s2. Funcția compară caracterele din șirurile de la adresele s1 și s2, ignorând deosebirea de cod ASCII dintre minuscule și majuscule. Funcția returnează prin numele său valoarea întreagă 0 dacă șirurile s1 și s2 sunt identice; o valoare întreagă negativă dacă s1 < s2, o valoare întreagă pozitivă dacă s1 > s2. Funcția compară șirul format cu primele n caractere din s1 (notat s1n) cu șirul format cu primele n caractere din șirul s2 (notat s2n). Funcția returnează prin numele său valoarea întreagă 0 dacă primele n caractere din șirurile s1n și s2n sunt identice; o valoare întreagă negativă dacă s1n < s2n, o valoare întreagă pozitivă dacă s1n > s2n. Dacă n>strlen(s1) şi / sau n>strlen(s2) atunci funcția compară tot șirul s1 și / sau tot șirul s2 cu tot șirul s2 și / sau tot șirul s1. Funcția este o combinație a funcțiilor stricmp și strnicmp.

Tabel 21 Funcții pentru compararea a două șiruri de caractere[1]

235


Algoritmică şi programare

Funcții de căutare Funcția char* strchr (char *s, int c)

char* strrchr (char *s, int c)

char* strstr (char *s1, char *s2)

char * strpbrk(char *s1, char *s2)

Descriere Funcția caută caracterul c în șirul s. Dacă c nu a fost găsit funcția returnează pointerul NULL. Dacă c a fost găsit, funcția returnează adresa primei apariții a caracterului c în șirul s. Funcția caută caracterul c în șirul s. Dacă c nu a fost găsit funcția returnează pointerul NULL. Dacă c a fost găsit, funcția returnează adresa ultimei apariții a caracterului c în șirul s. Funcția caută șirul s2 în șirul s1. Dacă șirul s2 nu a fost găsit funcția returnează pointerul NULL. Dacă șirul s2 a fost găsit, funcția returnează adresa primei apariții a șirului s2 în șirul s1. Funcția caută un caracter din șirul s2 în șirul s1. Dacă nici un caracter din s2 nu este în s1 funcția returnează pointerul NULL. Dacă este găsit un caracter din s2 în s1 funcția returnează adresa primei apariții în s1 a acestui caracter. Tabel 22 Funcții de căutare[1]

Funcții de setare Funcția char* strlwr (char *s) char* strupr (char *s) char* strrev (char *s) char* strset (char *s, int c) char* strnset (char *s, int c, unsigned n)

Descriere Funcția transformă majusculele din șirul s în minuscule și returnează prin numele său adresa șirului modificat (adică s). Funcția transformă minusculele din șirul s în majuscule și returnează prin numele său adresa șirului modificat (adică s). Funcția inversează în memorie șirul s și returnează prin numele său adresa șirului modificat (adică s). Funcţia transformă toate caracterele şirului s în caracterul c și returnează prin numele său adresa șirului modificat (adică s). Funcţia transformă primele n caractere ale şirului s în caracterul c și returnează prin numele său adresa șirului modificat (adică s). Tabel 23 Funcții de setare[1]

Aplicații practice Zilele săptămânii #include <stdio.h> #include <conio.h> int main() { int n;

236


Algoritmică şi programare

char *zile[] = {"Eroare", "Luni", "Marti", "Miercuri", "Joi", "Vineri", "Sambata", "Duminica"}; printf("Introduceti un numar intre 1 si 7:"); scanf("%i", &n); if(n>=1 && n<=7) printf(zile[n]); else printf(zile[0]); getch(); } Numărul de cuvinte dintr-un vector de tip char #include "stdio.h" #include "conio.h" int main() { char sir[100]; int nc,i; puts(" Sirul :"); gets(sir); i=0; if(sir[i]==' ' || sir[i]==',' || sir[i]==';') nc=-1; else nc=0; while(sir[i]!='\0') { if(sir[i]==' ' || sir[i]==',' || sir[i]==';') { nc++; i++; while(sir[i]==' ' || sir[i]==',' || sir[i]==';') i++; } i++; } nc++; printf("Numar cuvinte in sir=%d \n",nc); getch(); } Identificarea unui caracter într-un vector de tip char Afișarea numărului de caractere identice întâlnite.

237


Algoritmică Ĺ&#x;i programare

#include <stdio.h> #include <conio.h> int main() { int n=0,i=0; char s[50],c; // Declararea sirului de caractere s puts(" Inserati sirul:"); gets(s); puts(" Caracterul "); scanf("%c",&c); while(s[i]!='\0') { if(s[i]==c) n++; i++; } if(n==0) printf("Sirul %s nu contine caracterul %c \n",s,c); else printf(" Numarul de caractere %c din sirul %s este egal cu %d \n",c,s,n); getch(); //system("pause"); return 0; } Oglinda caracterelor dintr-un șir #include <stdio.h> #include <conio.h> #include <string.h> int main() { char mesaj[256]; int i, len; printf("Introduceti un text:\n"); gets(mesaj); len = strlen(mesaj); for(i = 0; i<len/2; ++i) { char temp = mesaj[i]; mesaj[i] = mesaj[len - i - 1]; mesaj[len - i - 1] = temp; }

238


Algoritmică şi programare

printf("Rezultat:\n%s", mesaj); getch(); } Criptarea cu n deplasări ASCII într-un șir de caractere #include <stdio.h> #include <string.h> #include <conio.h> int main() { char mesaj[256]; int i, len, n; printf("Introduceti un text:\n"); gets(mesaj); printf("Introduceti n:"); scanf("%d", &n); len = strlen(mesaj); for(i = 0; i<len; ++i) mesaj[i] += n; printf("Rezultat:\n%s", mesaj); getch(); } Primul cuvânt din propoziție cu majuscula #include <stdio.h> #include <string.h> #include <ctype.h> #include <conio.h> int main() { char mesaj[256]; int i, len, frazanoua = 1; printf("Introduceti un text:\n"); gets(mesaj); len = strlen(mesaj); for(i = 0; i<len; ++i) if(isalpha(mesaj[i])) if(frazanoua){ mesaj[i] = toupper(mesaj[i]); frazanoua = 0; }else{ mesaj[i] = tolower(mesaj[i]); }

239


Algoritmică şi programare

else if(ispunct(mesaj[i])) frazanoua = 1; printf("Rezultat:\n%s", mesaj); getch(); } Citirea unui mesaj de la tastatură și afișarea unui extras din șir #include <stdio.h> #include <string.h> #include <conio.h> int main() { char mesaj[256], extras[256]; int i, n, l; printf(„Introduceti un text:\n”); gets(mesaj); printf(„Pozitie de start: „); scanf(„%i”, &n); printf(„Lungime: „); scanf(„%i”, &l); i = 0; do { extras[i] = mesaj[i+n]; ++i; } while(extras[i-1] != 0 && i<l); printf(„Extras: %s”, extras); getch(); } Test palindrom #include <stdio.h> #include <string.h> #include <conio.h> int main() { char mesaj[256]; int i, len, pal = 1; printf("Introduceti un text:\n"); gets(mesaj); len = strlen(mesaj); for(i = 0; i<len/2; ++i) if(mesaj[i] != mesaj[len - i - 1])

240


Algoritmică şi programare

pal = 0; if(pal == 1) printf("Sirul este palindron."); else printf("Sirul nu este palindrom."); getch(); } Citirea unui șir de la tastatură și eliminarea primei apariții a unui subșir citit #include <stdio.h> #include <string.h> #include <conio.h> int main() { char mesaj[256], subsir[50], *inceput; int i, len; printf("Introduceti un text:\n"); gets(mesaj); printf("Introduceti subsirul:\n"); gets(subsir); inceput = strstr(mesaj, subsir); if(inceput != NULL) { len = strlen(subsir); strcpy(inceput, inceput+len); } printf("Rezultat:\n%s", mesaj); getch(); } Afișează cuvintele dintr-un șir de caractere câte unul pe linie #include <stdio.h> #include <string.h> #include <conio.h> int main() { char sir[100], sep[10], *pozitie; printf("Introduceti textul"); gets(sir); printf("Introduceti separatorii"); gets(sep); printf(" Cuvintele din text sunt: \n");

241


Algoritmică şi programare

pozitie=strtok(sir, sep); while(pozitie) { printf("%s\n", pozitie); pozitie=strtok(NULL,sep); } getch(); } Ștergerea tuturor aparițiilor unui caracter dintr-un șir #include "stdio.h" #include "conio.h" #include "string.h" int sterg(char *x, char c) { int i,k,j,este; i=0;este=0; while(i<strlen(x)) { j=i; while(*(x+j)==c) { este=1; for(k=j;*(x+k)!='\0';k++) *(x+k)=*(x+k+1); *(x+k+1)='\0'; } i++; } return este; } int main() { char sir[200],sirm[200],car; puts(" Sirul "); gets(sir); strcpy(sirm,sir); puts(" Caracterul care va fi sters "); scanf("%c",&car); if(sterg(sir,car)) { printf("Caracterul %c a fost garsit in sirul %s \n", car,sirm);

242


Algoritmică şi programare

puts(" Sirul modificat "); puts(sir); } else printf(" Caracterul %c nu a fost gasit in sirul %s \n",car,sir); getch(); } Inserarea unui șir sursă într-un șir destinație după caracterul aflat pe poziția n #include "stdio.h" #include "conio.h" void insert(char *d, char *s, int n) { int is,id,im; char m[100]; im=0; id=n+1; while(*(d+id)!='\0') { m[im]=*(d+id); id++; im++; } id=n+1; m[im]='\0'; is=0; while(*(s+is)!='\0') { *(d+id)=*(s+is); id++; is++; } im=0; while(*(m+im)!='\0') { *(d+id)=*(m+im); id++; im++; } *(d+id)='\0'; } int main() { char sird[100],sirs[100]; int n; puts(" Sirul sursa ");

243


Algoritmică şi programare

gets(sirs); puts(" Sirul destinatie "); gets(sird); puts(" Pozitia de inceput a inserarii "); scanf("%d",&n); insert(sird,sirs,n); puts(sird); getch(); } Înlocuirea unui caracter c dintr-un șir de caractere, cu un alt caracter #include "stdio.h" #include "conio.h" #include "string.h" int inlocuire(char *x, char cs, char cd) { int i,k,gasit; i=0; gasit=0; for(i=0;i<strlen(x);i++) { if(*(x+i)==cs) { gasit=1; *(x+i)=cd; } } if(gasit) return 1; else return 0; } int main() { char sir[200],cars,card; puts(" Sirul "); gets(sir); puts(" Caracterul de inlocuit "); cars=getche(); puts("\n Caracterul cu care inlocuiesc "); card=getche(); if(inlocuire(sir,cars,card))

244


Algoritmică şi programare

{ puts("\n Sirul modificat "); puts(sir); } else printf("\n Caracterul %c nu a fost gasit in sirul %s \n",cars,sir); getch(); }

Funcții pentru clasificarea caracterelor Biblioteca standard ctype.h dispune de diverse funcții pentru testarea apartenenței unui caracter la o mulțime bine precizată de caractere. Toate aceste funcții au un unic parametru de intrare a cărui apartenență la o mulțime de caractere se testează și returnează prin numele lor o valoare întreagă nenulă dacă caracterul aparține mulțimii la care face referire funcția sau valoarea întreagă 0 în caz contrar. Funcții de apartenență Funcția int isalpha (int c)

int islower (int c)

int isupper (int c)

int isdigit (int c)

int isxdigit (int c)

Int isalnum (int c)

Descriere Funcția testează apartenența caracterului c la mulțimea literelor și returnează prin numele său valoarea 1 dacă c aparține mulțimii și este majusculă, valoarea 2 dacă c aparține mulțimii și este minusculă, valoarea întreagă 0 dacă c nu este o literă. Funcția testează apartenența caracterului c la mulțimea literelor mici și returnează prin numele său valoarea 2 dacă c aparține mulțimii (este minusculă), valoarea întreagă 0 în caz contrar. Funcția testează apartenența caracterului c la mulțimea literelor mari și returnează prin numele său valoarea 1 dacă c aparține mulțimii (este majusculă), valoarea întreagă 0 în caz contrar. Funcția testează apartenența caracterului c la mulțimea cifrelor zecimale ({0,1,2,3,4,5,6,7,8,9}) și returnează prin numele său valoarea 4 dacă c aparține mulțimii (este cifră zecimală), valoarea întreagă 0 în caz contrar. Funcția testează apartenența caracterului c la mulțimea cifrelor hexazecimale ({0,1,2,3,4,5,6,7,8,9,a,A,b,B,c,C,d,D,e,E,f,F}) și returnează prin numele său valoarea 128 dacă c aparține mulțimii (este cifră hexazecimală), valoarea întreagă 0 în caz contrar. Funcția testează apartenența caracterului c la mulțimea literelor sau cifrelor zecimale și returnează prin numele său valoarea 1 dacă c aparține mulțimii și este majusculă,

245


Algoritmică şi programare Funcția

int isascii (int c)

int isspace (int c)

int iscntrl (int c)

int ispunct (int c)

int isgraph (int c)

int isprint (int c)

Descriere valoarea 2 dacă c aparține mulțimii și este minusculă, valoarea 4 dacă c aparține mulțimii și este cifră zecimală și valoarea întreagă 0 dacă c nu aparține mulțimii. Funcția testează apartenența caracterului c la mulţimea primelor 128 de caractere ASCII și returnează valoarea 1 dacă c are codul ASCII între 0 şi 127 şi valoarea 0 dacă c are codul între 128 şi 255. Funcția testează apartenența caracterului c la mulțimea formată cu caracterele de spațiere (spațiu, tab, new line, vertical tab, form feed sau carriage return) și returnează prin numele său o valoare întreagă nenulă dacă c aparține mulțimii, valoarea întreagă 0 dacă c nu este caracter de spațiere. Funcția testează apartenența caracterului c la mulțimea caracterelor de control (mulțimea caracterelor care au codul ASCII între 0,1,2,...,31 și 127) și returnează prin numele său o valoarea întreagă 32 dacă c aparține mulțimii, valoarea întreagă 0 dacă c nu este caracter de spațiere. Funcția testează apartenența caracterului c la mulțimea formată din caractere de punctuație ({ ! „ # $ % & ‚ < > * + . , : ; = ? { } |~ [ ] ^}) și returnează prin numele său valoarea întreagă nenulă 16 dacă c aparține mulțimii, valoarea întreagă 0 dacă c nu este caracter de punctuație. Funcția testează apartenența caracterului c la mulțimea formată cu caracterele grafice (caractere care au o reprezentare grafică având codurile ASCII între 33 și 126) și returnează prin numele său o valoare întreagă nenulă dacă c aparține mulțimii, valoarea întreagă 0 în caz contrar. Funcția testează apartenența caracterului c la mulțimea formată cu caracterele imprimabile (caractere care pot fi tipărite) și returnează prin numele său o valoare întreagă nenulă dacă c aparține mulțimii, valoarea întreagă 0 în caz contrar. Tabel 24 Funcții de apartenență[1]

Funcții pentru conversia caracterelor Biblioteca standard ctype.h pune la dispoziția utilizatorului două funcții pentru conversia majusculelor în minuscule și invers. Funcția int tolower (int c) int toupper (int c)

Descriere Funcția testează dacă c este o majusculă. Dacă c este o majusculă funcția returnează minuscula corespunzătoare. Funcția testează dacă c este o minusculă. Dacă c este o minusculă funcția returnează majuscula corespunzătoare. Tabel 25 Funcții pentru conversia caracterelor[1]

246


Algoritmică şi programare

Aplicații practice Definirea unei parole Definim o parolă ca fiind un șir de caractere cu următoarele proprietăți: 1. șirul de caractere are lungimea cel puțin 11; 2. șirul are cel puțin trei cifre zecimale; 3. șirul are cel puțin două majuscule; 4. șirul are cel puțin trei minuscule; 5. șirul are exact trei caractere de punctuație. Respectând proprietățile enumerate, șirul de caractere A17b3.;:Fcd este o parolă iar, șirul de caractere parola1234 nu este o parolă în sensul mai sus precizat. Astfel, vom scrie un program care citește un șir de caractere de la tastatură, testează dacă poate fi o parolă și afișează un mesaj corespunzător verificării proprietăților unei parole. În elaborarea programului, fiecare dintre cele cinci proprietăți corespunde unei funcții. În funcția principală main citim șirul de caractere, valida cele cinci proprietăți și vom afișa mesajul corespunzător. #include <stdio.h> #include <conio.h> #include <string.h> #include <ctype.h> // Prototipurile functiilor din program int lung(char *); int zec(char *); int maj(char *); int min(char *); int punct(char *); int parola(char *); //functia principala int main() { char sir[100]; printf(" Tastati sirul: "); gets(sir); if(parola(sir)) printf(" Sirul %s este o parola \n",sir); else printf(" Sirul %s nu este o parola \n",sir); 247


Algoritmică Ĺ&#x;i programare

getch(); } int lung(char *s) // Functie pentru determinarea lungimii sirului { return strlen(s); } int zec(char *s) // Functie pentru determinarea numarului de cifre zecimale { int i,z; for(z=0,i=0;*(s+i) != '\0';i++) if(isdigit(*(s+i))) z++; return z; } int maj(char *s) // Functie pentru determinarea numarului de majuscule { int i,z; for(z=0,i=0;*(s+i) != '\0';i++) if(isupper(*(s+i))) z++; return z; } int min(char *s) // Functie pentru determinarea numarului de minuscule { int i,z; for(z=0,i=0;*(s+i) != '\0';i++) if(islower(*(s+i))) z++; return z; } int punct(char *s) // Functie pentru determinarea numarului de caractere de punctuatie { int i,z; for(z=0,i=0;*(s+i) != '\0';i++) if(ispunct(*(s+i))) z++; return z; } 248


Algoritmică şi programare

// Functie pentru validare parola - returneaza 1 pentru parola valida si 0 pentru sir invalid int parola(char *s) { if(lung(s)>=11 && zec(s)>=3 && maj(s)>=2 && min(s)>=3 && punct(s)==3) return 1; else return 0; } Conversia unui șir de caractere reprezentând un număr întreg într-o valoare întreagă #include <stdio.h> #include <conio.h> #include <ctype.h> int ascii_int(char *); // Prototipul functiei ascii_int int main() { char sir[100]; printf(" Tastati numarul: "); gets(sir); printf(" Numarul este %d \n",ascii_int(sir)); getch(); } int ascii_int(char *s)

// Functia de conversie

{ int i,nr,semn; for(i=0;isspace(*(s+i));i++); // Se ignora spatiile semn=(*(s+i)=='-')?-1:1; // Determinare semn if(*(s+i)=='+' || *(s+i)=='-') i++; // Salt peste semn for(nr=0;isdigit(*(s+i));i++) // Conversie la cifre nr=10*nr+(*(s+i)-'0'); // Constructie numar return semn*nr; } Transformă toate minusculele unui șir de caractere în majuscule și invers #include "stdio.h“ // Minuscule majuscule #include "conio.h" #include "ctype.h"

249


Algoritmică Ĺ&#x;i programare

void schimb_m_M(char x[]); // prototip functie minuscule majuscule void schimb_M_m(char x[]); // majuscule in minuscule int main() { char s[100]; puts(" Tastati un sir de caractere "); gets(s); schimb_m_M(s); printf(" Transformarea tuturor minusculelor in majuscule\n"); puts(" Sirul modificat este "); puts(s); getch(); schimb_M_m(s); printf(" Transformarea tuturor majusculelor in minuscule\n"); puts(" Sirul modificat este "); puts(s); getch(); } void schimb_m_M(char x[]) // minuscule in majuscule { int i; i=0; while(x[i]!='\0') { x[i]=toupper(x[i]); i++; } } void schimb_M_m(char x[]) // majuscule in minuscule { int i; i=0; while(x[i]!='\0') { x[i]=tolower(x[i]); i++; } }

250

in


Algoritmică şi programare

Primul cuvânt din fiecare frază să înceapă cu majusculă #include <stdio.h> #include <string.h> #include <ctype.h> #include <conio.h> int main() { char mesaj[256]; int i, len, frazanoua = 1; printf("Introduceti un text:\n"); gets(mesaj); len = strlen(mesaj); for(i = 0; i<len; ++i) if(isalpha(mesaj[i])) if(frazanoua) { mesaj[i] = toupper(mesaj[i]); frazanoua = 0; }else {mesaj[i] = tolower(mesaj[i]); } else if(ispunct(mesaj[i])) frazanoua = 1; printf("Rezultat:\n%s", mesaj); getch(); } Determinarea numărului de cifre hexazecimale din șirul citit #include <stdio.h> #include <conio.h> #include <ctype.h> int hex(char *);

// Prototipul functiei hex

int main() { char sir[100]; printf(" Tastati un sir de caractere: "); gets(sir); printf(" Sirul %s are %d cifre hexazecimale \n",sir,hex(sir)); getch(); } // Definitia functiei hex. Returneaza prin numele sau numarul de cifre hexazecimale ale sirului de la adresa s

251


Algoritmică şi programare

int hex(char *s) { int i,h; for(h=0,i=0;*(s+i) != '\0';i++) if(isxdigit(*(s+i))) h++; return h; } Înlocuirea cifrelor zecimale dintr-un șir cu caracterul ‚#’ #include <stdio.h> #include <conio.h> #include <ctype.h> void replace(char *);

// Prototipul functiei replace

int main() { char sir[100]; printf(" Tastati un sir de caractere: "); gets(sir); replace(sir); printf(" Sirul modificat este: %s\n",sir); getch(); } void replace(char *s) // Definitia functiei replace { int i; for(i=0;*(s+i) != '\0';i++) if(isdigit(*(s+i))) *(s+i)='#'; } Înlocuirea caracterelor de spațiere dintr-un șir cu caracterul ‚#’ #include <stdio.h> #include <conio.h> #include <ctype.h> void replace(char *);

// Prototipul functiei replace

int main() { char sir[100]; printf(" Tastati un sir de caractere: "); 252


Algoritmică şi programare

gets(sir); replace(sir); printf(" Sirul modificat este: %s\n",sir); getch(); } void replace(char *s) // Definitia functiei replace { int i; for(i=0;*(s+i) != '\0';i++) if(isspace(*(s+i))) *(s+i)='#'; } Testează dacă cele două șiruri sunt într-o relație #include <stdio.h> #include <conio.h> #include <ctype.h> int ncz(char *); // Prototipul functiei ncz int main() { char sir1[100],sir2[100]; printf(" Tastati un sirul 1: "); gets(sir1); printf(" Tastati un sirul 2: "); gets(sir2); if(ncz(sir1)==ncz(sir2)) printf(" Sirul %s este in relatia R cu sirul %s\n",sir1,sir2); else printf(" Sirul %s nu este in relatia R cu sirul %s\n",sir1,sir2); getch(); } int ncz(char *s) // Definitia functiei ncz. Functia determina numarul de cifre zecimale din sirul s { int i,z; for(z=0,i=0;*(s+i) != '\0';i++) if(isdigit(*(s+i))) z++; return z; }

253


Algoritmică şi programare

Alocarea dinamică a memoriei O variabilă prezintă o clasă de memorare care rezidă fie din declarația ei, fie implicit din locul unde este definită variabila. Zona de memorie alocata unui program scris în limbajul C/C++ cuprinde 4 subzone: 1. Zona text, rezervată memorării codului programului 2. Zona de date, rezervată variabilelor globale 3. Zona stivă ,rezervată variabilelor locale (datele de manevră, temporare) 4. Zona heap rezervată datelor alocate dinamic (memoria dinamică). Alocarea datelor poate fi: 1. Statică atunci când variabilele sunt implementate în zona de date. 2. Auto atunci când variabilele sunt implementate în stivă. 3. Dinamică atunci când variabilele sunt implementate în heap. Memoria se alocă dinamic (la execuție) în zona “heap” atașată programului, dar numai la cererea explicită a programatorului, prin apelarea unor funcții de bibliotecă(malloc, calloc, realloc). Memoria este eliberată numai la cerere, prin apelarea funcției “free”. 4. Registru atunci când variabilele sunt implementate într-un registru de memorie. Variabilele din clasa (de memorie) static se declară cu cuvântul cheie static: static tip v1,v2,...,vn; în care v1,v2,...,vn sunt variabile iar tip este tipul comun celor n variabile. static definitie_functie în care definitie_functie este o definiție de funcție. Memoria necesară tuturor acestor variabile este alocată la compilare în segmentul de date alocat programului și nu se mai poate modifica în cursul execuției programului. Implicit, variabilele globale sunt statice precum declararea de tip static a unor variabile locale, definite în cadrul funcțiilor. O variabilă sau o funcție declarată static are durata de viață egală cu durata de viață a programului. Clasa de memorie static determină ca o variabilă (globală sau locală) sau o funcție să fie cunoscută unității unde a fost definită, devenind astfel inaccesibilă altei unități, chiar prin folosirea cuvântului cheie extern. Variabilele din clasa static pot fi variabile statice interne sau statice externe.

254


Algoritmică şi programare

Variabilele statice interne sunt variabile locale funcției în care au fost definite. Valorile variabilelor statice interne se păstrează pe toată durata execuției programului. Nici o altă funcție nu poate modifica valorile variabilelor statice interne ale funcției în care au fost definite. Variabilele statice externe se definesc în exteriorul oricărei funcții. Orice funcție poate modifica valorile variabilelor statice externe. Variabilele statice externe sunt variabile globale numai pentru fișierul sursă în care au fost definite. Variabilele din clasa de memorie auto se declară cu cuvântul cheie auto care este implicit: auto tip v1,v2,...,vn; în care v1,v2,...,vn sunt variabile de automatice cu tipul tip. Memoria necesară acestor variabile este alocată automat, prin apelul unei funcții, în zona stivă aferentă unui program și este eliberată automat la terminarea execuției funcției. Variabilele din această clasă sunt variabile locale funcției în care au fost definite. Dacă lipsește cuvântul cheie auto se consideră în mod implicit clasa auto. Valorile variabilelor auto sunt actualizate la fiecare execuție a funcției în care au fost definite aceste variabile și se distrug la terminarea execuției funcției. Nici o altă funcție nu poate modifica valorile variabilelor auto ale funcției în care au fost definite. În funcții diferite pot fi definite variabile locale care au același identificator. Variabilele din clasa de memorie extern se declară cu cuvântul cheie extern: extern tip v1,v2,...,vn; în care v1,v2,...,vn sunt variabile iar tip este tipul comun celor n variabile. extern definitie_functie în care definitie_functie este o definiție de funcție. Variabilele din clasa extern sunt variabile globale. Orice funcție poate modifica valorile variabilelor externe. Dacă o variabilă din clasa extern este declarată în exteriorul oricărei funcții cuvântul cheie extern poate să lipsească. Valorile variabilelor din clasa extern se păstrează pe toată durata execuției programului. Funcțiile sunt considerate variabile externe cu excepția cazurilor în care se precizează altceva. Variabilele externe se folosesc pentru ca funcțiile să poată comunica între ele. Variabilele din clasa registru se declară cu cuvântul cheie register: register tip v1,v2,...,vn; în care v1,v2,...,vn sunt variabile iar tip este tipul comun celor n variabile. 255


Algoritmică şi programare

Variabilele din această clasă sunt variabile locale funcției în care au fost definite și se comportă ca variabilele din clasa auto. Valorile variabilelor din clasa registru se memorează în regiștrii microprocesorului în scopul minimizării timpului de execuție a programului. Adresa unei variabile registru nu poate fi referită. În limbajul C/C++, dimensiunea fiecărei variabile trebuie cunoscută în momentul compilării programului; pentru variabilele cu tip fundamental, dimensiunea este furnizată implicit de tipul acestor; pentru masive, dimensiunea este furnizată explicit prin dimensiunile acestora (și tipul elementelor), etc. Un vector static nu este neapărat orice vector care are dimensiunea constantă. Un vector definit în cadrul unei funcții (cu excepția funcției principale main) nu este static deoarece, pe toată durata de execuție a programului, el nu ocupă memorie, chiar dacă dimensiunea lui este declarată în momentul dezvoltării programului. Alocarea pe stivă a unui vector definit într-o funcție este făcută în momentul apelului funcției (activarea funcției), iar la terminarea execuției funcției, memoria ocupată de vector este eliberată automat. Pentru a lucra cu informații ale căror dimensiuni nu sunt cunoscute la momentul compilării programului există două alternative: 1. declararea la compilare a unor dimensiuni care acoperă în mod sigur necesarul de memorie 2. alocarea memoriei necesare în timpul execuției programului. Funcțiile standard pentru gestiunea dinamică a memoriei se găsesc în biblioteca malloc.h şi stdlib.h. Pentru a fi generale, aceste funcții lucrează cu pointeri de tip void, deci este necesar ca programatorul să execute conversii explicite de tip. Algoritmul general de lucru în cazul alocării dinamice a memoriei constă în alocarea memoriei, folosirea ei și, în final, eliberarea acesteia.

Funcțiile standard pentru gestiunea dinamică a memoriei

void* size)

Funcția malloc (unsigned

void* calloc (unsigned cnt, unsigned size)

256

Descriere Funcția alocă o zonă de memorie de dimensiune size octeți și returnează prin numele său adresa zonei de memorie alocate sau NULL în caz de eroare. Funcţia alocă o zonă de memorie de dimensiune cnt*size octeți și completează cu valoarea 0 conținutul acesteia. Funcția returnează prin numele său adresa zonei de memorie alocate sau NULL în caz de eroare.


Algoritmică şi programare Funcția void* realloc(void * ptr, unsigned size)

void free (void *ptr)

Descriere Funcţia realocă zona de memorie indicată de pointerul ptr astfel încât să aibă dimensiunea size octeți și alocă o zonă de dimensiunea specificată de al doilea argument. Copiază la noua adresă datele de la adresa veche precizată de primul argument. Eliberează memoria de la adresa veche. Funcția returnează prin numele său adresa zonei de memorie realocate sau NULL în caz de eroare. Funcţia eliberează zona de memorie indicată de pointerul ptr. Eliberarea memoriei cu funcţia free este nefolositoare la terminarea unui program, deoarece înainte de încărcarea şi lansarea în execuţie a unui nou program se eliberează automat toată memoria heap.

Tabel 26 Funcții standard pentru gestiunea dinamică a memoriei[1]

Alocarea dinamică a memoriei Se declara directiva preprocesor #include<stdlib.h> Se alocă memorie (conform cu dimensiunea cerută): void *malloc(int numar_de_octeti); Se alocă memorie (conform cu dimensiunea cerută)și se inițializează cu zero zona alocată: void *calloc(int n_items, int size); Intrucât funcțiile de alocare nu știu tipul de date ce vor fi memorate la adresa respectivă au rezultat void*. Totodată, funcțiile au ca argument dimensiunea zonei de memorie alocată și ca rezultat adresa zonei de memorie alocate (de tip void *). Funcțiile de alocare au rezultat NULL dacă cererea de alocare nu poate fi satisfăcută (nu există un bloc contiguu de memorie egal cu dimensiunea solicitată). Se utilizează: 1. Operatorul sizeof (determină numărul de octeți necesar unui tip de date); 2. Operatorul de conversie cast (adaptează adresa de memorie primită de la funcție la tipul datelor memorate la adresa respectivă). Realocă memorie de dimensiunea numar_de_octeti void *realloc(void* adr,int numar_de_octeti); Alocă o zonă de memorie de dimensiunea specificată de cel de-al doilea argument Copiază datele de la vechea adresă la noua adresă.

257


Algoritmică şi programare

Eliberează memoria de la vechea adresă. void free(void* adr); Eliberează zona de memorie de la adresa adr. Este inutilă eliberarea memoriei cu ajutorul funcției free la terminarea unui program, deoarece toată memoria heap este eliberată la încărcarea și lansarea în execuție a unui nou program.

Alocarea dinamică a memoriei utilizând pointeri Se declara o variabila de memorie de tip pointer către tipul de data al variabilei dinamice <tip> * <nume_pointer>; In momentul in care programul utilizează variabila dinamica, se cere sistemului de operare alocarea unui spațiu de memorie in HEAP, cu ajutorul operatorului new: <nume_pointer> = new <tip>; Daca in program nu mai este utilizata variabila dinamica, se cere eliberarea spațiului de memorie alocat in HEAP cu ajutorul operatorului delete: delete <nume_pointer>; Operatorii new si delete sunt operatori unari, deci au prioritatea si asociativitatea acestor tipuri de operatori.

Aplicații practice 1. Program care alocă spațiu pentru o variabilă întreagă dinamică, după citire și tipărire, spațiul fiind eliberat. #include <stdlib.h> #include <stdio.h> int main(){ int *a; a=(int *)malloc(sizeof(int)); if(a==NULL){ puts("Memorie insuficienta "); return 1; } printf("Tastati un numar: "); scanf("%d",a); printf("Adresa pe heap a variabilei a=%p\n", a); 258


Algoritmică şi programare

printf("Adresa variabilei a=%p\n",&a); printf("sizeof(*a)=%d \t sizeof(a)=%d \t sizeof(&a)=%d\n",sizeof(*a), sizeof(a), sizeof(&a)); free(a); //eliberare memorie printf("a(dupa eliberarea memoriei):%p\n",a); system("pause"); return 0; } 2. Program care alocă dinamic spațiu pentru un vector, după citire și tipărire, spațiul fiind eliberat. #include <stdlib.h> #include <stdio.h> #include <windows.h> int main(){ int i,n,*A; printf("Inserati numarul de elemente: "); scanf("%d",&n); A=(int *)malloc(n*sizeof(int)); if(A==NULL){ puts("*** Memorie insuficienta ***"); return 1; // revenire din main } for (i=0; i<n; i++) {A[i]=i+1;} for (i = 0; i < n; i ++) {printf("%d ",A[i]);} free(A); //eliberare spatiu //system("pause"); return 0; } sau #include <stdlib.h> #include <stdio.h> #include <windows.h> int main(){ int i,n,*A; printf("Tastati numarul de elemente: "); scanf("%d",&n); 259


Algoritmică şi programare

A=(int *)calloc(n,sizeof(int)); if(A==NULL){ puts("Memorie insuficienta"); return 1 } for (i = 0; i < n; i ++) {printf("%d ",A[i]);} free(A); //eliberare spatiu de memorie system("pause"); return 0; } sau #include <stdlib.h> #include <stdio.h> #include <windows.h> int main(){ int i,n,*A; printf("Tastati numarul de elemente: "); scanf("%d",&n); A=(int *)malloc(n*sizeof(int));//A=(int *)calloc(n,sizeof(int)); if(A==NULL){ puts("Memorie insuficienta"); return 1; } for (i = 0; i < n; i ++) {printf("A[%d]: ",i);scanf("%d",&A[i]); } for (i=0; i<n; i++) {printf("%d ",A[i]);} free(A); system("pause"); return 0; } 3. Program care alocă dynamic spațiu pentru un vector, după care redimensionam vectorul #include <stdlib.h> #include <stdio.h> #include <windows.h> 260


Algoritmică şi programare

int main(){ int i,n,*A; printf("Inserati numarul de elemente: "); scanf("%d",&n); A=(int *)malloc(n*sizeof(int)); if(A==NULL){ puts("Memorie insuficienta "); return 1; } for (i=0; i<n; i++) {A[i]=i+1;} for (i=0; i<n; i++) {printf("%d ",A[i]);} int *B=(int *)realloc(A,2*n*sizeof(int)); //int *B=(int *)realloc(NULL,n*sizeof(int)); //echivalent cu malloc //int *A=(int *)realloc(A,0); //echivalent cu free(A) printf("Zona de memorie pentru A=%d\n Zona de memorie pentru B=%d\n ",A,B); for (i=0; i<2*n; i++) {printf("%d ",B[i]);} free(B); //eliberare spatiu system("pause"); return 0; } 4. Program care alocă spațiu pentru calculul sumei a 2 numere utilizând variabile dinamice, după citire și tipărire, spațiul fiind eliberat. #include <stdlib.h> #include <stdio.h> #include <windows.h> int main(){ int *a,*b,*s; a=(int *)malloc(sizeof(int)); b=(int *)malloc(sizeof(int)); s=(int *)malloc(sizeof(int)); if(a==NULL){ puts("Memorie insuficienta "); return 1; } printf("Inserati valoarea lui a: "); scanf("%d",a); 261


Algoritmică şi programare

printf("Inserati valoarea lui b: "); scanf("%d",b); *s=*a+*b; // calculul sumei printf("Suma=%d \n",*s); free(a); free(b); free(s);//eliberare spatiu system("pause"); return 0; } 5. Program care alocă spațiu pentru calculul sumei a n numere utilizând variabile dinamice, după citire și tipărire, spațiul fiind eliberat. #include <stdlib.h> #include <stdio.h> int main(){ int i, nr_numere, *valori_numere,s=0; printf("Calculul sumei pentru cate numere? : "); scanf("%d", &nr_numere); //valori_numere=(int *)malloc(nr_numere*sizeof(int)); valori_numere=(int *)calloc(nr_numere,sizeof(int)); if(valori_numere==NULL){ puts("Memorie insuficienta"); return 1; } for (i = 0; i < nr_numere; ++i) { printf("Introduceti valoarea a %d-a: ", i+1); scanf("%d", &valori_numere[i]); s=s+valori_numere[i]; // calculul sumei } printf("Suma=%d \n",s); free(valori_numere);//eliberare spatiu //system("pause"); return 0; } 6. Sa se calculeze suma si produsul a 2 numere citite de la tastatura. Obs: utilizam alocarea dinamica a variabilelor elementare iar pentru referirea mărimilor utilizam pointeri de tip float #include <stdio.h> 262


Algoritmică şi programare

#include <stdlib.h> #include <malloc.h> main() { float *a, *b, *s, *p; a=new float, b=new float, s=new float, p=new float; printf("Inserati a: "); scanf("%f",a); printf("Inserati b: "); scanf("%f",b); *s=(*a+*b); *p=*a * *b; //calculul variabilelor dinamice printf("Suma =%.2f, Produsul =%.2f\n", *s, *p); delete a; delete b; delete s; delete p; } 7. Sa se calculeze aria si perimetrul unui dreptunghi cunoscând laturile sale. Obs: utilizam alocarea dinamica a variabilelor elementare iar pentru referirea mărimilor utilizam pointeri de tip int (mărimea laturilor) si float (arie si perimetru) #include <stdio.h> #include <stdlib.h> #include <malloc.h> main() { int *a, *b; float *aria, *p; //declarare pointeri variabile input & output a=new int, b=new int, aria=new float, p=new float; //alocarea memoriei pentru variabilele dinamice si citirea valorilor lor de la tastatura printf("Inserati a: "); scanf("%d",a); printf("Inserati b: "); scanf("%d",b); *p=2*(*a+*b); *aria=*a * *b; //calculul variabilelor dinamice printf("Aria dreptunghiului =%.2f, perimetrul =%.2f\n", *aria, *p); //afisarea valorilor variabilelor dinamice delete a; delete b; delete aria; delete p; //eliberarea zonei de memorie alocata variabilelor dinamice }

263


Algoritmică şi programare

Funcții pentru operații cu blocuri de memorie Funcțiile permit prelucrarea simplă informației din memorie, la nivel de octeți, dar cu o viteză ridicată. Funcțiile standard pentru manipularea blocurilor de memorie se găsesc în bibliotecile string.h și mem.h. În prototipurile acestor funcții, parametrul de tip int reprezintă de fapt un octet (sau un caracter). Ele pot fi folosite atât pentru zone de memorie alocate dinamic, cât și pentru zone de memorie alocate static. Funcția void* memcpy (void *dest, void *src, unsigned cnt) void* memmove (void *dest, void *src, unsigned cnt) void* memchr (void *src, int c, unsigned cnt) void* memset (void *dest, int c, unsigned cnt) int memcmp (void *src1, void *src2, unsigned cnt)

Descriere Funcția copiază cnt octeți din zona de memorie src în dest (src și dest trebuie să fie disjuncte) și returnează prin numele său adresa destinație dest. Funcția copiază cnt octeți din zona de memorie src în dest (nu neapărat disjuncte)și returnează prin numele său adresa sursă src. Funcția caută valoarea c în primii cnt octeți din zona de memorie src și returnează prin numele său adresa octetului c sau NULL dacă c nu a fost găsit. Funcția scrie valoarea c în primii cnt octeți din zona de memorie dest și returnează prin numele său adresa destinație dest. Funcția compară în ordine cel mult cnt octeți din zonele de memorie src1 și src2. Funcția returnează prin numele său valoarea întreagă 0 dacă informația din src1 este identică cu cea din src2; valoarea întreagă -1 dacă primul octet diferit din src1 este mai mic decât octetul corespunzător din src2; valoarea întreagă 1 dacă primul octet diferit din src1 este mai mare decât octetul corespunzător din src2

Tabel 27 Funcții pentru operații cu blocuri de memorie[1]

Aplicații practice Program utilizare funcţie memcpy În program se definesc pointerii ps (sursa), pd și adrdest (destinaţia) către tipul char. Se citește de la tastatură un număr natural n. Se alocă dinamic n octeți pentru un șir de caractere (șirul sursă de la adresa ps). Adresa de început a șirului sursă se obține cu funcția malloc și este atribuită pointerului ps. Se citește șirul sursă. Se alocă dinamic n octeți pentru șirul destinație la adresa returnată de funcția malloc și atribuită pointerului pd. Se copiază cu funcția memcpy șirul de la adresa ps la adresa pd. Se afișează șirul destinație și se eliberează memoria alocată. #include <stdio.h> #include <conio.h> #include <mem.h> 264


Algoritmică şi programare

#include <malloc.h> int main() { char *ps,*pd,*adrdest; // ps (sursa), pd si adrdest (destinatia) pointeri la tipul char int n; printf(" Numar octeti de alocat "); scanf("%d",&n); if(ps=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,ps); printf(" Tastati un sir de caractere "); fflush(stdin); gets(ps); if(pd=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,pd); adrdest=(char *)memcpy(pd,ps,n); printf(" Sirul %s a fost copiat la adresa %p\n",ps,pd); printf(" Sirul la adresa destinatie %p este %s \n",pd,pd); free(pd); // Eliberare memorie destinatie free(ps); // Eliberare memorie sursa } else printf(" Alocare destinatie esuata \n"); } else printf(" Alocare sursa esuata \n"); getch(); } Program utilizare funcţie memmove În program se definesc pointerii ps (sursa), pd și adrdest (destinaţia) către tipul char. Se citește de la tastatură un număr natural n. Se alocă dinamic n octeți pentru un șir de caractere (șirul sursă de la adresa ps). Adresa de început a șirului sursă se obține cu funcția malloc și este atribuită pointerului ps. Se citește șirul sursă. Se alocă dinamic n octeți pentru șirul destinație la adresa returnată de funcția malloc și atribuită pointerului pd. Se copiază cu funcția memmove șirul de la adresa ps la adresa pd. Se afișează șirul destinație și se eliberează memoria alocată. #include <stdio.h> #include <conio.h>

265


Algoritmică şi programare

#include <mem.h> #include <malloc.h> int main() { char *ps,*pd,*adrdest; // ps (sursa), pd si adrdest (destinatia) pointeri la tipul char int n; printf(" Numar octeti de alocat "); scanf("%d",&n); if(ps=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,ps); printf(" Tastati un sir de caractere "); fflush(stdin); gets(ps); if(pd=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,pd); adrdest=(char *)memmove(pd,ps,n); printf(" Sirul %s a fost copiat la adresa %p\n",ps,pd); printf(" Sirul la adresa destinatie %p este %s \n",pd,pd); free(pd); // Eliberare memorie la destinatie } else printf(" Alocare destinatie esuata \n"); } else printf(" Alocare sursa esuata \n"); getch(); } Program utilizare funcţie memchr. În program se definesc pointerii pc și pcc către tipul char. Se citește de la tastatură un număr natural n. Se alocă dinamic n octeți pentru un șir de caractere. Adresa de început a șirului de caractere se obține cu funcția malloc și este atribuită pointerului pc. Se citește un șir de caractere și apoi un caracter c. Programul caută caracterul c în șirul citit și dacă este găsit, afișează prima apariție a caracterului c în șirul de la adresa memorată de pointerul pc. În funcția main se apelează funcția memchr(pc,c,n) care caută caracterul c în primele n caractere ale șirului de la adresa pc și returnează adresa primei apariții a caracterului c în șir, adresă memorată de pointerul pcc. Calculând diferență pcc-pc se obține poziția în șir a primei apariții a caracterului c în șirul citit.

266


Algoritmică şi programare

#include <stdio.h> #include <conio.h> #include <mem.h> #include <malloc.h> int main() { char *pc,*pcc,c; // pc si pcc pointeri la tipul char int n; printf(" Numar octeti de alocat: "); scanf("%d",&n); if(pc=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,pc); printf(" Tastati un sir de caractere "); fflush(stdin); gets(pc); printf(" Tastati caracterul de cautat "); c=getche(); if(pcc=(char *)memchr(pc,c,n)) { n=pcc-pc; printf("\n Caracterul %c apare in sirul %s la adresa %p \n",c,pc,pcc); printf(" Prima aparitie a lui %c in sirul %s este pe pozitia %d \n",c,pc,n); printf(" %p - %p = %d \n",pcc,pc); } else printf("\n Caracterul %c nu apare in sirul %s \n",c,pc); } else printf(" Alocare esuata \n"); free(pc); // Eliberare memorie getch(); } Program utilizare funcţie memset. În program se definesc pointerii pd și adrdest (destinația) către tipul char. Se citește de la tastatură un număr natural n. Se alocă dinamic n octeți pentru un șir de caractere. Adresa de început a șirului de caractere se obține cu funcția malloc și este atribuită pointerului pd. Se citește un caracter de umplere c. Caracterul c va fi scris la adresa pd de n ori cu apelul funcției memset(pd,c,n);. Se memorează terminatorul de șir la adresa pd+n și se

267


Algoritmică şi programare

afișează șirul de la adresa adrdest, care coincide cu adresa memorată de pd. În final se eliberează zona de memorie alocată de funcția malloc. #include <stdio.h> #include <conio.h> #include <mem.h> #include <malloc.h> int main() { char *pd,*adrdest,c;

// pd (destinatia) si adrdest pointeri la tipul

char int n; printf(" Numar octeti de alocat "); scanf("%d",&n); if(pd=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,pd); printf(" Tastati caracterul de umplere "); c=getche(); adrdest=(char *)memset(pd,c,n); *(pd+n)='\0'; // Terminatorul de sir de caractere printf("\n Sirul la adresa destinatie %p este %s \n",adrdest,pd); free(pd); // Eliberare memorie la destinatie } else printf(" Alocare destinatie esuata \n"); getch(); } Program utilizare funcţie memcmp. În program se definesc pointerii ps (sursa) şi pd (destinația) către tipul char. Se definesc variabilele comp care va memora valoarea returnată de funcția memcmp și lungime care va memora valoarea minimă dintre lungimile celor două șiruri care se vor compara. Se citește de la tastatură un număr natural n. Se alocă dinamic n octeți pentru șirul sursă de caractere (șirul sursă de la adresa ps). Adresa de început a șirului sursă se obține cu funcția malloc și este atribuită pointerului ps. Se citește șirul sursă. Se alocă dinamic n octeți pentru șirul destinație la adresa returnată de funcția malloc și atribuită pointerului pd. Se determină valoarea minimă dintre lungimile celor două șiruri. Se apelează funcția de comparare memcmp (ps, pd, lungime);. Funcția returnează prin numele său rezultatul comparației în variabila comp. Programul afișează valoarea lui comp și în funcție de

268


Algoritmică şi programare

această valoare emite un mesaj referitor la rezultatul comparării celor două șiruri de caractere. În final se eliberează zonele de memorie alocate dinamic de funcția malloc. #include <stdio.h> #include <conio.h> #include <mem.h> #include <malloc.h> int main() { char *ps,*pd; // ps (sursa) si pd (destinatia) pointeri la tipul char int n,comp,lungime; printf(" Numar octeti de alocat "); scanf("%d",&n); if(ps=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,ps); printf(" Tastati sirul sursa "); fflush(stdin); gets(ps); if(pd=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,pd); printf(" Tastati sirul destinatie "); gets(pd); printf(" Sirul sursa %s \n",ps); printf(" Sirul destinatie %s \n",pd); lungime=(strlen(ps)<strlen(pd))?strlen(ps):strlen(pd); comp=memcmp(ps,pd,lungime); printf(" comp = %d \n",comp); if(comp<0) printf(" %s < %s \n",ps,pd); if(comp==0) printf(" %s == %s \n",ps,pd); if(comp>0) printf(" %s > %s \n",ps,pd); free(pd); // Eliberare memorie la destinatie free(ps); // Eliberare memorie la sursa } else printf(" Alocare destinatie esuata \n"); } 269


Algoritmică şi programare

else printf(" Alocare sursa esuata \n"); getch(); } Sortare crescătoare șiruri distincte de caractere #include <stdio.h> #include <conio.h> #include <mem.h> #include <string.h> #include <malloc.h> int main() { char *s1,*s2,*s3; // s1,s2 si s3 pointeri la tipul char int n,comp1,comp2,comp3,l1,l2,l; printf(" Numar octeti de alocat: "); scanf("%d",&n); if(s1=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,s1); printf(" Tastati sirul 1: "); fflush(stdin); gets(s1); if(s2=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,s2); printf(" Tastati sirul 2: "); gets(s2); if(s3=(char *)malloc(n*sizeof(char))) { printf(" S-au alocat %d octeti la adresa %p \n",n,s3); printf(" Tastati sirul 3: "); gets(s3); l1=(strlen(s1)<strlen(s2))?strlen(s1):strlen(s2); l2=(strlen(s2)<strlen(s3))?strlen(s2):strlen(s3); l=(l1<l2)?l1:l2; comp1=memcmp(s1,s2,l1); comp2=memcmp(s2,s3,l2); comp3=memcmp(s1,s3,l); if(comp1<0 && comp2<0 && comp3<0) printf(" %s < %s < %s \n",s1,s2,s3); if(comp1<0 && comp2>0 && comp3<0) printf(" %s < %s < %s \n",s1,s3,s2); if(comp1>0 && comp2<0 && comp3<0) printf(" %s < %s < %s \n",s2,s1,s3);

270


Algoritmică Ĺ&#x;i programare

if(comp1>0 && comp2<0 && comp3>0) printf(" %s < %s < %s \n",s2,s3,s1); if(comp1<0 && comp2>0 && comp3>0) printf(" %s < %s < %s \n",s3,s1,s2); if(comp1>0 && comp2>0 && comp3>0) printf(" %s < %s < %s \n",s3,s2,s1); free(s1); free(s2); free(s3); // Eliberare memorie } else

printf(" Alocare sir 3 esuata \n");

} else printf(" Alocare sir 2 esuata \n"); } else printf(" Alocare sir 1 esuata \n"); getch(); }

271


Algoritmică şi programare

Fișiere I/O. Funcții pentru operații asupra fișierelor Fișiere de date. Introducere Fișierul este o colecție de date - de obicei de același tip, stocate pe suport extern. Fișierele sunt entități ale sistemului de operare deoarece numele lor respectă convențiile sistemului, fără legătură cu un anumit limbaj de programare. Conținutul unui fișier este variabil (numărul de elemente poate fi chiar nul) și poate fi format din texte, numere, informații binare (executabile, numere, imagini sau sunete în format binar). Fișierele se folosesc în principal pentru păstrarea permanentă a unor date importante, dar și pentru memorarea unor date inițiale (de intrare) sau date de ieșire numerice voluminoase, de interes pentru anumite programe. De obicei prin fișier se înțelege fișier disc plasat pe un mediu de stocare (magnetic, optic, etc.). Noțiunea de fișier include orice flux de date (I/O stream) din exterior spre memorie și viceversa, practic privește transferului de date. Programatorul se referă la un fișier printr-o variabilă de un anumit tip, funcție de limbajul folosit și chiar de funcțiile folosite în cadrul limbajul C/C++. Operațiile pentru prelucrarea fișierelor sunt: - crearea fișierului; - asignarea fișierului intern (logic) la unul extern (fizic); - deschiderea fișierului; - operații de acces la date - închiderea fișierului. Lucrul cu fișiere implică identificarea următoarelor caracteristici: • tipurile fișierelor, • metodele de organizare, • modul de acces, • tipul articolelor acceptate. În limbajul de programare C/C++ tipul de fișiere este unic, sub forma unui flux de octeți cu o organizare secvențială[ (înșiruire de octeți, fără nici un fel de organizare sau semnificație). De asemenea, accesul la fișiere se poate face secvențial (valabil doar pentru fișiere standard) sau direct. Prelucrarea fișierelor se poate face cu ajutorul funcțiilor predefinite, existente în bibliotecile limbajului. Există două niveluri de prelucrare a fișierelor: inferior și superior. Prelucrarea la nivel inferior (fără gestiunea automată a zonelor tampon de intrare/ieșire) este dependentă de mașină și sistem ceea ce conduce la o portabilitate scăzută a programelor. Prelucrarea la nivel superior (se folosesc

272


Algoritmică şi programare

funcții specializate de gestiune a fișierelor) asigură un grad ridicat de portabilitate a programelor, motiv pentru care vom prezenta doar nivelul superior de prelucrare a fișierelor. Funcțiile de prelucrare la nivel superior a fișierelor tratează fluxul de octeți acordându-i o semnificație oarecare. Din punctul de vedere al prelucrării, la acest nivel există două tipuri de fișiere: fișiere text și fișiere binare. Fișierele text conțin o succesiune de linii separate prin caracterul NewLine (\n). Fiecare linie are 0 sau mai multe caractere imprimabile sau Tab. Fișierele binare necesită un program care să cunoască și să interpreteze corect datele din fișier (structura articolelor, respectiv succesiunea de octeți). Citirea/scrierea se face cu ajutorul funcțiilor fread sau fwrite. Există fișiere standard, care sunt gestionate automat de sistem, dar asupra cărora se poate interveni și în mod explicit. Aceste fișiere sunt: • fișierul standard de intrare stdin (STanDard INput); • fișierul standard de ieşire stdout (STanDard OUTput); • fișierul standard scriere mesaje de eroare stderr (STanDard ERRor); • fișierul standard asociat portului serial stdaux (STanDard AUXiliary); • fișierul standard asociat imprimantei cuplate la portul paralel stdprn (STanDard PRiNter). Fișierele standard pot fi redirectate conform convențiilor sistemului de operare, cu excepția lui stderr care va fi asociat întotdeauna monitorului. În lucrul cu fișiere (sau la orice apel de sistem), în caz de eroare în timpul unei operații se setează variabila errno, definită în errno.h, stddef.h și stdlib.h. Valorile posibile sunt definite în stdlib.h. Specificatorul de fișier reprezintă un nume extern de fișier, conform convențiilor sistemului de operare. Specificatorul de fișier poate să conțină strict numele fișierului sau poate conține și calea completă pană la el. Nivelul superior de prelucrare a fișierelor La acest nivel, fișierul este considerat ca flux de octeți, din care funcțiile de prelucrare preiau secvențe pe care le tratează într-un anumit fel (sau în care inserează secvențe de octeți). Un fișier se descrie ca pointer către o structură predefinită, structura FILE descrisă în biblioteca stdio.h.

273


Algoritmică şi programare

Funcțiile folosite la acest nivel pot fi împărțite în următoarele categorii: • funcții de prelucrare generală; • funcții de citire/scriere cu/fără format; • funcții de citire/scriere pentru caractere; • funcții de citire/scriere pentru șiruri de caractere. Funcții de prelucrare generală Funcțiile de prelucrare generală se aplică tuturor fișierelor, indiferent de tipul informației conținute; prelucrarea efectuată de acestea nu are nici un efect asupra conținutului fișierului. Funcțiile de citire/scriere cu format se folosesc pentru citirea/scrierea fișierelor text care conțin informație de tip text (linii de text, separate prin perechea CR/LF, iar la sfârșit se găsește caracterul CTRL-Z). Funcțiile de citire/scriere fără format se folosesc pentru citirea/scrierea fișierelor binare. Funcțiile de citire/scriere pentru caractere se folosesc pentru transferul unui singur caracter între un fișier text și memorie. Funcțiile de citire/scriere pentru șiruri de caractere se folosesc pentru transferul unui șir de caractere între un fișier text și memorie. Funcțiile de citire/scriere deplasează pointerul de citire/scriere al fișierului, spre sfârșitul acestuia, cu un număr de octeți egal cu numărul de octeți transferați fără a trece de sfârșitul fișierului.

Deschiderea și/sau crearea unui fișier Crearea sau deschiderea unui fișier existent se realizează prin apelul funcției fopen. Funcția returnează un pointer, numit descriptor de fișier, spre o structură de tip FILE (în care sunt scrise date referitoare la fișierul deschis) sau NULL dacă fișierul nu poate fi deschis. Antetul funcției fopen este: FILE* fopen(const char * identificator, const char* mod); Tipul de dată FILE conține informații referitoare la fișier și la tamponul de transfer de date între memoria internă și fișier (adresa, lungimea tamponului, modul de utilizare a fișierului, indicator de sfârșit, de poziție în fișier). Toate referirile (operațiile) ulterioare la fișierul creat/deschis se fac prin intermediul descriptorului de fișier. Parametrul identificator este adresa zonei de memorie în care este memorat numele extern al fișierului creat sau deschis. Numele extern al

274


Algoritmică şi programare

fișierului poate conține și calea de căutare a fișierului conform standardului sistemului de operare. Parametrul mod este un șir de caractere, format dintr-un singur caracter sau mai multe caractere, care specifică modul de creare / deschidere a fișierului. Parametrul mod face distincția dintre fișierele text și fișierele binare.

Deschiderea și/sau crearea unui fișier text Valorile parametrului mod și semnificațiile acestor valori pentru crearea/deschiderea unui fișier text. Mod “w” sau “wt”

“w+” sau “wt+” sau “w+t” “r” sau “rt” “r+” sau “rt+” sau “r+t” “a” sau “at”

“a+” sau “at+” sau “a+t”

Descriere Mod de scriere fișier text. Dacă fișierul a fost anterior creat, conținutul fișierului este șters. Dacă fișierul nu a fost anterior creat, se creează un nou fișier, singura operație permisă fiind scrierea în fișier. Mod de scriere fișier text cu posibilitate de citire. Dacă fișierul a fost anterior creat, conținutul fișierului este șters. Dacă fișierul nu a fost anterior creat, se creează un nou fișier, în care se poate scrie și din care se poate citi. Mod de citire fișier text. Dacă fișierul nu a fost anterior creat se generează eroare, funcția fopen returnează pointerul NULL. Mod de citire fișier text cu posibilitate de scriere. Dacă fișierul nu a fost anterior creat se generează eroare, funcția fopen returnează pointerul NULL. Mod de adăugare fișiere text. Deschide un fișier existent pentru scriere la sfârșit (extindere) sau îl creează dacă nu există. Este permisă numai scrierea. Mod de adăugare cu posibilitate de citire fișiere text. Deschide un fișier existent pentru scriere la sfârșit (extindere) sau îl creează dacă nu există. Este permisă numai scrierea. Tabel 28. Deschiderea și / sau crearea unui fișier text[1]

Deschiderea și/sau crearea unui fișier binar Valorile parametrului mod și semnificațiile acestor valori pentru crearea / deschiderea unui fișier binar. Mod “wb”

“wb+” sau “w+b”

Descriere Mod de scriere fișier binar. Dacă fișierul a fost anterior creat, conținutul fișierului este șters. Dacă fișierul nu a fost anterior creat, se creează un nou fișier, singura operație permisă fiind scrierea în fișier. Mod de scriere fișier binar cu posibilitate de citire. Dacă fișierul a fost anterior creat, conținutul fișierului este șters. Dacă fișierul nu a fost anterior creat, se creează un nou fișier, în care se poate scrie și din care se poate citi.

275


Algoritmică şi programare Mod “rb” “rb+” sau “r+b” “ab”

“ab+” sau “a+b”

Descriere Mod de citire fișier binar. Dacă fișierul nu a fost anterior creat se generează eroare, funcția fopen returnează pointerul NULL. Mod de citire fișier binar cu posibilitate de scriere. Dacă fișierul nu a fost anterior creat se generează eroare, funcția fopen returnează pointerul NULL. Mod de adăugare fișiere binar. Deschide un fișier existent pentru scriere la sfârșit (extindere) sau îl creează dacă nu există. Este permisă numai scrierea. Mod de adăugare cu posibilitate de citire fișiere binar. Deschide un fișier existent pentru scriere la sfârșit (extindere) sau îl creează dacă nu există. Este permisă numai scrierea. Tabel 29 Deschiderea și/sau crearea unui fișier binar [1]

Dacă în parametrul mod nu este prezent nici caracterul b nici caracterul t, modul considerat depinde de valoarea variabilei _fmode: dacă valoarea este O_BINARY, se consideră fișier binar; dacă valoarea este O_TEXT, se consideră fișier text. Operaţia

Fişier text

Creare Citire Citire cu posibilitate scriere Creare şi actualizare Adăugare

de

“w” “r” nedefinit “w+” “a”

Fişier binar “wb” “rb” “r+b” sau “rb+” “rwb” sau “w+b” sau “wb+” nedefinit

Tabel 30 Deschiderea și/sau crearea unui fișier

Valoarea implicită a variabilei _fmode este valoarea O_TEXT, adică implicit se consideră că tipul fișierului este text. Pentru a crea un fișier text se definește un pointer la tipul FILE, se apelează funcția fopen cu parametrul mod “w” sau “wt”, se atribuie valoarea returnată pointerului la tipul FILE. Dacă valoarea returnată este pointerul NULL operația de creare a eșuat. La terminarea execuției programelor toate fișierele deschise în timpul execuției sunt închise automat. Închiderea fișierelor se poate face și în timpul execuției programelor utilizând funcțiile fclose și fcloseall. Antetul funcției fclose este: int fclose(FILE* f); Funcția închide fișierul al cărui descriptor este f, primit ca parametru, și returnează valoarea întreagă 0, în caz de succes, sau -1, în caz de eroare. 276


Algoritmică şi programare

Înainte de închiderea fișierului, sunt golite toate buffer-ele asociate lui. Buffer-ele alocate automat de sistem sunt eliberate. Antetul funcției fclose_all este: int fcloseall(); Funcția închide toate fișierele deschise în momentul apelului ei. Funcția nu are parametri de apel și returnează valoarea întreagă 0, în caz de succes, sau -1, în caz de eroare. Înainte de închiderea fișierului, sunt golite toate buffer-ele asociate lui. Buffer-ele alocate automat de sistem sunt eliberate. Exemple: Creare/închidere fișier Ex1: #include <stdio.h> int main() { FILE *f; // Pointer la tipul FILE if((f=fopen("F:\\temp\\fisier1.txt","w")) = NULL) puts(" Fisierul nu a fost creat!"); else puts(" Fisierul a fost creat!"); fclose(f); // Inchidere fisier getch(); } Ex2: #include <stdio.h> int main() { FILE *f; // Pointer la tipul FILE f=fopen("F:\\temp\\fisier2.txt","w"); if(f== NULL) puts(" Fisierul nu a fost creat!"); else puts(" Fisierul a fost creat!"); fclose(f); // Inchidere fisier getch(); }

277


Algoritmică şi programare

Ex3: Creare/inchidere fişier text #include <stdio.h> #include <conio.h> int main() { FILE *f; // Pointer la tipul FILE char fname[30]; // Identificatorul (numele extern) fisierului printf(" Nume fisier: "); gets(fname); if((f=fopen(fname,"w")) = NULL) printf(" Fisierul %s nu a fost creat \n",fname); else printf(" Fisierul %s a fost creat \n",fname); fclose(f); // Inchidere fisier getch(); } Pentru a crea un fișier se definește un pointer la tipul FILE, se apelează funcția fopen cu parametru mod “w”, se atribuie valoarea returnată pointerului la tipul FILE. Dacă valoarea returnată este pointerul NULL operația de creare a eșuat. Ex4: Creare/inchidere fişier binar #include <stdio.h> #include <conio.h> int main() { FILE *f; // Pointer la tipul FILE char fname[30]; // Identificatorul (numele extern) fisierului printf(" Nume fisier: "); gets(fname); if((f=fopen(fname,"wb")) = NULL) printf(" Fisierul %s nu a fost creat \n",fname); else printf(" Fisierul %s a fost creat \n",fname); fclose(f); // Inchidere fisier getch(); } Pentru a crea un fișier binar se definește un pointer la tipul FILE, se apelează funcția fopen cu parametru mod “wb”, se atribuie valoarea

278


Algoritmică şi programare

returnată pointerului la tipul FILE. Dacă valoarea returnată este pointerul NULL operația de creare a eșuat.

Poziționarea indicatorului de citire/scriere Programatorul poate în orice moment să controleze poziția indicatorul de citire/scriere în fișier cu ajutorul unor funcții specializate. Poziționarea indicatorului de citire / scriere la începutul fișierului: void rewind(FILE *f); Funcția are un singur parametru de apel și anume descriptorul fișierului implicat. Executarea funcției are ca efect poziționarea la începutul fișierului f deschis anterior, resetarea indicatorului de sfârșit de fișier și a indicatorilor de eroare. După apelul lui rewind poate urma o operație de scriere sau citire din fișier. Testarea sfârșitului de fișier se realizează prin apelul macro-definiției feof: int feof(FILE* f); Macro-ul furnizează valoarea indicatorului de sfârșit de fișier asociat lui f. Valoarea indicatorului este stabilită la fiecare operație de citire din fișier. Valoarea returnată este întregul 0 dacă indicatorul are valoarea sfârșit de fișier sau o valoare diferită de zero în caz contrar. Apelul lui feof trebuie să fie precedat de apelul unei funcții de citire din fișier. După atingerea sfârșitului de fișier, toate încercările de citire vor eșua, până la apelul funcției rewind sau închiderea și apoi redeschiderea fișierului. Determinarea poziției curente a indicatorului de citire / scriere: int fgetpos(FILE* f, fpos_t * poziţie); După apel, la adresa poziție se află poziția indicatorului de citire / scriere din fișierul f, ca număr relativ al octetului curent. Primul octet are numărul 0. Valoarea returnată poate fi folosită pentru poziționare cu funcția fsetpos. În caz de succes funcția returnează valoarea 0, iar în caz de eroare, o valoare nenulă. long ftell(FILE* f); Funcția returnează poziția în fișierul f a indicatorului de citire / scriere în caz de succes sau -1L în caz contrar. Dacă fișierul este binar, poziția este dată în număr de octeți față de începutul fișierului. Valoarea poate fi folosită pentru poziționare cu funcția fseek.

279


Algoritmică şi programare

Modificarea poziției indicatorului de citire / scriere : int fseek(FILE* f, long deplasare, int origine); Parametrul deplasare reprezintă numărul de octeți cu care se deplasează indicatorul în fișierul f, iar parametrul origine reprezintă referința față de care se deplasează indicatorul de citire / scriere. Parametrul origine poate avea valorile: SEEK_SET (0) poziționare față de începutul fișierului; SEEK_CUR (1) poziționare față de poziția curentă; SEEK_END (2) poziționare față de sfârșitul fișierului. Funcția returnează valoarea 0 în caz de succes (și uneori și în caz de eșec). Se semnalează eroare prin returnarea unei valori nenule numai în cazul în care fișierul cu descriptorul f nu este deschis. Poziționarea absolută a indicatorului de citire / scriere se face cu funcția: int fsetpos(FILE* f, const fpos_t poziţie); Indicatorul de citire / scriere se mută în fișierul cu descriptorul f la octetul cu numărul indicat de parametrul poziție (care poate fi o valoare obținută prin apelul lui fgetpos). Ambele funcții (fgetpos și fsetpos) resetează indicatorul de sfârșit de fișier. Redenumirea sau mutarea unui fișier existent Se realizează prin apelul funcției rename, care are următorul prototip: int rename(const char* n_vechi, const char* n_nou); Parametrul n_vechi reprezintă vechiul nume al fișierului, iar n_nou reprezintă numele nou. Dacă numele vechi conține numele discului (de exemplu C:), numele nou trebuie să conțină același nume de disc. Dacă numele vechi conține o cale, numele nou nu este obligat să conțină aceeași cale. Folosind o altă cale se obține mutarea fișierului pe disc. Folosind aceeași cale (sau nefolosind calea) se obține redenumirea fișierului. Nu sunt permise caracterele (?, *) în cele două nume. În caz de succes se funcția returnează valoarea 0. În caz de eroare funcția returnează valoarea -1. Ștergerea unui fișier existent Se poate realiza prin apelul funcției remove, care are următorul prototip: int remove(const char* identificator); 280


Algoritmică şi programare

Parametrul identificator reprezintă numele extern al fișierului și care poate să conțină calea de căutare a fișierului. Golirea explicită a zonei tampon a unui fișier se realizează prin apelul funcției fflush, care are următorul prototip: int fflush(FILE* f); Dacă fișierul cu descriptorul f are asociat un buffer de ieșire, funcția scrie în fișier toate informațiile din acesta, la poziția curentă. Dacă fișierul are asociat un buffer de intrare, funcția îl golește. În caz de succes returnează valoarea zero, iar în caz de eroare valoarea constantei simbolice EOF (definită în stdio.h). Înainte de a citi un șir de caractere de la tastatură, buffer-ul trebuie golit pentru a preveni citirea unui șir vid (datorită unei perechi CR/LF rămase în buffer de la o citire anterioară a unei valori numerice). Ștergerea se realizează prin apelul: fflush(stdin);

Funcții pentru tratarea erorilor Pentru tratarea erorilor se folosesc următoarele funcții: clearerr și ferror. void clearerr (FILE *f); Funcția resetează indicatorii de eroare și indicatorul de sfârșit de fișier pentru fișierul cu descriptorul f (se scrie valoarea 0). O dată ce indicatorii de eroare au fost setați la o valoare diferită de 0, operațiile de intrare / ieșire vor semnala eroare până la apelul lui clearerr sau rewind. int ferror (FILE *f); Este o macro-definiție care returnează codul de eroare al ultimei operații de intrare / ieșire asupra fișierului cu descriptorul f sau 0 dacă nu s-a produs eroare. Exemplu: #include <stdio.h> #include <conio.h> int main() { FILE *f; f=fopen("test.ttt","w"); getc(f); if(ferror(f))

// Deschidere fisier text pentru scriere // se generează eroare la citire // Test de eroare 281


Algoritmică şi programare

printf("Eroare al citirea din test.ttt\n"); clearerr(f); // Reseteaza indicatorii de eroare si sfirsit de fisier fclose(f); getch(); }

Funcții de citire/scriere fără format Funcțiile se folosesc pentru citirea/scriere fișierelor binare. Acestea efectuează transferuri de secvențe de octeți între memoria internă și un fișier de pe disc, fără a interveni asupra conținutului sau ordinii octeților respectivi. Citirea dintr-un fișier binar se realizează prin apelul funcției fread, care are următorul prototip: size_t fread(void* adr, size_t dim, size_t n, FILE* f); Funcția citește din fișierul f, de la poziția curentă, un număr de n articole, fiecare de dimensiune dim, și le memorează, în ordinea citirii, la adresa adr. Funcția fread returnează numărul de entități citite. În total se citesc, în caz de succes, n*dim octeți. În caz de eroare sau când se întâlnește sfârșitul de fișier, funcția returnează o valoare negativă sau 0; size_t este definit în mai multe fișiere header (între care stdio.h) și este un tip de dată folosit pentru a exprima dimensiunea obiectelor din memorie. Este compatibil cu tipul unsigned. Scrierea într-un fişier binar se poate realiza prin apelul funcției fwrite, care are următorul prototip: size_t fwrite(const void* adr, size_t dim, size_t n, FILE* f); Funcția scrie în fișierul f, începând cu poziția curentă, un număr de n articole fiecare de dimensiune dim, aflate în memorie la adresa adr. Funcția fwrite returnează numărul articolelor scrise cu succes. În caz de eroare returnează o valoare negativă. Transferul de date cu format controlat este realizat prin funcțiile: int fprintf(FILE* f, const char* format[,…]); int fscanf(FILR* f, const char* format[,…]); Funcțiile • fprintf () - fscanf () 282


Algoritmică şi programare

• •

fputc() - fgetc() fputs() - fgets() Funcțiile fprintf () și fscanf () sunt versiunea similara printf () și fscanf (), aplicata fișierelor . Singura diferență fata de fprintf () și fscanf (), este faptul că, primul argument este un pointer la structura de tip FILE. Funcțiile fgetc() si fgets() citesc 1 caracter, respectiv n-1 caractere si returnează valoarea citita sau EOF in caz de eroare. Funcțiile fputc() si fputs() scriu 1 caracter, respectiv n-1 caractere si returnează valoarea citita sau EOF in caz de eroare. Exemplu: Creare/scriere/închidere fișier #include <stdio.h> int main(){ int n; printf("Inserati un numar: "); scanf("%d",&n); FILE *f; f=(fopen("F:\\temp\\student.txt","w")); //write if(f==NULL) printf("Err!"); else fprintf(f,"Valoarea inserata=%d \n",n); fclose(f); return 0; } sau #include <stdio.h> int main(){ int n; printf("Inserati un numar: "); scanf("%d",&n); FILE *f; f=(fopen("F:\\temp\\student.txt",“a")); //apend if(f==NULL) printf("Err!"); else fprintf(f,"Valoarea inserata=%d \n",n); fclose(f); return 0; } 283


Algoritmică şi programare

Funcții de citire/scriere șir de caractere Transferul de șiruri de caractere se efectuează prin funcțiile: char* fgets(char* s, int n, FILE *f); int fputs(const char* s, FILE *f); Funcția fgets citește un șir de caractere din fișierul cu descriptorul f și îl memorează la adresa s. Transferul se termină atunci când s-au citit n-1 caractere sau s-a întâlnit caracterul newline. La terminarea transferului, se adaugă la sfârșitul șirului din memorie terminatorul de șir ‘\0’. Dacă citirea s-a terminat prin întâlnirea caracterului newline, acesta va fi transferat în memorie, caracterul nul fiind adăugat după el (spre deosebire de gets, care nu îl reține). La întâlnirea sfârșitului de fișier (fără a fi transferat vreun caracter) sau în caz de eroare fgets returnează NULL. În caz de succes returnează adresa șirului citit (aceeași cu cea primită în parametrul s). Funcția fputs scrie în fișierul f caracterele șirului de caractere memorat la adresa s. Terminatorul de șir (‘\0’) nu este scris și nici nu se adaugă caracterul newline. În caz de succes fputs returnează ultimul caracter scris. În caz de eroare funcția returnează EOF. Exemplu: Creare/adăugare/închidere fișier #include <stdio.h> int main(){ char sir[15]; FILE *f; f=(fopen("F:\\temp\\student.txt","a")); if(f==NULL) printf("Err!"); else puts("Inserati un string: "); gets(sir); fputs(sir,f); fclose(f); return 0; } Exemplu: Citire din fișier #include <stdio.h> main() { FILE *f;

284


Algoritmică şi programare

char sir[255]; f = fopen("F:\\temp\\test1.txt", "r"); fscanf(f, "%s", sir); printf("1 : %s\n", sir); fgets(sir, 255, (FILE*)f); printf("2: %s\n", sir); fgets(sir, 255, (FILE*)f); printf("3: %s\n", sir); fclose(f); }

Aplicații practice 1. Scrieți un program C care sa creeze un fișier cu numele Studenti.txt pentru citirea numelui si CNP-ului unui număr de studenți stabilit de utilizator. #include <stdio.h> int main(){ char name[50]; int cnp,i,n; printf("Numarul de studenti: "); scanf("%d",&n); FILE *f; f=(fopen("F:\\temp\\student.txt","w")); if(f==NULL) printf("Err!"); else for(i=0;i<n;++i) { printf("Pentru studentul %d\nInserati numele: ",i+1); scanf("%s",name); printf("Inserati CNP: "); scanf("%d",&cnp); fprintf(f,"\nNume: %s \nCNP=%d \n",name,cnp); } fclose(f); return 0; } 2. Scrieți un program C care sa creeze un fișier cu numele Studenti.txt pentru citirea numelui si CNP-ului unui număr de studenți stabilit de utilizator. Daca fișierul exista, sa se adauge informațiile in fișierul existent.

285


Algoritmică şi programare

#include <stdio.h> int main(){ char name[50]; int cnp,i,n; printf("Numarul de studenti: "); scanf("%d",&n); FILE *f; f=(fopen("F:\\temp\\student.txt",“a")); if(f==NULL) printf("Err!"); else for(i=0;i<n;++i) { printf("Pentru studentul %d\nInserati numele: ",i+1); scanf("%s",name); printf("Inserati CNP: "); scanf("%d",&cnp); fprintf(f,"\nNume: %s \nCNP=%d \n",name,cnp); } fclose(f); return 0; } 3. Să se scrie programul care citește un număr natural n cu semnificația de număr de studenți, numele și nota fiecăruia din cei n studenți, creează un fișier binar în care se scriu datele celor n studenți, închide fișierul, deschide fișierul, citește fișierul și afișează conținutul fișierului. #include <stdio.h> #include <conio.h> struct student { char nume[30]; int nota; }s; // Tipul de data student int main() // Functia principala { FILE *f; int n,i; char idf[50]; 286


Algoritmică Ĺ&#x;i programare

printf(" Identificator fisier : "); gets(idf); if((f=fopen(idf,"wb"))==NULL) // Creare fisier binar printf(" Eroare deschidere sursa \n"); else printf(" Numar studenti : "); // Preluare date de la tastatura scanf("%d",&n); for(i=1;i<=n;i++) { printf(" Nume student %d : ",i); scanf("%s",&s.nume); printf(" Nota studentului %s :",s.nume); scanf("%d",&s.nota); fwrite(&s,sizeof(s),1,f); // Scriere in fisier } fclose(f); // Inchidere fisier if((f=fopen(idf,"rb"))==NULL) // Deschidere fisier binar printf(" Eroare deschidere sursa \n"); else printf(" Fisierul %s contine studentii \n",idf); for(i=1;i<=n;i++) { fread(&s,sizeof(s),1,f); // Citire din fisier printf(" Studentul %s are nota %d \n",s.nume,s.nota); // Afisare } fclose(f); getch(); }

287


Algoritmică şi programare

Metode de programare Metoda “Divide et Impera” Metoda “Divide et Impera” (desparte și stăpânește) reprezintă o metodă de elaborare a algoritmilor care constă în descompunerea problemei de rezolvat în două sau mai multe subprobleme independente, similare problemei inițiale, care la rândul lor, se descompun în două sau mai multe subprobleme, până când se obțin subprobleme a căror rezolvare este directă și nu mai necesită alte descompuneri. Soluția problemei inițiale se obține prin combinarea soluțiilor problemelor cu rezolvare directă în care a fost descompusă. Metoda “Backtracking” Metoda „Backtracking” reprezintă o metodă de elaborare a algoritmilor care pentru rezolvarea anumitor probleme este necesară desfășurarea unui proces de căutare a soluției aflate într-o anumită mulțime, numită spațiul stărilor. Pentru fiecare element din spațiul stărilor este definită o mulțime de acțiuni sau alternative. Momentul inițial în rezolvarea problemei corespunde unei stări, numită inițială, iar soluțiile corespund drumurilor în spațiul stărilor, de la cea inițială până la una finală. Procesul de rezolvare a problemei poate fi imaginat ca o secvență de acțiuni care asigură deplasarea, prin intermediul unei secvențe de stări, în spațiul stărilor, din starea inițială la cea finală. În cazul anumitor probleme se dorește obținerea unei singure soluții, altele solicită determinarea tuturor soluțiilor sau determinarea unei soluții optime, dintr-un anumit punct de vedere, numită soluție optimală.

Metoda “Divide et Impera” Metoda presupune: • Descompunerea problemei curente P in subprobleme independente SPi. • In cazul in care subproblemele SPi admit o rezolvare imediata se compun soluțiile si se rezolva problema P. Altfel se descompun in mod similar si subproblemele Spi • soluția finala se obține c o m b i n â n d soluțiile subproblemelor rezolvate separat. Evident, nu toate problemele își găsesc rezolvarea prin utilizarea acestei metode. Majoritatea algoritmilor “Divide et Impera” presupun prelucrări pe tablouri (dar nu obligatoriu). Aceasta metoda (tehnica) se poate implementa atât iterativ dar si recursiv. Dat fiind ca problemele se împart in subprobleme in mod recursiv, 288


Algoritmică şi programare

de obicei împărțirea se realizează până când șirul obținut este de lungime 1, caz in care rezolvarea subproblemei este foarte ușoara, chiar banala.

Pașii metodei 1. Descompunerea problemei in doua sau mai multe subprobleme; DIVIDE 2. Rezolvare probleme separat; IMPERA 3. Combinarea rezultatelor parțiale;

Fig. 70 Metoda “Divide et Impera”

289


Algoritmică şi programare

Aplicații practice Calculul sumei primelor n numere naturale (Suma Gauss)

Fig. 71 Suma Gauss

#include "stdio.h" #include "conio.h“ int n; int s(int k,int p); void afis(int k,int p); int main() { int k,p; printf(" Inserati n: "); scanf("%d",&n); afis(1,n); printf(" S = %d ",s(1,n)); getch(); } int s(int k,int p) {

290


Algoritmică Ĺ&#x;i programare

if(k==p) return k; else return s(k,(k+p)/2)+s((k+p)/2+1,p); } void afis(int k,int p) { if(k==p) printf(" S(%d,%d)=%d\n",k,k,s(k,k)); else { afis(k,(k+p)/2); afis((k+p)/2+1,p); printf(" S(%d,%d)=%d\n",k,(k+p)/2, s(k,(k+p)/2)); printf("S(%d,%d)=%d \n",(k+p)/2+1,p, s((k+p)/2+1,p)); } }

291


Algoritmică şi programare

Calculul n factorial (n!)

Fig. 72 n factorial

#include "stdio.h" #include "conio.h“ int n; int fact(int k,int p); void afis(int k,int p); int main() { int k,p; printf(" n = "); scanf("%d",&n); afis(1,n); printf(" S = %d ",fact(1,n)); getch(); } int fact(int k,int p) 292


Algoritmică Ĺ&#x;i programare

{ if(k==p) return k; else return fact(k,(k+p)/2)*fact((k+p)/2+1,p); } void afis(int k,int p) { if(k==p) printf("fact(%d,%d)=%d \n",k,k,fact(k,k)); else { afis(k,(k+p)/2); afis((k+p)/2+1,p); printf("fact(%d,%d)=%d \n",k,(k+p)/2, fact(k,(k+p)/2)); printf(" fact(%d,%d)=%d \n", (k+p)/2+1, p, fact((k+p)/2+1,p)); } } Determinarea maximului dintr-un vector de numere reale

Fig. 73 Maximul dintr-un vector de numere reale

#include <stdio.h> #include <conio.h> #include <stdlib.h> int n; typedef float sir[100]; sir x; float max(sir x,int k,int p) { float m1,m2; if(k==p) return x[k]; else { m1=max(x,k,(k+p)/2); 293


Algoritmică Ĺ&#x;i programare

m2=max(x,(k+p)/2+1,p); if(m1>=m2) return m1; else return m2; } } int main() { int k,p,i; char c[10]; printf(" Numarul de componente ale vectorului n = "); scanf("%d",&n); for(i=1;i<=n;i++) { printf(" Termenul %d = ",i); scanf("%f",&x[i]); } printf(" Maximul din vector este %4f \n",max(x,1,n)); getch(); } Algoritmul de sortare Quicksort #include <stdio.h> // Quicksort crescator #include <conio.h> #include <malloc.h> // Prototipurile functiilor void permut(float *,float *); int pivot(int,int); void quicksort(float *,int,int); void citv(float *,int); void afisv(float *,int); void permut(float *x,float *y) // Functia de permutare a lui x cu y { float aux; aux=*x; *x=*y; *y=aux; } int pivot(int i,int j) // Functia pentru alegerea pivotului { return (i+j)/2;

294


Algoritmică Ĺ&#x;i programare

} void quicksort(float *x,int m,int n) // Functia quicksort { int i,j,k; float cheie; if(m < n) { k=pivot(m,n); permut(&x[m],&x[k]); cheie=x[m]; i=m+1; j=n; while(i <= j) { while((i <= n) && (x[i] <= cheie)) i++; while((j >= m) && (x[j] > cheie)) j--; if(i < j) permut(&x[i],&x[j]); } permut(&x[m],&x[j]); quicksort(x,m,j-1); // stanga quicksort(x,j+1,n); // dreapta } } void citv(float *x, int n) //Fct pt citirea vectorului { int i; for(i=0;i<n;i++) { printf("v[%d] = ",i); scanf("%f",x+i); } } void afisv(float *x, int n) // Functia pentru afisarea vectorului { int i; for(i=0;i<n;i++) printf("v[%d] = %8.4f\n", i,*(x+i)); } int main() // Functia principala { 295


Algoritmică Ĺ&#x;i programare

int n; // n numarul de componente ale vectorului float *v; // *v pointer la float, adresa vectorului printf("Dimensiune vector = "); scanf("%d",&n); if((v=(float *)malloc(n*sizeof(float))) != NULL) { citv(v,n); printf("\n Vectorul nesortat \n\n"); afisv(v,n); quicksort(v,0,n-1); // Sortare printf("\n Vectorul sortat crescator \n\n"); afisv(v,n) ; } else printf(" Alocare esuata \n"); getch(); } đ?&#x;?đ?’Œâˆ’đ?&#x;?

Calculul sumei ∑đ?’?đ?’Œ=đ?&#x;? đ?&#x;‘đ?’Œ+đ?&#x;?

#include "stdio.h“// Suma cu metoda "Divide et Impera" #include "conio.h“ int n; float s(int k,int p); int main() { int k,p; printf(" n = "); scanf("%d",&n); afis(1,n); printf(" Suma = %f ",s(1,n)); getch(); } float s(int k,int p) { if(k==p) return (2.*k-1.)/(3.*k+1.); else return s(k,(k+p)/2)+s((k+p)/2+1,p); } void afis(int k,int p) { if(k==p)

296


Algoritmică Ĺ&#x;i programare

printf(" S(%d,%d)=%f \n",k,k,s(k,k)); else { afis(k,(k+p)/2); afis((k+p)/2+1,p); printf(" S(%d,%d)=%f\n",k,(k+p)/2, s(k,(k+p)/2)); printf("S(%d,%d)=%f \n",(k+p)/2+1,p, s((k+p)/2+1,p)); } } đ?&#x;?đ?’Œ+đ?&#x;?

Calculul produsului âˆ?đ?’?đ?’Œ=đ?&#x;? đ?&#x;‘đ?’Œ+đ?&#x;‘ #include "stdio.h" // Produs cu metoda "Divide et Impera" #include "conio.h“ int n; float prod(int k,int p); void afis(int k,int p); int main() { int k,p; printf(" n = "); scanf("%d",&n); afis(1,n); printf(" Produsul = %f ",prod(1,n)); getch(); } float prod(int k,int p) { if(k==p) return (2.*k+2.)/(3.*k+3.); else return prod(k,(k+p)/2)*prod((k+p)/2+1,p); } void afis(int k,int p) { if(k==p) printf(" prod(%d,%d)=%f \n",k,k,prod(k,k)); else { afis(k,(k+p)/2); afis((k+p)/2+1,p); printf(" prod(%d,%d)=%f\n",k,(k+p)/2,prod(k,(k+p)/2)); printf(" prod(%d,%d)=%f \n",(k+p)/2+1,p,prod((k+p)/2+1,p)); 297


Algoritmică şi programare

} }

Metoda “Backtracking” Metoda „Backtracking” reprezintă o metodă de elaborare a algoritmilor care pentru rezolvarea anumitor probleme este necesară desfășurarea unui proces de căutare a soluției aflate într-o anumită mulțime, numită spațiul stărilor. Considerăm un labirint având una sau mai multe ieșiri. Starea inițială poate fi considerată orice cameră a labirintului, problema revenind la găsirea unui drum din camera respectivă către una dintre ieșiri. Desfășurarea procesului de căutare a unei stări finale presupune, la fiecare etapă, alegerea opțiunii pentru o alternativă posibilă a stării curente și detectarea acelor stări capcană din care nu mai este posibilă continuarea procesului, sau deja se cunoaște excluderea atingerii unei stări finale. Detectarea stării capcană trebuie să determine revenirea la starea din care s-a ajuns la ea și selectarea unei noi opțiuni de continuare. În cazul în care nu mai există alternative care să nu fi fost selectate anterior, o astfel de stare devine la rândul ei capcană și pentru ea se aplică același tratament. Prin soluție a problemei se înțelege o secvență de acțiuni care determină tranziția din starea inițială într-o stare finală, fiecare componentă a unui drum soluție reprezentând o alternativă din mulțimea de variante posibile. Cu alte cuvinte, x 1 este alternativa aleasă pentru starea inițială, x 2 este alternativa selectată pentru starea în care s-a ajuns pe baza opțiunii x 1 , x 3 este alternativa selectată pentru starea în care s-a ajuns pe baza opțiunii x 2 ,…, iar x n este alternativa selectată pentru starea în care s-a ajuns pe baza opțiunii x n-1 . După efectuarea acțiunii corespunzătoare alegerii alternativei x n rezultă o stare finală. Procesul de căutare a unui drum soluție revine la tentativa de extindere a porțiunii de drum construit, alegând prima alternativă disponibilă pentru starea curentă atinsă. Continuarea drumului poate fi realizată până la atingerea unei stări finale sau până la întâlnirea unei stări capcană (mulțimea vidă de alternative). Dacă este atinsă o stare capcană, atunci este necesară revenirea la starea anterioară și selectarea următoarei alternative disponibile acestei stări. Dacă nu mai există alternative disponibile, atunci se inițiază o nouă revenire și așa mai departe. În cazul în care există cel puțin încă o alternativă disponibilă, atunci se reia procesul de extindere a drumului rezultat. În condițiile în care revenirea poate conduce la atingerea stării inițiale și pentru ea nu mai există alternative disponibile, se consideră că problema nu are soluție.

298


Algoritmică şi programare

Pentru implementarea căutării este necesară reținerea alternativei selectate pentru fiecare stare atinsă până la cea curentă, astfel încât, în cazul unei reveniri să fie posibilă alegerea alternativei următoare. Cu alte cuvinte, procesul de căutare revine la tentativa de extindere a drumului curent (pasul de continuare), cu eventuala revenire în cazul atingerii unei stări capcană (pasul de revenire - back), memorând alternativele selectate pentru fiecare stare intermediară atinsă (track). De aici își are geneza numele metodei Backtracking.

Backtracking nerecursiv Pentru implementarea metodei definim următoarele funcții. 1. Funcția succesor testează dacă mulțimea S k mai are elemente și care atribuie variabilei sccs (succesor) valoarea 1 dacă mulțimea S k mai are elemente sau valoarea 0 în caz contrar. void successor(sir x,int k,int &sccs) { if(x[k]<n) { sccs =1; x[k]=x[k]+1; } else as=0; } 2. Funcția validare verifică dacă sunt satisfăcute condițiile interne specifice problemei și care atribuie variabilei ev (este valid) valoarea 1 dacă sunt satisfăcute condițiile interne sau valoarea 0 în caz contrar. Implementarea acestei funcții diferă de la o problemă la alta, în funcție de condițiile de continuare. void validare(sir x,int k, int &ev) { ev=1; for(i=1;i<=k-1;i++) if(!conditie) ev=0; } 3. Funcția afișare care afișează o soluție rezultat: void afisare(sir x, int k) 299


Algoritmică şi programare

{ int i; printf(" ( "); for(i=1;i<=k-1;i++) printf("%d, ",a[x[i]]); printf("%d ) \n",a[x[i]]); } 4. Secvența Backtracking care codifică mecanismul de generare a soluțiilor: k=1; x[k]=0; while(k>0) { do { succesor(x,k,sccs); if(sccs) validare(x,k,ev); }while(sccs && !ev); if(sccs) if(k==n) afisare(x,k); else { k=k+1; x[k]=0; } else k=k-1; }

Backtracking recursiv În variantă recursivă se definește funcția Backtracking: void back(int k) { if(sol(k)) afisare(x,k); else { init(k); while(succesor(x,k,sccs)) 300


Algoritmică şi programare

if(validare(x,k,ev)) back(k+1); } } Funcția are un singur parametru de tip întreg, care este indice al vectorului soluție. Funcția testează dacă s-a generat o soluție prin apelul funcției sol. Definiția funcției sol este: int sol(int k) { return k==n+1; } Dacă s-a obținut o soluție, aceasta este afișată de funcția afișare, cu aceeași definiție ca în cazul nerecursiv. Dacă nu s-a obținut o soluție, se inițializează nivelul k cu valoarea aflată înaintea tuturor valorilor posibile. Inițializarea este făcută de funcția init: void init(int k) { x[k]=0; } Funcția init efectuează inițializarea lui xk cu o valoare prin care se indică faptul că, până la acel moment, nu a fost selectată nici o alternativă pentru poziția k a vectorului x; După inițializare, se generează succesiv toate valorile din mulțimea Sk. Pentru generarea acestor valori se folosește funcția succesor, modificată fată de cazul nerecursiv în sensul că returnează valoarea variabilei de tip întreg sccs: int succesor(sir x,int k,int &sccs) { if(x[k]<n) { sccs=1; x[k]=x[k]+1; } else sccs=0; return sccs; } 301


Algoritmică şi programare

Pentru fiecare valoare generată se testează cu funcția validare condițiile de continuare. Funcția validare este modificată fată de cazul nerecursiv prin returnarea valorii de tip întreg a variabilei ev: int validare(sir x,int k, int &ev) { ev=1; for(i=1;i<=k-1;i++) if(!conditie) ev=0; return ev; } Dacă condițiile de continuare sunt îndeplinite se generează următoarea valoare pentru componenta k prin apelul recursiv al funcției back. În funcția main() se apelează funcția back cu parametrul 1 deoarece algoritmul pleacă de la componenta de indice 1.

Pașii metodei Pentru determinarea unei singure soluții, metoda presupune parcurgerea următorilor pași: 1. starea inițială x 1 a problemei este prima alternativă posibilă pentru starea curentă; 2. dacă starea curentă rezultată prin alternativa x 1 este finală, atunci soluția are o singură componentă, vectorul x= (x 1 ) și stop; 3. altfel, este selectată prima alternativă din mulțimea de acțiuni posibile pentru starea curentă, x 2 є S 2 ; 4. dacă secvența de alternative care a condus la starea curentă este x=(x 1, x 2, …, x k ) , atunci: 5. dacă starea curentă este finală, soluția este vectorul x=(x 1, x 2, …, x k ) şi stop; 6. dacă starea curentă nu este starea finală atunci: 7. dacă pentru starea curentă există alternative disponibile, atunci se alege prima dintre ele şi se continuă; 8. altfel, se revine la starea anterioară celei curente, soluția parțial construită devine x=(x 1, x 2, …, x k-1 ) și se face salt la Pasul 7. 9. dacă, în urma unui pas de revenire, s-a ajuns la starea inițială și nu mai sunt alternative disponibile, atunci problema nu are soluție și stop.

302


Algoritmică şi programare

În cazul în care trebuie determinate toate soluțiile problemei, căutarea continuă după determinarea fiecărei soluții prin efectuarea de reveniri succesive. Terminarea căutării este decisă în momentul în care s-a revenit la starea inițială și nu mai există alternative disponibile.

Aplicații practice Problema celor n dame si numărul soluțiilor rezultat (Backtracking recursiv) #include <stdio.h> #include <conio.h> typedef int sir[100]; sir x; int i,n,k,as,ev,nsol; void afisare(sir x, int k); void back(int k); void init(int k); int sol(int k); int succesor(sir x,int k,int &as); int validare(sir x,int k, int &ev); int main() { nsol=1; printf(" Problema celor n dame \n"); printf(" Numarul de dame n = "); scanf("%d",&n); back(1); printf(" Numarul total de solutii rezultat este %d \n",nsol-1); getch(); } void init(int k) { x[k]=0; } int sol(int k) { return k==n+1; } int succesor(sir x,int k,int &as) { if(x[k]<n) { as=1; 303


Algoritmică şi programare

x[k]=x[k]+1; } else as=0; return as; } int validare(sir x,int k, int &ev) { ev=1; for(i=1;i<=k-1;i++) if(x[k]==x[i] || (k-i==abs(x[k]-x[i]))) ev=0; return ev; } void afisare(sir x, int k) { for(i=1;i<=k-1;i++) printf(" Dama %d este pe linia %d si coloana %d \n",i,i,x[i]); } void back(int k) { if(sol(k)) { printf("Solutia %d\n",nsol); nsol++; afisare(x,k); } else { init(k); while(succesor(x,k,as)) if(validare(x,k,ev)) back(k+1); } } Permutările unei mulțimi de n obiecte (Backtracking nerecursiv) #include <stdio.h> #include <conio.h> typedef int sir[100]; sir a,x; int i,k,n,as,ev,nf;

304


Algoritmică Ĺ&#x;i programare

void succesor(sir x,int k,int &as); void validare(sir x,int k, int &ev); void afisare(sir x, int k); int main() { printf("Generarea permutarilor de n obiecte \n"); nf=0; printf(" Numarul de obiecte n = "); scanf("%d",&n); printf(" Tastati obiectele \n"); for(i=1;i<=n;i++) { printf(" a [ %d ] = ",i); scanf("%d",&a[i]); } k=1;x[k]=0; while(k>0) { do { succesor(x,k,as); if(as) validare(x,k,ev); } while(as && !ev); if(as) if(k==n) { nf++; afisare(x,k); } else { k=k+1; x[k]=0; } else k=k-1; } printf(" Numarul permutarilor de %d obiecte este egal cu %d \n",n,nf); getch(); } void afisare(sir x, int k) { int i; printf(" %d ( ",nf); for(i=1;i<=k-1;i++) printf("%d, ",a[x[i]]); printf("%d ) \n",a[x[i]]); } void succesor(sir x,int k,int &as) { 305


Algoritmică şi programare

if(x[k]<n) { as=1; x[k]=x[k]+1; } else as=0; } void validare(sir x,int k, int &ev) { ev=1; for(i=1;i<=k-1;i++) if(!(x[k]!=x[i])) ev=0; } Combinări de n obiecte luate câte p (Backtracking nerecursiv) #include <stdio.h> #include <conio.h> typedef int sir[100]; sir a,x; int p,i,k,n,as,ev,cnp; void succesor(sir x,int k,int &as); void validare(sir x,int k, int &ev); void afisare(sir x, int k); int main() { printf(" Generarea combinarilor de n luate cate p \n"); cnp=0; printf(" Numarul de obiecte n = "); scanf("%d",&n); printf(" Tastati obiectele \n"); for(i=1;i<=n;i++) { printf(" a [ %d ] = ",i); scanf("%d",&a[i]); } printf(" Combinari luate cate p = "); scanf("%d",&p); if(p<=n) { k=1; x[k]=0; while(k>0)

306


Algoritmică Ĺ&#x;i programare

{

do {succesor(x,k,as); if(as) validare(x,k,ev); }while(as && !ev); if(as) if(k==p) {cnp++; afisare(x,k); } else { k=k+1; x[k]=0; } else k=k-1; } printf(" Numarul combinarilor de %d obiecte luate cate %d este egal cu %d \n",n,p,cnp); getch(); } else printf(" Eroare in date: p>n \n"); getch(); } void succesor(sir x,int k,int &as) { if(x[k]<n) { as=1; x[k]=x[k]+1; } else as=0; } void validare(sir x,int k, int &ev) { ev=1; if((k>=2) && !(a[x[k]]>a[x[k-1]])) ev=0; } void afisare(sir x, int k) { 307


Algoritmică Ĺ&#x;i programare

int i; printf(" %d ( ",cnp); for(i=1;i<=k-1;i++) printf("%d, ",a[x[i]]); printf("%d ) \n",a[x[i]]); }

308


Algoritmică şi programare

Bibliografie [1] Ariton V., Fundamente ale tehnologiei informaţiilor şi comunicaţiilor, Ed. Didactică şi Pedagogică, 2003; [2] Viorel A., Gheorghe P., Postolache F., Răileanu A.B., Bazele informaticii (curs id sem II), Editura Universitară Danubius, 2010, ISBN 978-606-533-080-1; [3] Postolache F., Ariton V., Informatică economică, Editura Universitară Danubius, 2013, ISBN 978-606-533-319-2; [4] Băutu A., Vasiliu P., Bazele programării calculatoarelor, Ed. A.N.M.B., Constanţa, 2009; [5] Carlisle M., Introduction to programming course handouts, 2010; [6] Dragomir F.L., Postolache F., Alexandrescu G., Tools for Hierarchical Security Modeling - The 14th International Scientific Conference "eLearning and Software for Education", Bucharest: "Carol I" National Defence University. (eLSE 2018), April 19 - 20, 2018, Publisher: Advanced Distributed Learning Association, Volume 4|DOI: 10.12753/2066-026X-18-219|Pages: 34-38 [7] Dragomir F.L., Postolache F., Online Collaboration and Learning Environment IT Modelling, The 13th International Scientific Conference "eLearning and Software for Education", Bucharest: "Carol I" National Defence University. (eLSE 2017), April 27 - 28, 2017, Publisher: Advanced Distributed Learning Association, Volume 1|DOI: 10.12753/2066-026X-17-056|Pages: 382-386. [8] Kris Jamsa & Lars Klander, Totul despre C şi C++, Ed. Teora, 2000; [9] Postolache F, Introducere în ştiința calculatoarelor, Editura Zigotto, 2013, ISBN 978-606-669-092-8; [10] Schildt H., C++ manual complet, Ed. Teora, 1997; [11] http://raptor.martincarlisle.com/

309


Turn static files into dynamic content formats.

Create a flipbook
Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.