Лучший способ включить строку в C - PullRequest
65 голосов
/ 25 октября 2010

В 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 *?

Ответы [ 15 ]

88 голосов
/ 25 октября 2010

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

// 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: */
{
}
37 голосов
/ 25 октября 2010

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

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

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

26 голосов
/ 25 октября 2010

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

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

14 голосов
/ 09 мая 2016

Мой предпочтительный способ сделать это - использовать хеш-функцию (заимствовано из здесь ). Это позволяет использовать эффективность оператора 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);
    }
}

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

11 голосов
/ 25 октября 2010

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

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

}
5 голосов
/ 13 апреля 2015

Я опубликовал заголовочный файл для выполнения переключения строк в 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;
5 голосов
/ 20 мая 2013

Есть способ выполнить поиск строки быстрее. Предположения: поскольку речь идет об операторе 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;
}
5 голосов
/ 25 октября 2010

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

2 голосов
/ 29 августа 2012

Обычно я так делаю.

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";
        }
    }
}
1 голос
/ 13 августа 2018

Мы не можем уйти от лестницы 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

Ниже показано использование вложенного переключателя:

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
...