Это объявление:
enum fruit {
apple,
orange
};
объявляет три вещи: тип с именем enum fruit
и два перечислителя с именами apple
и orange
.
enum fruit
на самом деле особый тип. Он совместим с некоторым целочисленным типом, определяемым реализацией; например, enum fruit
может быть совместимо с int
, с char
или даже с unsigned long long
, если выбрана реализация, если выбранный тип может представлять все значения.
С другой стороны, перечислители являются константами типа int
. Фактически, есть обычная хитрость в использовании простого объявления enum
для объявления int
констант без использования препроцессора:
enum { MAX = 1000 };
Да, это означает, что константа apple
, даже если она была объявлена как часть определения enum fruit
, на самом деле не относится к типу enum fruit
. Причины этого исторические. И да, вероятно, для перечислителей было бы больше смысла быть константами типа.
На практике это несоответствие редко имеет большое значение. В большинстве случаев дискретные типы (то есть целочисленные и перечислимые типы) в значительной степени взаимозаменяемы, и неявные преобразования обычно делают правильные вещи.
enum fruit { apple, orange };
enum fruit obj; /* obj is of type enum fruit */
obj = orange; /* orange is of type int; it's
implicitly converted to enum fruit */
if (obj == orange) { /* operands are converted to a common type */
/* ... */
}
Но в результате этого, как вы видели, компилятор вряд ли предупредит вас, если вы используете константу, связанную с одним перечисляемым типом, когда вы собираетесь использовать другой.
Один из способов получить строгую проверку типов - это обернуть ваши данные в структуру:
enum fruit { /* ... */ };
enum color { /* ... */ };
struct fruit { enum fruit f; };
struct color { enum color c; };
struct fruit
и struct color
являются различными и несовместимыми типами без неявного (или явного) преобразования между ними. Недостатком является то, что вы должны явно ссылаться на член .f
или .c
. (Большинство программистов на Си просто рассчитывают на то, что они смогут правильно все сделать - со смешанными результатами.)
(typedef
не дает строгой проверки типа; несмотря на имя, он создает псевдоним для существующего типа, а не для нового типа.)
(правила в C ++ немного другие.)