Introducere în programare - curs și laborator - suport electronic

(C) 2023-2024 Bogdan Pătruț

Meniu

Lectia 1 | Lectia 2 | Lectia 3 | Lectia 4 | Lectia 5 | Lectia 6 | Lectia 7
Material pentru laboratoare

Rezumat video


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.

Video pentru cursul 1:

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
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 Puteți viziona aici un videoclip de prezentare a paradigmelor de programare https://www.youtube.com/watch?v=AmS2-9KEeS0

Limbaje de programare

Evoluția limbajelor de programare


Arborele genealogic al limbajelor de programare

Despre C++

Istorie și origini
Caracteristici

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
NoCompilatorInterpretor/Interpreter
1Compilatorul 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)
7 Examplu : C Compiler Exemple: Interpretor BASIC
Explicație video aici: https://www.youtube.com/watch?v=_C5AHaS1mOA
Pașii compilării în C
  1. 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.
  2. 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.
  3. 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ă.
  1. 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
    			#include <iostream>
    			#include <math.h>
    			#include <stdlib.h>
    			#include "biblioteca_mea.h"
    		
  2. 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:
  3. Directivele de compilare condiționată #if, #ifdef, #ifndef facilitează dezvoltarea și în special testarea codului.
    Directiva #if
    Directiva #ifdef
    Directiva #ifndef
    Incluziunea 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ță:
    				#ifndef _MODUL_A_
    				#define _MODUL_A_
    				...
    				#endif /* _MODUL_A_ */
    				
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: Există mai multe standarde de codificare a seturilor de caractere: EBCDIC, ASCII, UTF-8.
Detalii

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:
  1. 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);
  2. cuvinte rezervate - acestea nu pot fi folosite decât în scopul pentru care au fost definite (de ex., în limbajul C++).
    Cuvinte rezervate în C auto, 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, fortran
    Cuvinte 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:

1.6.2. Expresii

Majoritatea limbajelor de programare definesc expresiile după un sistem de reguli sintactice, care, în general sunt următoarele:
  1. orice constantă este expresie;
  2. orice variabilă este expresie;
  3. dacă E este expresie, atunci şi (E), -E, +E, F(E) sunt expresii, unde F este numele unei funcţii aplicabile expresiei E;
  4. 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

1.6.3. Variabile în C++

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:

1.7.1. Tipuri de date standard în C++

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, int
Reprezentarea datelor de tip float
Reprezentarea datelor de tip double


În C/C++ putem folosi următorii modificatori de tip, care schimbă domeniul de valori pentru un anumit tip:
  1. signed - acesta este modificatorul implicit pentru toate tipurile de date; bitul cel mai semnificativ din reprezentarea valorii este semnul (0 - pozitiv, 1 - negativ);
  2. 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;
  3. short - reduce dimensiunea tipului de date întreg la jumătate; se aplică doar pe întregi;
  4. 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)
  5. 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
  1. Reprezentarea numerelor 190 și -190:
  2. Suma în binar dintre 190 și -190:
  3. 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. 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

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, isspace
Tabel 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.
 123.456e78L
Funcţii (în biblioteca math.h): sin, cos, tan, asin, acos, atan, sinh, cosh, tanh, exp, log, log10, pow, sqrt, ceil, floor, fabs, ldexp, frexp, modf, fmod.

1.10. Date booleene (logice)

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:
		expresie_relationala ::= expr < expr | expr > expr | expr <= expr | expr >= expr | expr == expr | expr != expr 
		expresie_logica ::= ! expr | expr || expr | expr && expr
	
Valoarea expresiilor relaţionale
Negaț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

1.11. Tipul void

Pentru situații speciale, se poate folosi tipul void (care semnifică mulțimea vidă), după cum urmează: 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
		litera_mare u, v=‘a’;
		varsta v1, v2;
		vector x; string s;
		matrice a;
		complex z;
	
sau funcții
exemple
		complex suma(complex z1, complex z2) { 
			complex z; 
			z.re=z1.re+z2.re; z.im=z1.im+z2.im; 
			return z;
		}
	

1.13. Operatori speciali

1.13.1. Operatorul condiţional ? :

Detalii Sintaxa: exp1 ? exp2 : exp3 Semantica 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 */

1.13.2. Operatorul virgulă ,

Detalii Sintaxa: expresia_virgula ::= expresie, expresie Semantica 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:
		sizeof(int), sizeof(double);
		sizeof(b*b-4*a*c), sizeof(i);
		sizeof(char)<=sizeof(short)<=sizeof(int)<=sizeof(long)
		sizeof(signed)=sizeof(unsigned) = sizeof(int)
		sizeof(float)<=sizeof(double)<=sizeof(long double)
	
Exemplu de utilizare
		#include <iostream>
		using namespace std;
		void main(){
			int x = 1; double y = 9; long z = 0;
			cout << "Operatorul sizeof()\n\n\n";
			cout << "sizeof(char) = " << sizeof(char) << endl;
			cout << "sizeof(int) = " << sizeof(int) << endl;
			cout << "sizeof(short) = " << sizeof(short) << endl;
			cout << "sizeof(long) = " << sizeof(long) << endl;
			cout << "sizeof(float) = " <<sizeof(float) << endl;
			cout << "sizeof(double) = " << sizeof(double) << endl;
			cout << "sizeof(long double) = " << sizeof(long double) << endl;
			cout << "sizeof(x +y + z) = " << sizeof(x+y+z) << endl;
		}
	
Rezultatul executiei ar putea fi:
		Operatorul sizeof()
		sizeof(char) = 1
		sizeof(int) = 4
		sizeof(short) = 2
		sizeof(long) = 4
		sizeof(float) = 4
		sizeof(double) = 8
		sizeof(long double) = 8
		sizeof(x + y + z) = 8
	

1.13.4. Alți operatori

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:
Exemplu conversii implicite
	#include <iostream>
	using namespace std;
	int main(void){
		char c1 = -126, c2;          /* c1 = 10000010    */
		unsigned char c3, c4 = 255;  /* c4 = 111111111   */
		short s1, s2 = -32767; /* s2=10000000 00000001   */
		short s3 = -1, s4;     /* s3 = 11111111 11111111 */
		s1 = c1;
		cout << "c1=" << (int)c1 << " s1=" << s1 << endl;
		c2 = s2;
		cout << "c2=" << (int)c2 << " s2=" << s2 << endl;
		c3 = s3;
		cout << "c3=" << (int)c3 << " s3=" << s3 << endl;
		s4 = c4;
		cout << "c4=" << (int)c4 << " s4=" << s4 << endl;
		return 0;
	}
	
Rezultatul execuţiei

1.12.2.Forţarea tipului - cast

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> <float.h> <stdlib.h>
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)
detalii AND: 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.
Exemplu
OR: 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.
Exemplu
XOR: 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.
Exemplu
NOT : 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).
detalii Deplasarea ("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)
		
Test 4 - Operatori pe biți

Bibliografie