Pular para conteúdo

Semana 08 — Strings em C

Nesta semana

Unidade 2 · Seg 28/09 · Qua 30/09 · Sex 02/10

Até agora, sempre que precisamos guardar texto ficamos limitados a um único caractere de cada vez (o char da Semana 01) ou tivemos que imprimir textos "presos" dentro das aspas do printf. Nesta semana damos um salto: aprendemos a guardar, ler, percorrer e manipular palavras e frases inteiras — as chamadas strings. A boa notícia é que você já tem tudo o que precisa: uma string em C é simplesmente um vetor de char (Semana 07) com uma regrinha a mais no fim.

Essa "regrinha a mais" — o caractere nulo \0 — é o coração de tudo o que veremos. Entendê-lo bem explica por que strlen funciona, por que scanf("%s", ...) para no espaço, por que não se compara strings com == e por que existe uma família inteira de funções em <string.h>. Leia com calma e, como sempre, digite e execute cada exemplo.

🎯 Objetivos de aprendizagem

Ao final desta semana, você deverá ser capaz de:

  • explicar que uma string em C é um vetor de char terminado pelo caractere nulo \0, e distinguir 'A' (um caractere) de "A" (uma string);
  • declarar e inicializar strings, prevendo quantos bytes elas ocupam;
  • diferenciar capacidade (tamanho do vetor) de comprimento (número de caracteres até o \0);
  • ler e imprimir strings com %s, puts, scanf e, sobretudo, fgets, sabendo as armadilhas de cada um;
  • percorrer os caracteres de uma string com um laço até o \0;
  • usar as funções essenciais de <string.h>strlen, strcpy, strcmp, strcat — e explicar por que não se copia com = nem se compara com ==;
  • reconhecer e evitar o estouro de buffer, e conhecer <ctype.h> para trabalhar caractere a caractere.

📖 Segunda — Estudo do conteúdo

1. O que é, afinal, uma string em C

Lá na Semana 01 vimos a diferença entre 'A' (aspas simples, um caractere, 1 byte) e "A" (aspas duplas, uma cadeia de caracteres, que ocupa 2 bytes). Prometemos que o segundo caso seria tema da Semana 08 — e chegamos lá.

Na Semana 07 você aprendeu vetores: sequências de elementos do mesmo tipo, guardados lado a lado na memória, acessados por um índice (v[0], v[1], ...). Pois bem:

A definição que resume tudo

Uma string em C é um vetor de char cujo fim é marcado por um caractere especial: o caractere nulo, escrito \0 (barra-zero), cujo valor numérico é 0.

Não existe um "tipo string" embutido no C, como há em Python ou Java. O que existe é essa convenção: "onde aparecer um \0, a string acabou ali".

Visualmente, a string "Ola" na memória é assim:

Índice 0 1 2 3
Caractere 'O' 'l' 'a' \0
Valor ASCII 79 108 97 0

Repare: três letras, mas quatro posições ocupadas. A última guarda o \0, o "marco de fim". É por isso que "A" ocupava 2 bytes: o 'A' e o \0.

flowchart LR
    subgraph mem["String \"Ola\" na memoria (vetor de char)"]
    A["[0]<br/>'O'"] --> B["[1]<br/>'l'"] --> C["[2]<br/>'a'"] --> D["[3]<br/>'\0'"]
    end

2. Declaração e inicialização

Como toda string é um vetor de char, nós a declaramos como um vetor de char:

char nome[50];   // reserva espaco para 50 chars (ate 49 letras + o '\0')

Isso reserva 50 bytes na memória — um vetor com índices de 0 a 49. Ainda não há texto ali: são 50 posições com "lixo" (Semana 01), esperando ser preenchidas.

A forma mais cômoda de já nascer com um texto é inicializar com um literal de string (o texto entre aspas duplas):

char s[] = "Ola";   // o compilador conta os bytes e dimensiona o vetor

Neste caso, você não escreve o tamanho: o compilador olha "Ola", conta 3 letras, acrescenta automaticamente o \0 e dimensiona s com 4 bytes. É equivalente a escrever, na mão:

char s[] = {'O', 'l', 'a', '\0'};   // exatamente a mesma coisa

\"Ola\" ocupa 4 bytes, não 3

O erro clássico é dimensionar um vetor "justo" demais, esquecendo do \0. Para guardar a palavra "Ola" (3 letras) você precisa de 4 posições, não 3. Regra de bolso: capacidade útil = tamanho do vetor − 1 (a última fica sempre reservada ao \0).

char errado[3] = "Ola";   // NAO cabe o '\0' -> problema!
char certo[4]  = "Ola";   // ok: 'O','l','a','\0'

3. O terminador \0: capacidade × comprimento

O \0 (também chamado null terminator ou caractere nulo) existe por um motivo prático: um vetor, sozinho, não sabe onde a string termina. Se você declara char nome[50] e guarda só "Ana", restam 46 posições com lixo. Como as funções sabem que a palavra acabou no 'a'? Elas procuram o \0. Ele é o "ponto final" da frase.

Isso nos leva a dois conceitos diferentes, que iniciantes costumam confundir:

Conceito O que é Como obter Exemplo com char s[50] = "Ola"
Capacidade Tamanho total do vetor (posições reservadas na memória) sizeof(s) 50
Comprimento Nº de caracteres até o \0 (sem contá-lo) strlen(s) 3

Ou seja: a capacidade é fixada na declaração e não muda; o comprimento depende do conteúdo e pode variar durante a execução (desde que caiba na capacidade).

\0 (o número 0) NÃO é '0' (o caractere zero)

Três coisas parecidas na escrita, completamente diferentes na memória:

Escrita O que é Valor ASCII
\0 o caractere nulo — marca o fim da string 0
'0' o caractere dígito zero (aspas simples) 48
0 o número inteiro zero 0

Note que '\0' e 0 têm o mesmo valor (por isso s[i] == '\0' e s[i] == 0 dão no mesmo), mas '0' é bem diferente: é o desenho do algarismo zero na tela. Confundir \0 com '0' é fonte de bugs sutis.

Curiosidade 💡 — \0, '0' e a tabela ASCII

Na tabela ASCII, os dígitos '0' a '9' ocupam os códigos 48 a 57, em ordem. Isso tem um efeito bonito: para transformar o caractere '7' no número 7, basta '7' - '0' (57 − 48 = 9... opa, '7' é 55, '7' - '0' = 55 − 48 = 7 ✅). Já o \0 (código 0) fica lá no começo da tabela, entre os caracteres de controle — ele nunca é "impresso" como um símbolo visível; seu papel é puramente estrutural: dizer "acabou".

4. Ler e imprimir strings

4.1 Imprimir: %s e puts

Para imprimir uma string, use o especificador %s no printf, ou a função puts (que já adiciona uma quebra de linha ao final):

char nome[] = "Maria";
printf("Ola, %s!\n", nome);   // Ola, Maria!
puts(nome);                    // Maria  (e ja pula linha)

O printf com %s percorre a string até encontrar o \0 — mais uma vez, é o \0 que diz onde parar. Se, por acidente, a string não tiver um \0, o printf continua lendo a memória adiante e imprime lixo até topar com um zero perdido por lá.

4.2 Ler com scanf("%s", ...) — e seus perigos

Para ler uma string do teclado, a primeira opção que costumam ensinar é o scanf com %s:

char nome[50];
printf("Digite seu nome: ");
scanf("%s", nome);   // repare: SEM o & aqui!

Duas observações importantes:

  • Sem o &. Diferente de scanf("%d", &idade), aqui não usamos &. O motivo profundo (o nome do vetor já "é" um endereço) é tema da Unidade 3; por ora, guarde a regra: string no scanf vai sem &.
  • Ele para no primeiro espaço em branco. Se o usuário digitar Maria Silva, o scanf guarda apenas Maria — descarta tudo a partir do espaço. Além disso, ele não valida o tamanho: se o usuário digitar 80 letras num vetor de 50, o scanf escreve além do vetor (estouro de buffer — veja a seção 7).

Curiosidade 💡 — por que scanf(\"%s\") para no espaço?

O %s do scanf foi projetado para ler um "token" (uma sequência de caracteres sem espaços em branco). Ele pula os espaços/tabs/quebras no início, começa a guardar caracteres e para no primeiro espaço, tab ou \n que encontrar. É ótimo para ler uma palavra isolada; péssimo para ler um nome completo ou uma frase. Para isso, usamos o fgets.

4.3 Ler com fgets — a forma segura

O fgets resolve os dois problemas do scanf: lê a linha inteira (com espaços) e respeita o tamanho do vetor. Sua forma é:

fgets(destino, tamanho, stdin);
  • destino — o vetor onde guardar;
  • tamanho — quantos bytes no máximo ele pode escrever (use o tamanho do vetor);
  • stdin — a fonte da leitura (o teclado, a "entrada padrão").
char frase[100];
printf("Digite uma frase: ");
fgets(frase, 100, stdin);   // le ate 99 chars + '\0', com espacos
printf("Voce escreveu: %s", frase);

Como fgets nunca escreve mais que tamanho bytes (contando o \0), ele não estoura o vetor — por isso é a opção recomendada nesta disciplina.

O fgets pode guardar o \n no fim

O fgetsaté a quebra de linha inclusive: se você digitar Ana e apertar Enter, ele guarda A, n, a, \n, \0. Aquele \n "extra" costuma atrapalhar (o texto imprime com uma linha em branco a mais). Um jeito comum de removê-lo, depois de estudar strlen, é:

size_t n = strlen(frase);
if (n > 0 && frase[n - 1] == '\n') {
    frase[n - 1] = '\0';   // troca o '\n' pelo terminador
}

5. Percorrendo os caracteres de uma string

Como string é vetor, dá para acessar cada caractere pelo índice (s[0], s[1], ...). E como o fim é marcado pelo \0, o padrão clássico é repetir enquanto não chegar ao \0:

percorre.c
#include <stdio.h>

int main(void) {
    char s[] = "Ola";
    int i = 0;

    while (s[i] != '\0') {        // enquanto nao for o terminador
        printf("s[%d] = %c\n", i, s[i]);
        i++;
    }
    printf("Comprimento: %d\n", i);  // i parou exatamente no comprimento
    return 0;
}

Saída:

s[0] = O
s[1] = l
s[2] = a
Comprimento: 3

Repare que, ao terminar o laço, i vale 3 — exatamente o comprimento. Não por acaso: o laço andou até bater no \0, contando as letras pelo caminho. Isso é, em essência, o que a função strlen faz por dentro.

Se você já conhece strlen (próxima seção), pode escrever de forma equivalente com for:

for (int i = 0; i < strlen(s); i++) {   // funciona, mas veja o aviso adiante
    printf("%c\n", s[i]);
}

6. A biblioteca <string.h>

Manipular strings "na unha", caractere a caractere, funciona — mas o C já traz uma biblioteca com as operações mais comuns prontas: a <string.h>. Inclua-a no topo:

#include <string.h>

As quatro funções essenciais desta semana:

Função O que faz Assinatura (simplificada)
strlen devolve o comprimento (até o \0, sem contá-lo) size_t strlen(const char *s)
strcpy copia a string de origem para o destino strcpy(destino, origem)
strcmp compara duas strings int strcmp(a, b)
strcat concatena (gruda a origem no fim do destino) strcat(destino, origem)

6.1 strlen — comprimento

char s[] = "banana";
printf("%zu\n", strlen(s));   // 6  (%zu e o formato de size_t)

Curiosidade 💡 — por que strlen é O(n)?

Diferente de outras linguagens, em C a string não guarda seu próprio tamanho em lugar nenhum. A única forma de saber o comprimento é varrer o vetor contando até encontrar o \0. Isso é uma operação linear — O(n), proporcional ao tamanho. Consequência prática: escrever for (i = 0; i < strlen(s); i++) faz o strlen recomeçar a contagem a cada volta do laço (O(n²)!). Prefira guardar o comprimento antes: int n = strlen(s); for (i = 0; i < n; i++) ... — ou pare no \0, como na seção 5.

6.2 strcpy — copiar (NUNCA use =)

Para copiar o conteúdo de uma string para outra, use strcpy(destino, origem):

char origem[] = "teste";
char destino[20];
strcpy(destino, origem);   // agora destino contem "teste"

Não copie strings com =

Depois da declaração, destino = origem; não compila (ou não faz o que você espera). O = só serve para atribuir o valor na inicialização; para copiar o conteúdo de uma string já existente para outra, use strcpy. E garanta que o destino tenha capacidade suficientestrcpy copia até o \0 da origem, sem checar o tamanho do destino.

6.3 strcmp — comparar (NUNCA use ==)

Para saber se duas strings são iguais, use strcmp(a, b). Ela devolve:

Retorno de strcmp(a, b) Significa
0 as strings são iguais
negativo (< 0) a vem antes de b na ordem alfabética
positivo (> 0) a vem depois de b
char a[] = "casa";
char b[] = "casa";
if (strcmp(a, b) == 0) {
    printf("Sao iguais!\n");
}

Não compare strings com ==

Escrever if (a == b) compila, mas está errado: você não está comparando o texto, e sim os endereços dos dois vetores na memória (que quase sempre são diferentes, mesmo com o mesmo conteúdo). Para comparar o conteúdo, use sempre strcmp(a, b) == 0. Este é um dos erros mais comuns de quem vem de outras linguagens.

Atenção: iguais dá ZERO

Parece contraintuitivo: quando as strings são iguais, strcmp devolve 0 (que, como vimos na Semana 01, é "falso"). Por isso o teste de igualdade é strcmp(a, b) == 0, e o de diferença é strcmp(a, b) != 0. Leia strcmp como "quantos passos de diferença há entre elas": zero passos = iguais.

6.4 strcat — concatenar

strcat(destino, origem) gruda a origem no final do destino (a partir do \0 do destino), formando uma string só:

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

int main(void) {
    char saudacao[50] = "Ola, ";   // capacidade generosa!
    char nome[] = "Maria";

    strcat(saudacao, nome);        // saudacao vira "Ola, Maria"
    strcat(saudacao, "!");         // "Ola, Maria!"
    puts(saudacao);
    return 0;
}

O destino precisa ter capacidade para o resultado inteiro

strcat não cria espaço novo: ele escreve dentro do vetor destino que já existe. Se destino não tiver capacidade para o texto somado, você estoura o buffer. Por isso, no exemplo, declaramos saudacao[50] — folgado o suficiente para "Ola, Maria!".

7. Cuidados essenciais (leia duas vezes)

Strings são, historicamente, uma das maiores fontes de bugs (e de falhas de segurança) em C. Três cuidados resumem quase tudo:

  1. Estouro de buffer (buffer overflow). Escrever além da capacidade do vetor corrompe a memória vizinha — pode dar resultado errado, travar o programa ou abrir brechas de segurança. Sempre reserve capacidade suficiente (lembre do \0) e prefira funções que respeitam o tamanho, como o fgets.
  2. Não compare com ==. Use strcmp (compara o conteúdo; == compara endereços).
  3. Não copie com =. Use strcpy (o = só serve na inicialização).

Checklist dos três pecados capitais com strings

  • Dimensionar o vetor sem contar o \0 (capacidade justa demais).
  • Comparar strings com == em vez de strcmp.
  • Copiar strings com = em vez de strcpy.

8. Trabalhando caractere a caractere: <ctype.h>

Muitas tarefas com texto se resolvem olhando cada caractere e perguntando algo sobre ele: "é letra?", "é dígito?", ou transformando-o ("passa para maiúscula"). A biblioteca <ctype.h> traz funções prontinhas para isso:

Função Pergunta / ação
toupper(c) devolve c em maiúscula
tolower(c) devolve c em minúscula
isdigit(c) c é um dígito ('0''9')?
isalpha(c) c é uma letra?
isspace(c) c é espaço/tab/quebra de linha?
maiusculas.c
#include <stdio.h>
#include <string.h>
#include <ctype.h>

int main(void) {
    char s[] = "Ola, Mundo 123";
    int n = strlen(s);
    for (int i = 0; i < n; i++) {
        putchar(toupper(s[i]));   // imprime cada char em maiuscula
    }
    putchar('\n');   // OLA, MUNDO 123
    return 0;
}

Repare que toupper não altera o que não for letra: a vírgula, o espaço e os dígitos passam intactos. Isso torna essas funções muito convenientes para "limpar" ou padronizar texto.

9. Um exemplo que junta tudo

analisa_nome.c
#include <stdio.h>
#include <string.h>
#include <ctype.h>

int main(void) {
    char nome[50];

    printf("Digite seu nome completo: ");
    fgets(nome, 50, stdin);              // le a linha inteira, com espacos

    // remove o '\n' que o fgets costuma deixar no fim
    int n = strlen(nome);
    if (n > 0 && nome[n - 1] == '\n') {
        nome[n - 1] = '\0';
        n--;                            // atualiza o comprimento
    }

    printf("Comprimento: %d caracteres\n", n);

    // conta as letras (ignorando espacos)
    int letras = 0;
    for (int i = 0; i < n; i++) {
        if (isalpha(nome[i])) {
            letras++;
        }
    }
    printf("Letras (sem espacos): %d\n", letras);

    return 0;
}

10. Erros comuns desta semana (resumão)

Checklist de armadilhas

  • Esquecer que a string precisa de espaço para o \0 (capacidade justa demais).
  • Confundir \0 (fim de string, valor 0) com '0' (caractere, valor 48).
  • Confundir capacidade (sizeof) com comprimento (strlen).
  • Usar & no scanf de string (scanf("%s", nome) vai sem &).
  • Usar scanf("%s") esperando ler frases com espaço (ele para no espaço).
  • Esquecer de tratar o \n que o fgets deixa no fim.
  • Comparar strings com == (use strcmp).
  • Copiar strings com = (use strcpy).
  • Chamar strcat/strcpy num destino sem capacidade suficiente.
  • Chamar a função gets (proibida — veja a curiosidade abaixo).

Curiosidade 💡 — a função gets é PROIBIDA

Você pode encontrar, em textos antigos, a função gets(s) para ler uma linha. Nunca a use. Ela lê a linha sem checar o tamanho do vetor — ou seja, sempre pode estourar o buffer, não importa quão grande ele seja. Foi responsável por incontáveis falhas de segurança e por isso foi removida do padrão C11 (não existe mais no C moderno). Onde você usaria gets(s), use fgets(s, tamanho, stdin). Ponto final.


👥 Quarta — Encontro

Chegue ao encontro tendo estudado o material acima e tendo executado os exemplos. O encontro consolida e tira dúvidas — não é para ver o conteúdo pela primeira vez. Roteiro previsto:

  1. Revisão relâmpago: string = vetor de char + \0; capacidade × comprimento; %s, fgets; a família <string.h>.
  2. Construção ao vivo de um programa que lê texto e o processa caractere a caractere, praticando a modelagem: entrada → processamento → saída.
  3. Galeria de erros: provocamos de propósito os erros da seção 10 — comparar com ==, copiar com =, esquecer o \0, deixar o \n do fgets — para você aprender a reconhecer os sintomas.

Problema-guia do encontro — contador de vogais. Ler uma frase (com espaços!) e contar quantas vogais ela tem, sem diferenciar maiúsculas de minúsculas.

conta_vogais.c
#include <stdio.h>
#include <string.h>
#include <ctype.h>

int eh_vogal(char c) {
    char m = tolower(c);              // padroniza para minuscula
    return m == 'a' || m == 'e' || m == 'i' || m == 'o' || m == 'u';
}

int main(void) {
    char frase[100];
    printf("Digite uma frase: ");
    fgets(frase, 100, stdin);        // le a linha inteira

    int total = 0;
    int n = strlen(frase);
    for (int i = 0; i < n; i++) {
        if (eh_vogal(frase[i])) {
            total++;
        }
    }

    printf("Numero de vogais: %d\n", total);
    return 0;
}

Perguntas para pensar antes do encontro: por que usamos fgets e não scanf("%s", ...)? O que mudaria se a frase tivesse mais de 99 caracteres? Por que a função eh_vogal chama tolower logo no começo?


✍️ Sexta — Estudo dirigido

Reserve este tempo para praticar de verdade — escrever, errar, corrigir e executar.

Exercícios guiados (Beecrowd)

Crie/entre na sua conta no Beecrowd e resolva, em C, problemas que exercitam caracteres e strings. Sugestão (dificuldade crescente):

Problema O que exercita
1024 Criptografia percorrer caracteres, deslocar letras (ASCII) e inverter
1234 Encaixe / manipulação de texto separar e recombinar partes de uma string
1238 Combinação de strings intercalar/combinar caractere a caractere
1240 Encaixe? / busca em texto procurar um padrão dentro de uma string
1257 Manipulação de strings ler várias linhas e processá-las
1259 Ordenação com caractere classificar itens conforme um caractere marcador

Como escolher e conferir

A numeração e o tema exato de cada problema podem mudar no site — antes de investir tempo, leia o enunciado e confirme que ele envolve texto/caracteres. O objetivo aqui é treinar: leitura com fgets, percurso com laço até o \0, uso de <string.h> e <ctype.h>. Comece pelos mais curtos e vá subindo.

Desafios para escrever do zero

  1. Contar vogais de uma frase. (Já esboçado no encontro — refaça sem olhar.)
  2. Inverter uma string. Ler uma palavra e imprimi-la de trás para frente. (Dica: descubra o comprimento com strlen e percorra de n-1 até 0.)
  3. Verificar se é palíndromo. Uma string é palíndromo se é igual lida ao contrário (arara, osso). Compare s[i] com s[n-1-i].
  4. Contar palavras. Ler uma frase e contar quantas palavras ela tem. (Dica: conte as transições de espaço para não-espaço; isspace ajuda.)
  5. Converter para maiúsculas — duas versões. (a) Sem <ctype.h>: some/subtraia a diferença ASCII entre 'a' e 'A' apenas quando o char for uma letra minúscula. (b) Com <ctype.h>: use toupper. Compare as duas.
  6. Comparar duas strings com strcmp. Ler duas palavras, chamar strcmp e imprimir se são iguais ou qual vem primeiro na ordem alfabética. Explique, no comentário, o que significam os retornos negativo, zero e positivo.

Para investigar (curiosidade prática)

  • Declare char s[50] = "Ola"; e imprima, lado a lado, strlen(s) (com %zu) e sizeof(s) (com %zu). Explique por que um dá 3 e o outro dá 50 — relacione com capacidade × comprimento (seção 3).
  • Tente copiar uma string com = depois da declaração:
    char a[10] = "oi";
    char b[10];
    // b = a;   // descomente e tente compilar: o que o compilador diz?
    
    Depois, faça funcionar com strcpy(b, a); e confirme com puts(b);.
  • Imprima printf("%d %d\n", '\0', '0'); e explique os dois números que aparecem.

✅ Checklist de autoavaliação

Marque com sinceridade — se algum item não estiver ✅, volte à seção correspondente.

  • Explico que uma string é um vetor de char terminado por \0.
  • Sei que "Ola" ocupa 4 bytes e sempre reservo espaço para o \0.
  • Diferencio capacidade (sizeof) de comprimento (strlen).
  • Distingo \0 (valor 0, fim de string) de '0' (valor 48, o dígito).
  • Imprimo com %s/puts e leio com fgets, sabendo por que ele é mais seguro que scanf("%s").
  • Sei que o fgets pode deixar o \n no fim e sei como removê-lo.
  • Percorro uma string com um laço até o \0.
  • Uso strlen, strcpy, strcmp e strcat, cuidando da capacidade do destino.
  • Nunca comparo com == (uso strcmp) nem copio com = (uso strcpy).
  • Sei que a função gets é proibida e uso fgets no lugar.

🔗 Referências e para se aprofundar

  • Material 2026.1 — Aula 08: Strings em C.
  • BACKES, André. Linguagem C: completa e descomplicada (capítulo de cadeias de caracteres / strings).
  • KERNIGHAN, B.; RITCHIE, D. The C Programming Language (o "K&R"), cap. 1 (seções de arrays de caracteres) e cap. 5.
  • Referência: cppreference — <string.h> (funções strlen, strcpy, strcmp, strcat e a família de manipulação de bytes).
  • Prática: Beecrowd · Tabela ASCII: procure "tabela ASCII".

Antes de seguir para a Semana 09

Você começou a semana sabendo guardar um caractere e termina manipulando frases inteiras — descobrindo, pelo caminho, que "string" é só um vetor de char com um \0 no fim. Na Semana 09 daremos o próximo passo natural dos vetores: as matrizes (vetores de duas dimensões) — e você verá que um "vetor de strings" nada mais é que uma matriz de char. Antes de avançar, digite e execute os exemplos e resolva ao menos 3–4 desafios. Consulte também o plano de curso para se situar na Unidade 2.