
































































Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
- Introduzione - Input/Output e direttive - Variabili, memoria e operatori - Namespace e OOP - Strutture di controllo - Funzioni - Numeri casuali - Scope e memorizzazione - Ricorsione - Array - Stringhe - Puntatori - Classi - Ereditarietà - Polimorfismo
Tipologia: Dispense
1 / 72
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!

































































I programmi in C++ si compongono di classi e funzioni. Il modo migliore per creare dei programmi è con l’approccio “a blocchi”. Questo principio è noto come riutilizzo del software ed è alla base della programmazione orientata agli oggetti.
Gli ambienti di sviluppo C++ generalmente sono formati da diverse componenti: un ambiente di sviluppo dei programmi, il linguaggio e la libreria standard del C++. Prima di giungere all’esecuzione, un programma C++ attraversa sei fasi:
Prima fase: scrittura/modifica di un file. Allo scopo si utilizza un programma detto editor che consente al programmatore di scrivere o modificare il suo programma. Il programma viene successivamente salvato su un’unità di memorizzazione secondaria ,come per esempio il disco rigido , con la convenzione che i nomi dei file per i programmi C++ terminano con le estensioni .cpp , .cxx o .C. Seconda fase : Dopo avere scritto il programma è necessario compilarlo : il compilatore converte il codice del vostro programma C++ in linguaggio macchina (detto anche codice oggetto ). In un ambiente C++ prima della fase di compilazione viene eseguita una pre-elaborazione del programma per mezzo di un componente software chiamato preprocessore. Il preprocessore C++ esegue delle istruzioni speciali dette direttive al preprocessore , le quali indicano alcune manipolazioni da effettuare sul codice prima della fase di compilazione. Tali manipolazioni consistono generalmente nell’inclusione di file esterni e nella sostituzione di stringhe all’interno del testo del programma. E’ il compilatore che chiama il preprocessore prima di iniziare la compilazione. Terza fase : la fase successiva è detta collegamento o linking. I programmi in C++ contengono spesso dei riferimenti a funzioni definite altrove, per esempio nelle librerie standard. Il codice macchina prodotto dal compilatore (detto codice oggetto) contiene dunque dei riferimenti pendenti in corrispondenza di queste funzioni. Il linker lega il codice oggetto assieme con il codice delle funzioni mancanti per produrre quella che viene chiamata l’ immagine eseguibile (o più semplicemente l’eseguibile), in cui non ci sono più riferimenti non risolti. Quarta fase : La fase successiva è quella di caricamento : prima che un programma possa andare in esecuzione deve essere portato in memoria. È il loader (caricatore) che prende l’immagine eseguibile e la trasferisce in memoria. Infine il computer, sotto il controllo della CPU, esegue il programma un’istruzione alla volta.
Alcune funzioni C++ prendono i dati in input dallo stream cin (nome del flusso di dati per l’input standard) che coincide normalmente con la tastiera; tuttavia cin può essere connesso ad altre unità. I dati sono quindi inviati in output sullo stream cout (flusso di dati per l’output standard) che coincide normalmente con lo schermo; allo stesso modo di cin , tuttavia, cout può essere connesso ad altre unità. Esiste anche uno stream di dati standard per gli errori denominato cerr. Lo stream cerr , generalmente connesso allo schermo, viene utilizzato per visualizzare i messaggi di errore. Spesso i programmatori dirigono i dati destinati all'output, cioè a cout , a un'unità diversa dallo schermo, mantenendo l'associazione di cerr con lo schermo, in modo da essere informati immediatamente di eventuali errori.
Gli oggetti stream cin e cout consentono l'interazione fra l'utente e il computer. Poiché questo tipo di interazione ricorda un dialogo, essa viene anche detta elaborazione interattiva. Gli operatori = e + sono detti operatori binari perché ognuno di essi ha due operandi.
Gli operatori relazionali hanno tutti lo stesso livello di precedenza e associano da sinistra a destra. Anche gli operatori di uguaglianza associano da sinistra a destra e hanno lo stesso livello di precedenza, che è però più basso di quello degli operatori relazionali.
L’esempio seguente utilizza sei istruzioni if per confrontare due numeri immessi dall’utente. Se la condizione di una qualsiasi delle if viene soddisfatta, viene eseguita l’istruzione di
output di tale if.
using namespace std; → indica che stiamo utilizzando il namespace std : una recente caratteristica del C++. I namespace sono stati pensati per aiutare i programmatori a sviluppare nuovi componenti software senza generare conflitti con componenti già esistenti.
I namespace sono utilizzati per mantenere nomi univoci per ogni nuovo componente software.
→ Esempio di creazione di un namespace “Matematica”
Ogni file di intestazione utilizza uno spazio dei nomi (namespace) chiamato std per garantire che tutte le caratteristiche della libreria standard non entrino in conflitto con componenti software sviluppati da terze parti. Una volta inclusa l’istruzione using namespace std; possiamo utilizzare direttamente l’oggetto cout. Se si utilizzano due o più librerie di classi che hanno componenti dai nomi identici può crearsi un conflitto di nomi. In questo caso occorre qualificare completamente il nome che si vuole utilizzare, unendolo al namespace a cui appartiene → std::cout …
Le tecniche orientate agli oggetti e UML Possiamo suddividere gli oggetti in due categorie: oggetti animati e inanimati. I primi sono vivi, si muovono, intraprendono azioni; al contrario dei secondi. Entrambi hanno degli attributi (dimensione, forma, colore) e dei comportamenti. Oggetti diversi possono avere attributi e comportamenti molto simili.
La programmazione a oggetti (OOP) modella gli oggetti software sulla base degli oggetti del mondo reale. Essa si avvale del concetto di astrazione classe, per cui oggetti di una determinata classe hanno le stesse caratteristiche. Si avvale poi di relazioni di ereditarietà (singola e multipla) secondo le quali nuove classi di oggetti sono derivate da classi esistenti, ereditando le loro caratteristiche e estendendole con caratteristiche proprie. La OOP modella anche la comunicazione tra gli oggetti. La OOP incapsula i dati (gli attributi) e le funzioni (comportamenti) in pacchetti detti oggetti: dati e funzioni sono intimamente correlati. Gli oggetti hanno anche la proprietà di tenere nascoste le informazioni (non sempre hanno la possibilità di conoscere la struttura interna di altri oggetti).
In C++ l’unità è la classe , da cui si istanziano (creano) gli oggetti. → Raggruppa i dati (attributi) e le azioni (metodi). → Le classi contengono funzioni dette metodi. → Una classe contiene sia i dati che le funzioni deputate a manipolarli.
In C++ si concentra l’attenzione sulla creazione di nuovi tipi di dato → le classi. Le componenti di una classe (che sono dati) sono detti dati membro. Allo stesso modo, le funzioni sono dette funzioni membro o metodi. L’istanza di un tipo di dato predefinito come int è detta variabile. L’istanza di un tipo di dato definito dall’utente, come una classe, è detto oggetto, cioè:
Se la condizione è vera viene visualizzato a schermo “Promosso!”, se la condizione è falsa, l’istruzione viene saltata e si passa direttamente all’istruzione successiva del programma.
Struttura di selezione if/else: pseudocodice: Se il voto dello studente è maggiore o uguale a 60 Visualizza “Promosso!” altrimenti Visualizza “Bocciato”
if(voto >= 60) cout << “Promosso!”; else cout << “Bocciato”;
In C++ esiste l'operatore condizionale (?:) che dal punto di vista operativo è molto simile al costrutto if/else. L'operatore condizionale è l'unico operatore ternario del C++, cioè lavora su tre operandi. Operandi e operatore formano un'espressione condizionale: il primo operando costituisce la condizione, il secondo operando è il valore che assumerà l'intera espressione se la condizione è vera, mentre il terzo è il valore che assumerà l'intera espressione se la condizione è falsa.
cout << ( voto >= 60? "Promosso" : "Bocciato" ); oppure: voto >= 60? cout << "Promosso" : cout << "Bocciato"; va letta "Se voto è maggiore o uguale a 60 allora cout << "Promosso", altrimenti cout << "Bocciato". Anche questa operazione avrebbe potuto essere effettuata in modo equivalente da un costrutto if/else.
Struttura iterativa while Consente di eseguire ripetutamente un’azione specifica fin tanto che una data condizione rimane vera. pseudocodice: Finché ci sono ancora cose da comprare devo comprare il prossimo oggetto e depennarlo dalla lista
La condizione “ci sono ancora cose da comprare” può essere vera o falsa. Se è vera si effettua l’operazione “devo comprare il prossimo oggetto e depennarlo dalla lista”. L’operazione sarà eseguita continuamente, finché la condizione rimarrà vera. Ad un certo punto la condizione diventerà falsa e in quel momento l’iterazione termina, e l’esecuzione del programma prosegue dalla prima istruzione che segue il costrutto while.
Esempio: cercare la prima potenza di 2 superiore a 1000 int prodotto = 2; while(prodotto <= 1000) prodotto = 2 * prodotto;
Problema 1: Una classe di dieci studenti ha sostenuto un esame. Avete a disposizione i voti di ogni studente, in una scala da 0 a 100. Determinare la media dei voti.
pseudocodice: inizializzare il totale a zero inizializzare il contatore dei voti a uno Finché il contatore resta minore o uguale a 10 Prendere dall’input il prossimo voto Aggiungere il voto al totale Aggiungere uno al contatore Impostare il valore della media al totale diviso 10 Visualizzare la media
Problema 2: Scrivere un programma che calcoli la media dei voti di un numero arbitrario di studenti: il numero di studenti verrà deciso volta in volta dall'utente durante l’esecuzione.
Nel problema precedente sapevamo a priori che il numero di studenti sarebbe stato 10. Poiché non sappiamo quanti voti ci saranno, usiamo un valore speciale detto valore sentinella (o flag ). Le iterazioni controllate dal valore sentinella sono ripetizioni indefinite, prima dell’esecuzione non si può sapere quante volte sarà eseguito il ciclo. Il valore sentinella NON deve mai essere un valore valido per il tuo problema. → Se chiedi dei voti scolastici (che vanno da 0 a 100), -1 è un'ottima sentinella, perché nessuno prenderà mai -1 a un esame. Il computer capisce subito che non è un voto, ma un comando per fermarsi.
Utilizzo questo metodo per risolvere il problema della media: Livello top → “Determinare la media dei voti di una classe relativa per un determinato esame” 1° raffinamento: Inizializzare le variabili Effettuare l’input, la somma e il conteggio dei voti Calcolare e visualizzare la media dei voti 2° raffinamento: Inizializzare il totale a zero Inizializzare il contatore a zero Effettuare l’input del primo voto Finché l’utente non ha ancora digitato il valore sentinella Aggiungere il voto corrente al totale Aggiungere uno al contatore Effettuare l’input del prossimo voto Se il contatore non è uguale a zero Impostare il valore della media al totale diviso il contatore Visualizzare la media
Cicli controllati da variabili contatore In un ciclo di iterazione controllato da un contatore sono necessari:
#include int main() { int contatore = 1; //inizializzazione
while (contatore <= 10){ //condizione dell’iterazione std::cout << contatore << std::endl; ++contatore; //incremento } return 0; }
Struttura iterativa for: Gestisce l’iterazione controllata da un contatore nel modo più completo. Quando inizia l’esecuzione dell’istruzione for, la variabile contatore viene dichiarata e inizializzata a 1. Successivamente viene verificata la condizione del ciclo contatore <= 10. Dato che il valore iniziale del contatore è 1, la condizione è soddisfatta. La variabile contatore viene poi incrementata in contatore++, dopo che il ciclo riparte. Il processo continua finché il contatore non raggiunge il valore 11.
for (int contatore = 1; contatore <= 10; contatore++) std::cout << contatore << std::endl;
Formato generico del for: for(espressione1; espressione2; espressione3) istruzione;
espressione1 → inizializza la variabile di controllo del ciclo espressione2 → è la condizione di continuazione del ciclo espressione3 → incrementa la variabile di controllo
Può essere tradotto in un ciclo while equivalente: espressione1; while (espressione2){ istruzione espressione3; }
a) Far variare il contatore da 1 a 100 in incrementi di 1. for ( int i = 1; i <= 100; i++ ) b) Far variare il contatore da 100 a 1 in incrementi di -1, cioè decrementi di 1. for ( int i = 100; i >= 1; i-- ) c) Far variare il contatore da 7 a 77 in passi da 7. for ( int i = 7; i <= 77; i += 7 ) d) Far variare il contatore da 20 a 2 in passi da -2. for ( int i = 20; i >= 2; i -= 2 ) e) Far assumere al contatore la serie di valori: 2, 5, 8, 11, 14, 17, 20. for ( int j = 2; j <= 20; j += 3 ) (Parte da 2, arriva a 20, salta di 3 in 3). f) Far assumere al contatore la serie di valori: 99, 88, 77, 66, 55, 44, 33, 22, 11, 0. for ( int j = 99; j >= 0; j -= 11 ) (Parte da 99, scende fino a 0, sottrattendo 11 ogni volta).
Struttura di selezione switch: Si compone di una serie di etichette case e in via opzionale di un’etichetta default.
#include int main() { for(int x = 1; x <= 10; x++){ if(x == 5){ continue; std::cout << x << “ ”; } std::cout <<”\nUtilizzato continue per evitare di stampare il valore 5” << std::endl; return 0; }
Operatori logici Gli operatori logici sono && ( AND logico), | | ( OR logico) e! ( NOT logico).
I componenti di un programma C++ I componenti di un programma in C++ sono le funzioni e le classi. Un programma tipico si compone di queste parti, usando sia quelle scritte da voi, sia quelle già fornite "di serie" con il compilatore (cioè le funzioni della libreria standard e quelle fornite dalle librerie di classi disponibili).
E’ possibile scrivere delle nuove funzioni che eseguono dei compiti specifici, le quali possono essere utilizzate in diversi punti del programma. Queste funzioni si chiamano funzioni definite dal programmatore. Una funzione viene eseguita tramite una chiamata di funzione , questa specifica il nome della funzione e fornisce le informazioni (gli argomenti ) di cui la funzione ha bisogno per effettuare i suoi compiti.
Funzioni matematiche Le funzioni matematiche di libreria eseguono i calcoli matematici più comuni. Per chiamare una funzione si utilizza la seguente notazione: ** (argomento della funzione)** Per utilizzare le funzioni matematiche della libreria standard bisogna includere nel programma il file di intestazione → #include < cmath >
Le funzioni Le funzioni consentono di suddividere un programma in moduli. Le variabili dichiarate nelle definizioni delle funzioni sono dette variabili locali , perché sono valide soltanto all’interno della funzione in cui sono definite. La maggior parte delle funzioni prevede una lista di parametri , che rappresentano le informazioni che le funzioni si comunicano l’una all’altra.
La funzione square riceve una copia del valore di x nel parametro y e calcola il valore y * y. Il risultato viene passato nuovamente a main, nel punto in cui era stata invocata square, e main lo visualizza. Il valore di x non viene modificato dalla funzione.
Generazione di numeri casuali Funzione rand(). Fa parte della libreria standard. x = rand(); La funzione rand genera un numero intero compreso tra 0 e il valore della costante simbolica RAND_MAX, definita nel file di intestazione . Il valore di RAND_MAX deve essere almeno 32767, il massimo valore positivo che si può rappresentare con un intero di 2 byte (16 bit). Se è vero che rand produce numeri casuali, è anche vero che tutti i numeri nell’intervallo hanno uguale probabilità di essere estratti ad ogni chiamata della funzione.
Esempio: programma che simula 20 lanci consecutivi di un dado Per ottenere il risultato voluto si utilizza l’operatore modulo (%) assieme a rand rand () % 6 il che produce numeri interi compresi tra 0 e 5. In casi come questo, si suole dire che si applica un fattore di scala , che nel nostro caso è il numero 6.
int main(){ int frequenza1 = 0, frequenza2 = 0, frequenza3 = 0, frequenza4 = 0, frequenza5 = 0, frequenza6 = 0; int facce;
for(int i=1; i <= 6000; i++){ facce = 1 + rand() % 6;
switch (facce){ case 1: ++frequenza1; break; case 2: ++frequenza2; break; case 3: ++frequenza3; break; case 4: ++frequenza4; break; case 5: ++frequenza5; break; case 6: ++frequenza6; break; default: std::cout << "Non dovresti essere qui." << std::endl; } } std::cout << "Facce" << std::setw(13) << "Frequenza" << "\n 1" << std::setw(13) << frequenza << "\n 2" << std::setw(13) << frequenza << "\n 3" << std::setw(13) << frequenza << "\n 4" << std::setw(13) << frequenza << "\n 5" << std::setw(13) << frequenza << "\n 6" << std::setw(13) << frequenza6 << std::endl; return 0; }
Output:
La funzione rand produce in realtà numeri pseudocasuali ; le chiamate a rand producono ripetutamente delle stesse sequenze di numeri, che sembrano casuali. In realtà i numeri sono casuali, ma la loro sequenza si ripete uguale a se stessa ad ogni esecuzione del
I valori possono solo essere assegnati nelle parentesi.
Le informazioni di memorizzazione Un identificatore, oltre al suo nome, possiede altri attributi, fra cui le informazioni di memorizzazione, la visibilità o scope e le informazioni di collegamento o linkage.
Le informazioni di memorizzazione di un identificatore determinano quanto dura la sua permanenza in memoria. La visibilità di un identificatore definisce i punti del programma in cui esso può essere riferito. Alcuni identificatori si possono utilizzare in tutto il programma, mentre altri sono validi in porzioni limitate del codice. Le informazioni di collegamento di un identificatore determinano, nel caso di un programma composto di più file origine, se un identificatore sia noto soltanto nel file corrente o anche negli altri file.
Il C++ prevede 4 specificatori per le informazioni di memorizzazione di un identificatore: auto , register , extern e static. E’ possibile suddividere gli identificatori in due categorie
Regole di visibilità La porzione di un programma in cui esiste un identificatore prende il nome di visibilità o scope. Se dichiariamo una variabile locale in un blocco, possiamo riferirci ad essa soltanto in tale blocco. Esistono 5 tipi di visibilità degli identificatori: a livello di funzione , di file , di blocco , di prototipo di funzione e di classe.
Un identificatore dichiarato al di fuori di qualsiasi funzione ha visibilità a livello di file (è noto a tutte le funzioni che si trovano dopo la sua dichiarazione → variabili globali, definizioni delle funzioni e prototipi di funzione che si trovano al di fuori delle funzioni).
Le etichette , o label, sono identificatori seguiti da un segno di due punti come start:, esse rappresentano gli unici identificatori che hanno visibilità a livello di funzione. Le etichette
possono essere utilizzate ovunque nella funzione in cui appaiono, ma non possono essere riferite al di fuori del corpo di tale funzione. Le etichette sono utilizzate nei costrutti switch (nelle clausole case).
Gli identificatori dichiarati in un blocco hanno visibilità a livello di blocco. In questo caso lo scope inizia dalla dichiarazione dell'identificatore e termina con la parentesi graffa destra (}) che segnala la fine del blocco → variabili locali, parametri delle funzioni Le variabili locali static hanno scope a livello di blocco anche se vengono create sin dall'inizio del programma.
Gli unici identificatori che hanno visibilità a livello di prototipo di funzione sono i parametri elencati nei prototipi di funzione.
Concetto di ricorsione Una funzione ricorsiva è una funzione che direttamente o indirettamente richiama se stessa. Una funzione ricorsiva viene chiamata per risolvere un problema, la funzione deve saper risolvere soltanto una piccola parte del problema, cioè il caso o casi più semplici detti casi base. Se la funzione viene chiamata per un caso base, essa restituisce un risultato e termina; se invece viene chiamata per un caso più complesso, essa suddivide il problema in due parti: una prima parte che la funzione sa risolvere e una seconda che non sa risolvere. Perché il problema possa avere una soluzione ricorsiva, la seconda parte deve assomigliare al problema originario ed essere di dimensioni un po' più piccole di esso. Dato che quest'ultima parte assomiglia al problema originario, la funzione può chiamare una nuova copia di se stessa che lavorerà sul problema leggermente più piccolo, e così via → noto come passo ricorsivo. Un passo ricorsivo è eseguito mentre la chiamata originaria alla funzione è ancora pendente, ovvero non ha terminato la sua esecuzione. Il passo ricorsivo può consistere di tante chiamate alla stessa funzione. Man mano che le porzioni diventano sempre più piccole, esse devono convergere verso il caso base. A quel punto, la funzione riconosce il caso base e lo risolve, quindi restituisce il risultato alla copia precedente della funzione; quest’ultima la restituisce alla copia precedente e così via fino alla chiamata originaria.
Esempio:
1. Calcolo dei fattoriali