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.

Você vai aprender
  • Converter texto em número com std::stoi, std::stol e std::stod.
  • O que acontece com "42abc", "3.99" e espaços, e as mensagens std::invalid_argument: stoi e std::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 preferir std::format).
  • Converter sem exceções com std::from_chars C++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çãoLê-seDevolveExemplo
std::stoi(s)string to intint (até uns 2 bilhões)std::stoi("42") → 42
std::stol(s)string to longlongstd::stol("3000000000")
std::stoll(s)string to long longlong longNúmeros enormes
std::stod(s)string to doubledouble (com casas decimais)std::stod("19.90") → 19.9
std::stof(s)string to floatfloatRaramente usado
int numero = std::stoi(texto);
Lê o texto "42" e devolve o número 42, que é guardado num int. O texto continua 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, o std::cout tira o zero do fim: 39.8.
std::stol("3000000000")
Três bilhões não cabem num int; num long, 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 42abc por 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 stoi escreve em usados quantos caracteres conseguiu ler (o & entrega o "endereço" da variável para a função escrever nela; veja Ponteiros). Se usados for menor que o tamanho do texto, sobrou lixo: dá para recusar a entrada com if (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 << n nunca 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
Curiosidade: o "locale" muda o separador

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 com substr…
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::format C++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_chars não recebe o std::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_chars devolve dois dados juntos; o auto deixa 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 o abc que 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 o std::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.

Um dígito só: 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çãoUse
Texto → número, no dia a diastd::stoi / std::stod (com try/catch se o texto vem do usuário)
Texto → número, sem exceções ou com muitos númerosstd::from_chars C++17
Vários números numa linhastd::istringstream com >>
Número → texto, simplesstd::to_string
Número → texto, controlando casas e formatostd::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.

O preço perdeu os centavos (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).

O 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

EscritaO que fazExemplo → resultado
std::stoi(s)Texto → intstd::stoi("42") → 42
std::stol(s) / std::stoll(s)Texto → long / long longstd::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_argumentExceção: não é númerostd::stoi("abc"), std::stoi("")
std::out_of_rangeExceção: não cabe no tipostd::stoi("12345678901")
std::to_string(n)Número → textostd::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++17Confira .ec == std::errc()
std::istringstream / std::ostringstreamLer 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:

int n = std::(s);

2. Para decimal

Complete para converter "19.90" num double:

double preco = std::("19.90");

3. De volta para texto

Complete para transformar o número idade em texto:

std::string t = std::(idade);

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. Sem try/catch, o programa aborta com what(): 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) ou std::format.
  • std::from_chars C++17 converte sem exceções, conferindo .ec. std::stringstream lê e escreve em strings com >> e <<.
  • CPF, CEP e telefone são texto, não números.