Как определить перечислимый тип (enum) в C?

272

Я не уверен, каков правильный синтаксис для использования C перечислений. У меня есть следующий код:

enum {RANDOM, IMMEDIATE, SEARCH} strategy;
strategy = IMMEDIATE;

Но это не компилируется со следующей ошибкой:

error: conflicting types for strategy
error: previous declaration of strategy was here

Что я делаю не так?

Линделоф
источник
7
Летний вопрос, наверное, никто этого не увидит; но почему это дает ошибку? Насколько мне известно, это должно работать на отлично, как и в вопросе.
Уткан Гезер
2
@Solver, почему этот синтаксис неправильный?
MCG
6
@MCQ, necroing necro'd necro: Синтаксис, представленный в вопросе, не является неправильным в C. Он объявляет, strategyчто имеет анонимный перечисляемый тип, и присваивает ему одно из объявленных значений этого типа. Более того, если я оберну код, представленный в другой тривиальной main()функции, он прекрасно скомпилируется для меня, даже без предупреждения, с помощью gcc 4.4.7. Некоторые ответы подразумевают то же самое, хотя и не так много слов.
Джон Боллинджер
5
В большинстве ответов отсутствует тот факт, что две строки кода в вопросе - это не просто фрагмент кода. Они весь исходный файл. Если эти две строки включены в тело функции, ошибки нет. Если они появляются в области видимости файла, вне любого объявления функции, вы получите ошибки, о которых спрашивал OP (плюс некоторые другие, когда я пытался это сделать). Основная проблема заключается в том, что компилятор пытается воспринимать это strategy = IMMEDIATE;как объявление. У этого есть форма, которая была бы законна в до-ANSI C, но в современном C это незаконно. Назначения не разрешены в области файла.
Кит Томпсон
3
@Solver: enum strategy { ... };определяет перечислимый тип named enum strategy, где strategyнаходится тег. enum { ... } strategy;определяет анонимный перечислимый тип (без тега) и единственный объект этого типа с именем strategy. Оба совершенно законны; они просто имеют в виду разные вещи.
Кит Томпсон

Ответы:

377

Объявление переменной enum выполняется следующим образом:

enum strategy {RANDOM, IMMEDIATE, SEARCH};
enum strategy my_strategy = IMMEDIATE;

Тем не менее, вы можете использовать typedefдля сокращения объявлений переменных, например так:

typedef enum {RANDOM, IMMEDIATE, SEARCH} strategy;
strategy my_strategy = IMMEDIATE;

Хорошая идея - иметь соглашение об именах, чтобы различать типы и переменные:

typedef enum {RANDOM, IMMEDIATE, SEARCH} strategy_type;
strategy_type my_strategy = IMMEDIATE;
RichieHindle
источник
1
Но OP хотел переменную анонимного типа enum
osvein
Не мог я просто напечатать enum MyEnum {} myVar;и затем использовать переменную myVarследующим образом:myVar = SOMEENUMCONSTANT;
Mushy
451

Стоит отметить, что вам не нужноtypedef . Вы можете просто сделать это следующим образом

enum strategy { RANDOM, IMMEDIATE, SEARCH };
enum strategy my_strategy = IMMEDIATE;

Это вопрос стиля, предпочитаете ли вы typedef. Без этого, если вы хотите сослаться на тип перечисления, вам нужно использовать enum strategy. С этим можно просто сказать strategy.

Оба способа имеют свои плюсы и минусы. Один из них более многословный, но хранит идентификаторы типов в пространстве имен тегов, где они не будут конфликтовать с обычными идентификаторами (подумайте struct statи о statфункции: они тоже не конфликтуют), и где вы сразу увидите, что это тип. Другой короче, но переносит идентификаторы типов в обычное пространство имен.

Йоханнес Шауб - Литб
источник
6
Это не должен быть принятый ответ, потому что это неправильно. Вы не можете использовать стратегию enum {...}; в C - вы можете и должны делать это в C ++, хотя.
уточняю
19
@Clearer: этот код работает отлично. Вот рабочий пример: ideone.com/T0YV17 Обратите внимание, что он использует enumключевое слово в обеих строках.
RichieHindle
Или "typedef enum стратегии {RANDOM, IMMEDIATE, SEARCH} стратегии_t;" и разработчик, использующий перечисление, может использовать любое соглашение, которое они хотят.
Энди Ньюджент
это отлично работает: enum strategy { RANDOM, IMMEDIATE, SEARCH }; тогда, когда вам нужен экземпляр этого enum: `enum стратегии myEnum;
user3629249
2
@AndyNugent не делай этого! * _t типы зарезервированы POSIX
osvein
58

Вы пытаетесь объявить strategyдважды, и именно поэтому вы получаете вышеупомянутую ошибку. Следующие работы без каких-либо жалоб (составлено с gcc -ansi -pendantic -Wall):

#include <stdio.h>

enum { RANDOM, IMMEDIATE, SEARCH } strategy = IMMEDIATE;

int main(int argc, char** argv){
    printf("strategy: %d\n", strategy);

    return 0;
}

Если вместо вышеизложенного, вторая строка была изменена на:

...
enum { RANDOM, IMMEDIATE, SEARCH } strategy;
strategy = IMMEDIATE;
...

Из предупреждений вы можете легко увидеть свою ошибку:

enums.c:5:1: warning: data definition has no type or storage class [enabled by default]
enums.c:5:1: warning: type defaults to int in declaration of strategy [-Wimplicit-int]
enums.c:5:1: error: conflicting types for strategy
enums.c:4:36: note: previous declaration of strategy was here

Таким образом, компилятор принял strategy = IMMEDIATEобъявление переменной strategyс типом по умолчанию int, но ранее уже существовало объявление переменной с этим именем.

Однако, если вы поместите присвоение в main()функцию, это будет действительный код:

#include <stdio.h>

enum { RANDOM, IMMEDIATE, SEARCH } strategy = IMMEDIATE;

int main(int argc, char** argv){
    strategy=SEARCH;
    printf("strategy: %d\n", strategy);

    return 0;
}
ТАКП
источник
48

Когда ты говоришь

enum {RANDOM, IMMEDIATE, SEARCH} strategy;

вы создаете единственную переменную экземпляра, называемую «стратегией» безымянного перечисления. Это не очень полезная вещь - вам нужен typedef:

typedef enum {RANDOM, IMMEDIATE, SEARCH} StrategyType; 
StrategyType strategy = IMMEDIATE;

источник
9
Почему это не полезно? Если меня не интересует название типа, почему я должен дать его? Единственное, что здесь было задумано, - это присвоить имя переменной, чтобы можно было присвоить ей новые значения.
MSalters
3
Я сказал, что это НЕ ОЧЕНЬ полезно, и я не верю, что это так. Конечно, я не использую этот шаблон в своем собственном коде. YMMV.
3
@HorseSMith Безымянное перечисление не очень полезно, потому что у вас не может быть никакой другой переменной этого типа, или параметра функции, или возвращаемого значения. Если вам нужна только одна переменная , тогда все в порядке.
Боб Стейн
3
Тот, кто не использует анонимные перечисления, не доказывает, что они бесполезны. Вам не нужен typedef. Некоторые рекомендации по коду (kernel.org/doc/Documentation/CodingStyle) даже не одобряют это.
Мартинкунев
2
Этот ответ также вводит в заблуждение. Ответ Тарка - единственный правильный здесь.
ночной пул
13

Как написано, нет ничего плохого в вашем коде. Вы уверены, что не сделали что-то вроде

int strategy;
...
enum {RANDOM, IMMEDIATE, SEARCH} strategy;

На какие строки указывают сообщения об ошибках? Когда говорится, что «предыдущая декларация« стратегии »была здесь», что здесь «что» и что она показывает?

Джон Боде
источник
6
Он, вероятно, сделал strategy = IMMEDIATE;в области видимости файла. Назначение не может происходить в области видимости файла вне всех функций. Таким образом, компилятор попытался сделать все возможное из ошибки и предположил, что он имел в виду int strategy = IMMEDIATE;, в этот момент произошел конфликт.
Йоханнес Шауб -
2
Это лучший ответ, в других ответах так много путаницы, что это больно.
расслабиться
12

@ThoAppelsin в своем комментарии к опубликованному вопросу прав. Фрагмент кода размещен в вопросе, он действителен и без ошибок. Ошибка у вас должна быть из-за другого неправильного синтаксиса в любом другом месте вашего исходного файла c. enum{a,b,c};определяет три символические константы ( a, bи c), которые являются целыми числами со значениями 0, 1и 2соответственно, но когда мы используем enumэто, потому что мы обычно не заботимся о конкретном целочисленном значении, мы больше заботимся о значении имени символической константы. Это означает, что вы можете иметь это:

#include <stdio.h>
enum {a,b,c};
int main(){
  printf("%d\n",b);
  return 0;
}

и это будет выводить 1.

Это также будет действительным:

#include <stdio.h>
enum {a,b,c};
int bb=b;
int main(){
  printf("%d\n",bb);
  return 0;
}

и будет выводить то же, что и раньше.

Если вы делаете это:

enum {a,b,c};
enum {a,b,c};

у вас будет ошибка, но если вы сделаете это:

enum alfa{a,b,c};
enum alfa;

у вас не будет никакой ошибки.

ты можешь сделать это:

enum {a,b,c};
int aa=a;

и aaбудет целочисленной переменной со значением 0. но вы также можете сделать это:

enum {a,b,c} aa= a;

и будет иметь тот же эффект (то есть, aaбудучи intсо 0значением).

Вы также можете сделать это:

enum {a,b,c} aa= a;
aa= 7;

и aaбудет intсо значением 7.

поскольку вы не можете повторить символьное определение константы с использованием enum, как я уже говорил ранее, вы должны использовать теги, если хотите объявить intпеременные с использованием enum:

enum tag1 {a,b,c};
enum tag1 var1= a;
enum tag1 var2= b;

польза от typedefнего заключается в том, чтобы защитить вас от записи каждый раз enum tag1для определения переменной. С помощью typedefвы можете просто набрать Tag1:

typedef enum {a,b,c} Tag1;
Tag1 var1= a;
Tag1 var2= b;

Вы также можете иметь:

typedef enum tag1{a,b,c}Tag1;
Tag1 var1= a;
enum tag1 var2= b;

Последнее, что нужно сказать, это то, что, поскольку мы говорим об определенных символических константах, при использовании лучше использовать заглавные буквы enum, например:

enum {A,B,C};

вместо того

enum {a,b,c};
roggc
источник
10

Стоит отметить, что в C ++ вы можете использовать «enum» для определения нового типа без использования оператора typedef.

enum Strategy {RANDOM, IMMEDIATE, SEARCH};
...
Strategy myStrategy = IMMEDIATE;

Я считаю этот подход намного более дружелюбным.

[править - уточнил статус C ++ - у меня это было изначально, потом удалил!]

Родди
источник
Да, вы никогда не должны использовать typedef с перечислениями (или структурами, объединениями и т. Д.) В C ++.
17
Этот вопрос для C, а не для C ++. В C вышеуказанный код недействителен - вы должны либо использовать typedef, либо указать enumв объявлении переменной также: enum Strategy {RANDOM, IMMEDIATE, SEARCH}; ... enum Strategy myStrategy = IMMEDIATE;
Павел Минаев
@ Павел - мой плохой. Первоначально я занимался «на С ++», затем провел исследование, которое, казалось, противоречило этому.
Родди
@Pavel Я думаю, что это должен быть отдельный ответ, описывающий преимущества использования enum Strategy. Я сделал это, см. Ниже.
Йоханнес Шауб -
8

Кажется, есть путаница в декларации.

Когда strategyприходит раньше, {RANDOM, IMMEDIATE, SEARCH}как в следующем,

enum strategy {RANDOM, IMMEDIATE, SEARCH};

вы создаете новый тип с именем enum strategy. Однако при объявлении переменной вам нужно использовать enum strategyсебя. Вы не можете просто использовать strategy. Таким образом, следующее недействительно.

enum strategy {RANDOM, IMMEDIATE, SEARCH};
strategy a;

Пока действует следующее

enum strategy {RANDOM, IMMEDIATE, SEARCH};

enum strategy queen = RANDOM;
enum strategy king = SEARCH;
enum strategy pawn[100];

Когда strategyприходит после {RANDOM, IMMEDIATE, SEARCH}, вы создаете анонимное перечисление, а затем объявляете strategyпеременную этого типа.

Так что теперь вы можете сделать что-то вроде

enum {RANDOM, IMMEDIATE, SEARCH} strategy;
strategy = RANDOM;

Однако вы не можете объявить любую другую переменную типа, enum {RANDOM, IMMEDIATE, SEARCH}потому что вы никогда не называли ее. Так что следующее неверно

enum {RANDOM, IMMEDIATE, SEARCH} strategy;
enum strategy a = RANDOM;

Вы можете объединить оба определения тоже

enum strategy {RANDOM, IMMEDIATE, SEARCH} a, b;

a = RANDOM;
b = SEARCH;
enum strategy c = IMMEDIATE;

Typedef как отмечалось ранее, используется для создания более короткого объявления переменной.

typedef enum {RANDOM, IMMEDIATE, SEARCH} strategy;

Теперь вы сказали компилятору, что enum {RANDOM, IMMEDIATE, SEARCH}является синонимом strategy. Так что теперь вы можете свободно использовать strategyкак тип переменной. Вам не нужно enum strategyбольше печатать . Следующее действует в настоящее время

strategy x = RANDOM;

Вы также можете комбинировать Typedef вместе с именем enum, чтобы получить

typedef enum strategyName {RANDOM, IMMEDIATE, SEARCH} strategy;

Существует не так много преимуществ использования этого метода, кроме того, что вы можете теперь использовать strategyи enum strategyNameвзаимозаменяемо.

typedef enum strategyName {RANDOM, IMMEDIATE, SEARCH} strategy;

enum strategyName a = RANDOM;
strategy b = SEARCH;
Confuse
источник
1
Отличный ответ. Я также натолкнулся на определения enum, написанные так: typedef enum strategy {RANDOM, IMMEDIATE, SEARCH} strategyили typedef enum strategy {RANDOM, IMMEDIATE, SEARCH} strategy_type. Это имеет какое-то преимущество перед typedef enum {RANDOM, IMMEDIATE, SEARCH} strategy? Не могли бы вы добавить их в свой ответ для полноты?
августа
Да. Я изменил свой ответ. Насколько мне известно, в общем случае нет никаких существенных преимуществ.
путать
2
Отлично, теперь ваш ответ охватывает все это, спасибо. Жаль, что так далеко внизу списка ответов, не в последнюю очередь потому, что он явно обращается к первоначальному вопросу, с надлежащим объяснением.
Тьяллинг
2

Если вы объявляете имя для перечисления, никакой ошибки не произойдет.

Если не объявлено, вы должны использовать typedef:

enum enum_name {RANDOM, IMMEDIATE, SEARCH} strategy;
strategy = IMMEDIATE;

Это не будет отображать ошибку ...

Питер Мортенсен
источник
2

Моя любимая и единственная использованная конструкция всегда была:

typedef enum MyBestEnum
{
    /* good enough */
    GOOD = 0,
    /* even better */
    BETTER,
    /* divine */
    BEST
};

Я верю, что это устранит вашу проблему. Использование нового типа является, с моей точки зрения, правильным вариантом.

Sany
источник
1

Ответ Тарка самый лучший.

Большая часть обсуждения enum - красная сельдь.

Сравните этот фрагмент кода: -

int strategy;
strategy = 1;   
void some_function(void) 
{
}

который дает

error C2501: 'strategy' : missing storage-class or type specifiers
error C2086: 'strategy' : redefinition

с этим, который компилируется без проблем.

int strategy;
void some_function(void) 
{
    strategy = 1;   
}

Переменная strategy должна быть установлена ​​при объявлении или внутри функции и т. Д. Вы не можете писать произвольное программное обеспечение - в частности, назначения - в глобальной области видимости.

Тот факт, что он использовал enum {RANDOM, IMMEDIATE, SEARCH} вместо int, имеет отношение только к той степени, в которой он запутал людей, которые не могут видеть за его пределами. Сообщения об ошибках переопределения в вопросе показывают, что автор поступил неправильно.

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

Пример 1. НЕПРАВИЛЬНО!

enum {RANDOM, IMMEDIATE, SEARCH} strategy;
strategy = IMMEDIATE;
void some_function(void) 
{
}

Пример 2. ПРАВО.

enum {RANDOM, IMMEDIATE, SEARCH} strategy = IMMEDIATE;
void some_function(void) 
{
}

Пример 3. ПРАВО.

enum {RANDOM, IMMEDIATE, SEARCH} strategy;
void some_function(void) 
{
    strategy = IMMEDIATE;
}

Пример 4. ПРАВО.

void some_function(void) 
{
    enum {RANDOM, IMMEDIATE, SEARCH} strategy;
    strategy = IMMEDIATE;
}

Если у вас есть работающая программа, вы можете просто вставить эти фрагменты в вашу программу и увидеть, что некоторые компилируются, а некоторые нет.

Иван
источник
0

Я попытался с gcc и придумал для себя, я был вынужден использовать последнюю альтернативу, чтобы компилировать без ошибок.

typedef enum состояние {a = 0, b = 1, c = 2} состояние ;

typedef enum state {a = 0, b = 1, c = 2} state;

typedef enum state old; // New type, alias of the state type.
typedef enum state new; // New type, alias of the state type.

new now     = a;
old before  = b;

printf("State   now = %d \n", now);
printf("Sate before = %d \n\n", before);
гг
источник
newплохой выбор идентификаторов в семействе C, потому что это оператор в C ++.
jww
0

С

enum stuff q;
enum stuff {a, b=-4, c, d=-2, e, f=-3, g} s;

Объявление, которое действует как предварительное определение целого числа sсо знаком с полным типом, и объявление, которое действует как предварительное определение целого числа qсо знаком с неполным типом в области (которое преобразуется в полный тип в области, поскольку определение типа присутствует в любом месте область действия) (как и любое предварительное определение, идентификаторы qи sмогут быть повторно объявлены с неполной или полной версией того же типа intили enum stuffнесколько раз, но только один раз определены в области, то есть int q = 3; и могут быть переопределены только в подскопе, и можно использовать только после определения). Также вы можете использовать только полный типenum stuff в области видимости, потому что он действует как определение типа.

Определение типа перечисления для компилятора enum stuffтакже присутствует в области видимости файла (может использоваться до и ниже), а также в прямом объявлении типа (тип enum stuffможет иметь несколько объявлений, но только одно определение / завершение в области действия и может быть переопределен в подобласти). , Он также действует как директива компилятора для замены aна rvalue 0, bс -4, cс 5, dс -2, eс -3, fс -1и gс -2в текущей области видимости. Константы перечисления теперь применяются после определения до следующего переопределения в другом перечислении, которое не может быть на том же уровне области действия.

typedef enum bool {false, true} bool;

//this is the same as 
enum bool {false, true};
typedef enum bool bool;

//or
enum bool {false, true};
typedef unsigned int bool;

//remember though, bool is an alias for _Bool if you include stdbool.h. 
//and casting to a bool is the same as the !! operator 

Пространство имен тегов, совместно используемое enum, struct и union, является отдельным и должно начинаться с префикса с помощью ключевого слова type (enum, struct или union) в C, т. Е. После enum a {a} b, enum a cдолжно использоваться и не использоваться a c. Поскольку пространство имен тега отделено от пространства имен идентификатора, enum a {a} bоно разрешено, но enum a {a, b} bне потому, что константы находятся в том же пространстве имен, что и идентификаторы переменных, пространство имен идентификатора. typedef enum a {a,b} bтакже не допускается, потому что typedef-names являются частью пространства имен идентификатора.

Тип enum boolи константы следуют следующей схеме в C:

+--------------+-----+-----+-----+
|   enum bool  | a=1 |b='a'| c=3 |  
+--------------+-----+-----+-----+
| unsigned int | int | int | int |  
+--------------+-----+-----+-----+

+--------------+-----+-----+-----+
|   enum bool  | a=1 | b=-2| c=3 |  
+--------------+-----+-----+-----+
|      int     | int | int | int |  
+--------------+-----+-----+-----+

+--------------+-----+---------------+-----+
|   enum bool  | a=1 |b=(-)0x80000000| c=2 |
+--------------+-----+---------------+-----+
| unsigned int | int |  unsigned int | int |
+--------------+-----+---------------+-----+

+--------------+-----+---------------+-----+
|   enum bool  | a=1 |b=(-)2147483648| c=2 |
+--------------+-----+---------------+-----+
| unsigned int | int |  unsigned int | int |
+--------------+-----+---------------+-----+

+-----------+-----+---------------+------+
| enum bool | a=1 |b=(-)0x80000000| c=-2 |
+-----------+-----+---------------+------+
|    long   | int |      long     |  int |
+-----------+-----+---------------+------+

+-----------+-----+---------------+------+
| enum bool | a=1 | b=2147483648  | c=-2 |
+-----------+-----+---------------+------+
|    long   | int |      long     |  int |
+-----------+-----+---------------+------+

+-----------+-----+---------------+------+
| enum bool | a=1 | b=-2147483648 | c=-2 |
+-----------+-----+---------------+------+
|    int    | int |      int      |  int |
+-----------+-----+---------------+------+

+---------------+-----+---------------+-----+
|   enum bool   | a=1 | b=99999999999 | c=1 |
+---------------+-----+---------------+-----+
| unsigned long | int | unsigned long | int |
+---------------+-----+---------------+-----+

+-----------+-----+---------------+------+
| enum bool | a=1 | b=99999999999 | c=-1 |
+-----------+-----+---------------+------+
|    long   | int |      long     |  int |
+-----------+-----+---------------+------+

Это хорошо компилируется в C:

#include <stdio.h>
enum c j;
enum c{f, m} p;
typedef int d;
typedef int c;
enum c j;
enum m {n} ;
int main() {
  enum c j;
  enum d{l};
  enum d q; 
  enum m y; 
  printf("%llu", j);
}

C ++

В C ++ перечисления могут иметь тип

enum Bool: bool {True, False} Bool;
enum Bool: bool {True, False, maybe} Bool; //error

В этой ситуации все константы и идентификатор имеют одинаковый тип, bool, и произойдет ошибка, если число не может быть представлено этим типом. Возможно = 2, что не является булом. Кроме того, True, False и Bool не могут быть строчными, иначе они будут конфликтовать с ключевыми словами языка. Перечисление также не может иметь тип указателя.

Правила для перечислений различны в C ++.

#include <iostream>
c j; //not allowed, unknown type name c before enum c{f} p; line
enum c j; //not allowed, forward declaration of enum type not allowed and variable can have an incomplete type but not when it's still a forward declaration in C++ unlike C
enum c{f, m} p;
typedef int d;
typedef int c; // not allowed in C++ as it clashes with enum c, but if just int c were used then the below usages of c j; would have to be enum c j;
[enum] c j;
enum m {n} ;
int main() {
  [enum] c j;
  enum d{l}; //not allowed in same scope as typedef but allowed here 
  d q;
  m y; //simple type specifier not allowed, need elaborated type specifier enum m to refer to enum m here
  p v; // not allowed, need enum p to refer to enum p
  std::cout << j;
}

Переменные перечисления в C ++ больше не являются целыми числами без знака и т. Д., Они также имеют тип enum и могут быть назначены только константы в перечислении. Это может однако быть отброшено.

#include <stdio.h>
enum a {l} c;
enum d {f} ;
int main() {
  c=0; // not allowed;
  c=l;
  c=(a)1;
  c=(enum a)4;
  printf("%llu", c); //4
}

Enum классы

enum struct идентично enum class

#include <stdio.h>
enum class a {b} c;
int main() {
  printf("%llu", a::b<1) ; //not allowed
  printf("%llu", (int)a::b<1) ;
  printf("%llu", a::b<(a)1) ;
  printf("%llu", a::b<(enum a)1);
  printf("%llu", a::b<(enum class a)1) ; //not allowed 
  printf("%llu", b<(enum a)1); //not allowed
}

Оператор разрешения контекста все еще может использоваться для неперечисленных перечислений.

#include <stdio.h>
enum a: bool {l, w} ;
int main() {
  enum a: bool {w, l} f;
  printf("%llu", ::a::w);
}

Но поскольку вес не может быть определена как - то еще в объеме, нет никакой разницы между ::wи::a::w

Льюис Келси
источник