Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Programmazione OOP C++, Dispense di Programmazione Orientata agli Oggetti

- 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

2024/2025

Caricato il 13/05/2026

sabrina-mazzuoccolo-1
sabrina-mazzuoccolo-1 🇮🇹

1 documento

1 / 72

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Introduzione
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:
- scrittura/modifica,
- preprocessing,
- compilazione,
- linking (collegamento),
- caricamento ed esecuzione.
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.
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d
pf2e
pf2f
pf30
pf31
pf32
pf33
pf34
pf35
pf36
pf37
pf38
pf39
pf3a
pf3b
pf3c
pf3d
pf3e
pf3f
pf40
pf41
pf42
pf43
pf44
pf45
pf46
pf47
pf48

Anteprima parziale del testo

Scarica Programmazione OOP C++ e più Dispense in PDF di Programmazione Orientata agli Oggetti solo su Docsity!

Introduzione

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:

  • scrittura/modifica,
  • preprocessing,
  • compilazione,
  • linking (collegamento),
  • caricamento ed esecuzione.

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.

  • #include E’ una direttiva al preprocessore. Dice al preprocessore di includere nel programma il contenuto del file iostream, detto file d’intestazione (definisce gli stream di input/output).
  • int main() { … } int indica che restituisce un intero. Le parentesi che seguono il main indica che si tratta di un blocco del programma detto funzione.
  • cout << “testo”; E’ un’istruzione. Indica al computer di visualizzare a schermo la stringa di caratteri contenuta nei doppi apici.
  • cin >> intero1; Utilizza l'oggetto stream di input cin e l'operatore di estrazione dallo stream, >> , per ricevere un valore dalla tastiera. Utilizzando l'operatore di estrazione dallo stream, cin riceve l'input dal flusso di input standard, che coincide normalmente con la tastiera.

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.

  • cout << "La somma è " << sum << endl; visualizza la stringa di caratteri "La somma è ", poi il valore numerico della variabile somma , e infine il manipolatore di stream endl ("end line", ovvero fine di linea). Il manipolatore endl invia in output un carattere di nuova linea (corrispondente al carattere \n) e, successivamente, svuota il buffer di output. Su alcuni sistemi, infatti, l'output si accumula all'interno della macchina finché non ce n'è abbastanza perché sia il caso di visualizzarlo sullo schermo: in questi casi endl forza tutto l'output accumulato ad essere visualizzato in quel momento.

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 scrivi int numero;, stai creando un'istanza di un tipo base, e la chiami variabile.
  • Se scrivi Automobile miaAuto;, stai creando un'istanza di un tipo inventato da te (la classe), e la chiami oggetto. Con una sola classe puoi costruire decine di oggetti. Le classi inoltre possono presentare relazioni con altre classi: per esempio, In un programma per una banca, la classe ImpiegatoAlloSportello dovrà per forza comunicare con la classe Cliente (ad esempio per passargli dei soldi). Tali relazioni prendono il nome di associazioni.

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.

  • Ridefinizione attraverso passi di raffinamento top-down: descrive semplicemente il modo in cui la nostra mente risolve i problemi complessi. Top → il problema generale; Down → i dettagli piccoli e precisi. Esempio: per risolvere un problema parto dallo pseudocodice, raffino il problema fino a poi “tradurlo” in codice.

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:

  • il nome della variabile di controllo (contatore del ciclo)
  • il valore iniziale della variabile di controllo
  • una condizione che verifichi se la variabile di controllo ha raggiunto il suo valore finale (cioè se il ciclo deve continuare o meno)
  • l’incremento o il decremento , che modifica la variabile di controllo ad ogni ripetizione del ciclo

#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).

  • Scope :(che in italiano possiamo tradurre come "ambito di visibilità" o "raggio d'azione" ) indica la parte del programma in cui una determinata variabile esiste e può essere utilizzata. Quando crei una variabile all'interno di una coppia di parentesi graffe { } , quella variabile è "locale". Significa che nasce, vive e muore solo lì dentro. Nessun'altra parte del programma al di fuori di quelle parentesi sa che quella variabile esiste. Scope globale: Se dichiari una variabile fuori da tutte le funzioni, questa ha uno scope "globale".
  • La visibilità di una variabile definisce in quali parti del programma può essere utilizzata. Se definite e inizializzate la variabile di controllo di un ciclo for nell’intestazione di for, non potrete utilizzare tale variabile oltre i confini del ciclo.

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).

  1. Supponiamo di voler verificare se due condizioni sono entrambe vere → utilizziamo && (AND): l’istruzione viene eseguita solo se le due condizioni sono entrambe vere.
  2. Per verificare se una o entrambe le condizioni sono vere → utilizziamo | | (OR)

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.

  • approccio divide et impera, rendono più gestibile la struttura di un programma;
  • software riutilizzabile ;
  • evitare di ripetere delle porzioni di codice utilizzate in diverse parti di un programma.

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

  • identificatori a memorizzazione automatica: auto e register → sono create all’inizio del blocco in cui sono definite, esistono durante l’esecuzione di quel blocco e sono distrutte all’uscita dal blocco. Le variabili locali e i parametri di una funzione appartengono normalmente a questa categoria. auto float x, y; Le variabili locali sono automatiche per default, per cui la parola auto si utilizza raramente.
  • identificatori a memorizzazione statica: extern e static → Variabili di questo genere esistono sin dall’inizio del programma. Per le variabili, lo spazio in memoria è allocato e inizializzato una sola volta, all’inizio dell’esecuzione del programma; per le funzioni, il nome della funzione esiste sin dall’inizio. Ciò però non significa che i loro identificatori possono essere utilizzati in tutto il programma. Ci sono due tipi di identificatori statici: gli identificatori esterni (variabili globali e nomi di funzioni → per default sono extern ) e le variabili locali dichiarate con lo specificatore static (sono note soltanto nella funzione in cui sono dichiarate ma conservano il loro valore anche quando la funzione termina la sua esecuzione).

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