Lectia 1 - Elemente introductive. Expresii, operatori, tipuri de date
Scopul acestui curs este prezentarea unor noțiuni introductive despre compilare/interpretare și recapitularea unor cunoștințe despre elementele de bază
din limbajul C/C++ (despre variabile, expresii, tipuri de date, operatori, instrucțiunea de atribuire).
Accentul se va pune pe elementele de finețe, pe operatorii mai puțin cunoscuți (inclusiv operatorii pe biți), pe prioritatea și
asociativitatea operatorilor, pe tipurile de date și reprezentările lor, pe conversia între tipuri de date și altele.
1.1. Programarea calculatoarelor, limbaje de programare, istoric
Programarea calculatoarelor
Programarea este o activitate informatică de elaborare a produselor-program, a programelor (software) necesare.
Definiții
Programarea conține următoarele subactivități: specificarea, proiectarea, implementarea, documentarea și întreținerea produsului program.
Este un proces ce conduce de la formularea unei probleme (de calcul) la un program executabil pe un calculator
Sarcini înrudite sunt testarea, depanarea, întreținerea codului sursă, implementarea sistemului construit, managementul acestuia etc; acestea pot fi considerate ca părți ale procesului de programare, dar cel mai adesea sunt asociate termenului de dezvoltare software
Ingineria software (ingineria programării) combină tehnici inginerești cu practici de dezvoltare software
În ingineria software, programarea (implementarea) este privită ca una din fazele procesului de dezvoltare software
Există o permanentă dezbatere dacă scrierea de programe este o formă de artă, un meșteșug sau o disciplină inginerească
Există mai multe paradigme de programare. O paradigmă de programare reprezintă o modalitate de a clasifica
limbajele de programare în funcție de caracteristicile lor.
Paradigme de programare
Programarea structurată este o paradigmă a programării apărută după anul 1970 datorită complicării crescânde a programelor
de calculatoare. A apărut ca un model nou de programare, în scopul de a crea noi tehnici de programare apte de a produce programe
care să fie sigure în funcționare, pe o durată mai lungă. În primii ani ai informaticii, limbajul de programare BASIC nu conținea
toate elementele programării structurate, așa încât unele cicluri trebuiau realizate folosind instrucțiunea de salt necondiționat GOTO.
Limbajul de programare Pascal este un exemplu de limbaj de programare structurat, conceput special pentru învățarea programării.
Astăzi, toate limbajele de programare imperative sunt structurate. Acestea folosesc structuri de control de bază
(secvența sau structura liniară, decizia cu ambele ramuri (selecția), repetiția condiționată anterior) și structuri derivate
(decizia cu ramura „altfel” vidă, decizia multiplă, repetiția cu test final sau cu număr cunoscut de pași)
Teorema Böhm şi Jacopini
Există o serie de teoreme care se referă la programarea structurată, dar pentru noi are importanţă Teorema de structură a lui Böhm şi Jacopini,
care se poate enunţa astfel: orice schemă logică nestructurată poate fi înlocuită cu una echivalentă, dar structurată,
prin adăugarea de noi acţiuni şi condiţii. Teorema se referă la schemele logice, dar se poate aplica și algoritmilor descriși
în pseudocod sau chiar scriși sub formă de program, într-un limbaj de programare. Aşadar, orice program poate fi pus sub o formă
structurată, adică să conţină doar structurile de bază şi/sau structurile auxiliare, prin utilizarea unor variabile
boolene asociate unor funcţii suplimentare.
Exemplificare
Să exemplificăm pe cazul unui algoritm scris în limbaj pseudocod. Algoritmul următor determină cel mai mic element (notat min)
din şirul X de n elemente:
Citeste(n, X);
i = 2; min = X(1);
A:
dacă i>n atunci
treci la pasul B
altfel
dacă X(i)<min atunci
min = X(i);
i = i+1;
treci la pasul A
B:
Scrie(min)
Observaţi că algoritmul nu este scris sub o formă structurată (apare instrucţiunea de salt
necondiţionat "treci la pasul").
În schimb, următoarea formă reprezintă acelaşi algoritm şi este structurată:
Citeste(n, X);
min = X(1);
pentru i de la 2 la n execută
dacă X(i)<min atunci
min = X(i);
Scrie(min)
Aceasta este cea mai elegantă formă de scriere a algoritmului de determinare a minimului. O altă formă, care foloseşte doar
structurile de bază ar fi:
Citeste(n, X);
min = X(1);
i = 2;
cât timp i≤ n execută
dacă X(i)<min atunci
min = X(i);
i = i+1
Scrie(min)
Programarea imperativă/procedurală, în contrast cu programarea declarativă, este o paradigmă de programare care descrie calculul ca
instrucțiuni ce modifică starea unui program. În aproape același fel în care modul imperativ din limbajele naturale exprimă comenzi
pentru acțiuni, programele imperative sunt o secvență de comenzi pentru acționarea calculatorului.
Majoritatea limbajelor de programare de care ați auzit sunt limbaje de programare imperative
(C, C++, Pascal, JavaScript, Java, Python, Basic, PHP etc.)
Programarea declarativă este un stil de programare în care se descrie logica unui calcul, fără să se prezinte modul
de execuție; programarea declarativă răspunde la întrebarea ce trebuie calculat și nu la întrebarea cum
se face acest lucru.
Programarea funcțională este o paradigmă de programare care tratează calculul ca evaluare de funcții matematice și evită
starea și datele muabile. Se pune accent pe aplicarea de funcții, spre deosebire de programarea imperativă, care folosește în principal
schimbările de stare. Exemple de limbaje de programare pentru această paradigmă sunt Haskell și CaML.
Programarea logică este o paradigmă de programare bazată pe logica predicatelor de ordinul I.
Folosește conceptele de predicate și de clauză. Reprezentantul acestei paradigme este limbajul de programare PROLOG,
care a fost dezvoltat pentru prelucrarea limbajului natural, în inteligența artificială.
Programarea bazată pe reguli se înscriu în paradigma programării declarative, programul constând din aplicarea unor reguli asupra unor fapte, pentru a genera noi fapte.
Ordinea de aplicare a regulilor este necunoscută de programator, iar regulile se aplică până când nu se mai poate aplica niciuna, care să genereze noi fapte.
Exemple de limbaje de programare bazate pe reguli sunt CLIPS, EXSYS și se folosesc pentru generarea de sisteme expert, în inteligența artificială.
Programarea orientată spre obiecte (sau programarea orientată-obiect) este o paradigmă de programare, axată pe ideea încapsulării,
adică grupării datelor și codului care operează asupra lor, într-o singură structură. Un alt concept important asociat programării orientate
obiect este polimorfismul. Astăzi majoritatea limbajelor de programare acceptă această paradigmă, dar la început limbajul de programare C++ a fost un bun exemplu.
Plecând de la C++ s-a dezvoltat limbajul de programare Java, în care totul este obiectual.
uşor de programat (mai puţin timp de scris, mai scurt şi mai uşor de citit); dezbatere în sală
portabil - programele pot rula pe diferite calculatoare fără modificări sau cu puţine modificări
limbaj nesigur puternic tipizat (programatorul trebuie să ştie exact ce trebuie să facă)
acceptă atât variabile de tipuri definite explicit, cât şi deduse
acceptă verificarea statică (la compilare), dar şi dinamică a tipurilor (în timpul execuţiei programului)
oferă o gamă variată de paradigme de programare
colecţie foarte mare de biblioteci
limbaj ce acceptă mai multe paradigme de programare:
generică: se pot folosi tipuri ce vor fi specificate mai tărziu şi vor fi instanţiate în funcţie de necesităţi
structurată: control intuitiv (ordinea în care instrucţiunile sunt executate)
imperativă: cum (secvenţe de instrucţiuni ce trebuie executate de calculator)
orientată pe obiecte: obiecte + atribute + proceduri (metode)
declarativă: ce (ce calcule trebuie făcute şi nu cum să fie făcute)
funcţională: programele văzute ca evaluarea unor funcţii matematice
1.2. Compilare și interpretare. Depanare. Preprocesare
Un fişier cod sursă poate fi compilat sau interpretat. Prin compilare codul sursă este translatat de un program (denumit compilator)
în cod maşină, după care poate fi executat, iar prin interpretare un program este executat direct din cod sursă prin intermediul
unui program (numit interpretor), care traduce și execută instrucțiune cu instrucțiune codul sursă.
Diferențe între compilator și intepreter
No
Compilator
Interpretor/Interpreter
1
Compilatorul ia întregul program ca intrare.
Interpretorul (Interpreterul) ia fiecare instrucțiune ca intrare. (El traduce instrucțiune cu instrucțiune.)
2
Se generează un cod obiect intermediar.
Nu se generează un cod obiect intermediar.
3
Instrucțiunile de control condiționale se execută mai rapid.
Instrucțiunile de control condiționale se execută mai încet.
4
Necesarul de memorie: Mai mare (deoarece este generat un cod obiect (object code)
Necesarul de memorie este mai redus.
5
Programul trebuie să fie compilat de fiecare dată, după ce se modifică sursa.
De fiecare dată un program de nivel înalt este convertit într-un program de nivel scăzut.
6
Erorile sunt afișate după verificarea întregului program.
Erorile sunt afișate pentru fiecare instrucțiune interpretată (dacă sunt)
Preprocesarea: Compilatorul elimină comentariile, înlocuiește macro-urile, copiază conținutul fișierelor incluse.
Tot în această etapă se efectuează directivele de preprocesare #define. Astfel se obține codul procesat.
Compilarea și asamblarea: În această etapă codul este inițial transformat în cod de asamblare,
iar apoi acesta în cod mașină specific mașinii pe care se compilează. După acest pas se obține fișierul obiect corespunzător codului
(limbaj mașină) cu extensia .o, .obj. Codul generat în această etapă nu este încă executabil.
Editarea de legături: În acest pas toate fișierele obiect obținute în urma compilării sunt unite într-un singur
fișier executabil. De asemenea sunt legate de fișierul executabil și toate bibliotecile externe incluse în cod (asociate
fișierelor header). Tot acum are loc și transformarea adreselor simbolice în adrese reale.
Un linker este un program care face legătura între fișierele obiect obținute în urma compilării și bibliotecile externe,
obținând un fișier executabil. Bibliotecile pot fi legate în mai multe moduri: dinamic (când aplicația este executată,
sistemul de operare face legătura cu librăriile necesare), static (fișierul executabil conține și codul librăriilor pe
care le folosește) și întârziat (similar cu legarea în mod dinamic, dar bibliotecile sunt încărcate doar când aplicația
are nevoie de ele (nu de la începutul execuției))
Schema compilării în C++ Schema detaliată a întregului proces de la scrierea codului sursă până la lansarea în execuție a programului (proces).Alfabet
Depanare (debugging)
Putem avea mai multe erori în programul nostru, fie de compilare, fie de execuție. De aceea, este nevoie ca să depanăm programul,
pentru a elimina aceste erori.
La compilare putem avea mai multe erori, cum ar fi: încălcarea regulilor de sintaxă,
folosirea incorectă a tipurilor de date, alocării memoriei. De asemenea, putem avea erori în etapa de editare de legături
Prin bug înțelegem o eroare de programare, greu de sesizat.
Putem avea, însă, și erori în timpul execuției programului, când acesta nu face ceea ce te aştepţi să facă, ci altceva.
Se spune că „un calculator/program face ceea ce i-ai spus să facă, nu ceea ce ai vrea să facă”.
Prin bug înțelegem o eroare de programare, greu de sesizat, iar eliminarea bugurilor este o sarcină uneori dificilă.
Forma unui program simplu în C++
/*comentariu; el nu influențează programul */
//directive preprocesare
#include <biblioteci> (Input/output, math, strings, …)#define
//declarații ale variabilelor
tipuri utilizator;Variabile;Funcții;
....
// definirea funcțiilor utilizator ..........
//funcția principală
int main(){
..........
return 0;
} //aici se încheie programul
Exemplu
/* primul program in C++ */
// using namespace std;
#include <iostream>
int main ()
{
std::cout << "Primul test 1, 2, 3. ";
std::cout << "functioneaza.. \n";
return 0;
}
Rezultatul va fi:
Primul test 1, 2, 3. functioneaza..
Preprocesarea în C
Preprocesarea apare înaintea procesului de compilare a codului sursă (fișier text editat într-un editor și salvat cu extensia .c).
Preprocesarea codului sursă asigură: includerea conținutului fișierelor (de obicei a fișierelor header),
definirea de macro-uri (macrodefiniții) și compilarea condiționată.
Directiva #include copiază conținutul fișierului specificat în textul sursă.
Are sintaxa: #include <nume_fisier> sau #include "nume_fisier".
În prima variantă se caută nume_fisier în directorul unde se află fișierele din biblioteca standard instalată
odată cu compilatorul, iar în a doua variantă fișierul se caută în directorul curent (sau în subdirectoarele specificate ale acestuia).
Fișierele header (cu extensia .h) sunt fișiere care conțin declarații de funcții și definiții de macro-uri (macrodefiniții).
Ele sunt incluse în program folosind #include. Incluzând un fișier header într-un program, se va face legătura între program
și fișierul header la editarea de legături, astfel putând folosi funcțiile din fișierul header fără a copia codul acestora.
Utilitatea fișierelor header se observă mai ales când trebuie folosite aceleași funcții în mai multe fișiere de tip .c/.cpp.
Există două tipuri de fișiere header: de sistem și scrise de utilizator.
Cele scrise de utilizator se includ cu sintaxa #include "fisier.h",
iar cele de sistem cu sintaxa #include <fisier.h>.
Exemple
Directiva #define este folosită pentru definirea (înlocuirea) constantelor simbolice și a macro-urilor.
Definirea unei constante simbolice este un caz special al definirii unui macro (fragment de cod care primește un nume).
Sintaxa este #define nume sau #define nume text. Practic, în timpul preprocesării nume
este înlocuit cu text (acest text poate să fie mai lung decât o linie,
continuarea se poate face prin caracterul \ pus la sfârșitul liniei
Exemple
#define infinit 1000
#define pi 3.1415926
#define then
#define si &&
#define daca if
#define altfel else
#define max(x,y) x > y ? x : y
#define min(x,y) x < y ? x : y
#include <stdio.h>
int main()
{
float a,b,c;
printf("Dati lungimile laturilor:\n");
scanf("%f %f %f",&a,&b,&c);
daca ((a>=0) si (b>=0) si (c>=0) si (a<b+c) si (b<a+c) si (c<a+b))
printf("%4.2f, %4.2f si %4.2f formeaza triunghi.", a,b,c);
altfel
printf("Nu formeaza triunghi.");
return 0;
}
Macrouri predefinite:
Directivele de compilare condiționată #if, #ifdef, #ifndef facilitează dezvoltarea și în special testarea codului.
Directiva #ifDirectiva #ifdefDirectiva #ifndefIncluziunea multiplă a modulelor
Directivele #ifdef și #ifndef sunt folosite de obicei pentru a evita incluziunea multiplă a modulelor
în programarea modulară. Să considerăm că avem două fișiere header a.h și b.h. La începutul fiecărui fișier header se practică de obicei o astfel de secvență:
Test de autoverificare 1 - Paradigme de programare, compilare, interpretare
1.3. Alfabetul și vocabularul limbajului de programare
1.3.1.Setul de caractere
Orice limbaj de programare are la bază un anumit alfabet. În majoritatea cazurilor setul de caractere este format din:
literele alfabetului englez (de la A la Z, de obicei și majusculele, și literele mici, în total 52 de caractere);
cifrele arabe (de la 0 la 9, în total 10 caractere);
unele caractere speciale (. , ; = < > # $ % + - * / " ' ( ) etc.) , a căror semnificație poate sa difere de la un limbaj la altul.
secvențe Escape / caractere speciale în C++:
\b Backspace, \t Tab orizontal, \v Tab vertical, \n Linie nouă, \f Pagina nouă – formfeed
\r Început de rând, \” Ghilimele, \’ Apostrof, \\ Backslash, \? Semnul întrebării, \a Alarmă
Există mai multe standarde de codificare a seturilor de caractere: EBCDIC, ASCII, UTF-8.
Detalii
EBCDIC (Extended Binary Coded Decimal Interchenge Code), un cod pe 8 biți, introdus de IBM;
ASCII (American Standard Code for Information Interchange),
introdus de ANSI (American National Standard Institute), este un cod pe 7 biți și permite codificarea a 128 de caractere
(95 de caractere afișabile și 33 de caractere neafișabile, numite caractere de control).
Ulterior, setul ASCII a fost extins la o codificare pe 8 biți, fiind disponibile astfel 256 de caractere.
Primele 128 sunt din setul ASCII standard, iar următoarele 128 sunt coduri de caractere afișabile pentru caracterele
unor alfabete naționale europene (francez, spaniol, român etc.), o parte din literele alfabetului grecesc,
unele simboluri matematice, caractere speciale pentru desenat tabele etc.
Ordonarea caracterelor alfabetului se face pe baza codurilor numerice corespunzătoare caracterelor respective.
Tabela codurilor ASCII este...aici Unele coduri speciale sunt de interes pentru programatori:
UTF-8 (Unicode Transformation Format) este o codificare a caracterelor cu lățime variabilă,
capabilă să codifice toate cele 1.112.064 caractere existente în Unicode, folosind unul până la patru unități de cod
de 1 octet (8 biți). Codificarea este definită de Unicode Standard și a fost inițial proiectată de
Ken Thompson și Rob Pike. Numele este derivat din formatul de transformare Unicode (sau Universal Coded Character Set) - 8 biți.
Formatul a fost proiectat pentru o compatibilitate cu formatul existent ASCII.
Codurile cu valori numerice mai mici, care tind să apară mai frecvent, sunt codificate folosind mai puțini octeți.
Primele 128 de caractere ale Unicode, care corespund unu la unu cu cele din ASCII, sunt codificate
folosind un singur octet cu aceeași valoare binară ca ASCII, astfel încât textul ASCII valabil
este de asemenea un cod UTF-8 valid.
Deoarece octeții ASCII nu apar atunci când se codifică punctele de cod non-ASCII în UTF-8,
UTF-8 este sigur de utilizat în majoritatea limbajelor de programare și documente care interpretează anumite
caractere ASCII într-un mod special, cum ar fi "/" (slash) în nume de fișiere, "\" (backslash)
în secvențele de evadare și "%" în funcția printf.
1.3.2. Cuvinte cheie și cuvinte rezervate
Pe baza caracterelor ce alcătuiesc alfabetul limbajului se alcătuiesc cuvintele care formează vocabularul sau lexicul
limbajului și cu ajutorul cărora se construiesc expresiile și instrucțiunile limbajului. Există doua categorii de cuvinte și anume:
cuvinte cheie - acestea au un înțeles explicit într-un context precizat (de ex., în unele limbaje de programare cuvintele
ce desemnează instrucțiuni pot fi folosite și ca nume de variabile, neexistând restricții;
asemenea situații nu sunt însă indicate deoarece pot ascunde erori în logica programului și îl fac mai greu de înțeles);
cuvinte rezervate - acestea nu pot fi folosite decât în scopul pentru care au fost definite (de ex., în limbajul C++).
Cuvinte rezervate în Cauto, break, case, char, const, continue, default, do, double, else, enum, extern, float, for, goto, if, inline (C99),
int, long, register, restrict (C99), return, short, signed, sizeof, static, struct, switch, typedef, union, unsigned,
void, volatile, while, _Alignas (C11), _Alignof (C11), _Atomic (C11), _Bool (C99), _Complex (C99), _Generic (C11),
_Imaginary (C99), _Noreturn (C11), _Static_assert (C11), _Thread_local (C11) (acestea care încep cu simbolul _ sunt utilizate
prin intermediul macrodefinițiilor lor (din stdalign.h, stdatomic.h, stdbool.h, complex.h, stdnoreturn.h, assert.h, threads.h)
pentru preprocesor: if, elif, else, endif, defined, ifdef, ifndef, define, undef, include, line, error, pragma, _Pragma(C99),
asm, fortranCuvinte rezervate în C++alignas (C++11), alignof (C++11), and, and_eq, asm, atomic_cancel (TM TS), atomic_commit (TM TS), atomic_noexcept (TM TS),
auto(1), bitand, bitor, bool, break, case, catch, char, char8_t (C++20), char16_t (since C++11), char32_t (since C++11), class(1),
compl, concept (C++20), const, consteval (C++20), constexpr (C++11), constinit (C++20), const_cast, continue, co_await (C++20),
co_return (C++20), co_yield (C++20), decltype (C++11), default(1), delete(1), do, double, dynamic_cast, else, enum, explicit,
export(1)(3), extern(1), false, float, for, friend, goto, if, inline(1), int, long, mutable(1), namespace, new,
noexcept (C++11), not, not_eq, nullptr (C++11), operator, or, or_eq, private, protected, public, reflexpr (reflection TS),
register(2), reinterpret_cast, requires (C++20), return, short, signed, sizeof(1), static, static_assert (C++11),
static_cast, struct(1), switch, synchronized (TM TS), template, this, thread_local (C++11), throw, true, try, typedef,
typeid, typename, union, unsigned, using(1), virtual, void, volatile, wchar_t, while, xor, xor_eq Semnificația numerelor din paranteze este: (1) - schimbare sau nou sens adăugat în C++11, (2) - schimbare în C++17, (3) - schimbare în C++20.
1.4. Sintaxa limbajului de programare
Și în cazul limbajelor de programare succesiunile de "cuvinte" construite după anumite reguli formează "propoziții", numite instrucțiuni.
Sintaxa unui limbaj de programare reprezinta ansamblul de reguli prin care se determină dacă o anumită instrucțiune este alcătuită corect sau nu.
Sintaxa unui limbaj de programare poate fi descrisă în diverse moduri, unul dintre acestea fiind notația BNF (Backus-Naur Form).
Notația BNF (Backus-Naur Form) a fost utilizata prima data la descrierea sintaxei limbajului ALGOL
(în cadrul raportului ALGOL60 apărut în 1963) și este numită după doi dintre autorii acestui raport.
În cadrul BNF sunt folosite metasimboluri, simboluri terminale și simboluri neterminale.
Metasimboluri sunt simbolurile <, >, | și ::= și ele fac parte din mecanismul de descriere a limbajului.
Simbolul | semnifică o alternativă, simbolul ::= înseamnă "se definește astfel".
Simbolurile terminale sunt cuvinte care apar acolo unde se specifică în regulile de producție (de ex. for, while, do, +, ; etc.).
Simbolurile neterminale sunt încadrate în < și > și sunt definite prin productii ale limbajului (de ex., <variabila>, <identificator>, <instructiune if> etc.).
În limbajul C++ sintaxa unui identificator se descrie în BNF astfel:
<identificator>::=<litera>|<identificator><cifra>|<identificator><literă>|
unde:
<litera>::=a|b|...|z|A|B|...|Z|_
<cifra>::=0|1|2|...|9
Simbolul _ este considerat <litera> în acest context.
Conform acestei reguli, identificatorul are 3 definiții alternative: un identificator este fie o <litera>, fie un <identificator> urmat de o <cifra> sau o <litera> (definitie recursiva). Semnificatia acestei definitii este urmatoarea: un identificator poate sa contina o singura litera, sau o litera urmata de oricâte litere si/sau cifre. Conform acestei definitii, sunt corecte sintactic urmatorii identificatori: a, t1, sec12a1.
Descrierea sintaxei instructiunii conditionale if-else din limbajul C++ în notatie BNF este:
<instructiune if>::=
if (<conditie>)
<instructiune1>
else
<instructiune2>
1.5. Semantica limbajului de programare
Semantica unei instrucțiuni reprezintă înțelesul pe care îl are acea instrucțiune, adică ce va executa calculatorul când o întâlnește.
Astfel, pentru instrucțiunea if-else de mai sus, se va evalua condiția <conditie>, iar dacă aceasta este adevărată
(în momentul evaluării), atunci se va executa o singură dată instrucțiunea <instructiune1>, respectiv, dacă este falsă,
se va executa o singură dată instrucțiunea <instructiune2>.
La fiecare instrucțiune ce va fi descrisă în cadrul cursului se va prezenta semantica sa.
Test de autoverificare 2 - Sintaxă, semantică
1.6. Date
1.6.1. Variabile și constante
În cadrul programului, datele pot fi declarate ca fiind constante sau variabile.
O constantă este o dată a cărei valoare nu se poate modifica pe parcursul execuţiei programului, deci rămâne constantă.
O variabilă este o dată a cărei valoare se poate modifica pe parcursul execuţiei programului, deci ea poate varia, dar acest lucru nu este obligatoriu. Astfel, se poate declara o dată ca fiind variabilă în cadrul unui program, apoi ea să primească o anumită valoare, iar această valoare să rămână asociată respectivei variabile până la terminarea programului.
Evident, atunci când se va declara o dată constantă, se va preciza şi valoarea ei, iar când se va declara o dată variabilă, se subînţelege că ulterior, pentru a putea fi folosită, această variabilă va primi o anumită valoare.
Majoritatea limbajelor de programare asignează o valoare iniţială variabilelor, o dată cu declararea lor. Astfel, şirurile de caractere sunt iniţializate la şirul vid, iar numerele sunt considerate cu valoarea zero.
Fireşte, atât constantele cât şi variabilele au o anumită structură, mai simplă sau mai complicată, şi o anumită natură, dată de mulţimea valorilor posibile pentru o dată. Cu ele se pot face anumite operaţii, în funcţie de natura şi structura lor. Astfel, vom spune că o dată are un anumit tip.
Există anumite convenții lexicale pentru scrierea constantelor și variabilelor:
ALL_CAPS pentru constante
lowerToUpper pentru variabile, cu nume cât mai sugestive
1.6.2. Expresii
Majoritatea limbajelor de programare definesc expresiile după un sistem de reguli sintactice, care, în general sunt următoarele:
orice constantă este expresie;
orice variabilă este expresie;
dacă E este expresie, atunci şi (E), -E, +E, F(E) sunt expresii, unde F
este numele unei funcţii aplicabile expresiei E;
dacă E1 şi E2 sunt expresii,
atunci şi E1+E2, E1-E2, E1*E2, E1/E2
sunt expresii.
Acum, pe baza regulilor de mai sus putem construi expresii foarte complexe, pornind de la constante şi variabile.
Astfel, să considerăm entitatea (3+A)*(5/(-B+C)) şi să verificăm dacă ea este expresie sau nu.
Să presupunem că A, B şi C sunt variabile numerice întregi.
Cum 3 este constantă, conform regulii 1, ea este şi expresie. A, fiind variabilă este, conform regulii 2 expresie.
Acum, conform regulii 4, 3+A este expresie, iar (3+A) este tot expresie, conform regulii 3.
După simbolul înmulţirii (reprezentat adesea prin *), avem: 5 este expresie, fiind constantă, B, C,
apoi -B şi -B+C sunt expresii. În fine, conform regulii 3, (-B+C) este tot expresie, apoi şi
(5/(-B+C)) este expresie, în conformitate cu regula 4, şi, tot după această regulă, şi (3+A)*(5/(-B+C)) este expresie.
Datele sunt caracterizate de
Tip
Nume
Valoare
Adresă
Domeniu de vizibilitate
Timp de viaţă
Operatori și apeluri de funcții
1.6.3. Variabile în C++
Declararea variabilelor
tip variabila;
tip var1, var2, ...,varn;
tip variabila=expresie_constanta;
Variabile globale și variabile locale
Variabilele globale sunt declarate la începutul programului, în afara oricărei funcţii (inclusiv în afara funcției main)
și sunt vizibile oriunde în program (în cadrul oricărei funcții).
Variabilele locale se declară în corpul funcţiei, înainte de a fi utilizate, iar ele sunt vizibile doar în acea funcție.
Exemplificare
char c;
signed char sc;
int a, b;
a = b = 5;
int i;
int suma = 0;
long j;
float x1, x2, x3;
float pi = 3.14;
double y;
int patrat(int y)
{
int x; x=y*y;
return x;
}
Variabila x este caracterizată de tip = int, nume = x, valoare = 4 (de exemplu, dacă y este 2),
domeniu de vizibilitate: funcția patrat, adresă (nu o putem ști, dar o putem determina cu &x
și arată de ceva genul 0x7ffd3d518618), timp de viaţă = pe durata unui apel al funcției.
Parametri pentru funcții
Aceste variabile sunt folosite în mod generic ca parametri pentru o funcție. Ele sunt vizibile doar în contextul funcției
Explicație pe un exemplu
Cum se acceseaza o variabila globala cand exista o variabila locala cu acelasi nume?
Ce se intampla daca vrem sa accesam variabila globala, cand exista o variabila locala cu acelasi nume?
Pentru a rezolva aceasta problema va trebui sa folosim operatorul de rezolutie a domeniului.
Programul de mai jos explica cum se face acest lucru cu ajutorul operatorului de rezolutie a domeniului.
// Program C++ pentru a arata ca putem accesa o variabila globala
// folosind operatorul de rezolutie a domeniului :: cand
// exista o variabila locala cu acelasi nume
#include
using namespace std;
// x global
int x = 0;
int main()
{
// Local x
int x = 10;
cout << "Valoarea x global este " << ::x;
cout<< "\nValoarea x local este " << x;
returneaza 0;
}
Iesire:
Valoarea x global este 0
Valoarea lui x local este 10
1.6.4. Atribuirea (asignarea) variabilelor
Prin instrucțiunea de atribuire (asignare) o variabilă primește valoarea unei expresii. Cu alte cuvinte, întâi se evaluează expresia,
iar dacă evaluarea reușește, atunci variabila respectivă își pierde fosta valoare și primește valoarea evaluării expresiei.
În contextul instrucțiunii de atribuire, dacă variabilele nu sunt de același tip, trebuie să fie considerate cazurile de compatibilitate
și conversiile implicite care se fac (la tipul variabilei).
variabila = expresie;
SAU
Lvalues (left-side) = Rvalues (right-side)
unde Lvalues sunt variabile și Rvalues sunt expresii.
Exemplu
În atribuirea distance = rate * time; avem: Lvalue: „distance” și Rvalue: „rate * time”.
Contraexemple pt Lvalue: 5+5, "str", const int i.
1.7. Tipuri de date
Prin tip de date vom înţelege o mulţime de valori, împreună cu operaţiile ce se pot executa cu ele. Fiecărei variabile, la declarare,
i se va asocia un anumit tip. Tipul unei constante poate fi determinat implicit din valoarea constantei, sau poate fi precizat explicit
ca în cazul variabilelor. Astfel, dacă constanta K are valoarea numerică 7, putem trage concluzia că ea este de tip întreg,
sau de tip real, nu şi logic sau şir de caractere. Totuşi, există şi limbaje în care se fac anumite convenţii, de pildă că orice număr diferit
de zero este considerat ca fiind cu valoarea de adevăr adevărat, iar numărul zero are valoarea de adevăr fals.
Unele limbaje de programare (Basic, Python) permit declararea unor variabile fără a se preciza tipul lor, considerându-se astfel ca având un anumit tip general.
Astfel, atunci când va fi folosită, variabila respectivă va fi considerată ca având cel mai adecvat tip cu putinţă, în situaţia concretă
respectivă. De pildă, dacă este declarată o variabilă X, iar la un moment dat i se atribuie valoarea 3, atunci ea poate fi considerată
ca având un tip numeric. Dacă ulterior, variabila X va primi valoarea "abc", adică un şir de caractere, se poate considera că X
este de tip şir de caractere.
Pe baza constantelor şi variabilelor se formează expresii. Bineînţeles, în formarea expresiilor se vor folosi acei operatori şi acele
funcţii permise de tipurile valorilor asupra cărora se operează. Expresiile mici pot conduce la elaborarea de expresii mai mari,
din ce în ce mai complexe.
Domeniul tipului (colecţia de obiecte) este o mulțime de valori pentru care s-a adoptat un anumit mod de reprezentare
în memorie. Operaţiile tipului sunt operațiile ce se pot executa cu valorile de acel tip de date.
Avem următoarele categorii de tipuri de date:
tipuri de date standard;
tipuri de date structurate de nivel jos - operaţiile se desfășoară la nivel de componentă;
'tipuri de date de nivel înalt - operaţiile sunt implementate de algoritmi utilizator.
1.7.1. Tipuri de date standard în C++
Tipuri caracter: tipurile char, signed char, unsigned char
Tipuri întregi: tipurile caracter, întregi cu semn, întregi fără semn, tipurile enumerare
Tipuri reale: tipurile întregi şi tipurile flotante reale
Tipuri aritmetice: tipurile întregi şi cele flotante
Tipuri de bază: caracter, întregi cu şi fără semn, flotante
Tipul void: desemnează o mulţime vidă
1.7.2. Reprezentarea în memorie a tipurilor de date (pe 32 biți)
Datele se reprezintă în sistem binar (baza 2), pe biți. 8 biți formează un byte sau octet. Există 10 tipuri de studenți: cei care înțeleg sistemul binar și cei care nu îl înțeleg.
Reprezentarea datelor de tip char, intReprezentarea datelor de tip floatReprezentarea datelor de tip double
În C/C++ putem folosi următorii modificatori de tip, care schimbă domeniul de valori pentru un anumit tip:
signed - acesta este modificatorul implicit pentru toate tipurile de date; bitul cel mai semnificativ din reprezentarea valorii
este semnul (0 - pozitiv, 1 - negativ);
unsigned - restricționează valorile numerice memorate la valori pozitive; domeniul de valori este mai mare deoarece bitul de semn
este liber și participă în reprezentarea valorilor;
short - reduce dimensiunea tipului de date întreg la jumătate; se aplică doar pe întregi;
long - permite memorarea valorilor care depășesc limita specifică tipului de date; se aplică doar pe int sau
double (la int dimensiunea tipului de bază se dublează, iar la double se mărește dimensiunea,
de regulă cu doi octeți (de la 8 la 10 octeți)
long long - a fost introdus în C99 pentru stocarea unor valori întregi de dimensiuni foarte mari
Click aici pentru un tabel cu domeniile tipurilor de date (pe o anumită arhitectură cu un procesor pe 16 biți) La procesoarele pe 32 de biți, reprezentarea lui int este pe 32 de biți (4 octeți).
Reprezentarea numerelor pozitive diferă de cea a numerelor negative. Primul bit (numit și bit de semn) este folosit,
în cazul numerelor pozitive pentru reprezentarea unor valori mai mari, pe când, în cazul numerelor negative, acest bit este folosit ca bit de semn.
Adică, dacă el este 1, atunci se consideră că avem de a face cu un număr negativ, iar dacă e 0, cu un număr pozitiv.
Pentru a înțelege reprezentarea, vom face o comparație între doi întregi, numerele 190 și -190, care adunate trebuie să dea 0.
Reprezentarea se numește în complement față de 2.Reprezentare numere pozitive/negative
Reprezentarea numerelor 190 și -190:
Suma în binar dintre 190 și -190:
Suma dintre un număr pozitiv și corespondentl său negativ este zero:
Există anumite echivalențe între tipurile de date, după cum urmează: signed short int ≡ short,
unsigned short int ≡ unsigned short, signed int ≡ int, unsigned int ≡ unsigned, signed long int ≡ long, unsigned long int ≡ unsigned long.
1.7.3. Tipuri de date derivate
Acestea se construiesc din obiecte, funcţii şi alte tipuri incomplete.
tipul tablou de T (elementele de tip T)
tipul structură
tipul uniune
tipul funcţie, derivat din tipul returnat T şi numărul şi tipurile parametrilor (funcţie ce returnează T)
tipul pointer, derivat dintr-un tip referenţiat (tip funcţie, tip obiect, tip incomplet).
Valorile tipului pointer sunt referinţe la o entitate de tipul referenţiat (pointer la T).
Un tablou de dimensiune necunoscută, o structură sau uniune cu conţinut necunoscut sunt tipuri incomplete
Tipurile aritmetice și tipul pointer se mai numesc și tipuri scalare. De asemenea, tablourile și structurile se numesc tipuri agregat.
1.8. Tipuri întregi
1.8.1. Constante întregi
Zecimale
Octale: au prefixul 0 (zero), de exemplu: 032 = 26 și 077 = 63
Hexazecimale: au prefixul 0x sau 0X, de exemplu: 0x32 = 50 și 0x3F = 63
Întregi „long”: au sufixul l sau L, de exemplu: 2147483647L și 0xaf9Fl = 44959
Întregi „unsigned” au sufixul u sau U, de exemplu: 345u și 0xffffu = 65535
Caractere între apostrof: ‘A’, ‘+’, ‘n’
Caractere în zecimal: 65, 42
Caractere în octal: ’\101’, ‘\52’
Caractere în hexazecimal: ‘\x41’, ‘\x2A’
Caractere speciale – secvențe escape
1.8.2. Operații și funcții pentru tipurile întregi
Operațiile pentru tipurile întregi sunt + - * / % == != < <= > >= ++ --, iar funcţiile sunt
cele de la tipul flotant și cele din bibliotecă: tolower, toupper, isalpha, isalnum, iscntrl, isdigit,
isxdigit, islower, isupper, isgraph, isprint, ispunct, isspaceTabel cu operatorii tipurilor întregi
Operatorii speciali de incrementare și decrementare ++ și -- se aplică doar unei expresii ce desemnează un obiect din memorie (L-value).
De exemplu, ++5; --(k+1); ++i++ nu au sens, dar (++i)++ are sens.
Operatorii de forma a op= b (+=, -=, *=, /=) au semnificația a = a op b.
1.9. Tipuri reale
Numerele reale sunt reprezentate în C/C++ prin tipurile float și double. De fapt, vorbim despre o reprezentare a numerelor raționale,
deoarece numerele iraționale au un număr infinit de cifre, deci nu se pot reprezenta.
float reprezintă numere reale în simplă precizie, reprezentate pe 4 octeți (sizeof(float) = 4),
10-37<=abs(f)<=1038, 6 cifre semnificative), iar double reprezintă numere reale în dublă precizie,
adică pe 8 octeți (sizeof(double) = 8, 10-307<=abs(f)<=10308>, 15 cifre semnificative).
De asemenea, cu long double se pot reprezenta numere reale în „extra” dublă precizie, folosindu-se 12 octeți
(sizeof(long double) = 12, 10-4931<=abs(f)<=104932, 18 cifre semnificative).
Limitele acestor domenii de reprezentare se găsesc în fișierul header float.h.
Operaţiile ce se pot face cu numere reale sunt: + - * / == != < <= > >=.
Constantele reale sunt implicit double și pot fi descrise astfel:
vezi aici
125.435 1.12E2 123E-2 .45e+6 13. .56
1.12E2 = 1.12 x 102 = 112
123E-2 = 123 x 10-2 = 1.23
.45e+6 = 0.45 x 106 =450000
13. = 13.00 şi .56 = 0.56
Pentru a preciza că o constantă este de tip float și nu implicit double, constanta respectivă trebuie sa aibă sufixul f sau F, de exemplu:
.56f 23e4f 45.54E-1F
23e4f = 23 x 104 = 230000.00
Pentru long double trebuie sa aibă sufixul l sau L.
Tipul bool reprezintă domeniul de valori: {false, true}, în care false = 0, iar true = 1,
dar şi orice întreg nenul. Operațiile care se pot face cu valorile logice sunt: ! && || == != Declaraţii posibile:
bool x = 7; // x devine “true”
int y = true; // y devine 1
Expresii relaționale și expresii logice
Acestea au forma următoare:
Valoarea expresiilor relaţionaleNegația logică: ! 0 = 1, ! orice_nr_diferit_de_0 = 0.
Disjuncția logică (SAU) este reprezentată prin ||, iar expresia rezultat este adevărată când cel puțin unul din expresiile operanzi este adevărată.
Valoarea expresiilor logice || Conjuncția logică (ȘI) este reprezentată prin &&, iar expresia rezultat este adevărată când ambele expresii operanzi
sunt adevărate.
Valoarea expresiilor logice && Exemple
O condiţie de forma a ≤ x ≤ b se scrie în limbajul C++ astfel:
(x >= a) && (x <= b)
(a <= x) && (x <= b)
O condiţie de forma a > x sau x > b se scrie în limbajul C++ astfel:
(x < a) || (x > b)
!(x >= a && x <= b)
Legile lui De Morgan
! (A && B) este echivalent cu !A || ! B
! (A || B) este echivalentă cu ! A && ! B
1.11. Tipul void
Pentru situații speciale, se poate folosi tipul void (care semnifică mulțimea vidă), după cum urmează:
Conversia în tip void a unei expresii semnifică faptul că valoarea sa este ignorată
Utilizat pentru tipul pointer; nu se face controlul tipului la un pointer de tip void
Utilizat pentru funcţii fără valoare returnată sau pentru funcţii fără parametri
Este un tip incomplet ce nu poate fi completat
Conversia în tip void a unei expresii semnifică faptul că valoarea sa este ignorată.
Observație. Tipul void este un tip incomplet ce nu poate fi completat.
1.12. Utilizare typedef
Typedef este un mecanism prin care se asociază un tip unui identificator:
exemple
typedef char litera_mare;
typedef short varsta;
typedef int vector[20];
typedef char string[30];
typedef float matrice[10][10];
typedef struct { double re, im; } complex;
Identificatorul respectiv se poate utiliza pentru a declara variabile:
exemple
Dacă exp1 are valoare true (nenulă), atunci valoarea expresiei este valoarea lui exp2, iar exp3 nu se evaluează
Dacă exp1 are valoare false (nulă), atunci valoarea expresiei este valoarea lui exp3; exp2 nu se evaluează
Operatorul ?: este drept asociativ Exemple
x >= 0 ? x : y
x > y ? x : y
x > y ? x > z ? x : z : y > z ? y : z
joc=(raspuns==’1’)?JocSimplu();JocDublu();
#include <iostream>
using namespace std;
void main(){
int a=1, b=2, c=3;
int x, y, z;
x = a?b:c?a:b;
y = (a?b:c)?a:b; /* asociere stanga */
z = a?b:(c?a:b); /* asociere dreapta */
cout<< "x=" << x << "\ny=" << y << "/nz="<< z;
}
/* x=2 y=1 z=2 */
Valoarea şi tipul întregii expresii este valoarea şi tipul operandului drept.
Operatorul virgulă are cea mai mică precedenţă.
Exemplu
a = 1, b = 2;
i = 1, j = 2, ++k + 1;
k != 1, ++x * 2.0 + 1;
for(suma = 0, i = 1; i <= n; suma += i, ++i);
1.13.3. Operatorul sizeof()
sizeof() este un operator unar, sub forma unei funcții, ce permite determinarea numărului de octeţi pe care se reprezintă un
obiect (un tip sau o expresie). Exemple:
operatorul de acces la elementele tabloului [ ] : int a[100] ; a[3]=17;
operatorul de apel de funcție ():y = f(x);
operatorul adresă & și operatorul de „dereferențiere” *(strâns legat de pointeri (vom reveni)):
int a, *p; // p este un pointer la int
p = &a; // p este pointer la a
*p = 3; // valoarea lui a devine 3
1.14. Prioritatea și asociativitatea operatorilor.
Tabel cu prioritatea și asociativitatea operatorilor
click aici
1.15. Conversii între tipuri
1.12.1. Reguli pentru conversia implicită
În absenţa unui unsigned, obiectele se convertesc la tipul cel mai "înalt" în ordinea (descrescătoare):
long double, double, float, long int, int. Regulile de conversie pentru operanzii unsigned depind de implementare.
Conversia la unsigned se face doar în caz de necesitate (de ex. valoarea din unsigned nu "încape" în celălalt operand).
Regula "integer promotion" spune că operaţiile se fac cel puţin în tipul int, deci char şi short sunt “promovaţi” la int.
La o asignare (v = exp) tipul membrului drept se converteşte la tipul membrului stâng (care este şi tipul rezultatului).
Atenție! Prin conversiile implicite între tipuri, se pot produce:
Conversia explicită la tipul numetip se face prin: (numetip) expresie.
Exemple:
(long)(‘A’ + 1.0)
(int)(b*b-4*a*c)
(double)(x+y)/z
(float)x*y/z
x / (float)2
Exemplu de utilizare cast
#include <iostream>
using namespace std;
int main(void){
int i, j; double x, y, z, t;
i = 5/2; x = 5/2;
y = (double)(5/2); j = (double)5/2;
z = (double)5/2; t = 5./2;
cout << i << ", " << x << ", ";
cout << y << ", " << j << ", ";
cout << z << ", " << t << ", " << endl;
return 0;
}
/* 2, 2, 2, 2, 2.5, 2.5 */
Alt exemplu:
int a=1, b=2;
media = (a+(float)b)/2; // media devine 1.5
media = (a+b)/2; // media devine 1 !!!!
1.13. Fişiere în biblioteca standard, relative la tipuri
<limits.h>
pentru tipurile întregi
întregul min/max: INT_MIN, INT_MAX
Numărul de biţi pe caracter CHAR_BIT
<float.h>
pentru tipurile flotante
exponentul maxim
precizia zecimală etc.
<stdlib.h>
conţine funcţii de conversie
șir de caractere în int: atoi(const char*)
șir de caractere în float: atof(const char*)
Test 3 - Date, tipuri de date
1.16. Operații pe biți
În interiorul procesorului, toate operațiile matematice sunt făcute prin modificări la nivel de biți. În limbajul C, avem acces la
operatorii binari, astfel putând efectua operații pe biți.
1.14.1. Operatori logici pe biți
Operatorii logici pe biți sunt: & (AND), | (OR), ^ (XOR), ~ (NOT)detaliiAND: operatorul & Operatorul AND este un operator binar aplicat pe două șiruri de lungime egală de biți.
Dacă biții de pe aceeași poziție sunt ambii 1, bitul rezultat este 1, altfel este 0.
ExempluOR: operatorul | Operatorul OR este un operator binar aplicat pe două șiruri de lungime egală de biți.
Dacă biții de pe aceeași poziție sunt ambii 0, bitul rezultat este 0, altfel este 1.
ExempluXOR: operatorul ^ Ca și OR și AND, XOR este un operator binar aplicat pe două șiruri de lungime egală de biți.
Dacă biții de pe aceeași poziție au aceeași valoare, bitul rezultat este 0, altfel este 1.
ExempluNOT : operatorul ~ NOT se aplică unui șir de biți și inversează valoarea fiecărui bit în parte.
Exemplu Asadar, tabelele pentru operatorii pe biți sunt următoarele:
1.14.2. Operatorii de deplasare pe biți
Operatorii de deplasare pe biți sunt << și >>.
În exemplele următoare vom considera că un număr este reprezentat pe 1 octet (byte) (8 biți).
În realitate, reprezentarea unui număr depinde atât de sistemul folosit, cât și de tipul de variabilă (int, long, unsigned int, etc).
detaliiDeplasarea ("shiftare") la stânga <<
Operatorul de "shiftare" la stânga mută toți biții la stânga cu numărul specificat de poziții și umple restul de poziții rămase
goale cu 0. Operatorul de "shiftare" la stânga este echivalent cu înmulțirea numărului cu
2 la puterea numărului de poziții deplasate ("shiftate").
Exemplu
a = 5 (00000101 în reprezentarea binară pe un byte)
a << 1 = 10 (00001010 în binar) (echivalent cu 5*2)
a << 2 = 20 (00010100 în binar) (echivalent cu 5*4)
a << 3 = 40 (00101000 în binar) (echivalent cu 5*8)
Atenție însă la limita reprezentării numărului!
Astfel o "shiftare" cu 6 biți va duce la un rezultat diferit de ce ne așteptăm (320).
a << 6 = 64 (01000000 în binar) - primul bit de 1 s-a pierdut
Deplasarea ("shiftare") la dreapta: >>
Operatorul de "shiftare" la dreapta mută toți biții la dreapta cu numărul specificat de poziții și umple restul de poziții
rămase goale cu 0. Operatorul de shiftare la dreapta este echivalent cu împărțirea numărului la 2 la puterea numărului de
poziții "shiftate".
Exemplu
a = 5 (00000101 în reprezentarea binară pe un byte)
a >> 1 = 2 (00000010 în binar) (echivalent cu 5/2 (împărțire întreagă))
a >> 2 = 1 (00000001 în binar) (echivalent cu 5/4)
a >> 3 = 0 (00000000 în binar) (echivalent cu 5/8)
Atenție: Deoarece în cazul numerelor cu semn, primul bit din reprezentare este bitul de semn,
folosirea operatorilor de "shiftare" în cazul numerelor negative duce la rezultate greșite.
a = -5 (10000101 în reprezentarea binară cu semn pe un byte)
a >> 1 = 66 (0100010 în reprezentarea binară cu semn pe un byte)