Os tipos int e char

−2147483648 . . 2147483647

−128 . . 127

Na linguagem C, os números naturais (0, 1, 2, 3, …) são conhecidos como inteiros sem sinal, enquanto os números inteiros (…, −2, −1, 0, +1, +2, …) são conhecidos como inteiros com sinal.  Inteiros sem sinal são implementados pelos tipos-de-dados unsigned char e unsigned int.  Inteiros com sinal são implementados pelos tipos-de-dados charint.  Para declarar uma variável u do primeiro tipo, uma variável n do segundo, uma c do terceiro e uma i do quarto, basta dizer

unsigned char u;
unsigned int n;
char c;
int i;

(No lugar de unsigned int podemos escrever, simplesmente, unsigned.)  Existem ainda os tipos short int e long int e as correspondentes versões com prefixo unsigned, mas esses tipos serão usados apenas marginalmente neste sítio.

O tipo unsigned char

Um unsigned char é um inteiro sem sinal no intervalo 0 . . 28−1, ou seja, 0 . . 255  Cada unsigned char é implementado em 1 byte, usando notação binária.

Os inteiros fora do intervalo 0 . . 255 são reduzidos módulo 28, ou seja, representados pelo resto da divisão por 256. Em outras palavras, todo inteiro N sem sinal é representado pelo unsigned char u tal que a diferença  Nu  é um múltiplo de 256.

Há quem goste de usar byte como apelido de unsigned char.  Para fazer isso, basta introduzir a definição

typedef unsigned char byte;

Exemplo.  Para que o exemplo seja mais didático, suporemos que cada byte tem apenas 4 bits.  Nessas condições, o valor de todo unsigned char pertence ao intervalo 0 . . 24−1. Esse intervalo pode ser representado por um círculo para sugerir a redução módulo 24:

                            0
               15                     1
        14                                    2
   13                                              3
                     	       
12                                                    4
                     	       
   11                                              5
        10                                    6
                9                     7
                            8

Por exemplo, 16 é representado por 0 (pois 16 = 1×24 + 0),  17 é representado por 1 (pois 17 = 1×24 + 1)  e  36 é representado por 4 (pois 36 = 2×24 + 4).

Caracteres.   Números do tipo unsigned char são usados principalmente para representar caracteres.  A tabela ASCII converte o byte que representa o unsigned char em um caractere ASCII.  Note que apenas os números naturais menores que 128 representam caracteres.

O tipo char

Um char é um inteiro com sinal no intervalo −27 . . 27−1, ou seja, −128 . . 127.  Cada char é implementado em 1 byte usando notação complemento-de-dois.  (Portanto, entre 0 e 127, um char e um unsigned char são representados pelo mesmo byte.)

Inteiros fora do intervalo −128 . . 127 são reduzidos módulo 28, ou seja, todo inteiro N com sinal é representado pelo char c tal que a diferença  Nc  é um múltiplo inteiro (positivo ou negativo) de 256.

Exemplo.  Para que o exemplo seja mais didático, suporemos que todo byte tem apenas 4 bits.  Nessas condições, o valor de todo char pertence ao intervalo −8 . . 7. Esse intervalo pode ser representado por um círculo para sugerir a redução módulo 24:

                              +0
                 −1                      +1
         −2                                      +2
    −3                                                +3
                        
−4                                                       +4
                        
    −5                                                +5
         −6                                      +6
                 −7                      +7
                              −8

Por exemplo, 8 módulo 24 é −8 (pois 8 = 1×24 − 8),  9 módulo 24 é −7 (pois 9 = 1×24 − 7)  e  −30 módulo 24 é 2 (pois −30 = −2×24 + 2).

Caracteres.   Números do tipo char são usados principalmente para representar caracteres:  o byte que representa o número é convertido em um caractere ASCII pela tabela ASCIIApenas os números entre 0 e 127 representam caracteres. Nesse intervalo, um char e um unsigned char são implementados pelo mesmo byte e portanto representam o mesmo caractere.

Exercícios 1

  1. Escreva, em notação binária, o menor e o maior valor que um unsigned char pode ter. Repita o exercício com char.
  2. Escreva, em notação hexadecimal, o menor e o maior valor que um unsigned char pode ter. Repita o exercício com char.
  3. Qual o unsigned char que representa 1000?  Qual o char que representa 1000?  Qual o char que representa −1000
  4. Escreva e teste um programa que exiba na tela os caracteres representados pelos chars 32 a 127.  (Veja impressão em formato %c na página Entrada e saída.)  Exiba dez caracteres por linha.
  5. Escreva e teste um programa que tente exibir na tela os caracteres representados pelos bytes cujo primeiro bit é 1.  (Veja impressão em formato %c na página Entrada e saída.)
  6. Escreva um programa que receba 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. (Veja leitura em formato %c na página Entrada e saída.)
  7. Biblioteca ctype.  Familiarize-se com as funções da biblioteca ctype.  A função isspace, por exemplo, decide se um dado caractere ASCII é um branco. A função isalpha decide se um dado caractere ASCII é uma letra.

O tipo unsigned int

Um  unsigned int  é um inteiro sem sinal no intervalo 0 . . 28s−1.  Cada unsigned int é implementado em s bytes consecutivos, usando notação binária.  O valor de s depende do computador, mas em geral é 4. Em cada computador, o valor de s é dado pela expressão sizeof (unsigned int) e o número 28s−1 está registrado na constante  UINT_MAX,  definida na interface limits.h.

Inteiros maiores que UINT_MAX são reduzidos módulo UINT_MAX+1, ou seja, todo inteiro N é representado pelo unsigned int n tal que a diferença  Nn  é um múltiplo de UINT_MAX+1.

Desse ponto em diante, os exemplos supõem que s = 4. Assim, UINT_MAX vale 232−1, ou seja, 4294967295.  

Aritmética unsigned int.  As operações de adição, subtração e multiplicação entre números do tipo unsigned int estão sujeitas a transbordamento (= overflow), pois o resultado exato de uma operação pode fugir do intervalo 0..UINT_MAX. (Em geral isso não ocorre pois trabalhamos com números pequenos.)  Overflows não são tratados como algo excepcional e o resultado exato de cada operação é tacitamente reduzido módulo UINT_MAX+1.  Por exemplo:

unsigned int n, m, x;
n = 4000000000;
m =  300000000;
x = n + m; // overflow
// x vale 5032704

A operação de divisão entre unsigned ints não está sujeita a overflow, mas o quociente é truncado:  a expressão 9/2, por exemplo, tem valor ⌊9/2⌋, ou seja, o piso de 9/2.

O tipo int

Um  int  é um inteiro com sinal no intervalo −28s−1 . . 28s−1−1.  Cada int é implementado em s bytes consecutivos, usando notação complemento-de-dois.  O valor de s depende do computador, mas em geral é 4. Em cada computador, o valor de s é dado pela expressão sizeof (int), igual a sizeof (unsigned int). Os números −28s−1 e 28s−1−1 estão registrados nas constantes  INT_MIN  e  INT_MAX  respectivamente, ambas definidas na interface limits.h.  Como seria de se esperar, INT_MAX − INT_MIN = UINT_MAX.

Inteiros fora do intervalo INT_MIN..INT_MAX são reduzidos módulo UINT_MAX+1, ou seja, todo inteiro N com sinal é representado pelo inti tal que a diferença  Ni  é um múltiplo (positivo ou negativo) de UINT_MAX+1.

Desse ponto em diante, suporemos que s = 4. Assim, INT_MIN vale −231, ou seja, −2147483648, e INT_MAX vale 231−1, ou seja, 2147483647.  

Aritmética int.  As operações de adição, subtração e multiplicação entre números do tipo int estão sujeitas a transbordamento (= overflow), pois o resultado exato de uma operação pode fugir do intervalo INT_MIN..INT_MAX.  (Em geral isso não ocorre pois trabalhamos com números próximos de zero.)  Overflows não são tratados como algo excepcional e o resultado exato de cada operação é tacitamente reduzido módulo UINT_MAX+1.  Por exemplo:

int i, j, x;
i = 2147483000;
j = 2147483000;
x = i + j; // overflow
// x vale -1296

A atribuição de um unsigned int a um int também poderia resultar em overflow e portanto é feita módulo UINT_MAX+1.  Por exemplo:

int i;
unsigned int n;
n = 2147483700;
i = n; // overflow
// i vale -2147483596

A operação de divisão entre ints não está sujeita a overflow, mas o quociente é truncado: a expressão 9/2, por exemplo, tem valor ⌊9/2⌋, ou seja, o piso de 9/2.  Mas no caso de números estritamente negativos, o resultado da divisão é arredondado em direção ao zero: a expressão -9/2 tem valor −⌊9/2⌋, não ⌊−9/2⌋.

Exercícios 2

  1. Sizeof.  Compile e execute o seguinte programa:
    int main (void) {
       printf ("sizeof (unsigned): %lu\n", 
                sizeof (unsigned));        
       printf ("UINT_MAX: %u\n",
                UINT_MAX);
       printf ("sizeof (int) = %lu\n",  
                sizeof (int));        
       printf ("INT_MIN: %d\nINT_MAX: %d\n",
                INT_MIN,     INT_MAX);
       return EXIT_SUCCESS; }
    
  2. Escreva os números UINT_MAX, INT_MIN e INT_MAX em notação hexadecimal.
  3. Suponha que precisamos contar o número de ocorrências de um certo fenômeno num computador em que sizeof (unsigned) vale 2.  Sabemos de antemão que o fenômeno não ocorre mais que 65535 vezes.  Podemos usar uma variável do tipo unsigned para contar as ocorrências do fenômeno?  Que fazer se o fenômeno puder ocorrer mais que 65535 vezes? Proponha uma solução que use listas encadeadas para representar um contador em base 100. 
  4. Suponha que sizeof (unsigned) vale 2 no seu computador. Qual o valor de 60000 + 30000 em aritmética unsigned int?  Qual o valor de 30000 + 15000 em aritmética int?  Qual o valor de 60000 × 11 em aritmética unsigned int?  Qual o valor de 30000 × 2 em aritmética int?
  5. Detecção de overflow.  Escreva uma função booleana que receba unsigned ints nm e decida (true ou false) se a adição exata de nm produz overflow.  Repita o exercício supondo que n e m são do tipo int.
  6. Até onde?  Escreva um programa que receba um unsigned int n e imprima as potências  n2, n3, n4, n5, etc.  O programa só deve parar quando não for capaz de armazenar a próxima potência em uma variável do tipo unsigned int.

Constantes

O código de quase todo programa em C contém constantes inteiras. Muitos programas também têm constantes-caractere.  Por exemplo:

a = 999;
c = 'a';

As constantes inteiras (como 999 no exemplo) são tratadas como se fossem do tipo int e devem ter valor entre INT_MIN e INT_MAX.

As constantes-caractere (como 'a' no exemplo acima) são embrulhadas em aspas para que não sejam confundidas com nomes de variáveis.  Elas estão restritas ao alfabeto ASCII e portanto expressões como 'Ã''ç' não são válidas.  O valor de uma constante-caractere é dado pela tabela ASCII (assim, 'a' é o mesmo que 97).

Constantes-caractere são do tipo int e não do tipo char, como seria de se esperar. Mas é claro que quando a constante é atribuída a uma variável do tipo char, ela é convertida em um byte (por exemplo, 'a' é convertida em 01100001).

A título de ilustração, o seguinte fragmento de programa exibe as vinte e seis letras maiúsculas do alfabeto ASCII. Note que essas letras ocupam posições consecutivas na tabela ASCII e estão am ordem alfabética. Algo análogo acontece com as letras minúsculas.

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

Exercícios 3

  1. Qual a diferença entre 'O', 'o', '0', '\0'0?
  2. Qual o efeito do seguinte fragmento de programa?
    for (int i = 1; i <= 26; ++i) 
       printf ("%c\n", 'a' + i - 1);
    

Expressões aritméticas que misturam tipos

Operações aritméticas entre operandos do tipo char e/ou unsigned char não são executadas módulo 28, como poderíamos imaginar.  Nessas operações, todos os operandos são previamente convertidos (promovidos) ao tipo int e a operação é executada em aritmética int.

Por exemplo, se as variáveis u e v são do tipo unsigned char e têm valor 255 e 2 respectivamente, a expressão  u + v  é do tipo int e tem valor 257.  (É claro, entretanto, que a atribuição de u + v a uma variável do tipo unsigned char é feita módulo 28.)

Considerações análogas valem para expressões aritméticas sobre operandos de tipos mistos, como int, char e unsigned char.  Por exemplo, se a variável c é do tipo char e tem valor 127, a expressão  c + 2  é do tipo int e tem valor 129 (a constante 2 é do tipo int por definição).

Exercícios 4

  1. Qual o efeito do seguinte fragmento de código?
    unsigned char u, v, w;
    u = 255; v = 2;
    printf ("%d", u + v); 
    w = u + v;
    printf ("%d", w); 
    
  2. Qual o efeito dos dois fragmentos de código a seguir?
    unsigned char u;
    for (u = 0; u < 256; ++u) 
       printf (".");
    
    char c;
    for (c = 0; c < 128; ++c) 
       printf (".");
    

Tipos inteiros grandes

Algumas aplicações envolvem números inteiros que não cabem em um int.  Para atender essas aplicações, a linguagem C tem o tipo-de-dados  long int,  que ocupa mais bytes que o tipo int.  No meu computador, um long int ocupa 8 bytes, ou seja, 64 bits.  (Mas veja a página Is there any need of “long” data type in C and C++? em GeeksforGeeks.)


Perguntas e respostas