La mayoría de los lenguajes de programación contienen partes buenas y partes malas. Descubrí que podía ser un mejor programador usando solo las partes buenas y evitando las partes malas. Después de todo, ¿cómo se puede construir algo bueno con partes malas? Douglas Crockford - JavaScript: The Good Parts.
Programar de forma rápida, reducir la probabilidad de error, asegurar la portabilidad y generar binarios optimizados han sido los principales propósitos de NAppGUI desde sus inicios y eso incluye una revisión del propio lenguaje C. Se ha utilizado como base un subconjunto del ANSI-C90 con los enteros de tamaño fijo <stdint.h>, característica introducida en C99. Recomendamos que las aplicaciones basadas en este SDK sigan la misma filosofía. Entrando más en detalle, los objetivos perseguidos han sido estos:
Máxima portabilidad: Incluso en compiladores ya desfasados como MSVC 8.0 (Visual Studio 2005) o GCC 4.2 (Xcode 3). Las últimas características del lenguaje pueden no estar disponibles en plataformas donde debas portar tu código (piensa en dispositivos embebidos). También aseguras que dicho código será compatible con las futuras versiones de los principales compiladores.
Focalizar la atención: En el "que" y no en el "como". Hay veces que volvemos complicado lo sencillo tan solo por justificar el uso de esa nueva característica tan "cool". También es posible que seas un adicto por "estar a la última", lo que te obligará a "modernizar" el código para adaptarlo a una nueva versión del estándar. Céntrate en resolver el problema que tengas delante y, si puedes invertir más tiempo, en bajar la complejidad asintótica de tu solución. NAppGUI se encargará de que tus aplicaciones funcionen allá donde sea necesario.
Evitar características poco relevantes: Como el soporte multi-hilo (<threads.h>) de C11. Esto se resuelve con llamadas al sistema. Ver Hebras.
Rápida compilación: Determinadas construcciones del C no son más que una especie de "ensamblador portátil", que el compilador puede interpretar y traducir de una manera increíblemente eficiente.
Binarios pequeños y rápidos: Derivada de la anterior, el código generado requerirá pocas sentencias en ensamblador y será muy sencillo de optimizar por parte del compilador.
Evidentemente, este no es lugar para aprender C ni es nuestra pretensión. El núcleo del lenguaje es pequeño y fácil de recordar, pero programar bien requiere años de práctica. Lo que haremos aquí es mostrar la mínima expresión del lenguaje que utilizamos diariamente. En definitiva, estos son nuestros estándares.
1. Tipos básicos
Vacío:void.
Booleano:bool_t. Tipo de 8 bits con solo dos valores posibles TRUE (1) y FALSE (0).
Enteros:uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t. Los enteros de tamaño fijo se introdujeron en C99 mediante <stdint.h>. Consideramos una ventaja saber que nuestras variables tendrán el mismo tamaño en todos los sistemas. Queda prohibido el uso de int, long, short o unsigned, con la única excepción de las funciones de comparación.
Coma flotante:real32_t, real64_t. No se utiliza float ni double por coherencia con los tipos enteros.
Carácter:char_t (8 bits). Se utiliza "de facto" la representación UTF8 en todo el SDK, por lo que queda prohibido el acceso aleatorio a los elementos de una cadena, ya que se trata de una codificación de longitud variable. Se deben utilizar las funciones incluidas en Unicode o Strings para manipular arrays de carácteres. No se utilizan (ni se aconseja) los tipos wchar_t, char16_t, char32_t. No obstante, si tuvieses cadenas wide-char deberás convertirlas a UTF8 antes de utilizarlas en cualquier función de NAppGUI.
Uso de cadenas UTF8
/* Error! */constchar_t *mystr = "Ramón tiene un camión";
while (mystr[i] != '\0')
{
if (mystr[i] == 'ó')
{
/* Do something */
}
else
{
i += 1;
}
}
/* Correct! */constchar_t *it = mystr;
uint32_t cp = unicode_to_u32(it, ekUTF8);
while (cp != '\0')
{
if (cp == 'ó')
{
/* Do something */
}
else
{
it = unicode_next(it, ekUTF8);
cp = unicode_to_u32(it, ekUTF8);
}
}
/* Avoid using wchar_t constants (when possible).
wchar_t uses UTF16 encoding */const wchar_t *mywstr = L"Ramón tiene un camión";
char_t mystr[512];
unicode_convers((constchar_t*)mywstr, mystr, ekUTF16, ekUTF8, sizeof(mystr));
/* This is a NAppGUI function (UTF8-Encoding) */label_text(label, mystr);
Enumerados: Su principal cometido es manejar la especialización y serán evaluados en exclusiva dentro de un switch. Queda prohibido asignar valores aleatorios a los elementos de un enum, salvo 1 al primero de ellos. Se considera 0 como no inicializado y ENUM_MAX(align_t) como inválido.
Por lo general, las definiciones de estructuras no serán públicas y permanecerán ocultas en el *.c. Esto significa que no podrán declararse variables automáticas en el Segmento Stack y solo serán accesibles mediante funciones que acepten objetos dinámicos opacos.
/* Layout definition is hidden
We do not know the content of Layout */Layout layout; /* Compiler error! */
Normalmente, todos los objetos dinámicos dispondrán de una función destructora. Si no existiera, es porque dicho objeto solo tiene sentido como parte de otro. Por ejemplo, no existe layout_destroy() ni panel_destroy(), pero sí window_destroy que se encargará de destruir toda la jerarquía de paneles y layouts asociados a la ventana.
3. Control
if/else. Siempre abren un bloque {...}, a no ser que TODOS los caminos estén compuestos por una única sentencia. Por lo general, se evita el uso de funciones como argumentos del if/else con la excepción de funciones puras.
Uso de if/else
if (x == 1)
i_do_something(j);
else
i_do_nothing();
if (x == 1)
{
j += 2;
i_do_something(j);
}
else
{
i_do_nothing();
}
if (bmath_sqrtf(sqlen) < 20.5f)
i_do_something(j);
while. Nada que comentar.
do/while. No permitido. Utilizar for o while.
for. Para bucles infinitos, se utiliza for(;;) en lugar de while(TRUE), ya que evita warnings en algunos compiladores. Dado que hay compiladores basados en ANSI-C, como MSVC++ 8.0, no utilizamos declaraciones de variables dentro del for(), característica que fué incorporada en C99.
Uso de for
/* Infinite loop */for(;;)
{
...
}
/* Will not work in some compilers (not used) */for (uint32_t i = 0; i < 1024; ++i)
{
...
}
/* Ok */uint32_t i = 0;
...
for (i = 0; i < 1024; ++i)
{
...
}
switch. Únicamente se utiliza para discriminar entre los valores de un enum. NUNCA se evaluará otro tipo de datos en un switch ni tampoco se discriminará un enum dentro una construcción if/else. El compilador puede optimizar drásticamente el rendimiento de una construcción con estas características.
Las variables se declaran al principio de un bloque y no podrán mezclarse con sentencias, a no ser que abramos un nuevo ámbito. Declaraciones mezcladas con sentencias es una característica de C++ añadida al estándar C99, pero no todos los compiladores de C la soportan. Sí que está permitido inicializar una variable llamando a una función.
Al margen de las ventajas propias del uso de la aritmética de punteros a la hora de implementar ciertos algoritmos, en NAppGUI se utilizan punteros esencialmente en dos situaciones:
Paso de parámetros a una función, cuando dicho parámetro no sea un tipo básico.
Manejo de objetos opacos. Donde la definición del struct no está disponible y, por tanto, la única forma de comunicarnos con el objeto es mediante funciones que acepten un puntero al mismo.
Mención aparte merecen los punteros a función muy utilizados en C, pero menos en C++ ya que el lenguaje los oculta dentro de las vtable. No obstante, un puntero a función estratégicamente colocado puede simplificarnos el hecho de añadir funcionalidad especializada a objetos ya existentes, sin tener que adoptar un diseño orientado a objetos mas purista.
En nuestros estándares se hace un uso intensivo del preprocesador, sobre todo para la comprobación de tipos en tiempo de compilación. Esto ayuda a detectar errores en el código antes de ejecutar el programa (análisis estático), al contrario que el RTTI de C++ que lo hace una vez en marcha (análisis dinámico).
Uso del preprocesador para comprobar tipos.
#definearrst_destroy(array, func_remove, type)\
((void)((array) == (ArrSt(type)**)(array)),\
FUNC_CHECK_REMOVE(func_remove, type),\
array_destroy_imp((Array**)(array), (FPtr_remove)func_remove, (constchar_t*)(ARRST#type)))
ArrSt(Product) *products = arrst_create(Product);
...
staticvoid i_remove_product(Product *product)
{
}
...
/* 'products' and 'i_remove_product' will be checked at compile time */arrst_destroy(&products, i_remove_product, Product);
El tipado dinámico no es necesariamente bueno. Obtiene errores estáticos en tiempo de ejecución, que realmente deberían poder detectarse en tiempo de compilación. Rob Pike.
8. Comentarios
Por lo general se reducirá el uso de comentarios todo lo posible. Se pondrá un comentario al inicio de cada fichero a modo de descripción general. También utilizamos una línea de comentario como separador a la hora de implementar funciones.
stream.c
/* Data streams. Manage connection-oriented communication */#include "stream.h"
#include "stream.inl"
#include "bfile.h"
#include "bmem.h"
...
/*---------------------------------------------------------------------------*/staticvoid i_func1(void)
{
/* Do something */
}
/*---------------------------------------------------------------------------*/staticvoid i_func2(void)
{
/* Do something */
}
Los comentarios tipo C++ // Comment... NO están permitidos, ya que generan warnings en ciertos compiladores gcc -std=gnu90.
Otro aspecto que queda totalmente prohibido es la inclusión de bloques de documentación dentro del código fuente, ni siquiera en las propias cabeceras. NAppGUI utiliza ndoc para tareas de documentación, una utilidad que permite crear documentos html/pdf enriquecidos con imágenes, referencias cruzadas, ejemplos, etc y que utiliza sus propios archivos totalmente separados del código. Otra ventaja añadida es la limpieza que presentan las cabeceras *.h de todos los módulos, donde se hace muy sencillo localizar aquello que buscamos.
Los bloques de documentación NO están permitidos.
/* Forbidden, non used *//*! Gets the area of the polygon.
\param pol The polygon.
\return The area.
*/real32_tpol2d_areaf(constPol2Dd *pol);
Todos los comentarios en NAppGUI se realizan en lenguaje Inglés.
9. Entrada/Salida
La entrada/salida no forma parte del lenguaje C como tal. A medida que el lenguaje se iba extendiendo a mediados de los 70, se fueron agrupando una serie de rutinas útiles en lo que pasó a formar la Librería Estándar de C. NAppGUI encapsula toda su funcionalidad en Sewer, Osbs o Core implementándola generalmente como llamadas al sistema operativo, mucho más directas y eficientes.
Uso de funciones seguras de E/S.
/* Do not use cstdlib in applications */#include <stdio.h>
FILE *fp = fopen("/tmp/test.txt", "w+");
fprintf(fp, "This is testing for fprintf...\n");
fclose(fp);
/* Use NAppGUI functions */#include "stream.h"
Stream *stm = stm_to_file("/tmp/test.txt", NULL);
stm_printf(stm, "This is testing for stm_printf...\n");
stm_close(&stm);
No se recomienda el uso de la Librería Estándar de C. Busca la función equivalente en Sewer, Osbs o Core.
10. Algoritmos matemáticos
NAppGUI utiliza los C++ templates para implementar cualquier función o algoritmo matemático. Con esto se consigue ofrecer versiones float y double de manera elegante y con sencillo mantenimiento. Las plantillas están ocultas y no se exponen en el API, para que su uso siga siendo compatible con ANSI-C90. Para más información Plantillas matemáticas.
NAppGUI hace uso interno de C++98 template<> para implementar todo lo relativo al cálculo matemático.