Caracteres ASCII e o tipo char

     ! " # $ % & ' ( ) * + , - . /
  0 1 2 3 4 5 6 7 8 9
  : ; < = > ? @
  A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
  [ \ ] ^ _ `
  a b c d e f g h i j k l m n o p q r s t u v w x y z
  { | } ~
  

O tipo-de-dados char foi criado para representar caracteres numa época em que os únicos caracteres à disposição do programador eram os do alfabeto ASCII.  Com a ampliação do conjunto de caracteres, o tipo char não é mais capaz de representar todos os caracteres, mas continua sendo usado para representar o alfabeto ASCII. 

Bytes e chars

Um byte é uma sequência de 8 bits e portanto pode ter um de 256 valores:  de 00000000 a 11111111 (ou seja, 0x00 a 0xFF em notação hexadecimal).  Um byte pode ser interpretado de duas maneiras:

No primeiro caso, o byte é um número do tipo  unsigned char,  com valor entre 0 e 255.  No segundo, o byte é um número do tipo  char,  com valor entre −128 a +127.

Para criar uma variável x do segundo tipo e uma variável y do primeiro tipo, basta dizer

char x;
unsigned char y;

Caracteres ASCII e chars

A faixa de valores mais relevantes de um byte vai de  00000000  a  01111111.  Em notação hexadecimal, essa faixa vai de 0x000x7F, quer o byte seja interpretado como um char, quer como um unsigned char.

Os bytes 0x000x7F são usados para representar caracteres do alfabeto ASCII.  Os bytes 0x200x7E, por exemplo, representam os caracteres

  ! " # $ % & ' ( ) * + , - . /
0 1 2 3 4 5 6 7 8 9 
: ; < = > ? @
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
[ \ ] ^ _ `
a b c d e f g h i j k l m n o p q r s t u v w x y z
{ | } ~

Os bytes 0x00 a 0x1F e o byte 0x7F representam caracteres de controle.  Esses caracteres não são símbolos gráficos como os demais e por isso são indicados por uma notação especial que consiste em uma barra invertida seguida de alguma coisa.  Eis os mais importantes:

\0 caractere nulo (null)
\t tabulação horizontal (tab)
\n fim de linha (newline)
\v tabulação vertical
\f quebra de página (new page)
\r carriage return

O caractere  \0  não ocupa espaço algum ao ser impresso.  O caractere  \n  marca o fim de uma linha de texto e produz uma mudança de linha ao ser impresso.

Como os caracteres de controle são representados por um só byte cada, podemos usar a expressão byte de controle em lugar de caractere de controle.  Em particular, é indiferente dizer caractere \0 ou byte \0, caractere \n ou byte \n, caractere \t ou byte \t, etc.

O byte 0x20 representa um espaço e pode ser indicado por \ .  Os caracteres  \ \t\n\v\f,  e  \r  são conhecidos como brancos (= white-spaces).  Muitas funções das bibliotecas padrão tratam todos os brancos como se fossem \ .

A lista completa dos 128 caracteres ASCII e respectivos códigos está registrada na tabela ASCII.  É cômodo usar atalhos verbais óbvios ao falar de caracteres.  Por exemplo, em vez de dizer o caractere A podemos dizer

Impressão.  Para imprimir um dos caracteres entre 0x000x7F, podemos usar a função putchar ou a função printf com especificação de formato  %c.  Por exemplo, o fragmento de código

char x = 65; // 65 == 0x41
putchar (x);
printf (" %d %c", x, x);

imprime

A 65 A

Os outros 128 bytes.  Até aqui tratamos dos bytes 0x00 a 0x7F.  Que dizer dos bytes 0x80 a 0xFF?  Esses bytes, que têm valor entre 128 e 255 na interpretação unsigned char, não representam caracteres (supondo que a variável de ambiente LC_CTYPE do locale do seu sistema vale en_US.UTF-8, ou pt_BR.UTF-8, ou algo semelhante).

Chars versus caracteres.  Embora a palavra char sugira caractere, os dois conceitos não devem ser confundidos.  Por um lado, nem todo char representa um caractere. Por outro, nem todo caractere Unicode cabe em em um char.  Para quebrar a associação entre char e caractere, procuro dizer byte no lugar de char e unsigned char sempre que possível.

Exercícios 1

  1. Escreva e teste um programa que exiba na tela os caracteres que representam os bytes 0x200x7E.  Imprima dez caracteres por linha.
  2. Escreva e teste um programa que tente exibir na tela os caracteres que representam os bytes 0x800xFF
  3. Escreva um programa que leia dois caracteres do alfabeto ASCII digitados pelo usuário no terminal e diga se o primeiro vem antes ou depois do segundo na tabela ASCII.
  4. Considere o vetor  65 67 72 79 85 33 10 51 50 32 43 32 52 51 32 61 32 55 53  de bytes.  Imprima esses bytes com especificação de formato %c.  Qual o resultado?
  5. Familiarize-se com o programa od (o nome é uma abreviatura de octal dump).  Esse utilitário e imprime o valor de cada byte de um arquivo.

Constantes do tipo char

Podemos atribuir constantes literais a variáveis do tipo char.  Com isso, evitamos consultas à tabela ASCII e tornamos o código mais legível:

char x;
x = 'A'; // equivale a x = 65
x = ' '; // equivale a  x = 32

O seguinte fragmento de programa, por exemplo, imprime todas as vinte e seis letras maiúsculas do alfabeto ASCII:

for (char x = 'A'; x <= 'Z'; ++x) 
   printf ("%c ", x);

As constantes literais estão restritas ao alfabeto ASCII, pois caracteres fora desse alfabeto não cabem em um byte.  Assim, por exemplo, o fragmento de código

char x = 'À';

é inválido pois a representação UTF-8 do caractere  À  ocupa mais de um byte e assim não cabe em um char.

Exercícios 2

  1. Qual a diferença entre 'O', 'o', '0' e '\0'?
  2. Familiarize-se com as funções da biblioteca ctype.  A função isspace, por exemplo, decide se um dado caractere ASCII é um branco e a função isalpha decide se um dado caractere ASCII é uma letra.
  3. Escreva um programa para converter notação decimal em notação hexadecimal. O seu programa deve receber, pela linha de comando, um número em notação decimal e imprimir a representação hexadecimal do número.  Repita o execício para converter hexadecimal em decimal, decimal em octal, octal em decimal, etc.

Operações aritméticas com chars

As operações aritméticas entre bytes (do tipo char ou unsigned char) não são executadas módulo 256, como poderíamos imaginar.  Nessas operações, todos os operandos são previamente convertidos ao tipo int e o resultado da operação é um int.  Portanto, todas as operações aritméticas sobre bytes são executadas em aritmética int.

Por exemplo, se as variáveis x e y do tipo unsigned char valem 255 e 2 respectivamente, o valor da expressão  x + y  é 257.  Já a atribuição de um int a um unsigned char é feita módulo 256.  Assim, se z é um unsigned char então depois da atribuição  z = x + y  a variável z terá valor 1:

unsigned char x, y, z;
x = 255; y = 2;
printf ("%d", x + y); // imprime 257
z = x + y;
printf ("%d", z); // imprime 1

Por essa razão, os dois processos iterativos abaixo resultam em loop eterno:

unsigned char x;
for (x = 0; x < 256; ++x) 
   putchar ('.');
char y;
for (y = 0; y < 128; ++y) 
   putchar ('.');

Perguntas e respostas