Lezione 2 – Composizione di un programma C/C++



By admin on febbraio 7, 2010


In questa lezione gettiamo le basi su come deve essere strutturato un programma C/C++. Individuare le varie parti in cui è logicamente diviso. La divisione è logica e serve solo al programmatore a tenere ben distinte le diverse sezioni in modo da poterle rintracciare subito, al compilatore con interessa tale divisione (anzi, non ne è nemmeno a conoscenza!). Vedremo anche alcune differenze sintattiche tra il C e C++.

 

Composizione schematica di un programma C/C++

E' comune abitudine iniziare il programma C/C++ con la lista dei file esterni da includere nel proprio programma. Un file esterno può contenere di tutto: definizione di variabili, costanti, funzioni e per il C++ anche oggetti.
La sintassi usata in C/C++ per includere un file esterno è la seguente:

#include <file esterno>

oppure

#include "file esterno"

Ogni riga può contenere una sola direttiva #include, ed ogni direttiva può contenere un solo file. Il primo modo si utilizza per includere file di librerie contenuti nelle cartelle del compilatore, mentre il secondo modo è usato quando si devono includere file scritti da noi stessi. Il numero di #include può essere grande a piacere, ovviamente più funzioni vengono incluse nel proprio programma più il file eseguibile prodotto ed il tempo di compilazione saranno grandi.
Vediamo qualche esempio:

#include <iostream.h>
#include <stdio.h>
#include <stdlib.h>
#include "mio_file.h"

Subito dopo la lista delle direttive #include compare la lista delle direttive #define. Tale direttiva viene usata per definire le costanti (valori che non cambiano mai). Un utilizzo avanzato di questa direttiva è quello di definire con un nome anche una o più istruzioni.
La sintassi usata in C/C++ per utilizzare la direttiva #define è:

#define nome_della_costante  valore

#define nome_insieme_di_istruzioni  istruzioni_su_una_riga

Vediamo subito qualche esempio:

#define PI_GRECO 3.1415926535897932384626433832795
#define EURO_in_LIRE 1936.27
#define _2_ALLA_16 2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2
#define _2_ALLA_32 2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2 \
*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2


E' pratica comune scrivere i nomi delle costanti in maiuscolo in modo da essere ben evidenti nel codice. Le #define utilizzate con istruzioni C/C++ devono estendersi su una sola riga, se si intende scrivere su più righe bisogna terminare la riga con il carattere '\' (backslash). Infine si noti come nella definizione del nome non sono ammessi gli spazi.

Dopo le direttive #define vengono dichiarate le variabili globali. Non preoccupatevi se non capite cosa vogliono significare le diverse righe che seguiranno perché la discussione sulle variabili sarà ripresa più in là.
Un esempio completo sulla prima parte di un programma è il seguente:

#include <string.h>
#include <iostream.h>
#include <conio.h>
#define _2_ELEVATO_ALLA_17 2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2
#define GETCH getch();
int i,j;
char nome_e_cognome[50];


E' possibile definire un numero qualunque di variabili ed eventualmente quelle che hanno lo stesso tipo possono essere scritte separate da una virgola. Il ';' alla fine di ogni definizione di variabile è obbligatorio.
Il nome di una variabile può essere lungo al massimo 255 caratteri, può contenere lettere ('a',… ,'z') maiuscole o minuscole, numeri ed il carattere '_' (underscore) ma deve iniziare con una lettera oppure con il carattere '_'.

 

Funzioni

Subito dopo le variabili vengono scritte le funzioni. Come gia detto in precedenza, una funzione è una entità che prende in input un insieme di parametri (eventualmente vuoto) e ritorna un valore che possiede una certa relazione con i dati in ingresso. La sintassi da seguire è la seguente:

tipo_dato_ritornato Nome_della_funzione(tipo_dati1 dato1, tipo_dato2 dato2,...)
{
istruzioni tra cui compare return d;
}


Ad esempio, una funzione che esegue la quarta potenza del numero intero dato come input ha il seguente aspetto:

int X_alla_4(int x)
{
return x*x*x*x;
}


In un programma C++ prima di implementare la funzione, bisogna scrivere il prototipo. Quest'ultimo è semplicemente una riga di codice che stabilisce gli input e gli output della funzione. Esso non esegue alcuna azione diversa da quella da segnalare al compilatore quanto si deve aspettare successivamente nella definizione della funzione. Per i prototipi ci sono due regole da rispettare:

  • Il prototipo deve essere definito prima che il programma principale inizi.
  • Le definizioni delle funzioni devono essere poste dopo la fine del programma principale.

 

Quindi il precedente esempio va corretto nel seguente modo:

int X_alla_4(int x);

... //Altri prototipi, segue il Programma principale, il main(), che vedremo poi

int X_alla_4(int x)
{
return x*x*x*x;
}


Per richiamare una funzione è sufficiente scrivere il suo nome ed elencare tra parentesi tonde i valori da assegnare ai suoi parametri. Per l'esempio di prima:

y = X_alla_4(23); //y assumerà il valore 279841

 

Commenti

E' buona norma scrivere delle righe di commento nel programma. Un commento altro non è che una spiegazione di cosa verrà eseguito nelle prossime istruzioni o nella istruzione a sinistra del commento. Mentre state scrivendo un programma sapete cosa state facendo, ma supponete di dover riutilizzare una parte di codice gia scritto, chi vi dirà cosa fanno le righe scritte o meglio, chi vi spiegherà come utilizzare quella porzione di codice? Nessuno! A meno che non siete stati furbi a scrivere qualche commento.
Il C++ ha due modi di scrivere i commenti:

  1. Su riga singola.
  2. Su più righe.

 

Il commento a riga singola si introduce scrivendo i caratteri '//' seguiti dal testo del commento che può includere qualunque carattere. Ad esempio:

return x*x*x*x; //Ritorna il numero 'x' moltiplicato 4 volte per se stesso ;-)

Il commento su più righe invece si scrive così:

/*
La funzione che segue
calcola x elevato alla quarta
*/

int X_alla_4(int x)
{
return x*x*x*x;
}

Ovvero il commento viene iniziato con i caratteri '/*' e concluso con '*/'. Quest'ultimo tipo di commento è l'unico accettato dai compilatori C.

Oggetti

Passiamo ora alla descrizione, tra l'altro introduttiva, sugli oggetti. Un oggetto è una entità che permette di racchiudere insieme varabili e funzioni detti rispettivamente Membri Dati e Funzioni Membro. Questo tipo di entità è stato introdotto nella programmazione alcuni anni fa ed ha raccolto subito un enorme successo tant'è che sulla scia del C++ sono nati altri linguaggi ad oggetti come il JAVA, ADA e l'Object Pascal evoluzione del Pascal utilizzato nel Borland Delphi. Ma cosa ha di tanto potente la programmazione ad oggetti? Pensiamo un po' alla dimensione dei programmi di oggi, composti ad milioni di righe di codice, gestirle come un unico grande insieme di funzioni è praticamente impossibile. La potenza della OOP risiede nella capacità degli oggetti di:

  • Ereditarietà: Un oggetto, con dei propri dati e funzioni membro, può ereditare (ovvero farli diventare propri) i dati e le funzioni membro di un'altra classe diventando così ancora più potente.
  • Generalizzazione: Questo concetto è legato alla ereditarietà. Un oggetto viene progettato in modo da poter soddisfare una certa classe di problemi. Tale classe è composta da molti sotto-problemi più specifici, un oggetto può specializzarsi nella risoluzione di una sotto-classe del marco-problema iniziale e quindi eredita le proprietà dell'oggetto "padre" in questo modo mettendosi in un'ottica relativa all'oggetto più specializzato, si dice che il padre generalizza il problema.
  • Incapsulamento: L'incapsulamento non deve essere visto come il semplice raggruppamento di dati e funzioni in un'unica entità, quale l'oggetto. La potenza di questo concetto sta nella capacità di poter definire 3 tipi di incapsulamenti usabili anche contemporaneamente. I 3 modi sono:
    • Pubblico: All'esterno dell'oggetto possono essere utilizzati solo i dati e le funzioni definite come pubbliche.
    • Privato: Con questo tipo di incapsulamento si realizza l'Hiding dell'informazione ovvero, i dati e le funzioni membro vengono nascoste al mondo esterno. Solo le funzioni appartenenti all'oggetto stesso potranno utilizzare le funzioni e i dati privati.
    • Protetto: Quest'ultima modalità è legato alla ereditarietà. Come vedremo più avanti, anche un oggetto può essere ereditato in modo pubblico, privato e protetto. A seconda di come viene ereditato un oggetto i dati e le funzioni incapsulati in modo protetto saranno visibili o no al figlio.
  • Polimorfismo: Questo concetto è tra i più importanti nella programmazione ad oggetti. Alcuni linguaggi di programmazione supportano gli oggetti ma non il polimorfismo: sono dei falsi linguaggi ad oggetti. Polimorfismo significa "molte forme", nei linguaggi di programmazione vuol dire che ad una stessa funzione posso assegnare compiti diversi a seconda dell'oggetto che ha ereditato una particolare funzione detta virtuale. Questo concetto sarà esaminato meglio più avanti quando si conoscerà meglio la programmazione ad oggetti.

 

 

Differenza tra Espressione ed Istruzione

Parlando delle righe di codice C/C++ o di un altro linguaggio di programmazione  molto spesso di sente parlare di espressione e di istruzione.
Anche se non ancora abbiamo imparato a scrivere un programma C/C++ la distinzione tra i due termini può essere compresa facendo degli esempi sul linguaggio umano e che potrà essere poi applicato al C/C++ senza modifiche.

Linguaggio umano

C/C++

Espressioni:

  • La variabile A è maggiore della variabile B?
  • A maggiore di B e C uguale a D?

Espressioni:

  • A > B
  • (A>B) && (C==D)

Istruzioni:

  • Metti in Y il valore di X più 1
  • Se A maggiore è di B incrementa X altrimenti decrementa X

Istruzioni:

  • Y = X + 1;
  • if(A>B) x++; else x--;

 

Funzione main()

Qualunque programma esso sia deve avere un inizio ed una fine. In C/C++ l'inizio (o come si dice in gergo tecnico, il punto di ingresso) è dato da una particolare funzione denominata main. Vediamo subito un esempio di programma C++ vuoto ma sintatticamente corretto:

main()
{
//Dichiarazioni delle variabili locali

//istruzioni
return;  //Non è obbligatorio mettere questa istruzione
}


Il punto di uscita dal programma, ovvero l'ultima istruzione eseguita è nell'istruzione return, se presente, o nella parentesi graffa che termina la funzione main.
Ci sono diversi modi di scrivere la funzione main, inoltre quest'ultima ha anche dei parametri, ma non è obbligatorio utilizzarli e quindi metterli al contrario del C che invece bisogna inserirli anche se non vengono usati. Vediamo un modo alternativo di scrittura del main:

int main(int argc, char* argv[])
{
//Dichiarazioni delle variabili locali
//istruzioni return 0; //Adesso è obbligatorio mettere questa istruzione
}

Questo tipo di scrittura permette di scrivere programmi che accettano un input dal prompt dei comandi della shell (DOS, UNIX,…). Supponiamo che il precedente esempio faccia parte di un file chiamato maxnum.cpp, il cui file eseguibile dopo la compilazione si chiamerà maxnum.exe, nella shell sarà possibile scrivere:

C:\miei_programmi\maxnum 2 –2 8

La variabile argc contiene il numero di elementi nella riga di comando (4 nell'esempio precedente), mentre l'altra variabile, argv, contiene le 4 stringhe della riga di comando ("maxnum", "2", "-2" e "8").
Riprendiamo per un attimo il discorso sulle variabili. Nell'esempio precedente c'è il commento che accenna alle variabili locali. Una variabile si dice locale quando è dichiarata all'interno di una funzione f1. La funzione f1 che dichiara delle proprie variabili locali, può utilizzare sia quelle globali che quelle locali. Ovviamente le variabili locali di un'altra funzione f2 non sono accessibili all'interno di f1. Quanto detto vale sia per il C che per il C++ ed in generale vale per qualunque L. di P.
Il valore zero restituito dall'ultimo esempio dovrebbe essere utilizzato dal sistema operativo o dal programma che ha mandato in esecuzione un altro processo. Di solito si usa lo zero per indicare la normale terminazione del processo ed un codice diverso da zero per le condizioni di errore.





Lascia un Commento