C Texto ↔ Número
Tudo o que chega do teclado, de um arquivo ou da linha de comando chega como texto. Se a Ana digita 12, o programa recebe os caracteres '1' e '2', e não o número doze. Para fazer contas, é preciso converter o texto em número. E, no caminho de volta, montar um código de pedido como PED-00042 exige transformar o número em texto. Nesta aula você vai ver as ferramentas do C para as duas direções, e por que a mais famosa delas, o atoi, é a que você menos deve usar.
- Por que
"20"e20são coisas diferentes. atoi: o jeito curto, que não avisa quando o texto não é um número.strtolcomendptreerrno: o jeito certo, que detecta texto inválido, lixo depois do número e número grande demais.- Outras bases: binário, hexadecimal e a base 0.
strtodpara números com casas decimais (e o problema da vírgula).sscanfpara tirar vários valores de uma linha, como uma data.- Número → texto com
snprintf(e por queitoanão existe).
Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. As funções de conversão moram no stdlib.h ("standard library", biblioteca padrão), menos o sscanf e o snprintf, que são do stdio.h.
A ideia: o desenho do número e o número
Pense numa etiqueta de preço escrita à mão: "20". Você lê e entende "vinte", mas a etiqueta é só tinta no papel, dois desenhos de algarismos. Para somar dois preços, sua cabeça converte os desenhos em quantidades, soma, e escreve o resultado com novos desenhos.
O computador também. Na memória, a string "20" são os bytes 50 e 48 (os códigos de '2' e '0') e um '\0'; o int 20 é a quantidade vinte. Com strings, o máximo que dá para fazer é colar os desenhos:
#include <stdio.h>
int main(void) {
char a[] = "20";
char b[] = "22";
printf("Como texto: %s%s\n", a, b);
return 0;
}
$ gcc -std=c23 -Wall -Wextra soma.c -o soma $ ./soma Como texto: 2022 « colou os desenhos, não somou
Para somar, converta primeiro. A tabela mostra os dois sentidos e as ferramentas desta aula:
| Sentido | Ferramentas |
|---|---|
| Texto → inteiro | atoi (curto, sem detectar erro), strtol (o jeito certo), sscanf |
| Texto → número com casas decimais | atof (sem detectar erro), strtod (o jeito certo), sscanf |
| Número → texto | snprintf |
1. atoi: curto, mas cego
atoi = "ASCII to integer", de texto (ASCII) para inteiro. É a primeira função que todo mundo aprende:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("atoi(\"42\") = %d\n", atoi("42"));
printf("atoi(\" -7\") = %d\n", atoi(" -7"));
printf("atoi(\"12abc\") = %d\n", atoi("12abc"));
printf("atoi(\"abc\") = %d\n", atoi("abc"));
printf("atoi(\"0\") = %d\n", atoi("0"));
return 0;
}
$ gcc -std=c23 -Wall -Wextra atoi.c -o atoi
$ ./atoi
atoi("42") = 42
atoi(" -7") = -7 « pula espaços do começo e entende o sinal
atoi("12abc") = 12 « lê até onde dá e ignora o resto, calado
atoi("abc") = 0 « não é número: devolve 0…
atoi("0") = 0 « …e um "0" de verdade também dá 0
As duas últimas linhas mostram o defeito: o atoi não tem como avisar que deu errado. Se a Ana digitar "abc" na quantidade, o programa segue com zero unidades, como se ela tivesse digitado 0. E "12abc" vira 12 sem nenhum aviso. Pior: com um número grande demais o resultado é comportamento indefinido. No laboratório, atoi("99999999999999999999") deu -1; no editor do guia, o mesmo código deu 1661992959.
Use atoi só quando o texto com certeza é um número válido (por exemplo, um número que o seu próprio programa escreveu). Para qualquer coisa digitada por uma pessoa, use strtol.
2. strtol: o jeito certo
strtol = "string to long", de texto para long (um inteiro, em geral maior que o int, aula Inteiros). A diferença para o atoi é que ele conta onde parou de ler:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char texto[] = "125 reais";
char *fim;
long valor = strtol(texto, &fim, 10);
printf("valor = %ld\n", valor);
printf("parou em: \"%s\"\n", fim);
printf("leu %d caracteres\n", (int)(fim - texto));
return 0;
}
- char *fim;
- Uma variável que vai guardar um lugar dentro do texto (um ponteiro, aula Ponteiros e Strings). Leia como "marcador de página".
- strtol(texto,
- 1º argumento: o texto a converter.
- &fim,
- 2º argumento: endptr ("ponteiro do fim"). O
strtolpõe emfimo lugar do primeiro caractere que ele não usou. O&é porque a função precisa escrever na sua variável, como noscanf. - 10)
- 3º argumento: a base. 10 é a base decimal de sempre (seção 3 mostra outras).
- %ld
- O formato de
longnoprintf(aula Formatos do printf). - fim - texto
- A distância entre o começo e onde ele parou: quantos caracteres viraram número.
$ gcc -std=c23 -Wall -Wextra strtol.c -o strtol $ ./strtol valor = 125 parou em: " reais" « o resto do texto, a partir do espaço leu 3 caracteres
Com o marcador fim, dá para responder três perguntas que o atoi não responde:
| Pergunta | Como saber | Exemplo |
|---|---|---|
| Havia algum número? | fim == texto: não andou nada, então não havia dígitos | "abc", "" |
| Sobrou lixo depois? | *fim != '\0': o caractere onde parou não é o fim da string | "12abc" |
Coube no long? | errno == ERANGE (precisa zerar errno antes) | "99999999999999999999" |
O *fim (com asterisco) quer dizer "o caractere que está no lugar marcado por fim". E o errno ("error number", número do erro, do errno.h) é uma variável global que as funções da biblioteca usam para contar o que deu errado; ERANGE quer dizer "fora do intervalo" (aula Erros do Sistema).
Juntando tudo: ler uma quantidade com segurança
Este é o modelo para ler um número digitado: fgets para a linha (aula Ler uma Linha) e strtol com as três conferências:
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char linha[100];
printf("Quantidade: ");
if (fgets(linha, sizeof linha, stdin) == NULL) {
return 1;
}
linha[strcspn(linha, "\n")] = '\0';
char *fim;
errno = 0;
long qtd = strtol(linha, &fim, 10);
if (fim == linha) {
printf("\"%s\" não é um número.\n", linha);
} else if (*fim != '\0') {
printf("Sobrou texto depois do número: \"%s\"\n", fim);
} else if (errno == ERANGE) {
printf("Número grande demais.\n");
} else {
printf("Ok: %ld unidades.\n", qtd);
}
return 0;
}
- linha[strcspn(linha, "\n")] = '\0';
- Tira o
'\n'do Enter. Sem isso, o'\n'seria o "lixo depois do número" em toda leitura. - errno = 0;
- O
strtolsó escreve noerrnoquando dá erro; ele nunca o zera. Se você não zerar antes, pode ler um erro antigo, de outra função. - if (fim == linha)
- Nenhum dígito: texto vazio ou que não começa com número.
- else if (*fim != '\0')
- Leu um número, mas a linha não acabou ali.
- else if (errno == ERANGE)
- O número não cabe num
long.
$ gcc -std=c23 -Wall -Wextra validar.c -o validar $ echo "12" | ./validar Quantidade: Ok: 12 unidades. $ echo "abc" | ./validar Quantidade: "abc" não é um número. $ echo "12abc" | ./validar Quantidade: Sobrou texto depois do número: "abc" $ echo "" | ./validar Quantidade: "" não é um número. $ echo "99999999999999999999" | ./validar Quantidade: Número grande demais. $ echo " 8" | ./validar Quantidade: Ok: 8 unidades. « espaços no começo são aceitos $ echo "-3" | ./validar Quantidade: Ok: -3 unidades. « o sinal também (confira se negativo faz sentido) $ echo "7 " | ./validar Quantidade: Sobrou texto depois do número: " " « espaço no fim conta como sobra
Cada entrada errada recebeu a mensagem certa. Repare nas duas últimas: o strtol aceita -3, e uma quantidade negativa não faz sentido na lojinha. Conferir se o valor está na faixa esperada (qtd < 1, por exemplo) é trabalho seu, depois da conversão. Se você precisa do valor num int, confira também se ele cabe (qtd >= INT_MIN && qtd <= INT_MAX, com limits.h) antes de guardar. A aula Validar a Entrada transforma esse modelo numa função reutilizável.
O número grande demais, por dentro
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
int main(void) {
errno = 0;
long n = strtol("99999999999999999999", NULL, 10);
printf("n = %ld\n", n);
printf("LONG_MAX = %ld\n", LONG_MAX);
printf("errno == ERANGE? %s\n", errno == ERANGE ? "sim" : "não");
printf("atoi: %d\n", atoi("99999999999999999999"));
return 0;
}
$ gcc -std=c23 -Wall -Wextra grande.c -o grande $ ./grande n = 9223372036854775807 LONG_MAX = 9223372036854775807 « o maior long no Linux de 64 bits errno == ERANGE? sim atoi: -1 « comportamento indefinido
Quando o número não cabe, o strtol devolve o maior valor possível (LONG_MAX, ou LONG_MIN para negativos) e põe ERANGE no errno: um resultado previsível e um aviso. O NULL no lugar de &fim quer dizer "não quero saber onde parou". No editor do guia, o long tem 4 bytes e o mesmo programa mostra 2147483647 nas duas primeiras linhas (veja O Editor do Guia).
Missão: a Ana digitou 12abc (já está no campo Entrada) e o programa aceitou 12 unidades. Troque o atoi por strtol com &fim e confira se sobrou texto: nesse caso, mostre Entrada inválida.. Depois, teste com 12 na Entrada: tem de continuar dando Ok: 12 unidades..
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char linha[100];
printf("Quantidade: ");
if (fgets(linha, sizeof linha, stdin) == NULL) {
return 1;
}
linha[strcspn(linha, "\n")] = '\0';
int qtd = atoi(linha);
printf("Ok: %d unidades.\n", qtd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char linha[100];
printf("Quantidade: ");
if (fgets(linha, sizeof linha, stdin) == NULL) {
return 1;
}
linha[strcspn(linha, "\n")] = '\0';
char *fim;
long qtd = strtol(linha, &fim, 10);
if (fim == linha || *fim != '\0') {
printf("Entrada inválida.\n");
return 1;
}
printf("Ok: %ld unidades.\n", qtd);
return 0;
}
3. Outras bases
O 3º argumento do strtol é a base. Além de 10, as mais usadas são 2 (binário), 16 (hexadecimal, com dígitos de 0 a 9 e de a a f) e a base 0, que descobre a base pelo jeito como o número começa:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("\"ff\" na base 16 = %ld\n", strtol("ff", NULL, 16));
printf("\"1010\" na base 2 = %ld\n", strtol("1010", NULL, 2));
printf("\"0x1F\" na base 0 = %ld\n", strtol("0x1F", NULL, 0));
printf("\"017\" na base 0 = %ld\n", strtol("017", NULL, 0));
printf("\"017\" na base 10 = %ld\n", strtol("017", NULL, 10));
return 0;
}
$ gcc -std=c23 -Wall -Wextra bases.c -o bases $ ./bases "ff" na base 16 = 255 "1010" na base 2 = 10 "0x1F" na base 0 = 31 « começa com 0x: hexadecimal "017" na base 0 = 15 « começa com 0: octal (base 8)! "017" na base 10 = 17
Na base 0, um número que começa com 0 é lido em octal (base 8), como no código C (aula Inteiros). Um CEP, um mês "07" ou um código "017" digitado por uma pessoa viraria outra coisa. Para números digitados por gente, use sempre a base 10.
4. strtod: números com casas decimais
strtod = "string to double". Funciona como o strtol, com o mesmo &fim e o mesmo errno, mas sem a base:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
char *fim;
double preco = strtod("4.99", &fim);
printf("preço = %.2f (sobrou \"%s\")\n", preco, fim);
double virgula = strtod("4,99", &fim);
printf("com vírgula = %.2f (sobrou \"%s\")\n", virgula, fim);
double cientifico = strtod("1.5e3", &fim);
printf("1.5e3 = %.1f\n", cientifico);
return 0;
}
$ gcc -std=c23 -Wall -Wextra strtod.c -o strtod $ ./strtod preço = 4.99 (sobrou "") com vírgula = 4.00 (sobrou ",99") « parou na vírgula! 1.5e3 = 1500.0 « notação científica: 1,5 × 10³
O C usa o ponto como separador decimal, como os Estados Unidos. Com "4,99", o strtod leu o 4 e parou na vírgula, e o fim mostra isso. Num programa para brasileiros, há duas saídas simples: avisar "use ponto" quando *fim for uma vírgula, ou trocar a vírgula por ponto na string antes de converter (com strchr, aula Funções de String). Existe também o atof ("ASCII to float"), o atoi dos decimais, com o mesmo defeito: não avisa erro.
5. sscanf: vários valores de uma vez
O sscanf ("string scanf") é o scanf que lê de uma string em vez do teclado. É prático quando o texto tem um formato fixo, como uma data:
#include <stdio.h>
int main(void) {
char data[] = "17/10/2026";
int dia, mes, ano;
int lidos = sscanf(data, "%d/%d/%d", &dia, &mes, &ano);
printf("lidos = %d\n", lidos);
printf("dia %d, mês %d, ano %d\n", dia, mes, ano);
char outra[] = "17-10-2026";
lidos = sscanf(outra, "%d/%d/%d", &dia, &mes, &ano);
printf("com traços: lidos = %d\n", lidos);
return 0;
}
$ gcc -std=c23 -Wall -Wextra sscanf.c -o sscanf $ ./sscanf lidos = 3 dia 17, mês 10, ano 2026 com traços: lidos = 1 « leu o 17 e travou no '-', que não é '/'
- sscanf(data, …)
- O 1º argumento é a string de onde ler; o resto é igual ao
scanf(aula Entrada com scanf). - "%d/%d/%d"
- Um número, uma barra, um número, uma barra, um número. As barras do formato precisam aparecer no texto.
- lidos
- Quantos valores foram lidos. Confira se é o número esperado (3): com traços, só o dia foi lido.
O sscanf com %d detecta texto que não é número (o retorno fica menor), mas não detecta número grande demais: isso é comportamento indefinido. Para um único número digitado, prefira o strtol; para um formato com várias partes, o sscanf é a ferramenta mais prática.
6. Número → texto: snprintf
O caminho de volta é o snprintf da aula Funções de String: o printf que escreve num array, com limite. Todos os formatos do printf valem (aula Formatos do printf):
#include <stdio.h>
#include <string.h>
int main(void) {
int pedido = 42;
double total = 37.5;
char codigo[20];
char valor[20];
snprintf(codigo, sizeof codigo, "PED-%05d", pedido);
snprintf(valor, sizeof valor, "R$ %.2f", total);
printf("%s: %s\n", codigo, valor);
printf("O código tem %zu caracteres.\n", strlen(codigo));
return 0;
}
- "PED-%05d"
- O número com 5 dígitos, completando com zeros à esquerda.
- "R$ %.2f"
- O
doublecom 2 casas decimais. - strlen(codigo)
- Agora é uma string de verdade: dá para medir, comparar, copiar, guardar num arquivo.
$ gcc -std=c23 -Wall -Wextra paratexto.c -o paratexto $ ./paratexto PED-00042: R$ 37.50 O código tem 9 caracteres.
Você pode encontrar em tutoriais uma função itoa ("integer to ASCII"). Ela não faz parte do C: existia em alguns compiladores antigos do Windows. No gcc 15:
$ gcc -std=c23 -Wall -Wextra itoa.c -o itoa
itoa.c: In function ‘main’:
itoa.c:6:5: error: implicit declaration of function ‘itoa’ [-Wimplicit-function-declaration]
6 | itoa(42, texto, 10);
| ^~~~
O jeito portátil é sempre snprintf(texto, sizeof texto, "%d", n);. E o sprintf (sem o n) tem o mesmo problema do strcpy: não recebe o tamanho do array.
Qual usar
| Situação | Use |
|---|---|
| Um inteiro digitado por uma pessoa | strtol(texto, &fim, 10) + as três conferências |
| Um número com casas decimais digitado | strtod(texto, &fim) + as mesmas conferências |
| Vários valores num formato fixo (data, hora, coordenadas) | sscanf, conferindo o retorno |
| Texto que com certeza é um número válido | atoi / atof servem, mas o strtol nunca é pior |
| Número → texto | snprintf(dest, sizeof dest, "%d", n) |
Quando dá errado
O que significa: o programa usa atoi, que não detecta erro.
Como consertar: troque por strtol(texto, &fim, 10) e confira fim == texto (nenhum dígito) e *fim != '\0' (sobrou texto).
O que significa: a linha veio do fgets com o '\n' no fim, e o strtol parou nele.
Como consertar: tire o '\n' antes de converter: linha[strcspn(linha, "\n")] = '\0';.
errno diz ERANGE para um número pequeno
O que significa: o errno tinha um erro antigo de outra função; o strtol não o zera quando dá certo.
Como consertar: escreva errno = 0; logo antes do strtol.
"4,99" virou 4.00
O que significa: o strtod só entende ponto; parou na vírgula.
Como consertar: confira *fim; avise para usar ponto ou troque a vírgula por ponto antes de converter.
"08" ou "017" virou outro número (ou parou no meio)
O que significa: você usou a base 0, que lê números com zero à esquerda em octal ("017" = 15; em "08", o 8 nem é dígito octal).
Como consertar: use a base 10.
error: implicit declaration of function ‘itoa’
O que significa: itoa não existe no C padrão.
Como consertar: snprintf(texto, sizeof texto, "%d", n);.
error: implicit declaration of function ‘strtol’ (ou ‘atoi’)
O que significa: faltou o #include <stdlib.h>. Para errno e ERANGE, falta o #include <errno.h>.
Como consertar: acrescente os includes no topo.
Referência rápida
| Função | Nome em inglês | Cabeçalho | Converte | Detecta erro? |
|---|---|---|---|---|
atoi(s) | ASCII to integer | stdlib.h | texto → int | Não (devolve 0; estouro é indefinido) |
atol(s) | ASCII to long | stdlib.h | texto → long | Não |
atof(s) | ASCII to float | stdlib.h | texto → double | Não |
strtol(s, &fim, base) | string to long | stdlib.h | texto → long | Sim: fim e errno == ERANGE |
strtoll(s, &fim, base) | string to long long | stdlib.h | texto → long long | Sim |
strtoul(s, &fim, base) | string to unsigned long | stdlib.h | texto → unsigned long | Sim (mas aceita "-1"!) |
strtod(s, &fim) | string to double | stdlib.h | texto → double | Sim |
sscanf(s, "%d", &n) | string scan formatted | stdio.h | texto → vários | Em parte: retorno conta os lidos; estouro é indefinido |
snprintf(d, sizeof d, "%d", n) | string n print formatted | stdio.h | número → texto | Retorno: tamanho que o texto teria |
Exercícios
1. A base certa
Complete para converter um texto digitado por uma pessoa:
2. Nenhum dígito
Complete a condição que é verdadeira quando o texto não tinha nenhum número:
3. Número para texto
Complete para guardar o número n como texto em s:
4. Quanto vale atoi("abc")?
5. Depois de strtol("30kg", &fim, 10), o que printf("%s", fim) mostra?
6. Por que escrever errno = 0; antes do strtol?
7. O que strtod("2,5", &fim) devolve?
Missão final: o recibo da lojinha
Missão: a Ana digita a quantidade (3) e o preço unitário (2.50), uma linha cada (já estão no campo Entrada). Converta a quantidade com strtol e o preço com strtod, calcule o total e monte o recibo numa string com snprintf, no formato "%ld x R$ %.2f = R$ %.2f". Mostre o recibo e quantos caracteres ele tem. Sem avisos.
3 x R$ 2.50 = R$ 7.50 Recibo com 21 caracteres
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char linha_qtd[50];
char linha_preco[50];
char recibo[60] = "";
if (fgets(linha_qtd, sizeof linha_qtd, stdin) == NULL ||
fgets(linha_preco, sizeof linha_preco, stdin) == NULL) {
return 1;
}
// converta, calcule e monte o recibo aqui
printf("%s\n", recibo);
printf("Recibo com %zu caracteres\n", strlen(recibo));
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char linha_qtd[50];
char linha_preco[50];
char recibo[60] = "";
if (fgets(linha_qtd, sizeof linha_qtd, stdin) == NULL ||
fgets(linha_preco, sizeof linha_preco, stdin) == NULL) {
return 1;
}
long qtd = strtol(linha_qtd, NULL, 10);
double preco = strtod(linha_preco, NULL);
double total = qtd * preco;
snprintf(recibo, sizeof recibo, "%ld x R$ %.2f = R$ %.2f", qtd, preco, total);
printf("%s\n", recibo);
printf("Recibo com %zu caracteres\n", strlen(recibo));
return 0;
}
Aqui o NULL no lugar de &fim deixa a solução curta, e o '\n' no fim das linhas não atrapalha porque nada é conferido. Num programa de verdade, use o modelo da seção 2 com as três conferências. O recibo começa como string vazia (= "") para o programa inicial não mostrar lixo.
Resumo
- Texto que parece número (
"20") não é número: é preciso converter para fazer contas. atoié curto, mas devolve 0 para texto inválido, ignora lixo depois do número e tem resultado indefinido para números grandes. Só para texto que com certeza é válido.strtol(texto, &fim, 10)é o jeito certo:fim == texto(nenhum dígito),*fim != '\0'(sobrou texto),errno == ERANGE(grande demais, comerrno = 0antes). Tire o'\n'dofgetsantes.- Use base 10 para números digitados; a base 0 lê
"017"como octal. strtodconverte decimais e só entende ponto.sscanftira vários valores de um formato fixo; confira o retorno.- Número → texto:
snprintf(dest, sizeof dest, "%d", n).itoanão existe no C padrão.