Programare orientată obiect
Limbajul C++
Evoluția limbajelor de programare Cod mașină
●
programul în format binar, executat direct de processor Limbaj de asamblare
●
instrucțiuni in format binar înlocuit cu mnemonice, etichete simbolice pentru access la memorie
Procedural
●
descompune programul în proceduri/funcții Modular
●
descompune programul în module Orientat obiect
●
descompune programul într-o multime de obiecte care interacționeaza
Paradigma de programare orientată-object
Este metodă de proiectare şi dezvoltare a programelor:
● Oferă o abstractizare puternică și flexibilă
● Programatorul poate exprima soluția în mod mai natural (se concentrează pe structura soluției nu pe structura calculatorului)
● Descompune programul într-un set de obiecte, obiectele sunt elementele de bază
● Obiectele interacționeaza pentru a rezolva problema, există relații între clase
● Tipuri noi de date modeleaza elemente din spațiul problemei, fiecare obiect este o instanța a unui tip de data (clasă)
Un obiect este o entitate care:
● are o stare
● poate executa anumite operații (comportament) Poate fi privit ca și o combinație de:
● date (atribute)
● metode Concepte:
● obiect
● clasă
● metodă (mesaj) Proprietăți:
● abstractizare
● încapsulare
● moștenire
● polimorfism
Limbajul de programare C/C++
De ce C/C++:
● Folosit la scară largă atât în mediul academic cât și în industrie
● limbaj hybrid , implementeaza toate conceptele necesare pentru programare orientat obiect (C++)
● multe limbaje de programare au evoluat din C/C++ (Java, C#). Este mult mai usor să înveţi aceste limbaje daca ştii C/C++
Advantaje:
● Concis, cod lizibil
● Performanţă: programele scrise în c/c++ în general sunt mai rapide decât cele scrise în alte limbaje de programare
● Întreţinere: codul fiind concis programatorul are de întreținut un volum mai mic de cod
● Portabil: se pot scrie aplicații pentru orice fel de procesor, sistem de operare
● Productivitate: C++ a fost creat cu scopul principal de a mari productivitate (față de C).
Limbaje compilate. Procesul de compilare
Programul C/C++ trebuie compilat pentru a putea fi executat.
Programul scris în fisiere text ( fişiere soursă ) trebuie transformat în cod binar ce poate fi executat de procesor:
Fişiere sursa - fişiere text, conţin programul scris într-un limbaj de programare
| - compilatorul, analizează fișierele și crează fișiere obiect
Fișiere Obiect - fișiere intermediare, conține bucați incomplete din programul final
| - linker, combină mai multe fișiere obiect și crează programul care poate fi executat de calculator
Executabil
| - sistemul de operare, încarca fișierul executabil în memorie și executa programul
|
Program in memorie
În C/C++, pașii sunt executate înaintea rulării programului .
În unele limbaje transformarea se execută în timpul rulării. Acesta este una dintre motivele pentru care C/C++ în general are o performanță mai bună decât alte limbaje mai recente.
Python (Interpretat) vs C/C++ (compilat)
Mediu de dezvoltare pentru C/C++ (IDE - Integrated Development Environment)
Compilator
MinGW - - Minimalist GNU for Windows, implementare nativă Windows pentru compilatorul GNU Compiler Collection (GCC)
MinSYS – colecție de utilitare GNU (bash, make, gawk, grep)
Eclipse IDE
Eclipse CDC – mediu de dezvoltare integrat pentru C/C++ bazat pe platforma Eclipse
Colecție de pluginuri care ofera instrumentele necesare pentru a dezvolta aplicații în C/C++
Project C - Hello Word
Elemente de bază
C/C++ este case sensitive (a <> A) Identificator:
● Secvență de litere si cifre , începe cu o literă sau “_”(underline).
● Nume pentru elemente elemente din program (nume variabile, funcții, tipuri, etc)
● Ex. i, myFunction, rez, Cuvinte rezervate (Keywords):
● Identificatori cu semantica specială pentru compilator
● int, if, for, etc.
Literals:
● Constante specificate direct în codul sursă
● Ex. “Hello”, 72, 4.6, ‘c’
Operatori:
● aritmetici, pe biti, relaționali, etc
● +,-, <<
Separatori:
● Semne de punctuație folosite pentru a defini structura programului
● ; { } , ( ) Whitespace:
● Caractere ignorate de compilator
● space, tab, linie nouă Commentarii:
● // this is a single line comment
● /* This is a
* multiline comment */
● sunt ignorate de compilator
Tipuri de date
Un tip de date definește domeniul de valori și operațiile ce sunt definite pentru valorile din domeniu
Tipuri de date (Built in):
Name Description Size Range
char Character or small integer. 1byte signed: -128 to 127 unsigned: 0 to 255 short int
(short)
Short Integer. 2bytes signed: -32768 to 32767
unsigned: 0 to 65535
int Integer. 4bytes signed: -2147483648 to
2147483647
unsigned: 0 to 4294967295 long int
(long)
Long integer. 4bytes signed: -2147483648 to
2147483647
unsigned: 0 to 4294967295 float Floating point number. 4bytes +/- 3.4e +/- 38 (~7 digits) double Double precision floating point number. 8bytes +/- 1.7e +/- 308 (~15
digits) long double Long double precision floating point
number.
8bytes +/- 1.7e +/- 308 (~15 digits)
wchar_t Wide character. 2 or 4
bytes
1 wide character
● operații: +, -, *, /, %
● relații: <, >, <=, >=, ==, !=
● operațiile se pot executa doar dacă operanzii sunt de tipuri compatibile
● precedența operatorilor dicteaza ordinea de execuție
Vectori
Daca T e un tip de dată:
●
T[n] – este un vector cu n elemente de tip T
●
indicii sunt de la 0 la n-1
●
operatorul de indexare: [ ]
●
vector multidimensional : t[n][m]
#include <stdio.h>
int main() {
int a[5]; // create an array with 5 elements a[0] = 1; //index start from 0
a[1] = 2;
printf("a[0]=%d \n", a[0]);
printf("a[1]=%d \n", a[1]);
//!!! a[2] uninitialised printf("a[2]=%d \n", a[2]);
int b[] = { 1, 2, 3, 5, 7 };
printf("b[0]=%d \n", b[0]);
b[0] = 10;
printf("b[0]=%d \n", b[0]);
return 0;
}
Structuri (Record)
• este o colectie de elemente de tipuri diferite
• permite gruparea diferitelor tipuri de date simple într-o structură
struct name{
type1 field1;
type2 field2 }
struct car{
int year;
int nrKm;
}
car c;
c.year = 2010 c.nrKm = 30000;
#include <stdio.h>
//introduce a new struct called Car typedef struct {
int year;
int km;
} Car;
int main() {
Car car, car2;
//initialise fields car.year = 2001;
car.km = 20000;
printf("Car 1 fabricated:%d Km:%d \n", car.year, car.km);
//!!! car2 fields are uninitialised
printf("Car 1 fabricated:%d Km:%d \n", car2.year, car2.km);
return 0;
}
Declarații de variabile
●
Introduce un nume în program, asocieaza numele cu un tip
●
Se aloca memorie conform tipului variabilei
●
Tipul variabilei este folosit de compilator pentru a decide care sunt valorile si operațiile posibile
●
Valoarea este nedefinită după declarare.
●
Este recomandat sa combinăm declarația cu inițializarea
●
Trebuie ales nume sugestiv pentru orice variabilă din program
●
Variabila trebuie inițializată dupa declarație cu o valoare
<type> <identifier>
Ex. int i long rez int j=7
Constante
●
numerice: 1, 12, 23.5
●
sir de caractere: “Hello World”
●
caracter: ‘c’
Referințe și pointeri
- Pointer este un tip de date special, folosit pentru a lucra cu adresse de memorie - poate stoca adresa unei variabile, adres unei locații de memorie
Declarare
- ca și orice variabilă de alt tip doar ca se pune '*' ânainte de numele variabilei.
Ex: int *a; long *a, char *a Operatori
- address of '&': - returneaza adressa de memorie unde este stocat valoarea dintr- o variabilă
- dereferencing '*' - returneaza valoarea stocata în locația de memorie specificată
#include <stdio.h>
int main() { int a = 7;
int *pa;
printf("Value of a:%d address of a:%p \n", a, &a);
//assign the address of a to pa pa = &a;
printf("Value of pa:%d address of pa:%p \n", *pa, pa);
//a and pa refers to the same memory location a = 10;
printf("Value of pa:%d address of pa:%p \n", *pa, pa);
return 0;
}
Instrucțiuni
Toate instrucțiunile se termina cu: ; (excepție instrucțiunea compusă)
Expresii: a = b+c; i++; a==b Instrucțiune vidă: ;
Instrucțiunea compusă:
{
//multiple statements here }
Atribuire
● Operator de atribuire: =
● Atribuie o valoare la o variabilă (inițializează sau scimba valoare variabilei)
if, if-else, else if
if (condition){
//statements executed only if the condition is true }
if (condition){
//statements executed if the condition is true } else {
//statements executed only if the condition is not true }
if (condition1){
//statements executed if the condition1 is true } else if (condition2){
//statements executed only if condition1 is not true and the condition2 is true }
●
condition, condition1, condition2 - sunt expressii
●
orice expresie are o valoare
●
valoarea 0 înseamnă fals, orice altă valoare înseamnă adevărat
switch-case
switch(expression) {
case constant1:
statementA1 statementA2 ...
break;
case constant2:
statementB1 statementB2 ...
break;
...
default:
statementZ1 statementZ2 ...
}
Se evalueaza expresia, daca valoarea este egal cu constant1 atunci tot ce e pe ramura case constant1: se execută pană la primul break;
Dacă nu e egal cu constant1 se verifică cu constant2, 3...
Dacă nici o constantă nu este egală cu rezultatul expresiei atunci se
executa ramura default
Instrucțiuni repetitive
Execută repetat un set de instrucțiuni
while , do-while
while(condition) {
statement1 statement2 …
}
Cât timp condiția este adevarată (!=0) se execută corpul de instrucțiuni
do {
statement1 statement2 …
}
while(condition);
for
for(initialization; condition; incrementation) {
//body }
initialization – inițializează una sau mai multe variabile incrementation – se execută la fiecare iterație
condition – se verifică la fiecare iterație, cât timp e adivărat corpul de instrucțiuni se execută
for(initialization; condition; incrementation) {
statement1 statement2 …
}
initialization while(condition) {
statement1 statement2 …
incrementation }
Citire/Scriere
printf() - tipărește în consola (la ieșirea standard)
#include <stdio.h>
int main() { int nr = 5;
float nrf = 3.14;
char c = 's';
char str[] = "abc";
printf("%d %f %c %s", nr, nrf , c, str);
return 0;
}
scanf() - citește de la tastatura
int main() { int nr;
float f;
printf("Enter a decimal number:");
//read from the command line and store the value in nr scanf("%d", &nr);
printf("The number is:%d \n", nr);
printf("Enter a float:");
if (scanf("%f", &f) == 0) {
printf("Error: Not a float:");
} else {
printf("The number is:%f", f);
}
//wait until user enters 'e' while(getchar()!='e');
return 0;
}
Calculator numere raționale
Problem statement
Profesorul are nevoie de un program care permite elevilor să învețe despre numere raționale. Programul ajută studenții să efectueze operații aritmetice cu numere raționale
#include <stdio.h>
int main() {
int totalM = 0;
int totalN = 1;
int m;
int n;
while (1) {
printf("Enter m, then n to add\n");
scanf("%d", &m);
scanf("%d", &n);
totalM = totalM * n + m * totalN;
totalN = totalN * n;
printf("Total: %d/%d\n", totalM, totalN);
}
return 0;
}
Funcții
Funcția main este executat cand lansam in execuție un program C/C++
Declarare
<result type> name ( <parameter list>);
<result-type> - tipul rezultatului, poate fi orice tip sau void daca funcția nu returnează nimic
<name> - numele funcției
<parameter-list> - parametrii formali
Corpul funcției nu face parte din declarare
Definiție
<result type> name(<parameter list>){
//statements - the body of the function }
●
return <exp> rezultatul expresiei se returnează, execuția funcției se termina
●
o funcție care nu este void trebuie neapărat sa returneze o valoare prin expresia ce urmează dupa return
●
declararea trebuie sa corespunda cu definiția (numele parametrilor poate fi
diferit)
Specificații
●
Nume sugestiv
●
O scurtă descriere a funcției (ce face)
●
Semnificația parametrilor
●
condiții asupra parametrilor (precondiții)
●
ce se returnează
●
relația dintre parametri și rezultat (post condiții)
/*
* Verify if a number is prime
* nr - a number, nr>0
* return true if the number is prime (1 and nr are the only dividers)
*/
bool isPrime(int nr);
precondiții - sunt condiții care trebuie sa fie satisfacute de parametrii actuali inainte de a executa corpul funcției
postcondiții - condiții care sunt satisfacute dupa execuția funcției Apelul de funcții
name (<parameter list>);
●
Toate expresiile date ca parametru sunt evaluate înainte de execuția funcției
●
Parametrii actuali trebuie sa corespundă cu parametri formal (număr,poziție,tip)
●
declarația trebuie sa apară înainte de apel Supraîncărcare (Overloading)
●
Pot exista mai multe funcții cu același nume (dar parametrii formali diferiți)
●
La apel se va executa funcția care corespunde parametrilor actuali
Vizibilitate (scope)
Locul unde declarăm variabila determină vizibilitate lui (unde este variabila accesibilă).
Variabile, funcții declarate in interiorul unei instrucțiuni compuse ({ }) sunt vizibile doar in interiorul instrucțiunii compuse
Funcțiile au domeniul lor de vizibilitate - variabilele definite in interiorul funcțiilor sunt accesibile doar în funcție, ele sunt distruse dupa apelul funcției. (variabile locale)
Variabilele definite in afara funcțiilor sunt accesibile în orice funcție (variabile
globale).
Transmiterea parametrilor : prin valoare sau prin referinţă Transmitere prin valoare:
La apelul funcţiei se face o copie a parametriilor.
Schimbările făcute în interiorul funcţiei nu afectează variabilele exterioare.
Este mecanismul implicit de transmitere a parametrilor în C
Transmitere prin referinţă:
La apelul funcţiei se transmite adressa locaţiei de memorie unde se află valoarea variabilei.
Modificările din interiorul funcţiei sunt vizibile şi în afară.
void byValue(int a) { a = a + 1;
}
void byRef(int* a) {
*a = *a + 1;
}
int main() { int a = 10;
byValue(a);
printf("Value remain unchanged a=%d \n", a);
byRef(&a);
printf("Value changed a=%d \n", a);
return 0;
}
Vectorul este transmis prin referinţă
Calculator
/*
* Return the greatest common divisor of two natural numbers.
* Pre: a, b >= 0, a*a + b*b != 0 */
int gcd(int a, int b) { if (a == 0) {
return b;
} else if (b == 0) { return a;
} else {
while (a != b) { if (a > b) {
a = a - b;
} else {
b = b - a;
} }
return a;
} }
/*
* Add (m, n) to (toM, toN) - operation on rational numbers * Pre: toN != 0 and n != 0
*/
void add(int* toM, int* toN, int m, int n) {
*toM = *toM * n + *toN * m;
*toN = *toN * n;
int gcdTo = gcd(abs(*toM), abs(*toN));
*toM = *toM / gcdTo;
*toN = *toN / gcdTo;
}
//global variables. Store the current total int totalM = 0;
int totalN = 1;
int main() { int m,n;
while (1) {
printf("Enter m, then n to add\n");
scanf("%d", &m);scanf("%d", &n);
add(&totalM, &totalN, m, n);
printf("Total: %d/%d\n", totalM, totalN);
}
return 0;
}
Curs 1
– Introducere - POO – Limbajul C
• sintaxă
• tipuri de date
• instructiuni
• funcții
Funcții
Declarare
<result type> name ( <parameter list>);
<result-type> - tipul rezultatului, poate fi orice tip sau void daca funcția nu returnează nimic
<name> - numele funcției
<parameter-list> - parametrii formali
Corpul funcției nu face parte din declarare
Definiție
<result type> name(<parameter list>){
//statements - the body of the function }
●
return <exp> rezultatul expresiei se returnează, execuția funcției se termina
●
o funcție care nu este void trebuie neapărat sa returneze o valoare prin expresia ce urmează dupa return
●
declararea trebuie sa corespunda cu definiția (numele parametrilor poate fi diferit)
Funcția main este executat cand lansam in execuție un program C/C++
Specificații
●
Nume sugestiv
●
O scurtă descriere a funcției (ce face)
●
Semnificația parametrilor
●
condiții asupra parametrilor (precondiții)
●
ce se returnează
●
relația dintre parametri și rezultat (post condiții)
/*
* Verify if a number is prime
* nr - a number, nr>0
* return true if the number is prime (1 and nr are the only dividers)
*/
bool isPrime(int nr);
precondiții - sunt condiții care trebuie sa fie satisfacute de parametrii actuali inainte de a executa corpul funcției
postcondiții - condiții care sunt satisfacute dupa execuția funcției Apelul de funcții
name (<parameter list>);
●
Toate expresiile date ca parametru sunt evaluate înainte de execuția funcției
●
Parametrii actuali trebuie sa corespundă cu parametri formal (număr,poziție,tip)
●
declarația trebuie sa apară înainte de apel Supraîncărcare (Overloading)
●
Pot exista mai multe funcții cu același nume (dar parametrii formali diferiți)
●
La apel se va executa funcția care corespunde parametrilor actuali
Vizibilitate (scope)
Locul unde declarăm variabila determină vizibilitate lui (unde este variabila accesibilă).
Variabile locale
●
variabila este vizibila doar în interiorul instrucțiunii compuse ({ }) unde a fost delcarată
●
variabilele declarate în interiorul funcției sunt vizibile (accesibile) doar în funcție
●
instrucțiunile if, if else, for, while au domeniul propriu de vizibilitate
●
Incercarea de a accesa o variabilă în afara domeniului de vizibilitate genereaza eroare la compilare.
●
Ciclul de viață a unei variabile începe de la declararea lui si se termină când execuția iese din domeniul de vizibilitate a variabilei (variabila se distruge, memoria ocupată se elibereaza)
Global Variables
●
Variabilele definite in afara funcțiilor sunt accesibile în orice funcție, domeniul lor de vizibilitate este întreg aplicația
●
Se recomandă evitarea utilizării variabilelor globale (există soluții mai
bune care nu necesită variabile globale)
Transmiterea parametrilor : prin valoare sau prin referinţă Transmitere prin valoare:
La apelul funcţiei se face o copie a parametriilor.
Schimbările făcute în interiorul funcţiei nu afectează variabilele exterioare.
Este mecanismul implicit de transmitere a parametrilor în C
Transmitere prin referinţă:
La apelul funcţiei se transmite adressa locaţiei de memorie unde se află valoarea variabilei.
Modificările din interiorul funcţiei sunt vizibile şi în afară.
void byValue(int a) { a = a + 1;
}
void byRef(int* a) {
*a = *a + 1;
}
int main() { int a = 10;
byValue(a);
printf("Value remain unchanged a=%d \n", a);
byRef(&a);
printf("Value changed a=%d \n", a);
return 0;
}
Vectorul este transmis prin referinţă
Calculator
/*
* Return the greatest common divisor of two natural numbers.
* Pre: a, b >= 0, a*a + b*b != 0 */
int gcd(int a, int b) { if (a == 0) {
return b;
} else if (b == 0) { return a;
} else {
while (a != b) { if (a > b) {
a = a - b;
} else {
b = b - a;
} }
return a;
} }
/*
* Add (m, n) to (toM, toN) - operation on rational numbers * Pre: toN != 0 and n != 0
*/
void add(int* toM, int* toN, int m, int n) {
*toM = *toM * n + *toN * m;
*toN = *toN * n;
int gcdTo = gcd(abs(*toM), abs(*toN));
*toM = *toM / gcdTo;
*toN = *toN / gcdTo;
}
//global variables. Store the current total int totalM = 0;
int totalN = 1;
int main() { int m,n;
while (1) {
printf("Enter m, then n to add\n");
scanf("%d", &m);scanf("%d", &n);
add(&totalM, &totalN, m, n);
printf("Total: %d/%d\n", totalM, totalN);
}
return 0;
}
Funcții de test
Assert
#include <assert.h>
void assert (int expr);
expr – Se evaluează expresia. Daca e fals (=0) metoda assert generează o eroare și se termină execuția aplicației
Mesajul de eroare depinde de compilator (pot fi diferențe in funcție de compilator), conține informații despre locul unde a aparut eroarea (fisierul, linia), expresiea care a generat eroare.
Vom folosi instrucțiunea assert pentru a crea teste automate.
#include <assert.h>
/*
* greatest common divisor . * Pre: a, b >= 0, a*a + b*b != 0 * return gdc
*/
int gcd(int a, int b) { a = abs(a);
b = abs(b);
if (a == 0) { return b;
}
if (b == 0) { return a;
}
while (a != b) { if (a > b) {
a = a - b;
} else {
b = b - a;
} }
return a;
}
/**
* Test function for gcd */
void test_gcd() {
assert(gcd(2, 4) == 2);
assert(gcd(3, 27) == 3);
assert(gcd(7, 27) == 1);
assert(gcd(7, -27) == 1);
}
Review: Calculator – varianta procedurală
Profesorul are nevoie de un program care permite elevilor să învețe despre numere raționale. Programul ajută studenții să efectueze operații aritmetice cu numere raționale
/**
* Test function for gcd */
void test_gcd() {
assert(gcd(2, 4) == 2);
assert(gcd(3, 27) == 3);
assert(gcd(7, 27) == 1);
assert(gcd(7, -27) == 1);
} /*
* Add (m, n) to (toM, toN) - operation on rational numbers * Pre: toN != 0 and n != 0
*/
void add(int* toM, int* toN, int m, int n) {
*toM = *toM * n + *toN * m;
*toN = *toN * n;
int gcdTo = gcd(abs(*toM), abs(*toN));
*toM = *toM / gcdTo;
*toN = *toN / gcdTo;
}
int main() { test_gcd();
int totalM = 0, totalN = 1;
int m, n;
while (1) {
printf("Enter m, then n to add\n");
scanf("%d", &m);
scanf("%d", &n);
add(&totalM, &totalN, m, n);
printf("Total: %d/%d\n", totalM, totalN);
}
return 0;
}
Principii de proiectare pentru funcții
• Fiecare funcție sa aibă o singură responsabilitate (Single responsability principle)
• Folosiți nume sugestive (nume funcție, nume parametrii, variabile)
• Folosiți reguli de denumire (adauga_rational, adaugaRational, CONSTANTA), consistent în toată aplicația
• Specificați fiecare funcție din aplicație
• Creați teste automate pentru funcții
• Funcția trebuie sa fie usor de testat, (re)folosit, înțeles și modificat
• Folosiți comentarii în cod (includeți explicații pentru lucruri care nu sunt evidente în cod)
• Evitați (pe cât posibil) funcțiile cu efect secundar
Modular programming in C++.
Modulul este o colecție de funcții si variabile care oferă o funcționalitate bine definită.
Fișiere Header .
Declarațiile de funcții sunt grupate într-un fisier separat – fișier header (.h).
Implementarea (definițiile pentru funcții) intr-un fisier separat (.c/.cpp)
Scop
Separarea interfeței (ce oferă modulul) de implementare (cum sunt implementate funcțiile)
Separare specificații, declarații de implementare
Modulele sunt distribuite in general prin: fișierul header + fisierul binar cu implementările (.dll,.so)
• Nu e nevoie să dezvălui codul sursă (.c/.cpp )
Cei care folosesc modulul au nevoie doar de declarațiile de funcții (fișierul header)
nu si de implementări (codul din fișierele .c/.cpp)
Directive de preprocessare
Preprocesarea are loc inainte de compilare.
cod sursă – preprocessare – compilare – linkeditare – executabil
Permite printre altele: includere de fisiere header, definire de macrouri, compilare condiționată
Directiva Include
#include <stdio.h>
Pentru a avea access la funcțiile declarate intr-un modul (bibiliotecă de funcții) se folosește directiva #include
Preprocessorul include fisierul referit în fișierul sursă în locul unde apare directiva
avem două variante pentru a referi un modul: < > sau “”
#include "local.h” //cauta fișierul header relativ la directorul current al aplicației
#include <header> // caută fișierul header între bibliotecile system
(standard compiler include paths )
Aplicații modulare C/C++
Codul este inparțit in mai multe fisiere header (.h) si implementare (.c)
• fișierele .h conțin declarații (interfața)
• .c conține definiția (implementarea) funcțiilor
se grupeaza funcții in module astfel încât modulul sa ofere o funcționalitate bine definită (puternic coeziv)
•
Când un fișier .h se modifică este nevoie de recompilarea tuturor modulelor care îl referă (direct sau indirect)
• Fișierele .c se pot compila separat, modificarea implementării nu afectează modulele care folosesc (ele referă doar definițiile din header) Headerul este un contract între cel care dezvoltă modulul și cel care
folosește modulul.
Detaliile de implementare sunt ascunse in fișierul .h
Review: Calculator versiune modulară
Module:
• calculatorui.c – interfața utilizator
• calculator.h, calculator.c - TAD Calculator, operatii cu calculator
• rational.h, rational.c - TAD rational, operatii cu numere rationale
• util.h, util.c - funcții utile de operații cu numere (gcd)
Declarație multiplă – directivele #ifndev și #define
Într-un program mai complex este posibil ca un fișier header sa fie inclus de mai multe ori. Asta ar conduce la declarații multiple pentru funcții
Soluție: se folosesc directivele de preprocesare #ifndef, #ifdef, #define, #endif
Se poate verifica daca modulul a fost deja inclus, respectiv sa marcăm cand un modul a fost inclus (prin definirea unei etichete)
#ifndef RATIONAL_H_ /* verify if RATIONAL_H_ is already defined, the rest
(until the #endif will be processed only if RATIONAL_H_ is not defined*/
#define RATIONAL_H_ /* define RATIONAL_H_ so next time the prepocessor will not include this */
/**
* New data type to store rational numbers */
typedef struct { int a, b;
} Rational;
/**
* Compute the sum of 2 rational numbers * a,b rational numbers
* rez - a rational number, on exit will contain the sum of a and b */
void sum(Rational nr1, Rational nr2, Rational &rez);
#endif /* RATIONAL_H_ */
Principii de proiectare pentru module
• Separați interfața de implementare
◦ Headerul conține doar declarații, implementările în fișierul .c
• Includeți la începutul fișierului header un comentariu, o scurta descriere a modulului
• Creați module puternic coezive
◦ fiecare modul o singură funcționalitate, are o singură responsabilitate
• Șablonul - Arhitectură stratificată
◦
Straturi: ui, control, model, validation, repository
◦ Controlul dependențelor - Fiecare nivel depinde doar de nivelul următor
• Tip abstract de date – TAD
◦ operatiile definite in header (interfață) /implementarea in .c
◦ ascundere detalii de implementare
◦ specificații abstracte (independent de implementare, ce face
nu cum)
Biblioteci standard
#include <stdio.h>
Operatii de intrare/iesire
#include <math.h>
Funcții matematice – abs, sqrt, sin, exp, etc
#include <string.h>
sirul de caractere in C - vector de char care se termina cu caracterul '\0' strncpy – copiează string
strcat – concatenează string strcmp – compară stringuri strlen – lungimea stringului
#include<stdio.h>
#include<string.h>
int main(void) {
char arr[4]; // for accommodating 3 characters and one null '\0' byte.
char *ptr = "abc"; //a string containing 'a', 'b', 'c', '\0'
memset(arr, '\0', sizeof(arr)); //reset all
strncpy(arr, ptr, sizeof("abc")); // Copy the string printf("\n %s \n", arr);
arr[0] = 'p';
printf("\n %s \n", arr);
return 0;
}
Pointeri
Pointer este un tip de date , folosit pentru a lucra cu adresse de memorie - poate stoca adresa unei variabile, adres unei locații de memorie
Operatori:
'&','*'
#include <stdio.h>
int main() { int a = 7;
int *pa;
printf("Value of a:%d address of a:%p \n", a, &a);
//assign the address of a to pa pa = &a;
printf("Value of pa:%d address of pa:%p \n", *pa, pa);
//a and pa refers to the same memory location a = 10;
printf("Value of pa:%d address of pa:%p \n", *pa, pa);
return 0;
}
Null pointer
- valoare specială (0) pentru a indica faptul ca pointerul nu referă o memorie validă Pointer invalid (Dangling pointer)
Adresa refertă de pointer e invalid
#include <stdio.h>
int main() {
//init to null int *pa1 = NULL;
int *pa2;
//!!! pa2 refers to an unknown addres
*pa2 = 6;
if (pa1==NULL){
printf("pa1 is NULL");
}
return 0;
}
#include <stdio.h>
int* f() {
int localVar = 7;
printf("%d\n", localVar);
return &localVar;
}
int main() {
int* badP = f();
//!!! *badP refera o adresa de memorie //care a fost deja eliberata
printf("%d\n", *badP);
}
Vectori / pointeri - Aritmetica pointerilor
O variabila de tip vector - un pointer la primul element al vectorului
• vectorul este transmis prin referinta (se transmita adresa de memorie al primului element din vector – nu se face o copie).
• Indicele porneste de la 0 – primul element este la distantă 0 fața de începutul vectorului.
• Expresia array[3] – compilatorul calculează care este locația de memorie la distanță 3 față de începutul vectorului.
• Cu funcția sizeof(var) se poate afla numarul de bytes ocupat de valoarea din var (depinde de tipul lui var)
Aritmetica pointerilor
Folosirea de operații adăugare/scadere pentru a naviga in memorie (adrese de memorie)
#include <stdio.h>
int main() {
int t[3] = { 10, 20, 30 };
int *p = t;
//print the first elem
printf("val=%d adr=%p\n", *p, p);
//move to the next memory location (next int) p++;
//print the element (20)
printf("val=%d adr=%p\n", *p, p);
return 0;
p++ în funcție de tipul valorii referite de pointer, compilatorul calculeaza
urmatoarea adressa de memorie.
Gestiunea memoriei
Pentru variabilele declarate intr-o aplicație, compilatorul aloca memorie pe stivă (o zonă de memorie gestionat de compilator)
int f(int a) { if (a>0){
int x = 10; //memory for x is allocated on the stack }
//here x is out of scope and the memory allocated for x is no longer reserved //the memory can be reused
return 0;
}
int f(int a) { int *p;
if (a>0){
int x = 10;
p = &x;
}
//here p will point to a memory location that is no longer reserved *p = 5; //!!! undefined behavior, the program may crash
return 0;
}
Memoria este automat eliberată de compilator în momentul în care execuția părăseste domeniul de vizibilitate a variabilei.
La iesire dintr-o funcție memoria alocată pentru variabile locale este eliberată automat de compilator
Alocare dinamică
Folosind funcțiile malloc(size) și free(pointer) programatorul poate aloca memorie pe Heap – zonă de memorie gestionat de programator
#include <stdio.h>
#include <stdlib.h>
int main() {
//allocate memory on the heap for an int int *p = malloc(sizeof(int));
*p = 7;
printf("%d \n", *p);
//Deallocate free(p);
//allocate space for 10 ints (array) int *t = malloc(10 * sizeof(int));
t[0] = 0;
t[1] = 1;
printf("%d \n", t[1]);
//dealocate free(t);
return 0;
} /**
* Make a copy of str * str - string to copy * return a new string */
char* stringCopy(char* str) { char* newStr;
int len;
len = strlen(str) + 1; // +1 for the '\0'
newStr = malloc(sizeof(char) * len); // allocate memory strcpy(newStr, str); // copy string
return newStr;
}
Programatorul este responsabil sa dealoce memoria
Memory leak
Programul aloca memorie dar nu dealoca niciodata, memorie irosită
int main() { int *p;
int i;
for (i = 0; i < 10; i++) { p = malloc(sizeof(int));
//allocate memory for an int on the heap
*p = i * 2;
printf("%d \n", *p);
}
free(p); //deallocate memory
//leaked memory - we only deallocated the last int return 0;
}
void*
O funcție care nu returneaza nimic
void f() { }
Nu putem avea variabile de tip void dar putem folosi pointer la void - void*
#include <stdio.h>
#include <stdlib.h>
int main() { void* p;
int *i=malloc(sizeof(int));
*i = 1;
p = i;
printf("%d /n", *((int*)p));
long j = 100;
p = &j;
printf("%ld /n", *((long*)p));
free(i);
return 0;
}
Se pot folos void* pentru a crea structuri de date care funcționeaza cu orice tip de elemente
Probleme: verificare egalitate între elemente de tip void* , copiere elemente
Vector dinamic
typedef void* Element;
typedef struct { Element* elems;
int lg;
int capacitate;
} VectorDinamic;
/**
*Creaza un vector dinamic * v vector
* post: vectorul e gol */
VectorDinamic * creazaVectorDinamic();
/**
* Adauga un element in vector * v - vector dinamic
* el - elementul de adaugat */
void add(VectorDinamic *v, Element el);
/**
*Returneaza elementul de pe pozitia data * v - vector
* poz - pozitie, poz>=0
* returneaza elementul de pe pozitia poz */
Element get(VectorDinamic *v, int poz);
/**
*Initializeaza vectorul * v vector
* post: vectorul e gol */
VectorDinamic * creazaVectorDinamic() { VectorDinamic *v =
malloc(sizeof(VectorDinamic));
v->elems = malloc(INIT_CAPACITY * sizeof(Element));
v->capacitate = INIT_CAPACITY;
v->lg = 0;
return v;
} /**
* Elibereaza memoria ocupata de vector */
void distruge(VectorDinamic *v) { int i;
for (i = 0; i < v->lg; i++) { free(v->elems[i]);
}
free(v->elems);
free(v);
}
/**
* Aloca memorie aditionala pentru vector */
void resize(VectorDinamic *v) { int nCap = 2*v->capacitate;
Element* nElems=
malloc(nCap*sizeof(Element));
//copiez din vectorul existent int i;
for (i = 0; i < v->lg; i++) { nElems[i] = v->elems[i];
}
//dealocam memoria ocupata de vector free(v->elems);
v->elems = nElems;
v->capacitate = nCap;
} /**
* Adauga un element in vector * v - vector dinamic
* el - elementul de adaugat */
void add(VectorDinamic *v, Element el) { if (v->lg == v->capacitate) {
resize(v);
}
v->elems[v->lg] = el;
v->lg++;
}
Pointer la funcții
void (*funcPtr)(); // a pointer to a function
void *funcPtr(); // a function that returns a pointer
void func() {
printf("func() called...");
}
int main() {
void (*fp)(); // Define a function pointer fp = func; // Initialise it
(*fp)(); // Dereferencing calls the function void (*fp2)() = func; // Define and initialize (*fp2)(); // call
}
Putem folosi pointer la funcții în structurile de date generice typedef elem (*copyPtr)(elem&, elem);
typedef int (*equalsPtr)(elem, elem);
Limbajul de programare C++
Urmașul limbajului C apărut în anii 80 (C cu clase), dezvoltat inițial de Bjarne Stroustrup
Bibliografie: B. Stroustup, The C++ Programming Language, Addison Wesley
Limbajul C++
• multiparadigmă
• suportă paradidma orientat obiect (clase, obiecte, polimorfism, moștenire)
• tipuri noi – bool, referință
• spații de nume (namespace)
• șabloane (templates)
• excepții
• bibliotecă de intrări/ieșiri (IO Streams)
• STL (Standard Template Library)
Tipul de date mulțime - modular, implementat in C++
/*set.h*/
struct Mult;
typedef Mult* Set;
/**
* Create an empty set. Need to be invoked before we can use the set * n - the maximum number of element in the set; m - the set
*/
void createSet(Set &m, int n);
/**
* Add an element to the set
* m - the set; e the element to be added * return 0 if we can not add the element */
int add(Set m, Element e);
/*set.cpp*/
#include "set.h"
struct Mult { Element* e;
int c;
int max;
};
/**
* Release the memory allocated for this set */
void destroyM(Set& m) {
for (int i = 0; i < m->c; i++) destroyE(m->e[i]);
delete[] m->e;
delete m;
} /**
* Add an element to the set
* m - the set; e the element to be added * return 0 if we can not add the element */
int add(Set m, Element e) { if (m->c == m->max)
return 0;
if (!(contains(m, e))) { (m->c)++;
atrib(e, m->e[(m->c) - 1]);
}
return 1;
}
Tipul bool
domeniu de valori: adevărat (true) sau fals (false)
/**
* Verifica daca un numar e prim * nr numar intreg
* return true daca nr e prim */
bool ePrim(int nr) { if (nr <= 1) {
return false;
}
for (int i = 2; i < nr - 1; i++) { if (nr % i == 0) {
return false;
} }
return true;
}
Tipul referință
data_type &reference_name;
int y = 7;
int &x = y; //make x a reference to, or an alias of, y
Dacă schimbăm x se schimbă și y și invers, sunt doar două nume pentru același locație de memorie (alias)
Tipul referință este similar cu pointere:
• sunt pointeri care sunt automat dereferențiate cănd folosim variabile
• nu se poate schimba adresa referită /**
* C++ version
* Sum of 2 rational number */
void sum(Rational nr1, Rational nr2, Rational &rez) { rez.a = nr1.a * nr2.b + nr1.b * nr2.a;
rez.b = nr1.b * nr2.b;
int d = gcd(rez.a, rez.b);
rez.a = rez.a / d;
rez.b = rez.b / d;
} /**
* C version
* Sum of 2 rational number */
void sum(Rational nr1, Rational nr2, Rational *rez) { rez->a = nr1.a * nr2.b + nr1.b * nr2.a;
rez->b = nr1.b * nr2.b;
int d = gcd(rez->a, rez->b);
rez->a = rez->a / d;
rez->b = rez->b / d;
}
Const Pointer 1 const type*
int j = 100;
const int* p2 = &j;
Valoarea nu se poate schimba folosind pointerul Se poate schimba adresa referită
const int* p2 = &j;
cout << *p2 << "\n";
//change the memory address (valid) p2 = &i;
cout << *p2 << "\n";
//change the value (compiler error)
*p2 = 7;
cout << *p2 << "\n";
2 type * const
int * const p3 = &j;
Valoarea se poate schimba folosind acest pointer dar adressa de memorie referită nu se poate schimba
int * const p3 = &j;
cout << *p2 << "\n";
//change the memory address (compiler error) p3 = &i;
cout << *p3 << "\n";
//change the value (valid)
*p3 = 7;
cout << *p3 << "\n";
3 const type* const
const int * const p4 = &j;
Atât adresa cât și valoarea sunt constante
Paradigma de programare orientată-object
Este metodă de proiectare şi dezvoltare a programelor:
● Oferă o abstractizare puternică și flexibilă
● Programatorul poate exprima soluția în mod mai natural (se concentrează pe structura soluției nu pe structura calculatorului)
● Descompune programul într-un set de obiecte, obiectele sunt elementele de bază
● Obiectele interacționeaza pentru a rezolva problema, există relații între clase
● Tipuri noi de date modeleaza elemente din spațiul problemei, fiecare obiect este o instanța a unui tip de data (clasă)
Un obiect este o entitate care:
● are o stare
● poate executa anumite operații (comportament) Poate fi privit ca și o combinație de:
● date (atribute)
● metode Concepte:
● obiect
● clasă
● metodă (mesaj) Proprietăți:
● abstractizare
● încapsulare
● moștenire
● polimorfism
Caracteristici:
Încapsulare:
- capacitatea de a grupa date și comportament – controlul accesului la date/funcții,
– ascunderea implementării
– separare interfață de implementare
Moștenire
– Refolosirea codului
Polimorfism
– comportament adaptat contextului
– în funcție de tipul actual al obiectului se decide metoda apelată în
timpul execuției
Clase și obiecte în C++
Class: Un tip de dată definit de programator. Descrie caracteristicile unui lucru.
Grupează:
• date – atribute
• comportament – metode
Clasa este definită într-un fișier header (.h) Sintaxă:
/**
* Represent rational numbers */
class Rational { public:
//methods /**
* Add an integer number to the rational number */
void add(int val);
/**
* multiply with a rational number * r rational number
*/
void mul(Rational r);
private:
//fields (members) int a;
int b;
};
Definiții de metode
Metodele declarate în clasă sunt definite într-un fisier separat (.cpp)
Se foloseste operatorul :: (scope operator) pentru a indica apartanența metodei la clasă Similar ca și la module se separa declarațiile (interfața) de implementări
/**
* Add an integer number to the rational number */
void Rational::add(int val) { a = a + val * b;
}
Se pot defini metode direct in fișierul header. - metode inline class Rational {
public:
/**
* Return the numerator of the number */
int getNumerator() { return a;
} /**
* Get the denominator of the fraction */
int getDenominator() { return b;
} private:
//fields (members) int a;
int b;
Putem folosi metode inline doar pentru metode simple (fără cicluri)
Compilatorul inserează (inline) corpul metodei în fiecare loc unde se apelează metoda.
Obiect
Clasa descrie un nou tip de data.
Obiect - o instanța noua (o valoare) de tipul descris de clasă Declarație de obiecte
<nume_clasă> <identificator>;
• se alocă memorie suficientă pentru a stoca o valoare de tipul <nume_clasă>
• obiectul se inițializează apelând constructorul implicit (cel fără parametrii)
• pentru initializare putem folosi si constructori cu parametri (dacă în clasă am definit constructor cu argumente)
'
Rational r1 = Rational(1, 2);
Rational r2(1, 3);
Rational r3;
cout << r1.toFloat() << endl;
cout << r2.toFloat() << endl;
cout << r3.toFloat() << endl;
Acces la atribute (câmpuri) În interiorul clasei
int getDenominator() { return b;
}
Când implementăm metodele avem acces direct la attribute int getNumerator() {
return this->a;
}
Putem accesa atributul folosind pointerul this. Util daca mai avem variabile cu același nume în metodă (parametru, variabilă locală)
this: pointer la instanța curentă. Avem acces la acest pointer în toate metodele clasei, toate metodele membre din clasă au acces la this.
Putem accesa atributele și în afara clasei (dacă sunt vizibile)
• Folosind operatorul '.' object.field
• Folosind operatorul '->' dacă avem o referință (pointer) la obiect object_reference-
>field is a sau (*object reference).field