?
Solved

Switch Case with Cstring variable

Posted on 2003-03-19
18
Medium Priority
?
553 Views
Last Modified: 2012-06-21
How can I use a switch case with a CString variable?

Thanks
0
Comment
Question by:oliverUK
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 5
  • 5
  • 3
  • +4
18 Comments
 
LVL 12

Expert Comment

by:Salte
ID: 8165951
Isn't there a class member function that converts case to upper/lower case as needed? I remember there should be some function like that.

Check your documentation.

If you can't find it you can use this method:

CString to_upper(const CString & str)
{
   char * p = new char[str.Length() + 1];
   strupr(p,str);  // (*)
   CString x = p;
   delete [] p;
   return x;
}

Not 100% sure if strupr act like a strcpy with conversion to uppercase as I assume above or if it just take one argument and modifies the string. If it is a one argument function then you do it like this:

CString to_upper(const CString & str)
{
   char * p = new char[str.Length() + 1];
   strcpy(p,str);
   strupr(p);  // (*)
   CString x = p;
   delete [] p;
   return x;
}

Hope this is of help. If you want to change to lowercase the similar function is named strlwr(), again either one or two arguments, can't remember which at the moment.

Alf
0
 
LVL 48

Expert Comment

by:AlexFM
ID: 8165969
0
 

Author Comment

by:oliverUK
ID: 8165984
Sorry my question was ambiguous,

I actually meant how can a use a CString  variable in a "switch" statement.
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 30

Expert Comment

by:Axter
ID: 8166006
You would have to use some type of function that would convert your CString to a number.
switch statements only work with numbers.
So if your CString is a string number, then you can do the following:

switch(atoi(MyCString))

Other wise, you need to use an indexing function in order to use it with switch.
0
 
LVL 1

Expert Comment

by:codez80
ID: 8166007
oliverUK,

There is no real and effective way of doing that.
You can use what AlexFM suggested to make your code more readable but it does not have the advantages of a real switch statement, such as is jumping straight to the point of the constant.
The main reason is that switch statements rely on const integers internally when the code is compiled.
I assume that you already know the alternative:

if (strcmp(str, "hey") == 0)
{
  // do this
}
else if (strcmp(str, "aha") == 0)
{
  // do that
}
else if (strcmp(str, "xxx") == 0)
{
}

etc.


codez80


0
 
LVL 30

Expert Comment

by:Axter
ID: 8166010
Salte,
FYI, CString does have it's own MakeUpper and MakeLower functions.
0
 
LVL 1

Expert Comment

by:fsign21
ID: 8166032
You can not use CString in switch directly.
Recall the description of the switch-statement:

switch(expression){
   case constant_1:
      // code to execute if integer_val is val_1
      break;
    ...
   case constant_n:
      // code to execute if integer_val is val_n
      break;
   default:
      // code to execute if integer_val is none of the above
}
Note, that "expression" must evaluate to an integer type and that the "constant_s" must be integer constants (which include chars).

You have to use if() - else if() - else statements.

-----

Alternatively, you could program some kind of mapping of CString-constants to int-constants.
enum IntKeyConst { CONST_ONE, CONST_TWO };
struct LookUpStruct {
int key;
CString value;
};
LookUpStruct lus[] = {
  { CONST_ONE, "CONST_ONE"},
  { CONST_TWO, "CONST_TWO"}
};

int findKey(const CString& val) {
   insigned int cnt = sizeof(lus)/sizeof(LookUpStruct);
   for(unsigned i=0; i < cnt; ++i) {
      if(val==lus[i].value) return lus[i].key;
   }
   return -1;
}

Then you could write:
val="CONST_ONE";

switch(findKey(val)){
case CONST_ONE:
break;
...
}
0
 
LVL 1

Expert Comment

by:fsign21
ID: 8166059
You can not use CString in switch directly.
Recall the description of the switch-statement:

switch(expression){
   case constant_1:
      // code to execute if integer_val is val_1
      break;
    ...
   case constant_n:
      // code to execute if integer_val is val_n
      break;
   default:
      // code to execute if integer_val is none of the above
}
Note, that "expression" must evaluate to an integer type and that the "constant_s" must be integer constants (which include chars).

You have to use if() - else if() - else statements.

-----

Alternatively, you could program some kind of mapping of CString-constants to int-constants.
enum IntKeyConst { CONST_ONE, CONST_TWO };
struct LookUpStruct {
int key;
CString value;
};
LookUpStruct lus[] = {
  { CONST_ONE, "CONST_ONE"},
  { CONST_TWO, "CONST_TWO"}
};

int findKey(const CString& val) {
   insigned int cnt = sizeof(lus)/sizeof(LookUpStruct);
   for(unsigned i=0; i < cnt; ++i) {
      if(val==lus[i].value) return lus[i].key;
   }
   return -1;
}

Then you could write:
val="CONST_ONE";

switch(findKey(val)){
case CONST_ONE:
break;
...
}
0
 
LVL 22

Expert Comment

by:grg99
ID: 8166162
As others have said, you cant use strings directly in a switch statement.

But you can map the strings to integers, with some clever code like this:

Cmds = "cmd1|cmd2|cmd3...";

Index = strstr( Cmds, SwitchVar );

if( Index < 0 ) { // Switchvar not in list }
else
{  switch(  Index / 5 + 1) {
case 1:  cmd1(); break;
case 2:  cmd2(); break; ......

}

0
 
LVL 12

Accepted Solution

by:
Salte earned 150 total points
ID: 8166258
switch case with string variables aren't directly possible in C and C++. You must somehow convert that string to an integer.

Using a hash function can get you a long way but beware of two strings that computes the same hash value.

switch (hashfunc(stringvar)) {
case HASH_VALUE_1:
   //....string is a string that computes to HASH_VALUE_1
   break;
case HASH_VALUE_2:
   // ...string is a string that computes to HASH_VALUE_2
   break;
}


If the hash function is designed so that for any two strings a and b that you might provide as value for stringvar and hashfunc(a) == hashfunc(b) implies that strcmp(a,b) == 0 then this method above works fine.

If it doesn't you must check explicitely for the strings in the case, so:

case HASH_VALUE_1:
   if (strcmp(stringvar,"string_1") == 0) {
      // string is string_1.
   } else {
      // string is some other string but it also
      // computes to HASH_VALUE_1.
   }
   break;

The other method is to 'intern' the string into a hash table. This is so that you guarantee that if two strings a and b are such that strcmp(a,b) == 0 then a == b.

const char * intern(char * a)
{
   const char * p = find_in_hash_table(a);
   if (p == 0) {
       char * q = strdup(a);
       insert_into_hash_table(q);
       p = q;
   }
   return p;
}

This function looks up the string in the hash table and if the string is already in the table you get the string that is there, if not you allocate a new string and insert it into the hash table and then return that newly allocated string.

Now you can do:

const char * a = intern("hello");
const char * b = intern("hello");

and a == b if and only if strcmp(a,b) == 0.

Now you can do switch:

switch(int(intern(stringvar))) {
case STRING_VAR_1:
   // string is "string_1" if and only if
   // int(intern("string_1")) is STRING_VAR_1.
   break;

You still have to provide constant values in the switch so you must do associate each interned string with a specific value that can be known at compile time. The value int(a) for a const char * a is constant enough for a specific value for a string a, but it is not known at compile time and must be stored in a variable. case labels must be constants and so you end up with something like this:

create a map between each string and associate an integer value with it, for example the string "a" is 1, while string "foo" is 2 and string "bar" is 3. Since these values are known before hand you get it like this:

map<string,int> m;
m["a"] = 1;
m["foo"] = 2;
etc...


then you can do something like this:

map<string,int>::iterator p = m.find(stringvar);

void do_something(const char * str)
{
   int v;

   if (p == m.end()) {
     // string not in map... set to a 'default' value, a
     // value not used in any case label).
     v = SOME_DIFFERENT_VALUE;
   } else {
     v = *p;
   }
   switch (v) {
   case 1: // string is "a"
      ...
      break;
   case 2: // string is "foo"
      ...
      break;
   default: // string is something else.
      ....
      break;
   }
}

Note that this uses a map instead of an explicit hash function. This is better since you can then assign the values without regard for what the hashfunction compute the value to be.

C# allow strings in switch statements and they use a method similar to the 'intern' method shown above. However, in C# the compiler translates a case label like:

case "foo":

to something like, compute intern("foo") and then use the returned value from that intern call as the case label when it builds up the switch table.

The intern call isn't actually done explicitely since it is done with all string literals in C# and it is done when the literal is loaded into memory from the .exe file so the switch with strings is essentially almost as cheap as a switch with int values. It is therefore good practice to use strings in switches in C# even though it looks horrible to most C and C++ programmers.

Alf
0
 
LVL 1

Expert Comment

by:fsign21
ID: 8166291
grg99,

be careful because your solution has a limitation, that all string constants have to be of the same length.

For example, it does not work in case
Cmds = "cmd1|cmd2|cmd3|...|cmd10|cmd11";
or
Cmds = "one|two|three|four|five|six";
0
 
LVL 22

Expert Comment

by:grg99
ID: 8168072
>be careful because your solution has a limitation, that >all string constants have to be of the same length.

>For example, it does not work in case
>Cmds = "cmd1|cmd2|cmd3|...|cmd10|cmd11";
>or
>Cmds = "one|two|three|four|five|six";

You're right, BUT:
Not a problem if you soup-up the strings like this:

Cmds = "1|cmd1|2|commandtwo|3|commandthatisthree"...

p = strstr( Line, Cmds );
if( p > 0 ) p = Line[p-2];

It's also a good idea to add |delimiters| around the comamnd you're searching for, unless you do want a partial match.





0
 
LVL 1

Expert Comment

by:fsign21
ID: 8175445
grg99,
I see the same problem if
Cmds = "1|cmd1|2|whatever|...|10|ten";
because of p = Line[p-2];

It's also a good idea to test you solution with more than 9 entries and different strings...
0
 
LVL 12

Expert Comment

by:Salte
ID: 8175803
Actually if you have that no string is contain a | character you can do it like that, just have to make the function a tad more general:

int ifroms(const char * s, const char * t)
{
   // t is a string of the form "a|num|b|num|...|num"
   // s is compared to the strings a, b, c etc until
   // a match is found, if a match is found the number
   // after is return, otherwise if no string matches
   // 0 is returned. (if any case has 0 as return value
   // that is their problem.
   const char * p = strchr(t,'|');
   while (p != 0) {
      size_t len = p++ - t;
      if (strncmp(s,t,len) == 0 && s[len] == 0) // match.
         return strtol(p,0,0);
      t = strchr(p,'|'); // find | after number.
      if (t == 0)
         return 0; // no | after!
      p = strchr(++t,'|');
   }
   return 0;
}

Then you can do:

   switch(ifroms(str,"hello|1|there|2")) {
   case 1:
       // str == "hello"
       break;
   case 2:
      // str == "there"
      break;
   default:
      // other string.
   }


Note that setting "foo|0|bar|1" will cause 'foo' to be treated as 'default' case.

This permit the value after the string to be any value.

switch (ifroms(str,"hello|125378|there|0x3fff")) {
case 125378:
   // str == "hello"
case 0x3fff:
   // str == "there"
};

if you want to, you can have a little test for '\'' there and read the char value if you want:

I.e. replace the:

         return strtol(p,0,0);

with:

   if (*p != '\'')
      return strtol(p,0,0);
   else if (*++p != '\\')
      return *p;
   else {
      switch (*p) {
      case 'n': return '\n';
      case 't': return '\t';
      etc etc...
      case 'x':
         // '\xXX'
         return strtol(p+1,0,16);
      default:
         if (*p >= '0' && *p <= '7')
            return strtol(p,0,8);
         return *p;
      }
   }

and you can even do things like:

switch (ifroms(str,"hello|'a'|there|12768|foo|0x37|bar|0177")) {
...

with the obvious interpretation.

If the string becomes very long it might be a good idea to break it:

switch (ifroms(str,"hello|'a'|"
                   "there|12768|"
                   "foo|0x37|"
                   "bar|0177")) {
case 'a': ....

now you could even include enum values in there but that would be more fancy with table lookup and everything, your imagination is the limit essentially.

Alf
0
 
LVL 22

Expert Comment

by:grg99
ID: 8182232

>I see the same problem if
>Cmds = "1|cmd1|2|whatever|...|10|ten";
>because of p = Line[p-2];


Funny, I don't see the problem.  Neither do my programs that have been running with this method for the last, oh, 28 yrs.

Regards,


grg99

0
 
LVL 12

Expert Comment

by:Salte
ID: 8182557
The problem is that setting p = p - 2 means you move it twice back, so if you're looking for "foo" and you found it in "....|13|foo|...." the p will point at the "f" and move backwards once it will point to "|foo" and back one more it will move to "3|foo" and this is then taken as value 3 if you do *p but this isn't 3 it is 13!

To do it right you could do a:

 --p;
 while ( *--p != '|');
 switch(atoi(p+1)) {
 ...

This indicates that the very first number must also have a | in front, so:

"|1|...|2|...|3|etc..."

is the correct format of the string.

Alf
0
 
LVL 1

Expert Comment

by:fsign21
ID: 8194955
Salte, this is exactly what I ment.

Another thing: there is a limitation on string's content.
You can not store strings like "1","2","5" etc.  
0
 
LVL 12

Expert Comment

by:Salte
ID: 8195438
fsign21,

Don't worry, grg will probably get around to understand it one of these days too...you'll never know :-)

It's possible he never used more than 10 cases in those 28 years. Or that although he has more than 10 cases but those above 9 are never used and he never notice :-)

It's also possible that he does have more than 10 cases andt his code is written so that he can take care of it but he just never notice at this moment.

We'll never know.

Alf
0

Featured Post

VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

What is C++ STL?: STL stands for Standard Template Library and is a part of standard C++ libraries. It contains many useful data structures (containers) and algorithms, which can spare you a lot of the time. Today we will look at the STL Vector. …
This article will show you some of the more useful Standard Template Library (STL) algorithms through the use of working examples.  You will learn about how these algorithms fit into the STL architecture, how they work with STL containers, and why t…
The viewer will learn how to use the return statement in functions in C++. The video will also teach the user how to pass data to a function and have the function return data back for further processing.
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.

801 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question