Introduzione all'Input e Output in C++

Concetti Chiave
  • C++ non definisce istruzioni di input/output (I/O); invece, fornisce una libreria standard per l'I/O.
  • La libreria iostream fornisce gli oggetti cin (input standard) e cout (output standard) per gestire l'I/O.
  • L'operatore << viene utilizzato per scrivere su uno stream di output, mentre l'operatore >> viene utilizzato per leggere da uno stream di input.
  • Un flusso (stream) è una sequenza di caratteri letti da o scritti verso un dispositivo I/O.

Introduzione all'Input/Output

Il linguaggio C++ non definisce alcuna istruzione per effettuare operazioni di input o output (I/O).

Invece, C++ include una libreria standard estesa che fornisce I/O (e molte altre funzionalità). Per molti scopi, compresi gli esempi di questo corso, è sufficiente conoscere solo pochi concetti e operazioni di base della libreria I/O.

La maggior parte degli esempi di questo corso utilizza la libreria iostream. Fondamentali per la libreria iostream sono due tipi chiamati istream e ostream, che rappresentano rispettivamente flussi di input e di output. Un flusso è una sequenza di caratteri letti da o scritti verso un dispositivo I/O. Il termine flusso è inteso a suggerire che i caratteri vengano generati, o consumati, in modo sequenziale nel tempo.

Oggetti di Input e Output Standard

La libreria definisce quattro oggetti I/O.

Per gestire l'input, usiamo un oggetto di tipo istream chiamato cin (pronunciato see‑in). Questo oggetto è anche indicato come input standard. Per l'output, usiamo un oggetto ostream chiamato cout (pronunciato see‑out). Questo oggetto è anche noto come output standard. La libreria definisce inoltre due altri oggetti ostream, chiamati cerr e clog (pronunciati see‑err e see-log, rispettivamente). Tipicamente usiamo cerr, indicato come errore standard, per messaggi di avvertimento e di errore, e clog per informazioni generali sull'esecuzione del programma.

Normalmente, il sistema associa ciascuno di questi oggetti alla finestra in cui il programma è eseguito. Così, quando leggiamo da cin, i dati sono letti dalla finestra in cui il programma è in esecuzione, e quando scriviamo su cout, cerr o clog, l'output è scritto nella stessa finestra.

Un Programma che Usa la Libreria I/O

Scriviamo un primo semplice programma che usa la libreria I/O e che ci permette di familiarizzare con essa.

Supponiamo di voler sommare due numeri. Usando la libreria I/O, possiamo estendere il nostro programma principale visto nella lezione precedente per chiedere all'utente due numeri e poi stampare la loro somma:

#include <iostream>

int main()
{
    std::cout << "Inserisci due numeri:" << std::endl;

    int valore1 = 0, valore2 = 0;

    std::cin >> valore1 >> valore2;

    std::cout << "La somma di " << valore1 << " e " << valore2
              << " è " << valore1 + valore2 << std::endl;

    return 0;
}

Questo programma inizia stampando

Inserisci due numeri:

sullo schermo dell'utente e poi attende l'input da parte dell'utente. Se l'utente inserisce

3 7

seguito dalla pressione di Invio, il programma produce il seguente output:

La somma di 3 e 7 è 10

Adesso analizziamo questo programma.

La prima riga del nostro programma:

#include <iostream>

informa il compilatore che vogliamo utilizzare la libreria iostream. Il nome tra parentesi angolari (iostream in questo caso) fa riferimento a un header. Ogni programma che utilizza una funzionalità di libreria deve includere il relativo header. La direttiva #include deve essere scritta su una singola riga: il nome dell'header e la direttiva #include devono comparire sulla stessa riga. In generale, le direttive #include devono apparire al di fuori di qualsiasi funzione. Tipicamente, inseriamo tutte le direttive #include di un programma all'inizio del file sorgente.

Scrivere su uno stream

La prima istruzione nel corpo di main esegue un'espressione. In C++ un'espressione produce un risultato ed è composta da uno o più operandi e (di solito) un operatore. Le espressioni in questa istruzione usano l'operatore di output (l'operatore <<) per stampare un messaggio sull'output standard:

std::cout << "Inserisci due numeri:" << std::endl;

L'operatore << prende due operandi: l'operando di sinistra deve essere un oggetto ostream; l'operando di destra è un valore da stampare. L'operatore scrive il valore fornito sull'ostream indicato. Il risultato dell'operatore di output è il suo operando di sinistra. Cioè, il risultato è l'ostream su cui abbiamo scritto il valore.

La nostra istruzione di output usa l'operatore << due volte. Poiché l'operatore restituisce il suo operando di sinistra, il risultato del primo operatore diventa l'operando di sinistra del secondo. Di conseguenza, possiamo concatenare richieste di output. Pertanto, la nostra espressione è equivalente a

(std::cout << "Inserisci due numeri:") << std::endl;

Ogni operatore nella catena ha lo stesso oggetto come operando di sinistra, in questo caso std::cout. In alternativa, possiamo generare lo stesso output usando due istruzioni:

std::cout << "Inserisci due numeri:";
std::cout << std::endl;

Il primo operatore di output stampa un messaggio all'utente. Quel messaggio è un letterale stringa, cioè una sequenza di caratteri racchiusa tra virgolette doppie. Il testo tra le virgolette è stampato sull'output standard.

Il secondo operatore stampa endl, che è un valore speciale chiamato manipolatore. Scrivere endl ha l'effetto di terminare la riga corrente e di svuotare il buffer associato a quel dispositivo. Svuotare il buffer garantisce che tutto l'output generato finora dal programma sia effettivamente scritto sul flusso di output, anziché rimanere in memoria in attesa di essere scritto.

I programmatori aggiungono spesso istruzioni di stampa durante il debugging. Tali istruzioni dovrebbero sempre svuotare il flusso. Altrimenti, se il programma si arresta in modo anomalo, l'output potrebbe rimanere nel buffer, portando a inferenze errate su dove il programma sia andato in crash.

Usare i Nomi della Libreria Standard

I lettori attenti noteranno che questo programma usa std::cout e std::endl anziché solo cout e endl. Il prefisso std:: indica che i nomi cout e endl sono definiti all'interno del namespace chiamato std. I namespace ci permettono di evitare collisioni involontarie tra i nomi che definiamo e gli usi di quegli stessi nomi all'interno di una libreria. Tutti i nomi definiti dalla libreria standard sono nel namespace std.

Un effetto collaterale dell'uso di un namespace da parte della libreria è che, quando usiamo un nome della libreria, dobbiamo indicare esplicitamente che vogliamo il nome dal namespace std. Scrivere std::cout utilizza l'operatore di scope (l'operatore ::) per dire che vogliamo usare il nome cout definito nel namespace std. Vedremo, nelle prossime lezioni, un modo più semplice per accedere ai nomi della libreria.

Leggere da uno stream

Dopo aver chiesto all'utente un input, vogliamo leggere quell'input. Iniziamo definendo due variabili chiamate valore1 e valore2 per contenere l'input:

int valore1 = 0, valore2 = 0;

Definiamo queste variabili come tipo int, un tipo built‑in che rappresenta gli interi. Le inizializziamo anche a 0. Quando inizializziamo una variabile, le assegniamo il valore indicato nello stesso momento in cui la variabile viene creata.

L'istruzione successiva

std::cin >> valore1 >> valore2;

legge l'input. L'operatore di input (l'operatore >>) si comporta analogamente all'operatore di output. Prende un istream come operando di sinistra e un oggetto come operando di destra. Legge dati dal istream fornito e memorizza ciò che è stato letto nell'oggetto fornito. Come l'operatore di output, anche l'operatore di input restituisce il suo operando di sinistra come risultato. Pertanto, questa espressione è equivalente a

(std::cin >> valore1) >> valore2;

Poiché l'operatore restituisce il suo operando di sinistra, possiamo combinare una sequenza di richieste di input in un'unica istruzione. La nostra operazione di input legge due valori da std::cin, memorizzando il primo in valore1 e il secondo in valore2. In altre parole, la nostra operazione di input si esegue come

std::cin >> valore1;
std::cin >> valore2;

Completare il Programma

Rimane da stampare il risultato:

std::cout << "La somma di " << valore1 << " e " << valore2
          << " è " << valore1 + valore2 << std::endl;

Questa istruzione, sebbene più lunga di quella che ha richiesto l'input all'utente, è concettualmente simile. Stampa ciascuno dei suoi operandi sull'output standard. Ciò che è interessante in questo esempio è che gli operandi non sono tutti dello stesso tipo. Alcuni operandi sono letterali stringa, come "La somma di ". Altri sono valori int, come valore1, valore2 e il risultato dell'espressione aritmetica valore1 + valore2. La libreria definisce versioni degli operatori di input e output che gestiscono operandi di ciascuno di questi tipi differenti.

Esercizi

  • Scrivere un programma per stampare Ciao Mondo! sull'output standard.

    Soluzione:

    #include <iostream>
    
    int main()
    {
        std::cout << "Ciao Mondo!" << std::endl;
        return 0;
    }
    
  • Il nostro programma ha usato l'operatore di addizione + per sommare due numeri. Scrivere un programma che usi l'operatore di moltiplicazione * per stampare invece il prodotto.

    Soluzione:

    #include <iostream>
    
    int main()
    {
        int valore1 = 0, valore2 = 0;
        std::cout << "Inserisci due numeri: ";
        std::cin >> valore1 >> valore2;
        std::cout << "Il prodotto di " << valore1 << " e " << valore2
                  << " è " << valore1 * valore2 << std::endl;
        return 0;
    }
    
  • Abbiamo scritto l'output in un'unica istruzione grande. Riscrivere il programma usando un'istruzione separata per stampare ciascun operando.

    Soluzione:

    #include <iostream>
    
    int main()
    {
        int valore1 = 0, valore2 = 0;
        std::cout << "Inserisci due numeri: ";
        std::cin >> valore1 >> valore2;
        std::cout << "La somma di ";
        std::cout << valore1;
        std::cout << " e ";
        std::cout << valore2;
        std::cout << " è ";
        std::cout << valore1 + valore2;
        std::cout << std::endl;
        return 0;
    }
    
  • Spiegare se il seguente frammento di programma è legale.

    std::cout << "La somma di " << valore1;
            << " e " << valore2;
            << " è " << valore1 + valore2 << std::endl;
    

    Se il programma è legale, cosa fa? Se il programma non è legale, perché no? Come lo si potrebbe correggere?

    Soluzione: Il frammento di programma non è legale. Ogni riga dopo la prima inizia con l'operatore di output <<, che non ha un operando di sinistra. Per correggere il frammento, ogni riga deve iniziare con std::cout, come in:

    std::cout << "La somma di " << valore1;
    std::cout << " e " << valore2;
    std::cout << " è " << valore1 + valore2 << std::endl;