El lenguaje C (y C++) ofrece tres maneras de definir constantes. En este artículo queremos aclarar las diferencias entre #define
, enum
y const
. Como veremos a continuación, casi siempre es preferible usar const
.
#define CONSTANTE_DEFINE 5 enum T_ENUM { CONSTANTE_ENUM = 5 }; const int CONSTANTE_VARIABLE = 5;
Los tres valores CONSTANTE_DEFINE
, CONSTANTE_ENUM
y CONSTANTE_VARIABLE
valen lo mismo (tienen el valor 5) pero tienen diferencias significativas a la hora de compilación.
La directiva #define
es una palabra clave del pre-procesador. Técnicamente se podría decir que no forma parte del lenguaje C. Antes que el compilador ve el código, el pre-procesador ya a reemplazado la macro CONSTANTE_DEFINE
por su definición – en nuestro caso el símbolo 5. Es decir, en todos los sitios donde aparece el identificador CONSTANTE_DEFINE
, el compilador ve un 5 como si nosotros mismos hubiéramos escrito un 5 en estos sitios. Es posible que el compilador ni siquiera puede decirnos qué exactamente está mal en una compilación fracasa, porque no sabe nada del identificador CONSTANTE_DEFINE
.
Como la directiva #define
sólo tiene un significado textual y carece de cualquier información de tipo, es aconsejable evitar estas directivas y const
siempre y cuando sea posible. Sin embargo, el hecho que #define
no tiene un tipo asociado puede ser también una ventaja a la hora de hacer conversiones de tipos implícitas. No obstante, todavía es cuestionable si conversiones implícitas son deseables.
Los programadores saben que físicamente se codifica una constante definida con enum
como un const int
– conceptualmente una constante definida con enum
no tiene valor numérico asociado. El concepto es todo al contrario: usar identificadores más significativos como LUNES
, MARTES
, AZUL
, VERDADERO
en lugar de números en que uno tiene que saber que un «1» significa lunes, otro «1» azul y un tercero verdadero.
Por lo dicho conviene recordar que no hay nada mejor para representar el concepto de uno que el símbolo «1». Si queremos añadir un número específico entonces mejor ponemos este número en lugar de inventarnos una constante cuyo valor luego debemos buscar por ahí.
La razón por qué se pueden asignar valores específicos a constante de enumeración es para poder adaptarlas a valores exteriores. Por ejemplo, podría definir un tipo que encapsula constantes de error de HTML.
enum T_HTML_ERROR { E_PAGE_NOT_FOUND = 404; }
Por asignar de forma inteligente los valores a las constantes puedo asignar fácilmente un valor de tipo int
a una variable de tipo T_HTML_ERROR
mediante un cast. Por ponerle el nombre E_PAGE_NOT_FOUND
al valor 404, será más fácil entender el código. Sin embargo, todavía debo hacer un cast y por eso, en general, es un mejor estilo definir constantes con const int
que luego se asignan a una variable de tipo int
. Como ya hemos dicho, la idea de una constante enum
es justamente no tener en cuenta su valor.
Una constante enum
como LUNES
no es completa en sí, sino forma parte del conjunto de constantes que forman un tipo de enumeración como T_DIA
. Desde el punto de vista del lenguaje no tiene sentido asignar una constante a otra cosa que a una variable de este tipo de enumeración. En lenguajes más puristas como el Pascal es incluso prohibido convertir enumerativos a su representación numérica interna.
Finalmente queda la opción de definir constantes mediante const
. Este método permite definir el tipo y el valor de la constante. Es incluso posible esconder el valor de la constante, definiéndola extern
en el fichero de cabecera y asignar su valor en el fichero fuente. Aunque codificar constantes con const
es la manera más limpia, suele ser la menos usada. La razón será más bien costumbre porque técnicamente hay poca razón de usar #define
o enum
.
El compilador puede comprobar el tipo de las constantes con const
que las hace mejor que los #define
. Al mismo tiempo permite las misma conversiones implícitas. Si no tengo claro si quiero codificar un número como entero o una cadena de texto, puedo usar un typedef
para poder modificar también el tipo de la constante con facilidad.
Los enum
sólo pueden representar valores enteros, mientras una constante puede tener cualquier tipo: un número flotante, una cadena de texto, una estructura compleja. Todo. Una constante const
tiene una dirección de memoria, por lo cual se puede pasar un puntero a ella. El valor de una constante const
puede ser público en el fichero de cabecera o escondido en el fichero fuente.
En fin, hemos visto las diferencias y concluimos que por regla general es preferible usar constantes declaradas por const
. Las excepciones son:
- Podemos usar
#define
cuando efectivamente necesitamos hacer algo antes de compilar. Por ejemplo, constantes de error que dependen de la plataforma, en general dentro de una compilación condicional con#if
…#endif
. - Podemos usar
enum
si sólo queremos nombres sin necesidad de saber los valores con qué se representan internamente.
Por todo lo demás: const
4 comentarios
Comments feed for this article
20 octubre 2014 a 19:10
Aimy Osorio
Gracias por la información!
20 octubre 2014 a 20:37
trucosinformaticos
De nada. Me alegro que te haya servido.
3 noviembre 2014 a 03:53
AcidShout
El artículo es viejo, pero vale la pena aclarar esto.
Los enums no sirven para lo mismo que los #defines o consts, y se deberían usar cuando se quiere especificar el tipo de algo.
Por ejemplo:
/* bitmasks */ enum PlayerFlags { PF_CROUCHING, PF_PRONE, PF_SHOOTING, PF_ZOOMING };
/* tipos */ enum HookType { HT_JMP, HT_CALLEE };
/* estrictamente tipos */ enum class HookType{ Jump, Callee }; // uso: HookType::Jump. NO hay cast implícito a int, así que int x = (int) HookType::Jump es obligatorio.
/* tipos guardados en bitmasks, etc */ namespace HookType { enum { Jump, Callee }; }; // int x = HookType::Jump; int y = x | HookType::Callee; // todo funciona
Personalmente, uso enum class cuando especifico un tipo (estrictamente un tipo) para evitar strings y números sueltos (ya que PlayerState::Shooting es muy legible y agradable), y namespace + enum cuando quiero usar bitmasks sin tener que poner un prefix y todo mayúsculas en un enum (PlayerState::All & ~PlayerState::Shooting)
3 noviembre 2014 a 20:06
trucosinformaticos
Gracias por tus ejemplos.
Es verdad que los enums se usan muchas veces como dices. No obstante, conceptualmente es más correcto usar const cuando se quiere especificar un valor para la constante. (En un lenguaje como Pascal ni siquiera se deja hacer asignar un valor a un enumerativo.) Por mejor legibilidad se pueden encapsular las constantes en clases o namespaces.
class PlayerFlags
{
static const int PF_CROUCHING = 1 << 0;
static const int PF_PRONE = 1 << 1;
static const int PF_SHOOTING = 1 << 2;
static const int PF_ZOOMING = 1 << 3;
// Dependiendo del estánder de C++, se debe inicializar
// estas constantes en el fichero cpp.
};
namespace HookType
{
enum Type
{
Jump,
Callee
};
// Esto es un ejemplo de como añadir un nombre de tipo
// delante las constantes de enum, cuando se usa un estándar
// anterior a C++11.
}
Es cierto que toca escribir más con const que con enum. Es verdad también, que los compiladores no asignan memoria a los enums mientras los constantes, sí, podrían ocupar memoria dependiendo a la optimización. Pero lo de ahorrar escribir código suele imponerse más.
Otra vez gracias por colaborar y poner unos ejemplos de uso.