Manipulação de Strings sem Sofrimento

[381] Manipulação de Strings sem Sofrimento

O strtok escreve na string que recebe, trocando cada delimitador por um terminador — entregar um literal a ele derruba o programa. Com esse aviso na frente vêm o strchr e o strstr, a montagem de string no heap com o +1 de sempre, e o caminho de ida e volta entre texto e número.
Linguagem C

10 min de leitura

Processar texto é uma das tarefas mais frequentes na programação — e, em C, uma das mais traiçoeiras. Lá na aula Strings: Vetores de Caracteres e a Biblioteca string.h, conhecemos as strings e as funções básicas de <string.h>. Agora, com ponteiros, memória dinâmica e a biblioteca padrão sob domínio, podemos enfrentar o processamento de texto de verdade: dividir frases em palavras, buscar e substituir, construir strings dinamicamente. O objetivo de hoje é te dar as técnicas e os cuidados que transformam a manipulação de strings de um campo minado num trabalho tranquilo.

Revisando o essencial de string.h

Vale recapitular rapidamente as ferramentas que já temos, agora vendo-as como um conjunto. Para medir, strlen. Para copiar, strcpy (e a versão segura strncpy). Para concatenar, strcat (e strncat). Para comparar, strcmp (0 quando iguais). A lição de segurança daquela aula permanece central: as versões sem n não verificam limites e podem estourar buffers, então em código real preferimos as versões com limite ou controlamos os tamanhos manualmente. Todo o resto de hoje se apoia nessa base.

Buscando dentro de uma string

Duas funções de <string.h> localizam conteúdo dentro de uma string. strchr encontra a primeira ocorrência de um caractere, e strstr encontra a primeira ocorrência de uma substring. Ambas devolvem um ponteiro para o ponto encontrado (ou NULL se não houver):

#include <stdio.h>
#include <string.h>

int main(void) {
    char frase[] = "linguagem de programação C";

    char *p = strchr(frase, 'g'); // primeiro 'g'
    if (p != NULL) {
        printf("Primeiro 'g' na posição %ld\n", p - frase); // aritmética de ponteiros!
    }

    char *s = strstr(frase, "programação");
    if (s != NULL) {
        printf("Encontrado: %s\n", s); // programação C (do ponto em diante)
    }
    return 0;
}

Note como reaproveitamos a aritmética de ponteiros da aula Aritmética de Ponteiros e a Ligação Profunda com Vetores: p - frase calcula a posição do caractere encontrado subtraindo ponteiros. E strstr devolve um ponteiro para o início da substring dentro da original — imprimir a partir dele mostra o resto da frase. Essas duas funções são a base de qualquer busca em texto.

Dividindo texto em pedaços: strtok

Uma necessidade constante é quebrar uma string em partes — separar uma frase em palavras, uma linha CSV em campos. A função strtok faz isso, dividindo a string por delimitadores:

#include <stdio.h>
#include <string.h>

int main(void) {
    char frase[] = "arroz,feijao,batata,cebola"; // precisa ser modificável!

    char *token = strtok(frase, ","); // primeira chamada: a string e o delimitador
    while (token != NULL) {
        printf("Item: %s\n", token);
        token = strtok(NULL, ","); // chamadas seguintes: NULL como primeiro argumento
    }
    return 0;
}

O strtok tem um uso peculiar que você precisa memorizar: na primeira chamada, você passa a string; nas seguintes, passa NULL, e ela continua de onde parou. Cada chamada devolve o próximo pedaço, até retornar NULL no fim. Mas atenção a duas armadilhas importantes: primeiro, strtok modifica a string original (ela insere terminadores '\0' no lugar dos delimitadores), então nunca a use numa string literal (char *s = "...") — apenas em vetores modificáveis (char s[] = "..."). Segundo, strtok guarda estado interno entre chamadas, o que a torna problemática em código com múltiplas threads (existe strtok_r, uma versão segura para esses casos). Conhecendo esses limites, ela é uma ferramenta prática para tokenização simples.

Construindo strings dinamicamente

Juntando com a memória dinâmica da Fase 2, podemos criar strings de tamanho decidido em tempo de execução. O padrão: calcular o tamanho necessário, alocar com malloc (lembrando do +1 para o '\0'), preencher, e liberar quando terminar:

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

// junta duas strings numa nova, alocada no heap
char *concatenar_novo(const char *a, const char *b) {
    size_t tamanho = strlen(a) + strlen(b) + 1; // +1 para o terminador
    char *resultado = malloc(tamanho);
    if (resultado == NULL) return NULL;

    strcpy(resultado, a);   // copia a primeira
    strcat(resultado, b);   // concatena a segunda
    return resultado;       // devolve string do heap: sobrevive ao retorno
}

int main(void) {
    char *completo = concatenar_novo("Linguagem ", "C");
    if (completo != NULL) {
        printf("%s\n", completo);  // Linguagem C
        free(completo);            // quem recebe, libera
    }
    return 0;
}

Este exemplo é a síntese de tudo que construímos: calculamos o tamanho exato com strlen (mais o +1 para o terminador, a lição da aula Strings: Vetores de Caracteres e a Biblioteca string.h), alocamos no heap para que a string sobreviva ao retorno da função (a lição da aula Alocação Dinâmica: malloc, calloc, realloc e free), preenchemos com as funções de <string.h>, e deixamos claro que quem recebe a string é responsável por liberá-la. Construir strings dinamicamente assim é como programas C reais montam mensagens, caminhos de arquivo e saídas de tamanho variável.

Convertendo entre strings e números

Uma tarefa recorrente é a ponte entre texto e números — ler "42" como o inteiro 42, ou transformar 3.14 na string "3.14". Já temos as duas direções. De string para número: atoi, atof, e as robustas strtol, strtod da aula anterior. De número para string: snprintf, que vimos na aula Entrada e Saída Formatada: printf e scanf a Fundo:

#include <stdio.h>

int main(void) {
    // número -> string
    char buffer[50];
    int codigo = 7;
    double valor = 19.9;
    snprintf(buffer, sizeof(buffer), "Produto %03d custa R$ %.2f", codigo, valor);
    printf("%s\n", buffer); // Produto 007 custa R$ 19.90

    // string -> número (visto na aula anterior)
    // int n = atoi("42");  double d = atof("3.14");
    return 0;
}

O snprintf é a ferramenta ideal para "imprimir" dados formatados dentro de uma string em vez da tela, com a segurança do limite de tamanho. Essa conversão bidirecional entre texto e números é o que permite ler configurações, gerar relatórios e formatar saída — o trânsito constante entre a representação legível e a numérica.

Um utilitário completo: contando palavras únicas

Vamos consolidar com um programa que reúne várias técnicas — divide uma frase em palavras e conta quantas há:

#include <stdio.h>
#include <string.h>

int main(void) {
    char frase[] = "o rato roeu a roupa do rei de roma";
    int total = 0;

    char copia[100];
    strcpy(copia, frase); // strtok modifica: trabalhamos numa cópia

    char *palavra = strtok(copia, " ");
    while (palavra != NULL) {
        total++;
        printf("Palavra %d: %s\n", total, palavra);
        palavra = strtok(NULL, " ");
    }

    printf("Total de palavras: %d\n", total); // 9
    return 0;
}

Repare no cuidado central: fazemos uma cópia da frase antes de passá-la a strtok, porque ele modifica a string — e talvez precisemos da original intacta depois. Esse tipo de atenção aos efeitos colaterais das funções de string é exatamente a disciplina que evita o "sofrimento" do título.

O que vem a seguir

Hoje reunimos tudo que sabemos para manipular texto com tranquilidade: busca com strchr e strstr, divisão com strtok (e seus cuidados), construção dinâmica de strings no heap, e a conversão bidirecional entre texto e números. Processar strings em C exige atenção constante a limites de buffer e efeitos colaterais, mas com as técnicas certas deixa de ser um terreno perigoso. Na próxima aula, damos um passo conceitual empolgante: os ponteiros para função, que permitem tratar o próprio comportamento — não apenas dados — como algo que se pode guardar em variáveis e passar como argumento. É a porta de entrada para código genérico e flexível.

Fontes e leituras recomendadas

Exercícios

Exercício 1

Use strchr para encontrar a posição do caractere '@' numa string de e-mail como "usuario@dominio.com". Imprima a posição e a parte do domínio (o que vem depois do @).

Ver resposta

✓ Resposta:

#include <stdio.h>
#include <string.h>
int main(void) {
    char email[] = "usuario@dominio.com";
    char *arroba = strchr(email, '@');
    if (arroba != NULL) {
        printf("'@' na posição %ld\n", arroba - email); // 7
        printf("Domínio: %s\n", arroba + 1);            // dominio.com
    }
    return 0;
}

O arroba + 1 aponta para o caractere logo após o @, imprimindo o domínio; arroba - email dá a posição via aritmética de ponteiros.

Exercício 2

Use strstr para verificar se a palavra "C" aparece na frase "Eu programo em C e Python". Imprima a mensagem "encontrado" ou "não encontrado" conforme o resultado.

Ver resposta

✓ Resposta:

#include <stdio.h>
#include <string.h>
int main(void) {
    char frase[] = "Eu programo em C e Python";
    if (strstr(frase, "C") != NULL) {
        printf("encontrado\n");
    } else {
        printf("não encontrado\n");
    }
    return 0;
}

strstr retorna um ponteiro não-nulo porque "C" está presente.

Exercício 3

Use strtok para dividir a data "15/06/2026" em dia, mês e ano (delimitador "/"), e imprima cada parte separadamente. Lembre-se de trabalhar numa string modificável.

Ver resposta

✓ Resposta:

#include <stdio.h>
#include <string.h>
int main(void) {
    char data[] = "15/06/2026"; // vetor modificável
    char *dia = strtok(data, "/");
    char *mes = strtok(NULL, "/");
    char *ano = strtok(NULL, "/");
    printf("Dia: %s, Mês: %s, Ano: %s\n", dia, mes, ano);
    return 0;
}

A primeira chamada recebe a string; as seguintes recebem NULL para continuar de onde parou.

Exercício 4

Escreva uma função char *repetir(const char *s, int vezes) que aloque no heap e devolva uma nova string com s repetida vezes vezes. Por exemplo, repetir("ab", 3) deve retornar "ababab". Não esqueça do +1 e do free por quem chama.

Ver resposta

✓ Resposta:

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

char *repetir(const char *s, int vezes) {
    if (vezes <= 0) return NULL;
    size_t len = strlen(s);
    char *resultado = malloc(len * vezes + 1); // +1 para o '\0'
    if (resultado == NULL) return NULL;

    resultado[0] = '\0';           // começa vazia para o strcat funcionar
    for (int i = 0; i < vezes; i++) {
        strcat(resultado, s);       // anexa 's' a cada volta
    }
    return resultado;
}

int main(void) {
    char *r = repetir("ab", 3);
    if (r != NULL) {
        printf("%s\n", r); // ababab
        free(r);
    }
    return 0;
}

Alocamos espaço para len * vezes caracteres mais o terminador, iniciamos a string como vazia (resultado[0] = '\0') para que o primeiro strcat funcione, e anexamos s repetidamente. Quem recebe a string é responsável pelo free.

Exercício 5

Explique por que strtok não pode ser usado diretamente numa string literal como char *s = "a,b,c";, e o que aconteceria se você tentasse. Qual é a forma correta de declarar a string?

Ver resposta

✓ Resposta: strtok modifica a string que recebe: ele substitui cada delimitador encontrado por um terminador '\0', alterando o conteúdo original. Uma string literal como char *s = "a,b,c"; aponta para memória tipicamente somente-leitura, onde os literais são armazenados. Tentar modificá-la (o que strtok faz) é comportamento indefinido e normalmente causa uma falha de segmentação, travando o programa. A forma correta é declarar a string como um vetor modificável: char s[] = "a,b,c"; — isso cria uma cópia própria e alterável dos caracteres, na qual strtok pode inserir seus terminadores livremente.

Comentários

Mais em Linguagem C

Caçando Bugs de Memória com Valgrind e Sanitizers
Caçando Bugs de Memória com Valgrind e Sanitizers

A distância entre a causa e o sintoma é o que torna caro um bug de memória…

Sincronização: Mutexes e Variáveis de Condição
Sincronização: Mutexes e Variáveis de Condição

Esperar numa variável de condição pede while, nunca if: a thread pode acordar…

Campos de Bits e Estruturas Compactas
Campos de Bits e Estruturas Compactas

Declarar dia : 4 e gravar 20 trunca o valor sem aviso nenhum — dimensionar…