Hasta ahora hemos trabajado sobre todo con números y strings. Para modelar el mundo real, quizá queramos un número limitado de valores que una variable pueda tomar. Es posible que quieras un tipo dedicado con unos cuantos valores distintos, cada uno con su propio nombre. Por ejemplo, en una fábrica de patinetas, que el material de la tabla sea una elección entre solo arce, bambú o plástico.
Podrías usar enteros para codificar esos valores, pero tendrías que escribir código adicional para comprobar si el sistema envía un valor inválido para el material.
El significado de esos números mágicos es difícil de rastrear a lo largo del código fuente, y además es fácil confundirlos.
Las enumerations se pueden usar para fomentar un código expresivo y para limitar los errores de comparación accidentales.
El término específico para este tipo de enumeración es scoped enumeration.
El fragmento de abajo muestra cómo escribir una enumeration DeckMaterial.
Fíjate en la palabra clave enum class y en el ; al final de la definición:
enum class DeckMaterial {
maple,
bamboo,
plastic
};
Ahora observa una función de precios en la tienda de patinetas y fíjate en el operador de resolución de ámbito (::), que especifica un enumerator de la enumeration:
double deck_price(double base_price, DeckMaterial material) {
if(material == DeckMaterial::plastic) {
return base_price * 0.9;
}
return base_price * 1.3;
}
Imagina que tienes una segunda enumeration para el material de las ruedas:
enum class WheelMaterial {
steel,
clay,
plastic
};
Aunque tanto las ruedas como la tabla pueden ser de plástico, no se pueden confundir entre sí.
Son tipos distintos: el plástico de DeckMaterial y el plástico de WheelMaterial.
Cada enumeration tiene sus enumerators en su propio ámbito, su propio namespace.
Por eso se llaman scoped enumerations.
Quizá estés pensando que, con un nombre como scoped, también debería haber enumeraciones unscoped, y tienes razón.
Las unscoped enumerations son cada vez menos populares porque todas comparten el mismo namespace global.
Debido a eso, no podrías tener dos unscoped enumerations con los mismos enumerators, como plástico en el ejemplo de arriba.
Además, las Unscoped enumerations se convierten implícitamente a enteros.
Mira el ejemplo de abajo para ver un resultado sorprendente:
enum CitrusFruits {
lemons, // 0
oranges, //1
};
enum IceCream {
walnut, // 0
apples, // 1
};
bool comparison{apples == oranges};
// => true
// Example from above:
bool comparison{DeckMaterial::plastic == WheelMaterial::plastic};
// => Does not compile!
Si quieres convertir scoped enumerations a enteros, puedes usar static_cast<int>.
Como otros lenguajes, C++ también ofrece una sentencia switch.
Las sentencias switch son una forma más corta de escribir largas sentencias if ... else if.
Para crear un switch, empezamos usando la palabra clave switch seguida de un entero.
Después declaramos cada una de las condiciones con la palabra clave case.
También podemos declarar un caso default, que se ejecutará cuando ninguna de las condiciones case anteriores coincida.
Cada caso debe terminar con una sentencia break (o return).
int price{0};
int adults{3};
int kids{2};
switch (int group_size{adults + kids}) {
case 1:
price = 50;
break;
case 2:
price = 70;
break;
default:
price = group_size * 30;
}
Algo importante sobre la construcción switch es que el código seguirá ejecutándose hasta que lo detenga una sentencia break (o return).
Esto puede provocar un comportamiento inesperado.
int adults{1};
int kids{0};
switch (int group_size{adults + kids}) {
case 1:
price = 50;
case 2:
price = 70;
default:
price = group_size * 30;
}
// price will be 30!
El uso principal de esta ejecución continua es el de una sentencia que tiene varias etiquetas. Varios resultados del switch pueden apuntar al mismo fragmento de código que se va a ejecutar. Así, por ejemplo en una aplicación de reservas, la función que se llama para grupos de 2 y de 3 puede ser la misma:
switch (group_size) {
case 1:
book_room();
break;
case 2:
case 3:
book_apartment(group_size);
break;
default:
book_house(group_size);
}
// book_apartment happens when group_size is 2 or 3
Tu amiga Helma hizo un pequeño juego en línea que rápidamente ganó popularidad. Se llama HellMath. La pequeña comunidad atrajo a algunos trolls que hacen que el juego y los foros sean bastante desagradables. Helma te ha pedido que trabajes en un nuevo sistema de permisos para separar a los alborotadores.
El foro admite tres acciones distintas:
Hay cuatro tipos de cuentas, cada uno con permisos predeterminados distintos:
Helma se ha dado cuenta de que no sirve de nada banear las cuentas de trolls. Su estrategia es darles la ilusión de que su tiempo está «bien invertido», pero sus publicaciones solo se muestran a otros trolls. Para todo lo que requiera un orden de prioridad, los trolls van al final de cualquier secuencia. Cuando entran a un juego, el grupo de jugadores disponibles también se limita a otros trolls.
Esta práctica se conoce como baneo fantasma.
Primero, define una enumeración AccountStatus para representar los cuatro tipos de cuenta: troll, guest, user y mod.
Luego, define una enumeración Action para representar los tres tipos de permisos: read, write y remove.
Cada publicación de los foros guarda el AccountStatus de quien la publica en sus metadatos.
Asegúrate de que las publicaciones de los trolls solo se muestren a otros trolls.
Helma necesita una función display_post que reciba dos argumentos de tipo AccountStatus y devuelva un bool.
El primer argumento es el estado de quien publica y el segundo es el estado de quien la ve.
using namespace hellmath;
display_post(AccountStatus::troll, AccountStatus::user);
// => false
display_post(AccountStatus::mod, AccountStatus::guest);
// => true
Helma necesita una forma de comprobar si una acción determinada está permitida para un usuario.
Implementa una función permission_check que reciba una Action como primer argumento y un AccountStatus contra el que comprobar.
Debe devolver un bool según los permisos enumerados en la introducción.
permission_check(Action::remove, AccountStatus::guest);
// => false
permission_check(Action::write, AccountStatus::mod);
// => true
Para que los jugadores reales del juego respondan por sus acciones, Hellmath niega el acceso a los usuarios invitados. Como se mencionó antes, Helma quiere que los trolls troleen a otros trolls. Las conexiones de juego entre los demás usuarios no tienen restricciones.
Implementa la función valid_player_combination, que comprueba si dos jugadores pueden unirse al mismo juego.
La función tiene dos parámetros de tipo AccountStatus y devuelve un bool.
valid_player_combination(AccountStatus::guest, AccountStatus::mod);
// => false
valid_player_combination(AccountStatus::troll, AccountStatus::troll);
// => true
Con el enorme crecimiento del juego y de los foros, ahora Helma tiene que repartir la capacidad de cómputo y el ancho de banda entre los usuarios. Para atender emergencias, los moderadores reciben la prioridad más alta. Los invitados quedan en la cola detrás de los usuarios normales, y los trolls se ordenan detrás de todos los demás.
Implementa la función has_priority, que recibe dos argumentos de tipo AccountStatus y devuelve true si y solo si la primera cuenta tiene una prioridad estrictamente mayor que la segunda.
has_priority(AccountStatus::guest, AccountStatus::mod);
// => false
has_priority(AccountStatus::user, AccountStatus::troll);
// => true
Regístrate en Exercism para aprender y dominar C++ con 19 conceptos100 ejercicios y mentoría humana real, todo gratis.