Archivo

Entradas Etiquetadas ‘c’

Creando un servidor que acepte múltiples clientes simultáneos en C

Domingo, 6 de Febrero de 2011 Gaspar Fernández Sin comentarios

Para hacer una prueba de esto, crearemos un servidor al que nos podremos conectar por telnet y pedir cierta información a través de comandos. El ejemplo soporta los siguientes comandos (en mayúsculas):

  • DATE: Pide la fecha al servidor
  • TIME: Pide la hora al servidor
  • HOLA: Saluda y me dice mi IP
  • EXIT: Cierra el cliente actual
  • CERRAR: Cierra el servidor.
  • Cualquier otra cosa se manda repetida (ECHO)
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
/**
*************************************************************
* @file servtcp.c
* @brief Breve descripción
* Ejemplo de un cliente TCP usando threads
*
*
* @author Gaspar Fernández <blakeyed@totaki.com>
* @version 0.1Beta
* @date 13 ene 2011
* Historial de cambios:
*   20110113 - Versión inicial
*
*
*************************************************************/


#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <netinet/in.h>
#include <resolv.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>

/** Puerto  */
#define PORT       7000

/** Número máximo de hijos */
#define MAX_CHILDS 3

/** Longitud del buffer  */
#define BUFFERSIZE 512

int AtiendeCliente(int socket, struct sockaddr_in addr);
int DemasiadosClientes(int socket, struct sockaddr_in addr);
void error(int code, char *err);
void reloj(int loop);

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

    int socket_host;
    struct sockaddr_in client_addr;
    struct sockaddr_in my_addr;
    struct timeval tv;      /* Para el timeout del accept */
    socklen_t size_addr = 0;
    int socket_client;
    fd_set rfds;        /* Conjunto de descriptores a vigilar */
    int childcount=0;
    int exitcode;

    int childpid;
    int pidstatus;

    int activated=1;
    int loop=0;
    socket_host = socket(AF_INET, SOCK_STREAM, 0);
    if(socket_host == -1)
      error(1, "No puedo inicializar el socket");
   
    my_addr.sin_family = AF_INET ;
    my_addr.sin_port = htons(PORT);
    my_addr.sin_addr.s_addr = INADDR_ANY ;

   
    if( bind( socket_host, (struct sockaddr*)&my_addr, sizeof(my_addr)) == -1 )
      error(2, "El puerto está en uso"); /* Error al hacer el bind() */

    if(listen( socket_host, 10) == -1 )
      error(3, "No puedo escuchar en el puerto especificado");

    size_addr = sizeof(struct sockaddr_in);


    while(activated)
      {
    reloj(loop);
    /* select() se carga el valor de rfds */
    FD_ZERO(&rfds);
    FD_SET(socket_host, &rfds);

    /* select() se carga el valor de tv */
    tv.tv_sec = 0;
    tv.tv_usec = 500000;    /* Tiempo de espera */
   
    if (select(socket_host+1, &rfds, NULL, NULL, &tv))
      {
        if((socket_client = accept( socket_host, (struct sockaddr*)&client_addr, &size_addr))!= -1)
          {
        loop=-1;        /* Para reiniciar el mensaje de Esperando conexión... */
        printf("\nSe ha conectado %s por su puerto %d\n", inet_ntoa(client_addr.sin_addr), client_addr.sin_port);
        switch ( childpid=fork() )
          {
          case -1:  /* Error */
            error(4, "No se puede crear el proceso hijo");
            break;
          case 0:   /* Somos proceso hijo */
            if (childcount<MAX_CHILDS)
              exitcode=AtiendeCliente(socket_client, client_addr);
            else
              exitcode=DemasiadosClientes(socket_client, client_addr);

            exit(exitcode); /* Código de salida */
          default:  /* Somos proceso padre */
            childcount++; /* Acabamos de tener un hijo */
            close(socket_client); /* Nuestro hijo se las apaña con el cliente que
                         entró, para nosotros ya no existe. */

            break;
          }
          }
        else
          fprintf(stderr, "ERROR AL ACEPTAR LA CONEXIÓN\n");
      }

    /* Miramos si se ha cerrado algún hijo últimamente */
    childpid=waitpid(0, &pidstatus, WNOHANG);
    if (childpid>0)
      {
        childcount--;   /* Se acaba de morir un hijo */

        /* Muchas veces nos dará 0 si no se ha muerto ningún hijo, o -1 si no tenemos hijos
         con errno=10 (No child process). Así nos quitamos esos mensajes*/


        if (WIFEXITED(pidstatus))
          {

        /* Tal vez querremos mirar algo cuando se ha cerrado un hijo correctamente */
        if (WEXITSTATUS(pidstatus)==99)
          {
            printf("\nSe ha pedido el cierre del programa\n");
            activated=0;
          }
          }
      }
    loop++;
    }

    close(socket_host);

    return 0;
}

    /* No usamos addr, pero lo dejamos para el futuro */
int DemasiadosClientes(int socket, struct sockaddr_in addr)
{
    char buffer[BUFFERSIZE];
    int bytecount;

    memset(buffer, 0, BUFFERSIZE);
   
    sprintf(buffer, "Demasiados clientes conectados. Por favor, espere unos minutos\n");

    if((bytecount = send(socket, buffer, strlen(buffer), 0))== -1)
      error(6, "No puedo enviar información");
   
    close(socket);

    return 0;
}

int AtiendeCliente(int socket, struct sockaddr_in addr)
{

    char buffer[BUFFERSIZE];
    char aux[BUFFERSIZE];
    int bytecount;
    int fin=0;
    int code=0;         /* Código de salida por defecto */
    time_t t;
    struct tm *tmp;

    while (!fin)
      {

    memset(buffer, 0, BUFFERSIZE);
    if((bytecount = recv(socket, buffer, BUFFERSIZE, 0))== -1)
      error(5, "No puedo recibir información");

    /* Evaluamos los comandos */
    /* El sistema de gestión de comandos es muy rudimentario, pero nos vale */
    /* Comando TIME - Da la hora */
    if (strncmp(buffer, "TIME", 4)==0)
      {
        memset(buffer, 0, BUFFERSIZE);

        t = time(NULL);
        tmp = localtime(&t);

        strftime(buffer, BUFFERSIZE, "Son las %H:%M:%S\n", tmp);
      }
    /* Comando DATE - Da la fecha */
    else if (strncmp(buffer, "DATE", 4)==0)
      {
        memset(buffer, 0, BUFFERSIZE);

        t = time(NULL);
        tmp = localtime(&t);

        strftime(buffer, BUFFERSIZE, "Hoy es %d/%m/%Y\n", tmp);
      }
    /* Comando HOLA - Saluda y dice la IP */
    else if (strncmp(buffer, "HOLA", 4)==0)
      {
        memset(buffer, 0, BUFFERSIZE);
        sprintf(buffer, "Hola %s, ¿cómo estás?\n", inet_ntoa(addr.sin_addr));
      }
    /* Comando EXIT - Cierra la conexión actual */
    else if (strncmp(buffer, "EXIT", 4)==0)
      {
        memset(buffer, 0, BUFFERSIZE);
        sprintf(buffer, "Hasta luego. Vuelve pronto %s\n", inet_ntoa(addr.sin_addr));
        fin=1;
      }
    /* Comando CERRAR - Cierra el servidor */
    else if (strncmp(buffer, "CERRAR", 6)==0)
      {
        memset(buffer, 0, BUFFERSIZE);
        sprintf(buffer, "Adiós. Cierro el servidor\n");
        fin=1;
        code=99;        /* Salir del programa */
      }
    else
      {    
        sprintf(aux, "ECHO: %s\n", buffer);
        strcpy(buffer, aux);
      }

    if((bytecount = send(socket, buffer, strlen(buffer), 0))== -1)
      error(6, "No puedo enviar información");
      }

    close(socket);
    return code;
}

void reloj(int loop)
{
  if (loop==0)
    printf("[SERVIDOR] Esperando conexión  ");

  printf("\033[1D");        /* Introducimos código ANSI para retroceder 2 caracteres */
  switch (loop%4)
    {
    case 0: printf("|"); break;
    case 1: printf("/"); break;
    case 2: printf("-"); break;
    case 3: printf("\\"); break;
    default:            /* No debemos estar aquí */
      break;
    }

  fflush(stdout);       /* Actualizamos la pantalla */
}

void error(int code, char *err)
{
  char *msg=(char*)malloc(strlen(err)+14);
  sprintf(msg, "
Error %d: %s\n", code, err);
  fprintf(stderr, msg);
  exit(1);
}

La clave para poder atender varios clientes simultáneos es bifurcar (fork()) el programa cuando se conecta un nuevo cliente, de esta forma, el diálogo con el cliente se lleva a cabo desde un nuevo proceso mientras que el proceso padre (el primero) sigue escuchando en el puerto esperando conexiones.

Utilizamos select() para no detener la ejecución del programa cuando estamos esperando conexiones; tal vez el programa tenga que hacer algo mientras nadie está conectándose (y es que el programa principal permanecerá aquí la mayor parte del tiempo), por ejemplo lo que hacemos aquí es controlar los hijos que se mueren y ver cómo se han muerto. select() establece un tiempo máximo de espera (500000 microseguntos en este caso, ver la variable tv); si pasado ese tiempo el descriptor socket_host no ha cambiado, seguiremos la ejecución normal del programa, pero si durante ese tiempo cambia ejecutaremos accept().
La función select() funciona con grupos de descriptores y por eso utilizamos las macros FD_ZERO() para limpiar el grupo y FD_SET() para añadir un descriptor al grupo.
El primer parámetro de select() debe ser el descriptor de fichero más grande a comprobar+1; como sólo tenemos un descriptor (socket_host) debemos añadir este al grupo, y pasarle a select en su primer parámetro socket_host+1.

Cuando llega un cliente, como dijimos antes, se abrirá un proceso hijo que lo procesará con una de estas dos funciones: AtiendeCliente() o DemasiadosClientes() dependiendo de si el número máximo de clientes ha sido alcanzado o no. Además, la salida del proceso hijo se obtendrá de la salida de estas funciones, siendo el valor de salida clave 99 el que necesita el proceso padre para cerrar terminar el proceso servidor, es decir, si un hijo sale con el valor de retorno 99 provocará el cierre del servidor. Este valor se maneja debajo del switch(); primero con waitpid() esperamos la muerte de alguno de nuestros hijos aunque con el modificador WNOHANG provocamos que si no se ha muerto ninguno, no vamos a esperar, el programa seguirá su ejecución.

Desde AtiendeCliente() veremos un método muy rudimentario para implementar los comandos, aunque para un ejemplo y 4 comandos nos vale perfectamente.

DemasiadosClientes() devolverá únicamente un mensaje al cliente que se acaba de conectar avisando de que hay demasiados clientes conectados.

Reloj() es un pequeño reloj en modo texto que va girando, a cada iteración del bucle principal, está aquí para que nosotros veamos que el servidor está haciendo algo. Utiliza códigos ANSI, para volver a escribirse en el lugar que estaba, y fflush() para volcar el texto a pantalla antes de nada (puede ser que se acumulen varios mensajes y entonces el dibujado sería incorrecto).

Para probar este programa, debemos compilar con:

$ gcc -o servtcp servtcp.c

y ejecutarlo; y, en un terminal aparte, conectar con telnet, Si, por ejemplo lo queremos hacer todo desde el mismo ordenador; si el servidor está en un ordenador diferente y nos queremos conectar a él cambiamos “localhost” por la IP o el host del ordenador donde se esté ejecutando el cliente:

$ telnet localhost 7000

Preincremento y postincremento (x++ != ++x)

Jueves, 27 de Enero de 2011 Gaspar Fernández 2 comentarios

Hace unos días un alumno de clases particulares me preguntó la diferencia entre estos dos; me pareció una pregunta interesante ya que los únicos usos que había visto eran como única sentencia:

a++;
++a;

En este uso no hay diferencia, puesto que hagamos las cosas en el orden que las hagamos el resultado será igual; aunque en este ejemplo tampoco se tiene clara la idea del orden de las operaciones. Pero veamos otro ejemplo:

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

int main(int argc, char *argv[])
{
  int a=10;
  int b;

  b=a++;
  printf("a=%d\nb=%d\n", a, b);
}

Vemos que estamos realizando un postincremento de a, o lo que es lo mismo, incrementamos la variable a después de las demás operaciones (asignar a b el valor de a).

Por lo tanto, si a vale 10, b tomará el valor 10 y tras eso a incrementará 1 pasando a valer 11.
Al ver la salida podemos observar:

$ ./prepost
a=11
b=10

En cambio podemos crear este programa también (igual que el anterior cambiando a++ por ++a),

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

int main(int argc, char *argv[])
{
  int a=10;
  int b;

  b=++a;
  printf("a=%d\nb=%d\n", a, b);
}

Cuya salida es:

$ ./prepost2
a=11
b=11

En este caso, preincrementamos a. Antes de cualquier operación incrementamos la variable. Por tanto, si a vale 10, incrementamos esa variable pasando a valer 11 y luego asignamos ese valor a b.

La gran ventaja de esto es que con la experiencia suficiente nos ayuda a reducir el código que escribimos y optimizar la velocidad de nuestros programas. Además, a la hora de crear nuestros programas podremos ahorrar mucho código, sobre todo a la hora de lidiar con punteros de cadenas o imágenes donde, en ocasiones, podemos realizar tareas de forma más fácil.

Dejo algunos ejemplos más, aunque sencillos del uso de estos operadores:

Mareando un poco la perdiz

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

int main(int argc, char *argv[])
{
  int a=10;
  int b=5;

  b=a++ + ++b;
  printf("a=%d\nb=%d\n", a, b);
}

Que da como salida:

$ ./prepost3
a=11
b=16

En este caso… cogemos a (que vale 10), luego incrementamos b (que valía 5 y pasa a valer 6), sumamos los dos valores (que da en total 16) y luego incrementamos a (que pasa a valer 11).

En un do-while

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

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

  a=0;
  do
    printf("Bucle a=%d\n", a);
  while (++a<5);
}

Cuya salida es:

$ ./preposw
Bucle a=0
Bucle a=1
Bucle a=2
Bucle a=3
Bucle a=4

En este caso, al finalizar cada iteración, incrementamos a y luego miramos si es menor que 5; por lo que, en la última vuelta del bucle, si a vale 4, incrementaremos su valor (pasando a ser 5) y luego veremos que ya no es menor que 5 por lo que saldremos del bucle.

En contraposición podemos hacer:

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

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

  a=0;
  do
    printf("Bucle a=%d\n", a);
  while (a++<5);
}

Cuya salida es:

$ ./preposw
Bucle a=0
Bucle a=1
Bucle a=2
Bucle a=3
Bucle a=4
Bucle a=5

En este caso, al verificar que a < 5 antes de incrementar a. Cuando a vale 4, haremos la comparación, cuando todavía es menor que 5 e incrementaremos. Daremos otra vuelta más con a valiendo 5, donde, en la siguiente comparación (a<5) ahora sí que saldrá del bucle, aunque si verificáramos el valor de a tras salir del bucle sería 6.

Contar caracteres a mano

Un ejemplo tonto más, para contar las letras de una cadena:

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

int main(int argc, char *argv[])
{
  int i=0;
  char *str="HOLA";

  // Con esto vale
  while (str[++i]!='\0');

  printf("Longitud: %d\n", i);
  }

Salida:

$ ./prepos
Longitud: 4

En este caso, dentro del while, primero incrementamos i, luego comparamos str[i] con ‘\0′ y si efectivamente tiene ese valor saldremos del bucle. No se crea un bucle infinito porque el incremento se produce (aunque sea en la misma sentencia de la comparación).
Hay que tener cuidado con este ejemplo, ya que si la cadena está vacía (es decir, que el primer carácter que encontremos sea \0) tal vez este método produzca resultados inesperados, ya que nunca llegamos a comparar el carácter 0 (incrementamos antes de comparar ese carácter). Una posible solución es inicializar i a -1.

Nota: Todo lo comentado aquí vale tanto con preincremento/postincremento como con predecremento/postdecremento (–a, a–), ya que son operaciones muy similares.

Número variable de argumentos en una función de C

Lunes, 24 de Enero de 2011 Gaspar Fernández 3 comentarios

Si habéis programado en C, seguramente la primera función que aprendisteis a utilizar era printf() ; y en todo el tiempo que pasasteis estudiando C no encontrasteis una función similar. Y es que esta función puede tener un número indeterminado de parámetros y es capaz de analizarlos todos.

Y es que quitando printf(), scanf() y derivadas de estas, la necesidad de una función con múltiples parámetros casi no se nos presenta. Aunque tal vez, alguna vez necesitemos crear una función con parámetros opcionales, es decir, si están presentes se tomarán y si no, se tomará un valor por defecto.

Esto lo podemos hacer con la biblioteca stdarg.h la cual nos proporciona macros para hacer esto posible.

Echemos un vistazo al siguiente código:

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

void poneargs(int nargs, ...)
{
  va_list ap;
  int i;
  int num;

  va_start(ap, nargs);
  for (i =0; i<nargs; i++)
    {
      num=va_arg(ap, int);
      printf("Argumento %d = %d\n", i, num);
    }
  va_end(ap);
  printf("\n");
}

int main(int argc, char *argv[])
{
  poneargs(5, 6, 77, 445, 34, 23, 12, 43, 32);
  poneargs(2, 23, 94);
  poneargs(0);

  return EXIT_SUCCESS;
}

Aquí vemos cómo la función poneargs() nos coloca los valores de los argumentos que le hemos pasado, siendo el primero de ellos el número total de argumentos que tenemos.
Con esto, hay que dejar claro que no tenemos forma de contar los argumentos que pasamos (igual que printf, podíamos pasar tantos argumentos como %[x] tuviéramos en la cadena de formato). Así que las técnicas para llegar al límite de los comandos son:

  • Poner un argumento indicando el número de argumentos a colocar.
  • Poner un argumento con un valor fijo al final que nos indique que hemos llegado al último parámetro.
  • Poner códigos con los argumentos que tenemos que leer. Idea de printf()

Lo que sí que tenemos que tener en cuenta es que, como estamos acostumbrados cada argumento tiene un tipo fijo, y se lo tenemos que decir a va_arg() en su segundo argumento; y va_arg() nos va a devolver una variable del mismo tipo que le dijimos (éste es el valor del argumento).

En el siguiente ejemplo creamos una función new_cliente() que puede tener hasta cuatro argumentos de tipo cadena de caracteres. De hecho son opcionales. Cuando creamos un cliente en un programa real puede que no tengamos todos sus datos y puede que algunos sean opcionales:

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

void new_cliente(int nargs, ...)
{
  va_list ap;

  char *valor;

  va_start(ap, nargs);

  switch (nargs)
    {
    case 4:
      valor=va_arg(ap, char*);
      printf("Twitter: %s\n", valor);      
    case 3:
      valor=va_arg(ap, char*);
      printf("URL: %s\n", valor);      
    case 2:
      valor=va_arg(ap, char*);
      printf("Blog: %s\n", valor);      
    case 1:
      valor=va_arg(ap, char*);
      printf("Nombre: %s\n", valor);
      break;
    default:
      printf("Número de parámetros incorrecto\n");
    }
 
  va_end(ap);
  printf("\n");
}

int main(int argc, char *argv[])
{
  printf("Un parámetro:\n");
  new_cliente(1, "Gaspy");

  printf("Dos parámetros:\n");
  new_cliente(2, "Gaspy", "Poesía Binaria");

  printf("Tres parámetros:\n");
  new_cliente(3, "Gaspy", "Poesía Binaria", "http://totaki.com/poesiabinaria/");

  printf("Cuatro parámetros:\n");
  new_cliente(4, "Gaspy", "Poesía Binaria", "http://totaki.com/poesiabinaria/", "http://twitter.com/blakeyed");

  return EXIT_SUCCESS;
}

Esto también podría valernos como una pequeña simulación de sobrecarga de funciones (estilo C++) aunque en C no cumpliría la tarea de hacer más legible el código ya que en la misma función tendríamos que incluir las opciones de los argumentos.

Programar en C++ puede llegar a ser frustrante

Miércoles, 20 de Octubre de 2010 Gaspar Fernández Sin comentarios

cuchilloSiempre se dice que una de las reglas de la programación es “escribir poco”, tenemos que aunque en ciertos lenguajes, tenemos que escribir dos veces las cosas. Por ejemplo, para un sencillo hola mundo con clases en C++ (se puede hacer todo en el mismo archivo, pero queremos el código bien organizado):
[ hwclass.h ]

1
2
3
4
5
6
7
8
class HolaMundo
{
 public:
  HolaMundo();
  ~HolaMundo();

  void coutVersion();
};

[ hwclass.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include "hwclass.h"

using namespace std;

HolaMundo::HolaMundo()
{
  cout<<"Hola Mundo!!"<<endl;
}

HolaMundo::~HolaMundo()
{
  cout<<"Adiós Mundo!!"<<endl;
}

void HolaMundo::coutVersion()
{
  cout<<"HolaMundo Versión 1.0"<<endl;
}

[ main.cpp ]

1
2
3
4
5
6
7
#include "hwclass.h"

int main()
{
  HolaMundo hm;
  hm.coutVersion();
}

Si queremos compilar el proyecto podemos hacer:

$ g++ -o holamundo main.cpp hwclass.cpp

Bueno, a lo que voy, como veis en hwclass.cpp y hwclass.h se repiten algunas cosas, el nombre de la clase, y el nombre de los métodos cuando vamos a definir el código de cada uno… cuando en el archivo .h se definen decenas de métodos resulta muy repetitivo teclear una y otra vez lo mismo y más aún mantener el orden de los métodos.

Para ello quiero presentar dos aplicaciones:

lzz

Podemos descargarlo desde aquí, tiene multitud de opciones y es bastante potente. Aunque yo veo un gran defecto, tenemos que crear un fichero lzz, que luego el programa lo convertirá a cpp y hpp extrayendo la información según corresponda. Por otra parte, para generar el ejecutable, necesitamos el binario de lzz (como si de un compilador se tratara)

stubgen

Lo encontramos aquí, y aunque le falta alguna que otra cosilla como personalización de la documentación, es bastante potente y nos permite trabajar normalmente con nuestro .h y generar el .cpp con todos los métodos empezados, justo para que nosotros completemos el código.

Foto: ~Brenda_Starr~ (Flickr)
¿Por qué un cuchillo? Un stub en programación es un trozo de código incompleto (tal vez simula lo que va a hacer, o sólo es una pincelada)… stub se parece a stab (puñalada).

Interfaces Gráficos en Linux con gtkmm 1 [Hola Mundo]

Jueves, 14 de Octubre de 2010 Gaspar Fernández 4 comentarios

ventanaAunque a veces, cuando nos dedicamos a programar en Linux, recurrimos a la consola (ya que muchos de nosotros tenemos siempre una abierta), a veces es interesante crear un Interfaz Gráfico de Usuario (en inglés GUI, Graphical User Interface), para ello, si no queremos complicarnos demasiado tenemos dos opciones: Gtk+ y Qt.

Bien, vamos con Gtk+, está muy extendido y escrita en C, aunque aquí hablaré de una interfaz de Gtk+ para C++ llamada gtkmm, que nos proporciona las clases necesarias para jugar con la potencia de Gtk+ de una forma un poco más amigable (ya que podemos crear un botón, una etiqueta o una ventana como objetos de C++, hará todo un poco más intuitivo).

Una primera prueba que me gusta hacer es poner todo el código junto, sin mucha organización, para crear un “hola mundo”, para ver más o menos a qué nos enfrentamos y cómo, si vamos a tener que escribir mucho y esas cosas:

[ holamundo.cpp]

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 <gtkmm.h>

using namespace Gtk;

int main(int argc, char *argv[])
{
  // Inicialización GTK
  Main entorno(argc, argv);

  // Declaración de objetos
  Window ventana;
  Label etiqueta;

  // Características de la ventana
  ventana.set_title("Hola Mundo!");
  ventana.set_border_width(5);
  ventana.set_default_size(400, 200);

  // Etiqueta
  etiqueta.set_text("Hola Mundo!!");
  ventana.add(etiqueta);

  // Mostrar todo
  ventana.show_all_children();

  // Ejecutar GUI
  entorno.run(ventana);

  return 0;
}

Para compilar debemos hacer lo siguiente:

$ g++ -o holamundo holamundo.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Eso sí, debemos sustituir el 2.4 por la versión de gtkmm que tengamos instalada. Para ver cuál es, podemos hacer lo siguiente:

$ pkg-config –list-all | grep gtkmm

Bien, una vez hecho esto, vamos a intentar adquirir una metodología de programación que nos permita reaprovechar código, y tenerlo todo organizado, vamos a crear archivos .h y .cpp:

[ hworld.h ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef _HWORLD_H
#define _HWORLD_H

#include <gtkmm.h>

using namespace Gtk;

class HolaMundo : public Window
{
 public:
  HolaMundo();
  ~HolaMundo();

  Label etiqueta;
};

#endif

[ hworld.cpp ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "hworld.h"

HolaMundo::HolaMundo()
{
  this->set_title("Hola Mundo!");
  this->set_border_width(5);
  this->set_default_size(400, 200);

  etiqueta.set_text("Hola Mundo!!");
  this->add(etiqueta);

  this->show_all_children();
}

HolaMundo::~HolaMundo()
{
}

[ hellomain.cpp ]

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

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

  // Creamos la ventana
  HolaMundo hmundo;

  // Ejecutamos
  entorno.run(hmundo);

  return 0;
}

Ahora compilamos con:

$ g++ -o hellom hellomain.cpp hworld.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Hará lo mismo, ¡vaya tontería! pero lo tendremos todo mucho más organizado, hemos introducido todo lo referente a la ventana en una misma clase.

Una pequeña nota: escribo arriba using namespace Gtk para no repetir todo el rato Gtk::[Tipo] , Gtk::[Método], ya que todo lo que estoy utilizando hasta ahora pertenece al espacio Gtk.

Ahora vamos a hacer algo más complicado, vamos a utilizar algunos elementos más y vamos a introducir algún botón, para que se desempeñe alguna acción:
[ hworld.h ]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef _HWORLD_H
#define _HWORLD_H

#include <gtkmm.h>

using namespace Gtk;

class HolaMundo : public Window
{
 public:
  HolaMundo();
  ~HolaMundo();


  void click_salir();
  void click_mensaje();

  VBox cajaV;
  HButtonBox botonera;
  Label etiqueta;
  Button *botonSalir, *botonMensaje;
};

#endif

Creamos un objeto derivado de Gtk::Window en el que incluimos los elementos que habrá en la ventana:

  • VBox es una caja con divisiones horizontales, es como si cortáramos horizontalmente en trozos a lo largo de la vertical
  • HButtonBox es una caja para poner botones a lo largo de la horizontal
  • Button es un botón. Pondremos dos, uno para salir y otro para mostrar un mensaje emergente
  • Label es nuestra etiqueta de “Hola Mundo”

Definimos dos métodos para los eventos click_salir() y click_mensaje().

Los botones los he declarado como punteros para demostrar cómo podemos trabajar con objetos de este tipo.

[ hworld.cpp ]

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
#include "hworld.h"
#include <sstream>      // Para hacer string<<int

HolaMundo::HolaMundo()
{
  // Configuro la ventana
  set_title("Hola Mundo!");
  set_border_width(5);
  set_default_size(400, 200);

  // Configuro la etiqueta
  etiqueta.set_text("Hola Mundo!!");

  // Configuro la botonera
  botonSalir=new Button(Stock::QUIT);
  botonMensaje=new Button("Mensaje");

  botonSalir->signal_clicked().connect(sigc::mem_fun(*this, &HolaMundo::click_salir));
  botonMensaje->signal_clicked().connect(sigc::mem_fun(*this, &HolaMundo::click_mensaje));

  botonera.pack_start(*botonSalir, PACK_SHRINK);
  botonera.pack_start(*botonMensaje, PACK_SHRINK);

  // Configuro la división
  add(cajaV);
  cajaV.pack_start(etiqueta, PACK_EXPAND_WIDGET); // Esto ocupará el máximo tamaño
  cajaV.pack_start(botonera, PACK_SHRINK);

  show_all_children();
}

HolaMundo::~HolaMundo()
{
  delete botonSalir;
  delete botonMensaje;
}

void HolaMundo::click_mensaje()
{
  int res;
  std::stringstream ss;
  MessageDialog dialog("Esto es un mensaje emergente");
  res=dialog.run();

  if (res==RESPONSE_OK)
    etiqueta.set_text("Has dicho OK");
  else if (res==RESPONSE_DELETE_EVENT)
    etiqueta.set_text("Has cancelado el diálogo");
  else
    {
      ss<<"Has respondido otra cosa: "<<res;
      etiqueta.set_text(ss.str().data());
    }
}

void HolaMundo::click_salir()
{
  hide();
}

Vemos cómo en el constructor hemos configurado todos los elementos de la ventana, vemos cómo en la botonera y la cajaV, para añadir objetos se ha utilizado el método pack_start, las propiedades Gtk::PACK_SHRINK (encogerá el objeto contenedor) y Gtk::PACK_EXPAND_WIDGET (lo expanderá)

Pero la cajaV la añadiremos a la ventana con el método add.

Los botones los creamos con new (recordamos que eran punteros), tenemos muchos botones de Stock predefinidos, y podemos crear uno con esta propiedad (todos están en Gtk::Stock::XXXXX), aunque también podemos crearlos directamente con un texto.

Para conectar los eventos (de los botones por ejemplo) usamos connect(), como parámetro podemos incluir una función (con sigc::ptr_fun(&funcion)) o un método de clase (con sigc::mem_fun(*objeto, &Clase::metodo)), será la función o método que llamaremos cuando se genere el evento (en este ejemplo será el click sobre el botón).

Además, incluyo una caja de diálogo en la que luego distinguimos si se cierra por el botón aceptar (Gtk::RESPONSE_OK), o cerrando la ventana (Gtk::RESPONSE_DELETE_EVENT) modificando el texto del Label.

[ hellomain.cpp ]

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

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

  // Creamos la ventana
  HolaMundo hmundo;

  // Ejecutamos
  entorno.run(hmundo);

  return 0;
}

Como vemos, el programa principal es exactamente igual que el anterior, como la compilación del proyecto:

g++ -o hellom hellomain.cpp hworld.cpp `pkg-config –cflags gtkmm-2.4` `pkg-config –libs gtkmm-2.4`

Compilando y linkando a mano con GCC

Sábado, 11 de Septiembre de 2010 Gaspar Fernández Sin comentarios

GCC compila y linka automáticamente, nos devuelve un ejecutable directamente:

$ gcc -o ejecutable fuente.c

pero en realidad, aparte de pre-procesar y compilar, enlaza algunas bibliotecas del sistema para que nuestro ejecutable funcione bien. Sólo por jugar un poco, veamos, más o menos (depende del sistema) cómo obtener el ejecutable a mano, es decir, compilamos por un lado, y linkamos por otro.

Primero, creamos un programa sencillo, un hello.c que contenga lo siguiente:

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

int main(int argc, char *argv[])
{
   printf("Hola mundo!!\n");

   return 0;
}

Ahora obtenemos el fichero objeto, nuestro código compilado de la siguiente manera:

$ gcc -c hello.c

Veremos que se ha generado el fichero hello.o ; éste fichero aún no lo podremos ejecutar, ya que no tenemos las librerías del sistema necesarias para ello.

Ahora obtendremos el fichero ejecutable con el linkador:

ld -o hello -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/crtbegin.o -L/usr/lib/gcc/i686-pc-linux-gnu/4.2.3 hello.o /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/crtend.o /usr/lib/crtn.o -lc

Debemos cambiar /usr/lib/gcc/i686-pc-linux-gnu/4.2.3 por la ruta correspondiente en nuestro equipo; los archivos necesarios se encontrarán en en el directorio de GCC, tras seleccionar la arquitectura y SO correspondiente (en mi caso i686-pc-linux-gnu), y dentro de ese directorio encontraremos otro correspondiente a la versión de GCC (en mi caso la 4.2.3).

Más o menos lo que vemos en el proceso de linkado es lo siguiente:

  • -o hello : es el nombre del ejecutable
  • -m elf_i386 : es el tipo de ejecutable y la arquitectura. También puede ser elf_x86_64 si tenemos las bibliotecas necesarias.
  • -dynamic-linker /lib/ld-linux.so.2: es el cargador dinámico
  • /usr/lib/crt1.o /usr/lib/crti.o: son dos objetos encargados de la inicialización de memoria del programa
  • /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/crtbegin.o : indica cómo tiene que empezar el programa.
  • /usr/lib/gcc/i686-pc-linux-gnu/4.2.3/crtend.o : indica cómo tiene que terminar el programa
  • hello.o : es el objeto de nuestro programa.
  • -L/usr/lib/gcc/i686-pc-linux-gnu/4.2.3 : hacemos que el linkador busque bibliotecas en el directorio indicado.
  • /usr/lib/crtn.o : realiza tareas de limpieza de memoria.
  • -lc: incluye la librería de C estándar.

Los archivos que empiezan por crt* normalmente se encargan de poner los datos que nos entran en su sitio; parece que el hecho de coger los parámetros que nos pasan por la línea de comandos y colocarlos en memoria para que nuestro programa los lea es tarea fácil, y que luego nuestro programa devuelva una respuesta; y además, los programas no se ejecutan siempre en la misma posición de memoria, depende de la memoria que haya libre, eso lo suele controlar el S.O. pero nuestros ejecutables tienen que estar preparados… en definitiva, hay muchos agentes involucrados.

Una vez hecho esto, tenemos nuestro hola mundo preparado para ser ejecutado.

Debo agradecer a Carlos, que me dio la idea de escribir este artículo.

Enseñando a programar

Viernes, 7 de Mayo de 2010 Gaspar Fernández 5 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.

Por qué no debemos utilizar gets()

Lunes, 19 de Abril de 2010 Gaspar Fernández 3 comentarios

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: , , , , ,

Funciones con nombres raros, y cortos (PHP, Javascript ,C)

Viernes, 12 de Marzo de 2010 Gaspar Fernández Sin comentarios

El objetivo de la programación, además de escribir poco (ya sabemos que todos los que programamos no queremos dejarnos las manos escribiendo), es hacer nuestro código mantenible, y para eso es necesario que nuestras funciones y métodos tengan nombres descriptivos.
Es decir, podemos definir 20 funciones: a(), b(), c(),… y mientras hacemos el programa tener un mapa mental de todas ellas, y utilizarlas, pero dentro de 6 meses, cuando vayamos a hacer la versión 2.0 de nuestro programa, ¿nos acordaremos de para qué valía cada función? Seguramente no; pero siempre que hacemos un for, lo más normal es escribir: “for (i=0;….”, “for (j=0;……)”, etc, eso sí que sale automático :)

Podemos, por ejemplo, tener nombres descriptivos, pero tal vez sean demasiado largos y muchos de ellos empezarán por las mismas letras, lo cual podemos considerarlo improductivo.

Si habéis curioseado prototype o JQuery, veréis cómo hay una llamada que se repite: $() y ella sola, con lo corta que es, hace maravillas para obtener objetos en Javascript. Los que hayan curioseado gettext recordarán la función _(), que sirve para traducir un mensaje. Estas funciones tienen en común que no vamos a parar de llamarlas en nuestros programas, sabemos lo que hacen y necesitamos una forma corta de llamarlas, ya que si desde nuestro programa llamamos a la función gettext() unas 2000 veces, estaríamos escribiendo 12000 caracteres de más, y en Javascript, si por ejemplo queremos llamar a document.getElementById(), con $() (no es lo único que hace) unas 60 veces, estaremos escribiendo unas 1320letras de más (y en Javascript eso se traduce en más transferencia por nuestra parte).

Pero cuando vamos a hacer una aplicación, todo se utiliza más o menos a un ritmo similar, aunque encontraremos tal vez funciones de formateo que llamemos constantemente, y esas funciones podrán tener un nombre raro que puede ser un símbolo. Pero con lo caprichosos que son los lenguajes de programación, ¿qué símbolos podemos utilizar? Deben ser caracteres con los que podamos empezar una función.
Aquí van algunos ejemplos para funciones PHP:

  • €();
  • ¡();
  • ç();
  • º();
  • ½();
  • _(); Pertenece a gettext
  • ł();
  • ¶();
  • ŧ();
  • ←();

C sólo permite el uso de _()

En Javascript, tenemos por ejemplo los siguientes:

  • $() Ojo con jquery y prototype
  • _()
  • º()
  • ª()
  • ß()
  • ð()
  • ŋ()
  • ħ()
  • ł()

Hay algunos más, pero no es plan de abusar, y no quiere decir que tenemos que usarlos, sólo que tenemos esa posibilidad; también muchos caracteres dependen de la codificación que utilicemos, por lo tanto pueden darnos ciertos problemas en el futuro (sobre todo en Javascript).

Visita otras webs de la red