Archivo

Archivo para la categoría ‘C/C++’

Extrayendo datos de direcciones web (C++ sin cURL)

Jueves, 29 de Julio de 2010 admin Sin comentarios

url

En ocasiones, estamos inmersos en un proyecto que requiere conexión con un servidor.

Soy consciente de que existen muchas librerías que nos garantizan el acceso, y tienen muchas opciones aunque, a veces, no necesitamos tanto, sólo un simple acceso y un intercambio sencillo de información; del mismo modo, no nos apetece engordar demasiado nuestro ejecutable, o hacer que nuestro programa dependa de alguna librería externa.

Con esa intención, hice esta clase para C++ y que utilicé para uno de mis pequeños proyectos
hace unos años. Tengo que advertir que es una clase muy simple, y por eso también es válida para los que se quieran iniciar en programación de sockets.

Se puede descargar mysocktar (3.7Kb)

Para compilar el ejemplo:

$ g++ -c MySock.cpp
$ g++ -o ej_mysock ej_mysock.cpp MySock.o

Aún quedan cosas por hacer, sin perder el objetivo de una clase ligera, muy ligera:

  • Hacer documentación compatible con Doxygen
  • Mejor soporte para cabeceras HTML
  • Extracción de tags HTML
  • Soporte para contraseñas

Foto: Diego_3336 (Flickr)

Actualizado stermp.h (getch(), getche() y kbhit() en Linux)

Miércoles, 30 de Junio de 2010 admin Sin comentarios

Hace más de un año publiqué algunas funciones para manejar colores en la terminal de forma fácil. (Ver link). El objetivo era aprender un poco cómo funciona la terminal, y de paso ayudarme con mis alumnos de clases particulares (casi todos utilizan conio.h), yo quería una alternativa; por otra parte, facilitar el uso en proyectos pequeños, ya que si queremos algo más, para eso está ncurses.

Me he decidido sacar una segunda versión, ya que la primera no disponía de sustituto para getch() ni kbhit(), recordemos que estas funciones son para pedir un carácter desde teclado sin echo y sin necesidad de pulsar ENTER, y detectar la pulsación de una tecla sin parar la ejecución del programa respectivamente.

Tengo que avisar también de que este código no funciona con las flechas del teclado (estas teclas no tienen un carácter asociado y lo que hacen es devolver varios códigos de tecla, el primero de ellos ESC (27)), lo dejo para la próxima actualización.

También hay algunas correcciones, y algunas novedades internas.

Para descargar: stermp.h y stermp.c

Para compilar stermp (Simple Terminal Play):

$ gcc -c stermp.c

Para compilar nuestros proyectos:

$ gcc -o proyecto proyecto.c stermp.o

Y ahora un pequeño código de prueba:

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
#include <stdio.h>
#include <time.h>
#include "stermp.h"

void update_time()
{
  struct tm *tm;
  time_t _time;
  char texto[50];
  textcolor(YELLOW);
  _time=time(NULL);
  tm = localtime(&_time);
  strftime(texto,50,"%d/%m/%Y %H:%M:%S", tm);
  gotoxy(1,1);
  printf("%s    ", texto);
}

int main()
{
  int x,y;
  int ancho, alto;
  int tecla;
  term_init();
  ancho = screenwidth();
  alto = screenheight();

  /* Rellenamos de verde la pantalla */
  textbackground(GREEN);
  clrscr();

  textbackground(BLUE);
  /* Rellenamos de azul la primera fila */
  for (x=0; x<ancho; x++)
    printf(" ");

  gotoxy(1,alto);
  /* Rellenamos de azul la última fila */
  for (x=0; x<ancho; x++)
    printf(" ");

  gotoxy(2,2);
  while ((tecla=kbhit2())==0)
      update_time();    


  printf("Has pulsado: %d\n", tecla);

  term_defaults();
 
}

Bailando con bits: Trabajando a nivel de bit II

Miércoles, 23 de Junio de 2010 admin Sin comentarios

Hace unos días empecé con la serie Bailando con Bits (aunque llevaba escrito varios meses) trata de formas para trabajar a nivel de bit desde C.

Hoy voy a proponer otra forma, quizás menos intuitiva que la anterior, pero diferente. Esta vez no utilizaremos un registro enorme ni nada parecido, utilizaremos un mismo número entero para hacer el ejemplo:

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
#include <stdio.h>

#define PESOBIT(bpos) 1<<bpos
#define COGEBIT(var,bpos) (var & PESOBIT(bpos))?1:0
#define PONE_1(var,bpos) var | PESOBIT(bpos)
#define PONE_0(var,bpos) var & ~(PESOBIT(bpos))
#define CAMBIA(var,bpos) var ^ PESOBIT(bpos)

int main()
{
  int numero;
  int i;

  numero=63;
  printf ("Numero: %d\n", numero);


  for (i=31; i>=0; i--)
    printf("%4d", i);

  printf("\n");

  for (i=31; i>=0; i--)
    printf("%4d",COGEBIT(numero,i));

  printf("\n");

  numero=PONE_1(numero, 17);
  numero=PONE_0(numero, 3);
  numero=CAMBIA(numero, 20);
  numero=CAMBIA(numero, 5);

  for (i=31; i>=0; i--)
    printf("%4d",COGEBIT(numero,i));

  printf("\nNúmero: %d\n", numero);

}

Ahora usamos varias macros que harán operaciones de bit con la variable a analizar (están definidas en la parte de arriba), tenemos PESOBIT, COGEBIT, PONE_1, PONE_0 y CAMBIA:

  • PESOBIT: Nos dice cuánto vale un bit con valor 1 en la posición especificada, por ejemplo en la posición 0 (LSB) vale 1, en la posición 1, vale 2, en la posición 3, vale 4, en la posición 4, vale 8…
  • COGEBIT: Nos dice si el bit en la posición bpos de la variable var vale 0 ó 1
  • PONE_1: Pone un 1 en el bit bpos de la variable var
  • PONE_0: Pone un 0 en el bit bpos de la variable var
  • CAMBIA: Cambia el valor (de 0 a 1 y viceversa) del bit en la posición bpos de la variable var

Como vemos en el ejemplo si queremos poner a 1 el bit 5 de numero, tendremos que hacer numero=PONE_1(numero,5), aunque en el siguiente ejemplo veremos cómo simplificar todo eso.

Con este ejemplo podemos jugar con los bits de números enteros. Pero, y si queremos utilizar otro tipo de variables? (aunque estamos limitados a 32bits) También tenemos este ejemplo con un tipo float:

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
#include <stdio.h>

#define PESOBIT(bpos) 1<<bpos
#define COGEBIT(var,bpos) (*(unsigned*)&var & PESOBIT(bpos))?1:0
#define PONE_1(var,bpos) *(unsigned*)&var |= PESOBIT(bpos)
#define PONE_0(var,bpos) *(unsigned*)&var &= ~(PESOBIT(bpos))
#define CAMBIA(var,bpos) *(unsigned*)&var ^= PESOBIT(bpos)

int main()
{
  float numero;

  int i;

  numero=63.2317;
  printf ("Numero: %f\n", numero);


  for (i=31; i>=0; i--)
    printf("%4d", i);

  printf("\n");

  for (i=31; i>=0; i--)
    printf("%4d",COGEBIT(numero,i));

  printf("\n");

  PONE_1(numero, 5);
  PONE_0(numero, 3);
  CAMBIA(numero, 20);
  CAMBIA(numero, 5);
  CAMBIA(numero, 31);

  for (i=31; i>=0; i--)
    printf("%4d",COGEBIT(numero,i));

  printf("\n");

  printf("\nNúmero: %f\n", numero);

}

En este caso no tenemos que hacer una variable igual a PONE_1(variable, 5) para poner su bit a 1; simplemente con PONE_1(variable, 5) nos vale. En este ejemplo podemos ver una variable de tipo float desglosada en bits y podemos modificar a nivel de bit para obtener otros valores. (No le veo mucha utilidad a esto, pero bueno).

Post dedicado a algm, por su comentario en el artículo Bailando con bits anterior.

Fibonacci recursivo en C [ intentemos no repetir operaciones! ]

Lunes, 21 de Junio de 2010 admin Sin comentarios

fibo
Es un ejercicio muy típico cuando se está aprendiendo con funciones recursivas es la sucesión de Fibonacci, aquella en la que F(n)=F(n-1)+F(n-2).
Aunque esta técnica podemos (y deberíamos) utilizarla para una gran cantidad de algoritmos. A veces es necesario sacrificar un poco la memoria del sistema (sin pasarnos) para agilizar y hacer más rápido el programa. Debemos adoptar una solución que beneficie al usuario, no es plan de dejarlo sin memoria, pero tampoco es plan de que la solución se eternice.

Una primera implementación puede ser la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int fibonacci_simple(int n)
{
  /* La función normal, recursiva */
  if (n>2)
    return fibonacci_simple(n-1) + fibonacci_simple(n-2);
  /* Este es el caso más común,  */
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)       
    return 1;
  else if (n==0)
    return 0;
  else
    return -1;          /* Error */
}

Si creamos un programa de ejemplo como este:

1
2
3
4
5
6
7
8
9
10
int main(int argc, char *argv[])
{
    unsigned int numero;

    /* Generamos Fibonaccis */
    for (numero=0; numero<48; numero++)
      printf("%u\n", fibonacci_simple(numero));

    return 0;
}

Podemos observar que para generar la secuencia de 48 números (he escogido este valor, porque a partir de ahí no tenemos suficiente de 32bits para almacenar el valor obtenido), tardamos varios minutos, en concreto, en mi equipo (Pentium M 1.73MHz), echó algo más de 10 minutos. ¡ 10 minutos ! Es un buen rato para lo que ha hecho.
Si lo estudiamos detenidamente (en negrita pongo las veces que llamamos a fibonacci_simple() sin necesidad):

  • fibonacci_simple(0)
  • fibonacci_simple(1)
  • fibonacci_simple(2)
  • fibonacci_simple(3) (que llama a fibonacci_simple(2) y fibonacci_simple(1) )
  • fibonacci_simple(4) (que llama a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) ) y a fibonacci_simple(2) )
  • fibonacci_simple(5) (que llama a fibonacci_simple(4) ( que llama a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) ) y a fibonacci_simple(2) ) y a fibonacci_simple(3) ( que llama a fibonacci_simple(2) y fibonacci_simple(1) )

Yo creo que con esto nos hacemos una idea de lo que quiero decir, se repiten demasiadas operaciones, seguro que hay alguna forma de solucionar esto.

Propongo lo siguiente:

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
int fibonacci_buffer(int n)
{
  static int *fbuffer=NULL; /* Buffer */
  static int bufsize=0; /* Tamaño de buffer */
  int n3;           /* n-2 */
  int i;            /* Recorre buffers */

  if (n>2)
    {
      n3=n-2;
      if (fbuffer==NULL)
    {
      fbuffer = (int*) malloc((n3 * sizeof(int)));
      for (i=0; i<n3; i++)
        fbuffer[i]=0;   /* Ponemos buffer a 0 */

/*    printf("Creo buffer: %d -- %d\n", n3*sizeof(int), errno); */

      bufsize=n3;
    }
      else if (bufsize<n3)
    {
      fbuffer = (int*) realloc(fbuffer, n3 * sizeof(int));
/*    printf("Amplio buffer\n"); */

      for (i=bufsize; i<n3; i++) /* Ponemos a 0 la memoria nueva */
        fbuffer[i]=0;

      bufsize=n3;
    }
/*       printf("-%d--%d\n", n, fbuffer[n3-1]); */
      if (fbuffer[n3-1]!=0) /* Si se encuentra el fibonacci del número que buscamos  */
    return fbuffer[n3-1];   /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3-1] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2);
      //      printf("He generado con éxito fbuffer[%d]\n", n3-1);
      return fbuffer[n3-1];
    }

/*       return fibonacci_simple(n-1) + fibonacci_simple(n-2); */
    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;
  else if (n==-1)       /* Un caso nuevo n==-1 */
    free(fbuffer);      /* Liberamos memoria */

  return -1;            /* Error */
}

Es algo más enrevesado, ya que usamos variables estáticas (podríamos hacer variables globales, con cuidado), así evitamos que la información que vamos almacenando se pierda entre ejecuciones de la función; por otra parte, utilizamos malloc() y realloc() para reservar sólo la memoria que vamos necesitando.
Por otra parte para reducir la memoria utilizada, hacemos que fibonacci(3) se almacene en la posición 0 del buffer (porque el de 2, 1 y 0, ya nos los sabemos); por otra parte… la variable n3 vale n-2; esto es simplemente porque si tenemos que calcular fibonacci(5), y sabemos que fibonacci(3) está almacenado en fbuffer[0], fibonacci(4) estará en fbuffer[1] y fibonacci(5) estará en fbuffer[2]; es decir tenemos que almacenar 3 valores de fibonacci (5-2=3).
Por otra parte, siempre que reservamos memoria ponemos los nuevos espacios a 0, ya que comprobamos que no hay ningún valor almacenado si éstos son 0.

Con el almacén de los números conseguimos que el esquema anterior se quede en:

  • fibonacci_buffer(0)
  • fibonacci_buffer(1)
  • fibonacci_buffer(2)
  • fibonacci_buffer(3) (que llama a fibonacci_buffer(2) y fibonacci_buffer(1)
  • fibonacci_buffer(4) (que llama a fibonacci_buffer(3) ( que saca el valor del buffer ) y a fibonacci_simple(2) )
  • fibonacci_buffer(5) (que llama a fibonacci_buffer(4) (que saca el valor del buffer) y a fibonacci_buffer(3) (que saca el valor del buffer)

Con este algoritmo consigo generar todos los números en 4ms.

¿Será posible simplificarlo un poco más?
Sólo hay que ver cómo se hace la generación de valores de forma recursiva. Si lo pensamos bien, cuando llamamos a fibonacci_buffer(5) la primera vez y lo queremos almacenar en fbuffer[2] llamaremos a fibonacci_buffer(4) (que se almacenará en fbuffer[1], y éste llamará a fibonacci_buffer(3) (que se almacenará en fbuffer[0]); es decir, lo primero que se generará será fbuffer[0], luego fbuffer[1] y por último fbuffer[2]… ¡ vamos por orden ! Sabiendo esto, nos podremos ahorrar el código de poner a 0 los elementos del buffer sabiendo cuál es el último elemento generado.

Para los incrédulos, podemos eliminar el comentario de la línea:

1
//    printf("He generado con éxito fbuffer[%d]\n", n3-1);

y ejecutar… veremos con qué orden se van generando los números.

Bien, intentemos eliminar las puestas a 0 de los elementos del buffer.

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
int fibonacci_buffer2(int n)
{
  static int *fbuffer=NULL; /* Buffer */
  static int bufsize=0;         /* Tamaño de buffer */
  static int bpos=0;            /* Recorre buffers */
  int n3;           /* n-2 */

  if (n>2)
    {
      n3=n-2;
      if (fbuffer==NULL)
    {
      fbuffer = (int*) malloc((n3 * sizeof(int)));
      bufsize=n3;   /* Mantenemos bufsize por si redimensionamos el buffer */
    }
      else if (bufsize<n3)
    {
      fbuffer = (int*) realloc(fbuffer, n3 * sizeof(int));
      bufsize=n3;      
    }

      /* Utilizamos bpos para saber cuál es el último elemento del buffer */
      /* que hemos rellenado. */
      if (bpos>=n3)         /* Si se encuentra el fibonacci del número que buscamos  */
    return fbuffer[n3-1];   /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3-1] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2);
      bpos=n3;
      return fbuffer[n3-1];
    }

/*       return fibonacci_simple(n-1) + fibonacci_simple(n-2); */
    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;
  else if (n==-1)       /* Un caso nuevo n==-1 */
    free(fbuffer);      /* Liberamos memoria */

  return -1;            /* Error */
}

Con este algoritmo, consigo generar la secuencia en 2ms. Bastante más rápido que con el primer algoritmo, parece que la puesta a 0 del buffer llevaba su tiempo.

Para concluir, quiero proponer otra solución basada en arrays, supongo que muchos estaréis más familiarizados con estos:

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
int fibonacci_buffer3(int n)
{
  static int fbuffer[MAX_FIB];
  static int bufsize=0;         /* Tamaño de buffer */
  int n3;           /* n-3 */

  if (n>2)
    {
      n3=n-3;

      if (bufsize>n3)           /* Si se encuentra el fibonacci del número que buscaos  */
    return fbuffer[n3]; /* en memoria, lo devolvemos directamente */
      else
    {
      /* Si el fibonacci no se encuentra, vamos a generarlo */
      fbuffer[n3] = fibonacci_buffer(n-1) + fibonacci_buffer(n-2); /* y lo guardamos */
      bufsize=n3+1;
      /* Al ser fibonacci recursivo, y asignar el tamaño después de la generación */
      /* el buffer se rellenará por orden, es decir, el primero que se rellenará */
      /* será el [0], luego el [1], luego el [2]... */
      return fbuffer[n3];
    }

    }
  else if (n==2)        /* Ponemos esto para agilizar un poco el proceso */
    return 1;
  else if (n==1)
    return 1;
  else if (n==0)
    return 0;

  return -1;            /* Error, seguramente me hayan pasado un n negativo*/
}

Esta solución es muy parecida a las anteriores, aunque nos quitamos del medio la reserva de memoria, reducimos las líneas de código, ya que no vamos a alojar tantos valores diferentes… para esta secuencia serían unos 48 * 4 (sizeof(int)) = 192 bytes, y bufsize la utilizamos para ver cuál es el último elemento del buffer con información válida (como vimos antes, se rellenarán por orden, así que sólo tendríamos que llevar la cuenta). Las variables estáticas, como dije, podemos hacerlas globales (y quitarles el static, aunque queda más elegante como static).

Con esta solución tardo unos 5ms de media en generar la secuencia de números.

Foto: Eustaquio Santimano (Flickr)

Bailando con bits: Ver y modificar los bits de un número

Jueves, 10 de Junio de 2010 admin 2 comentarios

Hay muchas formas para hacer esto, pero quizás la más visual (tal vez también útil algunas veces) es la siguiente (se explica más abajo):

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <stdio.h>

typedef struct {
    unsigned int b1:1;
    unsigned int b2:1;
    unsigned int b3:1;
    unsigned int b4:1;
    unsigned int b5:1;
    unsigned int b6:1;
    unsigned int b7:1;
    unsigned int b8:1;
    unsigned int b9:1;
    unsigned int b10:1;
    unsigned int b11:1;
    unsigned int b12:1;
    unsigned int b13:1;
    unsigned int b14:1;
    unsigned int b15:1;
    unsigned int b16:1;
    unsigned int b17:1;
    unsigned int b18:1;
    unsigned int b19:1;
    unsigned int b20:1;
    unsigned int b21:1;
    unsigned int b22:1;
    unsigned int b23:1;
    unsigned int b24:1;
    unsigned int b25:1;
    unsigned int b26:1;
    unsigned int b27:1;
    unsigned int b28:1;
    unsigned int b29:1;
    unsigned int b30:1;
    unsigned int b31:1;
    unsigned int b32:1;

} Tint_bits;

int main()
{
  int numero;
  Tint_bits *bitpack;

  bitpack=(Tint_bits*)&numero;
  numero=63;
  printf ("Dir cos: %X\nDir paq: %X\n", &numero, bitpack);  // Vemos que las direcciones de memoria son idénticas.
  printf ("Numero: %d\n", numero);
  printf ("Bit 1: %d\n", bitpack->b1);
  printf ("Bit 2: %d\n", bitpack->b2);
  printf ("Bit 3: %d\n", bitpack->b3);
  printf ("Bit 4: %d\n", bitpack->b4);
  printf ("Bit 5: %d\n", bitpack->b5);
  printf ("Bit 6: %d\n", bitpack->b6);
  printf ("Bit 7: %d\n", bitpack->b7);
  printf ("Bit 8: %d\n", bitpack->b8);
  printf ("Bit 9: %d\n", bitpack->b9);
  printf ("Bit10: %d\n", bitpack->b10);
  printf ("Bit11: %d\n", bitpack->b11);
  printf ("Bit12: %d\n", bitpack->b12);
  printf ("Bit13: %d\n", bitpack->b13);
  printf ("Bit14: %d\n", bitpack->b14);
  printf ("Bit15: %d\n", bitpack->b15);
  printf ("Bit16: %d\n", bitpack->b16);
  printf ("Bit17: %d\n", bitpack->b17);
  printf ("Bit18: %d\n", bitpack->b18);
  printf ("Bit19: %d\n", bitpack->b19);
  printf ("Bit20: %d\n", bitpack->b20);
  printf ("Bit21: %d\n", bitpack->b21);
  printf ("Bit22: %d\n", bitpack->b22);
  printf ("Bit23: %d\n", bitpack->b23);
  printf ("Bit24: %d\n", bitpack->b24);
  printf ("Bit25: %d\n", bitpack->b25);
  printf ("Bit26: %d\n", bitpack->b26);
  printf ("Bit27: %d\n", bitpack->b27);
  printf ("Bit28: %d\n", bitpack->b28);
  printf ("Bit29: %d\n", bitpack->b29);
  printf ("Bit30: %d\n", bitpack->b30);
  printf ("Bit31: %d\n", bitpack->b31);
  printf ("Bit32: %d\n", bitpack->b32);
 
  bitpack->b9=1;
  printf("Numero= %d\n", numero);
}

Primero creamos un espacio de bits en nuestro registro Tint_bits, con ello, hacemos 32 variables de tamaño 1 bit (con un espacio en memoria de 32bits (lo que es un entero de 32bits). Lo que hacemos para poder consultar los bits, es crear un puntero de este tipo (Tint_bits) que apunte a la dirección de memoria de nuestro número, con ello podremos leer en memoria, los distintos bits del número accediendo a los diferentes miembros (b1->b32), y lo que es mejor, podemos modificarlos (como hacemos al final del programa).

Lo malo de este método es que no podremos acceder a los bits como si de un array se tratase, y que este método no es muy portable, para enteros de 64 bits habría que hacer otro registro por ejemplo, y tendremos problemas para pasar de un sistema big-endian a un little-endian y viceversa; pero, como yo digo podemos ver la memoria directamente, que a veces es interesante.

Una balanza que no funciona

Lunes, 31 de Mayo de 2010 admin Sin comentarios

450px-balanzaLlega el día en que nos dedicamos al desarrollo, ya sea por libre, en una empresa, para investigación o en cualquier otra circunstancia; tenemos que poner en práctica lo aprendido durante tanto tiempo y no estamos haciendo simples ejercicios ni programas de un máximo de 200 líneas (con muchos espacios en blanco, comentarios multi-línea y esas cosas).
Es decir, nos enfrentamos a un proyecto real y tenemos que dar lo mejor de nosotros mismos en su realización.
Como tampoco es un proyecto excesivamente grande decidimos pasar de hacer una planificación previa del proyecto, y decidimos que es más rápido pasar a la acción y ponernos a picar código.
Comprendo que si no se tiene mucha experiencia, es una tarea intelectualmente más ligera ponernos a escribir de forma estructurada nuestros algoritmos, excepcionalmente haciendo alguna función; no tenemos tiempo para separar estructuras, ni para idear algún algoritmo extra que nos simplifique un poco el código, pero no le damos importancia… simplemente nos damos el batacazo cuando el cliente nos pide un pequeño cambio en el código; de repente se nos viene a la cabeza la famosa teoría del caos. Y es que nuestro código es un caos. Y no comprendemos que el cliente (a no ser que seamos nosotros mismos), usuario, jefe, o persona que necesita el programa pero no tiene ni idea de cómo se desarrolla tiende a pensar que todos los cambios son pequeños y que cualquier modificación se hace en 10 minutos; por tanto, la primera versión de nuestro código no va a servir, y tenemos que distinguir entre: pijadas, o modificaciones en la interfaz, cosas que harían el programa más bonito; entrada/salida, tal vez campos de una base de datos que se suponía que teníamos que introducir y que no están (será porque nadie nos había informado de que la posición en que una persona jugaba al fútbol de pequeño era un dato personal básico e importantísimo); o que está visualizando más datos de los que quiere; cambios estructurales, esos ya son más peliagudos, pero bueno.
Lo que quiero decir, es que la versión preliminar que hagamos casi nunca va a ser la buena y tendremos que revisarlo, incluso al cabo de un tiempo, se necesitará añadir una característica más, y la hemos liao si seguimos por este camino.

Tenemos que hacer de nuestro código un arte:

  • Podemos comentar en nuestro idioma hasta la saciedad, aunque sin pasarnos.
  • Podemos dividir nuestro código, e incluso cuando hacemos cosas muy parecidas podemos unificarlas en una función/clase/proceso diferente. El lema divide y vencerás es muy útil, y a la larga hace nuestra aplicación más facilmente mantenible.
  • Podemos separar la lógica del programa, con lo relativo a datos y lo relativo a la visualización (MVC)… programar a pegotón no ayuda.
  • A veces, con alguna operación matemática, nos podemos ahorrar una secuencia iterativa
  • No por mucho que tarde nuestro programa en hacer una tarea determinada, lo hace más profesional, aunque hay empresas que esto no lo tienen muy claro.

Si bien es cierto que cuando llamamos a funciones estamos haciendo la ejecución de nuestro código más lenta (con operaciones recursivas ya ni hablamos), tenemos que tener en cuenta si podemos sacrificar unos cuantos milisegundos en la ejecución en favor de unas cuantas horas de desarrollo, y es que muchas veces, crear una función, incluso para hacer nuestro código más legible (por ejemplo, si estamos presentando los resultados de una búsqueda, y necesitamos ordenarlos, no tenemos por qué implementar el método de ordenación en el mismo sitio, nos lo podemos llevar fuera; incluso si tenemos que presentarlo de forma bonita para el usuario, podemos hacer una función aparte que presente esos datos), puede ahorrarnos mucho tiempo de mantenimiento; cuando al cabo de dos semanas nos enfrentemos de nuevo a nuestro código, seguro que preferimos ver las cosas más claras.

Dando ejemplos (no todo se hace en los lenguajes mencionados, esto lo podemos extender a muchos lenguajes más):

  • Vale, estamos diseñando un sistema de tiempo real y tiene que funcionar rápido, como ejecutamos las órdenes muchas veces, no podemos queremos hacer funciones ni hacer nada complicado, para no ralentizar mucho la ejecución… en C/C++, por ejemplo, podemos crear macros.
  • Nuestras necesidades pueden ser más complicadas, tenemos que tener en cuenta que un lenguaje de programación, nos da herramientas básicas, y las podemos extender. Imaginad el uso de mysql_query() [PHP], puede que necesitemos contar cuántas veces se ha llamado esta función. Esto implicaría ver dónde se ha llamado la función e incrementar una variable, aunque tal vez en el futuro tengamos más necesidades.
  • Ya no hablamos de la abstracción con objetos, al principio nos choca esa forma nueva de pensar, pero a la hora de embarcarnos en un proyecto, de primeras no la tenemos muy en cuenta. Por ejemplo, en C++ o en PHP es muy cómodo trabajar con una clase que se entienda con las bases de datos, nosotros símplemente tenemos que llamarla siempre que necesitemos algo.
  • A la hora de hacer una web dinámica se piensa poco en un posible re-diseño, y cuando este llega nos echamos las manos a la cabeza ya que hemos puesto multitud de código PHP entre el (x)html… podremos crear una biblioteca de funciones dedicada a la exportación web y desde nuestro php llamarlas… haría fáciles los re-diseños.

Todo esto viene unido a un post anterior: Enseñando a programar, sobre todo porque en muchos lugares se enseña programación (me refiero a carreras universitarias que no tienen que ver con informática; algunos ciclos formativos, etc) pero uno de los grandes objetivos es que nos sirva para el futuro; y de poco sirve si no se sientan unas bases. Es importante el reciclaje, la claridad y la sostenibilidad.

Foto: Feliciano

Enseñando a programar

Viernes, 7 de Mayo de 2010 admin 4 comentarios

Desde hace tiempo, me ofrezco como profesor particular de programación en C/C++ (entre otros), he conocido bastantes alumnos, y metodologías de varios profesores. Este artículo es una opinión personal de mi experiencia.

En principio tengo que decir que muchos de mis alumnos, sólo venían para sacarse una asignatura, no tenían demasiado interés, aunque fuera una de las asignaturas claves de sus estudios, y algo que les ayudaría el día de mañana; aunque es cierto que no todo el mundo puede aprender a programar desde cero y con soltura en 3 ó 4 meses, requiere un entrenamiento, dedicación, mucho tiempo y enfrentarse con problemas una y otra vez, y si le sumamos a esto poco interés estamos perdidos.

Es cierto que igual que en todas las ciencias e ingenierías tiene un aspecto recursivo y es que lo más sencillo se enlaza con aspectos muy complicados y es difícil introducir a alguien en este mundo sin que queden preguntas sin resolver y huecos que en el futuro se rellenarán, pero algunas insaciables mentes quieren ir más deprisa, y cuando las respuestas a sus preguntas empiezan a sonar a chino, abandonan.

Es por esto, que como profesor hay que ofrecer algún valor adicional al alumno, y aunque no es complicado ofrecer en un grupo reducido, existen muchos centros de enseñanza en los que ni siquiera se intenta; no podemos pretender enseñar C a una persona con Borland C, un IDE del año 1992 (sí, ya es mayor de edad y aunque hace años luz de su salida, se sigue usando). Los alumnos de hoy en día no valoran que hace 16 años flipábamos haciendo un entorno en modo texo, y estábamos acostumbrados a los 80 caracteres x 25 líneas.
Hoy en día ver sólo 80 letras por línea y tan pocas líneas, agobia un poco, eso de no poder cambiar el tipo de letra, el tamaño y elegir entre una gran variedad de colores parece que lo hace todo un tanto inútil, y da la impresion de que nunca se van a utilizar esas cosas.

Soy consciente de que es difícil enseñar a programar prescindiendo del modo texto, ya que la entrada/salida es muy fácil de implementar y de probar, y si estamos empezando es normal que compilemos mil millones de veces sin éxito, y luego tengamos errores de en tiempo de ejecución y ocurran sucesos extraños en nuestros pequeños programas. Por lo que en principio propondría un poco de renovación de software, es cierto que los centros donde se sigue enseñando con Borland C y programas de la época llevan haciéndolo así durante años, pero aunque C va a seguir siendo el mismo, se pueden probar otros IDEs que nos permitan por lo menos hacer el tema un poco más ameno para el alumno. (Dev-C++, Netbeans, Anjuta, o utilizar Kate y compilar a mano, también soy consciente de que éstos también se utilizan en muchos sitios).

Por otra parte, me gustaría hablar sobre la programación amena, es cierto que al principio, tenemos que limitarnos a enseñar las posibles herramientas de las que disponemos, con paciencia, ejemplos, y viendo lo que es capaz de hacer cada herramienta; pero propongo hacer programas libres a corto plazo, es decir, embarcarnos en pequeños proyectos (siempre viendo lo que está dentro de las posibilidades de lo enseñado), con lo que se pueda practicar y seguir aprendiendo. Por ejemplo:

  • Si el alumno está interesado en la economía, se puede hacer un sencillo gestor de economía personal, o un pequeño simulador FOREX (Mercado de divisas) con unos pocos datos generados.
  • Si le gusta la fotografía, podemos empezar (con algunas funciones prediseñadas) a leer y escribir archivos BMP o JPG. Aunque suene complicado símplemente se pueden entregar un .h y un .c y una pequeña documentación de cómo llamar a esas funciones.
  • Está bien el hecho de hacer juegos, pero meternos en un juego directamente es una tarea un tanto complicada, ¿por qué no empezar con las tres en raya? O un juego de sumas y restas, un ahorcado…
  • Está bien empezar también con algo tangible y que todos conocemos, por ejemplo una máquina expendedora, un programa que diga una frase, tipo Facebook, un programa de envío de email (aunque tengamos que entregar algunas funciones extra).

Por esto, siempre empiezo preguntando a mis alumnos por algo que les guste, es verdad que para una clase particular es fácil, un grupo grande es mucho más difícil de llevar, sobre todo si cada uno hace un trabajo diferente, pero se puede guiar por lo que más o menos les interese a todos. Y una cosa importante, que hayamos hecho algo mil veces, no nos quita de currar, y de intentar hacerlo siempre lo mejor que podemos

Propongo unas pistas/consejos, para hacer la programación mucho más emocionante:

  • system() no sólo vale para hacer system(”PAUSE”); para que en Windows no se nos cierre la ventana; podemos ejecutar cualquier cosa, desde un programa que reproduce sonidos, hasta otro para mostrar una imagen, como un navegador web, lo que sea… podemos usarlo para más cosas.
  • Hay librerías fáciles de usar que nos pueden hacer la vida un poco más amena, por ejemplo xosd, con lo que podremos presentar mensajes en pantalla
  • Kdialog, dialog, Xdialog, Zenity… son programas que nos ayudarán a mostrar mensajes en pantalla de forma fácil, usémoslos.
  • Los alumnos se acostumbrarán antes a llamar código de terceros si les proporcionamos las librerías para ello, a lo mejor se ve demasiado complicado, pero obtener un texto de una página web, o que se vea que un mensaje se ha sacado de Google o Twitter, cuando acabas de empezar te anima un poco.
  • No podemos pedir ejercicios muy simples, y de repente un ejercicio extremadamente complicado, que nadie va a ser capaz de resolver, debemos ir progresivamente y pensar que aún no se tiene una mentalidad de desarrollador, tenemos que desarrollarla nosotros, no hacer que abandone.
  • Lo más importante, paciencia, nosotros tenemos años de experiencia y tratamos con personas que no tienen ninguna, y empiezan ahora, tenemos que hacer un seguimiento de lo que están haciendo, aunque es cierto que algún día hay que aprender a buscarse la vida, tenemos que hacerlo poco a poco y escoger el momento.

Casi todo este post, se ha basado en mi experiencia de programación en C/C++, y es verdad que en otros lenguajes (Visual Basic por ejemplo), se realizan otro tipo de ejercicios algo más interactivos y que generalmente gustan más; es cierto que nos deja muchas cosas hechas y no tenemos que recorrer tanto camino para llegar a algo complicado, aunque también es cierto que hay muchos más lenguajes cuya sintaxis se parece más a C que a Basic, por lo que considero uno de los grandes pilares de la programación.

Me gustaría dedicar un par de párrafos al pseudolenguaje o pseudocódigo, es cierto que a muchas personas les ha ayudado en la comprensión del código y el diseño de los algoritmos, así como cuando se quiere decir algo en un idioma diferente al idioma natal, primero lo pensamos en nuestro idioma (más o menos) y luego traducimos en dos o tres iteraciones (primero normal, luego cambiamos verbos de sitio, luego adjetivos…); aunque a la hora de programar, primero tenemos en mente nuestra idea de lo que queremos hacer, y luego diseñamos el algoritmo en pseudolenguaje, tras ello, traducimos línea por línea, en un proceso que puede llegar a durar varios minutos.

Es una expresión en lenguaje natural, que resulta de todo menos natural, y realmente lo veo un tanto pérdida de tiempo; para aprender a programar y a crear algoritmos uno de los procesos fundamentales es compilaro y probarlo hasta que salga, y con el pseudolenguaje no tenemos forma de hacerlo (sí, hay algún compilador, pero prácticamente no se utiliza). ¿Por qué no enseñamos a programar directamente? Así podemos probar lo que hacemos en tiempo real y sobre todo, saber que progresamos. Sí está bien utilizar diagramas de flujo y tratar de entender el algoritmo, hacer esquemas, etc; pero me refiero a intentar aprender una sintaxis nueva que en tres o cuatro meses que suele haber para aprender la materia se puede atragantar y evita que avancemos.

El lenguaje C en primera posición [de nuevo]

Jueves, 29 de Abril de 2010 admin Sin comentarios

tiobeLeo en el Blog de Manuel Pereira que a su vez lee en Mi visión la nueva actualización del índice TIOBE.
En el que el lenguaje C vuelve a estar en primera posición con respecto a Java, que lo ha dejado en segundo lugar durante muchos años, y junto con C++ (aunque PHP sigue de cerca) forman el podio.

Aunque, en muchos centros de enseñanza se considere que C o C++ son lenguajes anticuados, podemos ver que están a la orden del día y de hecho son usados, y mucho. Eso me hace plantearme la forma en la que se enseña C en muchos sitios, algún día comentaré mi opinión de este tema.

El índice TIOBE se genera cada mes y se forma haciendo consultas en los buscadores más populares, por tanto, veremos los lenguajes más populares en este momento, es un punto a considerar a la hora de iniciar un nuevo proyecto de programación; para más información ver la web oficial.

Cosas que damos por hechas en C/C++: int main(int argc, char *argv[])

Martes, 27 de Abril de 2010 admin Sin comentarios

Esta serie de posts está dedicada a tod@s mis alumn@s de clases particulares de programación. Iré añadiendo información de diferentes niveles, dificultades, colores y sabores. Espero que les parezca interesante.

Fue en el primer año de carrera, cuando en la asignatura Fundamentos de Programación y luego en Laboratorio de Programación cuando empezábamos a hacer los primeros programas en C; yo hasta entonces no me había atrevido a programar nada en C, prefería Pascal, al menos llegué con una buena base de programación.

El IDE que utilizábamos (Dev-C++) directamente escribía las primeras líneas de programa:

#include

using namespace std;

int main(int argc, char *argv[])
{

}

Comprendo que en 4 meses hay que enseñar a mucha gente a defenderse con la programación, y no es objeto explicarlo todo detalladamente, aunque cuando alguien preguntaba siempre decían: “Eso hay que ponerlo siempre” es más, nada más empezar el curso, poca gente se va a enterar de qué va el asunto

Bien, empezamos por el include (vale, esto sí que lo contaban mis profesores, porque a veces teníamos que incluir alguna cosa más), incluir funciones y clases hechas por otras personas, y no nos tiene que importar cómo están hechas por dentro.

Un concepto más peliagudo y extenso es el de la línea siguiente: using namespace std (nos metemos a pelear con los espacios de nombres; no quiero ser demasiado extenso, pero imaginemos que hay dos casas en una calle, y cada casa aloja a tres personas: Jose, Virginia y Antonio en la casa A; y Alfonso, Manuela y Jose en la casa B. ¿Cómo distinguimos a Jose de la casa A de Jose de la casa B? Suena a pregunta tonta: Pues casaA::Jose y casaB::Jose, son dos personas diferentes.
Lo mismo podemos hacer en C++, podemos tener funciones que se llaman igual en espacios de nombres (o casas) diferentes, y para referirnos a ellos tenemos que decir primero en qué casa están, para que luego el compilador sepa ubicar a cada uno.
Pero como las pulsaciones de teclado de los desarrolladores son oro (sabemos que tenemos que escribir mucho, si podemos ahorrar algo mejor), y sabemos que muchas de las clases y funciones que vamos a utilizar pertenecen a la librería estándar (o std), es decir, estarán en la mansión std decimos:
using namespace std;
o lo que es lo mismo: si no te digo nada, y llamo a alguien que no encuentras, búscalo en std.

Llegamos al main(), podemos definirlo de muchas formas: muchos utilizan void main() (y gcc se queja), otros int main() (que es perfectamente válido), pero algunas veces se introduce toda esa parafernalia rara de int argc, char *argv[], que es impronunciable, y hace daño a la vista, e incluso si lo intentamos escribir las primeras veces, no nos saldrá igual.
Estos parámetros son para los argumentos del programa, es decir, para los parámetros que introduzcamos en la línea de comandos. Para muchos usuarios que vienen de Windows, les resulta raro entender por qué un programa requiere parámetros, pues bien: si abrimos Mi PC, nos vamos a una carpeta y abrimos un documento, cómo sabe Microsoft Office que hemos abierto este documento? Se lo hemos pasado como parámetro sin querer :)
Ahora bien, y esto requiere un poco de imaginación si estamos empezando:
argc es un número que indica el número de parámetros que tiene el ejecutable:

1
2
3
4
5
6
7
8
#include <stdio.h>

int main(int argc, char *argv)
{
  printf("Me has pasado: %d parámetros\n", argc);

  return 0;
}

Veamos qué pasa al ejecutarlo:

$ ./params
Me has pasado: 1 parámetros
$ ./params param1 param2 “parametro 3″
Me has pasado: 4 parámetros
$ ./params param1 param2 “parametro 3″ “parametro 4″
Me has pasado: 5 parámetros

Nos sobra uno no? Veamos cuáles son esos parámetros:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>

int main(int argc, char *argv[])
{
  int i;

  printf("Me has pasado: %d parámetros\n", argc);

  for (i=0; i<argc; i++)
    {
      printf ("Parámetro %d: %s\n",i,argv[i]);
    }

  return 0;
}

Salida:

$ ./params param1 param2 “parametro 3″ “parametro 4″
Me has pasado: 5 parámetros
Parámetro 0: ./params
Parámetro 1: param1
Parámetro 2: param2
Parámetro 3: parametro 3
Parámetro 4: parametro 4

Vemos que el parámetro 0 es el propio nombre del ejecutable y comprobamos que si cambiamos el nombre del ejecutable, podemos saberlo con ese parámetro 0. Los demás podemos averiguarlos recorriendo la variable argv. argv es un Array de punteros de tipo char o lo que es lo mismo un array de cadenas de caracteres puesto que cada parámetro es una cadena de caracteres.

Ahora la pregunta del millón, si utilizamos un array, para qué necesitamos argc ¿? Podríamos averiguar el número de elementos del mismo. La respuesta es que no, de ese Array desconocemos el número de elementos que tiene (depende de lo que nos pase el usuario), y si tenemos un método para calcular el número de parámetros que tenemos, que seguro que podremos encontrarlo, será mucho más rápido disponer de ese valor (y escribimos menos)

Por qué no debemos utilizar gets()

Lunes, 19 de Abril de 2010 admin 1 comentario

A veces me sorprendo (como profesor de programación) de que en muchos sitios siguen enseñando la función gets() para la entrada de datos desde teclado sin explicar lo que puede pasar.

gets() es una función peligrosa. Imaginemos que escribimos el siguiente programa:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main()
{
  char cadena2[10];
  char cadena1[10];

  printf("c1: %x\nc2: %x\n", cadena1, cadena2);
  gets(cadena1);

  printf("Cadena 2: %s\n", cadena2);
}

Ahora la compilamos, ejecutamos e introducimos un texto de prueba:

$ gcc -o test3 test3.c
/tmp/ccK2P2ON.o: In function `main’:
test3.c:(.text+0×32): warning: the `gets’ function is dangerous and should not be used.
$ ./test3
c1: bfc81580
c2: bfc8158a
Escribo un texto de mas de 10 caracteres
Cadena 2: texto de mas de 10 caracteres
Violación de segmento

Ya de primeras nos avisa gcc de que la función no debería ser usada, a lo que muchos se sorprenden.
Luego, en la ejecución, tal como se indica en el código fuente, mostramos las direcciónes de memoria donde se ubicarán cadena1 (c1) y cadena2 (c2).
Tras ello escribimos: “Escribo un texto de mas de 10 caracteres“, que se introducirá en la variable cadena1 y mostramos la variable cadena2, que muestra parte de lo que escribimos antes… y además de regalo una violación de segmento, porque nuestro programa ha escrito en una región de memoria que no le pertenecía.

Todo esto sucede porque gets() no tiene ninguna manera de saber cuánto puede escribir, y por ello, escribirá siempre en memoria, todos los caracteres que introduzcamos (para un ejemplo rápido vale, pero ya), de todas formas no debemos darlo todo por perdido, tenemos una excelente función, muy parecida fgets(), aunque tiene más parámetros:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

#define MAX_LON_CADENA 10

int main()
{
  char cadena2[MAX_LON_CADENA];
  char cadena1[MAX_LON_CADENA];

  printf("c1: %x\nc2: %x\n", cadena1, cadena2);

/*   gets(cadena1); */
  fgets(cadena1, MAX_LON_CADENA, stdin);
  printf("Cadena 1: \"%s\"\n", cadena1);
  printf("Cadena 2: %s\n", cadena2);
}
  1. El primer parámetro de fgets() es dónde vamos a almacenar lo leído.
  2. El segundo parámetro es cuántos caracteres leeremos como máximo (incluyendo el terminador de cadena). Y si hemos definido un valor máximo para la cadena, podemos utilizar éste.
  3. El tercero es de dónde lo leemos, y si queremos hacerlo desde teclado, usaremos stdin
Categories: C/C++ Tags: , , , , ,

Visita otras webs de la red

Easy AdSense by Unreal