Riepilogo
Uno std::string conserva byte, non lettere, e nel testo di oggi una lettera può occupare da uno a quattro byte. Questa lezione separa tre cose che il parlare quotidiano confonde: il carattere astratto, che Unicode identifica con un numero chiamato punto di codice; i byte con cui UTF-8 lo codifica; e la figura che lo schermo disegna. Spiega come UTF-8 ripartisce i bit di un punto di codice fra i suoi byte, perché la parola spagnola «año» (anno) ne occupa quattro e come si contano i caratteri di una stringa senza decodificarla. Mostra poi quali operazioni rompono il testo (invertire byte per byte, passare alle maiuscole byte per byte, tagliare a metà di un carattere), quali funzionano senza alcuna cautela (conservare, copiare, concatenare, cercare, scrivere) e che cosa non fa la libreria standard di C++23: le maiuscole fuori da ASCII, l’ordine alfabetico secondo la lingua, la normalizzazione.
Obiettivi di apprendimento
- Spiegare come UTF-8 codifica un punto di codice in uno a quattro byte, e decodificare a mano i byte della «ñ».
- Prevedere la dimensione in byte di una stringa con lettere non ASCII, come
"año"(4) o"programación"(13). - Contare i caratteri di una stringa UTF-8 contando i byte che non sono di continuazione, con la condizione
(c & 0xC0) != 0x80. - Riconoscere le operazioni che rompono il testo UTF-8 (invertire byte per byte,
std::toupperper byte, tagliare a metà di un carattere) e decidere che cosa fare in ciascun caso.
Caratteri, punti di codice e byte
Tre livelli
La lezione Caratteri, booleani, conversioni e costanti ha presentato il char come un intero di un byte che si scrive come lettera secondo la tabella ASCII, e ha avvertito che 'ñ' non vi entra; la lezione Compilare, eseguire e configurare ha mostrato che "año" occupa quattro byte e ha preparato la console per scriverlo. Questa lezione spiega perché. ASCII assegna un numero a 128 caratteri, da 0 a 127, che stanno in sette bit: le lettere dell’inglese, le cifre, i segni di punteggiatura e alcuni codici di controllo. Ora, il testo reale contiene la ñ, accenti, dieresi, lettere greche, ideogrammi cinesi, segni matematici ed emoji, che sommano centinaia di migliaia. Ne segue che occorrono, primo, una tabella molto più grande, e secondo, un modo di scrivere i suoi numeri in byte.
Conviene distinguere tre livelli. Il primo è il carattere astratto: la lettera «ñ minuscola», indipendentemente da come la si conservi o la si disegni. Unicode è lo standard che assegna a ogni carattere astratto un numero, il suo punto di codice, che si scrive U+ seguito da almeno quattro cifre esadecimali: la «ñ» è U+00F1 (241), la «A» è U+0041 (65, lo stesso numero che in ASCII), il «€» è U+20AC (8364). I punti di codice vanno da 0 a U+10FFFF, poco più di un milione. Il secondo livello è la codifica: la regola che converte ogni punto di codice in una successione di byte. UTF-8 è la codifica che usano il web, i sistemi attuali e questo corso. Il terzo livello è la rappresentazione grafica: la figura che il carattere tipografico disegna sullo schermo, che può cambiare da un tipo di carattere all’altro senza che cambi il carattere.
Se è così, un dato testuale non è un testo finché non se ne conosce la codifica: i byte c3 b1 sono la «ñ» in UTF-8, ma letti con la tabella di una console antica sono due simboli senza relazione con essa, che è il guasto che la lezione 1.4 ha insegnato a diagnosticare. La stessa questione, vista dalla composizione dei documenti, compare nella lezione «Lingue e caratteri» del corso LaTeX per la Scrittura Accademica.
Vedere i byte
Per vedere i byte di una stringa basta percorrerla: uno std::string e uno std::string_view sono sequenze di char, e il ciclo for basato su intervallo ne consegna i byte uno per uno. Per scriverli in esadecimale serve la specifica di formato {:02x} (due cifre esadecimali, con zero a sinistra). Come con {:d} nella lezione 2.4, e per la stessa correzione dello standard adottata nel 2023 e applicata da GCC 15, {:02x} scrive un char come byte senza segno. La regola conta, perché char ha segno in GCC su x86-64: il byte c3, convertito in int, è il numero , che {:02x} scriverebbe -3d.
#include <print>#include <string_view>// Byte di un testo.// Precondizione: nessuna.// Postcondizione: scrive una riga con text, la sua dimensione in byte e ogni byte in esadecimale.void printBytes(std::string_view text){ std::print("{:<4}{}:", text, text.size()); for (const char c : text) { std::print(" {:02x}", c); } std::println("");}int main(){ printBytes("A"); printBytes("ñ"); printBytes("€"); printBytes("😀"); printBytes("año");}A 1: 41ñ 2: c3 b1€ 3: e2 82 ac😀 4: f0 9f 98 80año 4: 61 c3 b1 6fOgni riga mostra il testo, la sua dimensione e i suoi byte. Un carattere per ogni lunghezza: la «A» occupa un byte, la «ñ» due, il «€» tre e l’emoji quattro. La parola «año» occupa quattro byte, 61, c3 b1 e 6f, e il suo size() è 4, non 3. Si osservi che la «A» è il byte 41, lo stesso che in ASCII: UTF-8 scrive i 128 caratteri di ASCII esattamente come ASCII.
La figura segue la codifica della «ñ» dall’alto in basso: il punto di codice, i suoi bit, la ripartizione di quei bit e i due byte risultanti, con i bit di prefisso evidenziati.
Dimostrazione
La «ñ» è il punto di codice
U+00F1, cioè .In binario, si scrive
11110001. È maggiore di 127 e minore di 2048, sicché UTF-8 lo scrive in due byte, che trasportano undici bit: si completa con zeri a sinistra fino a undici,00011110001.Gli undici bit si ripartiscono in due gruppi: i primi cinque,
00011, e gli ultimi sei,110001.Il primo byte porta il prefisso
110, che annuncia un carattere di due byte, seguito dai cinque bit del primo gruppo:11000011. Il secondo porta il prefisso10, che segna un byte di continuazione, seguito dai sei bit restanti:10110001.In esadecimale,
1100 0011èc3e1011 0001èb1: i due byte che il programma precedente ha scritto per la «ñ». Il cambiamento di base fra binario ed esadecimale, quattro bit alla volta, è quello della lezione «Sistemi di numerazione posizionale» di Algebra e Geometria I.
Caricamento del contenuto…