C++ Texto ↔ Número
Para o computador, o texto "42" e o número 42 são coisas completamente diferentes. O texto é um colar de dois caracteres, '4' e '2'; o número é um valor com o qual dá para fazer contas. Toda vez que um número chega como texto (digitado com getline, lido de um arquivo, recortado com substr) você precisa convertê-lo antes de somar. E o caminho contrário também aparece: pôr um número dentro de uma mensagem. Esta aula ensina as duas direções e o que fazer quando o texto não é um número.
- Converter texto em número com
std::stoi,std::stolestd::stod. - O que acontece com
"42abc","3.99"e espaços, e as mensagensstd::invalid_argument: stoiestd::out_of_range: stoi. - Por que
std::stod("3,14")dá 3, e como lidar com a vírgula brasileira. - Converter número em texto com
std::to_string(e quando preferirstd::format). - Converter sem exceções com
std::from_charsC++17. - A ideia do
std::stringstream.
Todos os programas desta aula foram compilados com o g++ 15.2 na máquina fictícia da Ana, na pasta ~/cpp, com o comando padrão g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.
A ideia: o desenho do número e o número
Pense numa etiqueta de preço escrita à mão, "42". Ela é um desenho: dois rabiscos. Você não soma etiquetas; você lê o desenho, entende "quarenta e dois" e faz a conta de cabeça. Converter texto em número é ensinar o programa a "ler a etiqueta". O caminho inverso, número em texto, é "escrever a etiqueta".
Veja a diferença na prática: com texto, + junta; com número, + soma.
#include <iostream>
#include <string>
int main() {
std::string texto = "42";
int numero = std::stoi(texto);
std::cout << numero + 8 << '\n';
std::cout << texto + "8" << '\n';
double preco = std::stod("19.90");
std::cout << preco * 2 << '\n';
long populacao = std::stol("3000000000");
std::cout << populacao << '\n';
}
$ g++ -std=c++23 -Wall -Wextra stoi.cpp -o stoi $ ./stoi 50 « número: 42 + 8 428 « texto: "42" junto com "8" 39.8 3000000000
1. Texto → número: stoi, stol, stod
Os nomes parecem enigmas, mas seguem um padrão: sto = "string to" ("de string para"), e a última letra é o tipo de destino.
| Função | Lê-se | Devolve | Exemplo |
|---|---|---|---|
std::stoi(s) | string to int | int (até uns 2 bilhões) | std::stoi("42") → 42 |
std::stol(s) | string to long | long | std::stol("3000000000") |
std::stoll(s) | string to long long | long long | Números enormes |
std::stod(s) | string to double | double (com casas decimais) | std::stod("19.90") → 19.9 |
std::stof(s) | string to float | float | Raramente usado |
- int numero = std::stoi(texto);
- Lê o texto
"42"e devolve o número 42, que é guardado numint. Otextocontinua igual. - std::stod("19.90")
- Para números com casas decimais: devolve um
double(veja float e double). O separador decimal é o ponto. Ao mostrar, ostd::couttira o zero do fim:39.8. - std::stol("3000000000")
- Três bilhões não cabem num
int; numlong, sim (na máquina da Ana, até uns 9 quintilhões; veja Inteiros).
O que o stoi aceita
O stoi é mais tolerante do que parece. Ele pula espaços do começo, lê os dígitos que conseguir e para no primeiro caractere que não é dígito, sem reclamar:
#include <iostream>
#include <string>
int main() {
std::cout << std::stoi(" 42") << '\n';
std::cout << std::stoi("42abc") << '\n';
std::cout << std::stoi("-7") << '\n';
std::cout << std::stoi("3.99") << '\n';
std::cout << std::stoi("+15") << '\n';
std::size_t usados = 0;
int n = std::stoi("42abc", &usados);
std::cout << n << " usou " << usados << " caracteres\n";
}
$ g++ -std=c++23 -Wall -Wextra stoi2.cpp -o stoi2 $ ./stoi2 42 « os espaços do começo foram pulados 42 « parou no "a", sem erro! -7 3 « parou no ponto: não arredonda, corta 15 42 usou 2 caracteres
- std::stoi("42abc")
- Devolve 42 e ignora o resto. Isso é perigoso: se a pessoa digitou
42abcpor engano, o programa segue como se estivesse tudo certo. - std::stoi("3.99")
- Para no ponto e devolve 3. Para ler o número com casas, use
std::stod. - std::stoi("42abc", &usados)
- Um segundo argumento opcional: o
stoiescreve emusadosquantos caracteres conseguiu ler (o&entrega o "endereço" da variável para a função escrever nela; veja Ponteiros). Seusadosfor menor que o tamanho do texto, sobrou lixo: dá para recusar a entrada comif (usados != texto.size()).
Missão: os dois números chegaram como texto, e o programa mostra 1527 em vez da soma. Converta os dois com std::stoi para mostrar 15 + 27 = 42.
#include <iostream>
#include <string>
int main() {
std::string a = "15";
std::string b = "27";
std::cout << a << " + " << b << " = " << a + b << '\n';
}
#include <iostream>
#include <string>
int main() {
std::string a = "15";
std::string b = "27";
int soma = std::stoi(a) + std::stoi(b);
std::cout << a << " + " << b << " = " << soma << '\n';
}
2. Quando o texto não é um número
Se o texto não começa com um número, o stoi não tem o que devolver. Ele então lança uma exceção, o "alarme" que você viu com o .at() em Acessar Caracteres:
#include <iostream>
#include <string>
int main() {
std::string idade = "trinta";
std::cout << "convertendo...\n";
int n = std::stoi(idade);
std::cout << n << '\n';
}
$ g++ -std=c++23 -Wall -Wextra invalido.cpp -o invalido $ ./invalido convertendo... terminate called after throwing an instance of 'std::invalid_argument' what(): stoi Abortado (imagem do núcleo gravada) ./invalido
- std::invalid_argument
- "Argumento inválido": o texto entregue não começa com um número. Acontece com
"trinta","abc","R$ 10"e com a string vazia""(por exemplo, quando a pessoa só apertou Enter). - what(): stoi
- A explicação é só o nome da função que reclamou. Não diz qual texto era: se o programa tem vários
stoi, você vai ter de descobrir qual foi. - Abortado
- Como ninguém tratou o alarme, o programa foi encerrado. O
std::cout << nnunca rodou.
E se o texto é um número, mas grande demais para o tipo?
#include <iostream>
#include <string>
int main() {
std::string cpf = "12345678901";
std::cout << "convertendo...\n";
int n = std::stoi(cpf);
std::cout << n << '\n';
}
$ g++ -std=c++23 -Wall -Wextra grande.cpp -o grande $ ./grande convertendo... terminate called after throwing an instance of 'std::out_of_range' what(): stoi Abortado (imagem do núcleo gravada) ./grande
std::out_of_range, "fora do intervalo": 12 bilhões não cabem num int. Com std::stoll, caberia. Mas repare no exemplo: um CPF não é um número de verdade. Você nunca vai somar dois CPFs, e um CPF começando com zero perderia o zero. Códigos, telefones, CEPs e CPFs devem ficar em std::string.
Tratar o erro em vez de morrer
Com try ("tente") e catch ("pegue"), o programa captura o alarme e decide o que fazer. A aula Exceções explica tudo; aqui está só o formato, para você reconhecer:
#include <iostream>
#include <stdexcept>
#include <string>
int main() {
std::string entrada = "trinta";
try {
int idade = std::stoi(entrada);
std::cout << "Idade: " << idade << '\n';
} catch (const std::invalid_argument&) {
std::cout << "\"" << entrada << "\" não é um número\n";
} catch (const std::out_of_range&) {
std::cout << "número grande demais\n";
}
}
$ g++ -std=c++23 -Wall -Wextra trycatch.cpp -o trycatch $ ./trycatch "trinta" não é um número
Se você prefere não lidar com exceções, a seção 5 mostra o std::from_chars, que avisa o erro por um valor de retorno.
3. A vírgula brasileira: stod("3,14") dá 3
No Brasil escrevemos 3,14. O C++ espera 3.14. Veja o que acontece quando alguém digita do jeito brasileiro:
#include <iostream>
#include <string>
int main() {
double a = std::stod("3.14");
double b = std::stod("3,14");
std::cout << a << '\n';
std::cout << b << '\n';
}
$ g++ -std=c++23 -Wall -Wextra decimal.cpp -o decimal $ ./decimal 3.14 3 « sem erro, sem aviso: as casas decimais sumiram
É a mesma regra do "42abc": o stod lê 3, encontra a vírgula, que para ele não faz parte de um número, e para ali. Não há exceção. Numa lojinha, um produto de R$ 12,50 passaria a custar R$ 12,00, em silêncio.
A solução mais simples é trocar a vírgula por ponto antes de converter, com o find da aula Funções de String:
#include <iostream>
#include <string>
int main() {
std::string digitado = "3,14";
std::size_t pos = digitado.find(',');
if (pos != std::string::npos) {
digitado[pos] = '.';
}
double valor = std::stod(digitado);
std::cout << valor * 2 << '\n';
}
$ g++ -std=c++23 -Wall -Wextra trocavirgula.cpp -o trocavirgula $ ./trocavirgula 6.28
O stod segue o locale do programa, a configuração de idioma e país. Todo programa C++ começa no locale "C", que usa ponto, mesmo num Linux em português. Se o programa pedir o locale do sistema, com std::setlocale(LC_ALL, "") (de <clocale>), na máquina da Ana (pt_BR.UTF-8) a regra se inverte:
$ cat locale.cpp
#include <clocale>
#include <iostream>
#include <string>
int main() {
std::setlocale(LC_ALL, "");
std::cout << std::stod("3,14") << '\n';
std::cout << std::stod("3.14") << '\n';
}
$ g++ -std=c++23 -Wall -Wextra locale.cpp -o locale
$ ./locale
3.14
3
O mesmo programa dá resultados diferentes conforme o computador em que roda. Por isso a recomendação é: não dependa do locale; troque a vírgula você mesmo, ou use std::from_chars (seção 5), que sempre usa ponto.
4. Número → texto: std::to_string
O caminho de volta. Você já usou std::to_string em Juntar Strings, para pôr um número dentro de uma mensagem:
#include <format>
#include <iostream>
#include <string>
int main() {
std::string a = std::to_string(42);
std::string b = std::to_string(-7);
std::string c = std::to_string(3.14);
std::string d = std::to_string(1.0 / 3);
std::cout << a << " | " << b << " | " << c << " | " << d << '\n';
std::cout << a.size() << '\n';
std::string e = std::format("{:.2f}", 3.14159);
std::cout << e << '\n';
}
$ g++ -std=c++23 -Wall -Wextra tostring2.cpp -o tostring2 $ ./tostring2 42 | -7 | 3.140000 | 0.333333 2 « "42" é um texto de 2 caracteres 3.14
- std::to_string(42)
- Devolve o
std::string"42". Agora ele tem.size(), pode ser juntado com+, recortado comsubstr… - std::to_string(3.14)
- Com
double, sempre 6 casas decimais:3.140000. Não dá para escolher. - std::format("{:.2f}", 3.14159)
- Quando você quer controlar as casas, o
std::formatC++20 resolve:{:.2f}= "número com 2 casas". Detalhes em std::format e std::print.
5. Sem exceções: std::from_chars C++17
std::from_chars ("a partir de caracteres", do cabeçalho <charconv>) é a conversão "profissional": a mais rápida, sempre com ponto (ignora o locale), e que não lança exceções. Em vez de abortar, ela devolve um resultado com um código de erro, que você confere com if. A escrita é mais comprida:
#include <charconv>
#include <iostream>
#include <string>
int main() {
std::string texto = "2026";
int ano = 0;
auto resultado = std::from_chars(texto.data(), texto.data() + texto.size(), ano);
if (resultado.ec == std::errc()) {
std::cout << "ano: " << ano << '\n';
}
std::string ruim = "abc";
int x = 0;
auto r2 = std::from_chars(ruim.data(), ruim.data() + ruim.size(), x);
if (r2.ec == std::errc::invalid_argument) {
std::cout << "\"" << ruim << "\" não é número\n";
}
std::string enorme = "99999999999";
auto r3 = std::from_chars(enorme.data(), enorme.data() + enorme.size(), x);
if (r3.ec == std::errc::result_out_of_range) {
std::cout << "grande demais para int\n";
}
std::string meio = "42abc";
auto r4 = std::from_chars(meio.data(), meio.data() + meio.size(), x);
std::cout << x << ", sobrou: " << r4.ptr << '\n';
double d = 0;
std::string preco = "19.90";
std::from_chars(preco.data(), preco.data() + preco.size(), d);
std::cout << d << '\n';
}
$ g++ -std=c++23 -Wall -Wextra fromchars.cpp -o fromchars $ ./fromchars ano: 2026 "abc" não é número grande demais para int 42, sobrou: abc 19.9
- texto.data(), texto.data() + texto.size()
- O
from_charsnão recebe ostd::string, recebe onde o texto começa e onde termina na memória..data()dá o endereço do primeiro caractere; somando o tamanho, o endereço logo depois do último. Copie a receita. - ano
- A variável onde o número é escrito. O tipo dela decide o tipo da conversão (
int,long,double…). Se der erro, ela não é mexida. - auto resultado = …
- O
from_charsdevolve dois dados juntos; oautodeixa o compilador descobrir o tipo (veja auto). - resultado.ec
- O código de erro (error code).
std::errc()= "nenhum erro";std::errc::invalid_argument= não é número;std::errc::result_out_of_range= não cabe no tipo. - r4.ptr
- Onde a leitura parou. Em
"42abc", aponta para oabcque sobrou: assim você descobre que havia lixo depois do número.
Diferente do stoi, o from_chars não pula espaços do começo e não aceita o sinal +: o texto tem de começar direto no número. Para quem começa, o stoi/stod com try/catch é mais fácil de ler. O from_chars brilha quando há muitos números para converter (um arquivo grande) ou num programa que não usa exceções.
6. A ideia do std::stringstream
Há uma terceira forma, mais antiga, que você vai encontrar muito em código e em respostas na internet: os string streams (cabeçalho <sstream>). A ideia é usar o >> e o << que você já conhece, só que lendo de uma string e escrevendo numa string, em vez do teclado e da tela:
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::istringstream entrada("10 20 30");
int a = 0, b = 0, c = 0;
entrada >> a >> b >> c;
std::cout << a + b + c << '\n';
std::ostringstream saida;
saida << "Total: " << a + b + c << " itens";
std::string texto = saida.str();
std::cout << texto << '\n';
}
$ g++ -std=c++23 -Wall -Wextra sstream.cpp -o sstream $ ./sstream 60 Total: 60 itens
- std::istringstream entrada("10 20 30");
- Uma "entrada de mentira" (
i= input) cujo conteúdo é o texto. Funciona como ostd::cin. - entrada >> a >> b >> c;
- Lê três números separados por espaço, exatamente como leria do teclado (aula Entrada com cin).
- std::ostringstream saida;
- Uma "tela de mentira" (
o= output): o que você manda com<<vai sendo guardado. - saida.str()
- Devolve tudo o que foi escrito, como
std::string.
É útil para separar uma linha em vários valores (você já viu isso com getline e vírgulas, em Ler uma Linha). Para converter um número, prefira stoi/stod; para montar textos, std::format.
c - '0'
Para converter um único caractere de dígito ('7') no número (7), não precisa de função: int n = c - '0';. Funciona porque os dígitos '0' a '9' estão em sequência na tabela de caracteres ('0' é 48, '5' é 53; 53 − 48 = 5). Veja Caracteres.
Qual usar?
| Situação | Use |
|---|---|
| Texto → número, no dia a dia | std::stoi / std::stod (com try/catch se o texto vem do usuário) |
| Texto → número, sem exceções ou com muitos números | std::from_chars C++17 |
| Vários números numa linha | std::istringstream com >> |
| Número → texto, simples | std::to_string |
| Número → texto, controlando casas e formato | std::format C++20 |
Número brasileiro (3,14) | Troque a vírgula por ponto antes de converter |
Quando dá errado
terminate called after throwing an instance of 'std::invalid_argument' / what(): stoi
O que significa: o texto entregue ao stoi (ou stod, stol: o what() diz qual) não começa com um número. Causas comuns: a string está vazia (o getline leu uma linha vazia, veja o bug do getline), tem letras, ou tem R$ na frente.
Como consertar: confira o texto antes (mostre-o entre colchetes: std::cout << "[" << s << "]"), limpe o que não é número e trate a exceção com try/catch, ou use std::from_chars.
terminate called after throwing an instance of 'std::out_of_range' / what(): stoi
O que significa: o texto é um número, mas não cabe no tipo (int vai até 2147483647).
Como consertar: use std::stoll (long long). Se é um código (CPF, telefone, CEP), não converta: guarde como std::string.
12,50 virou 12)
O que significa: std::stod parou na vírgula. Não há erro nem aviso.
Como consertar: troque ',' por '.' antes de converter (seção 3).
stoi aceitou "42abc" ou "3.99" sem reclamar
O que significa: o stoi lê o que pode e ignora o resto.
Como consertar: use o segundo argumento (std::stoi(s, &usados)) e recuse se usados != s.size(); ou confira o ptr do from_chars.
Referência rápida
| Escrita | O que faz | Exemplo → resultado |
|---|---|---|
std::stoi(s) | Texto → int | std::stoi("42") → 42 |
std::stol(s) / std::stoll(s) | Texto → long / long long | std::stoll("3000000000") |
std::stod(s) | Texto → double (com ponto) | std::stod("19.90") → 19.9 |
std::stoi(s, &usados) | Também diz quantos caracteres leu | "42abc" → 42, usados = 2 |
std::invalid_argument | Exceção: não é número | std::stoi("abc"), std::stoi("") |
std::out_of_range | Exceção: não cabe no tipo | std::stoi("12345678901") |
std::to_string(n) | Número → texto | std::to_string(3.14) → "3.140000" |
std::format("{:.2f}", x) | Número → texto formatado C++20 | "3.14" |
std::from_chars(ini, fim, var) | Texto → número, sem exceções (<charconv>) C++17 | Confira .ec == std::errc() |
std::istringstream / std::ostringstream | Ler de / escrever em uma string com >> / << (<sstream>) | saida.str() |
c - '0' | Um caractere de dígito → número | '7' - '0' → 7 |
Exercícios
1. Para inteiro
Complete para converter o texto s em int:
2. Para decimal
Complete para converter "19.90" num double:
3. De volta para texto
Complete para transformar o número idade em texto:
4. Quanto vale std::stod("2,75"), num programa que não mexe no locale?
5. O que acontece com std::stoi("") (string vazia)?
6. Qual o resultado de std::to_string(2.5)?
Missão final: o caixa da lojinha
Missão: o caixa lê a quantidade e o preço como texto (com getline). O preço vem do jeito brasileiro, 12,50. Converta a quantidade com std::stoi, troque a vírgula do preço por ponto, converta com std::stod e mostre o total. Com a entrada 3 e 12,50, a saída deve ser Total: 37.50 (ou Total: 37.5).
#include <iostream>
#include <string>
int main() {
std::string qtd_texto, preco_texto;
std::getline(std::cin, qtd_texto);
std::getline(std::cin, preco_texto);
// converta, troque a vírgula e calcule o total
}
#include <format>
#include <iostream>
#include <string>
int main() {
std::string qtd_texto, preco_texto;
std::getline(std::cin, qtd_texto);
std::getline(std::cin, preco_texto);
int quantidade = std::stoi(qtd_texto);
std::size_t virgula = preco_texto.find(',');
if (virgula != std::string::npos) {
preco_texto[virgula] = '.';
}
double preco = std::stod(preco_texto);
std::cout << std::format("Total: {:.2f}", quantidade * preco) << '\n';
}
Resumo
- O texto
"42"não é o número 42: com texto,+junta; com número, soma. - Texto → número:
std::stoi(int),std::stol/std::stoll,std::stod(double). Eles pulam espaços do começo e param no primeiro caractere que não é número, sem reclamar. - Texto que não começa com número (ou vazio) →
std::invalid_argument; número que não cabe →std::out_of_range. Semtry/catch, o programa aborta comwhat(): stoi. std::stod("3,14")dá 3: troque a vírgula por ponto antes. O locale pode inverter a regra; não dependa dele.- Número → texto:
std::to_string(doubles com 6 casas) oustd::format. std::from_charsC++17 converte sem exceções, conferindo.ec.std::stringstreamlê e escreve em strings com>>e<<.- CPF, CEP e telefone são texto, não números.