Capitolo 3

Leggere i dati: l'input

I programmi diventano interessanti quando chiedono dei dati all'utente. In questo capitolo impari a leggere quello che viene scritto con readln() e a trasformarlo in numeri. Poi vedi due casi difficili: il dato che non è un numero, e i dati che finiscono.

In questo capitolo impari

Ripasso lampo: le variabili e i conti

Serve il capitolo 2: variabili, tipi e conversioni. Se sbagli, torna a rileggere quella sezione prima di andare avanti. Le virgolette del capitolo 1 tornano qui: readln() dà sempre testo, come una stringa fra virgolette.

Che cosa stampa questo programma?

fun main() {
    var vite = 3
    vite = vite - 1
    vite = vite - 1
    println("Vite rimaste: $vite")
}
Vite rimaste: 3No: vite = vite - 1 cambia davvero il valore della variabile.
Vite rimaste: 1Esatto: da 3 si toglie 1 due volte, quindi si arriva a 1.
Un errore: vite è una costanteIn questo programma vite è dichiarata con var, quindi si può modificare.

Il valore di una variabile cambia solo quando il programma esegue un'assegnazione. Per sapere quanto vale alla fine bisogna leggere le istruzioni nell'ordine in cui verranno eseguite.

Che cosa stampa questo programma?

fun main() {
    val a = "3"
    val b = "4"
    println(a + b)
}
7No: "3" e "4" sono testo. Il + li attacca, non li somma.
34Esatto: due stringhe messe insieme. Per sommarle servirebbe "3".toInt() + "4".toInt().
Un errore, perché non si possono sommare due testiKotlin non dà errore: unisce i due pezzi in un testo solo.

Le virgolette fanno la differenza: 3 + 4 è 7, "3" + "4" è "34". Nel capitolo di oggi readln() dà sempre testo: senza toInt() o toDouble() i «numeri» letti si attaccano invece di sommarli.

Dove si scrivono i dati

Nella console sotto il codice. Quando il programma arriva a readln(), lo stato diventa ⏸ in attesa di dati… e il cursore si ferma in fondo all'output. Scrivi il valore e premi Invio. La prima readln() prende il primo valore, la seconda il secondo, e così via.

I dati li scrivi sempre tu. Se vuoi solo vedere in fretta il risultato previsto, usa il pulsante ⏎ prova con i dati dell'esempio.

Quando i dati finiscono (tasto ⏹ fine dei dati, oppure Ctrl+D / su Mac Cmd+D), readln() si ferma con un errore. readlnOrNull() invece dà null: è la differenza della sezione 3.4.

3.1 readln: leggere una riga di testo

readln() legge una riga di quello che l'utente scrive e la restituisce come testo (String). Si usa sempre con una variabile: val nome = readln(). Il valore che ottieni non può essere null: se non c'è niente da leggere, il programma si ferma con un errore invece di darti un valore rotto.

Prima la domanda con print, poi la lettura con readln

L'abitudine più comune nei programmi di console è stampare la domanda con print("Nome: ") — senza andare a capo — e leggere subito dopo con readln(): così l'utente scrive il suo dato sulla stessa riga della domanda. È esattamente quello che vedi nella console di questo manuale: il cursore si posiziona in coda all'output, proprio dove la readln() sta leggendo. Con println invece la risposta finirebbe sulla riga sotto la domanda: funziona lo stesso, ma è meno leggibile.

L'esempio 1 stampa la domanda con println, quindi la risposta va sulla riga sotto. Funziona. L'abitudine più comoda resta quella del riquadro: print e poi readln(), così domanda e risposta stanno sulla stessa riga.

Quante righe servono?

Tanti valori quanti sono le readln() del programma. Nell'esempio 2 servono due valori: prima il nome, poi la città. La console te ne chiede uno per volta: dopo il primo Invio il programma riparte, arriva alla seconda readln() e si ferma di nuovo ad aspettare il secondo valore, sempre con il cursore in coda all'output.

E se ti viene chiesto un dato che non vuoi dare? Puoi scrivere qualsiasi cosa: i valori sono tuoi e puoi sperimentare quanto vuoi. Se invece vuoi proprio vedere il risultato previsto dall'esempio senza digitare, usa ⏎ prova con i dati dell'esempio.

3.2 Trasformare il testo in numeri

Quello che si legge con readln() è sempre una String, anche se contiene cifre. Per fare conti serve toInt() (numeri interi) o toDouble() (numeri decimali). Si scrivono subito dopo la lettura: readln().toInt().

  1. Nella console vedi anche i valori che scrivi tu, uno per riga: fanno parte dell'output, come in una console vera.
  2. Le due readln() leggono le due righe nell'ordine: la prima riga va in a, la seconda in b.
  3. toInt() trasforma il testo in numero: senza di lui 12 + 30 diventerebbe 1230.

Che cosa stampa questo programma, se l'utente scrive 3 e poi 4?

fun main() {
    val a = readln()
    val b = readln()
    println(a + b)
}
7No: readln() restituisce testo, quindi a + b unisce i due testi invece di sommarli.
34Esatto: i due testi vengono attaccati. Per sommarli servirebbe readln().toInt().
Un errore di conversioneNon c'è nessuna conversione in questo programma: nessun errore, ma nemmeno una somma.

Le variabili lette con readln() sono String: finché non le converti con toInt() o toDouble(), sono testo.

Il punto, non la virgola

Kotlin vuole il punto per i numeri decimali: 1.75 va bene, 1,75 fa apparire l'errore "Non posso convertire ... in un numero decimale".

Stringa o numero?

readln() dà testo: se dimentichi toInt(), l'operatore + attacca i testi invece di sommarli. Con i due valori 3 e 4: readln() + readln() produce "34", mentre readln().toInt() + readln().toInt() produce 7.

3.3 Quando il dato non è un numero: toIntOrNull

Se l'utente scrive pippo dove ti aspetti un numero, toInt() fa fermare il programma con un errore. Quando l'input può essere sbagliato si usa toIntOrNull() (o toDoubleOrNull()): fa la conversione se possibile, altrimenti restituisce null. Unendolo all'operatore ?: si sceglie un valore di riserva.

  1. toIntOrNull() prova a convertire pippo: non ci riesce e restituisce null.
  2. L'operatore ?: legge quel null e mette al suo posto il valore di riserva 0.
  3. Quindi n vale 0 e il programma stampa 1, senza fermarsi con un errore.
Il comando da ricordare

readln().toIntOrNull() ?: 0 si legge: "leggi una riga, prova a convertirla in numero intero; se non è un numero, usa 0". È il modo più robusto di leggere un numero senza far fermare il programma. Con i decimali è readln().toDoubleOrNull() ?: 0.0.

3.4 Quando i dati finiscono: readlnOrNull e il valore null

readln() è comodissimo, ma ha un difetto: se i dati sono finiti il programma si ferma con un errore. Quando i dati possono mancare si usa readlnOrNull(), che restituisce una String?: una stringa che può anche valere null, cioè "nessun valore".

Kotlin non ti lascia usare un valore che potrebbe essere null senza dirlo: gli operatori ?. e ?: servono proprio a questo.

ScritturaTipoSignificato
readln()String"leggi una riga, sono sicuro che c'è" (errore se i dati sono finiti)
readlnOrNull()String?"leggi una riga se c'è; altrimenti dammi null"
readlnOrNull() ?: "vuoto"String"se non c'è niente, usa questo valore di riserva"
readlnOrNull()?.toIntOrNull() ?: 0Int"converti solo se il valore c'è; in ogni caso dammi un numero"
readlnOrNull()!!String"sono sicuro al 100% che non è null" (se sbagli, errore)
Prova a premere Ctrl+D

Nell'esempio 7 il programma chiede tre valori. Se al terzo, invece di scrivere, premi Ctrl+D (o il pulsante ⏹ fine dei dati), readlnOrNull() restituisce null e l'operatore !! fa apparire l'errore "Il valore è null: non posso usarlo con !!". È il suo mestiere: !! dice "qui non può mancare niente", e quando manca ti avverte invece di lasciar passare un valore sbagliato. Con readln() non serve mai scrivere !!.

Il programma esegue val s = readlnOrNull() senza che ci sia più niente da leggere (dati finiti). Quanto vale s?

"" (stringa vuota)No: una riga vuota è un dato vero e proprio. Qui non c'è proprio nessun dato.
nullEsatto: se i dati sono finiti, readlnOrNull() non ha una riga da dare. Al suo posto mette null, cioè «nessun valore».
Il programma si ferma con un erroreQuesto succede con readln(), non con readlnOrNull(): è proprio la differenza fra i due comandi.

Una riga vuota ("") è un dato vero. null è un'altra cosa: il dato non c'è. Per usare s in sicurezza servono ?. e ?:, che hai appena visto nella tabella.

Che cosa fa il punto interrogativo

testo?.uppercase() significa "esegui uppercase() solo se testo non è null, altrimenti dà null"; il ?: che segue mette il testo di riserva. Senza il ?. Kotlin segnalerebbe subito un errore, perché testo potrebbe non esserci. Qui interessa solo il ?.: i metodi delle stringhe come uppercase(), length e first() sono spiegati per davvero nel capitolo 8. Prova a premere Ctrl+D invece di scrivere: vedrai uscire (nessun testo), 0 e null.

3.5 readLine: il comando dei vecchi tutorial

In molti esempi che trovi online si vede readLine(): fa la stessa cosa di readlnOrNull(), cioè restituisce una String? che può essere null. La documentazione ufficiale di Kotlin consiglia di usare readln() al posto di readLine()!! e readlnOrNull() al posto di readLine(): sono più corti e più chiari. In questo manuale usiamo i comandi nuovi; se incontri readLine(), traduci così:

Codice vecchioCodice da scrivere oggi
readLine()!!readln()
readLine()readlnOrNull()
readLine()!!.toInt()readln().toInt()
readLine() ?: "vuoto"readlnOrNull() ?: "vuoto"
readInt(), readDouble()non esistono in Kotlin: usa readln().toInt(), readln().toDouble()

3.6 Un programma completo con input e output

Mettiamo insieme tutto: lettura dei dati, conversioni, valori di riserva, calcoli e stampa ordinata.

L'utente può scrivere una parola al posto del numero. Quale riga ti dà comunque un numero e va avanti?

readln().toInt()Questa fa fermare il programma con un errore appena il testo non è un numero.
readln().toIntOrNull() ?: 0Esatto: se la conversione non riesce si usa un valore di riserva e il programma continua.
readln()!!!! non converte niente in numero: serve solo a dire "sono sicuro che non è null", e qui non c'entra.

Per un numero che può arrivare sbagliato: toIntOrNull() e, se manca, il valore di riserva con ?:.

3.7 Riepilogo

ScriviSignificato
val s = readln()una String letta dall'input
val n = readln().toInt()un numero intero (Int)
val x = readln().toDouble()un numero decimale (Double)
readln().toIntOrNull() ?: 0un intero, oppure 0 se il dato non è un numero
val s = readlnOrNull()una String? (può essere null)
readlnOrNull() ?: "vuoto"una String, con valore di riserva
readlnOrNull()?.length ?: 0uso sicuro di un valore che può mancare
Errori tipici del terzo capitolo

3.8 Esercizi guidati

Chiedi tre voti (possono avere decimali), calcola la somma e la media e stampa tre righe: i voti letti, la somma e la media. Attenzione: la media deve avere i decimali. Con 6, 7.5 e 9 le tre righe sono Voti letti: 6.0, 7.5, 9.0, Somma: 22.5 e Media: 7.5.

I voti sono già Double, quindi la divisione per 3 è decimale: val media = (voto1 + voto2 + voto3) / 3. Le parentesi servono!

  1. readln().toDouble() legge una riga di testo e la converte subito in numero decimale.
  2. Senza le parentesi, voto1 + voto2 + voto3 / 3 dividerebbe solo il terzo voto per 3, perché la divisione ha la precedenza sulla somma.
  3. Se i voti fossero stati letti con toInt(), la media 7 / 2 avrebbe perso i decimali. Leggendo subito dei Double il problema non si presenta.

Un centro stampa chiede il nome del cliente, il numero di fotocopie e il costo di una fotocopia. Dopo le domande, una riga vuota e un preventivo di quattro righe: il titolo ------ PREVENTIVO ------, poi nome, numero di copie, totale da pagare. Con Luca, 12 copie e 0.25 euro l'una il totale è 3.0 euro. Se l'input non è valido (per esempio il numero di copie non si legge), usa 0 e vai avanti senza errori.

Il totale è copie * costo: moltiplicando un Int per un Double ottieni un Double.

  1. readlnOrNull()?.toIntOrNull() ?: 0 gestisce tre casi: dato presente e numerico, dato non numerico, dato assente. In tutti i casi copie è un numero.
  2. Il primo ?. lavora sulla String? di readlnOrNull(): se il dato manca, salta toIntOrNull() e si arriva subito al ?: 0.
  3. println() senza argomenti lascia una riga vuota, che separa le domande dal preventivo.
  4. copie * costo è il prodotto fra un Int e un Double: Kotlin lo accetta e restituisce un Double.

Il cinema vende biglietti interi e ridotti. Chiedi in ordine: nome del cliente, quantità di biglietti interi, prezzo intero, prezzo ridotto. Poi calcola: il costo dei biglietti interi, il costo di 2 biglietti ridotti e il totale. Dopo le domande, una riga vuota e lo scontrino: titolo ====== CINEMA ======, cliente, riga degli interi, riga dei ridotti, totale. Con Anna, 2 interi a 10.0 e ridotti a 5.0 il totale è 30.0 euro.

readln().toInt() legge e converte in una sola riga: non serve nessun !!. Il costo dei ridotti è 2 * prezzoRidotto.

  1. readln() legge una riga garantendo che ci sia: restituisce una String e non serve nessun !!.
  2. readln().toInt() legge e converte in un solo passaggio; se il dato non è un numero il programma si ferma con un messaggio chiaro (in alternativa readln().toIntOrNull() ?: 0).
  3. Il prezzo è un Double perché può avere i centesimi: readln().toDouble().
  4. I calcoli intermedi in variabili (costoInteri, costoRidotti) rendono lo scontrino più chiaro da scrivere e da correggere.

Scrivi da solo un programma che chiede tre dati e li legge in quest'ordine: il nome di una città, la temperatura minima e la temperatura massima (numeri decimali). Poi stampa una riga vuota e altre tre righe: la città, la temperatura media e l'escursione (cioè la massima meno la minima).

Con i dati Roma, -2.5, 7.5 le tre righe finali devono essere esattamente: Città: Roma, Media: 2.5 gradi e Escursione: 10.0 gradi (prima di queste tre, una riga vuota). Se una temperatura non è un numero valido (per esempio l'utente scrive pippo), considerala 0.0 e vai avanti senza fermare il programma. Se manca il nome della città, usa sconosciuta.

Leggi ogni temperatura con readlnOrNull()?.toDoubleOrNull() ?: 0.0: il ?. e il ?: coprono sia il dato assente sia il dato non numerico. Calcola prima media ed escursione, poi stampale.

  1. Le tre letture vanno fatte nello stesso ordine in cui il programma le chiede: nome, minima, massima.
  2. readlnOrNull()?.toDoubleOrNull() ?: 0.0 è la forma robusta: se il dato manca o non è un numero, al posto del valore arriva 0.0 e il programma continua.
  3. La media è (minima + massima) / 2: le parentesi servono, altrimenti verrebbe minima + massima / 2.
  4. L'escursione è la differenza fra massima e minima: con 7.5 e -2.5 vale 10.0.

Il programma deve chiedere il nome, l'anno di nascita e l'anno in cui siamo, e stampare l'età, ma non funziona. Eseguilo e, quando la console aspetta, scrivi Giulia e premi Invio, poi 2008, poi l'anno in cui siamo: leggi il messaggio di errore e correggi, fino a ottenere Giulia, 18 anni.

readln() restituisce sempre testo: per fare la sottrazione quel testo deve diventare un numero.

  1. readln() dà una String, anche quando contiene cifre.
  2. Senza .toInt(), annoAttuale - anno non è una sottrazione fra numeri: il programma si ferma con un errore di tipo.
  3. Con readln().toInt() il valore diventa un Int e il conto funziona: con i dati di prova 2026 - 2008 = 18. L'anno attuale non è scritto nel programma, ma letto dall'input: così non invecchia.
  4. Se i dati potessero mancare o non essere numerici, la forma robusta sarebbe readlnOrNull()?.toIntOrNull() ?: 0.
  • so leggere un dato con readln() e so spiegare che tipo ottengo
  • so convertire un dato letto in numero con toInt() e toDouble()
  • so usare toIntOrNull() con ?: per non far fermare il programma
  • so spiegare che differenza c'è fra readln() e readlnOrNull()
  • so usare ?., ?: e so perché !! è pericoloso
  • so scrivere i dati nella console del manuale e so dichiarare la fine dei dati
  • so leggere un messaggio di errore e correggere il programma da solo