Resumen
Un std::string guarda bytes, no letras, y en el texto de hoy una letra puede ocupar de uno a cuatro bytes. Esta clase separa tres cosas que el habla cotidiana confunde: el carácter abstracto, que Unicode identifica con un número llamado punto de código; los bytes con que UTF-8 lo codifica, y la figura que la pantalla dibuja. Explica cómo UTF-8 reparte los bits de un punto de código entre sus bytes, por qué «año» ocupa cuatro y cómo se cuentan los caracteres de una cadena sin descodificarla. Muestra después qué operaciones rompen el texto (invertir byte a byte, pasar a mayúsculas byte a byte, cortar en medio de un carácter), cuáles funcionan sin cuidado alguno (guardar, copiar, concatenar, buscar, escribir) y qué no hace la biblioteca estándar de C++23: mayúsculas fuera de ASCII, orden alfabético según la lengua, normalización.
Objetivos de aprendizaje
- Explicar cómo UTF-8 codifica un punto de código en uno a cuatro bytes, y descodificar a mano los bytes de la «ñ».
- Predecir el tamaño en bytes de una cadena con letras no ASCII, como
"año"(4) o"programación"(13). - Contar los caracteres de una cadena UTF-8 contando los bytes que no son de continuación, con la condición
(c & 0xC0) != 0x80. - Identificar las operaciones que rompen el texto UTF-8 (invertir byte a byte,
std::toupperpor byte, cortar en medio de un carácter) y decidir qué hacer en cada caso.
Caracteres, puntos de código y bytes
Tres niveles
La clase Caracteres, booleanos, conversiones y constantes presentó el char como un entero de un byte que se escribe como letra según la tabla ASCII, y advirtió que 'ñ' no cabe en él; la clase Compilar, ejecutar y configurar mostró que "año" ocupa cuatro bytes y preparó la consola para escribirlo. Esta clase explica por qué. ASCII asigna un número a 128 caracteres, del 0 al 127, que caben en siete bits: las letras del inglés, las cifras, los signos de puntuación y algunos códigos de control. Ahora bien, el texto real contiene eñes, tildes, diéresis, letras griegas, ideogramas chinos, signos matemáticos y emojis, que suman cientos de miles. De ello se sigue que hace falta, primero, una tabla mucho mayor, y segundo, una manera de escribir sus números en bytes.
Conviene distinguir tres niveles. El primero es el carácter abstracto: la letra «eñe minúscula», con independencia de cómo se guarde o se dibuje. Unicode es la norma que asigna a cada carácter abstracto un número, su punto de código, que se escribe U+ seguido de al menos cuatro cifras hexadecimales: la «ñ» es U+00F1 (241), la «A» es U+0041 (65, el mismo número que en ASCII), el «€» es U+20AC (8364). Los puntos de código van de 0 a U+10FFFF, poco más de un millón. El segundo nivel es la codificación: la regla que convierte cada punto de código en una sucesión de bytes. UTF-8 es la codificación que usan la web, los sistemas actuales y este curso. El tercer nivel es la presentación: la figura que la fuente tipográfica dibuja en la pantalla, que puede cambiar de un tipo de letra a otro sin que cambie el carácter.
Si esto es así, un dato textual no es un texto mientras no se sepa su codificación: los bytes c3 b1 son la «ñ» en UTF-8, pero leídos con la tabla de una consola antigua son dos símbolos sin relación con ella, que es el fallo que la clase 1.4 enseñó a diagnosticar. La misma cuestión, vista desde la composición de documentos, aparece en la clase «Idiomas y tipografías» del curso LaTeX para la Redacción Académica.
Ver los bytes
Para ver los bytes de una cadena basta recorrerla: un std::string y un std::string_view son secuencias de char, y el bucle for por rango entrega uno por uno sus bytes. Para escribirlos en hexadecimal sirve el campo {:02x} (dos cifras hexadecimales, con cero a la izquierda). Como en la clase 2.4 con {:d}, C++23 escribe con él un char como byte sin signo. La regla importa, porque char tiene signo en GCC sobre x86-64: el byte c3, convertido en int, es el número , que {:02x} escribiría -3d.
#include <print>#include <string_view>// Escribe text, su tamaño en bytes y cada byte en hexadecimal.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 6fCada línea muestra el texto, su tamaño y sus bytes. Un carácter de cada longitud: la «A» ocupa un byte, la «ñ» dos, el «€» tres y el emoji cuatro. La palabra «año» ocupa cuatro bytes, 61, c3 b1 y 6f, y su size() es 4, no 3. Obsérvese que la «A» es el byte 41, el mismo que en ASCII: UTF-8 escribe los 128 caracteres de ASCII exactamente como ASCII.
La figura sigue la codificación de la «ñ» de arriba abajo: el punto de código, sus bits, el reparto de esos bits y los dos bytes resultantes, con los bits de prefijo destacados.
Demostración
La «ñ» es el punto de código
U+00F1, es decir, .En binario, se escribe
11110001. Es mayor que 127 y menor que 2048, de modo que UTF-8 lo escribe en dos bytes, que transportan once bits: se completa con ceros a la izquierda hasta once,00011110001.Los once bits se reparten en dos grupos: los cinco primeros,
00011, y los seis últimos,110001.El primer byte lleva el prefijo
110, que anuncia un carácter de dos bytes, seguido de los cinco bits del primer grupo:11000011. El segundo lleva el prefijo10, que marca un byte de continuación, seguido de los seis bits restantes:10110001.En hexadecimal,
1100 0011esc3y1011 0001esb1: los dos bytes que el programa anterior escribió para la «ñ». El cambio de base entre binario y hexadecimal, de cuatro en cuatro bits, es el de la clase «Sistemas de numeración posicional» de Álgebra y Geometría I.
Cargando el contenido…