Publi

Algoritmos: Formas de transformar un entero a cadena en C y C++

8733967750_f80410aa6b_bEn ocasiones disponemos de un entero (int) y necesitamos ese mismo valor en forma de cadena de caracteres (char* en C y string en C++). Ponerlo en pantalla es fácil, nos basta con printf() o cout, pero si queremos transmitir el dato, almacenarlo o hacer que forme parte de un dato más grande, tendremos la necesidad de transformar el tipo de dato.

Para ello existe una función de stdlib.h no estándar itoa(), y como no es estándar no existe en todas las implementaciones de C, por lo que es conveniente desarrollar una versión de esta función para que podamos compilar nuestro programa en todos los compiladores, o si no disponemos de esta función, para poder utilizarla.

Aún así, hay varias formas de transformar un entero a cadena, aquí mostraré algunas de ellas, ya que dependiendo de la ocasión será útil escoger entre una u otra:

Para C

sprintf()
Es la más fácil, y de tremenda utilidad, sobre todo porque nos permite mucho más que transformar el número a cadena, nos permite cambiar de base, introducir más datos, y un sin fin de posibilidades, todo lo que hace printf() pero incluyéndolo en una cadena de caracteres, en lugar de mostrarlo en pantalla:

1
2
3
char texto[100];
int numero=2012;
sprintf(texto, "%d", numero);

También podemos crear una función y almacenarla en una biblioteca para incluir en nuestros proyectos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void strreverse(char* begin, char* end)
{  
    char aux;  
    while(end>begin)   
        aux=*end, *end--=*begin, *begin++=aux; 
}
void itoa_(int value, char *str)
{
    char* wstr=str;
    int sign;  
    div_t res;
   
    if ((sign=value) < 0) value = -value;
   
    do {   
      *wstr++ = (value%10)+'0';
    }while(value=value/10);
   
    if(sign<0) *wstr++='-';
    *wstr='\0';

    strreverse(str,wstr-1);
}

El objetivo al final es ir dividiendo el número en cuestión por 10 y sacar el resto de la división e ir añadiendo ese número a la cadena, aunque la cadena resultante estará al revés, por eso, luego le damos la vuelta a los caracteres que hemos obtenido.

También podemos optar por una un poco más larga, pero que nos hace la reserva de memoria directamente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
char *itoa__(int value)
{
  int count, /* number of characters in string */
    i, /* loop control variable */
    sign; /* determine if the value is negative */
  char *ptr, /* temporary pointer, index into string */
    *string, /* return value */
    *temp; /* temporary string array */

  count = 0;
  if ((sign = value) < 0) /* assign value to sign, if negative */
    { /* keep track and invert value */
      value = -value;
      count++; /* increment count */
    }

  /* allocate INTSIZE plus 2 bytes (sign and NULL) */
  temp = (char *) malloc(INTSIZE + 2);
  if (temp == NULL)
    {
      return(NULL);
    }
  memset(temp,'\0', INTSIZE + 2);

  string = (char *) malloc(INTSIZE + 2);
  if (string == NULL)
    {
      return(NULL);
    }
  memset(string,'\0', INTSIZE + 2);
  ptr = string; /* set temporary ptr to string */

  /*--------------------------------------------------------------------+
    | NOTE: This process reverses the order of an integer, ie: |
    | value = -1234 equates to: char [4321-] |
    | Reorder the values using for {} loop below |
    +--------------------------------------------------------------------*/

  do {
    *temp++ = value % 10 + '0'; /* obtain modulus and or with '0' */
    count++; /* increment count, track iterations*/
  } while (( value /= 10) >0);

  if (sign < 0) /* add '-' when sign is negative */
    *temp++ = '-';

  *temp-- = '\0'; /* ensure null terminated and point */
  /* to last char in array */

  /*--------------------------------------------------------------------+
    | reorder the resulting char *string: |
    | temp - points to the last char in the temporary array |
    | ptr - points to the first element in the string array |
    +--------------------------------------------------------------------*/

  for (i = 0; i < count; i++, temp--, ptr++)
    {
      memcpy(ptr,temp,sizeof(char));
    }

  return(string);
}

Aunque lo mismo que lo hacemos con el número 10, lo podemos hacer con cualquier base, para obtener números en base 2, base 8, base 16, o base 35…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void strreverse(char* begin, char* end) {  
    char aux;  
    while(end>begin)   
        aux=*end, *end--=*begin, *begin++=aux; 
}
   
void itoa(int value, char* str, int base) {
    static char num[] = "0123456789abcdefghijklmnopqrstuvwxyz";
    char* wstr=str;
    int sign;  
    div_t res;
    // Validate base
   
    if (base<2 || base>35){ *wstr='\0'; return; }  
    // Take care of sign
   
    if ((sign=value) < 0) value = -value;
    // Conversion. Number is reversed.
   
    do {   
        res = div(value,base); 
        *wstr++ = num[res.rem];
    }while(value=res.quot);
   
    if(sign<0) *wstr++='-';
    *wstr='\0';
    // Reverse string  
    strreverse(str,wstr-1);
}
Visto en: http://www.strudel.org.uk/itoa/

Aunque normalmente, yo prefiero un método parecido a este, sólo que devolviendo el puntero a char con la cadena resultante (aunque se lo pasemos como parámetro también, pero así lo podemos meter directamente a un printf() por ejemplo). El código a continuación es muy parecido al anterior, sólo que devolviendo la cadena:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void strreverse(char* begin, char* end) {  
    char aux;  
    while(end>begin)   
        aux=*end, *end--=*begin, *begin++=aux; 
}
   
char* itoa(int value, char* str, int base) {
    static char num[] = "0123456789abcdefghijklmnopqrstuvwxyz";
    char* wstr=str;
    int sign;  
    div_t res;
    // Validate base
   
    if (base<2 || base>35){ *wstr='\0'; return; }  
    // Take care of sign
   
    if ((sign=value) < 0) value = -value;
    // Conversion. Number is reversed.
   
    do {   
        res = div(value,base); 
        *wstr++ = num[res.rem];
    }while(value=res.quot);
   
    if(sign<0) *wstr++='-';
    *wstr='\0';
    // Reverse string  
    strreverse(str,wstr-1);
        return str;
}
Visto en: http://www.strudel.org.uk/itoa/

Para C++

El objetivo de hacerlo en C++ es devolver un string en lugar de un char* y así despreocuparnos un poco de la reserva de memoria, además de hacerlo todo un poco más c++ way.

Como en C, tenemos un método sencillo, casi implementado, pero en lugar de utilizar sprintf() utilizaremos la clase stringstream:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <sstream>

using namespace std;

string itoa(int num)
{
  stringstream s;
  s<<num;
  return s.str();
}

Aunque tal vez es un método un poco lento que hace uso de demasiadas estructuras (para el objetivo que perseguimos), además, no nos permite modificar la base, algo que es muy útil, aunque si hacemos uso de iomanip, podemos utilizar el modificador setbase().

Pero podemos utilizar otros métodos (más artesanos), pero con resultados más rápidos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
string itoa(int n){
  string rtn;
  bool neg=false;
  if (n<0)
    {
      neg=true;
      n=-n;
    }

  if (n==0)
    return "0";

  for(rtn="";n>0;rtn.insert(rtn.begin(),n%10+'0'),n/=10);

  if (neg)
    rtn.insert(rtn.begin(),'-');
  return rtn;
}

En este caso, en lugar de dar la vuelta, hacemos uso de la clase string para poder insertar los caracteres al principio de la cadena.

En este otro vemos la posibilidad de cambiar de base, con algunas optimizaciones como la pre-reserva de memoria en el string:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <cmath>
#include <string>
#include <algorithm>

std::string itoa(int value, int base) {

  std::string buf;

  // check that the base if valid
  if (base < 2 || base > 16) return buf;

  enum { kMaxDigits = 35 };
  buf.reserve( kMaxDigits ); // Pre-allocate enough space.

  int quotient = value;

  // Translating number to string with base:

  do {
    buf += "0123456789abcdef"[ std::abs( quotient % base ) ];
    quotient /= base;
  } while ( quotient );

  // Append the negative sign
  if ( value < 0) buf += '-';

  std::reverse( buf.begin(), buf.end() );
  return buf;
}
Fuente: http://www.strudel.org.uk/itoa/

Aunque yo me quedo con un método intermedio, pre-reservaremos memoria, podremos cambiar de base (hasta 35), y no llamaremos en cada iteración a abs(). Por otra parte, en lugar de dar la vuelta con reverse, seguiremos insertando caracteres al principio de la cadena (aunque dependiendo de la implementación, este segundo método puede ser más pesado que dar la vuelta):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
string itoa(int n, int base){
  string rtn;
  bool neg=false;
  if (n<0)
    {
      neg=true;
      n=-n;
    }

  if (base < 2 || base > 35)
    return rtn;

  if (n==0)
    return "0";

  for(rtn="";n>0;rtn.insert(rtn.begin(),"0123456789abcdefghijklmnopqrstuvwxyz"[n%base]),n/=base);

  if (neg)
    rtn.insert(rtn.begin(),'-');
  return rtn;
}

Foto: Historias Visuales (Flickr CC BY-NC-SA)

También podría interesarte...

Only 1 comment left Ir a comentario

  1. Pingback: Bitacoras.com /

Leave a Reply