Struttura di un Programma, Tipi, Scope e Namespace
g++ installato di defaultCos’è il preprocessore? Cosa fa?
iostream verrà incluso prima della compilazione di questo codice!
Sostituisce le direttive dell’utente con il codice sorgente richiesto (Solo pre-compilazione)
I commenti sono preceduti da // Possono iniziare ovunque nel programma, sia all’inizio della riga che subito dopo un’istruzione a metà della riga
In questo corso useremo il compilatore gratuito g++ in tutti gli esempi
-o Welcome: Nome dell’eseguibile (output binario)Welcome.cpp: File C++ da compilare e linkare;
//
mainmain deve restituire un int (intero)
Il compilatore richiede che il main restituisca un valore int!
iostream fornisce funzionalità di output al tuo programma
iostream fornisce anche funzionalità di input al tuo programma
// BadCode1.cpp
#include <iostream>
using namespace std;
int main() { // il main inizia qui
int nIterations;
cout << "How many
iterations? "; // impossibile andare a capo in mezzo a una stringa!
cin >> nIteration; // nome errato! manca la 's' finale
// stampa il messaggio sullo STDOUT
cout << "Numero di iterazioni richieste: " << nIterations << endl;
return 0 // manca il ; !
} // fine del main// SimpleVars.cpp
#include <iostream>
using namespace std;
int main() {
int samples; // solo dichiarazione
int events = 0; // dichiarazione e assegnazione
samples = 123; // assegnazione
cout << "Quanti samples? " ;
cin >> samples; // assegnazione tramite I/O
cout << "samples: " << samples
<< "\t" // inserisce un carattere di tabulazione (tab) nella stampa
<< "events: " << events
<< endl;
return 0;
} // fine del mainWarning
Lo sapete già dal C. Lo ripetiamo perché, nel calcolo scientifico, è il bug che costa più ore di debug di qualunque altro — e non dà nessun errore.
Se entrambi gli operandi sono interi, / è la divisione intera: 5 / 7 fa 0, con resto scartato. Il risultato viene poi convertito in double, ma il danno è già fatto. Il programma compila, gira, e stampa un’efficienza pari a zero.
Altri casi che vi capiteranno:
Basta che uno dei due operandi sia double: C++ promuove automaticamente anche l’altro, e la divisione diventa reale.
Tre regole pratiche:
1.0, non 1. Costa nulla e toglie ogni ambiguità.static_cast<double>(x): è esplicito, si cerca con grep, e non modifica x — produce un temporaneo.(double) x funziona ma è da evitare: non si distingue dalle conversioni pericolose fra puntatori, che vedremo nella Lezione 16.Tip
Nel Laboratorio 02, questa stessa settimana, vi chiederemo di far vedere l’errore a schermo con 5 / 2. Sembra banale: è il modo migliore per non dimenticarsene mai più.
cinWarning
Cosa succede se inseriamo una stringa invece di un numero? Dal C++11 >> azzera la variabile e alza un flag di errore. Il programma non si accorge di niente e prosegue con iters = 0: nessun avviso, nessun crash, un numero plausibile. È peggio di un valore assurdo, che almeno si nota.
int main() {
1 int iters;
cout << "iters prima: " << iters << endl;
cout << "iterazioni? ";
cin >> iters;
cout << "richieste " << iters << endl;
return 0;
}iters, e la spazzatura sparisce.
cinint, l’operazione fallisce silenziosamente.
.fail() per intercettare l’errore e gestirlo!
{ }.error esiste solo all’interno di compute_loss().
prediction esiste in tutto il main().
error non è in questo scope!
Cosa succede se dichiariamo una variabile con lo stesso nome in uno scope nidificato? La variabile più interna “nasconde” quella esterna!
x “nasconde” temporaneamente la x esterna!
x interna viene distrutta per sempre.
print().::#include <cmath>
namespace neural {
// la sigmoide: quella che useremo davvero nella rete
double activate(double x) { return 1.0 / (1.0 + std::exp(-x)); }
}
namespace math {
// la tangente iperbolica: un'altra scelta possibile
double activate(double x) { return std::tanh(x); }
}
int main() {
// Usa "::" (Operatore di Risoluzione di Scope)
1 double out1 = neural::activate(-2.5);
2 double out2 = math::activate(-2.5);
return 0;
}activate del namespace neural: vale 0.076.
math: vale −0.987. Stesso nome, funzioni diverse, nessuna collisione.
Note
L’operatore :: si chiama Scope Resolution Operator. Lo vediamo sempre in std::cout, perché cout appartiene al namespace std.
using namespacestd disponibili senza dover scrivere std::.
cout ed endl fanno parte di std.
using multipli!Creare ambiguità è facilissimo:
using namespace math;
using namespace neural;
using namespace std;
int main() {
double input = 3.5;
// Ambiguo! Di quale metodo activate() stiamo parlando?
1 double out1 = activate(input);
return 0;
}call of overloaded 'activate(double&)' is ambiguous — con la reference, perché input è una variabile e non una costante.
Important
Regola d’oro: Mai inserire using namespace std; in un file header (.h o .hpp). Potreste inquinare i file di chi usa la vostra libreria!
Tre frammenti, un minuto ciascuno. Rispondete prima di girare la slide: in tutti e tre la risposta istintiva è sbagliata.
1. L’utente digita ciao, non un numero.
2.
3.
1. Stampa 0, non 42. Dal C++11 una lettura fallita azzera la variabile: il valore che c’era prima è perso. Se vi serve, controllate cin.fail() prima di usare n.
2. Stampa 2 2.5. Nel primo caso a / b è già stato calcolato fra interi: il cast arriva troppo tardi e promuove un 2 in cui non c’è più niente da recuperare. Il cast va messo su un operando, mai sul risultato.
3. Stampa 0, non 3. L’i dichiarato nella testa del for vive solo dentro il ciclo e ombreggia quello esterno, che nessuno ha mai toccato. È per questo che esiste l’opzione -Wshadow: -Wall da sola non dice niente.