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
charterminado 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,scanfe, 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:
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):
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:
\"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).
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:
Duas observações importantes:
- Sem o
&. Diferente descanf("%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 noscanfvai sem&. - Ele para no primeiro espaço em branco. Se o usuário digitar
Maria Silva, oscanfguarda apenasMaria— 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, oscanfescreve 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 é:
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 fgets lê até 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, é:
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:
#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:
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:
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:
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¶
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):
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 suficiente — strcpy 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 |
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ó:
#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:
- 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 ofgets. - Não compare com
==. Usestrcmp(compara o conteúdo;==compara endereços). - Não copie com
=. Usestrcpy(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 destrcmp. - Copiar strings com
=em vez destrcpy.
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? |
#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¶
#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
&noscanfde string (scanf("%s", nome)vai sem&). - Usar
scanf("%s")esperando ler frases com espaço (ele para no espaço). - Esquecer de tratar o
\nque ofgetsdeixa no fim. - Comparar strings com
==(usestrcmp). - Copiar strings com
=(usestrcpy). - Chamar
strcat/strcpynum 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:
- Revisão relâmpago: string = vetor de
char+\0; capacidade × comprimento;%s,fgets; a família<string.h>. - Construção ao vivo de um programa que lê texto e o processa caractere a caractere, praticando a modelagem: entrada → processamento → saída.
- Galeria de erros: provocamos de propósito os erros da seção 10 — comparar com
==, copiar com=, esquecer o\0, deixar o\ndofgets— 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.
#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):
| Nº | 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¶
- Contar vogais de uma frase. (Já esboçado no encontro — refaça sem olhar.)
- Inverter uma string. Ler uma palavra e imprimi-la de trás para frente. (Dica:
descubra o comprimento com
strlene percorra den-1até0.) - Verificar se é palíndromo. Uma string é palíndromo se é igual lida ao contrário
(
arara,osso). Compares[i]coms[n-1-i]. - Contar palavras. Ler uma frase e contar quantas palavras ela tem. (Dica: conte as
transições de espaço para não-espaço;
isspaceajuda.) - 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>: usetoupper. Compare as duas. - Comparar duas strings com
strcmp. Ler duas palavras, chamarstrcmpe 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) esizeof(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: Depois, faça funcionar comstrcpy(b, a);e confirme computs(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
charterminado 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/putse leio comfgets, sabendo por que ele é mais seguro quescanf("%s"). - Sei que o
fgetspode deixar o\nno fim e sei como removê-lo. - Percorro uma string com um laço até o
\0. - Uso
strlen,strcpy,strcmpestrcat, cuidando da capacidade do destino. - Nunca comparo com
==(usostrcmp) nem copio com=(usostrcpy). - Sei que a função
getsé proibida e usofgetsno 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çõesstrlen,strcpy,strcmp,strcate 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.