Invatare Atomica

Subprograme in contextul OOP: modularizare, overloading, lambda, transmitere prin referinta

Progres lectie:
0%
🎯

Obiectivul lectiei

La clasa XII, subprogramele sunt fundament pentru OOP: metodele clasei sunt subprograme specializate. Vei consolida modularizarea, vei intelege supraincarcarea functiilor (C++), expresiile lambda si functiile de ordin superior, si vei analiza transmiterea prin valoare vs. referinta — baze pentru design-ul claselor OOP.

Dupa aceasta lectie vei putea:

  • Sa explici legatura directa intre subprogram si metoda de clasa OOP (self/this ca parametru implicit)
  • Sa implementezi supraincarcarea functiilor in C++ si sa explici rezolutia la compilare
  • Sa scrii expresii lambda si sa le folosesti cu map, filter, sort ca predicate
  • Sa aplici functii de ordin superior (map, filter, reduce) pentru procesarea colectiilor
  • Sa analizezi complexitatea temporala si spatiala a recursivitatii si sa recunosti tail recursion
  • Sa distingi transmiterea by value vs by reference in C++ si sa alegi varianta corecta in design

Incearca singur!

Provocare cls. XII:

In Python, sorted([3,1,2]) sorteaza crescator. Cum ai sorta lista [{"n":"Ana","v":9.5},{"n":"Ion","v":7.2},{"n":"Maria","v":8.8}] descrescator dupa valoarea cheii "v", folosind o expresie lambda pe o singura linie?

💡 Ai nevoie de un indiciu?

Lambda primeste un element din lista (un dict) si returneaza valoarea dupa care sortezi. key=lambda d: d["v"] sorteaza crescator; adauga reverse=True sau neaga: lambda d: -d["v"]. Aceasta tehnica se regaseste in metodele de sortare din clasele OOP pe care le vei construi in lectiile urmatoare.

1

1. Ce este un subprogram si de ce il folosim?

Un subprogram (functie sau procedura) este un bloc de cod cu un nume propriu, care efectueaza o sarcina bine definita si poate fi apelat (executat) ori de cate ori este nevoie, fara a rescrie codul.
De ce modularizam codul?
  • Reutilizare: scrii o data, apelezi de 1000 de ori
  • Claritate: programul principal devine mai scurt si mai usor de citit
  • Depanare: un bug se repara intr-un singur loc, nu in 50 de copii
  • Testare: poti testa fiecare subprogram independent
Analogie sigura:

Un subprogram este ca o tasta de scurtatura pe un sistem de taste programabile: apesi o singura tasta si ea executa automat o secventa predefinita. Definesti secventa o singura data, o folosesti la infinit.

# Fara subprograme - cod repetitiv (problematic)
print("--- Bun venit la magazin ---")
# ... calcule ...
print("--- Bun venit la magazin ---")

# Cu subprograme - cod curat si reutilizabil
def antet():
    print("--- Bun venit la magazin ---")

antet()   # apel 1
antet()   # apel 2 - acelasi cod, fara duplicare
Output real (rulat cu python):
--- Bun venit la magazin ---
--- Bun venit la magazin ---
2

2. Anatomia unui subprogram: antet, corp, return

Un subprogram are trei parti structurale:
Antet (capul) — Corp (blocul de instructiuni) — Return (rezultatul returnat apelantului)
#        ANTET: cuvant cheie + NUME + parametri formali
def suma(a, b):
    #    CORP: instructiunile care se executa
    rezultat = a + b
    #    RETURN: valoarea trimisa inapoi apelantului
    return rezultat

# APEL: folosim numele + argumente actuale
r = suma(7, 3)
print("7 + 3 =", r)
print("20 + 15 =", suma(20, 15))
Output real (rulat cu python):
7 + 3 = 10
20 + 15 = 35
Terminologie esentiala:
  • Parametri formali = variabilele din definitie (a, b in exemplu)
  • Argumente actuale = valorile transmise la apel (7, 3 sau 20, 15)
  • Definitia = scriem subprogramul o singura data (blocul def)
  • Apelul = folosim subprogramul apelandu-l dupa nume
3

3. Functii vs Proceduri (void)

Functie = subprogram care calculeaza si returneaza un rezultat (are return valoare).
Procedura = subprogram care efectueaza o actiune fara a returna un rezultat util (Python: implicit None; C++: tip void).
# PROCEDURA: efectueaza o actiune, nu returneaza nimic
def afiseaza_bun_venit(nume):
    print(f"Bun venit, {nume}!")

# FUNCTIE: calculeaza si returneaza
def calculeaza_aria(baza, inaltime):
    aria = (baza * inaltime) / 2
    return aria

afiseaza_bun_venit("Ana")
afiseaza_bun_venit("Ion")
a = calculeaza_aria(6, 4)
print(f"Aria triunghiului: {a}")
Output real (rulat cu python):
Bun venit, Ana!
Bun venit, Ion!
Aria triunghiului: 12.0
Regula practica:

Daca subprogramul calculeaza ceva ce ai nevoie mai tarziu → functie cu return.
Daca subprogramul face ceva vizibil (afiseaza, scrie in fisier) → procedura fara return.

⚡ Sectiune EXCLUSIV INTENSIV — C++ antet complet

In C++ tipul returnat este obligatoriu in antet. Procedura foloseste void. Tipul este necesar pentru fiecare parametru.

#include <iostream>
using namespace std;

// void = procedura: nu returneaza nimic
void afiseazaBunVenit(string nume) {
    cout << "Bun venit, " << nume << "!" << endl;
}

// double = functie: returneaza un numar real
double calculeazaAria(double baza, double inaltime) {
    return (baza * inaltime) / 2.0;
}

int main() {
    afiseazaBunVenit("Ana");
    afiseazaBunVenit("Ion");
    double a = calculeazaAria(6, 4);
    cout << "Aria triunghiului: " << a << endl;
    return 0;
}
Output real (compilat g++ -std=c++17, rulat):
Bun venit, Ana!
Bun venit, Ion!
Aria triunghiului: 12
4

4. Variabile locale, globale si domeniu de vizibilitate

Variabila locala = declarata IN subprogram; exista doar cat timp subprogramul ruleaza; nu e vizibila din afara.
Variabila globala = declarata IN AFARA oricarei functii; vizibila din tot programul.
total = 0  # variabila GLOBALA

def adauga(x):
    global total   # declaram ca vrem sa modificam globalul
    total += x

def demo_local():
    mesaj = "sunt local"  # variabila LOCALA
    print(mesaj)
    # mesaj dispare cand functia se termina

adauga(5)
adauga(3)
print("Total:", total)
demo_local()
# print(mesaj)  # NameError: mesaj nu exista in afara functiei!
Output real (rulat cu python):
Total: 8
sunt local
Regula de bun simt:

Evita variabilele globale modificabile — creeaza dependente ascunse. Prefera sa transmiti date prin parametri si sa primesti rezultate prin return.

⚡ Sectiune EXCLUSIV INTENSIV — C++ variabile locale si globale

In C++ variabilele globale sunt declarate in afara oricarei functii. Variabilele locale sunt in interiorul blocului { }.

#include <iostream>
using namespace std;

int total = 0;   // GLOBALA

void adauga(int x) {
    total += x;
}

void demoLocal() {
    int lungime = 10;   // LOCALA
    cout << "Lungime locala: " << lungime << endl;
}

int main() {
    adauga(5); adauga(3);
    cout << "Total: " << total << endl;
    demoLocal();
    return 0;
}
Output real (compilat g++ -std=c++17, rulat):
Total: 8
Lungime locala: 10
5

5. Parametri cu valori implicite (default) EXCLUSIV INTENSIV — C++

Parametrii pot avea valori implicite: daca apelantul nu transmite valoarea, se foloseste cea din definitie. Aceasta reduce numarul de variante de apel necesare.

# Python: parametru cu valoare implicita
def putere(baza, exponent=2):
    return baza ** exponent

print(putere(3))       # 3^2 = 9 (exponent implicit)
print(putere(2, 10))   # 2^10 = 1024 (exponent explicit)
Output real (rulat cu python):
9
1024
⚡ Sectiune EXCLUSIV INTENSIV — C++ parametri default

In C++ parametrii cu valoare implicita trebuie sa fie la sfarsitul listei. Tipul fiecarui parametru se declara explicit.

#include <iostream>
using namespace std;

int putere(int baza, int exp = 2) {
    int rez = 1;
    for (int i = 0; i < exp; i++) rez *= baza;
    return rez;
}

int main() {
    cout << putere(3) << endl;
    cout << putere(2, 10) << endl;
    return 0;
}
Output real (compilat g++ -std=c++17, rulat):
9
1024
Comparatie Python vs C++ — antet:
Python:  def suma(a, b):               # fara tipuri explicite
C++:     int suma(int a, int b) { }   # cu tipuri obligatorii
6

6. Recursia: functia care se apeleaza pe sine EXCLUSIV INTENSIV — C++

Recursia = o functie care se apeleaza pe ea insasi cu un subproblem mai mic, pana atinge un caz de baza (conditia de oprire).
Exemplu clasic: factorial (n!)

n! = n x (n-1)!   si   0! = 1! = 1 (cazul de baza)

# Python: factorial recursiv
def factorial(n):
    if n <= 1:                    # caz de baza: OPRIRE
        return 1
    return n * factorial(n - 1)   # apel recursiv

print("5! =", factorial(5))
print("10! =", factorial(10))
Output real (rulat cu python):
5! = 120
10! = 3628800
Urmarire pas cu pas: factorial(3)
factorial(3)
  = 3 * factorial(2)
        = 2 * factorial(1)
              = 1  (caz de baza, OPRIRE)
        = 2 * 1 = 2
  = 3 * 2 = 6
⚡ Sectiune EXCLUSIV INTENSIV — C++ recursie

In C++ recursia functioneaza identic. Folosim long long pentru factorial mare (evitam overflow la int).

#include <iostream>
using namespace std;

long long factorial(int n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

int main() {
    cout << "5! = " << factorial(5) << endl;
    cout << "10! = " << factorial(10) << endl;
    return 0;
}
Output real (compilat g++ -std=c++17, rulat):
5! = 120
10! = 3628800
Complexitate:

Timp: O(n) — un apel per nivel.
Spatiu: O(n) — n cadre pe stiva de apel. La n mare: Stack Overflow.

Exercitii practice

Exercitiul 1 (Nivel minim) — Scrie o functie simpla

Scrie o functie Python numita perimetru_dreptunghi(lungime, latime) care returneaza perimetrul unui dreptunghi. Apeleaz-o cu valorile 5 si 3 si afiseaza rezultatul.

Asteptat: P = 2 * (5 + 3) = 16

Exercitiul 2 (Nivel standard) — Procedura si functie combinate

Scrie doua subprograme Python:

  • verifica_par(n) — functie care returneaza True daca n este par, False altfel
  • afiseaza_pari(lista) — procedura care afiseaza doar elementele pare dintr-o lista

Testeaza cu lista [1, 2, 3, 4, 5, 6, 7, 8]. Asteptat: 2 4 6 8

Exercitiul 3 (Nivel performanta) — Recursie si C++ intensiv

Implementeaza suma cifrelor unui numar recursiv in Python si C++ (intensiv):

  • Caz de baza: n < 10 → returneaza n
  • Pas recursiv: returneaza (n % 10) + suma_cifre(n // 10)

Testeaza cu n=1234. Asteptat: 1+2+3+4 = 10. Analizeaza de ce complexitatea este O(log n in baza 10).

Ce ai invatat astazi

  • Subprogramul = bloc de cod cu nume, reutilizabil; reduce duplicarea si creste claritatea
  • Anatomia: antet (def + nume + parametri formali) + corp + return
  • Functie = returneaza valoare; Procedura = efectueaza actiune (void in C++, None in Python)
  • Variabile locale (in functie, temporare) vs globale (in afara, vizibile oriunde)
  • Parametri impliciti (default) — valori folosite cand apelantul nu le transmite
  • Recursia = functie care se apeleaza pe sine; obligatoriu caz de baza + subproblem mai mic
  • C++ (intensiv): tipuri explicite la fiecare parametru si tip return; void pentru proceduri

Urmatoarea lectie

Continua cu Transmiterea parametrilor: by value vs by reference, variabile locale/globale aprofundat.

Continua →