Лучший способ включить строку в 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;
}

Как можно получить такое же поведение (т.е. избежать так называемого "if-else лестница ") для строкового значения, т. е. char *?

16 ответов

Если вы имеете в виду, как написать что-то похожее на это:

// 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: */
{
}

Если у вас много дел и вы не хотите писать тонну strcmp() звонки, вы можете сделать что-то вроде:

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

Вам просто нужно убедиться, что ваша хеш-функция не имеет коллизий внутри набора возможных значений для строки.

Нет никакого способа сделать это в 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 */
}

Есть, конечно, более эффективные способы сделать это. Если вы сохраняете свои ключи отсортированными, вы можете использовать бинарный поиск. Вы также можете использовать хеш-таблицу. Эти вещи меняют вашу производительность за счет обслуживания.

Мой предпочтительный способ сделать это через хеш-функцию (заимствовано здесь). Это позволяет вам использовать эффективность оператора switch даже при работе с char *:

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

Конечно, этот подход требует, чтобы хеш-значения для всех возможных принятых символов * были рассчитаны заранее. Я не думаю, что это слишком большая проблема; тем не менее, поскольку оператор switch работает с фиксированными значениями независимо. Можно создать простую программу для передачи char * через хеш-функцию и вывода их результатов. Эти результаты затем могут быть определены с помощью макросов, как я сделал выше.

Я думаю, что лучший способ сделать это - отделить "распознавание" от функциональности:

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

}

Я опубликовал файл заголовка для выполнения переключения строк в C. Он содержит набор макросов, которые скрывают вызов strcmp() (или аналогичного) для имитации поведения, подобного переключателю. Я протестировал его только с GCC в Linux, но я совершенно уверен, что его можно адаптировать для поддержки других сред.

РЕДАКТИРОВАТЬ: добавил код здесь, как и просили

Это заголовочный файл, который вы должны включить:

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

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

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

Есть способ выполнить поиск строки быстрее. Предположения: поскольку мы говорим об операторе switch, я могу предположить, что значения не изменятся во время выполнения.

Идея состоит в том, чтобы использовать qsort и bsearch C stdlib.

Я буду работать над кодом 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;
}

Чтобы добавить ответ Phimueme выше, если ваша строка всегда состоит из двух символов, вы можете создать 16-разрядное целое число из двух 8-разрядных символов - и включить его (чтобы избежать вложенных операторов switch/case).

Мы не можем уйти от лестницы if-else, чтобы сравнить строку с другими. Даже обычный switch-case также является внутренним лестницей if-else (для целых чисел). Мы могли бы только симулировать случай переключения для строки, но никогда не могли бы заменить лестницу if-else. Лучшие из алгоритмов сравнения строк не могут избежать использования функции strcmp. Означает сравнивать символ за символом, пока не будет найдено совпадение. Поэтому использование if-else ladder и strcmp неизбежно.

DEMO

А вот простейшие макросы для имитации переключателя для строк.

#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

Если это 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_ * - это постоянные целые числа, используемые для индексации в массивах.

Это обычно, как я это делаю.

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

Предполагая, что порядок байтов немного меньше, а sizeof(char) == 1, вы можете сделать это (что-то подобное было предложено MikeBrom).

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

Это может быть обобщено для случая BE.

Вот как ты это делаешь. Нет, не совсем.

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

Функциональные указатели являются отличным способом сделать это, например,

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

... это вызывает функцию, которую вы установили с помощью строкового ключа (одна функция на случай):

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

Используйте уже существующую реализацию hashmap/table/dictionary, такую ​​как khash, верните этот указатель на функцию внутри switchFunction()и выполнить его (или просто вернуть его из switchFunction() и выполнить его самостоятельно). Если реализация карты не хранит это, просто используйте uint64_t вместо этого, что вы приведете в соответствии с указателем.

Сравнение с использованиемif () else if ()chain — это линейный поиск, предлагающийO(n)временная сложность. Для большого количества строк используется один из вариантовbsearch()(двоичный поиск) для достиженияO(log n)временная сложность. Ниже приведен пример моего использованияbsearchдля выполнения заданного действия на основе входной строки. (В частности, мой пример находит заданную математическую функцию по имени).

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

Привет это простой и быстрый способ, если у вас есть этот случай:

[БЫСТРЫЙ режим]

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-м меню приводит вас ко 2-му меню, то же самое относится и ко 2-му и 3-му меню. Но параметры отличаются, так что вы знаете, что пользователь выбрал конечную версию. пример:

меню 1: 1 ==> меню 2: 4==> меню 3: 2 (...) на выбор 142. другие случаи: 111,141,131,122...

Раствор: хранить первый 1-й в а, 2-й в б,3-й на в. а = 1, б =4, с = 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)
Другие вопросы по тегам