appunti3s:programmazione_multifile
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revision | |||
appunti3s:programmazione_multifile [2019/07/27 10:47] – [namespace] profpro | appunti3s:programmazione_multifile [2020/06/08 22:19] (current) – external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | ====== Programmazione multifile ====== | ||
+ | |||
+ | Durante la realizzazione di un software costituito da un //main()// e numerose funzioni, può risultare utile dividere " | ||
+ | In questo modo diventa più facile apportare modifiche e leggere il codice, soprattutto nei programmi di grandi dimensioni. | ||
+ | Inoltre, siccome in certi casi il compilatore può compilate le singole parti anche individualmente, | ||
+ | |||
+ | Nel seguente esempio riprenderemo un semplicissimo programma, che sarebbe stato inutile dividere in più file, ma che comunque divideremo per esercizio, per poter capire come procedere anche nei casi più complicati. | ||
+ | |||
+ | <file c main.cpp> | ||
+ | /** @file main.cpp | ||
+ | * | ||
+ | */ | ||
+ | |||
+ | #include < | ||
+ | |||
+ | int calcolaTriplo(int x) | ||
+ | { | ||
+ | std::cout << " | ||
+ | return 3*x; | ||
+ | } | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | int mioNumero; | ||
+ | |||
+ | std::cout << "Per favore scrivi un numero intero: "; | ||
+ | std::cin >> mioNumero; | ||
+ | |||
+ | std::cout << "il triplo di " << mioNumero << " vale " | ||
+ | << calcolaTriplo(mioNumero) << std::endl; // chiamata della funzione | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | La cosa che sembra più semplice da fare è quella di dividerlo in due file sorgenti. | ||
+ | Tuttavia, come si vedrà, da questa separazione si rischia di ottenere anche un errore. | ||
+ | Vedere il seguente codice sorgente, dove è evidenziata la riga contenente l' | ||
+ | Per comprendere l' | ||
+ | |||
+ | <file c calcolatriplo.cpp> | ||
+ | /** @file calcolatriplo.cpp | ||
+ | * | ||
+ | */ | ||
+ | |||
+ | #include < | ||
+ | |||
+ | int calcolaTriplo(int x) | ||
+ | { | ||
+ | std::cout << " | ||
+ | return 3*x; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <file c main.cpp> | ||
+ | /** @file main.cpp | ||
+ | * | ||
+ | */ | ||
+ | |||
+ | #include < | ||
+ | // <---- ERRORE qui manca una dichiarazione | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | int mioNumero; | ||
+ | |||
+ | std::cout << "Per favore scrivi un numero intero: "; | ||
+ | std::cin >> mioNumero; | ||
+ | |||
+ | std::cout << "il triplo di " << mioNumero << " vale " | ||
+ | << calcolaTriplo(mioNumero) << std::endl; // chiamata della funzione | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | NOTA: bisogna usare **#include < | ||
+ | |||
+ | ===== Compilazione ===== | ||
+ | Quando il compilatore legge il contenuto del file // | ||
+ | < | ||
+ | Dopo questa correzione, i file sorgente non vanno compilati separatamente, | ||
+ | Come già visto ([[appunti3s: | ||
+ | |||
+ | * Il primo comando serve a generare due file oggetto (compilazione) | ||
+ | * < | ||
+ | * Il secondo comando serve a collegarli in un unico file eseguibile (linking) | ||
+ | * < | ||
+ | La stessa cosa si può ottenere anche usando un unico comando... | ||
+ | < | ||
+ | In questo modo il programma viene compilato senza errori, ma il codice può essere ancora migliorato, usando un' | ||
+ | Infatti, se per poter usare // | ||
+ | |||
+ | Si può creare un nuovo file di intestazione (header file) chiamato // | ||
+ | - gli header file di cui hanno bisogno le funzioni del file // | ||
+ | - le dichiarazioni di tutte le funzioni del file // | ||
+ | <file c calcolatriplo.h> | ||
+ | /** @file calcolatriplo.h | ||
+ | * | ||
+ | */ | ||
+ | #include < | ||
+ | |||
+ | int calcolaTriplo(int x); | ||
+ | </ | ||
+ | Questo file può sostituire la precedente dichiarazione di // | ||
+ | |||
+ | <file c calcolatriplo.cpp> | ||
+ | /** @file calcolatriplo.cpp | ||
+ | * | ||
+ | */ | ||
+ | #include " | ||
+ | |||
+ | int calcolaTriplo(int x) | ||
+ | { | ||
+ | std::cout << " | ||
+ | return 3*x; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | <file c main.cpp> | ||
+ | /** @file main.cpp | ||
+ | * | ||
+ | */ | ||
+ | |||
+ | #include < | ||
+ | #include " | ||
+ | |||
+ | int main() | ||
+ | { | ||
+ | int mioNumero; | ||
+ | |||
+ | std::cout << "Per favore scrivi un numero intero: "; | ||
+ | std::cin >> mioNumero; | ||
+ | |||
+ | std::cout << "il triplo di " << mioNumero << " vale " | ||
+ | << calcolaTriplo(mioNumero) << std:: | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | Quando si compila il programma: prima vengono inclusi gli header file, poi vengono creati i file oggetto ed infine questi sono collegati dal linker. | ||
+ | |||
+ | Si può pensare a // | ||
+ | |||
+ | NOTA: la differenza tra le virgolette di //" | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | In questa figura si nota che il file calcolatriplo.h è incluso sia da calcolatriplo.cpp che da main.cpp. Si nota anche che < | ||
+ | In quest' | ||
+ | |||
+ | * Il primo comando serve a generare due file oggetto (compilazione) | ||
+ | * < | ||
+ | * Il secondo comando serve a collegarli in un unico file eseguibile (linking) | ||
+ | * < | ||
+ | {{ : | ||
+ | Aver suddiviso il programma in tre file distinti non è stato facile, ma ora se ne può trarre un vantaggio: ora è possibile modificare e ricompilare SOLO il file calcolaTriplo.cpp e poi ripetere il linking, mantenendo inalterato il file oggetto main.o. | ||
+ | < | ||
+ | In questo modo si evita di ricompilare tutto il programma per piccole modifiche ed inoltre è possibile distribuire le diverse parti del programma in modo diverso, ad esempio, pubblicando il codice sorgente del main e mantenendo " | ||
+ | ====== Le direttive ====== | ||
+ | |||
+ | Le direttive al pre-compilatore sono eseguite prima della compilazione | ||
+ | |||
+ | ===== #define ===== | ||
+ | sostituisce una PAROLA (per convenzione maiuscola) con qualsiasi altra cosa | ||
+ | < | ||
+ | |||
+ | ===== include ===== | ||
+ | * //# | ||
+ | * //# | ||
+ | * //# | ||
+ | * //# | ||
+ | * Di solito l' | ||
+ | * Anche le dichiarazioni delle funzioni del linguaggio C sono state incluse nella libreria standard del C++ ma oltre ai vecchi < | ||
+ | * Viceversa, per usare le funzioni del linguaggio C++ nei programmi in C, si possono usare < | ||
+ | * Attenzione: il linguaggio C++ " | ||
+ | |||
+ | =====include guards===== | ||
+ | Evitano di includere più volte la stessa cosa... | ||
+ | < | ||
+ | #define MYHEADER_H_ | ||
+ | // declarations of the header file is inserted here, | ||
+ | |||
+ | # | ||
+ | |||
+ | ====== La documentazione del software ====== | ||
+ | Documentare bene gli oggetti e le funzioni create permette al programmatore di riutilizzarle più facilmente in futuro, quindi di fare un investimento per poter risparmiare tempo. | ||
+ | Se la documentazione è in lingua inglese, chiunque potrà riutilizzare il software. | ||
+ | |||
+ | La documentazione del software può essere inserita nel software stesso, aggiungendovi degli opportuni commenti all' | ||
+ | Se il programma è costituito da più file sorgente, dovrebbe essere documentato anche **ogni file**, come in questo esempio. | ||
+ | < | ||
+ | /** | ||
+ | * @file main.cpp | ||
+ | * @author | ||
+ | * @version 1.3 | ||
+ | * | ||
+ | * @section LICENSE | ||
+ | * | ||
+ | * This program is free software; you can redistribute it and/or | ||
+ | * modify it under the terms of the GNU General Public License as | ||
+ | * published by the Free Software Foundation; either version 2 of | ||
+ | * the License, or (at your option) any later version. | ||
+ | | ||
+ | */ | ||
+ | </ | ||
+ | |||
+ | |||
+ | A loro volta **ogni def. di classe** e **ogni def. di funzione** devono essere documentate come in questi esempi: | ||
+ | |||
+ | =====Esempio per una classe===== | ||
+ | |||
+ | < | ||
+ | /** | ||
+ | * @file time.cpp | ||
+ | * | ||
+ | * @section DESCRIPTION | ||
+ | * | ||
+ | * The time class represents a moment of time. | ||
+ | */ | ||
+ | |||
+ | class Time { | ||
+ | |||
+ | public: | ||
+ | |||
+ | /** | ||
+ | * Constructor that sets the time to a given value. | ||
+ | * | ||
+ | * @param timemillis Number of milliseconds | ||
+ | * passed since Jan 1, 1970. | ||
+ | */ | ||
+ | Time (int timemillis); | ||
+ | |||
+ | /** | ||
+ | * show the time | ||
+ | * | ||
+ | */ | ||
+ | | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | =====Esempio per una funzione===== | ||
+ | |||
+ | < | ||
+ | /** | ||
+ | * <una descrizione breve, di una riga> | ||
+ | * | ||
+ | * <una descrizione piu' completa> | ||
+ | * <che puo' avere anche piu' righe> | ||
+ | * | ||
+ | * @param | ||
+ | * @param | ||
+ | * @return descrizione dell' | ||
+ | */ | ||
+ | void NomeClasse:: | ||
+ | { | ||
+ | // il codice della funzione... | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | |||
+ | ====== namespace ====== | ||
+ | In C++, come in C, si può suddividere il codice sorgente in più file, ma in più si possono usare i // | ||
+ | * i namespace sono dei contenitori che diminuiscono la visibilità del loro contenuto | ||
+ | * anche le classi sono un altro modo di definire un namespace | ||
+ | * i namespace, rispetto alle classi, non contengono funzioni né dati membri | ||
+ | * ????i namespace, rispetto alle classi, possono essere estesi anche dopo la loro definizione | ||
+ | |||
+ | Approfondimento: | ||
+ | ===== Campi di visibilità ===== | ||
+ | |||
+ | In inglese, il campo di visibilità di un elemento (o ambito di visibilità) è chiamato //scope//. | ||
+ | All' | ||
+ | |||
+ | Ogni elemento (variabile, oggetto o funzione) dichiarato in un certo campo di visibilità è visibile solo agli elementi che si trovano dentro tale campo di visibilità, | ||
+ | |||
+ | Ad esempio, una variabile di una funzione (variabile locale) è visibile (esiste) solo durante l' | ||
+ | |||
+ | Gli elementi globali, essendo i più esterni, sono visibili in ogni momento, in qualsiasi punto del codice. Da un lato ciò è utile perché è comodo poter accedere ai dati globali in ogni momento, ma dall' | ||
+ | |||
+ | {{ : | ||
+ | |||
+ | Come si vede in questa figura, per creare un nuovo campo di visibilità si possono usare funzioni, classi oppure namespace. Il campo di visibilità interno alle funzioni è detto anche //locale//, in contrapposizione a quello //globale// (esterno a tutte le funzioni e a tutti i namespace). | ||
+ | ===== Dichiarazione ===== | ||
+ | In un programma si possono usare due namespace diversi per poter avere due funzioni con lo stesso nome e poterle usare in momenti e contesti diversi. | ||
+ | |||
+ | NOTA: Ogni classe crea automaticamente anche un nuovo namespace | ||
+ | |||
+ | <file cpp 30.h> | ||
+ | namespace MioNameSpace | ||
+ | { | ||
+ | // tutto ciò che voglio... | ||
+ | } | ||
+ | </ | ||
+ | ===== namespace come contenitori di più header ===== | ||
+ | La situazione più comune è quella in cui viene definito un namespace per tutti gli header file che riguardano lo stesso argomento (esempio, di una stessa libreria di funzioni). A titolo di esempio si può citare la libreria standard del C++, che adotta il namespace " | ||
+ | |||
+ | Ad esempio, entrambe queste librerie potrebbero avere un proprio tipo di dato string, senza che vi fosse possibilità di confusione. Ogni tipo si distinguerebbe dall' | ||
+ | Il namespace diventa una specie di estensione del nome di un elemento. Un po' come si usa il cognome per distinguere due persone che hanno lo stesso nome: Rossi Mario e Dotti Mario. | ||
+ | |||
+ | Se in un programma non si usa nessun namespace, significa che si sta lavorando nel namespace globale... | ||
+ | ===== using ===== | ||
+ | Come visto, un namespace viene dichiarato dal programmatore per racchiudere le funzioni appartenenti ad un file.h. Viceversa, il programmatore che userà tali funzioni, dovrà specificare quel namespace solo all' | ||
+ | * con la // | ||
+ | * con la // | ||
+ | |||
+ | ===== scope resolutor :: ===== | ||
+ | < | ||
+ | Il simbolo del doppio "due punti" (%%::%%) appena visto è un operatore che si deve usare per introdurre l'uso di un qualificatore: | ||
+ | Nei file.cpp, questo operatore permette di //usare// un elemento che si trova in un diverso namespace da quello attualmente in uso. | ||
+ | Nei file.cpp, in generale, lo scope resolutor //%%::%%// si può usare liberamente, | ||
+ | |||
+ | Ad esempio, se si usasse la direttiva | ||
+ | < | ||
+ | questo sarebbe equivalente a sovrapporre il namespace std al campo di visibilità globale, cioè a sovrapporre, | ||
+ | |||
+ | Lo scope resolutor si può usare, sempre nei file.cpp, anche per // | ||
+ | Questo sistema permette, durante la stesura del codice, di separare la parte che riguarda l'// | ||
+ | |||
+ | La stessa separazione tra dichiarazioni e definizioni si può applicare anche per le //funzioni membro// di una classe, poiché anche le classi definiscono automaticamente un loro namespace. | ||
+ | |||
+ | <file cpp 38.h> | ||
+ | // | ||
+ | namespace MioNameSpace | ||
+ | { | ||
+ | void fun(std:: | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | <file cpp 38.cpp> | ||
+ | // | ||
+ | void MioNameSpace:: | ||
+ | { | ||
+ | // codice... | ||
+ | } | ||
+ | </ | ||
+ | In quest' | ||
+ | |||
+ | |||
+ | |||
+ | ===== namespace annidati ===== | ||
+ | omissis... | ||
+ | |||
+ | ===== namespace anonimi ===== | ||
+ | In un file.h è possibile dichiarare un namespace senza specificare nessun nome | ||
+ | < | ||
+ | { | ||
+ | // codice... | ||
+ | }</ | ||
+ | |||
+ | In questo caso viene creato un namespace " | ||
+ | |||
+ | In questo caso è possibile separare l' | ||
+ | <file cpp 39.h> | ||
+ | // | ||
+ | namespace | ||
+ | { | ||
+ | void fun(int x); | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | <file cpp 38.cpp> | ||
+ | //ERRORE: l' | ||
+ | void ::fun(int x) | ||
+ | { | ||
+ | // codice... | ||
+ | } | ||
+ | </ | ||
+ | Poiché non è possibile, il namespace anonimo può essere usato da un programmatore che scrive l' | ||
+ | |||
+ | ===== namespace genitori anonimi ===== | ||
+ | Oltre al namespace anonimo di un singolo file, esiste anche un namespace genitore anonimo. | ||
+ | Dichiarare oggetti all' | ||
+ | < | ||
+ | ...to do | ||
+ | </ | ||
+ | Un altro modo per definire elementi a "lunga durata" | ||
+ | |||
+ | ===== Strumenti ===== | ||
+ | (cenni a make cmake http:// | ||
+ | |||
+ | Esempi | ||
+ | 1. | ||
+ | esempio di due namespace che contengono funzioni omonime | ||
+ | 2. | ||
+ | esempio namespace condiviso tra più header / header contenenti più namespace | ||
+ | 3. | ||
+ | esempio namespace anonimo e anonimo genitore | ||