1. Identificatori
In limbajul C, identificatorii sunt combinatii de litere si cifre, primul caracter fiind o litera. In identificator este permis semnul subliniere, _. Reguli:
Cuvintele rezervate fac parte din vocabularul limbajului si au o mnemonica provenita din lb. engleza. Ele sunt întotdeuna scrise cu minuscule:
auto double int struct break else long switch case enum register typedef char extern return union const float short unsigned continue for signed void default goto sizeof volatile do if static while
In afara acestora, diferitele compilatoare mai pot defini si alte cuvinte ale vocabularului.
Fie programul exemplificativ,
main()
{
getch();
}
care nu face absolut nimic, dar este corect sintactic.
Cuvântul rezervat main este foarte important, defineste programul principal si trebuie sa apara o singura data în programul nostru. Dupa main apar doua paranteze, deoarece în limbajul C chiar si programul principal este tratat ca o functie care trebuie sa aiba o lista de argumente.
Acoladele definesc începutul si respectiv sfârsitul structurii folosite, în cazul nostru programul principal.
Alt exemplu:
int main() { printf("afisez");
getch();
return 0; }
La sfarsitul structurii apar cuvintele return 0, deoarece functia main trebuie sa furnizeze o valoare (întreag, în cazul de fata 0).
Programul are o comanda în plus, printf() pentru afisarea unui mesaj oarecare.
getch() este o comanda pentru introducerea unui caracter de la tastatura. Este folosita aici cu scopul de a întârzia terminarea programului pâna la apasarea unei taste oarecare.
Alt exemplu:
int main()
{
printf("Linie de text pe monitor.\n");
printf("Alta linie de text ");
printf("linie de text.\n\n");
printf("A treia linie.\n");
getch();
return 0;
}
In acest program, sunt afisate mai multe mesaje, toate folosind printf(). Executia are loc pe rând, pâna la ultima (secvential). Se remarca folosirea, pe lânga ghilimele a secventei de caractere \n, care înseamna rând nou (retur de car pentru masina de scris).
Semnul \ (engl. slant sau 'backslash') are o semnificatie speciala, de caracter de control. Astfel, între ghilimelele din printf() putem avea:
\n rând nou (retur de car)
\t tabulator
\a semnal sonor (engl. alert)
\\ semnul \
Prima linie din program scrie un text, dupa care avem un retur de car (rând nou pe ecran).
A doua linie din program scrie un text, dar fara retur de car. Ca urmare, a treia linie de program scrie un text în continuarea celui de-al doilea. A patra linie de program scrie un text oarecare urmat de un retur de car.
In limbajul C,pentru a evita programele incorecte, imposibil de corectat, toate variabilele trebuie declarate. Volumul ocupat în memorie este diferit în functie de nevoile programului.
Alocarea spatiului în memorie precede alocarea valorii. Prin urmare, declararea tipului (întreg, real, caracter, sir de caractere, fisier) precede întotdeauna programul propriu zis.
Int înseamna numar întreg. Pentru numere întregi, volumul ocupat este mai mic iar operatiile aritmetice sunt mult mai simple (deci mai rapide) decât pentru numerele reale.
In lb. engleza, signed înseamna cu semn iar unsigned înseamna fara semn, inclusiv zero.
Float însamna un numar real în simpla precizie iar double si long double - numere reale în dubla precizie, singurele reprezentari acceptabile pentru calcule stiintifice.
Char este o abreviere pentru caracter. In memoria calculatorului, caracterul este desemnat printr-un numar de ordine, dat de o conventie (de obicei standardul ASCII). De exemplu semnul (carcterul) î are codul 140, â are codul 131 iar ± are codul 241. Evident, numarul de ordine este un întreg. Pentru initializarea unei variabile de tip caracter putem folosi semnele '.' pentru caracter si " . " pentru sir de caractere. Operatiile cu siruri de caractere sunt diferite de cele pentru numere.
-------------------------------------------------------------------------------------------
Tip Name Baiti Domeniu de variatie-------------- Sisteme pe 16 biti ------------- char 1 -128 la 127 signed char 1 -128 la 127 unsigned char 1 0 la 255 short 2 -32768 la 32767 unsigned short 2 0 la 65535 int 2 -32768 la 32767 unsigned int 2 0 la 65,535 long 4 -2147483648 la 2147483647 unsigned long 4 0 la 4294967295 float 4 3.4E+/-38 (7 cifre) double 8 1.7E+/-308 (15 cifre) long double 10 1.2E+/-4932 (19 cifre) -------------- Sisteme pe 32 biti --------------- char 1 -128 la 127 signed char 1 -128 la 127 unsigned char 1 0 la 255 short 2 -32768 la 32767 unsigned short 2 0 la 65,535 int 4 -2147483648 la 2147483647 unsigned int 4 0 la 4294967295 long 4 -2147483648 la 2147483647 unsigned long 4 0 la 4294967295 float 4 3.4E+/-38 (7 cifre) double 8 1.7E+/-308 (15 cifre) long double 10 1.2E+/-4932 (19 cifre)
-------------------------------------------------------------------------------------------
Pentru transcrierea numerelor stocate în memorie din format binar în format zecimal, pe ecran, instructiunea printf() foloseste urmatoarele conventii: d întreg i întreg(coventie ANSI noua) o octal x hexadecimal u întreg fara semn c caracter s sir de caractere f numar real în simpla precizie (virgula fixa) lf numar real în dubla precizie (virgula fixa) g numar real, folosind formatul exponential (virgula mobila) e idem
Exemplu:
int main()
{
int index;
index = 13;
printf("Valoarea lui index este %d\n", index);
index = 27;
printf("Valoarea lui index este %d\n", index);
index = 10;
printf("Valoarea lui index este %d\n", index);
getch();
return 0;
}
Observati în linia 5 declaratia int index; care este folosita pentru definirea variabilei de tip întreg numita index. Cuvântul rezervat int desemneaza tipul întreg de variabila si trebuie folosit înaintea altor instructiuni care folosesc variabila index. In loc de numele "index" pentru variabila de tip întreg, am fi putut folosi orice nume permis de conventiile expuse mai sus, cu conditia utilizarii consecvente. Ultimul caracter din linie, ; semnifica sfarsitul logic al instructiunii. Observati ca desi am definit variabila, ca fiind de tip întreg, totusi nu este clar ce valoare îi atribuim. Acest lucru este facut mai jos, folosind instructiunea de atribuire, de tipul
index=13;
care se citeste index ia valoarea 13.
Din nou, semnul ; semnifica sfarsitul logic al instructiunii de atribuire.
Deci:
Int index; este o declaratie de tip iar index=13; este o instructiune de atribuire, care foloseste operatorul de atribuire =.
Mai jos, valorile variabilei index sunt modificate si stocate în memorie dupa necesitati.
Functia printf() are mnemonica print to file (scrie într-un fisier), deoarece consolei (tastatura si monitor) i se atribuie prin conventie semnificatia de fisier implicit. Daca am compila si apoi executa programul în modul "linie de comanda":
D:\program.exe >date.dat
iar apoi am edita fisierul date.dat, am observa ca are un continut identic cu afisajul de pe ecran, deoarece fisierul de iesire în acest caz nu mai este implicit (ecranul) ci fisierul definit de calea "D:\date.dat" .
Reprezentarea numerelor în memoria calculatorului se face în baza de numeratie binara. Pentru a fixa ideile, noi vom folosi doar tipurile:
întreg (int) reprezentat pe patru octeti pe calculatoarele PC pe 32 biti si
real în dubla precizie (double) reprezentat pe minimum opt octeti.
Pentru scrierea lor pe ecran (fisier) în baza zecimala cu o anumita aliniere la drepta (pentru întregi) sau cu un numar prescris de zecimale (pentru reale) avem nevoie de o asa numita specificatie de format.
%d desemneaza afisarea unui întreg,
%lf desemneaza afisarea unui numar real
%6d însamna ca numarul întreg va fi afisat ca fiind format din sase caractere
%4.2lf însamna ca numarul real este afisat pe 8 caractere ( primul este semnul, urmeaza trei pozitii pentru partea întreaga, punctul zecimal urmat de doua zcimale).
Avem astfel conventia ca între argumentele functiei printf() primul item va fi un sir de caractere marcat cu " ", si care va contine specificatia dorita, urmata de lista variabilelor ce vor fi afisate. Consecventa este obligatorie !
Comentariile (de foarte mare ajutor în citirea unui program) sunt incluse între semnele /* si */,
/*Comentariu*/
int index=13;/* Declaratia variabilei este însotita de atribuire. Metoda este permisa. */
O alta utilizare comuna a comentariilor, este anularea unei sectiuni optionale dintr-un program.
Din punctul de vedere al limbajului de programare si al compilatorului, continutul comentariilor este ignorat. Ele nu vor apare în forma executabila a programului si pot fi mai lungi decât programul însasi.
Pentru lizibilitate, programul trebuiescris cu instructiunile aliniate în cadrul fiecarei structuri folosite.
Totusi, aceasta este o operatie finala si cosmetica, compilatorul acceptând si programe scrise dezordonat, dar corecte din punct de vedere logic. Acesta este scopul principal, legat de comoditate al semnului ; folosit pentru sfârsitul logic al unei instructiuni sau declaratii. Istoric vorbind, la începutul dezvoltarii limbajelor de programare (FORTRAN si BASIC), când disponibilitatile de memorie erau foarte reduse, se considera ca o instructiune ocupa un rând. Desigur conventia a fost depasita atât logic cât si tehnic.
int main() /* Programul principal */
{
printf("Buna aliniere ");
printf ("poate ajuta la ");
printf ("citirea unui program.\n");
printf("O aliniere neglijenta ");
printf ("poate face un program ");
printf ("neinteligibil.\n");
getch();
return 0;
}
5.1. Structura repetitiva WHILE (){}
Structura data de cuvântul rezervat while(), adica cât timp() are mnemonica:
while(conditie)
{
...........
/* instructiuni de executat */}
...
Structura asigura executarea actiunilor descrise între acolade cât timp este îndeplinita conditia dintre parantezele rotunde, dupa care începe executia instructiunilor care urmeaza dupa sfârsitul structurii (marcata cu }).
Exemplu:
#include <stdio.h>
int main()
{
int contor;
contor = 0;
while (contor < 6)
{
printf("Valoarea contorului este %d\n", contor);
contor = contor + 1;
}
getch();
return 0;
}
In cazul acestui exemplu, valoarea variabilei contor este crescuta cu o unitate la fiecare traversare a corpului structurii pâna când valoarea sa ajunge la 6.
5.2. Structura repetitiva DO-WHILE()
Din considerente de comoditate, structura while are o varianta a carei mnemonica este:
Do
/* Fa */{
....
}
while (conditie)
/* cât timp este îndeplinita conditia */Exemplu:
#include <stdio.h>
int main()
{
int i;
i = 0;
do
{
printf("Valoarea lui i este acum %d\n", i);
i = i + 1;
} while (i < 5);
getch();
return 0;
}
Observatii:
{
}
while(conditie)
deoarece nu este clar ce rost au acoladele dinaintea lui while.
5.3. Structura repetitiva FOR
Structura for are urmatoarea sintaxa:
For(start;test;actiune)
/* pentru cazul care începe cu start, */ /* cât timp test este adevarat, executa */ /*actiune precum si instructiunile din acolade */{
.......
/* instructiuni aferente structurii */}
Exemplu:
#include <stdio.h>
int main()
{
int index;
for(index = 0 ; index < 6 ; index = index + 1)
printf("Valoarea lui index este %d\n", index);
getch();
return 0;
}
In cazul limbajului C, mai avem un separator pentru instructiuni succesive, virgula, , , însa aceasta trebuie folosita cu grija:
Int I;
for (I=1,I<=3; printf("%d",I),I=I+1);/* atentie la semnul ; */
sau
Int I;
for (I=1,I<=3;I=I+1) printf("%d",I); /* atentie la semnul ;*/
sau
Int I;
for (I=1,I<=3;I=I+1)
{
printf("%d",I);
}/* fara semnul ;*/
Structurile while() si do {} while() sunt convenabile daca nu stim de câte ori trebuie evaluat corpul structurii iar for(;;) este recomandat daca numarul pasilor de calcul este previzibil. Totusi, nu se prescrie ca variabila de contorizare pentru for(;;) sa fie de tip întreg. Ca urmare, spre deosebire de celelalte limbaje de programare, în limbajul C aceste structuri au parametri si rezultate identice, deosebirea constând numai în stil si inspiratie.
Totusi, la utilizarea structurii do {}while(), trebuie sa retinem ca lista de instructiuni din corpul structurii va fi executata cel putin o data.
In cazul unei structuri oarecare, mai ales repetitive, sunt utile niste instructiuni de salt neconditionat.
Cea mai simpla este
goto eticheta;
care are undeva în program o destinatie marcata prin eticheta: urmata de instructiunile de executat. Eticheta poate fi orice identificator valabil sau un numar, obligatoriu urmat de :
Aceasta instructiune era utilizata pe larg la începutul dezvoltarii imbajelor de programare, deoarece era simplu de implementat. Instructiunea exista înca în toate limbajele de programare, însa utilizarea ei este descurajata în prezent, deoarece în cazul unei greseli într-un program complicat, nu avem idee de unde a fost transferata executia programului în locul în care apare gresala. Desigur, daca programul este bine scris, prezenta sau absenta instructiunii este o chestiune de stil.
In locul instructiunii goto se pot utiliza niste variante care sunt legate de sfârsitul unei structuri, marcat prin }.
Astfel, continue sare instructiunile de la linia curenta pâna la sfarsitul structurii (pâna la }), fara însa sa iasa din structura.
Instructiunea break transfera controlul executiei la prima instructiune care urmeaza dupa } deci se iese din structura.
7.1. Structura alternativa de baza IF() { } ELSE { .}
Aceasta structura are sintaxa:
If (conditie)
/* Daca (conditie) este logic adevarata */{
/* început */.......
/* Ce se întâmpla daca conditia din enunt este adevarata */}
/*sfârsit*/else
/* altfel, în cazul ca conditie este logic falsa; aceasta parte poate lipsi. */
{
/* început*/...........
/* Ce instructiuni trebuie executate pentru cazul conditiei false. */}
/*sfârsit*/
Trebuie remarcat ca fiecare corp al acestei instructiuni, marcat prin acolade { .} poate contine de asemenea alte structuri.
Pentru orice structura se aplica regula includerii în alta structura (de exemplu programul principal). Este interzisa orice structura "imbricata":
7.2. Instructiunea alternativa generalizata SWITCH
Desigur, pentru o programare într-un limbaj de nivel înalt, este insuficienta situatia unor decizii de tip DA/NU data de if(){}else{}. De aceea s-a introdus instructiunea alternativa generalizata, care permite alegerea unui caz dintre mai multe optiuni. Desigur, aceasta instructiune este compusa din mai multe instructiuni elementare if-else însa avem avantajul unei scrieri mai comode. Avem deci în loc de if(conditie), cuvântul cheie switch(conditie) (comutator(în functie de conditie)) având mai multe corpuri, corespunzatoare fiecarei optiuni. Evident, iesirea se face cu break. Daca nu este îndeplinit nici unul dintre cazurile anticipate, se specifica o conditie implicita (engl. default).
#include <stdio.h>
int main()
{
int tr;
for (tr = 3 ; tr < 13 ; tr = tr + 1)
{
switch (tr)
{
case 3 : printf("Valoarea este 3\n");
break;
case 4 : printf("Valoarea este 4\n");
break;
case 5 :
case 6 :
case 7 :
case 8 : printf("valoare intre 5 si 8\n");
break;
case 11 : printf("Valoarea este 11\n");
break;
default : printf("Valoare nedefinita\n");
break;
} /* switch */
} /* Bucla for */
getch();
return 0;
}
Pentru a preveni greselile, aceste declaratii impun ca o valoare de tip const (atribuita "varaibilei"), identificata cu un nume sa nu poata fi modificata printr-o instructiune de atribuire obisnuita. Declaratia volatile permite modificarea valorilor initiale ale variabilelor prin instructiunea de atribuire; aceasta declaratie este implicita.
Exemple:
const int index1 = 2; const index2 = 6; const float big_value = 126.4; volatile const int index3 = 12; volatile int index4;
9.
Comparatia logica
poate fi facuta cu ajutorul operatorilor logici:
== strict egal,
!= diferit
< mai mic,
> mai mare,
<= mai mic sau egal,
>= mai mare sau egal,
&& si logic,
|| sau logic
10.
Operatori aritmetici
+ adunare
- scadere
* înmultire
/ împartire
In aplicatiile practice, apar foarte des instructiuni în care vechea valoare a unei variabile se modifica printr-o simpla operatie aritmetica, de genul:
INDEX=INDEX+1;
De aceea, pentru comoditate, în limbajul C s-au introdus niste operatori aritmetici suplimentari:
++, de exemplu, index++; înseamna index=index+1;
--, de exemplu, index--; înseamna index=index-1;
/=, de exemplu, index/=2; înseamna index=index/2;
*=, de exemplu, index*=3; înseamna index=index*3;
-=, de exemplu, index-=3; înseamna index=index-1;
+=, de exemplu, index+=13; înseamna index=index+13;
Nu uitati: semnul = înseamna atribuire si atât.
Functiile matematice simple sunt definite prin:
sin(x) sinusul lui x. x trebuie exprimat în radiani
cos(x) cosinus de x
tan(x) tangenta de x
atan(x) arctangenta lui x
exp(x) ex
pow(x,y) xy
etc.
Pentru functia valoare absoluta avem o diferentiere între numerele întregi : abs(x) pentru numere întregi si fabs(x) pentru numerele reale. Pentru partea întreaga a numerelor reale avem functiile floor(x) care da partea întreaga prin trunchiere si ceil(x) care da partea întrega prin rotunjire
In afara programului principal, mai avem nevoie de functii definite de noi conform scopului programului. O asemenea structura este necesara de oricâte ori o secventa de calcul este repetata, minimum de doua ori.
Int functie(double x)
/* functia este destinata calculului unui întreg si are o singura variabila, de tip real, notata cu x. */{
..
return (I*3);
/* specificatia return este tratata de asemenea ca o functie, de unde parantezele. Valoarea I*3 este chiar rezultatul asteptat pentru functie(x) si care va fi transmisa secventei de program care foloseste functie(x). */}
Mnemonica este evidenta: functia de tip întreg functie, de argument real, returneaza programului principal valoarea I*3.
In plus, programul principal poate beneficia de modificarile aduse variabilelor din lista de variabile a functiei.
Este usorde observat ca pentru executia programului principal, functiile trebuie declarate si specificate înaintea acestuia, pentru a fi deja înregistrate si omologate de catre compilator.
Este evident pe de alta parte ca pâna la scrierea programului nostru, au fost deja scrise foarte multe module folositoare, de exemplu grafice, pe care le putem folosi în scopuri proprii.
O solutie evidenta ar fi copierea lor în programul nostru. Insa, s-ar putea ca volumul ocupat de aceste module sa fie mai mare decât programul nostru. Pentru fiecare gresala ar trebui recompilate toate aceste detalii care au fost deja omologate.
Limbajul C are urmatoarea facilitate: Pentru functiile care sunt deja bine scrise, declaram înaintea programului principal un prototip care contine tipul functiei si lista variabilelor cu tipurile aferente. Continutul propriu-zis se gaseste la sfârsitul programului sau, daca se poate, si mai bine, în forma executabila pe disc. Acest modul va fi tasat programului doar în etapa finala a editarii de legaturi (compilarea lui nu pune probleme). Si mai scurt, declaratiile de tip se pot include într-un fisier de tip text, numit "header" (antet în lb. engleza), care se gaseste de asemenea pe disc. Astfel, timpul necesar scrierii si corectarii programului este redus la maximum. Atât bibliotecile translatabile caât si unele biblioteci ale utilizatorului pot fi declarate în acest fel. Pentru o maxima eficienta, mediul de programare, de exemplu Dev-C++ are doua directoare proprii pentru bibliotecile translatabile:
Include pentru fisierele antet si
Lib pentru forma obiect a functiilor (gata pentru editarea de legaturi).
Bibliotecile translatabile pot fi declarate dupa exemplul:
#include <math.h>
/*sunt folosite functiile matematice sin(x), etc.*/Functiile utilizatorului altele decât cele deja mentionate pot fi stocate pe disc în acelasi loc cu sursa programului curent în forma:
#include "utilizator.h"
O alta facilitate a limbajului C este utilizarea pointerilor, adica pentru o numita variabila, în afara de continut, pe care îl putem modifica prin atribuire, putem afla adresa din memorie a variabilei. Pe aceasta nu o putem modifica. Pentru simplitate, dorim ca aceasta adresa sa aiba un nume dat de noi si nu un numar. Evident:
Avem astfel urmatoarele notatii conventionale:
Desigur, utilizarea pointerilor este usor mai complicata, deoarece în exemplele anterioare nu era clar tipul variabilei f. Avem deci urmatoarea structura în program:
Double f,*I;
/* adica avem numarul real f si un pointer catre real, notat cu I. */Pointerii trebuie initializati.
Double f=15.1, *I;
/* -- I este un pointer(adresa) catre un real pe 8 octeti --*/...
I=&f;
...
De aici încolo, este indiferent daca folosim f ca pe un numar real sau *I. Avantajul evident este ca prin utilizarea pointerilor, operatiile se fac doar în numere întregi. Avem avantajul suplimentar ca la traversarea unui vector de date, cresterea valorii pointerului cu o unitate însamna elementul urmator din vector, ceea ce altfel ar fi însamnat mult mai multe operatii pentru unitatea centrala a calculatorului.
Utilizarea pointerilor este deocamdata singura maniera eficienta de marire a vitezei de executie (cam 30%), fara modificarea algoritmului.
Totusi, utilizarea gresita a pointerilor poate duce la greseli foarte greu de detectat, de aceea nu folosim acasta facilitate în aceasta lucrare.
Prin constructia discurilor magnetice ale calculatoarelor, operatiile de scriere si citire pe disc sunt:
Scrierea/citirea se pot face în format text , abreviere t (usor de urmarit vizual, dar foarte ne-economicos) sau binar, abreviere b (economicos, dar aproape imposibil de urmarit vizual).
Pentru fisier trebuie sa specificam calea unde poate fi gasit.
Din considerente tehnice, un fisier trebuie mai întâi deschis (fopen), iar la sfârsit trebuie închis (fclose).
Pentru fisiere, avem un tip special de variabila, numit FILE.
Este obligatoriu sa utilizam pointerii.
Exemplu:
FILE *Fisier=fopen("c:\\acasa\\maria.dat", "wb");
/* Adica am deschis fisierul maria.dat (observati ca am specificat calea folosind conventia de la specificatia format cu \\ în loc de \, ) de tip scriere (w) si în reprezentare binara (b)*/
I=10;
..
fprintf(Fisier, "%d \n", I);
/* In fisier scriem asemanator ca pe ecran: specificatie de format urmata de lista de variabile, însa pentru fprintf avem în plus pointerul catre fisier (care nu mai este implicit). */fclose(fisier);
/*Inchidem fisierul maria.dat*/
Utilizarea avansata a pointerului în sensul ca verificam daca fisierul a fost deschis sau nu:
if (Fisier == NULL) { printf("Nu pot deschide fisierul \n"); exit (1); }
Din aceasta prezentare, lipsesc:
-Adresarea directa (alocarea dinamica) a memoriei
- Operatii cu siruri de caractere
- Structuri si uniuni
care nu sunt folosite în lucrarea noastra.