Month
int
, но неявного преобразования типа Month
в тип int
не существует. Это имеет смысл, поскольку каждый объект класса Month
имеет эквивалентное целое значение, но большинство целых чисел не имеет эквивалентного значения типа Month
. Например, мы преднамеренно написали неправильную инициализацию.Month bad = 9999; // ошибка: целое число невозможно преобразовать
// объект типа Month
Month(9999)
К сожалению, мы не можем определить конструктор для перечисления, чтобы проверить начальные значения, но написать простую функцию для проверки не составляет труда.
Month int_to_month(int x)
{
if (x
return Month(x);
}
Теперь можно написать следующий код:
void f(int m)
{
Month mm = int_to_month(m);
// ...
}
Для чего нужны перечисления? В основном перечисление полезно, когда нам нужно множество связанных друг с другом именованных целочисленных констант. Как правило, с помощью перечислений представляют наборы альтернатив (up
down
; yes
, no
, maybe
; on
, off
; n
, ne
, e
, se
, s
, sw
, w
, nw
) или отличительных признаков (red
, blue
, green
, yellow
, maroon
, crimson
, black
).Обратите внимание на то, что элементы перечисления
enum Traffic_sign { red, yellow, green };
int var = red; // примечание: правильно Traffic_sign::red
Этот код вызывает проблемы. Представьте себе, что в вашей программе в качестве глобальных используются такие распространенные имена, как red
on
, ne
и dec
. Например, что значит ne:
“северо-восток” (northeast) или “не равно” (nor equal)? Что значит dec:
“десятичный” (decimal) или “декабрь” (December)? Именно о таким проблемах мы предупреждали в разделе 3.7. Они легко возникнут, если определить перечисление с короткими и общепринятыми именами элементов в глобальном пространстве имен. Фактически мы сразу сталкиваемся с этой проблемой, когда пытаемся использовать перечисление Month
вместе с потоками iostream
, поскольку для десятичных чисел существует манипулятор с именем dec
(см. раздел 11.2.1). Для того чтобы избежать возникновения этих проблем, мы часто предпочитаем определять перечисления в более ограниченных областях видимости, например в классе. Это также позволяет нам явно указать, на что ссылаются значения элементов перечисления, такие как Month::jan
и Color::red
. Приемы работы с перечислениями описываются в разделе 9.7.1. Если нам очень нужны глобальные имена, то необходимо минимизировать вероятность коллизий, используя более длинные или необычные имена, а также прописные буквы. Тем не менее мы считаем более разумным использовать имена перечислений в локальных областях видимости.9.6. Перегрузка операторов
Для класса или перечисления можно определить практически все операторы, существующие в языке С++. Этот процесс называют перегрузкой операторов (operator overloading). Он применяется, когда требуется сохранить привычные обозначения для разрабатываемого нами типа. Рассмотрим пример.
enum Month {
Jan=1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec
};
Month operator++(Month& m) // префиксный инкрементный оператор
{
m = (m==Dec) ? Jan : Month(m+1); // "циклический переход"
return m;
}
Конструкция ? :
m
становится равной Jan
, если (m==Dec
), и Month(m+1)
в противном случае. Это довольно элегантный способ, отражающий цикличность календаря. Тип Month
теперь можно написать следующим образом:Month m = Sep;
++m; // m становится равным Oct
++m; // m становится равным Nov
++m; // m становится равным Dec
++m; // m становится равным Jan ("циклический переход")