Try English version of Quizful



Раздаем бесплатные Q! подробности в группе Quizful.Alpha-test
Топ контрибуторов
loading
loading
Знаете ли Вы, что

После прохождения теста можно комментировать вопросы теста, а Ваши комментарии увидят модераторы теста и пользователи, которым когда-либо эти вопросы попадались.

Лента обновлений
ссылка Nov 16 17:00
Комментарий от Webs564418:
https://123sdfsdfsdfsd.ru/r.html?r=https%3A%2F%2Fbitn...
ссылка Nov 16 07:14
Добавлен вопрос в тест JavaScript - Основы
ссылка Nov 15 16:58
Комментарий от zzerron:
а еще нельзя делать так +-- или так --+. Зато можно дела...
ссылка Nov 15 16:54
Комментарий от daptaso:
А так же mixed, number, callback (callable), void
ссылка Nov 15 09:00
Добавлен вопрос в тест C++ - Основы
Статистика

Тестов: 153, вопросов: 8597. Пройдено: 420087 / 2050005.

Перечисления в C++ (c++ enum types)

head tail Статья
категория
C++
дата15.05.2009
авторanalizer
голосов23

Мнение автора может не совпадать с его точкой зрения
(с) спёрто из ЖЖ

Рекомендуемая литература:

Введение в перечисления

Как известно, перечисления - это тип который может содержать значения указанные программистом. Целочисленные именованные константы могут быть определены как члены перечисления. Например:

enum { RED, GREEN, BLUE };
определяет три целочисленные константы и присваивает им значения. По умолчанию, значения присваиваются по порядку начиная с нуля, т.е. RED == 0, GREEN == 1 и BLUE == 2. Перечисление также может быть именованным:
enum color { RED, GREEN, BLUE };
Каждое перечисление - это отдельный тип, и тип каждого члена перечисления - это само перечисление. Например RED имеет тип color. Объявление типа переменной как color, вместо обычного unsigned, может подсказать и программисту и компилятору о том как эта переменная должна быть использована. Например:
void f(color c)
{
    switch(c){
        case RED:
            // do something
            break;
        case BLUE:
            // do something
            break;
    }
}

В этом случае компилятор может выдать предупреждение о том, что обрабатываются только два значения color из трёх возможных.

Таким образом перечисления это:

- Создание именованных констант с автоматическим увеличением значения константы

- Предупреждения о возможных ошибках со стороны компилятора


Основные проблемы при использовании enum

На самом деле всё что выше - общие слова, которые нужны только для того чтобы те кто забрёл сюда по ошибке, хотя бы что-то из этой статьи вынесли. А мы сейчас поговорим о сложностях и хитростях с которыми приходится сталкиваться каждому кто более-менее юзает перечисления в нормальном девелопменте. Итак, с чем приходится сталкиваться:

1. Отображение значения перечисления в строку которая совпадает с именем члена перечисления, т.е. что-либо что для enum_map[RED] вернёт "RED".

2. Итерация по членам перечисления и контроль выхода за границы. Т.е. сколько бы вы не добавляли новых элементов в перечисление, у вас всегда есть константа которая ровно на единицу больше последнего члена последовательности.

3. (Тем, кто не прошёл тест по шаблонам, можно не читать) Отображение run-time целочисленной переменной в compile-time переменную или тип (указатель на функцию с определённым значением параметра шаблона etc.).

Разберём вышеозначенные задачи в деталях.


Отображение членов перечисления в строки

Плохое решение:
const char* const strs[]={"RED","GREEN","BLUE"};
void f(color c)
{
    puts(strs[c]);
}

Оправданий для того чтобы использовать такой код может быть только два:

  • Если стоит статическая проверка на равенство количества членов перечисления и количества элементов в массиве строк (о статических проверках можете прочитать у Александреску или дождаться моей следующей статьи, которая как раз будет написана по этой теме). Оправдание довольно слабое.
  • Если строки могут не совпадать с названиями членов перечисления (редкий, на самом деле, случай). Т.е. что-то типа такого: const char* const strs[]={"Red as an egypt rose","Green peace","Blue Screen of Death"};.
Решение:

Уберите от экранов детей!
присутствуют сцены аморального и порнографического характера

Превратим то изящное перечисление, которое у нас было:

enum color { RED, GREEN, BLUE };

вот в этого монстра Франкенштейна:


color.h

#include "enum_helper_pre.h"

enumeration_begin(color)
declare_member(RED) delimiter
declare_member(GREEN) delimiter
declare_member(BLUE)
enumeration_end;

#include "enum_helper_post.h"

Выглядит пугающе, но на самом деле мы просто добавили возможность переопределить каждый элемент конструкции enum:


enum_helper_pre.h

#ifndef delimiter
	#define delimiter ,
#endif


#ifndef enumeration_begin
	#define enumeration_begin(arg) enum arg {
#endif

#ifndef enumeration_end
	#ifdef last_enumerator
		#define enumeration_end delimiter last_enumerator }
	#else
		#define enumeration_end }
	#endif
#endif

#ifndef declare_member
	#define declare_member(arg) arg
#endif

#ifndef member_value
	#define member_value(arg) = arg
#endif
Думаю понятно что в конце для всех этих макросов нужно сделать #undef:


enum_helper_post.h

#undef delimiter
#undef enumeration_begin
#undef enumeration_end
#undef last_enumerator
#undef declare_member
#undef member_value

Теперь если вам нужно заполнить некий массив строковыми представлениями членов перечисления, то выглядеть это будет так:


main.c

#include <stdio.h>

#define enumeration_begin(arg) const char* const arg##_strs[]={
#define declare_member(arg) #arg
#include "color.h"

#include "color.h"

int main(int argc,char* argv[])
{
	unsigned c = RED;
	while(c <= BLUE)
	{
		puts(color_strs[c++]);
	}
	return 0;
}


Output

RED
GREEN
BLUE

Вуаля! Вот оно - три строчки кода и вы для любого перечисления, оформленного по нашим правилам, получаете массив из строковых представлений членов перечисления, независимо от количества членов перечисления. Любой человек который будет дебажить код по трейсам, в которых вместо безликих чисел будут стоять названия элементов, скажет вам огромное спасибо.

Итерация по членам перечисления и контроль выхода за границы

Опытный глаз заметит, что пример выше получился немного хромой, потому что перечислениям в крупных проектах свойственно расти с течением времени и верхнюю границу цикла приходится всё время переопределять. Например, если наше перечисление определяло компоненты цвета, то мы может добави в него компонент определяющий яркость цвета:

enum color { RED, GREEN, BLUE, BRIGHTNESS };

Теперь в примере придётся изменить условие выполнение цикла на while(c <= BRIGHTNESS). Если такой цикл один на всю программу, то в этом нет ничего страшного, но если подобный цикл встречается в десятке мест, то, рано или поздно, вы начнёте забывать все места где надо поменять предельное значение цикла, что приведёт к трудноуловимым ошибкам.

Другой пример, когда подобные проверки станут нашим кошмаром - валидация данных полученных из некоторого источника. Например сервер третьей стороны может передавать вам через сокет значения какого либо перечисления, и на каждое возможное значение вы выполняете различные действия. Представьте себе если администрация сервера добавила новый элемент в конец перечисления или же в сокет пришёл какой-либо мусор. Тогда вы заметите это лишь когда ваш клиент, слушающий сокет, отправит данные дальше и один из модулей, получив эти данные, рухнет. Решением в этом случае была бы проверка следующего вида:

if(c < RED || c > BRIGHTNESS)
	throw std::runtime_error("Wrong color received");

И опять же если вы добавите новый элемент в перечисление, вам придётся внести изменения во все файлы где есть такие проверки. Есть решение проще - добавить фиктивный член перечисления, с фиксированным именем, всегда являющийся посленим членом перечисления. С нашим определением файла color.h сделать это - проще простого:


main.c

#include <stdio.h>

#define enumeration_begin(arg) const char* const arg##_strs[]={
#define declare_member(arg) #arg
#include "color.h"

#define last_enumerator COLOR_END
#include "color.h"

int main(int argc,char* argv[])
{
	unsigned c = RED;
	while(c < COLOR_END)
	{
		puts(color_strs[c++]);
	}
	return 0;
}


Output

RED
GREEN
BLUE
BRIGHTNESS

Теперь вы можете добавлять сколько угодно членов в перечисление, не боясь ничего потерять (о неизменённых конструкциях switch вас предупредит компилятор).

Отображение run-time в compile-time

Я уже говорил что будут шаблоны?

Теперь представьте себе, что у вас есть семейство шаблонных функций вида template<color c> void f(), и, допустим, опять же через сокет к вам приходит число, и это для этого числа (интерпретированного как color) нужно вызвать соответствущую функцию f. Простое решение напрашивается само собой:

void f(color c)
{
	switch(c)
	{
	case RED:
		f<RED>();
		break;
	case GREEN:
		f<GREEN>();
		break;
	case BLUE:
		f<BLUE>();
		break;
	case BRIGHTNESS:
		f<BRIGHTNESS>();
		break;
	}
}

Вопрос только в том сколько элементов может быть в перечислении и, опять же, сколько файлов вам придётся поправить, если в перечислении что-то изменится. Поэтому проще создать карту указателей на все эти функции, и отображать входное значение в указатель на функцию:

void f(color c)
{
	if(c < RED || c >= COLOR_END)
		return;

	typedef void (*func_type)();
	#define enumeration_begin(arg) func_type func_map[]={
	#define declare_member(arg) f<arg>
	#include "color.h"

	func_map[c]();
}

Значительно проще, не правда ли?

Обещанный пример

Усложним задачу. Теперь нам нужно не только отображать член перечисления в строку, но и наоборот. И при этом RED == -2 и BLUE == 5. Используя стандартные перечисления добиться результата можно лишь напрямую забив данные в карту отображений.

Решение, на самом деле, очень простое, как и всё этой статье:


color.h

#include "enum_helper_pre.h"

enumeration_begin(color)
declare_member(RED) member_value(-2) delimiter
declare_member(GREEN) delimiter
declare_member(BLUE) member_value(5) delimiter
declare_member(BRIGHTNESS)
enumeration_end;

#include "enum_helper_post.h"


main.cpp


#include <iostream>
#include <string>
#include <boost/bimap.hpp>
#include <boost/preprocessor/stringize.hpp>

#include "color.h"

int main(int argc,char* argv[])
{
	typedef boost::bimap<color,std::string> map_type;
	map_type color_map;
	#define declare_member(arg) color_map.insert( map_type::value_type(arg,BOOST_PP_STRINGIZE(arg)) )
	#define delimiter ;
	#define enumeration_begin(arg)
	#define enumeration_end
	#define member_value(arg)
	#include "color.h"

	std::cout<<color_map.left.at(RED)<<std::endl;
	std::cout<<color_map.left.at(BLUE)<<std::endl;
	std::cout<<color_map.right.at("GREEN")<<std::endl;
	std::cout<<color_map.right.at("BRIGHTNESS")<<std::endl;
	return 0;
}


Output

RED
BLUE
-1
6

Собственно получилось простенько, но со вкусом. Таким образом придерживаясь немного громоздкого стиля описания перечислений, вы можете достичь невероятной гибкости при их использовании.

Вот собственно и всё что я хотел здесь рассказать, если у кого-то возникнут вопросы - я с радостью на них отвечу. Буду рад если кому-то эта статья облегчит нелёгкую программистскую жизнь.

----

Дмитрий analizer Потапов, март 2009

Для закрепления материала рекомендую пройти тесты:
Тест знаний C++ - Основы
Тест знаний C++ - Средний уровень

Если Вам понравилась статья, проголосуйте за нее

Голосов: 23  loading...
admin   c0nst   md6   clumsy   zagra   zipu4   LeoBear   Engineer9   kriolyth   alariq   sanchousf   Max_Bond   Plum   einstein   Pomste   dilukhin   DTSAR   amerlyq   menscrem   alleggssys   Ukus_bobra   loskamo   boesh