Як визначити перелічений тип (enum) у C?


272

Я не впевнений, який правильний синтаксис для використання C enums. У мене є такий код:

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

Але це не компілюється із наступною помилкою:

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

Що я роблю неправильно?


7
Багаторічне запитання, напевно, ніхто цього не побачить; але чому це помилка? Це повинно працювати чудово, як це стосується моїх знань.
Utkan Gezer

2
@Solver, чому цей синтаксис неправильний?
MCG

6
@MCQ, некроювання necro'd некро: Синтаксис, представлений у запитанні, не є помилковим у C. Він оголошує strategy, що має анонімний перелічений тип, і присвоює йому одне із оголошених значень цього типу. Більше того, якщо я обертаю код, представлений у іншому випадку, тривіальна main()функція, то він складається добре для мене, навіть не попереджуючи, з gcc 4.4.7. Деякі відповіді означають те саме, хоч і не так багато слів.
Джон Боллінгер

5
Більшість відповідей пропускає той факт, що два рядки коду у питанні - це не просто фрагмент. Вони - весь вихідний файл. Якщо ці два рядки включені в тіло функції, помилки немає. Якщо вони з’являються в області файлу, поза будь-якою декларацією функції, ви отримаєте помилки, про які запитував ОП (плюс деякі інші, коли я його спробував). Принципова проблема полягає в тому, що компілятор намагається трактувати strategy = IMMEDIATE;як декларацію. Він має форму, яка була б законною в попередньому ANSI C, але в сучасному C - незаконна. Призначення заборонено в межах файлу.
Кіт Томпсон

3
@Solver: enum strategy { ... };визначає перелічений тип з назвою enum strategy, де strategyтег. enum { ... } strategy;визначає анонімний перелічений тип (без тегів) та один об'єкт цього типу з назвою strategy. Обидва цілком законні; вони просто означають різні речі.
Кіт Томпсон

Відповіді:


377

Оголошення змінної enum робиться так:

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

Однак ви можете використовувати a typedefдля скорочення оголошень змінної, наприклад:

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

Положення про іменування для розмежування типів і змінних - це гарна ідея:

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

1
Але ОП хотів змінну типу анонімного перерахунку
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
Це не повинно бути прийнятою відповіддю, оскільки це неправильно. Ви не можете використовувати стратегію перерахунку {...}; в C - ви можете і повинні робити це в C ++, хоча.
Ясніше

19
@Clearer: Цей код працює чудово. Ось робочий приклад: ideone.com/T0YV17 Зверніть увагу, що він використовує enumключове слово в обох рядках.
RichieHindle

Або "typedef enum strategy {RANDOM, IMMEDIATE, SEARCH} strategy_t;" і розробник, що використовує enum, може використовувати будь-яку конвенцію.
Енді Нугент

це працює чудово: enum strategy { RANDOM, IMMEDIATE, SEARCH }; тоді, коли вам потрібен екземпляр цього enum: `enum strategy myEnum;
користувач3629249

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, як я вже говорив раніше, ви повинні використовувати теги, якщо ви хочете оголосити intvars з використанням 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};

10

Варто зазначити, що в C ++ ви можете використовувати "enum" для визначення нового типу, не потребуючи оператора typedef.

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

Я вважаю такий підхід набагато привітнішим.

[редагувати - уточнено статус C ++ - у мене це було спочатку, потім його видалили!]


Так, ніколи не слід використовувати typedef з перерахунками (або структурами, об'єднаннями тощо) в C ++.

17
Це питання для C, а не для C ++. У C наведений вище код недійсний - вам або потрібно використовувати typedefабо вказати також enumу змінному декларації: стратегія enum {RANDOM, IMMEDIATE, SEARCH}; ... стратегія перерахунку myStrategy = IMMEDIATE;
Павло Мінаєв

@pavel - моє погано. Спочатку я мав "в С ++", потім робив деякі дослідження, які, здавалося, суперечили цьому.
Родді

@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;

1
Чудова відповідь. Я також натрапив на визначення перелічень, написаних так: 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
};

Я вірю, що це усуне вашу проблему. Використання нового типу - з моєї точки зору правильний варіант.


1

Відповідь Тарка найкраща.

Значна частина дискусій про переживання - червона оселедець.

Порівняйте цей фрагмент коду: -

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 state {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, є окремим і повинен бути префіксований ключовим словом типу (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 є частиною простору імен ідентифікатора.

Тип enum boolта константи дотримуються наступної схеми в С:

+--------------+-----+-----+-----+
|   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 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 не може бути визначений як щось інше в області, немає різниці між ::wі::a::w

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.