GLG Programs fa uso di cookie per migliorare l'esperienza di navigazione degli utenti, ma non per tracciarne un profilo. Proseguendo nella navigazione, si accetta implicitamente l'utilizzo dei cookie.
[OK]Note legaliNon mi importa
Logo GLGPrograms Logo GLGPrograms

Basi di programmazione C++

[Icona della Guida] Introduzione alla programmazione C/C++

Appunti di programmazione C e C++ su GNU/Linux

Parte seconda

Strutture di controllo

Un programma sarebbe del codice statico se non ci fosse la possibilità di compiere delle scelte, saltare alcune parti o eseguirne delle altre, ripetere delle operazioni, eccetera... per questo, il C ci mette a disposizione alcuni costrutti per far pensare il nostro programma e dargli una sua struttura. Dalla programmazione procedurale si passa quindi alla programmazione strutturata.

Costrutti condizionali

In C (e C++) abbiamo a disposizione vari costrutti condizionali, che consentono di eseguire blocchi di codice solo se una condizione è verificata.

Costrutto if

if, dall'inglese se, è un costrutto che esegue il blocco successivo soltanto se l'espressione contenuta nelle parentesi tonde è vera.

...
if (a == 4) /* se a è uguale a 4 */
{
    /* ... inserire qui le istruzioni da eseguire... */
}
...

A coppia con if è possibile aggiungere else, che sta a indicare in tutti gli altri casi...; in questo modo viene eseguito il blocco di codice di if se la condizione è vera, altrimenti viene eseguito il blocco di codice di else:

...
if (a > 4) /* se a è maggiore di 4 */
{
     /* viene eseguito questo blocco, */
}
else /* altrimenti, */
{
    /* viene eseguito quest'altro blocco */
}
...

Importante: if ed else non hanno necessariamente bisogno di un blocco di istruzioni da eseguire: quando si deve eseguire una sola istruzione, per semplicità, le parentesi graffe che delimitano il blocco possono essere omesse.

Osservazione: specialmente per chi è alle prime armi, è facile commettere l'errore di valutare un'uguaglianza con l'operatore = anziché con l'operatore ==.

Il seguente programma riassume quanto visto fin'ora. Si consiglia di compilarlo, verificarne il funzionamento, capirlo e estenderlo.

/* Programma che dice se un numero è pari */
#include <stdio.h>

int main(int argc, char** argv)
{
  int n;
  printf("Inserisci il numero: "); scanf("%d", &n);

  if (n % 2) printf("Dispari");
  else printf("Pari");

  printf("\n");
  return 0;
}

Costrutto switch

Spesso capita di dover effettuare delle scelte tra un numero limitato di alternative, ad esempio, per svolgere un determinato compito in dipendenza del giorno della settimana. Per questo scopo esiste il costrutto switch.

int giornoDellaSettimana;
switch (giornoDellaSettimana)
{
  case 1: /* Istruzioni del lunedì...    */ break;
  case 2: /* Istruzioni del martedì...   */ break;
  case 3: /* Istruzioni del mercoledì... */ break;
  case 4: /* Istruzioni del giovedì...   */ break;
  case 5: /* Istruzioni del venerdì...   */ break;
  case 6: /* Istruzioni del sabato...    */ break;
  case 7: /* Istruzioni della domenica...*/ break;
  default: /* Che giorno sarà? */ break;
}

Ecco qui un esempio da compilare, verificarne il funzionamento, capire e correggere e modificare (è segnalato un errore di distrazione comune).

/* Scegli la marcia da innestare, e mostra una situazione tipo in cui utilizzarla */
#include <stdio.h>

int main()
{
  printf("Quale marcia innesti? (1-5) ");
  int m; scanf("%d", &m);

  switch (m)
  {
    case -1:
      printf("In manovra.");
      break;
    case 1:
      printf("Stai partendo.");
      break;
    case 2: case 3:                       /* Esempio di OR inclusivo */
      printf("Circolazione urbana.");
      break;
    case 4:
      printf("Sulla circonvallazione.");  /* Dimenticato del break? */
    case 5:
      printf("In autostrada.");
      break;
    default:
      printf("Questa marcia non esiste!");
      break;
  }

  printf("\n");
  return 0;
}

Costrutti iterativi

I costrutti iterativi sono particolari costrutti del C che permettono di eseguire più volte le stesse operazioni. Per questa ragione, sono più comunemente conosciuti come cicli.

Costrutto while e do...while

Il ciclo while permette di eseguire del codice finché una condizione è vera. La veridicità della condizione viene calcolata prima ancora di entrare nel ciclo, così si può fare in modo che, in determinate circostanze, il ciclo non venga eseguito nemmeno una volta.

Nel ciclo do...while, invece, la condizione viene valutata solamente alla fine del ciclo, il che implica che le istruzioni vengono eseguite almeno una volta.

...
while (condizione)
{
  /* istruzioni da eseguire finché la condizione è vera */
}
...
...
do
{
  /* istruzioni da eseguire almeno una volta, e finché la condizione è vera */
} while (condizione);
...

Ecco un programma d'esempio, come sempre da compilare, verificarne il funzionamento, capire e modificare a proprio piacere per impratichirsi.

/* Cicla finché non si immette un determinato numero */
#include <stdio.h>

int main()
{
  int r;
  do
  {
    printf("Per uscire, scrivi 5: ");
    scanf("%d", &r);
  } while (r != 5);

  return 0;
}

Costrutto for

Un altro ciclo utilizzato comunemente nella programmazione, è il ciclo for.

for (avvio; condizione; istruzione)
{
  /* ... altre istruzioni da eseguire ... */
}

Dove:

Il che implica, banalmente, che un ciclo for non è altro che un ciclo while costruito in questo modo:

avvio;
while (condizione)
{
  /* ... altre istruzioni da eseguire ... */
  istruzione;
}

Data la sua forma molto concisa, il ciclo for è particolarmente utile per ripetere del codice un numero prestabilito di volte, anche se può essere utilizzato in molti altri ambiti, ad esempio, per scorrere agevolmente una lista a basso livello. Ecco un programma d'esempio che conta da 1 fino al numero immesso, da compilare, capire e modificare a piacere:

#include <stdio.h>

int main()
{
  int n;
  printf("Fino a quanto devo contare? ");
  scanf("%d", &n);

  for (int i = 1; i <= n; ++i)
  {
    printf("%d... ", i);
  }
  printf("\n");
}

Nota: i tre campi del for non sono obbligatori, quindi possono essere omessi a piacere. Se si omettono tutti i campi, si genera un ciclo infinito.

Alienazione dei cicli

Il linguaggio C mette a disposizione due parole chiave che consentono di modificare l'esecuzione dei cicli a piacere, saltando iterazioni o interrompendo del tutto il ciclo: continue e break.

Esempio dimostrativo:

#include <stdio.h>

int main()
{
  short c;

  while (1)
  {
    printf("Inizio ciclo\n");
    printf("\t0 - continue\n\t1 - break\n\t2 - Normale\n\tScelta? ");
    scanf("%d", &c);
    if (c == 0) continue;
    if (c == 1) break;

    printf("Fine ciclo\n");
  }

  return 0;
}

Costanti

Un modo comodo per utilizzare dei dati che non devono essere modificati è utilizzare delle costanti. L'utilizzo delle costanti ha alcuni vantaggi:

Le costanti possono assumere lo stesso tipo delle variabili e si istanziano anteponendo la parola chiave const alla loro definizione come variabile. Si noti che una costante deve necessariamente essere definita assegnandole il valore.

const double pi = 3.14159265359;

Array

Detti anche vettori, o matrici quando si sviluppano su più dimensioni, l'array è una struttura che consente di memorizzare sequenzialmente più valori dello stesso tipo. Ad esempio, si vogliano memorizzare le temperature medie dei mesi dell'anno in 12 interi:

int temperatura[12]; /* allocazione in memoria di 12 int */

Ogni elemento dell'array è accessibile attraverso l'operatore [ ] (parentesi quadre) attraverso il numero della sua posizione all'interno della struttura. Si osservi che il primo valore di un array è contenuto nella locazione 0, e l'n-esimo nella locazione n - 1.

temperatura[0] = -4; /* imposta la temperatura di Gennaio a -4 */

Osservazione: quando si definisce un vettore, vengono occupate tante locazioni di memoria consecutive quante ne sono necessarie a contenerlo completamente; tuttavia esse non vengono inizializzate con nessun valore, pertanto, se si interpreta il loro contenuto, in realtà si andrà a leggere ciò che era precedentemente contenuto in tali locazioni, il che significa che, nella maggior parte dei casi, si leggeranno valori fuori contesto, casuali e apparentemente privi di senso. È quindi buona norma inizializzare un array prima di leggerne i valori; questa operazione è facilmente programmabile con un ciclo for.

Osservazione: per poter stabilire la quantità di memoria necessaria per contenere l'array, il compilatore deve conoscere il numero di elementi che esso contiene al momento della compilazione, perciò tale valore non può che essere costante. Si vedrà in seguito come gestire dinamicamente la memoria per realizzare array e altre strutture dati estensibili a piacere a tempo di esecuzione.

int n = 12;
int temperatura[n]; /* Esempio sbagliato: n non è costante */

In questo esempio, viene popolato (e successivamente mostrato) il vettore contenente le temperature medie dei vari mesi dell'anno (anche se, per semplicità, la distribuzione lineare dei valori non rappresenta il reale andamento della temperatura). Lo si compili, se ne verifichi il funzionamento, si capisca e lo si modifichi a piacere per esercizio.

#include <stdio.h>

int main()
{
  const int n = 12;
  int temperatura[n];

  for (int i = 0; i < n; ++i)
  {
    if (i < 6) temperatura[i] = 6 + 3 * i;
    else temperatura[i] = 6 + (12 - i) * 3;
  }

  printf("Mese\tTemperatura\n");
  for (int i = 0; i < n; ++i)
    printf("%d\t%d\n", i + 1, temperatura[i]);

  return 0;
}

Un modo alternativo di inizializzare un array è l'inizializzazione esplicita, indicando gli elementi tra parentesi graffe e separati da virgola, esattamente come si fa con gli insiemi:

int temperatura[] = { 6, 9, 12, 15, 18, 21, 24, 21, 18, 15, 12, 9 };

In questo caso non è necessario specificare la dimensione del vettore, perché il compilatore può facilmente dedurla. È possibile anche assegnare un valore noto ad un sottoinsieme degli elementi dell'array; in questo caso, inoltre, alle locazioni rimanenti verrà convenzionalmente assegnato un valore nullo:

int temperatura[12] = { 6, 9, 12 };

Una forma speciale per inizializzare tutte le locazioni a 0, molto concisa, è:

int temperatura[12] = { 0 };

Matrici

Viene detta matrice un vettore di vettori (o un vettore a più dimensioni). Questi nomi derivano direttamente dalle corrispondenti strutture dell'algebra, perciò risulteranno familiari a chiunque abbia anche solo letto l'introduzione di un qualunque libro di suddetta disciplina. La dichiarazione di una matrice e l'accesso ai suoi elementi avviene per mezzo dell'applicazione ripetuta dell'operatore [ ] (parentesi quadre).

int scacchiera[8][8];
printf("%d", scacchiera[0][0]);

L'argomento degli array e delle matrici verrà approfondito nel capitolo dei puntatori, dopo l'introduzione all'aritmetica dei puntatori.

C++

Una volta presa confidenza con le basi della programmazione in C, innalziamo il nostro livello di astrazione. Un buon sottoinsieme degli argomenti affrontati da ora in avanti, sono comunque validi anche in C, ma risulteranno molto meno ostici se si aggiunge quel poco di zucchero sintattico che tuttavia semplifica enormemente la scrittura del codice. Con l'espressione zucchero sintattico si denotano quei costrutti non necessari alla programmazione in sé, in quanto le operazioni da essi eseguite sono comunque fattibili con gli strumenti che si hanno a disposizione; tuttavia, questi costrutti semplificano enormemente il compito del programmatore, consentono di realizzare progetti più complessi con un approccio lineare, e aumentano la vita utile e la leggibilità del codice.

Come abbiamo già fatto con il C, compiliamo il nostro primo programma in C++. Da ora in avanti, ogni programma di questa guida verrà compilato con il compilatore C++.

La mia "seconda" compilazione

Listato del programma di esempio, da copiare, incollare in un qualunque editor, e salvare con estensione .cpp, ad esempio hello.cpp:

#include <iostream>
using namespace std;

int main()
{
  cout << "Hello world!" << endl;
  return 0;
}

Comando per compilare il programma, che invoca il compilatore g++ e che funziona in maniera del tutto analoga a gcc:

g++ hello.cpp

Come sempre, avviamo il programma con:

./a.out

e verifichiamo che stampi l'output atteso:

Hello world!

iostream

Dopo aver rivisto il concetto di flusso già affrontato nei primi capitoli, introduciamo il codice che ci permetterà di gestire i flussi più semplicemente in C++, in modo da capire subito le enormi potenzialità di questo linguaggio. In un certo senso, dovremo disimparare alcune cose apprese fino ad ora, ma ci accorgeremo che reimpararle col nuovo metodo sarà molto più facile e tuttavia rimarremo consapevoli di ciò che è mascherato all'apparenza, ma che si cela dietro le quinte. Quanto affrontato per i flussi in C++, ovviamente, non vale per il C.

iostream è la libreria fornita dal C++ per svolgere le operazioni di input e output sui flussi (Input/Output Stream), l'analoga di stdio.h. Per utilizzarla, sarà sufficiente includerla all'inizio del programma; per completezza, ma non ancora per chiarezza, si deve inoltre sapere che, in C++, dopo essere state raggruppate in librerie, le funzioni e gli altri elementi, possono essere ulteriormente raggruppati in spazi di nomi. Questo verrà spiegato meglio più avanti, ma intanto adoperiamo questo zucchero sintattico a nostro favore.

Includiamo iostream e utilizziamo lo spazio di nomi std:

#include <iostream>
using namespace std;

Output

cout è l'oggetto che rappresenta il flusso di uscita: agendo su di esso e su un altro operando per mezzo dell'operatore <<, è possibile stampare su schermo. L'operatore << è stato sovraccaricato dai programmatori della libreria iostream, cioè è in grado di accettare più tipi di dati, pertanto è possibile utilizzarlo per stampare qualunque tipo, senza che dobbiamo preoccuparci di specificarlo: sarà sufficiente separare i vari elementi ripetendo l'operatore.

int i; double d; char c;
cout << i << d << c << "Ciao";

Il seguente codice in C esegue esattamente la stessa operazione, ma è molto meno conciso e leggibile:

int i; double d; char c;
printf("%d%f%cCiao", i, d, c);

Inoltre, possono essere utilizzati tutte le sequenze di escape presenti anche nel C. Per andare a capo si può utilizzare anche la funzione alternativa endl (end line):

cout << "Vado" << endl << "a capo";

Input

A coppia con l'oggetto cout troviamo il cin, che consente di leggere dati dal flusso di ingresso e di immetterli nelle variabili. cin deve essere utilizzato con l'operatore >>, anch'esso sovraccaricato:

int i;
cin >> i;

Anche cin può leggere più dati (in realtà il merito è dell'operatore >>): nel flusso di ingresso, i dati separati da spazi o caratteri di ritorno di carrello (CR, Invio) vengono considerati come informazioni distinte.

Per esercizio, può essere utile riscrivere un paio dei programmi utilizzati fin'ora facendo uso di questi nuovi strumenti. Qualunque cosa non riguardi i flussi, come i tipi di variabili e i vari costrutti, viene ripresa dal C senza alcuna modifica. Segue un esempio che riassume in un breve programma i concetti esposti: lo si compili, si verifichi e si capisca il suo funzionamento.

#include <iostream>
using namespace std;

int main()
{
  cout << "Inserire espressione del tipo" << endl;
  cout << " X O Y" << endl;
  cout << " dove X, Y = due operandi numerici" << endl;
  cout << "      O    = un operatore a scelta tra + * - /" << endl;
  cout << "Input? ";
  double x, y, r; char o;
  cin >> x >> o >> y;

  switch(o)
  {
    case '+': r = x + y; break;
    case '-': r = x - y; break;
    case '*': case 'x': r = x * y; break;
    case '/': case ':': r = x / y; break;
    default: cout << "Operando sconosciuto!" << endl; break;
  }

  cout << "Risultato: " << r << endl;
  return 0;
}
Pagina scritta da Giovan BattistaGiovan Battista

Hai una domanda? Scrivici!
Questa pagina ti è piaciuta? Condividila!
Share on Facebook Share on Google+ Share on linkedin