Jump to content

Switch statement: Difference between revisions

From Wikipedia, the free encyclopedia
Content deleted Content added
Line 70: Line 70:
switch (n) {
switch (n) {
case 0:
case 0:
puts("You typed zero.");
print("You typed zero.");
break;
break;
case 1:
case 1:
case 4:
case 4:
case 9:
case 9:
puts("n is a perfect square.");
print("n is a perfect square.");
break;
break;
case 2:
case 2:
puts("n is an even number.");
print("n is an even number.");
case 3:
case 3:
case 5:
case 5:
case 7:
case 7:
puts("n is a prime number.");
print("n is a prime number.");
break;
break;
case 6:
case 6:
case 8:
case 8:
puts("n is an even number.");
print("n is an even number.");
break;
break;
default:
default:
puts("Only single-digit numbers are allowed.");
print("Only single-digit numbers are allowed.");
break;
break;
}
}

Revision as of 01:07, 21 May 2011

In computer programming, a switch, case, select or inspect statement is a type of selection control mechanism that exists in most imperative programming languages such as Pascal, Ada, C/C++, C#, Java, and so on. It is also included in several other types of languages. Its purpose is to allow the value of a variable or expression to control the flow of program execution via a multiway branch (or "goto", one of several labels). The main reasons for using a switch include improving clarity, by reducing otherwise repetitive coding, and (if the heuristics permit) also offering the potential for faster execution through easier compiler optimization in many cases.

History

In his 1952 text Introduction to Metamathematics, Stephen Kleene formally proves that the CASE function (the IF-THEN-ELSE function being its simplest form) is a primitive recursive function, where he defines the notion definition by cases in the following manner:

"#F. The function φ defined thus
φ(x1 , ... , xn ) =
  • φ1(x1 , ... , xn ) if Q1(x1 , ... , xn ),
  • . . . . . . . . . . . .
  • φm(x1 , ... , xn ) if Qm(x1 , ... , xn ),
  • φm+1(x1 , ... , xn ) otherwise,
where Q1 , ... , Qm are mutually exclusive predicates (or φ(x1 , ... , xn) shall have the value given by the first clause which applies) is primitive recursive in φ1, ..., φm+1, Q1, ..., Qm+1. (Definition by cases)." (Kleene 1952:229)

Kleene provides a proof of this in terms of the Boolean-like recursive functions "sign-of" sg( ) and "not sign of" ~sg( ) (Kleene 1952:222-223); the first returns 1 if its input is positive and −1 if its input is negative.

Boolos-Burgess-Jeffrey make the additional observation that "definition by cases" must be both mutually exclusive and collectively exhaustive. They too offer a proof of the primitive recursiveness of this function (Boolos-Burgess-Jeffrey 2002:74-75).

The IF-THEN-ELSE is the basis of the McCarthy formalism – its usage replaces both primitive recursion and the mu-operator.

Typical syntax

In most languages, a switch statement is defined across many individual lines using one or two keywords. A typical syntax is:

  • The first line contains the basic keyword, usually switch, case or select, followed by an expression which is often referred to as the control expression or control variable of the switch statement.
  • Subsequent lines define the actual cases (the values) with corresponding sequences of statements that should be executed when a match occur.

Each alternative begins with the particular value, or list of values (see below), that the control variable may match and which will cause the control to go to the corresponding sequence of statements. The value (or list/range of values) is usually separated from the corresponding statement sequence by a colon or an implication arrow. In many languages, every case must also be preceded by a keyword such as case or when. An optional default case is typically also allowed, specified by a default or else keyword; this is executed when none of the other cases matches the control expression.

In languages derived from C, a break keyword is used to go to the end of the switch, thus completing execution of the switch statement. In such languages, program execution "falls through" to the statements associated with the next case in the source text when no break is present, thereby behaving like a GOTO mechanism. Notable variations on this in the C-family include C#, in which all blocks must be terminated with a break unless the block is empty (i.e. fallthrough is used as a way to specify multiple values).

The Pascal family (Object Pascal, Modula, Oberon, Ada, etc) as well as modern BASIC dialects influenced by Pascal, do not allow fallthrough. The same goes for most functional languages and many others. Pascal type languages instead permit any number of values per case, given as a comma separated list, as a range, or a combination. Perl is a language where cases do not fall through by default, but may explicitly do so using a continue keyword.

Compilation

If the range of input values is identifiably 'small' and has only a few gaps, some compilers that incorporate an optimizer may actually implement the switch statement as a branch table or an array of indexed function pointers instead of a lengthy series of conditional instructions. This allows the switch statement to determine instantly what branch to execute without having to go through a list of comparisons.

Optimized switch

To optimize a switch statement, the programmer must use a very compact range of possible values to test.[1] Sometimes it is necessary to convert the switch to a more suitable range using an inexpensive transformation. See algorithmic efficiency for an explanation of how the programmer can "assist" the compiler to make an efficient choice. See also the section 'Compiler generated branch tables' in branch table article for why optimization is not always performed as expected and how to solve this.

Checking for optimization

Normally, the only method of finding out if this optimization has occurred is by actually looking at the resultant assembly or machine code output that has been generated by the compiler (and is therefore seldom, if ever, done by HLL programmers). The first 'C' example below would be eligible for this kind of optimization if the compiler supported it (the range '0' through '9' with zero gaps without a defined case label).

Advantages

In some languages and programming environments, the use of a case or switch statement is considered superior to an equivalent series of if-else statements because it is:

  • easier to debug (e.g. setting breakpoints on code vs. a call table, if the debugger has no conditional breakpoint capability)
  • easier to read (subjective)
  • easier to understand and therefore
  • easier to maintain

Additionally, as mentioned above, an optimized implementation may:

  • execute much faster than the alternative, because it is often implemented by using an indexed branch table
For example, deciding program flow based on a single character's value, if correctly implemented, is vastly more efficient than the alternative, reducing instruction path lengths considerably.

Disadvantages

When implemented with fall-through as the default path, switch/case statements are a frequent source of bugs among even experienced programmers, given that, in practice, the "break" is almost always the desired path, but not the default behavior of the switch/case construct (at least in C and Java).

Examples

The following are simple examples, written in the various languages, that use switch (or switch-like) statements to print one of several possible lines, depending on the value of an integer entered by the user.

C, C++, Java, PHP, ActionScript, JavaScript

In C and similarly-constructed languages, the lack of break keywords to cause fall through of program execution from one block to the next is used extensively. For example, if n=2, the fifth case statement will produce a match to the control variable. The next line outputs "n is an even number.". Execution then continues through the next 3 case statements and to the next line, which outputs "n is a prime number." this is a classic example of omitting the break line to allow for fall through. The break line after a case block causes the switch statement to conclude. If the user types in more than one digit, the default block is executed, producing an error message by executing the default code.

switch (n) {
  case 0:
    print("You typed zero.");
    break;
  case 1:
  case 4:
  case 9:
    print("n is a perfect square.");
    break;  
  case 2:
    print("n is an even number.");
  case 3:
  case 5:
  case 7:
    print("n is a prime number.");
    break;
  case 6:
  case 8:
    print("n is an even number.");
    break;
  default:
    print("Only single-digit numbers are allowed.");
    break;
}

C#

In C#, every case block that contains any statements must have an unreachable end point, or triggers a compilation error. Usually, this is a break statement, but any jump statement can be used – such as return, goto or throw – or the switch can simply end with an infinite loop.[2] Case fall-through is only permitted when there are no statements between one case statement and the next. If fall-through is otherwise desired, it must be made explicit with the goto case construct.

switch (n)
{
  case 0:
    Console.WriteLine("You typed zero.");
    break;
  case 1:
  case 4:
  case 9:
    Console.WriteLine("n is a perfect square.");
    break;
  case 2:
    Console.WriteLine("n is an even number.");
    goto case 3;
  case 3:
  case 5:
  case 7:
    Console.WriteLine("n is a prime number.");
    break;
    goto case 6;
  case 6:
  case 8:
    Console.WriteLine("n is an even number.");
    break;
  default:
    Console.WriteLine("Only single-digit numbers are allowed.");
    break;
}

Go

Like Perl, the Go programming language has an explicit fallthrough statement which can be used at the end of a case statement to indicate that control falls through next case clause in a expression "switch" statement.[3]

Pascal

Pascal does not allow “fall through”, but has ranges and comma separated literal lists.

 case age of
   0,1: writeln('baby');
   2,3,4: writeln('toddler');
   5..12: writeln('kid'); 
   13..19: writeln('teenager'); 
   20..25: writeln('young'); 
   else writeln('old ;)'); 
 end;

Perl

Perl 5.10 (backported from Perl 6) has a powerful built in switch statement called given, where the cases are called when:

use feature 'switch';
given ($foo) {
    when (undef) {
        say '$foo is undefined';
    }
    when ("foo") {
        say '$foo is the string "foo"';
    }
    when ([1,3,5,7,9]) {
        say '$foo is an odd digit';
        continue; # Fall through
    }
    when ($_ < 100) {
        say '$foo is numerically less than 100';
    }
    when (\&complicated_check) {
        say 'a complicated check for $foo is true';
    }
    default {
        die "I don't know what to do with $foo";
    }
}

Python

Python does not have direct language support for the switch statement; 'if'/'elif' is often used for that. However, it is possible to emulate this behaviour, through a dictionary of functions.

Here is an example that does not use a “fall through” mechanism. The default case is mimicked by using dict.get()'s fallback parameter:

switch = {
    "a": DoChoiceA,
    "b": DoChoiceB,
    "c": DoChoiceC,
    }

switch.get(choice, DoDefaultChoice)()

# A temporary variable is not needed.
answer = {
    0: "Zero",
    1: "Perfect square",
    2: "Prime",
    3: "Prime",
    4: "Perfect square",
    }.get(n, "Unknown")

Ruby

Ruby doesn’t allow “fall through”; it uses case, when and else:

case n
when 0 
  puts 'You typed zero'
when 1, 9 
  puts 'n is a perfect square'
when 2 
  puts 'n is a prime number'
  puts 'n is an even number'
when 3, 5, 7 
  puts 'n is a prime number'
when 4, 6, 8 
  puts 'n is an even number'
else              
  puts 'Only single-digit numbers are allowed'
end

Also can be used to assign a value, in a more compact way:

result = case n
  when 0 then 'none'
  when 1..9 then 'valid'
  else 'too much'
end
puts 'n is ' + result

Ada

Ada doesn’t not allow “fall through”; it uses case, when and others. Ada requires full coverage of all possible values for the type in the case statement. If a when others => case is not specified, then the code will not compile if either extra cases are specified, or missing. If at some point in the future, the definition of Digit is modified, the compiler will ensure that the programmer updates the case statement to reflect the changes to the type definition, which ensures that the program is kept up to date and helps to reduce maintenance costs. A list of values for a particular case can be combined using '|' as shown below, or a range of values may be specified using ".." to indicate the extents of the range. e.g., when 0 .. 4 => Put_Line ("Small Digits); In the example below, there is no need to check for values outside the range of 0 to 9 because the type is guaranteed to have a value within the range for the type.

   type Digit is new Integer range 0 .. 9;
   n : Digit;
   ...
   case n is
      when 0 =>
         Put_Line ("You typed zero");
      when 1 | 9 =>
         Put_Line ("n is a perfect square");
      when 2 =>
         Put_Line ("n is a prime number");
         Put_Line ("n is an even number");
      when 3 | 5 | 7 =>
         Put_Line ("n is a prime number");
      when 4 =>
         Put_Line ("n is a perfect square");
         Put_Line ("n is an even number");
      when 6 | 8 =>
         Put_Line ("n is an even number");
   end case;

Eiffel

Eiffel's multi-branch instruction uses inspect, when, and else. It does not have the “fall through” behavior. Also, the else part is optional. However, an omitted else differs from an included, but empty else. If the else is empty and a case is processed that is not specified in one of the when parts, control passes through the else. But if the else is omitted, it is assumed that all cases should be identified in a when part. In this case an exception will occur as a result of processing a case not handled in a when part.

inspect
	n
when 0 then
	print ("You typed zero%N")
when 1, 9 then
	print ("n is a perfect square%N")
when 2 then
	print ("n is a prime number%N")
	print ("n is an even number%N")
when 3, 5, 7 then
	print ("n is a prime number%N")
when 4 then
	print ("n is a perfect square%N")
	print ("n is an even number%N")
when 6, 8 then
	print ("n is an even number%N")
else
	print ("Only single digit numbers are allowed%N")
end

Haskell

Haskell's case construct, unlike C-influenced languages, has no fall-through behaviour. It is an expression which returns a value, and it can deconstruct values using pattern matching.

 case list of
   (f:r) -> "Not empty, first item is " ++ show f
   []    -> "List is empty!"

OCaml, F#

OCaml and F#'s match construct is like Haskell's case above.

(* OCaml *)
 match list with
   f::r -> "Not empty, first item is " ^ string_of_int f
 | []   -> "List is empty!"
// F#
 match list with
 | f::r -> "Not empty, first item is " + string f
 | []   -> "List is empty!"

Bash

Bash and similar shell scripting languages offer a case construct using the OR operator, |, to separate the selections, and the ) symbol to separate the list of selections from the action to be taken. Fall through is done using ;& whereas ;; acts as a case break.

case $n in
    0)      echo 'You typed 0.';;
    1|9)    echo "$n is a perfect square.";;
    3|5|7)  echo "$n is a prime number.";;
    4)      echo "$n is a perfect square.";&  # fall through
    2|6|8)  echo "$n is an even number.";;
    *)      echo 'Only single-digit numbers are allowed.';;
esac

Windows PowerShell

Windows PowerShell employs a construct whereby the action to be taken is enclosed in a scriptblock (i.e. curly braces), with the selector placed directly before it. The selector can consist of regular expressions if the "-regex" parameter is inserted after the "switch" command; similarly, wildcards are supported using the "-wildcard" parameter. In either case, the wildcard or regex must be enclosed in quote marks.[4]

Akin to C-based implementations, if a "break" statement is not included at the end of a scriptblock, the switch statement will continue to test each case and execute further scriptblocks.

switch (n)
{
  0 { Write-Host 'You typed 0' }
  { ($_ -eq 1) -or ($_ -eq 4) -or ($_ -eq 9) }
    { Write-Host 'n is a perfect square' }
  { (($_ % 2) -eq 0) -and ($_ -ne 0) }
    { Write-Host 'n is an even number' }
  { ($_ -eq 2) -or ($_ -eq 3) -or ($_ -eq 5) -or ($_ -eq 7) }
    { Write-Host 'n is an prime number' }
  default { Write-Host 'Only single-digit numbers are allowed' }
}

QBasic

In QBasic, the switch statement is called "Select Case", and fall-through to later blocks is not supported. The "Select Case" statement is more expressive because it allows conditionals within cases.

SELECT CASE age
	CASE IS < 12:	PRINT "Have some juice!"
	CASE 13 TO 20:	PRINT "Have a soda!"
	CASE IS >= 21:	PRINT "Have a beer!"
END SELECT

Visual Basic .NET

In Visual Basic .NET, the switch statement is called "Select Case", and fall-through to later blocks is not supported. However, ranges and various constructs from If statements are both supported

Select Case n
  Case Is < -5
    MsgBox("n is less than -5")
  Case -4 To -1
    MsgBox("n is between -4 and -1")
  Case 0
    MsgBox("n is 0")
  Case 2, 4, 6, 8
    MsgBox("n is even")
  Case 1, 3, 5, 7, 9
    MsgBox("n is odd")
  Case Else
    MsgBox("only single-digit numbers are allowed.", vbCritical)
End Select

Visual Basic (classic), VBA, VB Script

In Visual Basic, the switch statement is called "Select Case", and fall-through to later blocks is not supported. Short-circuit evaluation is used. But also, may doing exactly like C, using GOSUB behind Select Case. the block Select Case then call to GOSUB label, where each BREAK in C it's a RETURN (to gosub).

Select Case n
  Case "s"
    MsgBox "Case values of any type are supported"
  Case "s"
    MsgBox "This block is not an error but will never be selected"
  Case ArbitraryFunction()
    MsgBox "Any Expression which can be evaluated at runtime may be used as a case value or selector"
  Case 2+3: MsgBox "The colon is a general language feature, not a part of the switch statement"
            MsgBox "Each block is terminated by the following Case or End statement"
  Case Else
    MsgBox "Case values which do not match the selector type will cause an exception", vbCritical
End Select

WebDNA

WebDNA example is easy to understand:

[text]x=5[/text]

[switch value=[x]]
  [case value=1]
    The value of x was 1
  [/case]
  [case value=2]
    The value of x was 2
  [/case]
  [default]
    The value of x was neither 1 nor 2; it was [x]
  [/default]
[/switch]

Visual FoxPro

Visual FoxPro:

Do Case
 Case field_1 = "X"
  Replace field_b With 1
 Case field_1 = "Y"
  Replace field_b With 2
 Case field_1 = "Z"
  Replace field_b With 3
Endcase

Algol 60

In Algol 60 a switch is effectively an array of labels (branch table). A switch declaration defines its values (i.e., for each index value the name of a label occurring somewhere in the program). A goto statement can specify as destination, instead of a fixed label, an "array element" of this switch, i.e., the switch identifier with, in brackets, the index.

Symbolic constants

In many (but not all) circumstances, using symbolic names rather than explicit literal integers makes the source code easier to read and maintain. This is often achieved via enumerations and has no influence on the performance or behavior of the program. This style of switch statement is commonly used for finite state machine implementation. Here are some examples, given in Pascal (one of the first languages implementing enumerations, along with Algol68) as well as in C (which has a tradition of constants in all capitals, although not enforced by the compiler.)

Pascal (using an enumeration)

var state : (StateReady, StateSet, StateGo, StateFail);
 
case state of
    StateReady: begin
        state:=succ(state);
        if x < 0 then state:=StateFail
    end;
    StateSet: begin
        state:=succ(state);
        if y > 0 then state:=StateFail
    end;

    StateGo:   writeln("go!");
    StateFail: exit( -1 );
end;

C (using the enum keyword)

enum state
{
   STATE_READY = 1,
   STATE_SET = 2,
   STATE_GO = 3,
   STATE_FAIL = 4
};
 
switch( state )
{
   case STATE_READY:
       state = STATE_SET;
       if( x < 0 ) state = STATE_FAIL;
       break;
 
   case STATE_SET:
       state = STATE_GO;
       if( y > 0 ) state = STATE_FAIL;
       break;
 
   case STATE_GO:
       printf( "go!\n" );
       break;
 
   case STATE_FAIL:
       exit( -1 );
}

Alternative uses

Many languages evaluate expressions inside switch blocks at runtime, allowing a number of less obvious uses for the construction. This prohibits certain compiler optimizations, so is more common in dynamic and scripting languages where the enhanced flexibility is more important than the performance overhead.

For example, in PHP, a constant can be used as the "variable" to check against, and the first case statement which evaluates to that constant will be executed:

switch(true) {
  case ($x == 'hello'):
    foo(); 
    break;
  case ($z == 'howdy'): break;
}
switch(5) {
  case $x: break;
  case $y: break;
}

This feature is also useful for checking multiple variables against one value rather than one variable against many values. COBOL also supports this form (and others forms) in the EVALUATE statement.

In Ruby, due to its handling of === equality, the statement can be used to test for variable’s class:

case input
when Array: puts 'input is an Array!'
when Hash:  puts 'input is a Hash!'
end

Ruby also returns a value that can be assigned to a variable, and doesn’t actually require the case to have any parameters (acting a bit like an else if statement):

catfood = case
          when cat.age <= 1: junior
          when cat.age > 10: senior
          else               normal
          end

Alternatives

  • A series of nested if-else conditionals that examine the target one value at a time.
  • A lookup table, which contains, as keys, the case values and, as values, the part under the case statement.
(In some languages, only actual data types are allowed as values in the lookup table. In other languages, it is also possible to assign functions as lookup table values, gaining the same flexibility as a real switch statement. See Control table article for more detail on this).
This lookup technique is one way to implement switch statements in the Lua language - which has no built-in switch.[5]
In some cases, lookup tables are more efficient than non-optimized switch statements since many languages can optimize table lookups - whereas switch statements are not optimized unless the range of values is small with few gaps. A non-optimized, non-binary search lookup, however, will almost certainly be slower than either a non-optimized switch or the equivalent multiple if-else statements.
  • A control table (that may be implented as a simple lookup table) can also be customized to accommodate multiple conditions on multiple inputs if required and usually exhibits greater 'visual compactness' than an equivalent switch (that can occupy many statements).
  • For object-oriented programs, extensive use of polymorphism can be used

See also

References

  1. ^ http://www.rmbconsulting.us/Publications/PointerToFunction.pdf
  2. ^ switch (C# Reference)MSDN Library
  3. ^ "The Go Programming Language Specification". Retrieved 2010-01-18.
  4. ^ "Windows PowerShell Tip of the Week". Microsoft. January 2008. Retrieved 2009-04-28.
  5. ^ Switch statement in Lua
  • Stephen Kleene, 1952 (10th reprint 1991), Introduction to Metamathematics, North-Holland Publishing Company, Amsterdam NY, ISBN 0 7204 2103 9
  • George Boolos, John Burgess, and Richard Jeffrey, 2002, Computability and Logic: Fourth Edition, Cambridge University Press, Cambridge UK, ISBN 0521 00758 5 paperback. cf page 74-75.