Explorando a Biblioteca Padrão: stdlib, math, ctype, time

[380] Explorando a Biblioteca Padrão: stdlib, math, ctype, time

Antes de escrever uma função, vale perguntar se ela já existe — e quase sempre existe. O passeio passa por stdlib (o strtol que valida onde o atoi mente, o srand sem o qual todo sorteio se repete), pelo math e seu -lm esquecido no ligador, pelo ctype que classifica caractere e pelo time que cronometra.
Linguagem C

11 min de leitura

Uma verdade libertadora sobre programar em C: você não precisa reinventar tudo. Embora a linguagem seja pequena e enxuta — poucas palavras-chave, quase nenhuma função embutida —, ela vem acompanhada de uma biblioteca padrão rica, um conjunto de cabeçalhos com funções prontas para tarefas comuns. Já usamos várias (printf, malloc, strlen) sem parar para conhecê-las como um todo. Hoje fazemos um tour pelos cabeçalhos mais úteis do dia a dia, para você saber o que já existe antes de escrever do zero.

stdlib.h: o canivete suíço

O cabeçalho <stdlib.h> reúne utilidades gerais que você usará constantemente. Já conhecemos suas funções de memória (malloc, free) e de conversão (atoi, atof). Vamos ver outras categorias importantes.

Para conversão de strings em números, além de atoi, há a família strtol e strtod, que são mais robustas porque detectam erros de conversão:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    char *entrada = "42 sobrando";
    char *fim;

    long numero = strtol(entrada, &fim, 10); // base 10
    printf("Número: %ld\n", numero);         // 42
    printf("Resto não convertido: '%s'\n", fim); // ' sobrando'
    return 0;
}

Diferente de atoi (que retorna 0 tanto para "0" quanto para texto inválido, sem distinguir), strtol informa até onde conseguiu converter, via o ponteiro fim. Isso permite validar entrada de forma confiável — um passo importante de robustez.

Para números aleatórios, rand gera um inteiro pseudoaleatório, e srand define a semente que determina a sequência:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void) {
    srand(time(NULL)); // semente baseada no relógio: sequência diferente a cada execução

    for (int i = 0; i < 3; i++) {
        int dado = rand() % 6 + 1; // número de 1 a 6
        printf("Dado: %d\n", dado);
    }
    return 0;
}

O idioma rand() % 6 + 1 mapeia o aleatório para a faixa de 1 a 6 (um dado). O srand(time(NULL)) é essencial: sem ele, rand produziria sempre a mesma sequência a cada execução, porque a semente padrão é fixa. Usar o relógio como semente garante variação. (Uma ressalva: o rand do C é adequado para jogos e simulações simples, mas não para segurança ou criptografia, onde geradores especializados são obrigatórios.)

E <stdlib.h> traz ainda controle de programa: exit(codigo) encerra imediatamente o programa de qualquer ponto (com o mesmo significado de código que o return de main), e as constantes EXIT_SUCCESS e EXIT_FAILURE dão nomes legíveis a 0 e 1.

math.h: a caixa de ferramentas matemática

O cabeçalho <math.h> oferece as funções matemáticas que faltam à linguagem base. Ele exige um detalhe de compilação que já mencionamos: ligar a biblioteca matemática com a flag -lm.

#include <stdio.h>
#include <math.h>

int main(void) {
    printf("Raiz de 16: %.1f\n", sqrt(16.0));     // 4.0
    printf("2 elevado a 10: %.0f\n", pow(2.0, 10.0)); // 1024
    printf("Teto de 4.3: %.0f\n", ceil(4.3));      // 5
    printf("Piso de 4.7: %.0f\n", floor(4.7));     // 4
    printf("Valor absoluto: %.1f\n", fabs(-7.5));  // 7.5
    printf("Seno de 0: %.1f\n", sin(0.0));         // 0.0
    return 0;
}

As mais usadas: sqrt (raiz quadrada), pow (potência), ceil e floor (arredondar para cima e para baixo), round (arredondar ao mais próximo), fabs (valor absoluto de ponto flutuante), e as trigonométricas sin, cos, tan. Todas trabalham com double. Um lembrete que vale ouro: para valor absoluto de inteiros, use abs (de <stdlib.h>), não fabs; misturá-los é um erro comum. E não esqueça o -lm ao compilar, ou o ligador reclamará que não encontra sqrt e companhia.

ctype.h: classificando e transformando caracteres

O cabeçalho <ctype.h> fornece funções para examinar e converter caracteres individuais. Já usamos toupper e tolower; há uma família inteira de testes:

#include <stdio.h>
#include <ctype.h>

int main(void) {
    char c = 'A';
    printf("'%c' é letra?   %d\n", c, isalpha(c)); // 1 (verdadeiro)
    printf("'%c' é dígito?  %d\n", c, isdigit(c)); // 0 (falso)
    printf("'%c' é espaço?  %d\n", c, isspace(c)); // 0
    printf("Em minúscula:   %c\n", tolower(c));    // a

    char d = '7';
    printf("'%c' é dígito?  %d\n", d, isdigit(d)); // 1
    return 0;
}

As funções de teste — isalpha (é letra?), isdigit (é dígito?), isalnum (letra ou dígito?), isspace (espaço, tab, quebra de linha?), isupper/islower (maiúscula/minúscula?), ispunct (pontuação?) — retornam verdadeiro ou falso e são infinitamente mais legíveis e portáveis que comparações manuais como c >= 'a' && c <= 'z'. Elas são a ferramenta certa para validar e classificar texto: verificar se uma entrada contém só dígitos, contar palavras, limpar espaços. Um cuidado técnico que já aplicamos: ao passar um char para essas funções, converta-o para unsigned char (isdigit((unsigned char) c)) para evitar comportamento indefinido com caracteres de valor negativo.

time.h: medindo e representando o tempo

O cabeçalho <time.h> lida com datas, horas e medição de intervalos. Seu tipo central é time_t, que representa um instante (tipicamente segundos desde 1º de janeiro de 1970, a "época Unix"):

#include <stdio.h>
#include <time.h>

int main(void) {
    time_t agora = time(NULL); // instante atual
    printf("Segundos desde 1970: %ld\n", (long) agora);

    // converte para uma representação legível
    printf("Data e hora: %s", ctime(&agora)); // ctime já inclui '\n'
    return 0;
}

A função time(NULL) devolve o instante atual, e ctime o converte numa string legível. Para controle fino de data (dia, mês, ano separados), há localtime, que decompõe o time_t numa struct tm com campos individuais. E, para medir desempenho, clock mede o tempo de processamento gasto:

#include <stdio.h>
#include <time.h>

int main(void) {
    clock_t inicio = clock();

    long soma = 0;
    for (long i = 0; i < 100000000L; i++) soma += i; // trabalho pesado

    clock_t fim = clock();
    double segundos = (double)(fim - inicio) / CLOCKS_PER_SEC;
    printf("Tempo: %.3f segundos\n", segundos);
    return 0;
}

O padrão (fim - inicio) / CLOCKS_PER_SEC converte a diferença de "ticks" do relógio em segundos. Essa é a forma clássica de cronometrar um trecho de código — útil quando, mais adiante, formos comparar a eficiência de diferentes algoritmos.

A filosofia: não reinvente o que já existe

A lição maior desta aula não é decorar funções — é adquirir o reflexo de perguntar antes de escrever. Precisa de raiz quadrada? Não implemente uma; use sqrt. Precisa saber se um caractere é dígito? Não compare com '0' e '9'; use isdigit. A biblioteca padrão é testada, otimizada e portável — quase sempre melhor do que uma versão caseira feita às pressas. Programadores C experientes conhecem bem o que a biblioteca oferece, e consultam a referência (como o cppreference) sem hesitar. Reinventar uma função da biblioteca padrão costuma ser desperdício de esforço e uma fonte a mais de bugs.

O que vem a seguir

Hoje fizemos um tour pelos cabeçalhos mais úteis da biblioteca padrão: <stdlib.h> (conversão robusta, aleatoriedade, controle de programa), <math.h> (funções matemáticas, com o -lm), <ctype.h> (classificação de caracteres) e <time.h> (tempo e cronometragem). O reflexo de consultar o que já existe vai te poupar incontáveis horas. Na próxima aula, vamos focar num tema que a biblioteca padrão também serve, mas que merece tratamento próprio pela quantidade de armadilhas: a manipulação de strings — juntando o que sabemos de <string.h>, ponteiros e memória para processar texto sem sofrimento.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Use strtol para converter a string "250 reais" em um número inteiro e imprima tanto o número quanto a parte não convertida (" reais"). Explique a vantagem de strtol sobre atoi.

Ver resposta

✓ Resposta:

#include <stdio.h>
#include <stdlib.h>
int main(void) {
    char *entrada = "250 reais";
    char *fim;
    long n = strtol(entrada, &fim, 10);
    printf("Número: %ld\n", n);          // 250
    printf("Resto: '%s'\n", fim);         // ' reais'
    return 0;
}

A vantagem de strtol sobre atoi é que ela informa, via o ponteiro fim, até onde a conversão foi feita — permitindo detectar se havia texto após o número e validar a entrada. O atoi simplesmente para no primeiro caractere não numérico e retorna o que conseguiu (ou 0), sem meio de saber se a string era realmente um número válido.

Exercício 2

Simule 10 lançamentos de um dado de 6 faces usando rand, srand e time. Rode o programa duas vezes e confirme que as sequências diferem.

Ver resposta

✓ Resposta:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
    srand(time(NULL));
    for (int i = 0; i < 10; i++) {
        printf("%d ", rand() % 6 + 1);
    }
    printf("\n");
    return 0;
}

Como a semente vem de time(NULL) (que muda a cada segundo), as sequências geradas em execuções diferentes serão distintas. Sem o srand, a sequência seria sempre a mesma.

Exercício 3

Escreva um programa que, dado um raio, calcule e imprima a área e a circunferência de um círculo usando pow (ou multiplicação) e a constante M_PI (ou 3.14159265) de <math.h>. Lembre-se de compilar com -lm.

Ver resposta

✓ Resposta:

#include <stdio.h>
#include <math.h>
int main(void) {
    double raio = 5.0;
    double area = M_PI * pow(raio, 2.0);
    double circunferencia = 2.0 * M_PI * raio;
    printf("Área: %.2f\n", area);                 // 78.54
    printf("Circunferência: %.2f\n", circunferencia); // 31.42
    return 0;
}

Compile com gcc arquivo.c -o prog -lm. (Se M_PI não estiver disponível no seu ambiente, defina-o com #define M_PI 3.14159265358979 ou use o valor literal.)

Exercício 4

Escreva uma função int so_digitos(const char *s) que retorne 1 se a string contiver apenas dígitos (e 0 caso contrário), usando isdigit. Teste com "12345" e "12a45".

Ver resposta

✓ Resposta:

#include <stdio.h>
#include <ctype.h>
int so_digitos(const char *s) {
    if (s[0] == '\0') return 0; // string vazia: consideramos "não"
    for (int i = 0; s[i] != '\0'; i++) {
        if (!isdigit((unsigned char) s[i])) {
            return 0; // achou um não-dígito
        }
    }
    return 1;
}
int main(void) {
    printf("%d\n", so_digitos("12345")); // 1
    printf("%d\n", so_digitos("12a45")); // 0
    return 0;
}

A função percorre a string e retorna 0 assim que encontra qualquer caractere que não seja dígito; se chegar ao fim sem isso, retorna 1.

Exercício 5

Use clock para medir e comparar o tempo de dois laços: um que soma os inteiros de 0 a 100 milhões, e outro que faz o mesmo mas multiplicando por 2. Imprima os dois tempos.

Ver resposta

✓ Resposta:

#include <stdio.h>
#include <time.h>
int main(void) {
    clock_t i1 = clock();
    long soma = 0;
    for (long i = 0; i < 100000000L; i++) soma += i;
    clock_t f1 = clock();

    clock_t i2 = clock();
    long soma2 = 0;
    for (long i = 0; i < 100000000L; i++) soma2 += i * 2;
    clock_t f2 = clock();

    printf("Laço 1: %.3f s\n", (double)(f1 - i1) / CLOCKS_PER_SEC);
    printf("Laço 2: %.3f s\n", (double)(f2 - i2) / CLOCKS_PER_SEC);
    return 0;
}

Os tempos serão próximos (a multiplicação por 2 é barata), mas o exercício demonstra o padrão de cronometragem. Os valores exatos dependem da máquina e do nível de otimização do compilador — com -O2, aliás, o compilador pode até eliminar laços cujo resultado não é usado, então convém usar a soma no final para garantir que o trabalho aconteça.

Comentários

Mais em Linguagem C

Programação Defensiva e Contratos
Programação Defensiva e Contratos

Ponteiro nulo onde a função exige um válido é bug e pede assert; arquivo que…

Listas Encadeadas: A Estrutura que Só Existe com Ponteiros
Listas Encadeadas: A Estrutura que Só Existe com Ponteiros

Liberar um nó e só então ler o seu proximo é use-after-free: guarda-se o…

O Pré-processador: #define, Macros e Inclusões
O Pré-processador: #define, Macros e Inclusões

O quadrado de 2 + 3 dá 11 quando a macro esquece os parênteses — o…