User Tools

Site Tools


appunti3s:programmazione_multifile

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
appunti3s:programmazione_multifile [2019/07/27 10:47] – [namespace] profproappunti3s: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 "fisicamente" il codice del programma, in più file sorgenti.
 +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, in caso di modifiche ad uno dei file sorgenti non è necessario ricompilare tutto il programma, ma solo il file modificato. Comunque tutti i file sorgente appartenenti allo stesso programma dovranno essere contenuti in una sola cartella.
 +
 +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 <iostream>
 +
 +int calcolaTriplo(int x) 
 +
 +  std::cout << "promemoria: sto eseguendo calcolaTriplo()..." << std::endl; 
 +  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;
 +}
 +</file>
 +
 +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'errore.
 +Per comprendere l'errore bisogna aver letto il paragrafo sulle  //[[appunti3s:programmazione_procedurale#dichiarazioni|dichiarazioni]]//.
 +
 +<file c calcolatriplo.cpp>
 +/** @file calcolatriplo.cpp 
 +*
 +*/ 
 +
 +#include <iostream>
 +
 +int calcolaTriplo(int x) 
 +{
 +  std::cout << "promemoria: sto eseguendo calcolaTriplo()..." << std::endl; 
 +  return 3*x;
 +}
 +</file>
 +
 +<file c main.cpp>
 +/** @file main.cpp 
 +*
 +*/ 
 +
 +#include <iostream>
 +                     // <---- 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;
 +}
 +</file> 
 +                                             
 +NOTA: bisogna usare **#include <iostream>** SOLO quando serve! Cioè dove si vuole fare Input/Output.
 +
 +===== Compilazione =====
 +Quando il compilatore legge il contenuto del file //main.cpp// trova la chiamata alla funzione //calcolaTriplo()// ma non può verificarne la correttezza del passaggio dei parametri. È necessario far precedere il codice //main()// con la //dichiarazione// della funzione che si vuole usare.
 +<code>int calcolaTriplo(int x);</code>
 +Dopo questa correzione, i file sorgente non vanno compilati separatamente, bisogna comunicare al compilatore che da questi due file sorgente si deve ottenere un unico file eseguibile.
 +Come già visto ([[appunti3s:programmazione imperativa&#uso del compilatore g++]]) si effettua prima la compilazione e poi il linking.
 +
 +  * Il primo comando serve a generare due file oggetto (compilazione)
 +    * <code>g++ -c calcolatriplo.cpp main.cpp </code>
 +  * Il secondo comando serve a collegarli in un unico file eseguibile (linking)
 +    * <code>g++ calcolatriplo.o main.o -o triplica.exe</code>
 +La stessa cosa si può ottenere anche usando un unico comando...
 +<code>g++ main.cpp calcolatriplo.cpp -o triplica.exe</code>
 +In questo modo il programma viene compilato senza errori, ma il codice può essere ancora migliorato, usando un'organizzazione diversa del codice. 
 +Infatti, se per poter usare //%%std::cout%%// è necessaria la direttiva //#include//, allo stesso modo, una simile direttiva potrebbe essere utilizzata anche per usare //calcolaTriplo()//. Vedere anche [[appunti3s:programmazione_multifile&#include]] 
 +
 +Si può creare un nuovo file di intestazione (header file) chiamato //calcolatriplo.h// che contenga:
 +  - gli header file di cui hanno bisogno le funzioni del file //calcolatriplo.cpp//
 +  - le dichiarazioni di tutte le funzioni del file //calcolatriplo.cpp//,
 +<file c calcolatriplo.h>
 +/** @file calcolatriplo.h 
 +*
 +*/ 
 +#include <iostream>
 +
 +int calcolaTriplo(int x);
 +</file>
 +Questo file può sostituire la precedente dichiarazione di //calcolaTriplo()// per mezzo di un #include. Ecco come si presenterebbe la soluzione finale:
 +
 +<file c calcolatriplo.cpp>
 +/** @file calcolatriplo.cpp 
 +*
 +*/
 +#include "calcolatripolo.h"  // sostituisce l'inclusione di <iostream>
 +
 +int calcolaTriplo(int x) 
 +{
 +  std::cout << "Promemoria: sto eseguendo calcolaTriplo()...." << std::endl;
 +  return 3*x;
 +}
 +</file>
 +
 +<file c main.cpp>
 +/** @file main.cpp 
 +*
 +*/ 
 +
 +#include <iostream>
 +#include "calcolatriplo.h"  // sostituisce la dichiarazione di calcolaTriplo()
 +
 +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;
 +}
 +</file> 
 +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 //iostream// come all'interfaccia che permette di usare //%%std::cout%%// e a //calcolatriplo.h// come all'interfaccia che permette di usare //calcolaTriplo()//.
 +
 +NOTA: la differenza tra le virgolette di //"calcolatriplo.h"// e il simbolo maggiore/minore di //<iostream>// sta nel differente percorso dei file. Le virgolette si usano quando si vuole specificare un percorso relativo agli altri file sorgente. Il maggiore/minore si usa per indicare le cartelle predefinite del sistema operativo dove sono stati installati tutti gli header.
 +
 +{{ :appunti3s:header_file.png?400 |}}
 +
 +In questa figura si nota che il file calcolatriplo.h è incluso sia da calcolatriplo.cpp che da main.cpp. Si nota anche che <iostream> è incluso da main.cpp sia in modo diretto, che in modo indiretto (tramite calcolatriplo.h). Includere due volte lo stesso header file diminuisce l'efficienza della compilazione, ma la discussione di questo argomento viene rimandata...
 +In quest'ultima versione si può ripetere la solita compilazione con successivo linking.
 +
 +  * Il primo comando serve a generare due file oggetto (compilazione)
 +    * <code>g++ -c calcolatriplo.cpp main.cpp</code>
 +  * Il secondo comando serve a collegarli in un unico file eseguibile (linking)
 +    * <code>g++ calcolatriplo.o main.o -o triplica.exe</code>
 +{{ :appunti3s:cppcompilazione2.png?300 |}}
 +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. 
 +<code>g++ calcolatriplo.cpp main.o -o triplica-bis.exe</code>
 +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 "segreto" il codice della funzione.
 +====== Le direttive ======
 +
 +Le direttive al pre-compilatore sono eseguite prima della compilazione
 +
 +===== #define =====
 +sostituisce una PAROLA (per convenzione maiuscola) con qualsiasi altra cosa
 + <code> #define ANNO 2012 </code>
 +
 +===== include =====
 +  * //#include// sostituisce una riga con un intero file di testo
 +  * //#include// con le "virgolette" cerca il file nella cartella locale
 +  * //#include// con <maggiore/minore> cerca il file nella cartella predefinita di sistema
 +  * //#include// viene usato all'interno dei programmi per includere le dichiarazioni presenti nei file di intestazione (che hanno estensione .h)
 +  * Di solito l'estensione è .h. Fanno eccezione gli header della libreria standard del linguaggio C++, che sono privi del .h (come <iostream>, <string>, ecc.)
 +  * Anche le dichiarazioni delle funzioni del linguaggio C sono state incluse nella libreria standard del C++ ma oltre ai vecchi <stdio.h> , <math.h>, ecc., in C++ si può usare anche: <cstdio>, <cmath>, ecc. La differenza è che nei nuovi header (che iniziano con lettera "c" e che sono privi del ".h" finale) tutte le vecchie funzioni del C si trovano nel namespace //std//).
 +  * Viceversa, per usare le funzioni del linguaggio C++ nei programmi in C, si possono usare <code>#include <iostream.h></code>
 +  * Attenzione: il linguaggio C++ "contiene" il linguaggio C, cioè il compilatore C++ compila senza errori anche programmi in linguaggio C. I due linguaggi hanno però due stili diversi (vedi [[appunti3s:programmazione orientata agli oggetti#confronto tra linguaggio c e c++]]...)
 +
 +=====include guards===== 
 +Evitano di includere più volte la stessa cosa...
 +<code>#ifndef MYHEADER_H_ 
 +#define MYHEADER_H_ 
 +    // declarations of the header file is inserted here, 
 +     
 +#endif</code> 
 +
 +====== 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'inizio di ogni file.
 +Se il programma è costituito da più file sorgente, dovrebbe essere documentato anche **ogni file**, come in questo esempio.
 +<code>
 +/**
 + * @file main.cpp
 + * @author  Fabio <blabla@example.com>
 + * @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.
 + 
 + */
 +</code>
 +
 +
 +A loro volta **ogni def. di classe** e **ogni def. di funzione** devono essere documentate come in questi esempi:
 +
 +=====Esempio per una classe=====
 +
 +<code>
 +/**
 + * @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
 +        *
 +        */
 +       static Time now ();
 +};
 +</code>
 +
 +=====Esempio per una funzione=====
 +
 +<code>
 +/**
 + * <una descrizione breve, di una riga>
 + *
 + * <una descrizione piu' completa>
 + * <che puo' avere anche piu' righe>
 + *
 + * @param  descrizione dei parametri della funzione membro
 + * @param  ...
 + * @return descrizione dell'eventuale valore restituito
 + */
 +void NomeClasse::nomeFunzione()
 +{
 +      // il codice della funzione...
 +}
 +
 +</code>
 +
 +====== namespace ======
 +In C++, come in C, si può suddividere il codice sorgente in più file, ma in più si possono usare i //namespace// per organizzare ancora meglio la visibilità degli elementi al loro interno. Di solito, quando più header contengono dichiarazioni di elementi affini o su uno stesso argomento, si possono raggruppare all'interno di uno stesso //namespace//. Ad esempio, tutti gli elementi di una libreria possono appartenere ad un certo namespace. Il namespace a cui appartiene un header file viene specificato dentro lo stesso header file.
 +  * 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: [[appunti3s:namespace]]
 +===== Campi di visibilità =====
 +
 +In inglese, il campo di visibilità di un elemento (o ambito di visibilità) è chiamato //scope//.
 +All'interno di un programma esistono alcuni elementi che possono essere usati solo in alcune parti del codice. Ad esempio, le variabili locali di una funzione sono visibili solo dentro tale funzione. Come conseguenza di questo, tali variabili non sono nemmeno accessibili, cioè non si possono usare. Il concetto di accessibilità è più sottile di quello di visibilità e verrà approfondito successivamente nel paragrafo [[appunti3s:programmazione_orientata_agli_oggetti&#data_hiding]] della programmazione orientata agli oggetti.
 + 
 +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à, mentre non è visibile dal suo esterno. In altri termini, gli elementi più interni sono i meno visibili. Questo sistema offre una certa protezione dei dati da modifiche indesiderate.
 +
 +Ad esempio, una variabile di una funzione (variabile locale) è visibile (esiste) solo durante l'esecuzione della funzione. Oppure, un oggetto di un certo tipo classe può vedere solo le funzioni che si trovano all'interno del suo tipo di classe. 
 +
 +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'altro lato ciò è rischioso perché ciò può ridurre la "sicurezza" dei dati.
 +
 +{{ :appunti3s:cppvisibilita.png |}}
 +
 +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...
 +}
 +</file>
 +===== 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 "std", o la libreria wxwidget, che adotta il namespace "wx".
 +
 +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'altro grazie al namespace: //%%std::string%%// e //%%wx::string%%//.
 +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'interno dei file.cpp, in uno dei seguenti modi:
 +  * con la //direttiva// //using// in un file.cpp, per sottintendere l'uso di un certo namespace per tutte gli elementi utilizzati. Può sembrare una direttiva comoda, ma va usata con moderazione e solo nei file.cpp <code>using namespace std;</code>
 +  * con la //dichiarazione// //using//, per sottintende l'uso di un certo namespace per un solo elemento <code> using std::cout; using std::sqrt()</code>
 +
 +===== scope resolutor :: =====
 +<code>std::string parola;</code>
 +Il simbolo del doppio "due punti" (%%::%%) appena visto è un operatore che si deve usare per introdurre l'uso di un qualificatore: il namespace.
 +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, mentre la //direttiva using// è sconsigliata. Questa direttiva si potrebbe usare quando fosse noioso ripetere sempre lo scope resolutor, decine di volte.
 +
 +Ad esempio, se si usasse la direttiva 
 +<code>using namespace std; </code>
 +questo sarebbe equivalente a sovrapporre il namespace std al campo di visibilità globale, cioè a sovrapporre, nella figura, il disco arancione a quello bianco.
 +
 +Lo scope resolutor si può usare, sempre nei file.cpp, anche per //definire// gli elementi (come le funzioni) che erano stati precedentemente solo //dichiarati// dentro un certo namespace, in un file.h. 
 +Questo sistema permette, durante la stesura del codice, di separare la parte che riguarda l'//interfaccia// di una funzione dalla sua //implementazione//, cioè le dichiarazioni, che vanno nel file.h, dalle definizioni, che vanno nei file.cpp.
 +
 +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>
 +//l'interfaccia 
 +namespace MioNameSpace
 +{
 +   void fun(std::string x);
 +}
 +
 +</file>
 +<file cpp 38.cpp>
 +//l'implementazione
 +void MioNameSpace::fun(std::string x)
 +{
 +  // codice...
 +}
 +</file>
 +In quest'ultimo esempio si può notare che la funzione (fun) e il tipo del suo parametro (x) possono non appartenere allo stesso namespace...
 +
 +
 +
 +===== namespace annidati =====
 +omissis...
 +
 +===== namespace anonimi =====
 +In un file.h è possibile dichiarare un namespace senza specificare nessun nome
 +<code> namespace 
 +{
 +// codice...
 +}</code>
 +
 +In questo caso viene creato un namespace "anonimo" esclusivo per questo file.h. Diversamente dagli altri namespace, un namespace anonimo non può essere "condiviso" da più header, quindi non si possono aggiungere funzioni e classi a questo namespace oltre a quelle all'interno del file.h. 
 +
 +In questo caso è possibile separare l'interfaccia dall'implementazione?
 +<file cpp 39.h>
 +//l'interfaccia ANONIMA
 +namespace
 +{
 +   void fun(int x);
 +}
 +
 +</file>
 +<file cpp 38.cpp>
 +//ERRORE: l'implementazione ANONIMA non è possibile dall'esterno
 +void ::fun(int x)
 +{
 +  // codice...
 +}
 +</file>
 +Poiché non è possibile, il namespace anonimo può essere usato da un programmatore che scrive l'interfaccia di una libreria e che vuole impedirne l'implementazione dall'esterno.
 +
 +===== namespace genitori anonimi =====
 +Oltre al namespace anonimo di un singolo file, esiste anche un namespace genitore anonimo.
 +Dichiarare oggetti all'interno di questo namespace corrisponde ad usare il livello di visibilità "globale". Gli elementi globali sembrano comodi da usare perché sono sempre visibili e durano per tutta la durata del programma, ma sono di solito uno spreco e una cattiva abitudine...
 +<code> 
 +...to do
 +</code>
 +Un altro modo per definire elementi a "lunga durata" dentro una classe, è quello che utilizza [[appunti3s:possibili approfondimenti#static]]
 +
 +===== Strumenti =====
 +(cenni a make cmake http://www.kitware.com/products/protraining3.html)
 +
 +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