Como analisar uma seqüência de caracteres para um int em C ++?

261

Qual é a maneira C ++ de analisar uma string (fornecida como char *) em um int? A manipulação de erros clara e robusta é uma vantagem (em vez de retornar zero ).

Eugene Yokota
fonte
21
Que tal alguns dos exemplos a seguir: codeproject.com/KB/recipes/Tokenizer.aspx Eles são muito eficientes e um tanto elegantes
@ Beh Tou Cheh, se você acha que é uma boa maneira de analisar int, poste-o como resposta.
precisa
1
O mesmo para C: stackoverflow.com/questions/7021725/…
Ciro Santilli escreveu:

Respostas:

165

No novo C ++ 11, existem funções para isso: stoi, stol, stoll, stoul e assim por diante.

int myNr = std::stoi(myString);

Irá lançar uma exceção no erro de conversão.

Mesmo essas novas funções ainda têm o mesmo problema observado por Dan: elas convertem alegremente a string "11x" para o número inteiro "11".

Veja mais: http://en.cppreference.com/w/cpp/string/basic_string/stol

CC
fonte
4
Mas eles aceitam argumentos do que isso, sendo um deles um ponto para size_t que de, se não nulo, está programado para o primeiro caractere não convertido
Zharf
Sim, usando o segundo parâmetro de std :: stoi, você pode detectar entradas inválidas. Você ainda precisa rolar sua própria função de conversão ...
CC.
Assim como a resposta aceita fez, mas com essas funções padrão que seria muito mais limpo, imo
Zharf
Lembre-se de que o segundo argumento nessas funções pode ser usado para dizer se a cadeia inteira foi convertida ou não. Se o resultado size_tnão for igual ao comprimento da string, ele parou mais cedo. Ainda retornará 11 nesse caso, mas posserá 2 em vez do comprimento da string 3. coliru.stacked-crooked.com/a/cabe25d64d2ffa29
Zoe
204

O que não fazer

Aqui está o meu primeiro conselho: não use stringstream para isso . Embora a princípio pareça simples de usar, você descobrirá que precisa fazer muito trabalho extra se quiser robustez e bom tratamento de erros.

Aqui está uma abordagem que parece intuitivamente que deve funcionar:

bool str2int (int &i, char const *s)
{
    std::stringstream ss(s);
    ss >> i;
    if (ss.fail()) {
        // not an integer
        return false;
    }
    return true;
}

Isso tem um grande problema: str2int(i, "1337h4x0r")retornará feliz truee iobterá o valor 1337. Podemos solucionar esse problema, garantindo que não haja mais caracteres stringstreamapós a conversão:

bool str2int (int &i, char const *s)
{
    char              c;
    std::stringstream ss(s);
    ss >> i;
    if (ss.fail() || ss.get(c)) {
        // not an integer
        return false;
    }
    return true;
}

Corrigimos um problema, mas ainda existem alguns outros problemas.

E se o número na sequência não for a base 10? Podemos tentar acomodar outras bases configurando o fluxo no modo correto (por exemplo ss << std::hex) antes de tentar a conversão. Mas isso significa que o chamador deve saber a priori qual é o número base - e como o chamador pode saber disso? O chamador ainda não sabe qual é o número. Eles nem sabem que éum número! Como se espera que eles saibam qual é a base? Poderíamos apenas exigir que todos os números inseridos em nossos programas sejam da base 10 e rejeitem a entrada hexadecimal ou octal como inválida. Mas isso não é muito flexível ou robusto. Não há uma solução simples para esse problema. Você não pode simplesmente tentar a conversão uma vez para cada base, porque a conversão decimal sempre será bem-sucedida para números octais (com um zero à esquerda) e a conversão octal poderá ser bem-sucedida para alguns números decimais. Então agora você precisa verificar o zero inicial. Mas espere! Os números hexadecimais também podem começar com um zero à esquerda (0x ...). Suspiro.

Mesmo se você conseguir lidar com os problemas acima, ainda há outro problema maior: e se o chamador precisar distinguir entre entrada incorreta (por exemplo, "123foo") e um número que esteja fora do intervalo int(por exemplo, "4000000000" para 32 bits int)? Com stringstream, não há como fazer essa distinção. Só sabemos se a conversão foi bem-sucedida ou falhou. Se falhar, não temos como saber por que falhou. Como você pode ver, stringstreamdeixa muito a desejar se você deseja robustez e tratamento claro de erros.

Isso me leva ao meu segundo conselho: não use o Boost's lexical_castpara isso . Considere o que a lexical_castdocumentação tem a dizer:

Onde um nível mais alto de controle é necessário sobre conversões, std :: stringstream e std :: wstringstream oferecem um caminho mais apropriado. Nos casos em que são necessárias conversões não baseadas em fluxo, lexical_cast é a ferramenta errada para o trabalho e não é especificada para esses cenários.

O que?? Já vimos que stringstreamtem um nível de controle ruim e, no entanto, diz que stringstreamdeve ser usado em vez de lexical_castse você precisar de "um nível de controle mais alto". Além disso, como lexical_casté apenas um invólucro stringstream, ele sofre dos mesmos problemas stringstream: suporte insuficiente para várias bases numéricas e tratamento inadequado de erros.

A melhor solução

Felizmente, alguém já resolveu todos os problemas acima. A biblioteca padrão C contém uma strtolfamília que não apresenta nenhum desses problemas.

enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };

STR2INT_ERROR str2int (int &i, char const *s, int base = 0)
{
    char *end;
    long  l;
    errno = 0;
    l = strtol(s, &end, base);
    if ((errno == ERANGE && l == LONG_MAX) || l > INT_MAX) {
        return OVERFLOW;
    }
    if ((errno == ERANGE && l == LONG_MIN) || l < INT_MIN) {
        return UNDERFLOW;
    }
    if (*s == '\0' || *end != '\0') {
        return INCONVERTIBLE;
    }
    i = l;
    return SUCCESS;
}

Muito simples para algo que lida com todos os casos de erro e também suporta qualquer base numérica de 2 a 36. Se basefor zero (o padrão), ele tentará converter de qualquer base. Ou o chamador pode fornecer o terceiro argumento e especificar que a conversão deve ser tentada apenas para uma base específica. É robusto e lida com todos os erros com um esforço mínimo.

Outras razões para preferir strtol(e família):

  • Apresenta desempenho de tempo de execução muito melhor
  • Introduz menos sobrecarga em tempo de compilação (os outros obtêm quase 20 vezes mais SLOC dos cabeçalhos)
  • Isso resulta no menor tamanho de código

Não há absolutamente nenhuma boa razão para usar qualquer outro método.

Dan Moulding
fonte
22
@ JamesDunne: O POSIX precisa strtolser seguro para threads. O POSIX também requer o errnouso de armazenamento local de encadeamento. Mesmo em sistemas não POSIX, quase todas as implementações de errnosistemas multithread usam armazenamento local de encadeamento. O padrão C ++ mais recente exige errnoque seja compatível com POSIX. O padrão C mais recente também requer errnoarmazenamento local de encadeamento. Mesmo no Windows, que definitivamente não é compatível com POSIX, errnoé seguro para threads e, por extensão, também é strtol.
Dan Moulding
7
Eu realmente não posso seguir o seu raciocínio contra o uso de boost :: lexical_cast. Como se costuma dizer, std :: stringstream realmente oferece muito controle - você faz tudo, desde a verificação de erros até a determinação da base. A documentação atual coloca assim: "Para conversões mais envolvidas, como onde a precisão ou a formatação precisam de um controle mais rígido do que o oferecido pelo comportamento padrão do lexical_cast, recomenda-se a abordagem convencional std :: stringstream."
Fhd 6/05/12
8
Isso é inadequado na codificação C em C ++. A biblioteca padrão contém std::stolpara isso, que lançará adequadamente exceções em vez de retornar constantes.
fuzzyTew
22
@fuzzyTew Eu escrevi essa resposta antes std::stolmesmo foi adicionada à linguagem C ++. Dito isto, não acho justo dizer que isso é "codificação C dentro de C ++". É bobagem dizer que std::strtolé a codificação C quando faz parte explicitamente da linguagem C ++. Minha resposta se aplicava perfeitamente ao C ++ quando ele foi escrito e ainda se aplica mesmo ao novo std::stol. Chamar funções que podem gerar exceções nem sempre é o melhor para todas as situações de programação.
Dan Molding
9
@fuzzyTew: Ficar sem espaço em disco é uma condição excepcional. Arquivos de dados malformados produzidos por computador são uma condição excepcional. Mas erros de digitação na entrada do usuário não são excepcionais. É bom ter uma abordagem de análise capaz de lidar com falhas de análise normais e não excepcionais.
Ben Voigt
67

Esta é uma maneira C mais segura do que atoi ()

const char* str = "123";
int i;

if(sscanf(str, "%d", &i)  == EOF )
{
   /* error */
}

C ++ com biblioteca padrão stringstream : (obrigado CMS )

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  if((ss >> num).fail())
  { 
      //ERROR 
  }
  return num;
}

Com biblioteca de reforço : (obrigado jk )

#include <boost/lexical_cast.hpp>
#include <string>

try
{
    std::string str = "123";
    int number = boost::lexical_cast< int >( str );
}
catch( const boost::bad_lexical_cast & )
{
    // Error
}

Editar: corrigida a versão stringstream para que ele lida com erros. (graças ao comentário de CMS e jk na postagem original)

Luka Marinko
fonte
1
atualize sua versão do stringstream para incluir uma verificação do stringstream :: fail () (conforme solicitado pelo questionador "Tratamento robusto e claro de erros")
jk.
2
Sua versão do stringstream aceitará coisas como "10haha" sem reclamar
Johannes Schaub - litb 13/11/08
3
altere-o para (! (ss >> num) .fail () && (ss >> ws) .eof ()) de ((ss >> num) .fail ()) se você quiser o mesmo tratamento como lexical_cast
Johannes Schaub - litb 13/11
3
O método C ++ com biblioteca padrão stringstream não funciona para seqüências de caracteres como "12-SomeString", mesmo com a verificação .fail ().
12125 captonssj
C ++ 11 inclui funções padrão rápidas para isso agora
fuzzyTew
21

O bom e velho jeito C ainda funciona. Eu recomendo strtol ou strtoul. Entre o status de retorno e o 'endPtr', você pode fornecer uma boa saída de diagnóstico. Ele também lida com várias bases bem.

Chris Arguin
fonte
4
Oh, por favor, não use esse material antigo de C ao programar C ++. Existem maneiras melhores / mais fáceis / mais limpas / mais modernas / mais seguras de fazer isso em C ++!
jk.
27
É engraçado quando as pessoas se preocupam com maneiras "mais modernas" de resolver um problema.
J Miller
@ Jason, IMO forte segurança de tipos e tratamento de erros é a idéia mais moderna comparada com a de C.
Eugene Yokota
6
Eu olhei para as outras respostas e até agora nada é obviamente melhor / mais fácil / mais limpo ou mais seguro. O pôster dizia que ele tinha um caractere *. Isso limita a quantidade de segurança que você está indo para obter :)
Chris Arguin
16

Você pode usar o a string do libraray padrão C ++:

stringstream ss(str);
int x;
ss >> x;

if(ss) { // <-- error handling
  // use x
} else {
  // not a number
}

O estado do fluxo será configurado para falhar se um não dígito for encontrado ao tentar ler um número inteiro.

Consulte Armadilhas de fluxo para armadilhas de tratamento de erros e fluxos em C ++.

jk.
fonte
2
O método stringstream C ++ não funciona para strings como "12-SomeString", mesmo com a verificação 'stream state'.
12125 captonssj
10

Você pode usar o stringstream

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  ss >> num;
  return num;
}
CMS
fonte
4
Mas isso não lida com nenhum erro. Você precisa verificar se há falhas no fluxo.
jk.
1
Certo, você deve verificar o fluxo se ((ss >> num) .fail ()) {// ERRO}
CMS
2
A ++ método stringstream C não funciona para cordas, como "12-SomeString" mesmo com o 'estado stream' cheque
captonssj
8

Eu acho que esses três links resumem:

As soluções stringstream e lexical_cast são praticamente as mesmas que o elenco lexical está usando o stringstream.

Algumas especializações de elenco lexical usam abordagens diferentes, consulte http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp para obter detalhes. Agora, números inteiros e flutuantes são especializados para conversão de número inteiro em cadeia.

Pode-se especializar o lexical_cast para suas próprias necessidades e torná-lo rápido. Esta seria a solução definitiva para todas as partes, limpa e simples.

Os artigos já mencionados mostram comparação entre os diferentes métodos de conversão de cadeias inteiras <->. As abordagens a seguir fazem sentido: c-way antigo, spirit.karma, fastformat, loop simples e ingênuo.

Lexical_cast está bom em alguns casos, por exemplo, na conversão de int para string.

A conversão de string para int usando conversão lexical não é uma boa ideia, pois é 10-40 vezes mais lenta que atoi, dependendo da plataforma / compilador usada.

O Boost.Spirit.Karma parece ser a biblioteca mais rápida para converter números inteiros em string.

ex.: generate(ptr_char, int_, integer_number);

e o loop simples básico do artigo mencionado acima é a maneira mais rápida de converter uma string para int, obviamente não a mais segura, strtol () parece uma solução mais segura

int naive_char_2_int(const char *p) {
    int x = 0;
    bool neg = false;
    if (*p == '-') {
        neg = true;
        ++p;
    }
    while (*p >= '0' && *p <= '9') {
        x = (x*10) + (*p - '0');
        ++p;
    }
    if (neg) {
        x = -x;
    }
    return x;
}
caa
fonte
7

A Biblioteca de C ++ String Toolkit (StrTk) tem a seguinte solução:

static const std::size_t digit_table_symbol_count = 256;
static const unsigned char digit_table[digit_table_symbol_count] = {
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xFF - 0x07
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x08 - 0x0F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x10 - 0x17
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x18 - 0x1F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x20 - 0x27
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x28 - 0x2F
   0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 0x30 - 0x37
   0x08, 0x09, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x38 - 0x3F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x40 - 0x47
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x48 - 0x4F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x50 - 0x57
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x58 - 0x5F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x60 - 0x67
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x68 - 0x6F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x70 - 0x77
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x78 - 0x7F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x80 - 0x87
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x88 - 0x8F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x90 - 0x97
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0x98 - 0x9F
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA0 - 0xA7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xA8 - 0xAF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB0 - 0xB7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xB8 - 0xBF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC0 - 0xC7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xC8 - 0xCF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD0 - 0xD7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xD8 - 0xDF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE0 - 0xE7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xE8 - 0xEF
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 0xF0 - 0xF7
   0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF  // 0xF8 - 0xFF
 };

template<typename InputIterator, typename T>
inline bool string_to_signed_type_converter_impl_itr(InputIterator begin, InputIterator end, T& v)
{
   if (0 == std::distance(begin,end))
      return false;
   v = 0;
   InputIterator it = begin;
   bool negative = false;
   if ('+' == *it)
      ++it;
   else if ('-' == *it)
   {
      ++it;
      negative = true;
   }
   if (end == it)
      return false;
   while(end != it)
   {
      const T digit = static_cast<T>(digit_table[static_cast<unsigned int>(*it++)]);
      if (0xFF == digit)
         return false;
      v = (10 * v) + digit;
   }
   if (negative)
      v *= -1;
   return true;
}

O InputIterator pode ser de iteradores char *, char * ou std :: string não assinados, e espera-se que T seja um int assinado, como int, int ou long

Rup
fonte
1
AVISO Esta implementação parece boa, mas não lida com estouros até onde eu sei.
Vinnie Falco
2
O código não manipula o estouro. v = (10 * v) + digit;transborda desnecessariamente com a entrada de string com o valor de texto de INT_MIN. Tabela é de valor questionável vs simplesmentedigit >= '0' && digit <= '9'
chux - Reinstate Monica
6

Se você tiver C ++ 11, as soluções adequadas nos dias de hoje são o C ++ integer funções de conversão em <string>: stoi, stol, stoul, stoll, stoull. Eles lançam exceções apropriadas quando recebem informações incorretas e usam os métodos rápido e pequenostrto* funções sob o capô.

Se você está preso a uma revisão anterior do C ++, seria portátil da sua parte imitar essas funções em sua implementação.

fuzzyTew
fonte
6

A partir do C ++ 17, você pode usar std::from_charso <charconv>cabeçalho conforme documentado aqui .

Por exemplo:

#include <iostream>
#include <charconv>
#include <array>

int main()
{
    char const * str = "42";
    int value = 0;

    std::from_chars_result result = std::from_chars(std::begin(str), std::end(str), value);

    if(result.error == std::errc::invalid_argument)
    {
      std::cout << "Error, invalid format";
    }
    else if(result.error == std::errc::result_out_of_range)
    {
      std::cout << "Error, value too big for int range";
    }
    else
    {
      std::cout << "Success: " << result;
    }
}

Como bônus, ele também pode lidar com outras bases, como hexadecimal.

Pharap
fonte
3

Eu gosto da resposta de Dan Moulding , vou adicionar um pouco de estilo C ++ a ela:

#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>

int to_int(const std::string &s, int base = 0)
{
    char *end;
    errno = 0;
    long result = std::strtol(s.c_str(), &end, base);
    if (errno == ERANGE || result > INT_MAX || result < INT_MIN)
        throw std::out_of_range("toint: string is out of range");
    if (s.length() == 0 || *end != '\0')
        throw std::invalid_argument("toint: invalid string");
    return result;
}

Ele funciona para std :: string e const char * através da conversão implícita. Também é útil para conversão de base, por exemplo, all to_int("0x7b")e to_int("0173")and to_int("01111011", 2)and to_int("0000007B", 16)and to_int("11120", 3)andto_int("3L", 34); retornaria 123.

Ao contrário std::stoi, funciona no pré-C ++ 11. Também diferente std::stoi, boost::lexical_castestringstream lança exceções para strings estranhos como "123hohoho".

Nota: Esta função tolera espaços à esquerda, mas não espaços à direita, ou seja, to_int(" 123")retorna 123 enquanto to_int("123 ")lança exceção. Verifique se isso é aceitável para o seu caso de uso ou ajuste o código.

Essa função pode fazer parte do STL ...

user3925906
fonte
2

Conheço três maneiras de converter String em int:

Use a função stoi (String para int) ou apenas siga a Stringstream, a terceira maneira de conversão individual; o código está abaixo:

1º método

std::string s1 = "4533";
std::string s2 = "3.010101";
std::string s3 = "31337 with some string";

int myint1 = std::stoi(s1);
int myint2 = std::stoi(s2);
int myint3 = std::stoi(s3);

std::cout <<  s1 <<"=" << myint1 << '\n';
std::cout <<  s2 <<"=" << myint2 << '\n';
std::cout <<  s3 <<"=" << myint3 << '\n';

2º Método

#include <string.h>
#include <sstream>
#include <iostream>
#include <cstring>
using namespace std;


int StringToInteger(string NumberAsString)
{
    int NumberAsInteger;
    stringstream ss;
    ss << NumberAsString;
    ss >> NumberAsInteger;
    return NumberAsInteger;
}
int main()
{
    string NumberAsString;
    cin >> NumberAsString;
    cout << StringToInteger(NumberAsString) << endl;
    return 0;
} 

Terceiro método - mas não para uma conversão individual

std::string str4 = "453";
int i = 0, in=0; // 453 as on
for ( i = 0; i < str4.length(); i++)
{

    in = str4[i];
    cout <<in-48 ;

}
Iqra.
fonte
1

Eu gosto da resposta do Dan , especialmente por evitar exceções. Para o desenvolvimento de sistemas embarcados e outro desenvolvimento de sistema de baixo nível, pode não haver uma estrutura de exceção adequada disponível.

Adicionada uma verificação de espaço em branco após uma sequência válida ... essas três linhas

    while (isspace(*end)) {
        end++;
    }


Também foi adicionada uma verificação de erros de análise.

    if ((errno != 0) || (s == end)) {
        return INCONVERTIBLE;
    }


Aqui está a função completa ..

#include <cstdlib>
#include <cerrno>
#include <climits>
#include <stdexcept>

enum STR2INT_ERROR { SUCCESS, OVERFLOW, UNDERFLOW, INCONVERTIBLE };

STR2INT_ERROR str2long (long &l, char const *s, int base = 0)
{
    char *end = (char *)s;
    errno = 0;

    l = strtol(s, &end, base);

    if ((errno == ERANGE) && (l == LONG_MAX)) {
        return OVERFLOW;
    }
    if ((errno == ERANGE) && (l == LONG_MIN)) {
        return UNDERFLOW;
    }
    if ((errno != 0) || (s == end)) {
        return INCONVERTIBLE;
    }    
    while (isspace((unsigned char)*end)) {
        end++;
    }

    if (*s == '\0' || *end != '\0') {
        return INCONVERTIBLE;
    }

    return SUCCESS;
}
pelucida
fonte
O @chux adicionou código para cuidar das preocupações que você mencionou.
pellucide
1) Ainda não conseguiu detectar erros com entradas como " ". strtol()não está especificado para definir errnoquando nenhuma conversão ocorre. Melhor usar if (s == end) return INCONVERTIBLE; para detectar nenhuma conversão. E então if (*s == '\0' || *end != '\0')pode simplificar para if (*end)2) || l > LONG_MAXe || l < LONG_MINnão serve para nada - eles nunca são verdadeiros.
chux - Restabelece Monica 31/08
@chux Em um mac, o errno está definido para analisar erros, mas no linux o errno não está definido. Código alterado para depender do ponteiro "end" para detectar isso.
pellucide
0

Você poderia usar este método definido.

#define toInt(x) {atoi(x.c_str())};

E se você converter de String para Inteiro, faça o seguinte.

int main()
{
string test = "46", test2 = "56";
int a = toInt(test);
int b = toInt(test2);
cout<<a+b<<endl;
}

A saída seria 102.

Boris
fonte
4
Sei lá. Escrever uma macro de definição atoinão parece "a maneira C ++", à luz de outras respostas como a aceita std::stoi().
Eugene Yokota
Acho mais divertido usar métodos predefinidos: P
Boris
0

Sei que essa é uma pergunta mais antiga, mas já a encontrei muitas vezes e, até o momento, ainda não encontrei uma solução bem modelada com as seguintes características:

  • Pode converter qualquer base (e detectar o tipo de base)
  • Detectará dados incorretos (ou seja, garantirá que toda a cadeia, menos espaço em branco inicial / final, seja consumida pela conversão)
  • Garantirá que, independentemente do tipo convertido, o intervalo do valor da sequência seja aceitável.

Então, aqui está o meu, com uma tira de teste. Como ele usa as funções C strtoull / strtoll sob o capô, ele sempre converte primeiro no maior tipo disponível. Então, se você não estiver usando o tipo maior, ele executará verificações de intervalo adicionais para verificar se o seu tipo não excedeu (sub) o fluxo. Para isso, é um pouco menos eficiente do que se alguém escolhesse strtol / strtoul corretamente. No entanto, ele também funciona para shorts / chars e, pelo que sei, não existe uma função de biblioteca padrão que faça isso também.

Aproveitar; espero que alguém ache útil.

#include <cstdlib>
#include <cerrno>
#include <limits>
#include <stdexcept>
#include <sstream>

static const int DefaultBase = 10;

template<typename T>
static inline T CstrtoxllWrapper(const char *str, int base = DefaultBase)
{
    while (isspace(*str)) str++; // remove leading spaces; verify there's data
    if (*str == '\0') { throw std::invalid_argument("str; no data"); } // nothing to convert

    // NOTE:  for some reason strtoull allows a negative sign, we don't; if
    //          converting to an unsigned then it must always be positive!
    if (!std::numeric_limits<T>::is_signed && *str == '-')
    { throw std::invalid_argument("str; negative"); }

    // reset errno and call fn (either strtoll or strtoull)
    errno = 0;
    char *ePtr;
    T tmp = std::numeric_limits<T>::is_signed ? strtoll(str, &ePtr, base)
                                              : strtoull(str, &ePtr, base);

    // check for any C errors -- note these are range errors on T, which may
    //   still be out of the range of the actual type we're using; the caller
    //   may need to perform additional range checks.
    if (errno != 0) 
    {
            if (errno == ERANGE) { throw std::range_error("str; out of range"); }
            else if (errno == EINVAL) { throw std::invalid_argument("str; EINVAL"); }
            else { throw std::invalid_argument("str; unknown errno"); }
    }

    // verify everything converted -- extraneous spaces are allowed
    if (ePtr != NULL)
    {
            while (isspace(*ePtr)) ePtr++;
            if (*ePtr != '\0') { throw std::invalid_argument("str; bad data"); }
    }

    return tmp;
}

template<typename T>
T StringToSigned(const char *str, int base = DefaultBase)
{
    static const long long max = std::numeric_limits<T>::max();
    static const long long min = std::numeric_limits<T>::min();

    long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type

    // final range check -- only needed if not long long type; a smart compiler
    //   should optimize this whole thing out
    if (sizeof(T) == sizeof(tmp)) { return tmp; }

    if (tmp < min || tmp > max)
    {
            std::ostringstream err;
            err << "str; value " << tmp << " out of " << sizeof(T) * 8
                << "-bit signed range (";
            if (sizeof(T) != 1) err << min << ".." << max;
            else err << (int) min << ".." << (int) max;  // don't print garbage chars
            err << ")";
            throw std::range_error(err.str());
    }

    return tmp;
}

template<typename T>
T StringToUnsigned(const char *str, int base = DefaultBase)
{
    static const unsigned long long max = std::numeric_limits<T>::max();

    unsigned long long tmp = CstrtoxllWrapper<typeof(tmp)>(str, base); // use largest type

    // final range check -- only needed if not long long type; a smart compiler
    //   should optimize this whole thing out
    if (sizeof(T) == sizeof(tmp)) { return tmp; }

    if (tmp > max)
    {
            std::ostringstream err;
            err << "str; value " << tmp << " out of " << sizeof(T) * 8
                << "-bit unsigned range (0..";
            if (sizeof(T) != 1) err << max;
            else err << (int) max;  // don't print garbage chars
            err << ")";
            throw std::range_error(err.str());
    }

    return tmp;
}

template<typename T>
inline T
StringToDecimal(const char *str, int base = DefaultBase)
{
    return std::numeric_limits<T>::is_signed ? StringToSigned<T>(str, base)
                                             : StringToUnsigned<T>(str, base);
}

template<typename T>
inline T
StringToDecimal(T &out_convertedVal, const char *str, int base = DefaultBase)
{
    return out_convertedVal = StringToDecimal<T>(str, base);
}

/*============================== [ Test Strap ] ==============================*/ 

#include <inttypes.h>
#include <iostream>

static bool _g_anyFailed = false;

template<typename T>
void TestIt(const char *tName,
            const char *s, int base,
            bool successExpected = false, T expectedValue = 0)
{
    #define FAIL(s) { _g_anyFailed = true; std::cout << s; }

    T x;
    std::cout << "converting<" << tName << ">b:" << base << " [" << s << "]";
    try
    {
            StringToDecimal<T>(x, s, base);
            // get here on success only
            if (!successExpected)
            {
                    FAIL(" -- TEST FAILED; SUCCESS NOT EXPECTED!" << std::endl);
            }
            else
            {
                    std::cout << " -> ";
                    if (sizeof(T) != 1) std::cout << x;
                    else std::cout << (int) x;  // don't print garbage chars
                    if (x != expectedValue)
                    {
                            FAIL("; FAILED (expected value:" << expectedValue << ")!");
                    }
                    std::cout << std::endl;
            }
    }
    catch (std::exception &e)
    {
            if (successExpected)
            {
                    FAIL(   " -- TEST FAILED; EXPECTED SUCCESS!"
                         << " (got:" << e.what() << ")" << std::endl);
            }
            else
            {
                    std::cout << "; expected exception encounterd: [" << e.what() << "]" << std::endl;
            }
    }
}

#define TEST(t, s, ...) \
    TestIt<t>(#t, s, __VA_ARGS__);

int main()
{
    std::cout << "============ variable base tests ============" << std::endl;
    TEST(int, "-0xF", 0, true, -0xF);
    TEST(int, "+0xF", 0, true, 0xF);
    TEST(int, "0xF", 0, true, 0xF);
    TEST(int, "-010", 0, true, -010);
    TEST(int, "+010", 0, true, 010);
    TEST(int, "010", 0, true, 010);
    TEST(int, "-10", 0, true, -10);
    TEST(int, "+10", 0, true, 10);
    TEST(int, "10", 0, true, 10);

    std::cout << "============ base-10 tests ============" << std::endl;
    TEST(int, "-010", 10, true, -10);
    TEST(int, "+010", 10, true, 10);
    TEST(int, "010", 10, true, 10);
    TEST(int, "-10", 10, true, -10);
    TEST(int, "+10", 10, true, 10);
    TEST(int, "10", 10, true, 10);
    TEST(int, "00010", 10, true, 10);

    std::cout << "============ base-8 tests ============" << std::endl;
    TEST(int, "777", 8, true, 0777);
    TEST(int, "-0111 ", 8, true, -0111);
    TEST(int, "+0010 ", 8, true, 010);

    std::cout << "============ base-16 tests ============" << std::endl;
    TEST(int, "DEAD", 16, true, 0xDEAD);
    TEST(int, "-BEEF", 16, true, -0xBEEF);
    TEST(int, "+C30", 16, true, 0xC30);

    std::cout << "============ base-2 tests ============" << std::endl;
    TEST(int, "-10011001", 2, true, -153);
    TEST(int, "10011001", 2, true, 153);

    std::cout << "============ irregular base tests ============" << std::endl;
    TEST(int, "Z", 36, true, 35);
    TEST(int, "ZZTOP", 36, true, 60457993);
    TEST(int, "G", 17, true, 16);
    TEST(int, "H", 17);

    std::cout << "============ space deliminated tests ============" << std::endl;
    TEST(int, "1337    ", 10, true, 1337);
    TEST(int, "   FEAD", 16, true, 0xFEAD);
    TEST(int, "   0711   ", 0, true, 0711);

    std::cout << "============ bad data tests ============" << std::endl;
    TEST(int, "FEAD", 10);
    TEST(int, "1234 asdfklj", 10);
    TEST(int, "-0xF", 10);
    TEST(int, "+0xF", 10);
    TEST(int, "0xF", 10);
    TEST(int, "-F", 10);
    TEST(int, "+F", 10);
    TEST(int, "12.4", 10);
    TEST(int, "ABG", 16);
    TEST(int, "10011002", 2);

    std::cout << "============ int8_t range tests ============" << std::endl;
    TEST(int8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
    TEST(int8_t, "80", 16);
    TEST(int8_t, "-80", 16, true, std::numeric_limits<int8_t>::min());
    TEST(int8_t, "-81", 16);
    TEST(int8_t, "FF", 16);
    TEST(int8_t, "100", 16);

    std::cout << "============ uint8_t range tests ============" << std::endl;
    TEST(uint8_t, "7F", 16, true, std::numeric_limits<int8_t>::max());
    TEST(uint8_t, "80", 16, true, std::numeric_limits<int8_t>::max()+1);
    TEST(uint8_t, "-80", 16);
    TEST(uint8_t, "-81", 16);
    TEST(uint8_t, "FF", 16, true, std::numeric_limits<uint8_t>::max());
    TEST(uint8_t, "100", 16);

    std::cout << "============ int16_t range tests ============" << std::endl;
    TEST(int16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
    TEST(int16_t, "8000", 16);
    TEST(int16_t, "-8000", 16, true, std::numeric_limits<int16_t>::min());
    TEST(int16_t, "-8001", 16);
    TEST(int16_t, "FFFF", 16);
    TEST(int16_t, "10000", 16);

    std::cout << "============ uint16_t range tests ============" << std::endl;
    TEST(uint16_t, "7FFF", 16, true, std::numeric_limits<int16_t>::max());
    TEST(uint16_t, "8000", 16, true, std::numeric_limits<int16_t>::max()+1);
    TEST(uint16_t, "-8000", 16);
    TEST(uint16_t, "-8001", 16);
    TEST(uint16_t, "FFFF", 16, true, std::numeric_limits<uint16_t>::max());
    TEST(uint16_t, "10000", 16);

    std::cout << "============ int32_t range tests ============" << std::endl;
    TEST(int32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
    TEST(int32_t, "80000000", 16);
    TEST(int32_t, "-80000000", 16, true, std::numeric_limits<int32_t>::min());
    TEST(int32_t, "-80000001", 16);
    TEST(int32_t, "FFFFFFFF", 16);
    TEST(int32_t, "100000000", 16);

    std::cout << "============ uint32_t range tests ============" << std::endl;
    TEST(uint32_t, "7FFFFFFF", 16, true, std::numeric_limits<int32_t>::max());
    TEST(uint32_t, "80000000", 16, true, std::numeric_limits<int32_t>::max()+1);
    TEST(uint32_t, "-80000000", 16);
    TEST(uint32_t, "-80000001", 16);
    TEST(uint32_t, "FFFFFFFF", 16, true, std::numeric_limits<uint32_t>::max());
    TEST(uint32_t, "100000000", 16);

    std::cout << "============ int64_t range tests ============" << std::endl;
    TEST(int64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
    TEST(int64_t, "8000000000000000", 16);
    TEST(int64_t, "-8000000000000000", 16, true, std::numeric_limits<int64_t>::min());
    TEST(int64_t, "-8000000000000001", 16);
    TEST(int64_t, "FFFFFFFFFFFFFFFF", 16);
    TEST(int64_t, "10000000000000000", 16);

    std::cout << "============ uint64_t range tests ============" << std::endl;
    TEST(uint64_t, "7FFFFFFFFFFFFFFF", 16, true, std::numeric_limits<int64_t>::max());
    TEST(uint64_t, "8000000000000000", 16, true, std::numeric_limits<int64_t>::max()+1);
    TEST(uint64_t, "-8000000000000000", 16);
    TEST(uint64_t, "-8000000000000001", 16);
    TEST(uint64_t, "FFFFFFFFFFFFFFFF", 16, true, std::numeric_limits<uint64_t>::max());
    TEST(uint64_t, "10000000000000000", 16);

    std::cout << std::endl << std::endl
              << (_g_anyFailed ? "!! SOME TESTS FAILED !!" : "ALL TESTS PASSED")
              << std::endl;

    return _g_anyFailed;
}

StringToDecimalé o método terra do usuário; está sobrecarregado para que possa ser chamado assim:

int a; a = StringToDecimal<int>("100");

ou isto:

int a; StringToDecimal(a, "100");

Eu odeio repetir o tipo int, então prefiro o último. Isso garante que, se o tipo de 'a' for alterado, não haverá resultados ruins. Eu gostaria que o compilador pudesse descobrir como:

int a; a = StringToDecimal("100");

... mas o C ++ não deduz os tipos de retorno de modelo, então é o melhor que posso obter.

A implementação é bem simples:

CstrtoxllWrapperenvolve ambos strtoulle strtoll, chamando o que for necessário, com base na assinatura do tipo de modelo e fornecendo algumas garantias adicionais (por exemplo, entrada negativa não será permitida se não estiver assinada e garante que toda a cadeia seja convertida).

CstrtoxllWrapperé usado por StringToSignede StringToUnsignedcom o maior tipo (long long / unsigned long long) disponível para o compilador; isso permite que a conversão máxima seja realizada. Então, se for necessário, StringToSigned/ StringToUnsignedexecuta as verificações finais do intervalo no tipo subjacente. Finalmente, o método do ponto final,StringToDecimal , decide qual dos métodos de modelo StringTo * chamar com base na assinatura do tipo subjacente.

Eu acho que a maior parte do lixo pode ser otimizada pelo compilador; praticamente tudo deve ser determinístico em tempo de compilação. Qualquer comentário sobre esse aspecto seria interessante para mim!

DreamWarrior
fonte
"use o maior tipo" -> por que ao long longinvés de intmax_t?
chux - Reinstala Monica 29/08
Confiante que você quer if (ePtr != str). Além disso, use isspace((unsigned char) *ePtr)para manipular adequadamente valores negativos de *ePtr.
chux - Restabelece Monica
-3

Em C, você pode usar int atoi (const char * str),

Analisa a cadeia de caracteres C interpretando seu conteúdo como um número inteiro, retornado como um valor do tipo int.

Mamba negra
fonte
2
Como eu vinculei atoina pergunta, eu estou ciente disso. A questão claramente não é sobre C, mas sobre C ++. -1
Eugene Yokota