CodingBison

A program may need to execute different sets of statements depending upon a condition. As an example, if we were to search for a painter of a painting from a list of painters and if it finds the painter, then the program should indicate that it has found the painter. Else, it should continue its search. C handles such cases using conditional expressions.

Let us understand conditional expression using the following flow-chart. The flow-chart starts with a condition that can evaluate to True or False. If the condition evaluates to True, then the program executes task pertaining to that condition otherwise it executes other task. Needless to say, the location of putting a condition expression is dictated by the application logic.



Figure: Conditional Expression in C

C provides four types of conditional expressions: (a) if, (b) if-else, (c) if-"else if"-else, and (d) switch. One can consider the first two forms as simplification of the if-"else if"-else form.

Moving on, we provide generic formats of the first three expressions; we will revisit the generic format of switch expression a little later. For the if-"else if"-else clause, there is no limit to the occurrence of "else if". We show two conditions ("condition1" and "condition2") for "else if" clause, but, a program is free to add as many clauses as it requires.

 /* Format of a single if expression */
 if (condition) {
     Execute some statements
 } 

 /* Format of an if-else expression */
 if (condition) {
     Execute statements pertaining to the "condition"
 } else {
     Execute Alternative statements
 }

 /* Format of an if-"else if"-else expression */
 if (condition) {
     Execute statements pertaining to the "condition"
 else if (condition1) {
     Execute statements pertaining to the "condition1"
 else if (condition2) {
     Execute statements pertaining to the "condition2"
 } else {
     Execute Alternative statements
 }

Before we go any further, let us spend some time thinking about what it takes to make the condition evaluate to True or False. We can broadly put such expressions into two categories.

The first category is one that is purely boolean in nature. Let us say, we have a variable, var_x, with a value of 2. Thus, an expression " if (var_x == 2) " would evaluate to True since value of var_x is indeed 2. Further, expressions like " if (x > 0)" or "if (x != 4)" would also evaluate to True. On the other hand, some of the expressions would evaluate to False, like " if (x < 0)" or "if (x != 2)". So, basically, relational expressions that compare a variable for equality, greater than, less than, etc.

The second category is more subtle. C also evaluates expressions with non-zero value as True. Thus, "if (var_x)" would be True because var_x is not equal to zero and so it has some value! As an example, "if (!var_x)" would be False since negation of 2 would be a 0, which is not a value.

A common example for this case is when we have a pointer (we will revisit pointers soon!). Pointers can have a value of NULL and a value of NULL is considered False because NULL means it is pointing to nothing. Thus, if var_ptr has a value of NULL, then the expression "if (var_ptr)" would be False and " if (!var_ptr)" would be True since negation of nothing is, well, something!

Now that we have seen the generic formats and some understanding of what makes a condition True (or False), let us write a simple program and implements these expressions. The program (provided below) handles a simple case where we check the value of a variable, painting_name, against names of various paintings. In the interests of simplicity, we keep painter names as macros and initialize the variable with one of the painting names.

 #include <stdio.h>

 /* Paintings by Leonardo da Vinci and Van Gogh */
 #define THE_LAST_SUPPER   1001
 #define MONA_LISA         1002
 #define POTATO_EATERS     1003
 #define CYPRESSES         1004

 int main() {
     int painting_name = THE_LAST_SUPPER;

     /* An if clause */
     if (painting_name == MONA_LISA) { 
         printf("Mona Lisa was painted by Leonardo da Vinci (1505)\n");
     }   

     /* An if-else clause */
     if (painting_name == MONA_LISA) { 
         printf("Mona Lisa was painted by Leonardo da Vinci (1505)\n");
     } else {
         printf("Not aware of this painting\n");
     }   

     /* An if-"else if"-else clause */
     if (painting_name == MONA_LISA) { 
         printf("Mona Lisa was painted by Leonardo da Vinci (1505)\n");
     } else if (painting_name == THE_LAST_SUPPER ) { 
         printf("The Last Supper was painted by Leonardo da Vinci (1497)\n");
     } else if (painting_name == POTATO_EATERS) { 
         printf("Potato Eaters was painted by Van Gogh (1885)\n");
     } else if (painting_name == CYPRESSES) {
         printf("Cypreses was painted by Van Gogh (1889)\n");
     } else {
         printf("Not aware of this painting\n");
     }   
     return 0;
 }

Since we initialize the value of painting_name to THE_LAST_SUPPER, the if clause evaluates to False and so, we do not enter the if clause block. For the "if-else" clause, the if case is once again False and so we enter the "else" part and we would see the corresponding output of the printf() call. For the if-"else if"-else clause, all of the expressions evaluate to False, except the second "else if" clause and the output would print the corresponding text.

When we run the program, we find the output provided below.

 Not aware of this painting
 The Last Supper was painted by Leonardo da Vinci (1497)

Switch Statements

If the conditions form an if-"else if"-else clause are always integer values and belong to a set of closely-related values, then we can use a special conditional expression, the "switch" statement.

Let us first see a pseudo-code that shows a typical format of the switch expression. A switch handles each clause by comparing to different case values, where each case value must be a constant. If the expression matches any of the case values, then it runs the corresponding the statements; else, it runs the default case (if we have one). We also have a break statement present for each cases. This means that once we are done running the statements, we break out of the switch expression.

 switch (expression) {
     If needed, declare Variables

     case constant0:
     Execute statements pertaining to the "constant0"
     break;

     case constant1:
     Execute statements pertaining to the "constant1"
     break;

     default:
     Execute Alternative statements
     break;
 } 

It is important to keep two things in mind for a switch statement.

First, the above constant expressions cannot be variables themselves; replacing these case constants, "constant0" or "constant1" with expressions (even if that leads to an integer) would lead to a compilation error. Let us say that we have an integer variable, temp_var that equals 100. In this case, it is not allowed to replace a constant by temp_var, instead they should be 100 or other integer value (often, we use macros or enums to achieve this).

Second, the "expression" should also evaluate to an integer value, otherwise that would also lead to a compilation error. This "expression" value can be either 100 or temp_var since both are of type integer and they both evaluate to an integer value. In addition to an integer, the expression can also evaluate to the related types of short, long, or char.

A note about the default case. It is not mandatory for a switch statement to have a default case but keeping one is a good practice. Basically, the default case can catch all values (perhaps, error cases) that are not covered by individual cases. Alternatively, there could also be scenarios, where most of the cases intentionally go to default and only a few specialized values get the preferential treatment.

Let us see a simple example of a switch statement; this example uses some of the paintings by Leonardo da Vinci and Van Gogh. We define a macro to represent each painting. Typically, if a program uses constants to represent certain states or certain types of data, then they can be represented as macros.

 #include <stdio.h>

 /* Paintings by Leonardo da Vinci and Van Gogh */
 #define THE_LAST_SUPPER   1001
 #define MONA_LISA 	  1002
 #define POTATO_EATERS 	  1003
 #define CYPRESSES 	  1004

 int main() {
     int painting_name = THE_LAST_SUPPER;

     switch (painting_name) {
         case THE_LAST_SUPPER: 
         printf("The Last Supper was painted by Leonardo da Vinci (1497)\n");
         break;

         case MONA_LISA:
         printf("Mona Lisa was painted by Leonardo da Vinci (1505)\n");
         break;

         case POTATO_EATERS:
         printf("Potato Eaters Lisa was painted by Van Gogh (1885)\n");
         break;

         case CYPRESSES:
         printf("Cypreses was painted by Van Gogh (1889)\n");
         break;

         default:
         printf("Not a painting by Leonardo da Vinci/Van Gogh\n");
         break;
     }
     return 0;
 }

The output of the above program is "The Last Supper was painted by Leonardo da Vinci (1497)".

We could also make several case statements do the same task. For example, if were to be less specific about the painting, then we could group all the paintings of a specific painter. We can achieve this by removing some of the break keywords and provide the modified program as follows:

 #include <stdio.h>

 /* Paintings by Leonardo da Vinci and Van Gogh */
 #define THE_LAST_SUPPER   1001
 #define MONA_LISA         1002
 #define POTATO_EATERS     1003
 #define CYPRESSES         1004

 int main() {
     int painting_name = THE_LAST_SUPPER;

     switch (painting_name) {
         case MONA_LISA:
         /* Fall-through */
         case THE_LAST_SUPPER: 
         printf("This painting (%d) was painted by Leonardo da Vinci\n", 
 		painting_name);
         break;

         case CYPRESSES:
         /* Fall-through */

         case POTATO_EATERS:
         printf("This painting (%d) was painted by Van Gogh\n", 
 		painting_name);
         break;

         default:
         printf("Not a painting by Leonardo da Vinci or Van Gogh\n");
         break;
     }
     return 0;
 }

The output of the above modified switch program is "This painting (1001) was painted by Leonardo da Vinci".

Please note that it is a good practice to provide the comment "/* Fall-through */" -- this way, new programmers who get to maintain this code would know that the fall-through is intended. God forbid, if they add a break statement in place of fall-through (thinking that it is missing!), then they would break (pun intended!) the program logic.

Conditional Operator

C also provides a handy conditional operator for "if-else" clause: "if (condition) ? do_this_1 : do_this2". If the "condition" is True, then C runs "do_this_1", else, it runs "do_this_2".

A simple example could be to find the maximum of the two numbers; we provide the example below. The output of this program is "The maximum is 10".

 #include <stdio.h>

 int main() {
     int x1 = 5;
     int x2 = 10; 

     printf("The maximum is %d\n",  (x1 > x2 ? x1 : x2));
     return 0;
 }




comments powered by Disqus