Lezione 6 di 7 · 18 min di lettura

Stampare come si deve

printf e i segnaposto: mettere i valori dentro le frasi, scegliere le cifre decimali, allineare le colonne.

Il problema

Finora per stampare “Ada ha 36 anni” avremmo dovuto fare tre stampe separate, e il risultato sarebbe finito su tre righe. Non va bene. Vogliamo comporre una frase con dentro dei valori.

La soluzione è io::printfn, la versione “formattata” di printn (la f sta per format):

sentence.c3
import std::io;

fn void main()
{
	String name = "Ada";
	int age = 36;
	io::printfn("My name is %s and I am %d years old.", name, age);
}
output
My name is Ada and I am 36 years old.

Il primo argomento è la stringa di formato: una frase con dei buchi. I buchi sono i segnaposto che iniziano con %. Gli argomenti successivi riempiono i buchi, in ordine: %s prende name, %d prende age.

Ecco la famiglia completa delle funzioni di stampa:

FunzioneFormattata?Va a capo?
io::print(x)nono
io::printn(x)nosì
io::printf("...", a, b)sìno
io::printfn("...", a, b)sìsì

I segnaposto principali

SegnapostoCosa stampaEsempioRisultato
%squalsiasi cosa, nella sua forma naturale%s con "ciao", 42, trueciao, 42, true
%dun numero intero%d con 4242
%fun decimale con 6 cifre dopo la virgola%f con 1.651.650000
%.2fun decimale con 2 cifre (o quante ne scrivi)%.2f con 1.651.65
%cun char come carattere%c con 'Z'Z
%xun intero in esadecimale%x con 255ff
%bun intero in binario%b con 5101
%%il simbolo % letterale"100%%"100%

Il caso dei decimali

Il segnaposto più importante da padroneggiare è %.Nf, perché 1.650000 non è quasi mai quello che vuoi mostrare:

c3
double price = 19.99;
double discount = 0.15;
double final_price = price * (1 - discount);

io::printfn("Final price: %f", final_price);
io::printfn("Final price: %.2f", final_price);
io::printfn("Discount of %.0f%%", discount * 100);
output
Final price: 16.991500
Final price: 16.99
Discount of 15%

Il numero dopo il punto è quante cifre decimali vuoi. %.0f arrotonda all’intero. E nota %% per ottenere un % vero, perché un % da solo sarebbe letto come l’inizio di un segnaposto.

Dettagli nerd Perché 1,65 «non esiste»?

Perché un double è una frazione binaria: può rappresentare esattamente solo le somme di potenze di 2 (1/2, 1/4, 1/8…). 1,65 non si può scrivere così con un numero finito di cifre, come 1/3 in base 10, e il double si ferma alla migliore approssimazione che sta nei suoi 52 bit di mantissa (lo abbiamo visto nella lezione 4 con 0.1). Per i soldi, la soluzione classica è lavorare in centesimi con un intero: int cents = 165; è esatto, sempre.

Quiz

Cosa stampa io::printfn("%d%%", 7 / 2);?

Larghezza e allineamento

Tra % e la lettera puoi mettere una larghezza minima: %5d stampa il numero in almeno 5 caratteri, riempiendo di spazi a sinistra. Con un - riempie a destra, con uno 0 riempie di zeri. Serve per fare le colonne:

c3
io::printfn("[%5d]", 42);
io::printfn("[%-5d]", 42);
io::printfn("[%05d]", 42);
io::printfn("[%8.2f]", 3.14159);
io::printfn("[%-8s]", "ab");
output
[   42]
[42   ]
[00042]
[    3.14]
[ab      ]

Le parentesi quadre sono nel testo solo per far vedere gli spazi. Con questi strumenti puoi fare una tabella ordinata:

tabella.c3
import std::io;

fn void main()
{
	io::printfn("%-10s %5s %8s", "Prodotto", "Qta", "Prezzo");
	io::printfn("%-10s %5d %8.2f", "Caffe", 2, 1.20);
	io::printfn("%-10s %5d %8.2f", "Cornetto", 3, 1.50);
	io::printfn("%-10s %5d %8.2f", "Spremuta", 1, 3.00);
}
output
Prodotto     Qta   Prezzo
Caffe          2     1.20
Cornetto       3     1.50
Spremuta       1     3.00

Costruire una riga a pezzi

printf (senza n) non va a capo, e questo permette di costruire una riga in più passaggi, per esempio dentro un ciclo (li vedremo nel Modulo 2):

c3
io::printf("%d, ", 1);
io::printf("%d, ", 2);
io::printfn("%d.", 3);
output
1, 2, 3.

Le sequenze di escape, di nuovo

Dentro la stringa di formato valgono tutte le sequenze della lezione 4. La più utile con le tabelle è \t, la tabulazione, che allinea “a scatti” di 8 caratteri:

c3
io::printfn("Name:\tAda");
io::printfn("Age:\t36");
output
Name:	Ada
Age:	36

È più grezza della larghezza esplicita (%-10s), ma per due righe al volo va benissimo.

Dettagli nerd Perché proprio 8 caratteri?

È un’eredità delle macchine da scrivere e delle telescriventi: il tasto tabulatore faceva scorrere il carrello fino al fermo successivo, e i terminali hanno adottato fermi fissi ogni 8 colonne. Il carattere \t è un singolo byte (il numero 9 nella tabella ASCII) e non contiene nessuno spazio: è il terminale che, ricevendolo, sposta il cursore al fermo successivo. Per questo Name: (5 caratteri) e Age: (4) fanno partire il valore dalla stessa colonna, la nona. Molti editor invece mostrano la tabulazione larga 4: stesso byte, disegno diverso.

Quiz

Quale chiamata stampa esattamente Total: 6.90 euro se double t = 6.9;?

Esercizio · sul tuo computer

Lo scontrino

Scrivi receipt.c3. Dichiara due costanti, COFFEE_PRICE = 1.20 e CROISSANT_PRICE = 1.50, entrambe double. Poi le quantità: int coffees = 2; e int croissants = 3;. Calcola il costo dei caffè, quello dei cornetti, il totale, e il resto se il cliente paga con 10 euro.

Stampa esattamente queste quattro righe (attenzione a spazi, x, = e alle due cifre decimali):

output
Coffee x2 = 2.40
Croissants x3 = 4.50
Total: 6.90 euro
Paid with 10 euro, change: 3.10

Le quantità devono venire dalle variabili, non scritte a mano nella stringa. Riporta l’output.

Mostra una soluzione (prima prova da solo!)
receipt.c3
import std::io;

const double COFFEE_PRICE = 1.20;
const double CROISSANT_PRICE = 1.50;

fn void main()
{
	int coffees = 2;
	int croissants = 3;
	int paid = 10;

	double coffee_cost = coffees * COFFEE_PRICE;
	double croissant_cost = croissants * CROISSANT_PRICE;
	double total = coffee_cost + croissant_cost;
	double change = paid - total;

	io::printfn("Coffee x%d = %.2f", coffees, coffee_cost);
	io::printfn("Croissants x%d = %.2f", croissants, croissant_cost);
	io::printfn("Total: %.2f euro", total);
	io::printfn("Paid with %d euro, change: %.2f", paid, change);
}

Ricapitolando

  • io::printfn("frase con %s e %d", a, b) riempie i segnaposto in ordine.
  • %s stampa qualsiasi cosa; %d qualsiasi intero; %.2f un decimale con 2 cifre; %c un carattere; %% un %.
  • Larghezza: %5d allinea a destra, %-5d a sinistra, %05d riempie di zeri.
  • Il compilatore controlla che argomenti e segnaposto corrispondano.
  • printf senza n non va a capo: utile per costruire righe a pezzi.

Sei arrivato all’ultima lezione del modulo: un riepilogo, una tabella da tenere a portata di mano e una sfida che mette insieme tutto.