C++枚举是有符号还是无符号

Are C++ enums signed or unsigned?

本文关键字:无符号 符号 枚举 C++      更新时间:2023-10-16

C++枚举是有符号的还是无符号的?通过扩展,通过检查输入是否<=您的最大值来验证输入,并省略>=您的最小值(假设您从0开始并递增1)是否安全?

让我们去源头。 以下是 C++03 标准 (ISO/IEC 14882:2003) 文档在 7.2-5(枚举声明)中的内容:

枚举的基础类型 是一个整数类型,可以表示 中定义的所有枚举器值 枚举。是的 实现定义哪个积分 类型用作基础类型 对于枚举,除了 底层类型不得更大 比 int 除非 枚举器不能适合 int 或 无符号整数

简而言之,编译器可以选择(显然,如果某些枚举值为负数,则它将被签名)。

你不应该依赖任何特定的表示形式。 阅读以下链接。 此外,该标准说,它是实现定义的,哪个整数类型用作枚举的基础类型,除了它不能大于 int,除非某些值不能放入 int 或无符号 int。

简而言之:您不能依赖已签名或未签名的枚举。

你不应该依赖它们被签名或未签名。 如果要使它们显式签名或未签名,可以使用以下内容:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

你不应该依赖它是有符号的还是无符号的。根据标准,它是实现定义的,哪个整数类型用作枚举的基础类型。但是,在大多数实现中,它是一个有符号整数。

在 C++0x 中,将添加强类型枚举,这将允许您指定枚举的类型,例如:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

但是,即使是现在,也可以通过将枚举用作变量或参数类型来实现一些简单的验证,如下所示:

enum Fruit { Apple, Banana };
enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit
                    // even though it has the same value as banana.

即使是一些旧的答案也获得了44票,我倾向于不同意所有这些。简而言之,我认为我们不应该关心枚举的underlying type

首先,C++03 枚举类型本身就是一种独特的类型,没有符号的概念。从 C++03 标准dcl.enum

7.2 Enumeration declarations 
5 Each enumeration defines a type that is different from all other types....

因此,当我们谈论枚举类型的符号时,例如在使用<运算符比较 2 个枚举操作数时,我们实际上是在谈论将枚举类型隐式转换为某个整数类型。重要的是这种积分类型的标志。当将枚举转换为整型时,此语句适用:

9 The value of an enumerator or an object of an enumeration type is converted to an integer by integral promotion (4.5).

而且,显然,枚举的基础类型与积分提升无关。由于该标准对整体促销的定义是这样的:

4.5 Integral promotions conv.prom
.. An rvalue of an enumeration type (7.2) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration
(i.e. the values in the range bmin to bmax as described in 7.2: int, unsigned int, long, or unsigned long.

因此,枚举类型是变为signed int还是unsigned int取决于signed int是否可以包含定义的枚举器的所有值,而不是枚举的基础类型。

查看我的相关问题转换为整型后C++枚举类型不正确的符号

将来,使用 C++0x,可以使用强类型枚举,并具有多种优点(例如类型安全、显式基础类型或显式作用域)。 有了这个,您可以更好地确定该类型的符号。

编译器可以决定枚举是有符号的还是无符号的。

验证枚举的另一种方法是将枚举本身用作变量类型。 例如:

enum Fruit
{
    Apple = 0,
    Banana,
    Pineapple,
    Orange,
    Kumquat
};
enum Fruit fruitVariable = Banana;  // Okay, Banana is a member of the Fruit enum
fruitVariable = 1;  // Error, 1 is not a member of enum Fruit even though it has the same value as banana.

除了其他人已经说过的有符号/无符号之外,以下是标准对枚举类型范围的说法:

7.2(6):"对于 e(min) 是最小枚举器且 e(max) 是最大枚举器的枚举,枚举的值是 b(min) 到 b(max) 范围内的基础类型的值,其中 b(min) 和 b(max) 分别

是可以存储 e(min) 和 e(max) 的最小位域的最小值和最大值。可以定义一个枚举,该枚举的值未由其任何枚举器定义。

所以例如:

enum { A = 1, B = 4};

定义一个枚举类型,其中 e(min) 为 1,e(max) 为 4。如果基础类型是有符号的 int,则所需的最小位字段有 4 位,如果实现中的 int 是 2 的补码,则枚举的有效范围为 -8 到 7。如果基础类型是无符号的,则它有 3 位,范围为 0 到 7。如果您关心,请检查编译器文档(例如,如果要将枚举器以外的整数值强制转换为枚举类型,则需要知道该值是否在枚举范围内 - 如果不是,则未指定生成的枚举值)。

这些值是否是

函数的有效输入可能与它们是否是枚举类型的有效值是不同的问题。您的检查代码可能担心前者而不是后者,因此在此示例中至少应该检查>=A 和 <=B。

检查它是否std::is_signed<std::underlying_type + 作用域枚举默认为 int

https://en.cppreference.com/w/cpp/language/enum 意味着:

主.cpp

#include <cassert>
#include <iostream>
#include <type_traits>
enum Unscoped {};
enum class ScopedDefault {};
enum class ScopedExplicit : long {};
int main() {
    // Implementation defined, let's find out.
    std::cout << std::is_signed<std::underlying_type<Unscoped>>() << std::endl;
    // Guaranteed. Scoped defaults to int.
    assert((std::is_same<std::underlying_type<ScopedDefault>::type, int>()));
    // Guaranteed. We set it ourselves.
    assert((std::is_same<std::underlying_type<ScopedExplicit>::type, long>()));
}

GitHub 上游。

编译并运行:

g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main main.cpp
./main

输出:

0

在 Ubuntu 16.04、GCC 6.4.0 上测试。

虽然上面的一些答案可以说是正确的,但它们并没有回答我的实际问题。编译器 (gcc 9.3.0) 发出警告:

enum FOO_STATUS {
    STATUS_ERROR = (1 << 31)
};

警告是在使用时发出的:

unsigned status = foo_status_get();
if (STATUS_ERROR == status) {

(除了此代码不正确的事实...不要问。

当正确询问时,编译器不会发出错误。

enum FOO_STATUS {
    STATUS_ERROR = (1U << 31)
};

请注意,1U会使表达式无符号。