15
Bibliografie selectivă recomandată Burileanu, D., Dan, C., Pădure, M., Programare în C. Culegere de probleme, Editura Printech, Bucureşti, 2004. Costea, D., Iniţiere în limbajul C, Editura Teora, Bucureşti, 1995. Cristea, V., Ciumale, C., Kalisz, E., Panoiu, A., Limbajul C standard, Editura Teora, Bucureşti, 1992. Dan, C., Burileanu, D., Introducere în programarea calculatoarelor. Limbajul C, Editura Printech, Bucureşti, 2001. Kernigham, B.W., Ritchie, D.M., The C Programming Language, Pretice Hall. Knuth, D.E., Tratat de programarea calculatoarelor Algoritmi fundamentali, vol. I, Editura Tehnică, Bucureşti, 1973. Năstac, D.I. Programarea calculatoarelor în limbajul C Elemente fundamentale, Editura Printech, Bucureşti, 2006. Negrescu, L., Limbajele C şi C++ pentru începători, Vol. I, Editura Microinformatica, Cluj- Napoca, 1996. Negrescu, L., Limbajul C Culegere de probleme, Fascicolele 1-2, Cluj-Napoca, 1991. Rusu, I., Gavrilescu, D., Grosu, V., Îndrumar de laborator pentru programarea calculatoarelor, Editura MATRIX ROM, Bucureşti, 2004. Rusu, I., Gavrilescu, D., Grosu, V., Programarea calculatoarelor în limbaj C, Editura MATRIX ROM, Bucureşti, 2002.

Curs 1 si 2

Embed Size (px)

DESCRIPTION

Curs 1 si 2 Adrian Lupasc

Citation preview

Page 1: Curs 1 si 2

Bibliografie selectivă recomandată

Burileanu, D., Dan, C., Pădure, M., Programare în C. Culegere de probleme, Editura Printech,

Bucureşti, 2004.

Costea, D., Iniţiere în limbajul C, Editura Teora, Bucureşti, 1995.

Cristea, V., Ciumale, C., Kalisz, E., Panoiu, A., Limbajul C standard, Editura Teora, Bucureşti,

1992.

Dan, C., Burileanu, D., Introducere în programarea calculatoarelor. Limbajul C, Editura

Printech, Bucureşti, 2001.

Kernigham, B.W., Ritchie, D.M., The C Programming Language, Pretice Hall.

Knuth, D.E., Tratat de programarea calculatoarelor – Algoritmi fundamentali, vol. I, Editura

Tehnică, Bucureşti, 1973.

Năstac, D.I. Programarea calculatoarelor în limbajul C – Elemente fundamentale, Editura

Printech, Bucureşti, 2006.

Negrescu, L., Limbajele C şi C++ pentru începători, Vol. I, Editura Microinformatica, Cluj-

Napoca, 1996.

Negrescu, L., Limbajul C – Culegere de probleme, Fascicolele 1-2, Cluj-Napoca, 1991.

Rusu, I., Gavrilescu, D., Grosu, V., Îndrumar de laborator pentru programarea calculatoarelor,

Editura MATRIX ROM, Bucureşti, 2004.

Rusu, I., Gavrilescu, D., Grosu, V., Programarea calculatoarelor în limbaj C, Editura MATRIX

ROM, Bucureşti, 2002.

Page 2: Curs 1 si 2

1. Noţiuni de bază în programarea calculatoarelor

Programarea este o componentă a informaticii care are ca principal obiectiv realizarea de

programe care să constituie soluţiile oferite cu ajutorul calculatorului unor probleme date. Programatorii

sunt acele persoane apte să implementeze într-un limbaj de programare algoritmul propus ca soluţie la

respectiva problemă, ce se pretează a fi rezolvată cu ajutorul unui sistem de calcul. După nivelul de

implicare în efortul de rezolvare a problemelor specialiştii în programare pot fi împărţiţi în mai multe

categorii: analişti, analişti-programatori, ingineri-programatori, programatori amatori, etc. Cu toţii au

însă în comun faptul că fiecare trebuie să cunoască sintaxa şi semantica unui limbal de programare şi să

fie capabil, nu doar să citească, ci chiar să scrie “codul sursă”, adică programul propriu-zis. Din acest

punct de vedere cunoştinţele de programare sunt considerate “ABC-ul” informaticii şi sunt indispensabile

oricărui profesionist în domeniu şi nu numai.

Pentru a putea fi rezolvată cu ajutorul unui sistem de calcul, fiecare problemă va trebui să treacă

prin trei etape obligatorii: analiza problemei, proiectarea algoritmului de soluţionare şi implementarea

algoritmului într-un program pe calculator. În ultima etapă, sub acelaşi nume, au fost incluse în plus

două subetape cunoscute sub numele de testarea şi întreţinerea programului. Aceste subetape nu lipsesc

din “ciclul de viaţă” a oricărui produs-program.

Dacă etapa implementării algoritmului într-un program executabil este o etapă exclusiv practică,

realizată “în faţa calculatorului”, celelalte două etape au un pronunţat caracter teoretic. În consecinţă,

primele două etape sunt caracterizate de un anumit grad de abstractizare. Din punct de vedere practic

însă, şi în ultimă instanţă, criteriul decisiv ce conferă succesul rezolvării problemei este dat de calitatea

implementării propriu-zise. Mai exact, succesul soluţionării este dat de performanţele programului:

utilitate, viteză de execuţie, fiabilitate, posibilităţi de dezvoltare ulterioare, lizibilitate, etc. Cu toate

acestea este imatură şi neprofesională “strategia” programatorilor începători care, neglijînd primele două

etape, sar direct la a treia, evitând etapa de analiză şi componenta abstractă a efortului de soluţionare. Ei

se justifică cu toţii prin expresii puerile de genul: “Eu nu vreau să mai pierd vremea cu “teoria”, am să

fac programul cum ştiu eu. Câtă vreme nu va face altcineva altul mai bun decît al meu, nu am de ce să-

mi mai bat capul!”.

Page 3: Curs 1 si 2

Stabilirea corectitudinii şi eficienţei soluţionării problemei de rezolvat

Este adevărat că etapa care vizează implementarea unei probleme este fundamentală, dar primele

două etape au o importanţă capitală. Ele sunt singurele ce pot oferi răspunsuri corecte la următoarele

întrebări dificile: Există certitudinea că soluţia găsită este şi cea corectă? Există certitudinea că

problema este rezolvată complet? Cât de eficientă este soluţia găsită? Cât de aproape este soluţia aleasă

de cea optimă?

Să menţionăm în plus că literatura de specialitate conţine un număr impresionant de probleme

“capcană” pentru începători, şi nu numai pentru ei. Ele provin majoritatea din realitatea imediată dar

pentru fiecare dintre ele nu se cunosc soluţii eficiente. De exemplu, este dovedit teoretic că problema,

“aparent banală” pentru un calculator, a proiectării Orarului optim într-o instituţie de învăţămînt

(facultate sau liceu) este o problemă intratabilă la ora actuală (toate programele care s-au realizat pînă

acum nu oferă decît soluţii aproximative fără a putea spune cât de aproape sau de departe este soluţia

optimă de orar).

Câţi dintre programatorii începători n-ar fi surprinşi să afle că problema “atât de simplă” (ca

enunţ), a cărei soluţionare tocmai au abandonat-o, este de fapt o problemă dovedită teoretic ca fiind

intratabilă sau chiar insolvabilă algoritmic? Partea proastă a lucrurilor este că problemele netratabile pot

fi cu uşurinţă confundate cu unele uşoare la o privire rapidă şi lipsită de experienţă.

Etapa de analiză este singura care permite dovedirea cu argumente riguroase a corectitudinii

soluţiei, iar etapa de proiectare este singura care poate oferi argumente precise în favoarea eficienţei

soluţiei propuse.

În general problemele concrete din informatică au în forma lor iniţială sau în enunţ o caracteristică

pragmatică, fiind foarte ancorate în realitatea imediată. Totuşi ele conţin în formularea lor iniţială un grad

mare de eterogenitate, diversitate şi lipsă de rigoare. Fiecare dintre aceste “defecte” este un obstacol

major pentru demonstrarea corectitudinii soluţiei. Rolul esenţial al etapei de analiză este acela de a

transfera problema într-un plan abstract, adică de a o modela. Acest “univers paralel abstract” este dotat

cu mai multă rigoare şi disciplină internă, avînd legi precise, şi poate oferi instrumentele logice şi formale

necesare pentru demonstrarea riguroasă a corectitudinii soluţiei problemei. Planul abstract în care sunt

“transportate” toate problemele de informatică este planul sau universul obiectelor matematice iar

corespondentul problemei în acest plan va fi modelul matematic abstract asociat problemei.

Demonstrarea corectitudinii proprietăţilor ce leagă obiectele universului matematic a fost şi este sarcina

matematicienilor. Celui ce analizează problema din punct de vedere informatic îi revine sarcina (nu

tocmai uşoară) de a dovedi printr-o demonstraţie constructivă că există o corespondenţă precisă între

Page 4: Curs 1 si 2

părţile componente ale problemei reale, “dezasamblată” în timpul analizei, şi părţile componente ale

modelului abstract asociat. Odată descoperită, formulată precis şi dovedită, această “perfectă oglindire”

a problemei reale în planul obiectelor matematice oferă certitudinea că toate proprietăţile şi legăturile ce

există între subansamblele modelului abstract se vor regăsii precis (prin reflectare) între părţile interne

ale problemei reale, şi invers. Atunci, soluţiei abstracte descoperite cu ajutorul modelului matematic

abstract îi va corespunde o soluţie reală concretizată printr-un algoritm ce poate fi implementat într-un

program executabil.

Ideea centrală a etapei a doua – proiectarea unui algoritm de soluţionare eficient poate fi formulată

astfel: din studiul proprietăţilor şi limitelor modelului matematic abstract asociat problemei se deduc

limitele inferioare ale complexităţii minimale (“efortului minimal obligatoriu”) inerente oricărui

algoritm ce va soluţiona problema în cauză. Complexitatea internă a modelului abstract şi complexitatea

soluţiei abstracte se va reflecta imediat asupra complexităţii reale a algoritmului, adică asupra eficienţei

de soluţionare a problemei. Acest fapt permite prognosticarea încă din această fază – faza de proiectare

a algoritmului de soluţionare – a eficienţei practice, măsurabilă ca durată de execuţie, a programului.

Algoritmul

Se ştie că la baza oricărui program stă un algoritm (care, uneori, este numit metodă de rezolvare).

Noţiunea de algoritm este o noţiune fundamentală în informatică şi înţelegerea ei, alături de înţelegerea

modului de funcţionare a unui calculator, permite înţelegerea noţiunii de program executabil. Vom oferi

în continuare o definiţie unanim acceptată pentru noţiunea de algoritm:

Definiţie. Prin algoritm se înţelege o mulţime finită de operaţii (instrucţiuni) elementare care executate într-o

ordine bine stabilită (determinată), pornind de la un set de date de intrare dintr-un domeniu de valori posibile

(valide), produce în timp finit un set de date de ieşire (rezultate).

Cele trei caracteristici esenţiale ale unui algoritm sunt:

Determinismul – dat de faptul că ordinea de execuţie a instrucţiunilor algoritmului este bine precizată

(strict determinată). Acest fapt dă una din calităţile de bază a calculatorului: “el” va face întotdeauna

ceea ce i s-a cerut (prin program) să facă, “el” nu va avea iniţiative sau opţiuni proprii, “el” nu-şi

permite să greşească nici măcar odată, “el” nu se va plictisi ci va duce programul la acelaşi sfârşit

indiferent de câte ori i se va cere să repete acest lucru. Nu aceeaşi situaţie se întâmplă cu fiinţele umane

(Errare humanum est). Oamenii pot avea în situaţii determinate un comportament non-deterministic

(surprinzător). Acesta este motivul pentru care numeroşi utilizatori de calculatoare (de exemplu

contabilii), datorită fenomenului de personificare a calculatorului (confundarea acţiunilor şi dialogului

Page 5: Curs 1 si 2

“simulat” de programul ce rulează pe calculator cu reacţiile unei personalităţi vii), nu recunosc

perfectul determinism ce stă la baza executării oricărui program pe calculator.

Universalitatea – dată de faptul că, privind algoritmul ca pe o metodă automată (mecanică) de

rezolvare, această metodă are un caracter general-universal. Algoritmul nu oferă o soluţie punctuală,

pentru un singur set de date de intrare, ci oferă soluţie pentru o mulţime foarte largă (de cele mai multe

ori infinită) de date de intrare valide. Aceasta este trăsătura de bază care explică deosebita utilitate a

calculatoarelor şi datorită acestei trăsături suntem siguri că investiţia financiară făcută prin cumpărarea

unui calculator şi a produsului software necesar va putea fi cu siguranţă amortizată. Cheltuiala se face

o singură dată în timp ce programul pe calculator va putea fi executat rapid şi economicos de un număr

oricât de mare de ori, pe date diferite!

De exemplu, algoritmul de rezolvare învăţată la liceu a ecuaţiilor de gradul doi: ax2+bx+c=0, se aplică

cu succes pentru o mulţime infinită de date de intrare: (a,b,c)\{0}xx.

Finitudinea – pentru fiecare intrare validă orice algoritm trebuie să conducă în timp finit (după un

număr finit de paşi) la un rezultat. Această caracteristică este analogă proprietăţii de convergenţă a

unor metode din matematică: trebuie să avem garanţia, dinainte de a aplica metoda (algoritmul), că

metoda se termină cu succes (ea converge către soluţie).

Să observăm şi diferenţa: în timp ce metoda matematică este corectă chiar dacă ea converge către

soluţie doar la infinit (!), un algoritm trebuie să întoarcă rezultatul după un număr finit de paşi. Să

observăm de asemenea că, acolo unde matematica nu oferă dovada, algoritmul nu va fi capabil să o

ofere nici el. De exemplu, nu este greu de scris un algoritm care să verifice corectitudinea afirmaţiei

Conjecturii lui Goldbach: “Orice număr par se scrie ca sumă de două numere prime”, dar, deşi

programul rezultat poate fi lăsat să ruleze pînă la valori extrem de mari, fără să apară nici un contra-

exemplu, totuşi conjectura nu poate fi astfel infirmată (dar nici afirmată!).

Descrierea algoritmilor

Două dintre metodele clasice de descriere a algoritmilor sunt Schemele logice şi Pseudo-Codul.

Ambele metode de descriere conţin doar patru operaţii (instrucţiuni) elementare care au fiecare un

corespondent atât schemă logică cât şi în pseudo-cod.

În cele ce urmează vom descrie doar varianta oferită de pseudo-cod deoarece folosirea schemelor

logice s-a redus drastic în ultimii ani. Schemele logice mai pot fi întâlnite sub numele de diagrame de

proces în anumite cărţi de specialitate inginereşti. Avantajul descrierii algoritmilor prin scheme logice

este dat de libertatea totală de înlănţuire a operaţiilor (practic, săgeata care descrie ordinea de execuţie,

Page 6: Curs 1 si 2

pleacă de la o operaţie şi poate fi trasată înspre orice altă operaţie). Este demonstrat matematic riguros

că descrierea prin pseudo-cod, deşi pare mult mai restrictivă (operaţiile nu pot fi înlănţuite oricum, ci

trebuie executate în ordinea citirii: de sus în jos şi de la stînga la dreapta), este totuşi perfect echivalentă.

Deci, este dovedit că plusul de ordine, rigoare şi simplitate pe care îl oferă descrierea prin pseudo-cod nu

îngrădeşte prin nimic libertatea programării. Totuşi, programele scrise în limbajele de asamblare, care

sunt mult mai compacte şi au dimensiunile mult reduse, nu ar putea fi descrise altfel decît prin scheme

logice.

1. Atribuirea

var:=expresie;

2. Intrare/Ieşire

Citeşte var1, var2, var3, …;

Scrie var1, var2, var3, …; sau Scrie expresia1, expresia2, expresia3,…;

3. Condiţionala

Dacă <condiţie_logică> atunci instrucţiune1 [altfel instrucţiune2];

4. Ciclurile – Există (din motive de uşurinţă a descrierii algoritmilor) trei tipuri de instrucţiuni de

ciclare. Ele sunt echivalente între ele, oricare variantă de descriere putînd fi folosită în locul celorlalte

două, cu modificări sau adăugiri minimale:

repetă instrucţiune1, instrucţiune2, … pînă când <condiţie_logică>;

cât timp <condiţie_logică> execută instrucţiune;

pentru var_contor:=val_iniţială pînă la val_finală execută instrucţiune;

În cazul ciclurilor, grupul instrucţiunilor ce se repetă se numeşte corpul ciclului iar condiţia logică

care permite sau nu reluarea execuţiei ciclului este denumită condiţia de ciclare. Observăm că ciclul de

tipul Repetă are condiţia de repetare la sfârşit ceea ce are ca şi consecinţă faptul că, corpul ciclului se

execută cel puţin odată, în mod obligatoriu, înainte de verificarea condiţiei logice. Nu acelaşi lucru se

întâmplă în cazul ciclului de tipul cât timp, când este posibil ca instrucţiunea compusă din corpul ciclului

să nu poată fi executată nici măcar odată. În plus, să mai observăm că ciclul de tipul Pentru … pînă la

conţine (în mod ascuns) o instrucţiune de incrementare a variabilei contor.

Limbajele de programare care sunt relativ apropiate de limbajele naturale sunt denumite limbaje

de nivel înalt (high-level), de exemplu limbajul Pascal, spre deosebire de limbajele de programare mai

apropiate de codurile numerice ale instrucţiunilor microprocesorului. Acestea din urmă se numesc

limbaje de nivel scăzut (low-level), de exemplu limbajul de asamblare. Limbajul de programare C are un

statut mai special el putând fi privit, datorită structurii sale, ca făcînd parte din ambele categorii.

Page 7: Curs 1 si 2

Peste tot unde în pseudo-cod apare cuvîntul instrucţiune el poate fi înlocuit cu oricare din cele

patru instrucţiuni elementare. Această substituire poartă numele de imbricare. Prin instrucţiune se va

înţelege atunci, fie o singură instrucţiune simplă (una din cele patru), fie o instrucţiune compusă.

Instrucţiunea compusă este formată dintr-un grup de instrucţiuni delimitate şi grupate în mod precis (între

acolade { } în C).

Spre deosebire de pseudo-cod care permite doar structurile noi formate prin imbricarea repetată

a celor patru instrucţiuni în modul precizat, schemele logice permit structurarea în orice succesiune a

celor patru instrucţiuni elementare, ordinea lor de execuţie fiind dată de sensul săgeţilor. Repetăm că

deşi, aparent, pseudo-codul limitează libertatea de descriere doar la structurile prezentate, o teoremă

fundamentală pentru programare afirmă că puterea de descriere a pseudo-limbajului este aceeaşi cu cea

a schemelor logice.

Forma de programare care se bazează doar pe cele patru structuri se numeşte programare

structurată (spre deosebire de programarea nestructurată bazată pe descrierea prin scheme logice).

Teorema de echivalenţă a puterii de descriere prin pseudo-cod cu puterea de descriere prin schemă

logică afirmă că programarea structurată (aparent limitată de cele patru structuri) este echivalentă cu

programarea nestructurată (liberă de structuri impuse). Evident, prin ordinea, lizibilitatea şi fiabilitatea

oferită de cele patru structuri elementare (şi asta fără a îngrădi libertatea de exprimare) programarea

structurată este net avantajoasă. În fapt, limbajele de programare nestructurată (Fortran, Basic) au fost

de mult scoase din uz, ele (limbajele de asamblare) sunt necesare a fi folosite în continuare doar în

programarea de sistem şi în programarea industrială (în automatizări).

Programul

Prin program se înţelege un şir de instrucţiuni-maşină care sunt rezultatul compilării algoritmului

proiectat spre rezolvarea problemei dorite ce a fost descris într-un limbaj de programare (ca şi cod sursă).

Etapele realizării unui program sunt:

editarea codului sursă, etapă ce se realizează cu ajutorul unui program editor de texte rezultatul fiind

un fişier C, cu extensia .c (.cpp);

compilarea, etapa de traducere din limbajul de programare C în limbajul intern al micro-procesorului,

şi este realizată cu ajutorul programului compilator C şi are ca rezultat un fişier obiect, cu extensia

.obj (în limbajul C) sau .exe (în limbajul Pascal);

link-editarea, etapă la care se adaugă modului obiect rezultat la compilare diferite module conţinînd

subprograme şi rutine de bibliotecă, rezultînd un fişier executabil (această etapă este comasată în

Turbo Pascal sau Borland Pascal cu etapa de compilare), cu extensia .exe

Page 8: Curs 1 si 2

execuţia (Run), etapa de lansare în execuţie propriu-zisă a programului obţinut, lansare realizată de

interpretorul de comenzi al sistemului de operare (command.com pentru sistemele DOS+Windows)

Observăm că aceste etape sunt complet independente în timp unele de altele şi necesită pentru

limbajul C utilizarea a patru programe ajutătoare: editor de texte, compilator C, link-editor şi

interpretorul de comenzi al sistemului de operare. În cazul mediilor de programare integrate (Borland)

comandarea acestor patru programe ajutătoare precum şi depanarea erorilor de execuţie este mult

facilitată.

OBSERVAŢIE: Link-editarea reprezintă asamblarea unor module precompilate pentru a rezulta un fişier

executabil. De exemplu, dacă avem mai multe module de program (să zicem diverse funcţii) le putem

compila pe fiecare în mod separat şi în felul acesta putem observa şi erorile mai uşor iar la sfârşit le

linkedităm şi va rezulta programul. Fişierele precompilate (pe care le vom link-edita) au avantajul că se

pot folosi la diferite proiecte păstrând de exemplu o oarecare “paternitate” asupra lor, va fi foarte dificil

ca cineva să obţinţă codul sursă şi să-l poată modifica fără să depună un efort considerabil. Deci va putea

utiliza funcţiile dar e destul de dificil să modifici sau să copiezi într-un program personal doar câteva din

aceste funcţii. E mai uşoară şi depanarea programului pe module.

De asemenea, merită subliniat faptul că în timp ce fişierul text C, ce conţine codul sursă, poate fi

transportat pe orice maşină (calculator) indiferent de micro-procesorul acesteia urmând a fi compilat “la

faţa locului”, în cazul fişierului obiect acesta nu mai poate fi folosit decât pe maşina (calculatorul) pentru

care a fost creat (datorită instrucţiunilor specifice micro-procesorului din care este compus). Deci, pe

calculatoare diferite (avînd micro-procesoare diferite) vom avea nevoie de compilatoare C diferite.

În plus, să remarcăm faptul că fişierele obiect rezultate în urma compilării pot fi link-editate

împreună, chiar dacă provin din limbaje de programare diferite. Astfel, un program rezultat (un fişier

.exe sau .com) poate fi compus din module obiect care provin din surse diferite (fişiere Pascal, C,

asamblare, etc.).

Page 9: Curs 1 si 2

2. Introducere în limbajul C – noţiuni de bază

Toate elementele care compun un limbaj de programare, aşa cum este şi limbajul C nu sunt de

sine stătătoare, ci în conjuncţie unul cu celălalt. Din acest motiv este important să fie înţelese aspectele

fundamentale ale limbajului, înainte ca ele să fie detaliate.

Toate aplicaţiile realizate prin intermediul limbajului C au anumite trăsături comune. Programele

conţin cel puţin o funcţie, fiecare incluzând una sau mai multe instrucţiuni. O funcţie poate fi definită ca

fiind o subrutină care poate fi apelată de diferite părţi ale unei aplicaţii. O instrucţiune se referă la o

acţiune care trebuie să fie executată de un program. Toate instrucţiunile scrise într-un program trebuie să

aparţină unei funcţii şi se termină obligatoriu cu caracterul ;.

Cea mai simplă formă a unei funcţii este următoarea:

nume_funcţie()

{

instrucţiuni;

}

unde nume_funcţie reprezintă numele unei funcţii, iar instrucţiuni reprezintă secvenţa de instrucţiuni (una

sau mai multe) care aparţine funcţiei declarate.

În ceea ce priveşte numele funcţiei (şi nu numai) trebuie spus că limbajul C este de tip CASE-

SENSITIVE, ceea ce presupune ca programatorul să aibă în vedere faptul că, compilatorul C face

deosebire între litere mari şi litere mici.

Orice program în limbajul C conţine cel puţin o funcţie: funcţia main(). Execuţia fiecărui program

începe cu funcţia main(), ceea ce înseamnă că dacă programatorul omite scrierea acestei funcţii,

compilatorul va fi în imposibilitatea compilării programului editat. Astfel, atunci când se execută un

program, primele instrucţiuni executate sunt cele care fac parte din funcţia main. Dacă programul conţine

mai multe funcţii, numele acestora va fi definit de utilizator.

Toate programele scrise în limbajul C sunt memorate într-un fişier sau mai multe, acestea având

extensia .c. Un fişier care conţine un program scris în C sau care conţine numai o parte a acestuia

se numeşte fişier sursă.

Prin compilarea fişierului sursă rezultă un fişier obiect, care are extensia .obj. Fişierele sursă

care intră în compunerea unui program pot fi compilate împreună sau separat. În urma unei compilării

rezultă un fişier obiect; fişierele obiect aferente aplicaţiei pot fi reunite într-un program executabil prin

link-editare. În urma link-editării rezultă un fişier executabil, cu extensia .exe.

Page 10: Curs 1 si 2

Un nume este o succesiune de litere şi eventual cifre, primul caracter fiind literă. Pot fi utilizate

litere mici şi litere mari precum şi caracterul subliniere (_).

Există un număr de cuvinte împrumutate din limba engleză care au o semnificaţie predefinită.

Utilizatorul nu poate da o altă utilizare acestora şi de aceea se mai numesc cuvinte cheie. Acestea se scriu

mereu cu litere mici şi reprezintă nume cu destinaţii speciale (for, if, while, break, exit, etc.).

Atomi lexicali

Ca şi alte limbaje, C are un alfabet şi reguli pentru scrierea programelor corecte folosind semne

de punctuaţie. Aceste reguli formează sintaxa limbajului C. Compilatorul C are rolul de a testa dacă un

program editat este corect. Dacă sunt erori, atunci va afişa o listă de mesaje de eroare şi se va opri. Iniţial,

compilatorul împarte mulţimea caracterelor (programul sursă) în atomi lexicali, care reprezintă

vocabularul de bază al limbajului. În ANSI C (ANSI = American National Standards Institute) sunt şase

tipuri de atomi lexicali (care se mai numesc şi elemente lexicale sau unităţi lexicale):

cuvinte rezervate;

identificatori;

constante;

şiruri constante;

operatori;

semne de punctuaţie.

Caractere şi atomi lexicali

Un program C este o secvenţă de caractere; caracterele permise în programele C sunt:

literele mici şi literele mari ale alfabetului: a b ... z, A, B, …, Z

cifre : 0 1 ... 9

alte caractere: * / = ( ) { } [ ] < > ' " ! @ # $ % & _ | ^ ~ \ . , ; : ?

spaţii: blank, newline şi tab

Identificatori

Un identificator este un atom lexical compus dintr-o secvenţă de litere, cifre sau caracterul

underscore ("_") cu restricţia că primul caracter este o literă sau underscore. În multe implementări C, se

face distincţie între litere mici şi mari. În general, se obişnuieşte ca identificatorii să fie scrişi cu nume

sugestive care să uşureze citirea şi documentarea programului.

Exemple:

1. k, _id, contor, un_identificator sunt identificatori;

2. gresit#unu, 100_gresit_doi, -plus nu sunt identificatori.

Page 11: Curs 1 si 2

Comentarii în C

Comentariile sunt şiruri de caractere cuprinse între /* şi */. Comentariile nu reprezintă atomi

lexicali. Practic, compilatorul va traduce comentariile într-un singur caracter spatiu, de aceea

comentariile nu fac parte din codul executabil.

Avantajele folosirii comentariilor:

uşurarea documentării ulterioare; scopul documentării este explicarea clară a folosirii

programelor;

un comentariu poate conţine informaţii care argumentează demonstraţia corectitudinii unui

algoritm;

comentariile sunt foarte bine venite a fi scrise odată cu textul programului.

Tipuri de date de bază

Mulţimea tipurilor de date predefinite constituie o caracteristică importantă şi un argument în

alegerea unui limbaj sau altul pentru rezolvarea unei probleme. În limbajul C există o serie de tipuri

simple de date predefinite (tabelul 2.1), a căror lungime poate să difere de la un calculator la altul şi de

la o implementare la alta.

Un tip de date descrie un set de date (care se mai numesc şi obiecte) care au aceeaşi reprezentare.

De asemenea, există un număr de operaţii asociat unui tip de date (spre exemplu, cu tipul întreg se

asociază cele patru operaţii aritmetice şi eventual şi altele).

Pentru fiecare tip predefinit din limbajul C există cuvinte rezervate, care reprezintă modificatori

de tip: unsigned, signed, long şi short pentru tipul int; signed şi unsigned pentru tipul char; long pentru

tipul double. Cuvintele rezervate dintre parantezele pătrate sunt opţionale şi diferitele combinaţii

definesc acelaşi tip de dată.

Tipurile de date specifice limbajului C sunt prezentate în tabelul 2.1.

De asemenea, o componentă importantă a unui program C o reprezintă biblioteca funcţiilor C (a

funcţiilor executate de un program) care pot realiza:

operaţii de intrare/ieşire,

operaţii matematice,

operaţii cu şiruri, etc.

Printre cele mai des folosite funcţii din C se numără funcţia printf, care este principala funcţie de

ieşire aferentă limbajului C, şi este redată sub următoarea formă:

printf(“sir de caractere”);

Pentru exemplul prezentat, pe ecran se va afişa şirul de caractere inclus între ghilimele.

Page 12: Curs 1 si 2

Tabelul 2.1 Principalele tipuri de date în limbajul C

Tip Nr.octeţi Interval de valori Semnificaţie

int 4 [-2147483648 – 2147483647]

(-231 – 231-1)

conţine numere întregi cu semn,

reprezentate binar pe 4 octeţi

short int 2 [-32768 – 32767]

(-215 – 215-1)

conţine numere întregi cu semn,

reprezentate binar pe 2octeţi

long int 8 (-231 – 231-1) / (-215 – 215-1) conţine numere întregi cu semn,

reprezentate binar pe 8 octeţi

unsigned int 4 [0 –2147483647] (0-231 –1)

se foloseşte pentru a crea întregi fără

semn. Diferenţa dintre întreg cu semn şi

fără semn constă în modul în care

compilatorul interpretează bitul de semn.

Dacă bitul de semn este 0, numărul este

pozitiv, şi dacă bitul de semn este 1,

numărul este negativ. De cele mai multe

ori, numerele negative se reprezintă în

complement faţă de doi. Dacă un întreg

este declarat cu unsigned, când bitul de

semn este 1, numărul devine 231-1

[signed]

char 1 [-128 – 127] (-27 – 27-1)

sunt caractere, adică simboluri

tipografice elementare: litere, cifre,

semne de punctuaţie, simboluri

matematice, etc;

unsigned

char 1 [0 – 255] (28-1) caracter fără semn

Float 4 [3,4*10-38 – 3,4*1038]

zecimal în virgulă flotantă simplă

precizie – conţin numere reprezentate în

virgulă flotantă (pot avea partea

fracţionară)

double 8 [1,7*10-308 – 1,7*10308]

zecimal în virgulă flotantă dublă precizie

– conţin numere reprezentate în virgulă

flotantă (pot avea partea fracţionară)

long double 12 [3,4*10-4932 – 1,1*104932] Reprezentare flotantă în dublă precizie

Fiecare program scris în limbajul C poate fi prelucrat înaintea procesului de compilare prin

intermediul preprocesării, care este un proces automat realizat de compilator înaintea compilării

programului sursă. Rolul preprocesării este de a include diferite fişiere cu texte sursă, definiţii şi apeluri

de macrouri sau de a realiza compilarea condiţionată.

Astfel, o altă componentă importantă care caracterizează majoritatea programelor C sunt fişierele

header. Toate informaţiile aferente funcţiilor din biblioteca standard se află în fişiere care sunt transmise

împreună cu compilatorul, aceste fişiere recunoscându-se prin intermediul extensiei: .h. Informaţiile din

aceste fişiere sunt folosite de compilator pentru lucrul cu funcţiile din bibliotecă. Toate fişierele necesare

se adaugă prin intermediul directivei preprocesor #include, care are rolul de a determina compilatorul

să citească un alt fişier şi să-l includă în program.

Page 13: Curs 1 si 2

Preprocesarea presupune prelucrarea informaţiilor specifice care sunt precedate de caracterul diez

(#). Astfel, un fişier cu text sursă poate fi inclus în cadrul programului curent prin intermediul construcţiei

#include, folosind unul din formatele:

Un fişier cu text sursă poate fi inclus cu ajutorul construcţiei #include. Această construcţie are

formatul:

#include “specificator_fişier” sau #include<specificator_fişier>

unde specificator_fişier (care depinde de sistemul de operare) defineşte un fişier cu text sursă păstrat pe

disc. În faza de preprocesare, textul fişierului respectiv se substituie construcţiei #include. Astfel, textul

fişierului respectiv ia parte la compilare împreună cu textul în care a fost inclus.

În cazul sistemului de operare DOS, specificatorul de fişier trebuie să fie un nume de fişier

împreună cu extensia lui (.C pentru compilatorul C, .H pentru fişiere de tip header – fişiere cu

prototipuri, etc.). De asemenea, în afară de numele şi extensia fişierului, specificatorul de fişier poate

conţine şi o “cale” dacă este necesar, pentru localizarea fişierului.

Diferenţa dintre cele două formate constă în modul de căutare al fişierului de inclus:

formatul cu paranteze unghiulare <…> se foloseşte la includerea fişierelor standard, cum sunt cele

care conţin prototipuri pentru funcţii din bibliotecă;

în cazul în care se foloseşte formatul cu ghilimele, fişierul este căutat în directorul curent sau conform

“căii” dacă aceasta este prezentă.

Spre exemplu, pentru a include fişierul sursă stdio.h care conţine prototipul funcţiilor de

intrare/ieşire se va folosi construcţia: #include <stdio.h>. Dacă se foloseşte construcţia #include

„un_fisier.c”, atunci compilatorul va include fişierul un_fisier din cadrul directorului curent. De

asemenea, pentru a include în program fişierul un_fisier care se găseşte în folderul Aplicaţii, în directorul

C, se va folosi construcţia: #include „c:\\Aplicatii\\un_fisier.c”. În cadrul ultimului exemplu prezentat,

se obervă că pentru a descrie calea, caracterul backslash se dublează în concordanţă cu convenţia de

reprezentare a caracterului backslash într-un şir de caractare.

Un fişier standard care se include frecvent este stdio.h; acesta conţine prototipurile pentru o serie

de funcţii ce realizează operaţii de intrare/ieşire.

Construcţiile #include se scriu de obicei la începutul fişierelor sursă, pentru ca textele inserate să

fie valabile în tot fişierul sursă care se compilează.

Pentru a putea folosi într-un program funcţia printf(), este necesară includerea directivei

#include<stdio.h> deoarece, aşa cum spuneam anterior, această directivă conţine informaţii cu privire la

această funcţie de ieşire. Directivele incluse în program nu se finalizează cu caracterul ;, deoarece nu

reprezintă un cuvânt cheie al programului, ci o “instrucţiune” către compilatorul C.

Page 14: Curs 1 si 2

Cel mai simplu program scris în limbajul C este următorul:

Exemplu:

#include <stdio.h>

main()

{

printf("Primul program in limbajul C !!!");

getch();

}

care va afişa pe suprafaţa ecranului mesajul: Primul program in limbajul C !!!.

Observaţie: getch() este o funcţie de ieşire fără ecou (caracterul tipărit nu este afişat pe ecran) asupra

căreia se va reveni în cursul aferent funcţiilor de intrare/ieşire; este folosită de mediul Dev-C++ pentru

a păstra pe ecran (până la apăsarea unei taste) rezultatul programului compilat.

Practic, în acest program banal sunt incluse toate aspectele comune şi obligatorii aferente unui

program scris în C. Prima linie permite includerea în program a fişierului stdio.h. pentru a fi citit de

compilator. Începutul funcţiei principale este desemnat de a doua linie, main(). Corpul tuturor funcţiilor

în C (deci, şi a funcţiei main) este inclus între două paranteze acoladă; tot ce este scris în interiorul acestor

paranteze acolade constituie corpul funcţiei. În cazul programului prezentat anterior, corpul funcţiei

main() este format dintr-o singură instrucţiune, care apelează funcţia printf() din biblioteca standard

pentru a permite afişarea pe ecran a şirului dorit. Atunci când corpul unei funcţii este format din mai

multe instrucţiuni, parcurgerea acestora se va face secvenţial.

Constante şi variabile în limbajul C

O constantă în limbajul C are un tip şi o valoarea. Atât tipul cât şi valoarea sunt determinate de

caracterele care intră în compunerea constantei. Valoarea unei constante nu poate fi schimbată în timpul

execuţiei programului în care a fost utilizată. Există mai multe tipuri de constante:

întregi – sunt constante care pot fi scrise în sistemul de numeraţie în baza 8, 10 sau 16. O constantă

zecimală întreagă este un şir de cifre zecimale care are prima cifră diferită de zero. Constantele octale

reprezintă succesiuni de cifre octale (0-7) precedate de un zero nesemnificativ. O constantă hexazecimală

este o succesiune de cifre hexazecimale precedate de 0x sau 0X.

Ex: 15150 care se reprezintă binar prin: 00000000 00000000 00111011 00101110

flotante – reprezintă un număr raţional şi se compune din:

o parte întreagă care poate fi şi vidă – este o constantă zecimală;

o parte fracţionară care poate fi şi vidă – se compune din caracterul punct după care urmează o

succesiune de cifre zecimale;

Page 15: Curs 1 si 2

un exponent care poate fi şi vid – începe cu litera e sau E, după care urmează un semn opţional

(plus sau minus) şi un şir de cifre zecimale. Exponentul defineşte un factor care exprimă o putere

a lui 10.

Ex:

100. – 100

100.48 – 100,48

.48 – 0,48

12e5 – 12*105

.5E-5 – 0,5*10-5

15.255e2 – 15,255*102

1500.123e-3 – 1500,123*10-3

caracter – o constantă caracter are ca valoare codul ASCII al caracterului pe care-l reprezintă şi are

tipul int. O constantă caracter corespunzătoare unui caracter imprimabil se reprezintă prin caracterul

respectiv inclus între caractere apostrof:

‘A’ – valoarea 65

‘a’ – valoarea 97

‘*’ – valoarea 42