Array C-Style, Puntatori, Reference e Funzioni
Quasi tutto è ripasso del C, e lo segnaliamo con come in C. Le novità del C++ sono reference, nullptr, const reference, overloading e argomenti di default.
float v[n] con n variabile è lecito in C99, non nello standard C++.Importante
L’indice parte sempre da 0: v1[0], v1[1], v1[2].
Nota
float v[n] compila lo stesso: g++ e clang++ lo accettano come estensione. Con -Wpedantic g++ avvisa; clang++ recente avvisa anche senza opzioni.
Come in C, in esecuzione non c’è alcun controllo sui limiti dell’array: richiederebbe un test a ogni accesso.
#include <iostream>
int main() {
float vect[3] = {0.4, 1.34, 56.156};
1 for (int i = 0; i < 5; ++i) {
std::cout << "vect[" << i << "]: " << vect[i] << std::endl;
}
return 0;
}i = 4, ma l’ultimo elemento è vect[2].
Avviso
Si leggono le celle di memoria adiacenti (buffer over-read; scrivendo, over-write). È undefined behaviour: può stampare valori privi di significato (per esempio 9.18341e-41, -1.05467e+36) oppure terminare con Segmentation fault. Nella Lezione 08 vedremo l’AddressSanitizer, che rileva questi errori in esecuzione.
Come in C. In memoria è un unico blocco contiguo, riga dopo riga (row-major order).
#include <iostream>
int main() {
1 double matrix[2][3] = {
{1.0, 2.0, 3.0}, // riga 0
{4.0, 5.0, 6.0} // riga 1
};
2 std::cout << "Elemento (1, 2): " << matrix[1][2] << std::endl;
double sum = 0.0;
for (int i = 0; i < 2; ++i) {
for (int j = 0; j < 3; ++j) {
sum += matrix[i][j];
}
}
std::cout << "Somma: " << sum << std::endl; // stampa 21
return 0;
}matrix[riga][colonna]. Stampa 6: cout non mostra gli zeri finali di un double (per cambiare formato serve <iomanip>).
*), come in C, è una variabile che contiene l’indirizzo di un’altra variabile.&), novità del C++, è un alias: un altro nome per la stessa variabile.b è un puntatore: contiene l’indirizzo di a.
x è un reference: è un altro nome di a.
b, cioè a.
x, cioè ancora a: è lo stesso oggetto.
*).
*).
a.
Un puntatore può contenere un indirizzo non valido:
nullptr è il NULL del C++ (dal C++11): ha un tipo proprio (std::nullptr_t) e, a differenza di 0 o NULL, non si confonde con un intero (lo vedremo con l’overloading).
Segmentation fault, ma lo standard non lo garantisce.
In C++ si preferiscono i reference e si usano i puntatori solo quando necessario.
Reference (&) |
Puntatore (*) |
|
|---|---|---|
| Inizializzazione | Obbligatoria | Facoltativa (anche nullptr) |
| Riassegnazione | Mai: resta legato all’oggetto iniziale | Può ricevere un altro indirizzo |
| Sintassi | Come la variabile | Va dereferenziato (*p = 5) |
| Sicurezza | Mai nullo | Nullo o non valido: va controllato |
Quando serve un puntatore? Quando l’oggetto può non esistere (nullptr), o per gli array dinamici del C.
void.#include <iostream>
double calcola_energia_cinetica(double massa, double velocita) {
return 0.5 * massa * velocita * velocita;
}
void print_risultato(double res) {
std::cout << "Risultato: " << res << " J\n";
}
int main() {
double E = calcola_energia_cinetica(2.0, 10.0);
print_risultato(E);
return 0;
}Come in C, di default i parametri si passano per valore: la funzione riceve una copia, e le modifiche all’interno della funzione non si riflettono sulla variabile del chiamante.
10.
Il costo: passare per valore un oggetto grande, per esempio una matrice 1000×1000 di double, comporta la copia di 8 MB a ogni chiamata.
Con un reference (&) la funzione riceve un alias della variabile: nessuna copia, e le modifiche sono visibili al chiamante (serve anche a restituire più di un risultato). In C si usa un puntatore.
In C, con un puntatore:
Per passare un oggetto grande senza modificarlo si usa un const reference: & evita la copia, const (per esteso nella Lezione 06) vieta le modifiche.
struct come in C. In C++ il tipo si chiama semplicemente Strato, senza struct davanti.
const struct Strato* s.
assignment of read-only location 's.Strato::pesi[0]'.
Come in C, un array nativo non si può passare per valore: decade (decay) a un puntatore al suo primo elemento.
#include <iostream>
// Il parametro sembra un array, ma il compilatore lo tratta come:
// void stampa_array(int* array, int lunghezza)
1void stampa_array(int array[], int lunghezza) {
for (int i = 0; i < lunghezza; ++i) {
std::cout << array[i] << " ";
}
std::cout << std::endl;
}
int main() {
int dati[5] = {1, 2, 3, 4, 5};
2 stampa_array(dati, 5);
return 0;
}int* array.
&dati[0].
Come in C, il compilatore legge il file dall’alto verso il basso: una funzione va dichiarata prima del suo uso.
Sbagliato:
#include <iostream>
int main() {
1 print();
return 0;
}
void print() {
std::cout << "Ciao\n";
}error: 'print' was not declared in this scope. In C i compilatori meno recenti davano solo un avviso (implicit declaration); in C++ è sempre un errore.
Novità del C++ (in C due funzioni non possono avere lo stesso nome): più funzioni con lo stesso nome, purché la firma sia diversa, cioè i parametri in numero o tipo. Il solo tipo di ritorno non basta.
#include <iostream>
void stampa(int x) { std::cout << "Intero: " << x << std::endl; }
void stampa(double x) { std::cout << "Reale: " << x << std::endl; }
void stampa(int x, int y) { std::cout << "Coordinate: (" << x << ", " << y << ")\n"; }
int main() {
stampa(5); // stampa(int)
stampa(3.14); // stampa(double)
stampa(10, 20); // stampa(int, int)
return 0;
}Per questo nullptr è preferibile a NULL: con g(int) e g(int*), la chiamata g(NULL) è ambigua, mentre g(nullptr) sceglie g(int*).
Novità del C++: gli ultimi parametri possono avere un valore predefinito, usato quando la chiamata li omette.
#include <iostream>
#include <cmath>
1double calcola_distanza(double x, double y, double z = 0.0) {
return std::sqrt(x*x + y*y + z*z);
}
int main() {
std::cout << calcola_distanza(3.0, 4.0) << std::endl; // 2D: stampa 5
std::cout << calcola_distanza(3.0, 4.0, 12.0) << std::endl; // 3D: stampa 13
return 0;
}z è opzionale: se manca vale 0.0. Se c’è un prototipo, il default si scrive solo lì: ripeterlo nella definizione dà error: default argument given for parameter 3.
Avviso
I parametri con un default stanno sempre in fondo alla lista. Con void f(int a=1, int b); il compilatore segnala error: default argument missing for parameter 2 of 'void f(int, int)'.
Scrivete un programma che:
void normalizza(double& x, double& y), che riceve per reference le due componenti di un vettore e le divide per la sua lunghezza \(\sqrt{x^2 + y^2}\), così che il vettore abbia lunghezza 1;main definisca \(x = 3{,}0\) e \(y = 4{,}0\) e le stampi;normalizza e le stampi di nuovo. Cosa è cambiato, e perché?(Per std::sqrt() serve #include <cmath>.)
#include <iostream>
#include <cmath>
1void normalizza(double& x, double& y) {
double norma = std::sqrt(x*x + y*y);
if (norma > 0) {
x = x / norma;
y = y / norma;
}
}
int main() {
double x = 3.0;
double y = 4.0;
std::cout << "Prima: (" << x << ", " << y << ")\n";
2 normalizza(x, y);
std::cout << "Dopo : (" << x << ", " << y << ")\n";
return 0;
}& è necessario per modificare le variabili del main.
Dopo : (0.6, 0.8).
Due frammenti. Rispondete prima di passare alla slide successiva: in entrambi i casi la risposta più immediata è sbagliata.
1. Con le tre funzioni stampa della slide sull’overloading:
2.
1. Stampa Intero: 65. Non esiste una stampa(char): il char 'A' viene promosso a int, che ha la precedenza sulla conversione a double, e il suo codice ASCII è 65.
2. Stampa 2 2: r = b non lega r a b: copia in a il valore di b, perché un reference resta legato all’oggetto con cui è stato inizializzato. Poi b = 5 non tocca né a né r.