Manual de estilo C / C++

[< anterior]       [indice]       [siguiente >]

3. Variables


3.1 Nomenclatura de las variables

  • Los nombres utilizados para las variables tienen que ser autoexplicativos. De manera que en el propio nombre esté la indicación del uso o finalidad de las variables.

  • Para componer los nombres de las variables se utilizarán principalmente sustantivos, pudiendo calificarse con adjetivos.
    Esta norma se establece para mejorar la identificación de los distintos componentes de un sistema.

  • Una práctica muy habitual es utilizar una serie de prefijos para poder identificar el tipo de variable de una forma sencilla y rápida. Este tipo de nomenclaturas se denomina notación húngara (ideada por Charles Simonyi, arquitecto jefe de Microsoft, [2]).
    Estos prefijos se añaden al nombre de la variable, sin separarlos de ninguna forma, como se muestra en los ejemplos de la tabla.

    Tipo Prefijo Ejemplo
    void v void vVariableVacia;
    bool b bool bOperacionValida;
    char c char cTeclaPulsada;
    int n int nNumeroCeldas;
    long l long lTotalUnidades;
    float f float fPrecioUnitario;
    double d double dAnguloMinimo;
    * (puntero) p int *pnMatrizValores;
    & (referencia(C++)) r float &rfMaximoAngulo
    [] (array) a double afRangosMaximos[3];
    enum (enumeraciones) e EBoole eResultado;

    Como se ve en los mismos ejemplos indicados en esta tabla, se pueden utilizar varios de estos prefijos en una misma definición.

    Para el tema de cadenas de caracteres (strings, no confundir con la clase String), podríamos pensar que la forma correcta de definirlas es, por ejemplo: char acNombrefichero[256+1];. Pero para poder identificarlas, ya que se usan de manera estendida, podriamos usar el prefijo s. Por ejemplo:
    // definición de una cadena de caracteres
    char sNombreFichero [256+1];

    De forma adicional, podríamos diferenciar la definición de una cadena de caracteres mediante el uso de [] (corchetes) (como en el ejemplo anterior) de la definición de un puntero a caracter (char *) que contendría la dirección de memoria donde empieza esta cadena de caracteres.
    // definición de un puntero a una zona de memoria que contiene una cadena
    char *psPunteroAlNombre;

    Estas dos definiciones se usan para esos casos, y quizás contradigan en parte la anterior norma de nomenclatura, pero pueden ayudar a diferenciar las variables "cadena de caracteres" definidas por [] (corchetes) de las definidas mediante char * (puntero a caracter), ya que pueden ser tratadas de manera muy diferente por los compiladores, y la forma de utilizarlas por el programador es diferente.

    Para otros tipos de variable no especificados en la tabla anterior no se establece prefijo, pero se puede utilizar algún otro definido por otra norma o por el propio usuario.
    Por ejemplo los siguientes:

    Tipo Prefijo
    byte o unsigned char (uchar) by
    word o unsigned int (uint) w
    unsigned long (dword) dw
    handle (manejador, puntero de objetos de windows) h

  • Para el contador de bucle for se pueden utilizar variables numéricas llamada i, j... siempre que esto no perjudique en la comprensión del código (y estas variables se utilicen solamente dentro del bucle del for).
    Para estos casos (C++) se recomienda declarar la variable i en el propio bucle.
    Ejemplo:
    for (int i=0; i<strlen(sCadenaPrueba); i++) { ... }

[subir]

3.2 Utilización de variables

  • Todas las variables hay que declararlas en el ámbito más restringido posible.
    Esto lleva a una utilización más eficiente de la pila y a una reducción de los posibles errores por efectos "colaterales".

  • En general se aconseja limitar al máximo posible la visibilidad de las variables. Como norma general de protección de datos.

  • Se debe evitar la conversión explícita de tipos de datos, ya que atenta contra la comprobación de tipos de datos que hace el compilador.
    Se aconseja utilizar la comversión implícita de tipos de datos cuando sea posible.

  • Se aconseja que cada variable se declare de forma separada.

    Así se desaconseja una definicion como la siguiente:
    // Multiple definición de variables, desaconsejada
    int nContador, nNumeroFilas, nNumeroColumnas, nTotalElementos;

    Mientras que lo correcto sería lo siguiente:
    // Definición correcta de variables.
    int nContador;
    int nNumeroFilas;
    int nNumeroColumnas;
    int nTotalElementos;

  • Usar unsigned para variables que se sepa con seguridad que nunca van a tomar valores negativos.

  • Es aconsejable que todas las variables sean inicializadas en la propia declaración de las mismas.
    Por ejemplo:
    // Definición más correcta de variables, con inicialización.
    int nContador = 0;
    int nNumeroFilas = 0;
    int nNumeroColumnas = 0;
    int nTotalElementos = 0;

[subir]

3.3 Variables locales

  • Las variables locales se deberían definir solamente en el bloque en el que se vayan a utilizar (en C++).
    Así mejoraríamos el uso de la pila.

  • Las variables locales se deberían definir justo antes de su utilización (en C++).
    De esta manera se evita el error habitual de tener variables definidas que luego no se utilizan.

[subir]

3.4 Variables globales

  • En general: NO UTILIZAR VARIABLES GLOBALES salvo en caso totalmente inevitable.
    La existencia de variables globales atenta contra la comprensión del código y su encapsulamiento, además de que puede provocar efectos "colaterales" inesperados (si una función varía el valor de la variable global de forma no controlada) que desembocan en errores muy difíciles de identificar.

  • En el caso completamente inevitable de tener que utilizar variables globales, documentar ampliamente en los ficheros y funciones que variables globales se van a utilizar, y cuales se van a modificar.

[subir]

3.5 Variables estáticas

  • Se recomienda minimizar el uso de variables estáticas, y solamente para casos en los que se necesite "recordar" el estado de una función entre llamadas consecutivas a la misma.
    No se deberían utilizar las variables estáticas como variables globales ocultas.

  • Las variables estáticas se tienen que inicializar en el momento en que se declaran, de manera obligatoria.

[subir]

3.6 Inicialización de variables

  • Todas las variables hay que inicializarlas explícitamente antes de ser utilizadas, en especial las que se alojan en la pila y las que obtienen espacio de almacenamiento de forma dinámica durante la ejecución.
    De esta manera evitamos la ejecución de código con valores desconocidos de las variables, que provoca el funcionamiento aleatorio del programa.

  • Las variables estáticas se tienen que inicializar en el momento en que se declaran.

[subir]

3.7 Asignación de memoria

  • Se aconseja el uso de new y delete para C++, en lugar de malloc, callod y free (propios de C), ya que los operadores new y delete están especialmente diseñados para trabajar con clases y llamar a los constructores y destructores (respectivamente) de las mismas.

  • Siempre que se alloque memoria de manera dinámica (tanto con malloc y calloc (propios de C) como con new (propio de C++)) hay que asegurarse de que el espacio requerido fué, efectivamente, servido por el sistema operativo. Nunca se debería suponer que una asignación de memoria ha terminado correctamente.
    Si no se comprueba si se ha reservado correctamente la memoria se pueden obtener errores fatales, difíciles de detectar en pruebas y difíciles de localizar.

    Un ejemplo correcto es el siguiente:
    char *psCadena = new char[24];
    // se comprueba si se ha obtenido memoria realmente
    if (0 == psCadena) return ERROR_RESERVA_MEMORIA;
    ...

  • Hay que liberar toda la memoria que se haya reservado de manera dinámica. No hay que esperar que "algo" libere la memoria, hay que hacerlo explícitamente.

  • Si suna variable ya tiene asignada memoria de manera dinámica, hay que liberarla antes de cambiar y asignarle nueva memoria.

  • Cuando se libere memoria con delete (C++) reservada para un array, hay que indicar el operador [] (corchetes).
    Esto se hace por legibilidad, y es obligatorio para algunos compiladores.

    Así sería incorrecto indicar...
    char *psCadena = new char[24];
    ...
    delete psCadena; // Uso incorrecto de delete para un array

    En cambio lo correcto seria indicar...
    char *psCadena = new char[24];
    ...
    delete [] psCadena; // Uso correcto de delete para un array

  • Cuando se libera memoria asignada dinámicamente (tanto con free (propio de C) como con delete (propio de C++)) siempre se asignará a 0 el puntero superviviente.
    De esta manera se minimizan los problemas que surgen de una mala gestión de memoria.

    Por ejemplo:
    delete pAuxiliar; // Liberamos memoria
    pAuxiliar = 0; // asignamos a 0 el puntero resultante.

  • En el uso de punteros se desaconseja la comparación con NULL o la asignación a NULL. Es preferible realizar la comparación con 0 o la asignación a 0.
    NULL es un estandard ANSI-C definido como (void*)0 o 0. Si NULL esta definido de tipo (void *) no se puede asignar arbitrariamente a otro tipo de puntero sin utilizar una conversión explícita. Por esta razón es preferible utilzar 0 en asignaciones y comparaciones con punteros.

    Ejemplo:
    char *psCadena = 0;
    ... // unas cuantas líneas de código
    psCadena = new char[24];
    if (0 == psCadena) return ERROR_RESERVA_MEMORIA;
    ... // muchas líneas de código más adelante
    delete psCadena;
    psCadena = 0;

  • Se desaconseja el uso de punteros a punteros. Estas técnicas complican mucho la síntaxis y la comprensión del código, pudiendo ser sustituidas utilizando otras técnicas.

[subir]

3.8 Cadenas de caracteres

  • Como se ha dicho anteriormente (notación húngara), en la definición de cadenas de caracteres se puede utilizar el prefijo s o el prefijo ps, dependiendo del caso, para identificar el tipo de variable, como por ejemplo:
    char sNombreFichero [256+1]; // variable con zona de memoria ya reservada
    char *sPunteroAlNombre; // puntero que apuntará a una cadena en memoria

  • La memoria necesaria para almacenar una cadena de N caracteres se definirá como una zona de memoria para N+1 caracteres.
    La posición número N+1 es la del caracter "\0" o de fin de cadena (hay que tener en cuenta que la primera posición es la número 0).
    Esta regla resultará muy util para evitar confusiones por el caracter "extra" que supone el caracter fin de cadena.

    Por ejemplo, para el siguiente código:
    char sMiCadena[4+1];
    sprintf (sMiCadena, "hola");

    la variable sMiCadena contendría lo siguiente:
    caracter ->  h   o   l   a   \0 
    posición -> 0 1 2 3 4

[subir]


[< anterior]       [indice]       [siguiente >] Oscar Valtueña García - "Manual de estilo C / C++"