是否有任何方法可以将带有常数索引的常数数组作为开关案例标签



我有一些恒定值和数组定义其标签和哈希代码。例如,

#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个标签。

在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中。开关案例需要一个整数常数,但是一旦将整数常数放入变量中,它就会成为运行时实体(即使它被声明为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不认为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 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中实现菜单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;
    }
  }
}

这种方法当然可以变化,但我希望您明白这个想法。基本上,它只是定义具有某些属性的项目("标签","哈希",以及其他任何内容),并将它们与实现此项目的相应操作的函数联系起来。

如果VALUE_HASH仅用于获取交换机常数,为什么不用跳台替换它?

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

首先为表中的fucntions定义类型:

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数组的有效索引,因为否则,您可以将随机号码称为函数。

它不足以使操作数成为常数。在编译时不足以使他们知道(无论TBAT的意思是什么,C标准都不会在这些术语中说话)。案例标签必须为整数常数表达式

整数常数表达式由C标准严格定义。粗略地,整数常数必须由整数常数(也枚举,字符常数等)构建,并且即使它们是恒定的,也不能包含数组或指针。有关可访问但thirough的解释,请参见例如这个。

最新更新