¿Cómo analizar una cadena a un int en C ++?


261

¿Cuál es la forma en C ++ de analizar una cadena (dada como char *) en un int? El manejo de errores robusto y claro es un plus (en lugar de devolver cero )


21
¿Qué tal algunos ejemplos de los siguientes: codeproject.com/KB/recipes/Tokenizer.aspx Son muy eficientes y algo elegantes

@Beh Tou Cheh, si crees que es una buena manera de analizar int, publícalo como respuesta.
Eugene Yokota el

Respuestas:


165

En el nuevo C ++ 11 hay funciones para eso: stoi, stol, stoll, stoul, etc.

int myNr = std::stoi(myString);

Lanzará una excepción en el error de conversión.

Incluso estas nuevas funciones todavía tienen el mismo problema que señaló Dan: felizmente convertirán la cadena "11x" al número entero "11".

Ver más: http://en.cppreference.com/w/cpp/string/basic_string/stol


44
Pero aceptan argumentos más que eso, uno de ellos es un punto de tamaño: eso, si no es nulo, se establece en el primer personaje no convertido
Zharf el

Sí, utilizando el segundo parámetro de std :: stoi puede detectar entradas no válidas. Sin embargo, todavía tiene que rodar su propia función de conversión ...
CC.

Justo como lo hizo la respuesta aceptada, pero con estas funciones estándar que serían mucho más limpias, imo
Zharf

Tenga en cuenta que el segundo argumento en estas funciones se puede usar para determinar si la cadena completa se convirtió o no. Si el resultado size_tno es igual a la longitud de la cadena, entonces se detuvo antes. Todavía devolverá 11 en ese caso, pero posserá 2 en lugar de la longitud de la cadena 3. coliru.stacked-crooked.com/a/cabe25d64d2ffa29
Zoe

204

Qué no hacer

Aquí está mi primer consejo: no use stringstream para esto . Si bien al principio puede parecer simple de usar, encontrará que debe hacer mucho trabajo adicional si desea robustez y buen manejo de errores.

Aquí hay un enfoque que intuitivamente parece que debería funcionar:

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

Esto tiene un problema importante: str2int(i, "1337h4x0r")felizmente regresará truey iobtendrá el valor 1337. Podemos solucionar este problema asegurándonos de que no haya más caracteres stringstreamdespués de la conversión:

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;
}

Solucionamos un problema, pero todavía hay otros problemas.

¿Qué pasa si el número en la cadena no es base 10? Podemos intentar acomodar otras bases configurando la transmisión en el modo correcto (por ejemplo ss << std::hex) antes de intentar la conversión. Pero esto significa que la persona que llama debe saber a priori qué base es el número, y ¿cómo puede saberlo? La persona que llama aún no sabe cuál es el número. Ni siquiera saben que es¡un número! ¿Cómo se puede esperar que sepan qué base es? Podríamos exigir que todos los números ingresados ​​a nuestros programas deben ser de base 10 y rechazar el ingreso hexadecimal u octal como no válido. Pero eso no es muy flexible ni robusto. No hay una solución simple para este problema. No puede simplemente intentar la conversión una vez para cada base, porque la conversión decimal siempre tendrá éxito para los números octales (con un cero inicial) y la conversión octal puede tener éxito para algunos números decimales. Así que ahora tienes que buscar un cero a la izquierda. ¡Pero espera! Los números hexadecimales también pueden comenzar con un cero inicial (0x ...). Suspiro.

Incluso si logra resolver los problemas anteriores, todavía hay otro problema mayor: ¿qué sucede si la persona que llama necesita distinguir entre una entrada incorrecta (por ejemplo, "123foo") y un número que está fuera del rango de int(por ejemplo, "4000000000" para 32 bits int)? Con stringstream, no hay forma de hacer esta distinción. Solo sabemos si la conversión tuvo éxito o falló. Si falla, no tenemos forma de saber por qué falló. Como puede ver, stringstreamdeja mucho que desear si desea robustez y manejo claro de errores.

Esto me lleva a mi segundo consejo: no use Boost lexical_castpara esto . Considere lo que la lexical_castdocumentación tiene que decir:

Cuando se requiere un mayor grado de control sobre las conversiones, std :: stringstream y std :: wstringstream ofrecen una ruta más apropiada. Cuando se requieren conversiones no basadas en secuencias, lexical_cast es la herramienta incorrecta para el trabajo y no está especialmente diseñada para tales escenarios.

¿¿Qué?? Ya hemos visto que stringstreamtiene un bajo nivel de control y, sin embargo, dice que stringstreamdebe usarse en lugar de lexical_castsi necesita "un mayor nivel de control". Además, debido a que lexical_castes solo una envoltura stringstream, sufre los mismos problemas que stringstreamtiene: pobre soporte para múltiples bases de números y pobre manejo de errores.

La mejor solucion

Afortunadamente, alguien ya ha resuelto todos los problemas anteriores. La biblioteca estándar de C contiene una strtolfamilia que no tiene ninguno de estos 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;
}

Bastante simple para algo que maneja todos los casos de error y también admite cualquier base de números del 2 al 36. Si basees cero (el valor predeterminado) intentará convertir desde cualquier base. O la persona que llama puede proporcionar el tercer argumento y especificar que la conversión solo debe intentarse para una base particular. Es robusto y maneja todos los errores con un mínimo esfuerzo.

Otras razones para preferir strtol(y familia):

  • Exhibe mucho mejor rendimiento en tiempo de ejecución
  • Introduce menos sobrecarga en tiempo de compilación (los otros obtienen casi 20 veces más SLOC de los encabezados)
  • Resulta en el tamaño de código más pequeño

No hay absolutamente ninguna buena razón para usar ningún otro método.


22
@JamesDunne: POSIX requiere strtolser seguro para subprocesos. POSIX también requiere errnousar almacenamiento local de subprocesos. Incluso en sistemas que no son POSIX, casi todas las implementaciones de errnosistemas multiproceso utilizan almacenamiento local de subprocesos. El último estándar C ++ requiere errnoque sea compatible con POSIX. El último estándar C también requiere errnotener almacenamiento local de subprocesos. Incluso en Windows, que definitivamente no es compatible con POSIX, errnoes seguro para subprocesos y, por extensión, también lo es strtol.
Dan Molding el

77
Realmente no puedo seguir tu razonamiento contra el uso de boost :: lexical_cast. Como dicen, std :: stringstream ofrece mucho control: usted hace todo, desde la verificación de errores hasta determinar la base usted mismo. La documentación actual lo dice así: "Para las conversiones más complicadas, como cuando la precisión o el formato necesitan un control más estricto que el que ofrece el comportamiento predeterminado de lexical_cast, se recomienda el enfoque convencional std :: stringstream".
fhd

8
Esta es una codificación C inapropiada dentro de C ++. La biblioteca estándar contiene std::stolpara esto, que arrojará excepciones en lugar de devolver constantes.
fuzzyTew

22
@fuzzyTew Escribí esta respuesta std::stolincluso antes de agregarla al lenguaje C ++. Dicho esto, no creo que sea justo decir que esto es "codificación C dentro de C ++". Es tonto decir que std::strtoles codificación C cuando es explícitamente parte del lenguaje C ++. Mi respuesta se aplicó perfectamente a C ++ cuando se escribió y aún se aplica incluso con la nueva std::stol. Llamar a funciones que pueden generar excepciones no siempre es lo mejor para cada situación de programación.
Dan Moulding

9
@fuzzyTew: quedarse sin espacio en disco es una condición excepcional. Los archivos de datos mal formateados producidos por computadora son una condición excepcional. Pero los errores tipográficos en la entrada del usuario no son excepcionales. Es bueno tener un enfoque de análisis que sea capaz de manejar fallas de análisis normales y no excepcionales.
Ben Voigt

67

Esta es una forma C más segura que atoi ()

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

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

C ++ con secuencia de cadena de biblioteca estándar : (gracias CMS )

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

Con la biblioteca de impulso : (gracias 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: se corrigió la versión de secuencia de cadena para que maneje los errores. (gracias al comentario de CMS y jk en la publicación original)


1
actualizó su versión de secuencia de cadena para incluir una verificación de secuencia de secuencia :: fail () (según lo solicitado por el interrogador "Manejo de errores claro y robusto")
jk.

2
Su versión de cadena aceptará cosas como "10haha" sin quejarse
Johannes Schaub - litb

3
cámbielo a (! (ss >> num) .fail () && (ss >> ws) .eof ()) desde ((ss >> num) .fail ()) si desea el mismo manejo como lexical_cast
Johannes Schaub - litb

3
El C ++ con el método de secuencia de cadena de biblioteca estándar no funciona para cadenas como "12-SomeString" incluso con la comprobación .fail ().
captonssj

C ++ 11 incluye funciones rápidas estándar para esto ahora
fuzzyTew

21

La buena y antigua forma de C todavía funciona. Recomiendo strtol o strtoul. Entre el estado de retorno y el 'endPtr', puede dar una buena salida de diagnóstico. También maneja múltiples bases muy bien.


44
Oh, por favor, no use estas viejas cosas de C cuando programe C ++. ¡Hay formas mejores / más fáciles / más limpias / más modernas / más seguras de hacer esto en C ++!
jk.

27
Es divertido cuando la gente está preocupada por las formas "más modernas" de resolver un problema.
J Miller

@ Jason, la seguridad de tipo IMO más fuerte y el manejo de errores es una idea más moderna en comparación con la de C.
Eugene Yokota

66
He visto las otras respuestas, y hasta ahora nada es obviamente mejor / más fácil / más limpio o más seguro. El cartel decía que tenía un char *. Eso limita la cantidad de seguridad que vas a obtener :)
Chris Arguin


16

Puede usar un flujo de cadena del libraray estándar de C ++:

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

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

El estado de la secuencia se establecerá en error si se encuentra un no dígito al intentar leer un número entero.

Vea las trampas de Stream para las trampas de manejo de errores y streams en C ++.


2
El método de secuencia de cadena de C ++ no funciona para cadenas como "12-SomeString" incluso con la comprobación del "estado de la secuencia".
captonssj

10

Puedes usar stringstream's

int str2int (const string &str) {
  stringstream ss(str);
  int num;
  ss >> num;
  return num;
}

44
Pero esto no maneja ningún error. Tienes que verificar la transmisión en busca de fallas.
jk.

1
A la derecha, debe verificar la transmisión si ((ss >> num) .fail ()) {// ERROR}
CMS

2
El método de secuencia de cadena de C ++ no funciona para cadenas como "12-SomeString" incluso con la comprobación de "estado de transmisión"
captonssj

8

Creo que estos tres enlaces lo resumen:

Las soluciones stringstream y lexical_cast son casi lo mismo que lexical cast está usando stringstream.

Algunas especializaciones del reparto léxico utilizan un enfoque diferente; consulte http://www.boost.org/doc/libs/release/boost/lexical_cast.hpp para obtener más detalles. Los enteros y flotantes ahora están especializados para la conversión de entero a cadena.

Uno puede especializarse en lexical_cast para sus propias necesidades y hacerlo rápido. Esta sería la solución definitiva para todas las partes, limpia y simple.

Los artículos ya mencionados muestran una comparación entre los diferentes métodos de conversión de enteros <-> cadenas. Los siguientes enfoques tienen sentido: viejo c-way, spirit.karma, fastformat, simple ingenuo loop.

Lexical_cast está bien en algunos casos, por ejemplo, para la conversión de int a string.

No es una buena idea convertir una cadena a int usando el reparto léxico, ya que es 10-40 veces más lento que atoi, dependiendo de la plataforma / compilador utilizado.

Boost.Spirit.Karma parece ser la biblioteca más rápida para convertir enteros en cadenas.

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

y el bucle simple básico del artículo mencionado anteriormente es una forma más rápida de convertir cadenas a int, obviamente no es la más segura, strtol () parece una solución más 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;
}

7

La biblioteca de C ++ String Toolkit (StrTk) tiene la siguiente solución:

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;
}

InputIterator puede ser de iteradores char *, char * o std :: string sin firmar, y se espera que T sea un int con signo, como con signo int, int o long


1
ADVERTENCIA Esta implementación se ve bien, pero no puedo manejar desbordamientos por lo que puedo decir.
Vinnie Falco

2
El código no maneja el desbordamiento. v = (10 * v) + digit;se desborda innecesariamente con la entrada de cadena con el valor de texto de INT_MIN. La tabla tiene un valor cuestionable frente a simplementedigit >= '0' && digit <= '9'
chux - Restablecer Monica

6

Si usted tiene 11 C ++, las soluciones adecuadas hoy en día son el C ++ número entero funciones de conversión en <string>: stoi, stol, stoul, stoll, stoull. Lanzan excepciones apropiadas cuando se les da una entrada incorrecta y usan el rápido y pequeñostrto* funciones debajo del capó.

Si está atascado con una revisión anterior de C ++, sería portátil para usted imitar estas funciones en su implementación.


6

Desde C ++ 17 en adelante, puede usar std::from_charsdesde el <charconv>encabezado como se documenta aquí .

Por ejemplo:

#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 beneficio adicional, también puede manejar otras bases, como hexadecimal.


3

Me gusta la respuesta de Dan Moulding , solo le agregaré un poco de estilo C ++:

#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;
}

Funciona tanto para std :: string como const char * a través de la conversión implícita. También es útil para la conversión de base, por ejemplo, todos to_int("0x7b")y to_int("0173")y to_int("01111011", 2)y to_int("0000007B", 16)y to_int("11120", 3)y yto_int("3L", 34); devolvería 123.

A diferencia de std::stoiesto funciona en pre-C ++ 11. También a diferencia std::stoi, boost::lexical_castystringstream arroja excepciones para cadenas extrañas como "123hohoho".

NB: esta función tolera los espacios iniciales pero no los espacios finales, es decir, to_int(" 123")devuelve 123 mientras to_int("123 ")lanza una excepción. Asegúrese de que esto sea aceptable para su caso de uso o ajuste el código.

Dicha función podría ser parte de STL ...


2

Conozco tres formas de convertir String en int:

Use la función stoi (String to int) o simplemente vaya con Stringstream, la tercera forma de conversión individual, el código está a continuación:

1er 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';

2do 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;
} 

3.er método, pero no para una conversión 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 ;

}

1

Me gusta la respuesta de Dan , especialmente por evitar excepciones. Para el desarrollo de sistemas integrados y otros desarrollos de sistemas de bajo nivel, es posible que no haya un marco de excepción adecuado disponible.

Se agregó una marca de espacio en blanco después de una cadena válida ... estas tres líneas

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


También se agregó una comprobación de errores de análisis.

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


Aquí está la función 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;
}

@chux agregó código para atender las inquietudes que mencionó.
Pellucidio

1) Todavía no detecta el error con la entrada como " ". strtol()no se especifica para establecer errnocuando no se produce conversión. Mejor usar if (s == end) return INCONVERTIBLE; para detectar que no hay conversión. Y luego if (*s == '\0' || *end != '\0')puede simplificarse a if (*end)2) || l > LONG_MAXy || l < LONG_MINno sirve para nada: nunca son ciertas.
chux - Restablece a Mónica el

@chux En una Mac, errno está configurado para errores de análisis, pero en linux no está configurado. Se modificó el código para depender del puntero de "fin" para detectar eso.
pellucidio

0

Podrías usar este método definido.

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

Y si fuera a convertir de String a Integer, simplemente haría lo siguiente.

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

El resultado sería 102.


44
no sé. Escribir una macro de definición atoino parece "la forma C ++", a la luz de otras respuestas como la aceptada std::stoi().
Eugene Yokota

Me resulta más divertido usar métodos predefinidos: P
Boris

0

Sé que esta es una pregunta anterior, pero me la he encontrado muchas veces y, hasta la fecha, todavía no he encontrado una solución bien diseñada que tenga las siguientes características:

  • Puede convertir cualquier base (y detectar el tipo de base)
  • Detectará datos erróneos (es decir, garantizará que la conversión consuma toda la cadena, menos espacios en blanco iniciales / finales)
  • Se asegurará de que, independientemente del tipo convertido, el rango del valor de la cadena sea aceptable.

Entonces, aquí está el mío, con una correa de prueba. Debido a que usa las funciones C strtoull / strtoll debajo del capó, siempre se convierte primero al tipo más grande disponible. Luego, si no está utilizando el tipo más grande, realizará comprobaciones de rango adicionales para verificar que su tipo no se haya desbordado. Para esto, es un poco menos eficaz que si uno eligiera correctamente strtol / strtoul. Sin embargo, también funciona para cortos / caracteres y, que yo sepa, no existe una función de biblioteca estándar que haga eso también.

Disfrutar; ojalá alguien lo encuentre ú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;
}

StringToDecimales el método de usuario-tierra; está sobrecargado, por lo que puede llamarse así:

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

o esto:

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

Odio repetir el tipo int, así que prefiero el último. Esto asegura que si el tipo de 'a' cambia, uno no obtendrá malos resultados. Desearía que el compilador pudiera resolverlo como:

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

... pero, C ++ no deduce los tipos de retorno de plantilla, así que eso es lo mejor que puedo obtener.

La implementación es bastante simple:

CstrtoxllWrapperenvuelve ambos strtoully strtoll, llamando lo que sea necesario en función de la firma del tipo de plantilla y brindando algunas garantías adicionales (por ejemplo, la entrada negativa no se permite si no está firmada y asegura que se haya convertido toda la cadena).

CstrtoxllWrapperes utilizado por StringToSignedy StringToUnsignedcon el tipo más grande (largo largo / sin signo largo largo) disponible para el compilador; Esto permite que se realice la conversión máxima. Luego, si es necesario, StringToSigned/ StringToUnsignedrealiza las comprobaciones de rango final en el tipo subyacente. Finalmente, el método del punto final,StringToDecimal , decide a cuál de los métodos de plantilla StringTo * llamar en función de la firma del tipo subyacente.

Creo que la mayoría de la basura puede ser optimizada por el compilador; casi todo debería ser determinista en tiempo de compilación. ¡Cualquier comentario sobre este aspecto sería interesante para mí!


"use el tipo más grande" -> ¿por qué en long longlugar de intmax_t?
chux - Restablece a Mónica el

Seguro de ti mismo que quieres if (ePtr != str). Además, use isspace((unsigned char) *ePtr)para manejar adecuadamente los valores negativos de *ePtr.
chux - Restablece a Mónica el

-3

En C, puedes usar int atoi (const char * str),

Analiza la cadena C-string interpretando su contenido como un número integral, que se devuelve como un valor de tipo int.


2
Como me vinculé a atoila pregunta, soy consciente de ello. La pregunta claramente no es sobre C, sino sobre C ++. -1
Eugene Yokota
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.