22 KiB
language | filename | contributors | translators | lang | |||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
c++ | learncpp.cpp |
|
|
it-it |
Il C++ è un linguaggio di programmazione il quale, secondo il suo inventore Bjarne Stroustrup, è stato progettato per
- essere un "miglior C"
- supportare l'astrazione dei dati
- supportare la programmazione orientata agli oggetti
- supportare la programmazione generica
Nonostante la sintassi possa risultare più difficile o complessa di linguaggi più recenti, è usato in maniera vasta poichè viene compilato in istruzioni macchina che possono essere eseguite direttamente dal processore ed offre un controllo stretto sull'hardware (come il linguaggio C) ed allo stesso tempo offre caratteristiche ad alto livello come i generici, le eccezioni, e le classi. Questa combinazione di velocità e funzionalità rende il C++ uno dei più utilizzati linguaggi di programmazione.
//////////////////
// Confronto con il C
//////////////////
// Il C++ è _quasi_ un superset del C e con esso condivide la sintassi di base per
// la dichiarazione di variabili, tipi primitivi, e funzioni.
// Proprio come nel C, l'inizio del programma è una funzione chiamata
// main con un intero come tipo di ritorno,
// Questo valore serve come stato d'uscita del programma.
// Vedi http://it.wikipedia.org/wiki/Valore_di_uscita per maggiori informazioni.
int main(int argc, char** argv)
{
// Gli argomenti a linea di comando sono passati tramite argc e argv così come
// avviene in C.
// argc indica il numero di argomenti,
// e argv è un array di stringhe in stile-C (char*)
// che rappresenta gli argomenti.
// Il primo argomento è il nome che è stato assegnato al programma.
// argc e argv possono essere omessi se non hai bisogno di argomenti,
// in questa maniera la funzione avrà int main() come firma.
// Lo stato di uscita 0 indica successo.
return 0;
}
// Tuttavia, il C++ varia nei seguenti modi:
// In C++, i caratteri come letterali sono da un byte.
sizeof('c') == 1
// In C, i caratteri come letterali sono della stessa dimensione degli interi.
sizeof('c') == sizeof(10)
// C++ ha prototipizzazione rigida
void func(); // funziona che non accetta argomenti
// In C
void func(); // funzione che può accettare un qualsiasi numero di argomenti
// Usa nullptr invece di NULL in C++
int* ip = nullptr;
// Gli header C standard sono disponibili in C++,
// ma sono prefissati con "c" e non hanno il suffisso ".h".
#include <cstdio>
int main()
{
printf("Ciao, mondo!\n");
return 0;
}
///////////////////////////////
// Overloading per le funzioni
//////////////////////////////
// Il C++ supporta l'overloading per le funzioni
// sia dato che ogni funzione accetta parametri diversi.
void print(char const* myString)
{
printf("Stringa %s\n", myString);
}
void print(int myInt)
{
printf("Il mio int è %d", myInt);
}
int main()
{
print("Ciao"); // Viene chiamata void print(const char*)
print(15); // Viene chiamata void print(int)
}
////////////////////////
// Argomenti di default
///////////////////////
// Puoi fornire argomenti di default per una funzione
// se non sono forniti dal chiamante.
void faiQualcosaConInteri(int a = 1, int b = 4)
{
// fai qualcosa con gli interi qui
}
int main()
{
faiQualcosaConInteri(); // a = 1, b = 4
faiQualcosaConInteri(20); // a = 20, b = 4
faiQualcosaConInteri(20, 5); // a = 20, b = 5
}
// Gli argomenti di default devono essere alla fine della lista degli argomenti.
void dichiarazioneInvalida(int a = 1, int b) // Errore!
{
}
/////////////
// Namespaces
/////////////
// I namespaces forniscono visibilità separata per dichiarazioni di variabili, funzioni,
// ed altro.
// I namespaces possono essere annidati.
namespace Primo {
namespace Annidato {
void foo()
{
printf("Questa è Primo::Annidato::foo\n");
}
} // fine di namespace Annidato
} // fine di namespace Primo
namespace Secondo {
void foo()
{
printf("Questa è Secondo::foo\n")
}
}
void foo()
{
printf("Questa è foo globale\n");
}
int main()
{
// Assume che tutto venga dal namespace "Secondo"
// a meno che non venga dichiarato altrimenti.
using namespace Secondo;
foo(); // stampa "Questa è Secondo::foo"
Primo::Annidato::foo(); // stampa "Questa è Primo::Annidato::foo"
::foo(); // stampa "Questa è foo globale"
}
///////////////
// Input/Output
///////////////
// L'input e l'output in C++ utilizza gli streams
// cin, cout, e cerr i quali rappresentano stdin, stdout, e stderr.
// << è l'operatore di inserzione >> è l'operatore di estrazione.
#include <iostream> // Include gli streams di I/O
using namespace std; // Gli streams sono nel namespace std (libreria standard)
int main()
{
int myInt;
// Stampa su stdout (o terminalee/schermo)
cout << "Inserisci il tuo numero preferito:\n";
// Prende l'input
cin >> myInt;
// cout può anche essere formattato
cout << "Il tuo numero preferito è " << myInt << "\n";
// stampa "Il tuo numero preferito è <myInt>"
cerr << "Usato per messaggi di errore";
}
////////////
// Stringhe
///////////
// Le stringhe in C++ sono oggetti ed hanno molte funzioni membro
#include <string>
using namespace std; // Anche le stringhe sono contenute nel namespace std (libreria standard)
string myString = "Ciao";
string myOtherString = " Mondo";
// + è usato per la concatenazione.
cout << myString + myOtherString; // "Ciao Mondo"
cout << myString + " Bella"; // "Ciao Bella"
// le stringhe in C++ possono essere modificate.
myString.append(" Mario");
cout << myString; // "Ciao Mario"
///////////////
// Riferimenti
//////////////
// Oltre ai puntatori come quelli in C,
// il C++ ha i _riferimenti_.
// Questi non sono tipi puntatori che non possono essere riassegnati una volta settati
// e non possono essere null.
// Inoltre, essi hanno la stessa sintassi della variabile stessa:
// * non è necessario per la dereferenziazione e
// & ("indirizzo di") non è usato per l'assegnamento.
using namespace std;
string foo = "Io sono foo";
string bar = "Io sono bar";
string& fooRef = foo; // Questo crea un riferimento a foo.
fooRef += ". Ciao!"; // Modifica foo attraverso il riferimento
cout << fooRef; // Stampa "Io sono foo. Ciao!"
// Non riassegna "fooRef". Questo è come scrivere "foo = bar", e
// foo == "Io sono bar"
// dopo questa riga.
fooRef = bar;
const string& barRef = bar; // Crea un riferimento const a bar.
// Come in C, i valori const (i puntatori e i riferimenti) non possono essere modificati.
barRef += ". Ciao!"; // Errore, i riferimenti const non possono essere modificati.
//////////////////////////////////////////////////
// Classi e programmazione orientata agli oggetti
/////////////////////////////////////////////////
// Primo esempio delle classi
#include <iostream>
// Dichiara una classe.
// Le classi sono in genere dichiara in un header file (.h o .hpp).
class Cane {
// Variabili e funzioni membro sono private di default.
std::string nome;
int peso;
// Tutti i membri dopo questo sono pubblici (public)
// finchè "private:" o "protected:" non compaiono.
public:
// Costruttore di default
Cane();
// Dichiarazioni di funzioni membro (le implentazioni sono a seguito)
// Nota che stiamo usando std::string invece di porre
// using namespace std;
// sopra.
// Mai usare uno statement "using namespace" in uno header.
void impostaNome(const std::string& nomeCane);
void impostaPeso(int pesoCane);
// Le funzioni che non modificano lo stato dell'oggetto
// dovrebbero essere marcate come const.
// Questo permette di chiamarle con un riferimento const all'oggetto.
// Inoltre, nota che le funzioni devono essere dichiarate espliciamente come _virtual_
// per essere sovrascritte in classi derivate.
// Le funzioni non sono virtual di default per motivi di performance.
virtual void print() const;
// Le funzioni possono essere definite anche all'interno del corpo della classe.
// Le funzioni definite in questo modo sono automaticamente inline.
void abbaia() const { std::cout << nome << " abbaia!\n"; }
// Assieme con i costruttori, il C++ fornisce i distruttori.
// Questi sono chiamati quando un oggetto è rimosso o esce dalla visibilità.
// Questo permette paradigmi potenti come il RAII
// (vedi sotto)
// I distruttori devono essere virtual per permettere a classi di essere derivate da questa.
virtual ~Dog();
}; // Un punto e virgola deve seguire la definizione della funzione
// Le funzioni membro di una classe sono generalmente implementate in files .cpp .
void Cane::Cane()
{
std::cout << "Un cane è stato costruito\n";
}
// Gli oggetti (ad esempio le stringhe) devono essere passati per riferimento
// se li stai modificando o come riferimento const altrimenti.
void Cane::impostaNome(const std::string& nomeCane)
{
nome = nomeCane;
}
void Cane::impostaPeso(int pesoCane)
{
peso = pesoCane;
}
// Notare che "virtual" è solamente necessario nelle dichiarazioni, non nelle definizioni.
void Cane::print() const
{
std::cout << "Il cane è " << nome << " e pesa " << peso << "kg\n";
}
void Cane::~Cane()
{
cout << "Ciao ciao " << nome << "\n";
}
int main() {
Cane myDog; // stampa "Un cane è stato costruito"
myDog.impostaNome("Barkley");
myDog.impostaPeso(10);
myDog.print(); // stampa "Il cane è Barkley e pesa 10 kg"
return 0;
} // stampa "Ciao ciao Barkley"
// Ereditarietà:
// Questa classe eredita tutto ciò che è public e protected dalla classe Cane
class MioCane : public Cane {
void impostaProprietario(const std::string& proprietarioCane)
// Sovrascrivi il comportamento della funzione print per tutti i MioCane. Vedi
// http://it.wikipedia.org/wiki/Polimorfismo_%28informatica%29
// per una introduzione più generale se non sei familiare con
// il polimorfismo.
// La parola chiave override è opzionale ma fa sì che tu stia effettivamente
// sovrascrivendo il metodo nella classe base.
void print() const override;
private:
std::string proprietario;
};
// Nel frattempo, nel file .cpp corrispondente:
void MioCane::impostaProprietario(const std::string& proprietarioCane)
{
proprietario = proprietarioCane;
}
void MioCane::print() const
{
Cane::print(); // Chiama la funzione print nella classe base Cane
std::cout << "Il cane è di " << proprietario << "\n";
// stampa "Il cane è <nome> e pesa <peso>"
// "Il cane è di <proprietario>"
}
///////////////////////////////////////////////////
// Inizializzazione ed Overloading degli Operatori
//////////////////////////////////////////////////
// In C++ puoi sovrascrivere il comportamento di operatori come +, -, *, /, ecc...
// Questo è possibile definendo una funzione che viene chiamata
// ogniqualvolta l'operatore è usato.
#include <iostream>
using namespace std;
class Punto {
public:
// Così si assegna alle variabili membro un valore di default.
double x = 0;
double y = 0;
// Definisce un costruttore di default che non fa nulla
// ma inizializza il Punto ai valori di default (0, 0)
Punto() { };
// La sintassi seguente è nota come lista di inizializzazione
// ed è il modo appropriato di inizializzare i valori membro della classe
Punto (double a, double b) :
x(a),
y(b)
{ /* Non fa nulla eccetto inizializzare i valori */ }
// Sovrascrivi l'operatore +.
Punto operator+(const Punto& rhs) const;
// Sovrascrivi l'operatore +=
Punto& operator+=(const Punto& rhs);
// Avrebbe senso aggiungere gli operatori - e -=,
// ma li saltiamo per rendere la guida più breve.
};
Punto Punto::operator+(const Punto& rhs) const
{
// Crea un nuovo punto come somma di questo e di rhs.
return Punto(x + rhs.x, y + rhs.y);
}
Punto& Punto::operator+=(const Punto& rhs)
{
x += rhs.x;
y += rhs.y;
return *this;
}
int main () {
Punto su (0,1);
Punto destro (1,0);
// Questo chiama l'operatore + di Punto
// Il Punto su chiama la funzione + con destro come argomento
Punto risultato = su + destro;
// Stampa "Risultato è spostato in (1,1)"
cout << "Risultato è spostato (" << risultato.x << ',' << risultato.y << ")\n";
return 0;
}
/////////////////
// Templates
////////////////
// Generalmente i templates in C++ sono utilizzati per programmazione generica, anche se
// sono molto più potenti dei costrutti generici in altri linguaggi. Inoltre,
// supportano specializzazione esplicita e parziale, classi in stile funzionale,
// e sono anche complete per Turing.
// Iniziamo con il tipo di programmazione generica con cui forse sei familiare. Per
// definire una classe o una funzione che prende un parametro di un dato tipo:
template<class T>
class Box {
// In questa classe, T può essere usato come qualsiasi tipo.
void inserisci(const T&) { ... }
};
// Durante la compilazione, il compilatore in effetti genera copie di ogni template
// con i parametri sostituiti, e così la definizione completa della classe deve essere
// presente ad ogni invocazione. Questo è il motivo per cui vedrai le classi template definite
// interamente in header files.
// Per instanziare una classe template sullo stack:
Box<int> intBox;
// e puoi usarla come aspettato:
intBox.inserisci(123);
//Puoi, ovviamente, innestare i templates:
Box<Box<int> > boxOfBox;
boxOfBox.inserisci(intBox);
// Fino al C++11, devi porre uno spazio tra le due '>', altrimenti '>>'
// viene visto come l'operatore di shift destro.
// Qualche volta vedrai
// template<typename T>
// invece. La parole chiavi 'class' e 'typename' sono _generalmente_
// intercambiabili in questo caso. Per una spiegazione completa, vedi
// http://en.wikipedia.org/wiki/Typename
// (si, quella parola chiave ha una sua pagina di Wikipedia propria).
// Similmente, una funzione template:
template<class T>
void abbaiaTreVolte(const T& input)
{
input.abbaia();
input.abbaia();
input.abbaia();
}
// Nota che niente è specificato relativamente al tipo di parametri. Il compilatore
// genererà e poi verificherà il tipo di ogni invocazione del template, così che
// la funzione di cui sopra funzione con ogni tipo 'T' che ha const 'abbaia' come metodo!
Cane fluffy;
fluffy.impostaNome("Fluffy")
abbaiaTreVolte(fluffy); // Stampa "Fluffy abbaia" tre volte.
// I parametri template non devono essere classi:
template<int Y>
void stampaMessaggio() {
cout << "Impara il C++ in " << Y << " minuti!" << endl;
}
// E poi esplicitamente specializzare i template per avere codice più efficiente. Ovviamente,
// la maggior parte delle casistiche reali non sono così triviali.
// Notare che avrai comunque bisogna di dichiarare la funzione (o classe) come un template
// anche se hai esplicitamente specificato tutti i parametri.
template<>
void stampaMessaggio<10>() {
cout << "Impara il C++ più velocemente in soli 10 minuti!" << endl;
}
printMessage<20>(); // Stampa "impara il C++ in 20 minuti!"
printMessage<10>(); // Stampa "Impara il C++ più velocemente in soli 10 minuti!"
////////////////////////////
// Gestione delle eccezioni
///////////////////////////
// La libreria standard fornisce un paio di tipi d'eccezioni
// (vedi http://en.cppreference.com/w/cpp/error/exception)
// ma ogni tipo può essere lanciato come eccezione
#include <exception>
// Tutte le eccezioni lanciate all'interno del blocco _try_ possono essere catturate dai successivi
// handlers _catch_.
try {
// Non allocare eccezioni nello heap usando _new_.
throw std::exception("È avvenuto un problema");
}
// Cattura le eccezioni come riferimenti const se sono oggetti
catch (const std::exception& ex)
{
std::cout << ex.what();
// Cattura ogni eccezioni non catturata dal blocco _catch_ precedente
} catch (...)
{
std::cout << "Catturata un'eccezione sconosciuta";
throw; // Rilancia l'eccezione
}
///////
// RAII
///////
// RAII sta per Resource Allocation Is Initialization.
// Spesso viene considerato come il più potente paradigma in C++.
// È un concetto semplice: un costruttore di un oggetto
// acquisisce le risorse di tale oggetto ed il distruttore le rilascia.
// Per comprendere come questo sia vantaggioso,
// consideriamo una funzione che usa un gestore di file in C:
void faiQualcosaConUnFile(const char* nomefile)
{
// Per cominciare, assumiamo che niente possa fallire.
FILE* fh = fopen(nomefile, "r"); // Apri il file in modalità lettura.
faiQualcosaConIlFile(fh);
faiQualcosAltroConEsso(fh);
fclose(fh); // Chiudi il gestore di file.
}
// Sfortunatamente, le cose vengono complicate dalla gestione degli errori.
// Supponiamo che fopen fallisca, e che faiQualcosaConUnFile e
// faiQualcosAltroConEsso ritornano codici d'errore se falliscono.
// (Le eccezioni sono la maniera preferita per gestire i fallimenti,
// ma alcuni programmatori, specialmente quelli con un passato in C,
// non sono d'accordo con l'utilità delle eccezioni).
// Adesso dobbiamo verificare che ogni chiamata per eventuali fallimenti e chiudere il gestore di file
// se un problema è avvenuto.
bool faiQualcosaConUnFile(const char* nomefile)
{
FILE* fh = fopen(nomefile, "r"); // Apre il file in modalità lettura
if (fh == nullptr) // Il puntatore restituito è null in caso di fallimento.
return false; // Riporta il fallimento al chiamante.
// Assumiamo che ogni funzione ritorni false se ha fallito
if (!faiQualcosaConIlFile(fh)) {
fclose(fh); // Chiude il gestore di file così che non sprechi memoria.
return false; // Propaga l'errore.
}
if (!faiQualcosAltroConEsso(fh)) {
fclose(fh); // Chiude il gestore di file così che non sprechi memoria.
return false; // Propaga l'errore.
}
fclose(fh); // Chiudi il gestore di file così che non sprechi memoria.
return true; // Indica successo
}
// I programmatori C in genere puliscono questa procedura usando goto:
bool faiQualcosaConUnFile(const char* nomefile)
{
FILE* fh = fopen(nomefile, "r");
if (fh == nullptr)
return false;
if (!faiQualcosaConIlFile(fh))
goto fallimento;
if (!faiQualcosAltroConEsso(fh))
goto fallimento;
fclose(fh); // Chiude il file
return true; // Indica successo
fallimento:
fclose(fh);
return false; // Propaga l'errore
}
// Se le funzioni indicano errori usando le eccezioni,
// le cose sono un pò più pulite, ma sono sempre sub-ottimali.
void faiQualcosaConUnFile(const char* nomefile)
{
FILE* fh = fopen(nomefile, "r"); // Apre il file in modalità lettura
if (fh == nullptr)
throw std::exception("Non è stato possibile aprire il file.").
try {
faiQualcosaConIlFile(fh);
faiQualcosAltroConEsso(fh);
}
catch (...) {
fclose(fh); // Fai sì che il file venga chiuso se si ha un errore.
throw; // Poi rilancia l'eccezione.
}
fclose(fh); // Chiudi il file
// Tutto è andato bene
}
// Confronta questo con l'utilizzo della classe C++ file stream (fstream)
// fstream usa i distruttori per chiudere il file.
// Come detto sopra, i distruttori sono automaticamente chiamati
// ogniqualvolta un oggetto esce dalla visibilità.
void faiQualcosaConUnFile(const std::string& nomefile)
{
// ifstream è l'abbreviazione di input file stream
std::ifstream fh(nomefile); // Apre il file
// Fai qualcosa con il file
faiQualcosaConIlFile(fh);
faiQualcosAltroConEsso(fh);
} // Il file viene chiuso automaticamente chiuso qui dal distruttore
// Questo ha vantaggi _enormi_:
// 1. Può succedere di tutto ma
// la risorsa (in questo caso il file handler) verrà ripulito.
// Una volta che scrivi il distruttore correttamente,
// È _impossibile_ scordarsi di chiudere l'handler e sprecare memoria.
// 2. Nota che il codice è molto più pulito.
// Il distruttore gestisce la chiusura del file dietro le scene
// senza che tu debba preoccupartene.
// 3. Il codice è sicuro da eccezioni.
// Una eccezione può essere lanciata in qualunque punto nella funzione e la ripulitura
// avverrà lo stesso.
// Tutto il codice C++ idiomatico usa RAII in maniera vasta su tutte le risorse.
// Esempi aggiuntivi includono
// - Utilizzo della memoria con unique_ptr e shared_ptr
// - I contenitori - la lista della libreria standard,
// vettori (i.e. array auto-aggiustati), mappe hash, e così via
// sono tutti automaticamente distrutti con i loro contenuti quando escono dalla visibilità.
// - I mutex usano lock_guard e unique_lock
///////////////////////
// Roba divertente
//////////////////////
// Aspetti del C++ che potrebbero sbalordire i nuovi arrivati (e anche qualche veterano).
// Questa sezione è, sfortunatamente, selvaggiamente incompleta; il C++ è uno dei linguaggi
// più facili con cui puoi spararti da solo nel piede.
// Puoi sovrascrivere metodi privati!
class Foo {
virtual void bar();
};
class FooSub : public Foo {
virtual void bar(); // sovrascrive Foo::bar!
};
// 0 == false == NULL (la maggior parte delle volte)!
bool* pt = new bool;
*pt = 0; // Setta il valore puntato da 'pt' come falso.
pt = 0; // Setta 'pt' al puntatore null. Entrambe le righe vengono compilate senza warnings.
// nullptr dovrebbe risolvere alcune di quei problemi:
int* pt2 = new int;
*pt2 = nullptr; // Non compila
pt2 = nullptr; // Setta pt2 a null.
// Ma in qualche modo il tipo 'bool' è una eccezione (questo è per rendere compilabile `if (ptr)`.
*pt = nullptr; // Questo compila, anche se '*pt' è un bool!
// '=' != '=' != '='!
// Chiama Foo::Foo(const Foo&) o qualche variante del costruttore di copia.
Foo f2;
Foo f1 = f2;
// Chiama Foo::Foo(const Foo&) o qualche variante, ma solo copie di 'Foo' che fanno parte di
// 'fooSub'. Ogni altro membro di 'fooSub' viene scartato. Questo comportamento
// orribile viene chiamato "object slicing."
FooSub fooSub;
Foo f1 = fooSub;
// Chiama Foo::operator=(Foo&) o una sua variante.
Foo f1;
f1 = f2;
Letture consigliate:
Un riferimento aggiornato del linguaggio può essere trovato qui http://cppreference.com/w/cpp
Risorse addizionali possono essere trovate qui http://cplusplus.com