web-dev-qa-db-fra.com

Meilleur moyen d'allumer une chaîne en C

En C, il existe une construction switch qui permet d’exécuter différentes branches de code conditionnelles sur la base d’une valeur entière de test, par exemple:

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

Comment est-il possible d’obtenir le même comportement (c’est-à-dire d’éviter le prétendu "if-else ladder") pour une valeur de chaîne, c’est-à-dire un char *?

56
Niklas

Si vous voulez dire, comment écrire quelque chose de similaire à ceci:

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

Alors la solution canonique en C consiste à utiliser une échelle if-else:

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

Si vous avez plusieurs cas et que vous ne voulez pas écrire une tonne d'appels strcmp(), vous pouvez faire quelque chose comme:

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

Vous devez simplement vous assurer que votre fonction de hachage n'a pas de collision à l'intérieur de l'ensemble des valeurs possibles pour la chaîne.

36
Edgar Bonet

Il n'y a aucun moyen de faire cela en C. Il y a beaucoup d'approches différentes. Le plus simple consiste généralement à définir un ensemble de constantes représentant vos chaînes et à effectuer une recherche chaîne par chaîne pour obtenir la constante:

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

Il existe bien sûr des moyens plus efficaces de le faire. Si vous gardez vos clés triées, vous pouvez utiliser une recherche binaire. Vous pouvez aussi utiliser une table de hachage. Ces choses changent votre performance au détriment de la maintenance.

25
plinth

Ma méthode préférée pour cela consiste à utiliser une fonction de hachage (empruntée à ici ). Cela vous permet d'utiliser l'efficacité d'une instruction switch même lorsque vous utilisez des caractères car *:

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

Bien sûr, cette approche nécessite que les valeurs de hachage de tous les caractères acceptés acceptés soient calculées à l'avance. Je ne pense pas que cela pose trop de problème. Cependant, puisque l'instruction switch fonctionne indépendamment des valeurs fixes. Un programme simple peut être conçu pour faire passer les caractères * par la fonction de hachage et afficher leurs résultats. Ces résultats peuvent ensuite être définis via des macros, comme je l’ai déjà fait.

11
Matthew Rasa

Je pense que la meilleure façon de faire est de séparer la "reconnaissance" de la fonctionnalité:

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

}
11
xtofl

J'ai publié un fichier d'en-tête pour effectuer le basculement sur les chaînes en C. Il contient un ensemble de macros masquant l'appel à strcmp () (ou similaire) afin d'imiter un comportement semblable à un commutateur . Je ne l'ai testé qu'avec GCC sous Linux, mais je suis à peu près sûr qu'il peut être adapté pour prendre en charge un autre environnement.

EDIT: ajout du code ici, comme demandé

C'est le fichier d'en-tête que vous devriez inclure:

#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__

Et voici comment vous l'utilisez:

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
Andrea Carron

Pour ajouter à la réponse de Phimueme ci-dessus, si votre chaîne est toujours composée de deux caractères, vous pouvez créer un int de 16 bits sur les deux caractères de 8 bits - et l'activer (pour éviter les instructions imbriquées de type switch/case).

5
MikeBrom

Il existe un moyen d'effectuer la recherche de chaîne plus rapidement. Hypothèses: puisqu'il s'agit d'une instruction switch, je peux supposer que les valeurs ne changeront pas pendant l'exécution. 

L'idée est d'utiliser qsort et bsearch de C stdlib.

Je vais travailler sur le code de 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
Dariusz

C'est généralement comme ça que je le fais.

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

Voici comment tu le fais. Non, pas vraiment.

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

Nous ne pouvons pas échapper à if-else afin de comparer une chaîne avec d'autres. Même un commutateur standard est également une échelle if-else (pour les entiers) en interne. Nous voudrons peut-être seulement simuler le casse de la chaîne, mais ne pourrons jamais remplacer l'échelle if-else. Le meilleur des algorithmes de comparaison de chaînes ne peut échapper à l'utilisation de la fonction strcmp. Permet de comparer caractère par caractère jusqu'à ce qu'une non correspondance soit trouvée. Donc, utiliser if-else ladder et strcmp est inévitable.

DEMO

Et voici les macros les plus simples pour simuler le cas de commutation pour les chaînes.

#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

Et vous pouvez les utiliser comme

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

Sortie:

in def
in ghi
in DEFAULT

Vous trouverez ci-dessous l'utilisation de SWITCH imbriquée:

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

Sortie:

in def
in def => xyz
in ghi
in DEFAULT

Voici la chaîne inversée SWITCH, où vous pouvez utiliser une variable (plutôt qu'une constante) dans la clause 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

Sortie:

in str3
1
Ramu

Si c'est une chaîne de 2 octets, vous pouvez faire quelque chose comme dans cet exemple concret où j'allume les codes de langue 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_ * étant des entiers constants utilisés pour indexer dans des tableaux.

1
Patrick Schlüter

En supposant un peu de finalité et de taille (char) == 1, vous pouvez le faire (MikeBrom a suggéré quelque chose de ce genre).

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

Il pourrait être généralisé pour le cas BE.

0
ruslik