Перечисляемый тип
В этой программе объявлено перечисление с именем Gender. Переменная типа enum Gender может принимать теперь только два значения – это MALE И FEMALE.
По умолчанию, первое поле структуры принимает численное значение 0, следующее 1, следующее 2 и т.д. Можно задать нулевое значение явно:
Будут выведены значения 0 1 0 1 2. То есть, значение SYMBOL равно значению EXPRESSION, а NUMBER равно OPERATOR. Если мы изменим программу и напишем
То SYMBOL будет равно значению 0, NUMBER равно 1, EXPRESSION равно 10, OPERATOR равно 11, UNDEFINED равно 12.
Принято писать имена полей перечисления, как и константы, заглавными буквами. Так как поля перечисления целого типа, то они могут быть использованы в операторе switch.
Заметьте, что мы не можем присвоить переменной типа Token просто численное значение. Переменная является сущностью типа Token и принимает только значения полей перечисления. Тем не менее, переменной числу можно присвоить значение поля перечисления.
Обычно перечисления используются в качестве набора именованных констант. Часто поступают следующим образом — создают массив строк, ассоциированных с полями перечисления. Например
Так как поля принимают численные значения, то они могут использоваться в качестве индекса массива строк. Команда exit(N) должна получать код ошибки, отличный от нуля, потому что 0 — это плановое завершение без ошибки. Именно поэтому первое поле перечисления равно единице.
Перечисления используются для большей типобезопасности и ограничения возможных значений переменной. Для того, чтобы не писать enum каждый раз, можно объявить новый тип. Делается это также, как и в случае структур.

Всё ещё не понятно? – пиши вопросы на ящик
Что такое enum в си
Перечисления представляют типы данных, которые содержат набор констант, и каждой константе сопоставлено определенное числовое значение. Перечисление определяется с помощью ключевого слова enum :
После слова enum идет название перечисления, затем в фигурных скобках перечисляются константы перечисления.
Определим и используем простейшее перечисление:
Здесь определено перечисление, которое называется operation и которое условно представляет математическую операцию. Оно определяет три константы: ADD, SUBTRACT и MULTIPLY.
В функции main определяем переменную перечисления:
Этой переменной можно присвоить одну из констант перечисления. В данном случае присваиваем константу ADD :
Далее мы можем использовать эту константу. В данном случае просто выводим ее значение на консоль как число. И в реальности каждой константе будет присваиваться числовое значение: первой константе — число 0, второй константе — число 1, третей — 2 и так далее. Поэтому здесь консольный вывод будет следующим:
Значение константы также можно присвоить числовой переменной:
Также можно явным образом присвоить константам числовые значения, если нам не устраивают значения по умолчанию. Можно указать значение для первой константы, тогда все последующие будут получть значение на единицу больше:
Можно присвоить всем уникальные значения:
Применение перечислений
Нередко константы перечисления выступают в качестве набора состояний, и в зависимости от состояния программа выполняет ту или иную логику. Например:
Здесь определена функция calculate() , которая выполняет арифметическую операцию. Первая два параметра функции представляют два числа, над которыми выполняется операция. А третий параметр — значение перечисления operation представляет тип операции. В самой функции в зависимости от третьего параметра выполняем ту или иную операцию и возвращаем ее результат.
В функции main три раза вызываем функцию calculate с одиними и теми числами, но разными типами операций.
Конечно, мы могли бы обоитись и без перечислений, используя обычные числа. Но, во-первых, перечисления представляют для своих констант описательные имена, которые часто говорят сами за себя, например, ADD(сложение). Во-вторых, в случаем с перечислением мы имеем дело с ограниченным набором констант, которыми проще управлять, нежели разрознеными числами.
Enums in C
Working with enums in C is frequently considered as easy, but a lot of code dealing with them is at best written poorly or worst, in a dangerous or even incorrect way.
In this blog post, I’ll try to describe simple albeit good practices, making their use more robust and practical.
Introduction
An enum is meant to represent and gather related values. Whenever possible, they should be used in favor of preprocessor macros, because they are a way for a developer to document admissible values for function parameters or struct fields.
Enum declaration
An enum can be declared this way:
This code will declare FRUIT_APPLE , FRUIT_ORANGE and FRUIT_CRANBERRY names as having value 0 , 1 and 2 respectively.
Don’t force their values when it’s not necessary, for example, doing this:
Will provide you nothing than headaches when you’ll have to maintain it.
Another common pattern is:
It should be avoided because it:
- declares an anonymous enum which is typedef-ed
Having it named instead, is more explicit in tools’ output, like compilers or IDEs - will prevent developers from using enum fruit , which is clearer than just fruit
What’s more, it is rejected by the Linux kernel coding style that I personnally like to use for my code.
The same advice is also valid for struct s and union s.
Automatic numbering is restarted after each value provided explicitly, for example:
In this case, FIRST_VALUE == 3 , SECOND_VALUE == 4 , THIRD_VALUE == 10 and FOURTH_VALUE == 11 .
A given value can be affected to multiple names and a name already declared can be used as an other name’s value. These aspects are frequently used in this pattern:
Allowing to iterate simply on the enum values like this:
CAVEAT you have to ensure that the fruit++ operation will result in one of the values taken by names declared in the fruit enum, otherwise, the value taken by the fruit variable may have no meaning. No enforcing is done by the C language on the values taken by an enum variable. This warning is valid for any arithmetic operation performed on enum values.
Nice to have is an explicit invalid value, a simple way is to add:
This value may be used for example as a function return value, indicating that something went wrong.
Usage as array indices
It’s perfectly valid to use enum values as indices for an array, for example:
Always use designated initializers for an array initializer, to be independant of both the actual values and order of an enum’s names and the order of the initializer’s lines.
If your enum has “holes”, this initialization is still valid. Values of mean_fruit_weight corresponding to “holes” will be initialized here, to 0 because the array has been declared as static . But don’t forget to be careful when iterating over enum values.
Enum value validation
The C language doesn’t check that a value stored in an enum variable is one of those declared in the enum declaration. That’s why any API defining an enum type should provide a mean to validate that an enum value is valid and it has to use it internally to validate the API’s inputs, wherever relevant.
There are two situations:
For enums with consecutive values
Don’t make assumption on the sign of the enum’s values, one compiler may represent the enum as a signed integer when another will use an unsigned representation.
For enums with “holes”
The following version is correct and more concise, but less future-proof:
because when other fruits will be added, the first one will trigger a warning of this kind:
which will force the developer to add a new switch case. On the contrary, the version with the default clause will silently handle the new fruit and say it’s invalid, which is obviously wrong here.
String conversion
Converting enum values to string
It’s quite simple:
Don’t do this:
First case is not future proof, as the caller will declare somewhere a fixed size which may (read: will) not be sufficient at some point in the future. The second is a call for buffer overflows.
Don’t bother with optimizing this kind of thing too early, like using arrays. Start optimizing only when measurements tell you to do so.
It’s possible to return NULL on invalid values, but then you’ll force the user to check the return of and it will prevent your from doing this kind of thing:
Converting enum values from string
Again, it’s simple:
Again, this could be optimized, but only “start optimizing only when measurements tell you to do so”.
Printing an enum
As a little bonus, I’ll present an absolutely non portable, because glibc-specific, albeit cool way to print fruits using functions of the printf-family.
This method can be used for custom printing of other types, like structs, IP addresses…
We’ll register a function as a handler for the ‘d’ printf format specifier, which is used for printing integers and thus, fruit enums, using register_printf_specifier . This function will take care of converting the fruit to string, by mean of the fruit_to_string function.
But we don’t want that all integers are printed as a fruit. To avoid that, we’ll also register a printf modifier using register_printf_modifier , which our specifier handler will detect, in order to know if he has to take care of converting the integer argument or not.
Here is the declaration of the two functions we need:
Now call this code somewhere to register your handler and specifier. Personnally, I like to put it in a function marked with __attribute__((constructor)) (gcc specific, supported by clang):
After that, you’ll be able to printf / sprintf or whatever, your fruits this way:
Or, clearer, in my opinion:
Which will give fruit2 really is a cranberry .
When using -Wformat (which I highly recommend), gcc will not be aware of your custom printf modifier and, if it starts with, say, ‘Y’ , it will print a warning of this kind:
That’s why I recommend making the modifier start with the real format specifier which would be used for your custom type. As a bonus, incompatible types passed will be properly detected, like here, if the compiler decided to use something else than an integer to represent our enum.
I also recommend using the same letter for the printf specifier as for the first letter of the modifier, for coherence.
It’s not clear whether using this kind of method can be considered a good practice or not, but it’s fun enough to merit being mentionned here ^^.
More information in the glibc documentation.
Last words
When used correctly, enums provide a good way to improve your code’s readability and robustness, provided they are used in a clean way. Most of the time, they should be used instead of macros.
An example implementation of simple fruit library is provided, with an example usage program here, there and there. You can test it doing:
Enumerations
An enumerated type is a distinct type whose value is a value of its underlying type (see below), which includes the values of explicitly named constants (enumeration constants).
Contents
[edit] Syntax
Enumerated type is declared using the following enumeration specifier as the type-specifier in the declaration grammar:
| enum attr-spec-seq (optional) identifier (optional) <enumerator-list > | (1) | |
| enum attr-spec-seq (optional) identifier (optional) : type <enumerator-list > | (2) | (since C23) |
where enumerator-list is a comma-separated list (with trailing comma permitted) (since C99) of enumerator , each of which has the form:
| enumeration-constant attr-spec-seq (optional) | (1) |
| enumeration-constant attr-spec-seq (optional) = constant-expression | (2) |
- applied to the whole enumeration if appears after enum ,
- applied to the enumerator if appears after enumeration-constant
As with struct or union, a declaration that introduced an enumerated type and one or more enumeration constants may also declare one or more objects of that type or type derived from it.
[edit] Explanation
Each enumeration-constant that appears in the body of an enumeration specifier becomes an integer constant with type int (until C23) in the enclosing scope and can be used whenever integer constants are required (e.g. as a case label or as a non-VLA array size).
During the processing of each enumeration constant in the enumerator list, the type of the enumeration constant shall be:
- the previously declared type, if it is a redeclaration of the same enumeration constant; or,
- the enumerated type, for an enumeration with fixed underlying type; or,
- int , if there are no previous enumeration constants in the enumerator list and no explicit = with a defining integer constant expression; or,
- int , if given explicitly with = and the value of the integer constant expression is representable by an int; or,
- the type of the integer constant expression, if given explicitly with = and if the value of the integer constant expression is not representable by int ; or,
- the type of the value from last enumeration constant with 1 added to it. If such an integer constant expression would overflow or wraparound the value of the previous enumeration constant from the addition of 1, the type takes on either:
- a suitably sized signed integer type (excluding the bit-precise signed integer types) capable of representing the value of the previous enumeration constant plus 1; or,
- a suitably sized unsigned integer type (excluding the bit-precise unsigned integer types) capable of representing the value of the previous enumeration constant plus 1.
A signed integer type is chosen if the previous enumeration constant being added is of signed integer type. An unsigned integer type is chosen if the previous enumeration constant is of unsigned integer type. If there is no suitably sized integer type described previous which can represent the new value, then the enumeration has no type which is capable of representing all of its values.
If enumeration-constant is followed by = constant-expression , its value is the value of that constant expression. If enumeration-constant is not followed by = constant-expression , its value is the value one greater than the value of the previous enumerator in the same enumeration. The value of the first enumerator (if it does not use = constant-expression ) is zero.
The identifier itself, if used, becomes the name of the enumerated type in the tags name space and requires the use of the keyword enum (unless typedef’d into the ordinary name space).
Each enumerated type without a fixed underlying type (since C23) is compatible with one of: char , a signed integer type, or an unsigned integer type (excluding the bit-precise integer types) (since C23) . It is implementation-defined which type is compatible with any given enumerated type, but whatever it is, it must be capable of representing all enumerator values of that enumeration. For all enumerations with a fixed underlying type, the enumerated type is compatible with the underlying type of the enumeration. (since C23)
The enumeration member type for an enumerated type without fixed underlying type upon completion is:
- int if all the values of the enumeration are representable as an int ; or,
- the enumerated type.
All enumerations have an underlying type. The underlying type can be explicitly specified using an enum-type-specifier and is its fixed underlying type. If it is not explicitly specified, the underlying type is the enumeration’s compatible type, which is either a signed or unsigned integer type, or char .
Enumerated types are integer types, and as such can be used anywhere other integer types can, including in implicit conversions and arithmetic operators.
[edit] Notes
Unlike struct or union, there are no forward-declared enums in C:
Enumerations permit the declaration of named constants in a more convenient and structured fashion than does #define ; they are visible in the debugger, obey scope rules, and participate in the type system.
Moreover, as a struct or union does not establish its scope in C, an enumeration type and its enumeration constants may be introduced in the member specification of the former, and their scope is the same as of the former, afterwards.