如何使用`switch`语句比较C中的字符串?


84

在C语言中,有一种switch结构可以使用户根据测试整数值执行不同的条件代码分支,例如,

int a;
/* Read the value of "a" from some source, e.g. user input */
switch (a) {
  case 100:
    // Code
    break;
  case 200:
    // Code
    break;
  default:
    // Code
    break;
}

对于字符串值(即a),如何获得相同的行为(即避免所谓的“ if-else阶梯”)char *


“打开”是什么意思?
kennytm 2010年

请重新制定格式,以使问题清楚地表明您实际上在尝试做什么(或询问)。
西蒙·托特

8
OP可能希望使用字符串作为switch语句的参数。据我所知,这是不可能的。
dandan78 2010年

Answers:


101

如果您的意思是,如何编写与此类似的内容:

// switch statement
switch (string) {
  case "B1": 
    // do something
    break;
  /* more case "xxx" parts */
}

然后,C语言中的规范解决方案是使用if-else梯形图:

if (strcmp(string, "B1") == 0) 
{
  // do something
} 
else if (strcmp(string, "xxx") == 0)
{
  // do something else
}
/* more else if clauses */
else /* default: */
{
}

1
实际上,问题是我已经在int上打开了一个开关,在特殊情况下,我想在同一开关中使用值“ B1”和“ B2”。唯一的方法是以某种方式转换“ B1”和“ B2”值并将其用作int !!?
尼克拉斯

2
@Niklas:这是您提出问题的重要信息。您可以更新您的问题并解释(如果可能的话,使用一些(伪)代码)您要做什么?
Bart van Ingen Schenau,2010年

4
@Niklas:您应该澄清您的问题:“ B1”和“ B2”到底怎么可能是int的特例?
埃德加·博内特

1
#define A 1 #define B 2 #define C S1 #define D S2这些是我要在交换机中使用的值。如此简单:-)
Niklas 2010年

5
@Niklas:定义不是字符串。如果定义是针对数字的,则可以像这样在开关中直接使用它switch (something) { case A: /*...*/ break; case B: /*...*/ break; }
Bart van Ingen Schenau,2010年

45

如果您有很多情况,并且不想写大量strcmp()电话,则可以执行以下操作:

switch(my_hash_function(the_string)) {
    case HASH_B1: ...
    /* ...etc... */
}

您只需要确保您的哈希函数在该字符串的可能值集中没有冲突。


8
“确保您的哈希函数在该字符串的可能值集中没有冲突。” -字母是否存在这种哈希函数[a-zA-Z0-9_]?有什么例子吗?
阿伦(Arun)2010年

8
@ArunSaha:显然不是针对此类字符的任意组合
Edgar Bonet 2010年

3
如果使用固定长度的字符串键,则可以将它们各自转换为唯一的整数;没有碰撞的可能。
工程师

@ArcaneEngineer Um ...这不是问题要解决的确切问题吗?仅给出字符串,如何选择一个整数呢?“使用开关或if / else梯形图”也许您是说4个字符之类的简短内容?
ebyrob

@ebyrob我的意思是在快速操作中具有可比性,例如2个64位,uint其位被视为8个1字节ASCII char。我前一阵子实现了此功能,以便在C中的哈希表中进行键比较。因此,您无需使用任何哈希或存储桶。问题出在需要超过64位的地方。然后,当您遍历char完整字符串中的每8 s集时,便需要为条件语句付出代价。除非您展开循环,否则如果您知道密钥的最大大小。这是一种很好的平衡行为。
工程师

39

用C语言无法做到这一点。有很多不同的方法。通常,最简单的方法是定义一组代表您的字符串的常量,然后按字符串进行查找以获取常量:

#define BADKEY -1
#define A1 1
#define A2 2
#define B1 3
#define B2 4

typedef struct { char *key; int val; } t_symstruct;

static t_symstruct lookuptable[] = {
    { "A1", A1 }, { "A2", A2 }, { "B1", B1 }, { "B2", B2 }
};

#define NKEYS (sizeof(lookuptable)/sizeof(t_symstruct))

int keyfromstring(char *key)
{
    int i;
    for (i=0; i < NKEYS; i++) {
        t_symstruct *sym = lookuptable[i];
        if (strcmp(sym->key, key) == 0)
            return sym->val;
    }
    return BADKEY;
}

/* ... */
switch (keyfromstring(somestring)) {
case A1: /* ... */ break;
case A2: /* ... */ break;
case B1: /* ... */ break;
case B2: /* ... */ break;
case BADKEY: /* handle failed lookup */
}

当然,有更有效的方法可以做到这一点。如果对键进行排序,则可以使用二进制搜索。您也可以使用哈希表。这些事情以牺牲维护成本为代价来改变您的性能。


7
对于密钥,使用枚举而不是一组#define会更好,但除此之外,您可以做到最好。
克雷格·林格

增量不正确。lookuptable + i * sizeof(t_symstruct)不等于lookuptable [i]。
asdf

@asdf这就是c语言中指针算法的工作方式。sizeof是隐式的。
ijustlovemath

20

我这样做的首选方法是通过哈希函数(从此处借用)。这使您即使在使用char *的情况下也可以利用switch语句的效率:

#include "stdio.h"

#define LS 5863588
#define CD 5863276
#define MKDIR 210720772860
#define PWD 193502992

const unsigned long hash(const char *str) {
    unsigned long hash = 5381;  
    int c;

    while ((c = *str++))
        hash = ((hash << 5) + hash) + c;
    return hash;
}

int main(int argc, char *argv[]) {
    char *p_command = argv[1];
    switch(hash(p_command)) {
    case LS:
        printf("Running ls...\n");
        break;
    case CD:
        printf("Running cd...\n");
        break;
    case MKDIR:
        printf("Running mkdir...\n");
        break;
    case PWD:
        printf("Running pwd...\n");
        break;
    default:
        printf("[ERROR] '%s' is not a valid command.\n", p_command);
    }
}

当然,此方法要求预先计算所有可能接受的char *的哈希值。我认为这不是一个太大的问题。但是,由于switch语句无论如何都对固定值进行操作。可以制作一个简单的程序来通过散列函数传递char *并输出其结果。然后,可以像上面一样通过宏定义这些结果。


欢迎使用堆栈溢出。您所显示的内容很好地呈现了一个不错的主意,但是…但是,与其他一些答案并没有那么明显的不同–在这个想法上有一些使用较小的变体。如果您为一个旧的稳定问题添加了新的答案,则应该确保自己掌握了很好的新信息。这主要是一个警告。我当然不会为此而投票。
乔纳森·勒夫勒

16

我认为最好的方法是将“识别”与功能分开:

struct stringcase { char* string; void (*func)(void); };

void funcB1();
void funcAzA();

stringcase cases [] = 
{ { "B1", funcB1 }
, { "AzA", funcAzA }
};

void myswitch( char* token ) {
  for( stringcases* pCase = cases
     ; pCase != cases + sizeof( cases ) / sizeof( cases[0] )
     ; pCase++ )
  {
    if( 0 == strcmp( pCase->string, token ) ) {
       (*pCase->func)();
       break;
    }
  }

}

8

我已经发布了一个头文件,以对C中的字符串执行切换。它包含一组宏,这些宏隐藏对strcmp()(或类似文件)的调用,以模仿类似开关的行为。我仅在Linux中的GCC上对其进行了测试,但是我非常确定它可以进行调整以支持其他环境。

编辑:根据要求在此处添加代码

这是您应包括的头文件:

#ifndef __SWITCHS_H__
#define __SWITCHS_H__

#include <string.h>
#include <regex.h>
#include <stdbool.h>

/** Begin a switch for the string x */
#define switchs(x) \
    { char *__sw = (x); bool __done = false; bool __cont = false; \
        regex_t __regex; regcomp(&__regex, ".*", 0); do {

/** Check if the string matches the cases argument (case sensitive) */
#define cases(x)    } if ( __cont || !strcmp ( __sw, x ) ) \
                        { __done = true; __cont = true;

/** Check if the string matches the icases argument (case insensitive) */
#define icases(x)    } if ( __cont || !strcasecmp ( __sw, x ) ) { \
                        __done = true; __cont = true;

/** Check if the string matches the specified regular expression using regcomp(3) */
#define cases_re(x,flags) } regfree ( &__regex ); if ( __cont || ( \
                              0 == regcomp ( &__regex, x, flags ) && \
                              0 == regexec ( &__regex, __sw, 0, NULL, 0 ) ) ) { \
                                __done = true; __cont = true;

/** Default behaviour */
#define defaults    } if ( !__done || __cont ) {

/** Close the switchs */
#define switchs_end } while ( 0 ); regfree(&__regex); }

#endif // __SWITCHS_H__

这是您的用法:

switchs(argv[1]) {
    cases("foo")
    cases("bar")
        printf("foo or bar (case sensitive)\n");
        break;

    icases("pi")
        printf("pi or Pi or pI or PI (case insensitive)\n");
        break;

    cases_re("^D.*",0)
        printf("Something that start with D (case sensitive)\n");
        break;

    cases_re("^E.*",REG_ICASE)
        printf("Something that start with E (case insensitive)\n");
        break;

    cases("1")
        printf("1\n");
        // break omitted on purpose

    cases("2")
        printf("2 (or 1)\n");
        break;

    defaults
        printf("No match\n");
        break;
} switchs_end;

我编辑示例时并未添加“中断”,而是强调了可以忽略的事实
Andrea Carron

1
更好!在我使用“ sscanf”进行匹配之前,学习了“ regex.h”,这与字符串大小写非常相关:)
LinconFive

与开关/机壳相比,这是一个多么漂亮的解决方案,可读性强,功能更多-谢谢!结束括号后,请不要忘记“ switchs_end:”。
Achim

6

有一种方法可以更快地执行字符串搜索。假设:由于我们在谈论switch语句,因此我可以假设值在运行时不会改变。

这个想法是使用C stdlib的qsort和bsearch。

我将研究xtofl的代码。

struct stringcase { char* string; void (*func)(void); };

void funcB1();
void funcAzA();

struct stringcase cases [] = 
{ { "B1", funcB1 }
, { "AzA", funcAzA }
};

struct stringcase work_cases* = NULL;
int work_cases_cnt = 0;

// prepare the data for searching
void prepare() {
  // allocate the work_cases and copy cases values from it to work_cases
  qsort( cases, i, sizeof( struct stringcase ), stringcase_cmp );
}

// comparator function
int stringcase_cmp( const void *p1, const void *p2 )
{
  return strcasecmp( ((struct stringcase*)p1)->string, ((struct stringcase*)p2)->string);
}

// perform the switching
void myswitch( char* token ) {
  struct stringcase val;
  val.string=token;
  void* strptr = bsearch( &val, work_cases, work_cases_cnt, sizeof( struct stringcase), stringcase_cmp );
  if (strptr) {
    struct stringcase* foundVal = (struct stringcase*)strptr;
    (*foundVal->func)();
    return OK;
  }
  return NOT_FOUND;
}

6

要添加到上面的Phimueme答案中,如果您的字符串始终是两个字符,则可以从两个8位字符中构建一个16位int-并将其打开(以避免嵌套的switch / case语句)。


如果您确实希望这样做To add to Phimueme's answer above,请随时使用注释功能。:)
洋葱骑士

3
@Onion:您会注意到,MikeBrom当前不具备评论自己的帖子和回答自己的问题的声誉。就是说,@ Mike“以上”在SO中比较滑,因为没有可靠的排序顺序。最好链接到诸如“ ...在Phimueme的答案中……”之类的答案(尽管该答案现在已删除,并且该链接仅对信誉超过10k的用户有用)。
dmckee ---前主持人小猫,2010年

3

为了将字符串与其他字符串进行比较,我们无法逃脱if-else阶梯。甚至常规的开关盒在内部也是if-else阶梯(对于整数)。我们可能只想模拟字符串的开关盒,但永远不能替换if-else阶梯。最好的字符串比较算法不能逃脱使用strcmp函数。表示逐字符比较直到找到不匹配的方法。因此,不可避免地要使用if-else阶梯和strcmp。

演示

这里是最简单的宏,用于模拟字符串的切换情况。

#ifndef SWITCH_CASE_INIT
#define SWITCH_CASE_INIT
    #define SWITCH(X)   for (char* __switch_p__ = X, int __switch_next__=1 ; __switch_p__ ; __switch_p__=0, __switch_next__=1) { {
    #define CASE(X)         } if (!__switch_next__ || !(__switch_next__ = strcmp(__switch_p__, X))) {
    #define DEFAULT         } {
    #define END         }}
#endif

您可以将它们用作

char* str = "def";

SWITCH (str)
    CASE ("abc")
        printf ("in abc\n");
        break;
    CASE ("def")              // Notice: 'break;' statement missing so the control rolls through subsequent CASE's until DEFAULT 
        printf("in def\n");
    CASE ("ghi")
        printf ("in ghi\n");
    DEFAULT
        printf("in DEFAULT\n");
END

输出:

in def
in ghi
in DEFAULT

下面是嵌套的SWITCH用法:

char* str = "def";
char* str1 = "xyz";

SWITCH (str)
    CASE ("abc")
        printf ("in abc\n");
        break;
    CASE ("def")                                
        printf("in def\n");
        SWITCH (str1)                           // <== Notice: Nested SWITCH
            CASE ("uvw")
                printf("in def => uvw\n");
                break;
            CASE ("xyz")
                printf("in def => xyz\n");
                break;
            DEFAULT
                printf("in def => DEFAULT\n");
        END
    CASE ("ghi")
        printf ("in ghi\n");
    DEFAULT
        printf("in DEFAULT\n");
END

输出:

in def
in def => xyz
in ghi
in DEFAULT

这是反向字符串SWITCH,在其中您可以在CASE子句中使用变量(而不是常量):

char* str2 = "def";
char* str3 = "ghi";

SWITCH ("ghi")                      // <== Notice: Use of variables and reverse string SWITCH.
    CASE (str1)
        printf ("in str1\n");
        break;
    CASE (str2)                     
        printf ("in str2\n");
        break;
    CASE (str3)                     
        printf ("in str3\n");
        break;
    DEFAULT
        printf("in DEFAULT\n");
END

输出:

in str3

“内部即使是常规开关箱也是一个if-else阶梯(对于整数)”,这是不正确的。如果可能,编译器将生成一个跳转表,该跳转表的效率将大大提高。请参阅stackoverflow.com/a/14067661/4990392
达达

2

通常,这就是我的操作方式。

void order_plane(const char *p)
{
    switch ((*p) * 256 + *(p+1))
    {
        case 0x4231 : /* B1 */
        {
           printf("Yes, order this bomber.  It's a blast.\n");
           break;
        }

        case 0x5354 : /* ST */
        {
            printf("Nah.  I just can't see this one.\n");
            break;
        }

        default :
        {
            printf("Not today.  Can I interest you in a crate of SAMs?\n";
        }
    }
}

有趣。缺乏防御性编码(可能是通过选择)。我很欣赏其他花括号,以防万一。使代码更具可读性(尽管我更喜欢使用埃及括号)。
Dariusz

1
顺便说一句,您可以在大小写标签中使用常量表达式。case 'B'<<8+'1':我认为这比0x4231更清楚。
詹斯(Jens)

我会使用一个宏。#define twochar(a) (((uint16_t)a[1]<<8)|a[0])
v7d8dpo4 '16

1

这就是你的做法。不,不是。

#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdint.h>


 #define p_ntohl(u) ({const uint32_t Q=0xFF000000;       \
                     uint32_t S=(uint32_t)(u);           \
                   (*(uint8_t*)&Q)?S:                    \
                   ( (S<<24)|                            \
                     ((S<<8)&0x00FF0000)|                \
                     ((S>>8)&0x0000FF00)|                \
                     ((S>>24)&0xFF) );  })

main (void)
{
    uint32_t s[0x40]; 
    assert((unsigned char)1 == (unsigned char)(257));
    memset(s, 0, sizeof(s));
    fgets((char*)s, sizeof(s), stdin);

    switch (p_ntohl(s[0])) {
        case 'open':
        case 'read':
        case 'seek':
            puts("ok");
            break;
        case 'rm\n\0':
            puts("not authorized");
            break;
        default:
            puts("unrecognized command");  
    }
    return 0;
}

3
我不认为这是标准C.
约翰Kotlinski

2
使宏支持混合字节序或函数供读者练习。

2
它是标准C,但不是可移植的。多字节char的字节顺序是“实现依赖”,不需要反映机器的字节顺序。我使用了一次,结果被烧毁:在Solaris SPARC(大端)GNU-C 3.4使用不同的字节顺序比Sunstudio 12
帕特里克SCHLÜTER

@tristopia你当然是对的(就像尝试做类似的事情一样真实)。这就是为什么我们都应该使用B的原因。

你为什么要杀了你的帐户?

1

如果它是2字节的字符串,则可以在此具体示例中执行类似的操作,在该示例中,我打开了ISO639-2语言代码。

    LANIDX_TYPE LanCodeToIdx(const char* Lan)
    {
      if(Lan)
        switch(Lan[0]) {
          case 'A':   switch(Lan[1]) {
                        case 'N': return LANIDX_AN;
                        case 'R': return LANIDX_AR;
                      }
                      break;
          case 'B':   switch(Lan[1]) {
                        case 'E': return LANIDX_BE;
                        case 'G': return LANIDX_BG;
                        case 'N': return LANIDX_BN;
                        case 'R': return LANIDX_BR;
                        case 'S': return LANIDX_BS;
                      }
                      break;
          case 'C':   switch(Lan[1]) {
                        case 'A': return LANIDX_CA;
                        case 'C': return LANIDX_CO;
                        case 'S': return LANIDX_CS;
                        case 'Y': return LANIDX_CY;
                      }
                      break;
          case 'D':   switch(Lan[1]) {
                        case 'A': return LANIDX_DA;
                        case 'E': return LANIDX_DE;
                      }
                      break;
          case 'E':   switch(Lan[1]) {
                        case 'L': return LANIDX_EL;
                        case 'N': return LANIDX_EN;
                        case 'O': return LANIDX_EO;
                        case 'S': return LANIDX_ES;
                        case 'T': return LANIDX_ET;
                        case 'U': return LANIDX_EU;
                      }
                      break;
          case 'F':   switch(Lan[1]) {
                        case 'A': return LANIDX_FA;
                        case 'I': return LANIDX_FI;
                        case 'O': return LANIDX_FO;
                        case 'R': return LANIDX_FR;
                        case 'Y': return LANIDX_FY;
                      }
                      break;
          case 'G':   switch(Lan[1]) {
                        case 'A': return LANIDX_GA;
                        case 'D': return LANIDX_GD;
                        case 'L': return LANIDX_GL;
                        case 'V': return LANIDX_GV;
                      }
                      break;
          case 'H':   switch(Lan[1]) {
                        case 'E': return LANIDX_HE;
                        case 'I': return LANIDX_HI;
                        case 'R': return LANIDX_HR;
                        case 'U': return LANIDX_HU;
                      }
                      break;
          case 'I':   switch(Lan[1]) {
                        case 'S': return LANIDX_IS;
                        case 'T': return LANIDX_IT;
                      }
                      break;
          case 'J':   switch(Lan[1]) {
                        case 'A': return LANIDX_JA;
                      }
                      break;
          case 'K':   switch(Lan[1]) {
                        case 'O': return LANIDX_KO;
                      }
                      break;
          case 'L':   switch(Lan[1]) {
                        case 'A': return LANIDX_LA;
                        case 'B': return LANIDX_LB;
                        case 'I': return LANIDX_LI;
                        case 'T': return LANIDX_LT;
                        case 'V': return LANIDX_LV;
                      }
                      break;
          case 'M':   switch(Lan[1]) {
                        case 'K': return LANIDX_MK;
                        case 'T': return LANIDX_MT;
                      }
                      break;
          case 'N':   switch(Lan[1]) {
                        case 'L': return LANIDX_NL;
                        case 'O': return LANIDX_NO;
                      }
                      break;
          case 'O':   switch(Lan[1]) {
                        case 'C': return LANIDX_OC;
                      }
                      break;
          case 'P':   switch(Lan[1]) {
                        case 'L': return LANIDX_PL;
                        case 'T': return LANIDX_PT;
                      }
                      break;
          case 'R':   switch(Lan[1]) {
                        case 'M': return LANIDX_RM;
                        case 'O': return LANIDX_RO;
                        case 'U': return LANIDX_RU;
                      }
                      break;
          case 'S':   switch(Lan[1]) {
                        case 'C': return LANIDX_SC;
                        case 'K': return LANIDX_SK;
                        case 'L': return LANIDX_SL;
                        case 'Q': return LANIDX_SQ;
                        case 'R': return LANIDX_SR;
                        case 'V': return LANIDX_SV;
                        case 'W': return LANIDX_SW;
                      }
                      break;
          case 'T':   switch(Lan[1]) {
                        case 'R': return LANIDX_TR;
                      }
                      break;
          case 'U':   switch(Lan[1]) {
                        case 'K': return LANIDX_UK;
                        case 'N': return LANIDX_UN;
                      }
                      break;
          case 'W':   switch(Lan[1]) {
                        case 'A': return LANIDX_WA;
                      }
                      break;
          case 'Z':   switch(Lan[1]) {
                        case 'H': return LANIDX_ZH;
                      }
                      break;
        }
      return LANIDX_UNDEFINED;
    }

LANIDX_ *是用于在数组中建立索引的常量整数。


0

假设字节序少且sizeof(char)== 1,则可以执行此操作(MikeBrom建议使用类似方法)。

char* txt = "B1";
int tst = *(int*)txt;
if ((tst & 0x00FFFFFF) == '1B')
    printf("B1!\n");

可以将其推广到BE案例。


2
不要那样做!这可能会导致“数据对齐”异常。不保证char * txt指向与int的对齐要求匹配的地址。
哈珀2010年

@R他要的。@harper x86并非如此。
ruslik 2010年

Niklas并没有要求使用x86。而且,由于您提到了大字节序的情况,因此您不能仅针对x86环境。所以”
哈珀

此外,多字节字符不必按机器字节顺序排列。看到我对jbcreix答案的评论。
帕特里克SCHLÜTER

0

函数指针是执行此操作的好方法,例如

result = switchFunction(someStringKey); //result is an optional return value

...这将调用您通过字符串键设置的功能(每种情况下一个功能):

setSwitchFunction("foo", fooFunc);
setSwitchFunction("bar", barFunc);

使用预先存在的hashmap / table / dictionary实现(例如khash),将该指针返回到内的函数switchFunction(),然后执行该指针(或仅返回switchFunction()并自己执行)。如果地图实现未存储该代码,则只需使用uint64_t,然后将其相应地转换为指针即可。


@ eri0o如果您认为它不错,那么为什么不投票呢?原来的下降投票者早已不复存在。
工程师,

-2

嗨,如果您遇到这种情况,这是简便快捷的方法:

[快速模式]

int concated;
char ABC[4]="";int a=1,b=4,c=2;            //char[] Initializing
ABC<-sprintf(ABC,"%d%d%d",a,b,c);          //without space between %d%d%d
printf("%s",ABC);                          //value as char[] is =142
concated=atoi(ABC);                        //result is 142 as int, not 1,4,2 (separeted)

//now use switch case on 142 as an integer and all possible cases

[专用模式]

例如:我有很多菜单,第一个菜单上的每个选项都将您带到第二个菜单,第二个菜单和第三个菜单的功能相同。但是选项是不同的,因此您知道用户已经选择了。例子:

菜单1:1 ==>菜单2:4 ==>菜单3:2(...)选择142.其他情况:111,141,131,122 ...

解决方案:将第一个第一个存储在c的a,第二个在b,第三个中。a = 1,b = 4,c = 2

 char ABC[4]="";
 ABC<-sprintf(ABC,"%d%d%d",a,b,c);              //without space between %d%d%d
 printf("%s",ABC);                              //value as char[]=142

      //now you want to recover your value(142) from char[] to int as  int value 142

 concated=atoi(ABC);                            //result is 142 as int, not 1,4,2 (separeted)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.