Vai al contenuto

Dal C al C++/Utilizzo basilare di librerie/Le funzioni standard

Wikibooks, manuali e libri di testo liberi.
Indice del libro

La libreria standard del C++ contiene numerose funzioni che non sono membro di qualche classe. Ecco un programma che mostra l'uso delle funzioni più importanti ("swap", "min", "max", "abs"):

#include <iostream>
#include <cstring>
#include <cmath>
using namespace std;
int main() {
   double x = -3.2;
   cout << abs(x) << "\n";
   int a = 5;
   int b = 7;
   swap(a, b);
   cout << a << ", " << b << "; "
      << min(a, b) << ", " << max(a, b) << "\n";
   string s = "x";
   string t = "abc";
   swap(s, t);
   cout << s << ", " << t << "; "
      << min(s, t) << ", " << max(s, t) << "\n";
}

Questo programma stampa le seguenti righe:

3.2
7, 5; 5, 7
abc, x; abc, x

Le funzioni "swap", "min" e "max" sono chiamate due volte; la prima volta ricevono due variabili di tipo "int", mentre la seconda volta due variabili di tipo "string". Ovviamente, in un linguaggio a tipizzazione statica come il C++, la medesima funzione non può accettare come parametri valori di vari tipi, a meno che esista una conversione implicita dal tipo del valore al tipo del parametro. Siccome non esiste una conversione implicita né dal tipo "int" al tipo "string", né dal tipo "string" al tipo "int", né da entrambi i tipi a un terzo tipo, si deduce che esistono più versioni delle funzioni citate.

Tale molteplicità di versioni si può ottenere con i template di funzione, oppure con le funzioni sovraccaricate. In effetti, "min" e "max" sono template di funzione, "abs" è un insieme di funzioni sovraccaricate, mentre "swap" è un insieme di template di funzioni e di funzioni sovraccaricate. Pertanto, invece che parlare di funzioni sarebbe più corretto parlare di famiglie di funzioni.

In seguito vengono ripetute e analizzate porzioni del programma mostrato sopra.

Le famiglie di funzioni matematiche

[modifica | modifica sorgente]

Si consideri questo programma:

#include <iostream>
#include <cmath>
using namespace std;
int main() {
   double x = -3.2;
   cout << abs(x) << "\n";
}

Questo programma stampa "3.2". La chiamata ad "abs" prende come parametro un numero non intero ("-3.2") e rende un numero non intero ("3.2").

Nella libreria standard del C esiste una funzione chiamata "abs", ma prende un "int" e rende un "int". Passandogli un valore "double", questo viene prima troncato a "int" e poi gli viene tolto il segno. Pertanto, in linguaggio C, dopo aver eseguito l'istruzione "double a = abs(-3.2)", la variabile "a" vale "3.".

Questo è uno dei rari casi in cui la compatibilità tra il C e il C++ è solo parziale. Infatti, in linguaggio C++, dopo aver eseguito l'istruzione appena citata, la variabile "a" vale "3.2".

In linguaggio C, per ottenere lo stesso risultato, si deve usare la funzione "fabs", che prende un "double" e rende un "double". In C++, la funzione "fabs" è stata mantenuta per compatibilità, ma è considerata obsoleta, in quanto la nuova famiglia di funzioni "abs" si applica a tutti i tipi di dati numerici.

Nel file di intestazione "cmath" sono dichiarate numerose versioni sovraccaricate di "abs". C'è la versione che prende un "int" e rende un "int" ereditata dal C, ma ce ne sono altre quattro: una che prende un "long" e rende un "long", una che prende un "float" e rende un "float", una che prende un "double" e rende un "double", e una che prende un "long double" e rende un "long double".

Non ci sono versioni per "char", e "short", quindi tali valori vengono implicitamente convertiti in "int".

Inoltre, nel file di intestazione "complex" c'è un'altra versione di "abs", che prende un numero complesso e rende un altro numero complesso.

Questa molteplicità di versioni della stessa funzione per tipi diversi non è esclusiva della funzione "abs". Per molte altre funzioni matematiche ereditate dal C, come "sqrt", "floor", "pow", che in linguaggio C gestivano solamente numeri "double", in C++ esistono versioni per vari tipi.

Avere più versioni della stessa funzione consente di ottimizzare le prestazioni per il tipo di dato da elaborare.

La famiglia di funzioni "min" e "max"

[modifica | modifica sorgente]

Si consideri questo programma:

#include <iostream>
#include <string>
using namespace std;
int main() {
   int a = 5;
   int b = 7;
   cout << min(a, b) << ", " << max(a, b) << "\n";
   string s = "x";
   string t = "abc";
   cout << min(s, t) << ", " << max(s, t) << "\n";
}

Questo programma stampa le seguenti righe:

5, 7
abc, x

Le prime chiamate a "min" e a "max" ricevono due valori di tipo "int", e, da come risulta in stampa, rendono un "int". Le seconde chiamate a "min" e a "max" ricevono due valori di tipo "string", e, da come risulta in stampa, rendono un oggetto di tipo "string".

Per ottenere questo effetto è bastato definirle come template di funzioni. Qualunque sia il tipo dei loro parametri, queste funzioni rendono un valore dello stesso tipo.

Le uniche condizioni da rispettare sono due:

  • I due parametri devono essere dello stesso tipo.
  • Deve essere definito l'operatore "<" tra i parametri.

La seconda condizione si applica sia a numeri interi che a oggetti "string", ma non a tutti gli oggetti. Si consideri il seguente programma:

#include <iostream>
#include <cmath>
#include <complex>
using namespace std;
int main() {
   complex<double> a, b;
   //cout << std::min(a, b) << "\n"; // Illegale
   const char *p1 = "x1";
   const char *p2 = "abc";
   const char *p3 = "x2";
   cout << std::min(p1, p2) << " " << std::min(p2, p3);
}

Prima di tutto si noti che è stata commentata la seconda riga della funzione "main", in quanto illegale. La funzione "min" non si può applicare ai numeri complessi, in quanto tra di essi non è definito l'operatore "<".

Il resto del programma stampa qualcosa, che però non è ben definito dal linguaggio. Probabilmente è "x1 abc", oppure "abc x2".

Passare dei puntatori a carattere alla funzione "min", non significa passarle delle stringhe; significa passarle dei puntatori. La funzione rende quello dei due puntatori avente valore inferiore. Per valore di un puntatore, si intende il suo indirizzo di memoria.

Se, come è ragionevole, il compilatore alloca le stringhe letterali nell'ordine con cui le trova, in memoria ci sarà qualcosa del genere: "x1(NUL)abc(NUL)x2(NUL)", dove l'espressione "(NUL)" indica lo zero binario. Se invece, come pure è ragionevole, il compilatore alloca le stringhe letterali nell'ordine inverso, in memoria ci sarà qualcosa del genere: "x2(NUL)abc(NUL)x1(NUL)". In entrambi i casi si ha che il puntatore "p2" ha un valore intermedio tra quello di "p1" e quello di "p2", per cui risulterà minore di una e maggiore dell'altro. Ma non si possono del tutto escludere altri risultati.

La famiglia di funzioni "swap"

[modifica | modifica sorgente]

Si consideri questo programma:

#include <iostream>
#include <string>
using namespace std;
int main() {
   int a = 5;
   int b = 7;
   swap(a, b);
   cout << a << ", " << b << "\n";
   string s = "x";
   string t = "abc";
   swap(s, t);
   cout << s << ", " << t << "\n";
}

Questo programma stampa le seguenti righe:

7, 5
abc, x

La prima chiamata a "swap" scambia il contenuto delle variabili "a" e "b". Infatti, quando viene stampato prima "a" e poi "b", viene emesso "7, 5", che sono i valori originali scambiati.

Un funzione di libreria non può modificare dei valori esterni ricevendone solo una copia; deve ricevere l'indirizzo o il riferimento a tali valori. Come si vede, nella chiamata a "swap" non vengono passati indirizzi, quindi entrambi i parametri devono essere passati per riferimento.

Per scambiare il contenuto di due oggetti, la funzione swap deve copiare un numero di bit che dipende dalla dimensione degli oggetti stessi. Per poter funzionare correttamente, deve essere un template che viene istanziato per ogni tipo di oggetto per cui viene chiamato.

In realtà, la sola definizione di un temlplate introdurrebbe delle inefficienze nei casi in cui la copia dell'oggetto è costosa. Per esempio, se passando a "swap" due oggetti di tipo "vector" venissero copiati gli interi vettori di dati, l'operazione sarebbe terribilmente lenta. Pertanto, per alcune classe, per esempio per "vector" e "string", sono definite nella libreria standard delle versioni sovraccaricate di "swap", che sono molto efficienti.