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

在 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;
}

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

328093 次浏览

If you mean, how to write something similar to this:

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

Then the canonical solution in C is to use an if-else ladder:

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

To add to Phimueme's answer above, if your string is always two characters, then you can build a 16-bit int out of the two 8-bit characters - and switch on that (to avoid nested switch/case statements).

If you have many cases and do not want to write a ton of strcmp() calls, you could do something like:

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

You just have to make sure your hash function has no collisions inside the set of possible values for the string.

I think the best way to do this is separate the 'recognition' from the functionality:

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;
}
}


}

There is no way to do this in C. There are a lot of different approaches. Typically the simplest is to define a set of constants that represent your strings and do a look up by string on to get the constant:

#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 */
}

There are, of course, more efficient ways to do this. If you keep your keys sorted, you can use a binary search. You could use a hashtable too. These things change your performance at the expense of maintenance.

Assuming little endianness and sizeof(char) == 1, you could do that (something like this was suggested by MikeBrom).

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

It could be generalized for BE case.

This is how you do it. No, not really.

#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;
}

If it is a 2 byte string you can do something like in this concrete example where I switch on ISO639-2 language codes.

    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_* being constant integers used to index in arrays.

This is generally how I do it.

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";
}
}
}

There is a way to perform the string search faster. Assumptions: since we are talking about a switch statement, I can assume that the values won't be changing during runtime.

The idea is to use the C stdlib's qsort and bsearch.

I'll be working on xtofl's code.

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;
}

I have published a header file to perform the switch on the strings in C. It contains a set of macro that hide the call to the strcmp() (or similar) in order to mimic a switch-like behaviour. I have tested it only with GCC in Linux, but I'm quite sure that it can be adapted to support other environment.

EDIT: added the code here, as requested

This is the header file you should include:

#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 *ss__sw = (x); bool ss__done = false; bool ss__cont = false; \
regex_t ss__regex; regcomp(&ss__regex, ".*", 0); do {


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


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


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


/** Default behaviour */
#define defaults    } if ( !ss__done || ss__cont ) {


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


#endif // __SWITCHS_H__

And this is how you use it:

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;

Please notice that there is no colon (':') after each case nor after the defaults.

My preferred method for doing this is via a hash function (borrowed from here). This allows you to utilize the efficiency of a switch statement even when working with char *'s:

#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);
}
}

Of course, this approach requires that the hash values for all possible accepted char *'s are calculated in advance. I don't think this is too much of an issue; however, since the switch statement operates on fixed values regardless. A simple program can be made to pass char *'s through the hash function and output their results. These results can then be defined via macros as I have done above.

Hi this is the easy and fast way if you have this case :

[QUICK Mode]

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

[EXPLAINED Mode]

for example : i have many menus, each choice on the 1st menu takes u to the 2nd menu, the same thing with the 2nd menu and the 3rd menu.but the Options are diferent so you know that the user has choised finnaly. exemple :

menu 1: 1 ==> menu 2: 4==> menu 3: 2 (...)the choice is 142. other cases : 111,141,131,122...

sollution: store the first 1st in a,2nd in b,3rd on c. 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)

Function pointers are a great way to do this, e.g.

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

...this calls a function that you have set by string key (one function per case):

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

Use a pre-existing hashmap/table/dictionary implementation such as khash, return that pointer to a function inside of switchFunction(), and execute it (or just return it from switchFunction() and execute it yourself). If the map implementation doesn't store that, just use a uint64_t instead that you cast accordingly to a pointer.

We cannot escape if-else ladder in order to compare a string with others. Even regular switch-case is also an if-else ladder (for integers) internally. We might only want to simulate the switch-case for string, but can never replace if-else ladder. The best of the algorithms for string comparison cannot escape from using strcmp function. Means to compare character by character until an unmatch is found. So using if-else ladder and strcmp are inevitable.

DEMO

And here are simplest macros to simulate the switch-case for strings.

#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

And you can use them as

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

Output:

in def
in ghi
in DEFAULT

Below is nested SWITCH usage:

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

Output:

in def
in def => xyz
in ghi
in DEFAULT

Here is reverse string SWITCH, where in you can use a variable (rather than a constant) in CASE clause:

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

Output:

in str3

Comparing using an if () else if () chain is a linear search, offering O(n) time complexity. For a large number of strings, one option is using bsearch() (binary search) to achieve O(log n) time complexity. Below is an example of my use of bsearch to perform a given action based on an input string. (Specifically, my example finds a given mathematical function by name).

static int c(const void *const restrict a, const void *const restrict b) {
return strcmp(*(const char *const *)a, *(const char *const *)b);
}
static double (*func(const char *const str))(double) {
static const char *const s[] = {"abs", "acos", "acosh", "asin", "asinh", "atan", "atanh", "cbrt", "cos", "cosh", "ln", "log", "sin", "sinh", "sqrt", "tan", "tanh"};
static double (*const f[])(double) = {fabs, acos, acosh, asin, asinh, atan, atanh, cbrt, cos, cosh, log, log10, sin, sinh, sqrt, tan, tanh};
const char *const *const r = bsearch(&str, s, sizeof(s)/sizeof(*s), sizeof(*s), c);
return r ? f[r-s] : NULL;
}