Salta al contenuto
Note per Studenti STL - algoritmi, predicati e lambda

STL - algoritmi, predicati e lambda

In questa pagina 7

La STL (STL - sequenze, iteratori e container (vector, list, string)La STL (Standard Template Library) fornisce contenitori (vector, list, map, ...) e algoritmi generici (sort, find, accumulate, ...) tenuti insieme da una interfaccia unificata: la sequenza, un intervallo [begin, end) di elementi gestito tramite iteratori (oggetti che identificano un elemento: * dereferenzia, ++ avanza, == e != confrontano; end è il primo elemento dopo l'ultimo, quindi una sequenza vuota ha begin == end). Gli algoritmi conoscono solo gli iteratori e i contenitori espongono solo iteratori, così i due mondi sono disaccoppiati e cambiare contenitore non cambia l'algoritmo. Un iteratore di lista è una classe annidata con un puntatore al nodo; auto deduce il tipo dell'iteratore. vector (contiguo, [] e at(), insert/erase costosi e che invalidano gli iteratori), string (un vector di char con + e +=), list (nodi concatenati, insert/erase senza spostare gli altri elementi, niente []) e char[] (array C: dimensione fissa, niente iteratori, == confronta i puntatori) a confronto.STL - sequenze, iteratori e container (vector, list, string) →) fornisce circa 80 algoritmi generici. Non serve conoscerli tutti: conoscerne alcuni abitua alla sintassi e permette di usare gli altri; e risparmia tempo e fatica rispetto a riscrivere cicli a mano. Sono compatibili con tutti i contenitori, perché operano su sequenze [first, last) descritte da iteratori. Si includono con #include <algorithm> (quelli numerici con #include <numeric>). Documentazione completa: cppreference.com, sezione algorithm.

Una selezione

Convenzioni: b, e sono gli iteratori di inizio e fine, v un valore, p un predicato, x un risultato.

algoritmo significato
r = find(b, e, v) r punta alla prima occorrenza di v in [b, e), o è e
r = find_if(b, e, p) r punta al primo elemento x in [b, e) con p(x) == true
x = count(b, e, v) numero di occorrenze di v in [b, e)
x = count_if(b, e, p) numero di elementi per cui p(x) == true
sort(b, e) ordina [b, e) con <
sort(b, e, p) ordina [b, e) con il criterio p
copy(b, e, b2) copia [b, e) in [b2, b2 + (e - b)); serve spazio a destinazione
unique_copy(b, e, b2) come copy, ma elimina i duplicati (consecutivi)
merge(b, e, b2, e2, r) fonde due sequenze ordinate in [r, r + (e - b) + (e2 - b2))
r = equal_range(b, e, v) sottosequenza dell'intervallo ordinato [b, e) che contiene v (ricerca binaria)

Quelli numerici:

algoritmo significato
equal(b, e, b2) confronta gli elementi di [b, e) e [b2, b2 + (e - b))
x = accumulate(b, e, i) x = i + somma degli elementi di [b, e)
x = accumulate(b, e, i, op) come sopra, ma con la somma calcolata con op
x = inner_product(b, e, b2, i) prodotto scalare di [b, e) e [b2, ...) più i (nelle slide la seconda sequenza è scritta [b2:b2+(e-i)], un refuso: la lunghezza è e - b)
x = inner_product(b, e, b2, i, op, op2) come sopra, con op e op2 al posto di + e *

Altri da conoscere: partial_sum, adjacent_difference, transform, for_each.

find

find cerca un elemento con un dato valore in una sequenza. L'implementazione è semplice ed efficiente:

cpp
template<typename In, typename T>
// In: iteratore di input; T deve essere confrontabile con *In
In find(In first, In last, const T& val)
{
    while (first != last && *first != val) ++first;
    return first;
}

La sequenza è definita da due iteratori [first, last). Il valore restituito è l'iteratore alla prima occorrenza trovata oppure end() (last) se il valore non c'è: in STL restituire end() indica "non trovato". find è generico rispetto al tipo di contenitore e al tipo dell'elemento, e la chiamata non cambia:

cpp
void f(vector<int>& v, int x)      { auto p = find(v.begin(), v.end(), x); if (p != v.end()) { /* trovato */ } }
void f(list<string>& v, string x)  { auto p = find(v.begin(), v.end(), x); if (p != v.end()) { /* trovato */ } }

È l'effetto dell'interfaccia standard gestita dagli iteratori, comune a tutti gli algoritmi.

find_if e i predicati

find confronta valori. find_if verifica che un criterio sia soddisfatto:

cpp
template<typename In, typename Pred>
In find_if(In first, In last, Pred pred)
{
    while (first != last && !pred(*first)) ++first;
    return first;
}

Definizione (predicato). Un predicato è una funzione (o un oggetto richiamabile) che restituisce true o false. Esempio: bool odd(int x) { return x % 2; }.

Primo modo: puntatore a funzione

Il predicato si passa con il solo nome: find_if(v.begin(), v.end(), odd). Il nome di una funzione senza parentesi è il puntatore a quella funzione (ereditato dal C: Ordinare e cercare in C - qsort, bsearch e puntatori a funzionePuntatori a funzione e funzioni di confronto; qsort per ordinare array di interi, stringhe e struct; bsearch per la ricerca binaria; dizionario ordinato su array dinamico di struct con inserimento (memmove e realloc) e ricerca in O(log n).Ordinare e cercare in C - qsort, bsearch e puntatori a funzione →); aggiungendo () la funzione sarebbe chiamata.

Passare un valore al criterio

Il criterio "maggiore di un dato valore" ha bisogno di un parametro (la soglia), che odd non aveva. Non lo si può passare come argomento, perché con un puntatore a funzione non esistono parentesi. La soluzione scadente è una variabile globale (v_val): funziona, ma il valore è modificabile da ovunque, non c'è incapsulamento ed è difficile tenerlo sotto controllo.

Secondo modo: function object

Definizione (function object). Un function object è un oggetto di una classe che ridefinisce l'operatore di chiamata operator() (function call operator), quindi si usa come una funzione. La soglia è un dato membro salvato dal costruttore.

cpp
class Larger_than {
    double v;
public:
    Larger_than(double vv) : v{vv} {}                 // salvataggio dell'argomento
    bool operator()(double x) const { return x > v; } // confronto
};

find_if(v.begin(), v.end(), Larger_than(31)) funziona così: il function object è creato nell'argomento, passato a find_if per copia, e dentro find_if l'espressione pred(*first) è tradotta in una chiamata a operator(). I function object sono una tecnica efficiente: per averne il massimo si passano per copia se sono piccoli (più di qualche byte, meglio per riferimento) e si lascia operator() inline; questa combinazione è più efficiente di una chiamata a funzione.

Terzo modo: lambda expression

Il function object ha una debolezza: è definito in una regione del codice e usato altrove. Una lambda expression è un modo per definire una funzione localmente, nel punto in cui serve: equivale a definire un function object, crearne uno al volo e passarlo, ma il tipo è anonimo e non riusabile altrove.

Un programma che mette a confronto i modi (eseguito):

predicati/predicati.cppcpp
#include <algorithm>
#include <iostream>
#include <list>
#include <vector>
using namespace std;

// ---- 1) predicato = funzione che restituisce bool ----
bool odd(int x) { return x % 2; }

// ---- 2) il criterio "maggiore di un valore" con una variabile globale (da evitare) ----
double v_val;
bool larger_than_v(double x) { return x > v_val; }

// ---- 3) function object: il valore e' un dato membro ----
class Larger_than {
    double v;
public:
    Larger_than(double vv) : v{vv} {}                 // salva l'argomento
    bool operator()(double x) const { return x > v; } // function call operator
};

// versione delle slide: operator()(int) -> converte i double in int (errore)
class Larger_than_int {
    int v;
public:
    Larger_than_int(int vv) : v{vv} {}
    bool operator()(int x) const { return x > v; }
};

int main()
{
    vector<int> vi{2, 4, 7, 8, 9};
    auto p = find_if(vi.begin(), vi.end(), odd);      // si passa il NOME della funzione (puntatore)
    cout << "primo dispari: " << *p << '\n';
    cout << "nessun dispari in [2,4): " << (find_if(vi.begin(), vi.begin() + 2, odd) == vi.begin() + 2) << '\n';

    list<double> ld{30.5, 31.5, 40.0};

    v_val = 31;                                        // scrittura sulla variabile globale
    auto q = find_if(ld.begin(), ld.end(), larger_than_v);
    cout << "globale, > 31: " << *q << '\n';

    auto r = find_if(ld.begin(), ld.end(), Larger_than(31));    // function object
    cout << "function object, > 31: " << *r << '\n';
    auto s = find_if(ld.begin(), ld.end(), Larger_than(35));
    cout << "function object, > 35: " << *s << '\n';

    auto t = find_if(ld.begin(), ld.end(), Larger_than_int(31));   // x convertito a int: 31.5 -> 31
    cout << "versione con int, > 31: " << *t << "  (31.5 e' stato scartato!)\n";

    int soglia = 35;
    auto u = find_if(ld.begin(), ld.end(), [soglia](double x) { return x > soglia; });   // lambda
    cout << "lambda, > soglia: " << *u << '\n';

    cout << "count(odd) = " << count_if(vi.begin(), vi.end(), odd)
         << ", count(9) = " << count(vi.begin(), vi.end(), 9) << '\n';
}

Uscita:

primo dispari: 7
nessun dispari in [2,4): 1
globale, > 31: 31.5
function object, > 31: 31.5
function object, > 35: 40
versione con int, > 31: 40  (31.5 e' stato scartato!)
lambda, > soglia: 40
count(odd) = 2, count(9) = 1

Si noti la riga con Larger_than_int. Nelle slide il function object è class Larger_than { int v; ... bool operator()(int x) const { return x>v; } } ed è usato con una list<double>: operator()(int) converte ogni double in int prima del confronto, quindi 31.5 diventa 31 e 31 > 31 è falso; find_if salta il 31.5 e restituisce 40. È un errore sottile (compila e non avvisa): il tipo del parametro di operator() deve essere quello degli elementi, qui double.

Lambda expression in dettaglio

Sintassi: [cattura](parametri) -> tipo_ritorno { corpo }. Le lambda accettano argomenti come le funzioni; il tipo restituito è opzionale (si deduce, a meno di ambiguità); possono catturare le variabili locali della funzione in cui sono definite, che è ciò che le rende comode.

clausola di cattura effetto
[&epsilon, zeta] epsilon per riferimento, zeta per valore (copia)
[&] tutte le variabili usate nella lambda per riferimento
[=] tutte le variabili usate nella lambda per valore
[&, epsilon] tutte per riferimento, ma epsilon per valore
[=, &epsilon] tutte per valore, ma epsilon per riferimento
[] nessuna cattura
lambda/lambda.cppcpp
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

void func5(vector<double>& v, const double& epsilon)
{
    transform(v.begin(), v.end(), v.begin(),
              [epsilon](double d) -> double {           // epsilon catturata per valore
                  if (d < epsilon) return 0;
                  else return d;
              });
}

int main()
{
    vector<double> v{0.001, 2.5, 0.0002, 7.0};
    func5(v, 0.01);
    for (double d : v) cout << d << ' ';
    cout << '\n';

    int a = 1, b = 10;
    auto per_valore = [=]() { return a + b; };          // copia al momento della creazione
    auto per_ref = [&]() { return a + b; };             // vede i valori correnti
    a = 100;
    cout << "per valore: " << per_valore() << ", per riferimento: " << per_ref() << '\n';

    int contatore = 0;
    for_each(v.begin(), v.end(), [&contatore](double) { ++contatore; });   // modifica la variabile locale
    cout << "contatore = " << contatore << '\n';

    int soglia = 5;
    auto prima = count_if(v.begin(), v.end(), [soglia](double d) { return d > soglia; });
    soglia = 1;                                         // la lambda ha la sua copia: non cambia
    auto dopo = count_if(v.begin(), v.end(), [soglia](double d) { return d > soglia; });
    cout << "maggiori di 5: " << prima << ", maggiori di 1 (nuova lambda): " << dopo << '\n';
}

Uscita eseguita:

0 2.5 0 7
per valore: 11, per riferimento: 110
contatore = 4
maggiori di 5: 1, maggiori di 1 (nuova lambda): 2

Che cosa mostra: func5 azzera i valori sotto epsilon (catturato per valore) con transform e -> double; con [=] la lambda ha la sua copia di a e b fatta quando viene creata, quindi dopo a = 100 restituisce ancora 11, mentre [&] vede il valore corrente (110); [&contatore] permette alla lambda di modificare una variabile locale; cambiare soglia dopo la creazione non altera la lambda che l'ha catturata per valore.

sort

sort ordina una sequenza. Ha due versioni:

cpp
template<typename Ran>
// Ran deve avere un random_access_iterator
void sort(Ran first, Ran last);                    // ordina con operator<

template<typename Ran, typename Cmp>
void sort(Ran first, Ran last, Cmp cmp);          // ordina con un criterio

Cmp è un comparison function object che deve soddisfare il requisito Compare: accetta due argomenti a, b, e il risultato (convertibile a bool) significa "a viene prima di b in questo ordinamento". Poiché sort ordina tra due iteratori, si può ordinare anche solo una parte di un contenitore: sort(v.begin(), v.begin() + v.size()/2) ordina la prima metà.

Richiede iteratori random access (vector, array, deque, string, array C): non funziona su list, che ha una propria list::sort(). La funzione sort(c) che ordina un contenitore intero, usata nelle slide, non è standard: è definita nel file std_lib_facilities.h del libro di testo (sort(c.begin(), c.end())). Con la libreria standard si scrive sort(c.begin(), c.end()) (in C++20 std::ranges::sort(c)).

Criteri di ordinamento

Ordinare un vector<Record> in base a un dato membro richiede un criterio per ciascun dato: due function object, oppure due lambda.

ordinamento/ordinamento.cppcpp
#include <algorithm>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Record {
    string name;
    char addr[24];
};

struct Cmp_by_name {
    bool operator()(const Record& a, const Record& b) const { return a.name < b.name; }
};
struct Cmp_by_addr {
    bool operator()(const Record& a, const Record& b) const { return strncmp(a.addr, b.addr, 24) < 0; }
};

Record make(const char* n, const char* a)
{
    Record r;
    r.name = n;
    strncpy(r.addr, a, sizeof(r.addr) - 1);
    r.addr[sizeof(r.addr) - 1] = '\0';
    return r;
}

void stampa(const char* titolo, const vector<Record>& vr)
{
    cout << titolo << ':';
    for (const Record& r : vr) cout << ' ' << r.name << '(' << r.addr << ')';
    cout << '\n';
}

int main()
{
    vector<Record> vr{make("Carla", "via Roma 3"), make("Anna", "via Dante 9"), make("Bruno", "via Alfieri 1")};

    sort(vr.begin(), vr.end(), Cmp_by_name());         // function object
    stampa("per nome", vr);
    sort(vr.begin(), vr.end(), Cmp_by_addr());
    stampa("per indirizzo", vr);

    sort(vr.begin(), vr.end(),                          // criterio per nome con una lambda
         [](const Record& a, const Record& b) { return a.name < b.name; });
    stampa("lambda per nome", vr);

    vector<int> v{9, 4, 7, 1, 8, 3};
    sort(v.begin(), v.begin() + v.size() / 2);          // solo la prima meta': 9 4 7 -> 4 7 9
    for (int x : v) cout << x << ' ';
    cout << '\n';
    sort(v.begin(), v.end(), [](int a, int b) { return a > b; });   // decrescente
    for (int x : v) cout << x << ' ';
    cout << '\n';
}

Uscita eseguita:

per nome: Anna(via Dante 9) Bruno(via Alfieri 1) Carla(via Roma 3)
per indirizzo: Bruno(via Alfieri 1) Anna(via Dante 9) Carla(via Roma 3)
lambda per nome: Anna(via Dante 9) Bruno(via Alfieri 1) Carla(via Roma 3)
4 7 9 1 8 3
9 8 7 4 3 1

L'indirizzo è un array di char, quindi il confronto lessicografico usa strncmp (header <cstring>, che le slide non nominano) e non <, che confronterebbe i puntatori. Le righe 4 7 9 1 8 3 e 9 8 7 4 3 1 mostrano l'ordinamento della sola prima metà di {9, 4, 7, 1, 8, 3} e poi di tutto in senso decrescente con a > b.

copy e copy_if

cpp
template<typename In, typename Out>
Out copy(In first, In last, Out res)
{
    while (first != last) { *res = *first; ++res; ++first; }
    return res;
}

Copia una sequenza in un'altra, anche di contenitori diversi. La sorgente è indicata con inizio e fine, la destinazione solo con l'inizio: sta al programmatore assicurarsi che ci sia abbastanza spazio (politica simile al range check del vector: non si fanno operazioni costose e non sempre necessarie). Se lo spazio non basta, il comportamento è indefinito.

cpp
void f(vector<double>& vd, list<int>& li)
{
    if (vd.size() < li.size()) error("Target container too small\n");
    copy(li.begin(), li.end(), vd.begin());
}

(error è la funzione di std_lib_facilities.h; in codice standard si lancia un'eccezione.) copy_if(b, e, b2, p) copia solo gli elementi per cui il predicato è vero, con la stessa sintassi più un quarto argomento. L'esempio delle slide vector<int> v2(v.size()); copy_if(v.begin(), v.end(), v2.begin(), Larger_than(6)); lascia in v2 tanti elementi quanti v, con in coda zeri non scritti; con un contenitore vuoto e back_inserter(v2) (header <iterator>) la destinazione cresce solo dei valori copiati.

algoritmi/algoritmi.cppcpp
#include <algorithm>
#include <functional>
#include <iostream>
#include <iterator>
#include <list>
#include <numeric>
#include <vector>
using namespace std;

template <typename C>
void stampa(const char* t, const C& c)
{
    cout << t << ':';
    for (const auto& x : c) cout << ' ' << x;
    cout << '\n';
}

int main()
{
    vector<int> v{5, 3, 5, 1, 3, 3, 8};
    cout << "find(8): posizione " << find(v.begin(), v.end(), 8) - v.begin()
         << ", find(99) == end: " << (find(v.begin(), v.end(), 99) == v.end()) << '\n';
    cout << "count(3) = " << count(v.begin(), v.end(), 3) << '\n';

    // copy: la destinazione deve avere SPAZIO
    list<int> l(v.size());                              // 7 elementi a 0
    copy(v.begin(), v.end(), l.begin());
    stampa("copy in list", l);

    vector<int> grandi;                                 // vuoto: si usa back_inserter
    copy_if(v.begin(), v.end(), back_inserter(grandi), [](int x) { return x > 3; });
    stampa("copy_if > 3 (back_inserter)", grandi);

    vector<int> v2(v.size());                           // come nelle slide: dimensione fissa
    copy_if(v.begin(), v.end(), v2.begin(), [](int x) { return x > 3; });
    stampa("copy_if in v2 gia' dimensionato", v2);      // gli elementi non scritti restano 0

    sort(v.begin(), v.end());
    stampa("sort", v);
    vector<int> senza;
    unique_copy(v.begin(), v.end(), back_inserter(senza));
    stampa("unique_copy", senza);

    vector<int> a{1, 4, 9}, b{2, 3, 10}, m(6);
    merge(a.begin(), a.end(), b.begin(), b.end(), m.begin());
    stampa("merge", m);

    auto er = equal_range(v.begin(), v.end(), 3);       // v e' ordinato: ricerca binaria
    cout << "equal_range(3): " << er.second - er.first << " elementi, dalla posizione " << er.first - v.begin() << '\n';

    cout << "equal(a,a) = " << equal(a.begin(), a.end(), a.begin()) << '\n';
    cout << "accumulate = " << accumulate(a.begin(), a.end(), 100) << " (100 + 1 + 4 + 9)\n";
    cout << "prodotto con op: " << accumulate(a.begin(), a.end(), 1, multiplies<int>()) << '\n';
    cout << "inner_product = " << inner_product(a.begin(), a.end(), b.begin(), 0) << " (1*2 + 4*3 + 9*10)\n";
}

Uscita eseguita:

find(8): posizione 6, find(99) == end: 1
count(3) = 3
copy in list: 5 3 5 1 3 3 8
copy_if > 3 (back_inserter): 5 5 8
copy_if in v2 gia' dimensionato: 5 5 8 0 0 0 0
sort: 1 3 3 3 5 5 8
unique_copy: 1 3 5 8
merge: 1 2 3 4 9 10
equal_range(3): 3 elementi, dalla posizione 1
equal(a,a) = 1
accumulate = 114 (100 + 1 + 4 + 9)
prodotto con op: 36
inner_product = 104 (1*2 + 4*3 + 9*10)

equal_range richiede la sequenza ordinata; unique_copy elimina solo i duplicati consecutivi, quindi toglie tutte le ripetizioni solo se la sequenza è ordinata (qui dopo il sort). accumulate con multiplies<int>() (header <functional>) calcola il prodotto invece della somma; inner_product somma i prodotti degli elementi corrispondenti.

Domande d'esame

1. Descrivere il concetto di function object, come è implementato, con un esempio. Qual è la relazione con il predicato? Traccia. Oggetto di una classe che ridefinisce operator(): si usa come una funzione e può portarsi dietro dati (la soglia) in membri. Un predicato è una funzione/oggetto che restituisce bool; un function object con operator() che restituisce bool è un predicato, per esempio class Larger_than { int v; public: Larger_than(int vv):v{vv}{} bool operator()(int x) const {return x>v;} }; usato in find_if(v.begin(), v.end(), Larger_than(31)).

2. Cosa fa std::sort con un terzo argomento? Scrivere il codice per ordinare un vector<Record> per nome con una lambda. Traccia. Il terzo argomento è il criterio (Compare): a precede b se restituisce true. sort(vr.begin(), vr.end(), [](const Record& a, const Record& b){ return a.name < b.name; });.

3. Differenza tra [=] e [&] in una lambda. Traccia. [=] copia le variabili usate (al momento della creazione, non modificabili senza mutable), [&] le cattura per riferimento e vede/modifica i valori correnti.

Versione ripasso

predicati/predicati.cppcpp
#include <algorithm>
#include <iostream>
#include <list>
#include <vector>
using namespace std;

// ---- 1) predicato = funzione che restituisce bool ----
bool odd(int x) { return x % 2; }

// ---- 2) il criterio "maggiore di un valore" con una variabile globale (da evitare) ----
double v_val;
bool larger_than_v(double x) { return x > v_val; }

// ---- 3) function object: il valore e' un dato membro ----
class Larger_than {
    double v;
public:
    Larger_than(double vv) : v{vv} {}                 // salva l'argomento
    bool operator()(double x) const { return x > v; } // function call operator
};

// versione delle slide: operator()(int) -> converte i double in int (errore)
class Larger_than_int {
    int v;
public:
    Larger_than_int(int vv) : v{vv} {}
    bool operator()(int x) const { return x > v; }
};

int main()
{
    vector<int> vi{2, 4, 7, 8, 9};
    auto p = find_if(vi.begin(), vi.end(), odd);      // si passa il NOME della funzione (puntatore)
    cout << "primo dispari: " << *p << '\n';
    cout << "nessun dispari in [2,4): " << (find_if(vi.begin(), vi.begin() + 2, odd) == vi.begin() + 2) << '\n';

    list<double> ld{30.5, 31.5, 40.0};

    v_val = 31;                                        // scrittura sulla variabile globale
    auto q = find_if(ld.begin(), ld.end(), larger_than_v);
    cout << "globale, > 31: " << *q << '\n';

    auto r = find_if(ld.begin(), ld.end(), Larger_than(31));    // function object
    cout << "function object, > 31: " << *r << '\n';
    auto s = find_if(ld.begin(), ld.end(), Larger_than(35));
    cout << "function object, > 35: " << *s << '\n';

    auto t = find_if(ld.begin(), ld.end(), Larger_than_int(31));   // x convertito a int: 31.5 -> 31
    cout << "versione con int, > 31: " << *t << "  (31.5 e' stato scartato!)\n";

    int soglia = 35;
    auto u = find_if(ld.begin(), ld.end(), [soglia](double x) { return x > soglia; });   // lambda
    cout << "lambda, > soglia: " << *u << '\n';

    cout << "count(odd) = " << count_if(vi.begin(), vi.end(), odd)
         << ", count(9) = " << count(vi.begin(), vi.end(), 9) << '\n';
}
  • Codice (lambda/lambda.cpp):
lambda/lambda.cppcpp
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

void func5(vector<double>& v, const double& epsilon)
{
    transform(v.begin(), v.end(), v.begin(),
              [epsilon](double d) -> double {           // epsilon catturata per valore
                  if (d < epsilon) return 0;
                  else return d;
              });
}

int main()
{
    vector<double> v{0.001, 2.5, 0.0002, 7.0};
    func5(v, 0.01);
    for (double d : v) cout << d << ' ';
    cout << '\n';

    int a = 1, b = 10;
    auto per_valore = [=]() { return a + b; };          // copia al momento della creazione
    auto per_ref = [&]() { return a + b; };             // vede i valori correnti
    a = 100;
    cout << "per valore: " << per_valore() << ", per riferimento: " << per_ref() << '\n';

    int contatore = 0;
    for_each(v.begin(), v.end(), [&contatore](double) { ++contatore; });   // modifica la variabile locale
    cout << "contatore = " << contatore << '\n';

    int soglia = 5;
    auto prima = count_if(v.begin(), v.end(), [soglia](double d) { return d > soglia; });
    soglia = 1;                                         // la lambda ha la sua copia: non cambia
    auto dopo = count_if(v.begin(), v.end(), [soglia](double d) { return d > soglia; });
    cout << "maggiori di 5: " << prima << ", maggiori di 1 (nuova lambda): " << dopo << '\n';
}
  • Codice (ordinamento/ordinamento.cpp):
ordinamento/ordinamento.cppcpp
#include <algorithm>
#include <cstring>
#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct Record {
    string name;
    char addr[24];
};

struct Cmp_by_name {
    bool operator()(const Record& a, const Record& b) const { return a.name < b.name; }
};
struct Cmp_by_addr {
    bool operator()(const Record& a, const Record& b) const { return strncmp(a.addr, b.addr, 24) < 0; }
};

Record make(const char* n, const char* a)
{
    Record r;
    r.name = n;
    strncpy(r.addr, a, sizeof(r.addr) - 1);
    r.addr[sizeof(r.addr) - 1] = '\0';
    return r;
}

void stampa(const char* titolo, const vector<Record>& vr)
{
    cout << titolo << ':';
    for (const Record& r : vr) cout << ' ' << r.name << '(' << r.addr << ')';
    cout << '\n';
}

int main()
{
    vector<Record> vr{make("Carla", "via Roma 3"), make("Anna", "via Dante 9"), make("Bruno", "via Alfieri 1")};

    sort(vr.begin(), vr.end(), Cmp_by_name());         // function object
    stampa("per nome", vr);
    sort(vr.begin(), vr.end(), Cmp_by_addr());
    stampa("per indirizzo", vr);

    sort(vr.begin(), vr.end(),                          // criterio per nome con una lambda
         [](const Record& a, const Record& b) { return a.name < b.name; });
    stampa("lambda per nome", vr);

    vector<int> v{9, 4, 7, 1, 8, 3};
    sort(v.begin(), v.begin() + v.size() / 2);          // solo la prima meta': 9 4 7 -> 4 7 9
    for (int x : v) cout << x << ' ';
    cout << '\n';
    sort(v.begin(), v.end(), [](int a, int b) { return a > b; });   // decrescente
    for (int x : v) cout << x << ' ';
    cout << '\n';
}

Esercizi su questo argomento

Teoria collegata