开发者

write a C construct

开发者 https://www.devze.com 2023-03-25 10:19 出处:网络
Is there a way (in C) to write a construct like the switch statement, but for strings? Is there a way to write a C construct at all in C?

Is there a way (in C) to write a construct like the switch statement, but for strings? Is there a way to write a C construct at all in C?

By C construct I mean a statement with braces ... like an if statement has brace开发者_如何学Gos, and it's a C construct... right?


The simplest approach is an if-else chain using strcmp to do the comparisons:

if (strcmp(str, "String 1") == 0)
  // do something
else if (strcmp(str, "String 2") == 0)
  // do something else
else if (strcmp(str, "String 3") == 0)
  // do something else 
...
else
  printf("%s not found\n", str);

A more complicated approach is to use a lookup table, keyed by the string:

struct lookup {const char *key; int value};

struct lookup LookupTable[] = {
  {"String 1", 1}, 
  {"String 2", 2}, 
  {"String 3", 3}, 
  ...
  {NULL, -1}
};

int lookup(const char *key)
{
  size_t i = 0;
  while (LookupTable[i].key != NULL)
    if (strcmp(str, LookupTable[i].key) == 0)
      return LookupTable[i].value;
    else
      i++;
  return -1;
}
...
switch(lookup(str))
{
  case 1: ...
  case 2: ...
  case 3: ...
  ...
  default: printf("%s not found\n", str); break;
}

If you want to get really fancy, you could modify the lookup table so that the value is a pointer to a function:

void String1Cmd(void) { ... }
void String2Cmd(void) { ... }
void String3Cmd(void) { ... }
...
void BadCmd(void) { printf("Key not found!\n"); }

struct lookup {char *key, void (*cmd)(void); };

struct lookup LookupTable[] = {
  {"String 1", String1Cmd}, 
  {"String 2", String2Cmd}, 
  {"String 3", String3Cmd}, 
  ...
  {NULL, BadCmd}
};

void (*lookup(const char *str))(void)
{
  size_t i = 0;
  while(LookupTable[i].key != NULL)
    if (strcmp(str, LookupTable[i].key) == 0)
      return LookupTable[i].cmd;
    else
      i++;
  return BadCmd;
}
...
void (*f)(void) = lookup(str); // retrieve the function for the given string
f(); // execute the function

In the last example, if str == "String 1", then String1Cmd will be executed. If str is a string not found in the lookup table, then BadCmd will be executed. This method is very flexible, and depending on your design, allows you to add behavior at runtime (sort of a plug-in architecture).

However, note that we've just deferred the main problem - branching on a string value - to the lookup function, and that the lookup function is back to just doing strcmp against each value in the table. We could speed that part of the process up by using a hash table or a tree to minimize the number of comparisons. Depending on how many strings you're branching on, that may or may not be worth the additional effort.


No, you have to do it yourself. There are many variants:

if (strcmp(str, "toto") == 0)
{
    // ...
}

else if (strcmp(str, "tata") == 0)
{
    // ...
}

else
{
    // ...
}

If the number of strings is expected to grow, then a dispatch table with function pointers

struct dispatch_entry
{
    const char *key;
    void (*action)(void);
};

// Make sure it is sorted !
dispatch_entry dispatch_table[] = 
{
    { "tata", &action_tata },
    { "toto", &action_toto },
};

coupled with binary search:

int dispatch_compare(const void *x, const void *y)
{
    const dispatch_entry *xx = x, *yy = y;
    return strcmp(xx->key, yy->key);
}

// Return -1 on failure
int dispatch(const char *str)
{
    static const size = sizeof(struct dispatch_entry);
    static const n = sizeof(dispatch_table) / size ;

    dispatch_entry tmp = { str, NULL };
    dispatch_entry *what = bsearch(tmp, dispatch_table, n, size, &dispatch_compare);

    if (what == NULL) return -1;

    (*what->action)();
    return 0;
}

will do. Hash table based approaches are OK as well.


if you have the function lfind in your lib (POSIX or gcc) you can use it like:

  enum   { NOTFOUND, HELLO,  WORLD,  FOO,  BAR };
  char list[][100]={"hello","world","foo","bar"};

  size_t r, siz = sizeof*list, num = sizeof list/siz;

  char *tosearch = "foo";

  switch ( (r=lfind(tosearch,list,&num,siz,strcmp))?
          (r+siz-(size_t)list)/siz:0 )  {

    case HELLO: puts("hello");break;
    case WORLD: puts("world");break;
    case FOO:   puts("foo");  break;
    case BAR:   puts("bar");  break;
    case NOTFOUND:puts("not found");

  }

each string in the array must have the same size and should not be a pointer


a hashtable if you have a large number of strings and speed is an issue


No, since the switch may only be used with integral types, or a type convertible to an integral type


No, switch works on an integer value (I think floats/doubles are not even allowed). You can emulate that with if/else if/else doing strcmp.

if (strcmp(mystr, "this") == 0) {
    //mystr == "this"
}
else if (strcmp(mystr, "that") == 0) {
    //mystr == "that"
}
else {
   //mystr is not "this" or "that"
}


Yes, and the way is - long if-else-if statement. (for reference: Why switch statement cannot be applied on strings? )

And what do you mean by "a C construct at all in C" o.O ? I'll edit my post, when you answer :)


Sure, depending on how much work you are willing to do.

You can use a preprocessor and some macros to map strings to integral identifiers, giving you a syntax like:

switch (SOSID_LOOKUP (sample_string)) {
   case SOSID (hello): printf ("Hello ");   break;
   case SOSID (world): printf ("World! ");  break;
   case 0: default:    printf ("unknown "); break;
}

If you can use C++ instead of C, you can use litb's template-based string switcher, giving you syntax like:

 sswitch(s) {
    scase("foo"): {
      std::cout << "s is foo" << std::endl;
      break; // could fall-through if we wanted
    }

    // supports brace-less style too
    scase("bar"):
      std::cout << "s is bar" << std::endl;
      break;

    // default must be at the end
    sdefault():
      std::cout << "neither of those!" << std::endl;
      break;
  }
0

精彩评论

暂无评论...
验证码 换一张
取 消