【问题标题】:Is there any way to use a constant array with constant index as switch case label in C?有没有办法在 C 中使用具有常量索引的常量数组作为 switch case 标签?
【发布时间】:2017-03-06 09:37:26
【问题描述】:

我有一些常量值和数组来定义它们的标签和哈希码。例如,

#define LABEL_A 0 //or const int LABEL_A = 0;
#define LABEL_B 1
#define LABEL_C 2
#define LABEL_D 3

const char *VALUE[] = {"LABEL_A", "LABEL_B", "LABEL_C", "LABEL_D"};
const int VALUE_HASH[] = {67490, 67491, 67493, 67459);

在运行时,这些标签可以以任何顺序出现,并且需要进行相应的解析。我为此目的使用开关盒。 此代码在编译时生成错误“需要常量表达式。

function(const char* LabelAtRuntime){
  int i = getHashCode(LabelAtRuntime);
  switch(i){
    case VALUE_HASH[LABEL_A]: //line giving compile time error
      break;
    default:
      break;
}

但是,当我提供实际常量时,它会起作用。 这段代码运行良好。

function(const char* LabelAtRuntime){
  int i = getHashCode(LabelAtRuntime);
  switch(i){
    case 67490: //line not giving compile time error
      break;
    default:
      break;
}
  1. 我无法理解,为什么会这样?我的数组和它的索引都是常量,那不就相当于一个常量字面量吗?
  2. 是否有任何其他方式可以以所需的方式提供我的常量?

我以这种方式使用常量来提供更好的代码语义、可读性和可重用性。请不要提供基于if-else 的解决方案。在上面的例子中,只有 4 个标签,但实际上可能有 100 个。

【问题讨论】:

  • 索引到数组实际上是取消引用一个指针。我相信这不能由编译器静态完成,因此会出现错误。
  • case 67490: //line giving compile time error..err..真的吗?
  • #define LABEL_A_HASH 67490 怎么样?
  • 选择 C ​​或 C++。 const int LABEL_A 的代码不会在 C 中编译,但可以在更高版本的 C++ 中编译。因此,对此给出“C/C++”答案没有意义,请从问题中删除任何一种语言。
  • 您是否考虑过创建一个指向函数的指针数组并在没有 if-else 语句或 switch-cases 的情况下简单地调用所需的函数?

标签: c arrays switch-statement


【解决方案1】:

在 C++ 中,编译:

#include <stdio.h>
#include <stdlib.h>

constexpr int x[] = { 42, 43 };

int main(int argc, char **argv)
{
    switch(atoi(argv[1]))
    {
        case x[0]: puts("forty_two");
                   break;
        case x[1]: puts("forty_three");
    }
    return 0;
}

所以数组上的constexpr 似乎是现代 C++ 中的解决方案。 (注意:问题最初标记为 C++ 和 C)

如果要保留数组,在 C 中是不可能的。 Switch case 需要integer constant,但是一旦将整数常量放入变量中,它就会成为运行时实体(即使它被声明为 const)。 你可以做的是用一堆直接定义替换内存中的数组,并且可能有一个使用其他宏查找宏的宏(如果你想保持你的代码形式):

#define LABEL_A 0
#define LABEL_B 1
#define LABEL_C 2
#define LABEL_D 2

#define VALUE_HASH__0 67490
#define VALUE_HASH__2 67491
#define VALUE_HASH__3 67491
#define VALUE_HASH__4 64759

//append what Index expands to to VALUE_HASH__
#define HASH_LOOKUP(Index) MC_cat(VALUE_HASH__,Index) 
#define MC_cat_(X,Y) X##Y
#define MC_cat(X,Y) MC_cat_(X,Y)

function(const char* LabelAtRuntime){
  int i = getHashCode(LabelAtRuntime);
  switch(i){
    case HASH_LOOKUP(LABEL_A)
      break;
    default:
      break;
}

【讨论】:

  • 抱歉,我正在寻找基于 C 的解决方案
  • @AmberBeriwal 好吧,那就不要标记它 C++ ;-)。我为您的问题添加了一个 C 解决方案。
【解决方案2】:

错误的原因仅仅是 C 不认为 const int LABEL_A=0; 是编译时常量。不幸的是,这就是语言的定义方式。可以改用#define LABEL_A 0来解决。

第三种选择是使用枚举,这可能会更好,因为它们可用于将所有数据绑定在一起并在维护期间提供更多的数据完整性:

typedef enum
{
  LABEL_A,
  LABEL_B,
  LABEL_C,
  LABEL_D,
  LABELS_N
} label_index_t;

typedef void func_t (void);

typedef struct
{
  const char* str;
  int         hash;
  func_t*     func;
} value_t;

...

const value_t VALUE [] = 
{
  [LABEL_A] = { .str = "LABEL_A", .hash = 67490, .func = a_func },
  [LABEL_B] = { .str = "LABEL_B", .hash = 67491, .func = b_func },
  [LABEL_C] = { .str = "LABEL_C", .hash = 67493, .func = c_func },
  [LABEL_D] = { .str = "LABEL_D", .hash = 67459, .func = d_func },
};

_Static_assert(sizeof VALUE / sizeof *VALUE == LABELS_N,
               "Size of VALUE does not match label_t.");

...

// instead of switch(n):
VALUE[n].func();

【讨论】:

  • 我相信我可以试试这个……但它是c兼容的还是c++兼容的?
  • @AmberBeriwal C++ 可能会遇到指定初始化程序之类的问题。为了最大限度地兼容古代 C 或 C++,您可以将每一行写为 {"LABEL_A", 67490, a_func},。将 _Static_assert 更改为 static_assert 并包含 assert.h。
  • 这个是一个很好的解决方案,但是没有接受多个答案的选项:D
【解决方案3】:

Switch case 需要在编译时知道这些值。在您的数组值的情况下,这些值直到运行时才知道。

如果您使用的是 c++11,则可以使用constexpr,它会强制编译器在编译时评估数组值。下面的代码运行良好。

constexpr int VALUE_HASH[] = {67490, 67491, 67493, 67459};

int i = getHashCode(LabelAtRuntime);
switch(i) {
  case VALUE_HASH[LABEL_A]:
    break;
  default:
    break;
}

【讨论】:

  • 他没有标记问题 C++ 所以constexpr 可能不是一个选项。
  • @JeremyP 抱歉,我之前错误地标记了 c 和 c++。所以也许,他当时提出了他的解决方案。
  • @Hesham 我正在寻找一个 c 解决方案
【解决方案4】:

我不知道你到底在做什么。但是当我不得不在 C 中实现一个菜单 UI 时,我是这样做的:

// Typedef for a menu item's logic function (callback):
typedef void (*menu_item_cb_t)(void*)

// Struct defining a menu item:
typedef struct menu_item {
  const char* label;
  const int hashValue;
  const menu_item_cb_t callback;
  const void* callback_arg;
} menu_item_t;


// Callback for menu item "Do X":
void menu_do_x( void* arg ) {
 // ...
}

// Definition of menu item "Do X":
const menu_item_t menu_item_x = {
  "Do X",
  12345,
  &menu_do_x,
  NULL // Don't need it to do x
}

// All menu items go into one array:
const menu_item_t* MENU[] = { &menu_item_x, ...};
#define MENU_ITEM_CNT xxx

然后您可以对选定的项目进行操作,例如:

void menuItemSelected( const char* label ) {
  const int hash = getHashCode(label);
  for ( int i = 0; i < MENU_ITEM_CNT; i++ ) {
    const menu_item_t* const mi = MENU[i];
    if ( hash == mi->hash ) {
      mi->callback( mi->callback_arg );
      break;
    }
  }
}

这种方法当然可以多种多样,但我希望你能明白。它基本上只是定义具有某些属性(“label”、“hash”和其他任何东西)的项目,并将它们与实现该项目相应操作的函数直接关联。

【讨论】:

  • 这与-Lundin 的回答非常相似。谢谢。非常好的解决方案。
  • 是的,确实如此。我们一定同时在写我们的答案:)
【解决方案5】:

如果VALUE_HASH只是用来获取开关常量,为什么不换成跳转表呢?

请注意,以下内容均未经过测试甚至编译。可能存在语法错误。

首先为表中的函数定义一个类型:

typedef void (*Callback)(/* parameters you need */);

那么你需要你的实际功能

void labelAProcessing(/* the parameters as per the typedef */)
{
    /// processing for label A
}
// etc

然后是你的桌子

Callback valueCallbacks[] = { labelAProcessing, labelBProcessing, ... };

你的代码变成了

int i = getHashCode(LabelAtRuntime);
valueCallbacks[i](/* arguments */);

我不能过分强调 i 需要经过验证以确保它是 valueCallbacks 数组的有效索引,否则,您可能会像调用函数一样调用随机数。

【讨论】:

  • 不会跳转表要求大小等于最大哈希值吗?
  • Hashcode 是一个误导性的名称。您只需要一个按标签编号索引的表格。即,就像哈希码从 0 开始并按顺序递增一样。
【解决方案6】:

操作数是常量是不够的。在编译时仅仅知道它们是不够的(无论 tbat 是什么意思,C 标准都没有用这些术语来说话)。案例标签必须是一个整数常量表达式

整数常量表达式由 C 标准严格定义。粗略地说,整数常量表达式必须由整数常量(也包括枚举数、字符常量等)构建而成,并且不能包含数组或指针,即使它们是常量。有关易于理解但透彻的解释,请参见例如this.

【讨论】:

  • 是否表示可以使用枚举?
  • 枚举是为编写switch 语句而发明的,所以是的,你可以使用它们。
猜你喜欢
  • 2012-06-29
  • 1970-01-01
  • 2010-09-06
  • 2016-03-25
  • 2011-11-02
  • 2022-06-16
  • 2013-08-29
  • 2010-09-10
  • 1970-01-01
相关资源
最近更新 更多