簡體   English   中英

為什么枚舉作為類型存在於 C

[英]Why enum exists as a type in C

我從教科書中了解到,枚舉的典型定義是這樣的:

enum weather {
    sunny,
    windy,
    cloudy,
    rain,
} weather_outside;

然后像這樣聲明一個var:

enum weather weather_outside = rain;

我的問題是,如果僅通過說rain保持 integer 3 就可以使用枚舉常量,那么像enum weather weather_outside = rain;這樣的類型更復雜的減速到底有什么用和意義? weather_outside等於 3(因為枚舉值只能是編譯時常量)? 為什么不直接使用const或宏呢? 我有點困惑枚舉是否真的有必要?!

C 中的枚舉主要是為了方便,因為它們不是強類型的。 它們的創建是為了表達命名選項,但語言發展的局限性以及人們將它們用於其他用途的方式導致了目前它們只不過是命名為 integer 值的情況。

枚舉支持我們有各種不同選項的情況,例如您顯示的天氣狀況,並希望為它們命名。 理想情況下,枚舉應該是強類型的,這樣rain就不容易轉換為 3,反之亦然; int x = rain; enum weather x = 3; 會從編譯器中產生警告或錯誤。

但是,這樣做存在問題。 考慮一下我們何時要編寫處理枚舉中所有值的代碼,例如:

for (enum weather i = sunny; i <= rain; i = i+1)
    DoSomethingWithWeatherCondition(i);

看看那個更新表達式, i = i+1 這對於有經驗的 C 程序員來說是再自然不過的了。 (我們可以寫成i++ ,但那是同一件事,這里拼寫出來是為了說明。)我們知道它會將i更新為下一個值。 但是,當我們考慮它時,什么是i+1 i是一個枚舉值, 1是一個int 所以我們要添加兩個不同的東西。

為了實現這一點,C 將枚舉值視為整數。 這允許i+1以普通方式計算為兩個整數的加法。 , which means we have to allow assigning an int to an enum weather .此外,結果是一個int ,我們有i = ,這意味着我們必須允許將一個int分配給一個enum weather

也許對此的一種解決方案是定義枚舉值和整數的加法,這樣i+1就不需要將i視為 integer; 它只是定義為在i之后返回枚舉中的下一個值。 但是早期的C開發並沒有這樣做。 這本來會是更多的工作,而且編程語言中的新功能並不是在有先見之明的情況下一次性開發出來的,這些功能會很有用,或者可能會出現什么問題。 它們通常是一點一點開發的,用少量原型代碼嘗試新事物。

因此,枚舉值是整數。 一旦它們成為整數,人們就開始將它們用於簡單的原始目的之外的目的。 枚舉對於定義可在編譯器需要常量表達式的地方使用的常量很有用,包括數組維度和 static 對象的初始值。 const當時不存在,但它不會起作用,因為定義了const int x = 3; 甚至static const int x = 3; , 我們不能在float array[x];中使用x . (可變長度 arrays 當時不存在,即使現在也不能用於 static 對象。)我們也不能在int m = 2*x+3;中使用x m的定義在 function 之外時(因此它定義了一個 static 對象)。 但是,如果x被定義為枚舉值而不是int ,它可以用於這些目的。

這導致在沒有真正枚舉事物的情況下使用枚舉。 例如,它們通常用於各種位掩碼:

enum
{
    DeviceIsReadable           = 1,
    DeviceIsWriteable          = 2,
    DeviceSupportsRandomAccess = 4,
    DeviceHasFeatureX          = 8,
    …
}

一旦人們開始以這種方式使用枚舉,就無法對枚舉進行強類型化和定義算術運算了。 這些位掩碼必須可用於按位運算符| , &^ ,而不僅僅是+1 人們將它們用於任意常數和算術運算。 如果要重新定義 C 語言的這一部分並更改現有代碼,那就太困難了。

因此,枚舉從未在 C 中發展為適當的獨立類型。

這不是正確的語法:

warning: unused variable 'weather_outside'

這個例子:

enum light {green, yellow, red};

enum weather { sunny, windy, cloudy, rain,};

enum weather wout;
wout = red;  // mismatch

使用-Wextra發出警告:

implicit conversion from 'enum light' to 'enum weather'

這有助於防止錯誤。


const一開始不存在,可以是一個很好的選擇; 但是使用enum你不必分配一個數字 - 但你可以:

enum {sunny, windy, cloudy, 
      rain = 100, snow}

這一定是獲得兩個分離區域 (0,1,2,100,101) 的最緊湊方式。

這兩種方法在功能上是等效的,但枚舉可以讓您更好地表達某物是幾個命名選擇之一 “Rainy”比“3”更容易理解,“South”比“2”更容易理解。 它還限制了枚舉可以采用的值*。

使用typedef有助於使代碼不那么冗長:

typedef enum
{
    SUNNY,
    WINDY,
    CLOUDY,
    RAIN
} Weather;

Weather weather_outside = RAIN;

switch (weather_outside)
{
    case SUNNY:
        printf("It's sunny\n");
        break;

    case WINDY:
        printf("It's windy\n");
        break;

    // ...
}

這里的另一個優點是,如果不是所有枚舉值都在開關中處理,編譯器可能會發出警告,如果weather_outside是 integer,則不會發出警告。

看看 function 聲明,這個:

void takeStep(Direction d)

比以下更具表現力:

void takeStep(int d)

當然你也可以寫成int direction ,但這是用一個變量來表示一個類型

[*] 技術上允許編寫Weather weather_outside = 12 ,因為枚舉值是integer 常量,但這應該看起來像代碼味道。

您的代碼無效 當你寫

enum weather {
    sunny,
    windy,
    cloudy,
    rain,
} weather_outside;

您已經聲明了一個名為enum weather的新類型一個名為weather_outside的新變量。 enum weather weather_outside = rain; 將創建一個具有相同名稱的新變量,因此我嘗試過的所有編譯器都會在該變量上發出錯誤

所以正確的做法是去掉第一個變量定義

enum weather {
    // ...
};
enum weather weather_outside = rain;

或者使用typedef來避免到處使用enum

typedef enum {
    // ...
} weather;
weather weather_outside = rain;

由於命名空間污染,后者在 C 中可能不是好的做法,在 Linux kernel 中被禁止


回到主要問題。

enum weather weather_outside = rain; weather_outside等於 3(因為枚舉值只能是編譯時常量)? 為什么不直接使用const或宏呢? 我有點困惑枚舉是否真的有必要?!

語義上3沒有任何意義,當在它之前插入一個新的枚舉成員時,沒有什么能阻止rain改變值。 命名值總是比幻數好。 除此之外,它還限制了weather_outside可以接受的值的范圍。 如果您看到或必須執行weather_outside = 123那么您就知道出了問題

為了避免使用神奇的數字,我也可以只使用宏#define RAIN 3

所有大寫字母都更難閱讀,並且通常不鼓勵使用inline (對於函數)或const (對於值)宏。 但最重要的是:

  • enum允許調試器將當前值顯示為名稱,這在調試時非常有用。 沒有人知道123是什么意思,但他們肯定明白windy代表什么

    它在這個例子中可能沒有那么有用,但假設你有一個包含 200 個不同值的巨大枚舉,你如何在不計算的情況下知道第 155 個項目是什么? 中間項目也可以重新編號,因此它們的值不再對應於它們的位置

    我敢肯定,當您有 200 行const#define時,您將無法記住所有這 200 個值。 繼續查看 header 文件,因為該值很乏味。 你將如何獲得const int my_weather = sunny | windy的值? const int my_weather = sunny | windy還是#define RAIN (cloudy + floody) 無需跟蹤那些enum 它只是有效

    enum { sunny = X, windy = Y, my_weather = sunny | windy, cloudy, floody, rain = cloudy + rain }
  • enum允許您在數組聲明中使用常量

    enum array_items { SPRING, SUMMER, FALL, WINTER, NUMBER_OF_SEASONS }; int a[NUMBER_OF_SEASONS] = { 1, 2, 3, 4}; // const int MAX_LENGTH = 4; // int b[MAX_LENGTH]; /* doesn't work without VLA */ return a[0];

對於類型可能有用的示例, enum text_color {... }; void set_text_color(enum text_color col); enum text_color {... }; void set_text_color(enum text_color col); – 平庸的蔬菜1

我也可以調用set_text_color(2)而不會從我的編譯器那里得到任何警告!

這是 C 和 gcc 的限制,因為enum只是 C 中的 integer 類型,而不是 C++ 中的真實類型,因此 gcc 可能無法對其進行大量檢查。 ICC 可以就此向您發出警告 Clang 也比 gcc 有更好的警告。參見:

當然,C 中的enum不會阻止您像 C++ 中的enum class那樣開槍射擊自己,但它比宏或常量要好得多

是的,在一個層面上,使用 integer 變量和一組預處理器 #defines 幾乎完全等同於使用enum 您實現了相同的目標:少量不同的值,沒有必要的數字解釋,緊湊地編碼為(通常)小整數,但在源代碼中由更有意義的符號名稱表示。

但在某種程度上,預處理器是一種拼湊,現代實踐建議盡可能避免使用它。 而枚舉,因為它們是編譯器本身已知的,所以有許多額外的優點:

  • 類型安全——如果您使用不屬於的值,編譯器會警告您(例如weather = green
  • 調試——調試器可以將枚舉的值顯示為它的符號名稱,而不是一個難以理解的數字
  • 附加警告——如果你對枚舉進行了switch但忘記了其中一種情況,編譯器會警告你

枚舉是整數,可以用作常量表達式。

enum weather {
    sunny,
    windy,
    cloudy,
    rain,
} weather_outside;

int main(void)
{
    int weather = cloudy;
    printf("%d\n", rain);
    printf("`weather`==%d\n", weather);
}

https://godbolt.org/z/939KvreEY

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM