Website Logo

3. Tests and Conditions

By Joker. February 11th, 2020. 3:09 PM

Check the guide's index here.
Objectives
  • How the logic values of TRUE and FALSE are represented in C
  • Logic conditions
  • Relational operators - (==, >, <, >=, <=, !=)
  • Logical operators - (&&, ||, !)
  • if-else, switch, break
  • Chained if-else clauses
  • Conditional operator - ?
  • Operator precedence
  • Instruction blocks
  • Indentation

Intro

Till this moment, the programs we have made are particularly adapted to a perfect world. No errors, no doubts, no variations of any kind. Every instruction is followed one after the other, always following the same order of execution, whichever are the input values.

With the concepts we've presented you up until this point, we could write a program with the object of preparing a person for their work day. Something like:

Wear Sweater
Wear Coat
Wear Pants
Put on Shoes
Brush Hair
...

However, if you think about our everyday life you'll notice that, right when we get up, one of the first things we do in the morning is to check the weather, to decide if we should dress in warmer clothes or not.

In the case we find ourselves in plain winter, the previous program could be more or less adapted, however, should the sun bright ablaze, maybe it wouldn't be the best way to dress to work - it makes no sense to walk with a coat in the middle of summer.

So what is the solution?

If we take the line Wear Coat, the program becomes adapted to summer, but not to winter.

The solution is to mantain the line Wear Coat in the program and execute it only when the temperature is low enough.

Unlike previously written programs, where all instructions were always executed, we can write programs that have the ability to take decisions about which instructions to execute.

Wear Sweater
Wear Coat If It's Too Cold
Wear Pants
Put on Shoes
Brush Hair
...

In the above example, the option to Wear Coat exists in the program, but only for winter days.

This type of programming does no more than to computationally reflect about our daily activity about which products to buy, the price/quality ratio, what to lunch, where to lunch, etc., a never ending list of choices we need to make every day, and now, we'll analyze how to implement these through C.

Logic Values - True and False

As we've seen in the previous chapter, C possesses only 4 types of data types (int, float, char and double), and there is no other type that represents both logical values (True and False). There are, however, languages that allow a specific type to represent logical values like Boolean in PASCAL, which can receive the values TRUE and FALSE.

Note: In C there is no specific data type to store logical valuse.

Oh, also.

Note: In C, the logic value of FALSE is represented by 0 (ZERO). Anything different from 0 (ZERO) represents the logic value of TRUE.

Examples:

False   : 0
True    : 2 , -3 , 123.45 , 0.000001
Note: The logic value TRUE in C is not the value 1, but it can be any value different from 0 (ZERO). 1 is only one of many possible values to represent TRUE.

The logical values result, normally, from prepositions that are analyzed, and which analysis determines if a preposition is true or false.

Examples:

The Earth is square. (FALSE)
Ice is solid. (TRUE)
20 is bigger than 14. (TRUE)
20 is bigger than 34. (FALSE)

Relational Operators

In C, there is a set of 6 relational operators, which can be used in the analysis of expressions. Its objective conists in estabilishing relationships between operands.

OperatorNameExampleWhat does the example mean?
==Equalsa == bis a equal to b?
>Bigger thana > bis a bigger than b?
>=Bigger or equala >= bis a bigger or equal to b?
<Smaller thana < bis a smaller than b?
<=Smaller or equala <= bis a smaller or equal to b?
!=Differenta != bis a different from b?
Note: An expression that contains a relational operator always returns TRUE (1) or FALSE (0).

Example: Write a program that asks the user for two integers, and then applies every C relational operator on those read integers.

prog0301.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   int x, y;
 5:   printf("Input 2 integers: ");
 6:   scanf("%d%d", &x, &y);
 7:   printf("The Result of %d == %d : %d\n", x, y, x==y);
 8:   printf("The Result of %d > %d  : %d\n", x, y, x>y);
 9:   printf("The Result of %d >= %d : %d\n", x, y, x>=y);
10:   printf("The Result of %d < %d  : %d\n", x, y, x<y);
11:   printf("The Result of %d <= %d : %d\n", x, y, x<=y);
12:   printf("The Result of %d != %d : %d\n", x, y, x!=y);
13:   return 0;
14: }

$ ./prog0301
Input 2 integers: 7 5
The Result of 7 == 5 : 0
The Result of 7 > 5 : 0
The Result of 7 >= 5 : 0
The Result of 7 < 5 : 0
The Result of 7 <= 5 : 0
The Result of 7 != 5 : 1

In the previous example, the expressions were analyzed, and the result of each analysis is written as an integer (0 - False, 1 - True).

The relational operators can be applied to both simple and complex expressions.

Example:

4 >= 2
x <= y
(x + y) < (x * 2 + y * (-4))

Attention: A very frequent error in programming is misplacing the == operator for the = operator. The == operator is used to verify if two expressions are equal. The = operator is used to give values to variables.

if-else

The if-else clause is one of the many flux control instructions in C. It can indicate the circumstances in which a certain instruction (or set of instructions) should be executed.

Its syntax is:

if (condition)
instruction1;
[ else instruction2;]

As you can see, if's else component is optional.

That instructions allows the existence of instructions that are never executed in a program. You only need the necessary condition for its execution to never be verified.

The if-else clause works like this:

  • The condition is analyzed;
  • If the condition is true, it executes instruction1;
  • If the condition is false, it executes instruction2 (if the else exists).
Note: if's condition must always be placed inbetween parenthesis. Both instruction1 and instruction2 are followed by a semi-colon (;).

Example: Write a program tells us if a number is positive (>=0) or negative.

prog0302.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   int x;
 5:   printf("Input an integer: ");
 6:   scanf("%d", &x);
 7:   if(x>=0)
 8:     printf("Positive Number\n");
 9:   else
11:     printf("Negative Number\n");
10: }

In the previous example, we ask for and read an integer (lines 6: and 7:). Since we want to indicate if the read number is positive or negative, we will test x is bigger or equal to zero using the relational operator >= (line 8:).

If the result of the expression's (x >= 0) analysis is True, then we invoke the printf function with the string "Positive Number\n" (line 9:); otherwise, i.e., if analysing (x >= 0) returns False (zero), we invoke the printf function with the string "Negative Number\n" (line 11:).

$ ./prog0302
Input an integer: 15
Positive Number

$ ./prog0302
Input an integer: -15
Number Number

Example: Write a program tells us if a number is zero or not.

prog0303.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   int x;
 5:   printf("Input an integer: ");
 6:   scanf("%d", &x);
 7:   if(x!=0)
 8:     printf("%d is not zero.\n", x);
 9:   else
11:     printf("%d is equal to zero.\n", x);
10: }

This program is similar to the previous one. Only the type of conditional test is different, because we're using the != operator (different).

Now, note this little detail.

When the value of x is different from zero, the result of analyzing the expression (x != 0) returns True. Now, since x is different from zero (and since zero is False, everything different from zero represents True), x represents by itself True (since it's different from zero). So, writing (x != 0) or (x) is exactly the same thing.

This way, the previous program could have been written like this:

prog0303.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   int x;
 5:   printf("Input an integer: ");
 6:   scanf("%d", &x);
 7:   if(x) /* instead of if(x != 0) */
 8:     printf("%d is not zero.\n", x);
 9:   else
11:     printf("%d is equal to zero.\n", x);
10: }

As you can verify, there are 1001 ways to write a program correctly. However, this last version loses readibility, since we take the value of a variable as a logic value, instead of using the test x != 0, which represents the test that we actually want to execute.

Note: In C, the value of a variable/constant can be used by a coder as a logic value, using it as FALSE (== 0) or TRUE (!= 0).

Generally, coders proficient in C like to write code that no one else understands, making use of some of the language's own characteristics. However, much of the code written by then is so compact or confusing that even they find it hard to understand it.

Note: A program must be written in usch a way that a glance upon the source code indicates, with no shadow of a doubt, what that program should do.

Example: Implement a program that adds $1.000,00 to an individual's salary, if it's lower than $100.000,00.

prog0303.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   float salary;
 5:   printf("What's the salary?: ");
 6:   scanf("%f", &salary);
 7:   if(salary < 100000)
 8:     salary = salary + 1000
 9:   printf("Final Salary: %.2f\n", salary);
10: }

$ ./prog0304
What's the salary?: 93720
Final Salary: 94720.00

$ ./prog0304
What's the salary?: 120000
Final Salary: 120000.0

In the program prog0304.c, the salary only receives a $1.000,0 addition if it's under $100.000,00.

In this case, we only want to execute line 9 for individuals who receive less than $100.000,00.

Although we write the value $100.000,00 in here, in computer terms, it's always represented by 100000 or 100000.00.

Those who receive $100.000,0 or more stay exactly as is, having no need to implement an else clause.

Note that the line

10: printf("Final Salary: %.2f\n", salary);

presents a small novelty, which is the salary's write format.

We already knew that a float's writing format is %f. In order to avoid presenting a large amount of decimal points, we indicate that after the dot we only want two digits (.2). This way, the written floating point number presents only the two most signicative decimal points of its fractionary part, not limiting the integer part in any way.

Instruction Block

As you can see from if-else's syntax, ONE AND ONLY ONE INSTRUCTION can follow an if or an else.

If we want a set of instructions to be executed, either in an if or in an else, these must be written inbetween { } so that this set of instructions forms only one Instruction Block.

A Block is, as such, a set of two or more instructions delimited by curly brackets.

A Block can be placed with no problems in a program, in place of what can be a simple instruction.

Note: A semi-colon (;) does need to be placed after a Block.

Example: Write a program that writes two numbers and presents them in crescent order.

prog0305.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   int x, y, tmp;
 5:   printf("Input two numbers: ");
 6:   scanf("%d %d", &x, &y);
 7:   if(x > y){
 8:     tmp = x;
 9:     x = y;
10:     y = tmp;
11:   }
12:   printf("%d %d\n", x, y);
13: }

Note that if the value of x is bigger than the value of y, we need to switch their values so that x is always smaller than y, avoiding having to invoke printf more than once.

This switching is always done in 3 steps, and always needs an auxiliary variable.

Say that we want to switch the contents of two cups, one full of milk and one full of coffee. To do that, we need an additional empty cup to help switch those contents without mixing them.

Similarly, to switch between two values, you always need a third variable of the same type, being that the switch is a 3 step process.

Since the switch is dependent on the if clause and is made out of three instructions, we have to group them in a single block, placing curly brackets around. If the if's condition is true, the three instructions in the block are executed. Otherwise, none of them are executed.

The next printf is always executed, because it doesn't depend on the if clause.

Indentation

In the first two chapters of this guide, a program was always composed by a set of instructions that would be always executed. By adding an if-else, we introduce instructions that may or may not be executed, depending on the conditions existing along the program's runtime.

That way, some instructions are dependent on the if-else clause's condition.

It's common for programmers to indicate the dependencies that some instructions have from previous instructions placing them (n spaces or a tab) ahead of the instruction on which they depend.

Check the difference in writing here:

if(x != 0)
printf("%d is zero.\n", x);
else
printf("%d is not zero.\n", x);
if(x != 0)
  printf("%d is zero.\n", x);
else
  printf("%d is not zero.\n", x);

We can observe, with a single glance, that in the example on the right, the first printf instance depends on the if clause, and that the second instance depends on the else clause; however, looking at the example on the left, it's more difficult to observe what is the instruction chaining, since they're all placed at the same level.

It's this way of organizing code, visually indicating the dependencies that some instructions have on one another, that we call Indentation.

Indentation is a coder's own personal characteristic, subjective to their own writing and reading ability - thus, there is no superior form of indentation.

Example: The way I indent my programs is mostly how I present them to you in this guide.

I normally use a tab every time an instruction depends on another instruction:

if(x >= 0)
  printf("...");

Every time I make an instruction block, I put my opening curly bracked right in front of the instruction that asks for them, and make them stay at the same level:

if(x > 0){
  printf("One");
  printf("Two");
} else {
  printf("Two");
  printf("One");
}

This way, it's easy to verify when a block is executed since the condition for its execution directly precedes it.

Let me make it clear once more: the format used in program indentation is subjective to every coder's writing and reading. The most important aspect, though, is that the code is indented in a way that it represents the instruction execution hierarchy correctly.

Note: Indentation is a process which objective is to represent, in simple visual form, the dependencies existent in a program. Its objective is to ease the life of a programmer or whoever reads the code. The indentation has no relevance to the compiler - it simply ignores all spaces, blank lines and tabs it finds it finds throughout the compiling process.

Chained if-else Clauses

There are situations in which the test of one condition isn't enough to make a decision. There may be a need to test more than one condition.

Example: Write a program that asks the user his salary and presents that tax he has to pay.

  • If the salary is negative or zero, the respective error should be shown.
  • If the salary is bigger than 1000, the user pays 10% tax, otherwise, they only pay 5% tax.

To solve this problem, all we need to do is write what's up above, but replace the "If"s with if-else clauses.

prog0306.c (Bad implementation!)

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   float salary;
 5:   printf("What's the salary: ");
 6:   scanf("%f", &salary);
 7:   if(salary<=0)
 8:     printf("Salary: invalid value\n");
 9:   if(salary>1000)
10:     printf("Tax: %.2f\n", salary*0.10);
11:   else
12:     printf("Tax: %.2f\n", salary*0.05);
13: return 0;
14: }

Let's check the execution results:

$ ./prog0306
What's the salary: 2000
Tax: 200.00
$ ./prog0306
What's the salary: 100
Tax: 5.00
$ ./prog0306
What's the salary: -1000
Salaty: invalid value
Tax: -50.00

As can be observed, if the value of the salary is positive, the corresponding tax values are presented correctly (10% and 5%). However, something weird appears when you input a negative value.

What happens is, if the salary is negative (-1000, like in the above example), the test in line 8: executed returns True (since -1000 is lower than 0), so we write the corresponding error message. The if clause ends here.

The program will move on to the next instruction, which is also an if clause, but now, the condition (salary > 1000) returns False. Since it returns False, the instruction under the if clause won't be executed, but the one under the else clause will, which corresponds to the tax value of 5%.

So what's wrong?

Once the first if condition is tested, and if it's true, only the corresponding error message should be presented - the tax calculation should not be even executed. Therefore, the second if clause should only be executed if the first if condition returns False. It should, then, behave as the first if's corresponding else clause, so that it only executes if the condition (salary <= 0) returns False.

So the program prog0306.c should be written like this:

prog0306.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   float salary;
 5:   printf("What's the salary: ");
 6:   scanf("%f", &salary);
 7:   if(salary<=0)
 8:     printf("Salary: invalid value\n");
 9:   else
10:     if(salary>1000)
11:       printf("Tax: %.2f\n", salary*0.10);
12:     else
13:       printf("Tax: %.2f\n", salary*0.05);
14: return 0;
15: }

Let's check the execution results:

$ ./prog0306
What's the salary: -1000
Salaty: invalid value

Note the indentation of the two if-else clauses.

Note that the exterior if does not need a block in its else clause, because the interior if is a single instruction, despite also having only one else clause.

Example: Write a program that calculates the gross salary, net salary and tax to be paid following this rule:

SalaryTax
< 10005%
>= 1000 and < 500011%
>= 500035%
prog0307.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   float salary, tax;
 5:   printf("What's the salary: ");
 6:   scanf("%f", &salary);
 7:   if(salary < 1000)
 8:     tax = .05;
 9:   else
10:     if(salary < 5000)
11:       tax = .11;
12:     else
13:       tax = .35;
14:
15:   printf("Gross salary: %.2f\nNet salary: %.2f\nTax: %.2f\n",
16:                        salary, salary-(salary*tax), salary*tax);
17:   return 0;
18: }

Note that what we want to know is what tax is going to be applied, saving that value in the variable tax. This way, we only write one printf, whatever the salary is and whatever the tax is, avoiding writing printf three times within each if clause for all 3 taxes.

Also take note that the conditions written in the code are simpler than the ones written on our problem's wording.

If someone presents a salary of 3000, the first condition if(salary < 1000) returns False (line 8:), moving along to the first else. Next, we have another if, and all we have to do is ask if the salary is less than 5000, since it's already guaranteed that it's higher or equal to 1000, since it entered the else clause through the first if clause.

Note: We need to be quite careful when calculating taxes, since the attribution tax = .05; could not be substitute by the attribution tax = 5/100; which would return 0 (zero) because it's an integer division.

Sometimes, chained if-else clauses can cause some problems if they're not used correctly.

Case in point, let's check this next program:

prog0308.c (Bad indentation!)

 1: #include <stdio.h>
 2: 
 3: int main(){
 4:   int a, b;
 5:   printf("Input two integers: ");
 6:   scanf("%d%d", &a, &b);
 7:   if(a >= 0)
 8:     if(b > 10)
 9:       printf("B is very big\n");
10:   else
11:     printf("A is negative\n");
12:   return 0;
13: }

As you can observe from this program's indentation, there's an exterior if that contains an else. If that if's condition is True, then it will execute the interior if clause.

Well, that'ws what the program's indentation reveals us, but as was mentioned before, indentation serves uniquely to help reading the code, being entirely ignored by the compiler.

Note: Every time chained if-else clauses exist, every else component always belongs to the last if (even if it has no associated else).

Thus, in the previous program, the else belongs to if(b > 10) and not the first if, as we would believe from the program's indentation. In that case, the correct indentation would be:

 8:   if(b > 10)
 9:       printf("B is very big\n");
10:   else
11:     printf("A is negative\n");
12:   return 0;

But that wasn't our objective. Our goal was to write the else clause so that it belongs to the first if(exterior), and not the second if(interior). To solve this problem, we put curly brackets in the instruction associated to the first if.

if(a >= 0){
  if(b > 10)
    printf("B is very big\n");
} else
  printf("A is negative\n");

This way, we limit the interior if's reach to the block to which it belongs.

If an if clause in a set of chained if clauses does not need an else, then you should place that instruction between curly brackets so that the else clause from another exterior if is not wrongly associated to this if.

Logic Operators

There are circumstances in which a simple condition is not sufficient to make a decision, which is why there's a need to connect two or more conditions. To do that, most languages makes available a set of logic operators, which function in the same way as arithmetic operators, but applied to logic values.

OperatorMeaningExample
&&AND (logic AND)x >= 1 && x >= 19
||OR (logic OR)x == 1 || x == 2
!NOT (logic negation)! Continue

These operators allow us to combine two or more logic expressions into one single expression, which returns one single logic value (True or False).

Let's see then what results we get from applying logic operators to two conditions:

Condition 1OperatorCondition 2Result
The earth is round (True)&&Men are mammals (True)True
The earth is round (True)&&Cars are mammals (False)False
The earth is square (False)&&Men are mammals (True)False
The earth is square (False)&&Cars are mammals (False)False

As you can see, the result of applying the logic operator && only returns True when both conditions are true, otherwise it always returns False.

Normally, the results of those operators are presented in Truth Tables, in which you place the values of True and False both in one column and one row. The result of applying the operator to each one of the values can be obtained from the intersection of the pretended column and row.

&&TrueFalse
TrueTrueFalse
FalseFalseFalse
||TrueFalse
TrueTrueTrue
FalseTrueFalse
!TrueFalse
  
FalseTrue
Note: The logic operators are binary operators, while the relational operator ! is a unary operator, being applied only to one expression/condition.

In short,

OperatorExampleResult
&&(cond1 && cond2)True if both conditions are true, False otherwise.
||(cond1 || cond2)True if any of the conditions are true, False if they're both false.
!(!cond)Switches logic values (returns True if the condition is False and vice versa).
Note: In the previous table, we speak of conditions. However, any expression in C can be used as a condition, because it uses a value that can be used as a logic value.
Note: While (x == 10 || 20) represents what we say when we want to indicate that x has to be 10 or 20, that condition is computationally wrong. What we want to guarantee is that the value of x is either 10 or the value of x is 20, thus the respective condition should be (x == 10 || x == 20). Note that, in the case of the expression (x == 10 || 20), the result of that analysis is always True, independently of the value of X, because an OR operation is executed with 20 (which, since it's different from zero, represents True).

Example: Write a program that applies a 10% tax to single people and 9% to married people.

prog0309.c

 1: #include <stdio.h>
 2: 
 3: int main(){
 4:   float salary;
 5:   char civSta;
 6:
 7:   printf("What's the salary: "); scanf("%f", &salary);
 8:   printf("What's your civil state (s/M): "); scanf(" %c", &civSta);
 9:   if(civSta == 'm' || civSta == 'M')
10:     printf("Tax: %.2f\n", salary*0.09);
11:   else
12:     if(civSta == 's' || civSta == 'S')
13:       printf("Tax: %.2f\n", salary*0.1);
14:     else printf("Invalid Civil State!\n");
15:   return 0;
16: }

Relational and Logic Operator Precedence

In C, like in most programming languages, an expression not always analyzed from left to right. For example, the expression 2+3*4 returns the value 14 and not 20, as would be expected if you executed (2+3)*4.

To define the order in which operations are executed, there is a Precedence Table inside which is defined the precedence of every operator.

PrecedenceOperator(s)
5< <= > >=
4== !=
3&&
2||
1? :

In this table are indicated all of the operators presented in this chapter. Note that they all occupy different levels in the hierarchy. The precedence level at the left column means that, the higher an operator is, the bigger its precedence, i.e., when the compiler has to decide the order in which these operations are done, it executes the operations with bigger precedence operators first.

Let's use the next expression as a supposition:

if(x != || y > 1 && y < 10)

The set of operators in competition is:

!= , || , > , && , <

The table indicates that the operators > and < are the ones with bigger precedence, so, the first operations to be executed are y > 1 and y < 10.

The next operation to be executed is the operation with the next biggest precedence operator (!=): x != 10.

And then there's still the || and && operations.

Note: In C, the && operator has bigger precedence than the || operator.

Thus, the AND (&&) operation will be executed before the OR (||) operation.

y > 1 && y < 10

And only then do we apply our OR (||) operation to the obtained result with x != 10.

In sum, you can represent the order of these operations with parenthesis:

if((x != 10) || ((y > 1) && (y < 10)))

Conditional Operator - ?

Example: Write a program that calculates raises in wage for the current year. If the wage is > 1000 it should be raised 5%, otherwise it should be raised 7%.

prog0310.c

 1: #include <stdio.h>
 2: int main(){
 3:   float salary;
 4:   printf("What's the salary: "); scanf("%f", &salary);
 5:   if(salary > 1000)
 6:     salary = salary * 1.05
 7:   else
 8:     salary = salary * 1.07
 9:   printf("New Salary: %.2f\n", salary);
10:   return 0;
11: }

As you can see, the value of the new salary is stored, on both cases, inside the same variable. What happens is - the new value to be atributed depends on a certain condition. We stand in front of a typical case, in which you should use one of C's special operators - the Conditional Operator ?.

The Conditional Operator ? is the only ternary operator in C, which indicates it needs 3 arguments. Its syntax is:

condition ? expression1 : expression2

It works like this:

  • The condition is analysed.
  • If the result is True, the result of the whole expression is the value returned by expression1.
  • If the result is False, the result of the whole expression is the value returned by expression2.

The previous program could be written like this, for example:

prog0310.c

 1: #include <stdio.h>
 2: int main(){
 3:   float salary;
 4:   printf("What's the salary: "); scanf("%f", &salary);
 5:   salary = salary > 1000 ? salary * 1.05 : salary * 1.07;
 6:   printf("New Salary: %.2f\n", salary);
 7:   return 0;
 8: }

The ? operator returns: the result of salary * 1.05 if salary > 1000; the result of salary * 1.07 otherwise.

The result of the operator ? is then atributed to the variable salary.

Now let me show you how line 5:...

 5:   salary = salary > 1000 ? salary * 1.05 : salary * 1.07;

... could have been written in may different ways:

 5:   salary = salary * (salary > 1000) ? 1.05 : 1.07;
 5:   salary = salary * (1 + ((salary > 1000) ? .05 : .07));
 5:   salary = salary + salary * ((salary > 1000) ? .05 : .07);
Note: Although they seem similar, if-else clauses and the ? operator are not the same. if-else indicates which instructions to execute while the operator ? always returns a result (which if does not do).

Example: Write a program that, given a letter, will indicate the a person's civil state.

prog0311.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   char civ_State;
 5:   puts("What's the civil state: ");
 6:   civ_State = getchar();
 7:   if(civ_State == 's' || civ_State == 'S')
 8:     printf("Single");
 9:   else
10:     if(civ_State == 'm' || civ_State == 'M')
11:       printf("Married");
12:     else
13:       if(civ_State == 'd' || civ_State == 'D')
14:         printf("Divorced");
15:       else
16:         if(civ_State == 'w' || civ_State == 'W')
17:           printf("Widow");
18:         else
19:           printf("Invalid Civil State");
20:   printf("\n");
21:   return 0;
22: }

As can be easily observed, when there are many different possible conditions for the same value, the usage of if-else clauses becomes impractical. The code becomes confusing and long.

To solve this problem, C supplies one other instruction that allows the selection of executable code, from a set of possible values for a certain expression.

switch

Switch clauses adapt themselves particularly to decision taking where the number of possibilities is very big (normally, more than 2, otherwise we can use an if-else clause), in order to reduce the complexity of consecutive and chained if-else.

Syntax:

switch (expression){
  case const1: instructions1;
  case const2: instructions2;
   ......
  case const_n: instructions_n;
  [default: instructions; ]
}

In the presented syntax, expression represents any expression which result can be a numeric value of type char, int or long. The expression is analyzed and, right after, switch compares the result of the expression with the value of each constant that follows each one of the cases. The set of all possible values for the expression is placed inbetween curly brackets.

Note: In each switch case, only one char, int or long type constant can be present for analysing.

Example: Write a program that indicates which civil state corresponds to character input in uppercase.

prog0312.c (Bad implementation!)

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   char civ_State;
 5:   puts("What's the civil state: ");
 6:   civ_State = getchar(); /* you can also use scanf("%c", &civ_State); */
 7:   switch(civ_State){
 8:     case 'M': printf("Married\n");
 9:     case 'S': printf("Single\n");
10:     case 'D': printf("Divorced\n");
11:     case 'W': printf("Widow\n");
12:     default : printf("Invalid Civil State\n");
13:   }
14:   return 0;
15: }

In the previous example, we read a character representing the civil state. It's the value present in the variable civ_State that we will have to study to know which result to place in the screen.

That way, the variable we will study is the variable Est_Civil (which is represented by the line 7:).

 7:   switch(civ_State){

The many values that the Civil State can take are: 'M', 'S', 'D' and 'W' (respectively Married, Single, Divorced and Widow). That way, for each of the possible values there should be a case.

Note that, since we're working with characters, so they should be placed between apostrophes.

 8:     case 'M': printf("Married\n");
 9:     case 'S': printf("Single\n");
10:     case 'D': printf("Divorced\n");
11:     case 'W': printf("Widow\n");

The default presents the message to printed out, should the variable civ_State not be equal to any of the constants present in the switch cases.

12:     default : printf("Invalid Civil State\n");

After the colon (:) in each case is the instruction we want to execute in that particular case.

Let's test the program now:

$ ./prog0312
What's the civil state: S
Single
Divorced
Widow
Invalid Civil State
$ ./prog0312
What's the civil state: D
Divorced
Widow
Invalid Civil State
$ ./prog0312
What's the civil state: X
Invalid Civil State

As you see, the program has a bit of an awkward behavior, because it's not indicating the apropriate civil state, and instead, displays a set of unexpected lines.

Note: In the switch clause, when the expression is equal to one of the constants present in a case, the instruction(s) associated to that case will be executed, as well as the instructions of all the cases following the first case (including the default case).

Let's suppose we indicated D in the civil state.

 7:   switch(civ_State){ <===== 'D' Character
 8:     case 'M': printf("Married\n");
 9:     case 'S': printf("Single\n");
10:     case 'D': printf("Divorced\n");
11:     case 'W': printf("Widow\n");
12:     default : printf("Invalid Civil State\n");

Switch will execute the tests and enter into the code relative to case 'D': (line 12:). From there, it will execute all instructions associated to the initial case or any other case following it (including the default case), until it reaches the switch's final bracket.

break

The break instruction allows you to stop inside of a switch, continuing the program in the instruction after the switch.

Note: Every time there is a coincidence between a switch's expression and one of the possible contants for that expression, every instruction associated to the corresponding and following cases are executed until the switch ends or the break instruction is found (or the return instruction).

I now present to you the previous program, but written correctly:

prog0312.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   char civ_State;
 5:   printf("What's the civil state: ");
 6:   civ_State = getchar(); /* you can also use scanf("%c", &civ_State); */
 7:   switch(civ_State){
 8:     case 'M': printf("Married\n"); break;
 9:     case 'S': printf("Single\n"); break;
10:     case 'D': printf("Divorced\n"); break;
11:     case 'W': printf("Widow\n"); break;
12:     default : printf("Invalid Civil State\n");
13:   }
14:   return 0;
15: }

As you can see, we don't need to create a block {} after a case if it's made out of more than one instruction, since they'll all be executed until a break is found or the switch reaches its end.

$ ./prog0312
What's the civil state: D
Divorced

Note: The last case (also called the default case) of a switch does not need a break, because after executing all instructions associated to the last case ends the switch.

Example: Alter the previous prograqm so that it works with both uppercase and lowercase characters.

prog0313.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   char civ_State;
 5:   printf("What's the civil state: ");
 6:   civ_State = getchar(); /* you can also use scanf("%c", &civ_State); */
 7:   switch(civ_State){
 8:     case 'm':
 9:     case 'M': printf("Married\n"); break;
10:     case 's':
11:     case 'S': printf("Single\n"); break;
12:     case 'd':
13:     case 'D': printf("Divorced\n"); break;
14:     case 'w':
15:     case 'W': printf("Widow\n"); break;
16:     default : printf("Invalid Civil State\n");
17:   }
18:   return 0;
19: }

Notice that, if we input a lowecase letter ('d', for example), the switch enters through the respective case. Since the entrances relative to lowercase characters have no break, it keeps executing towards the next case, corresponding to its uppercase equivalent (in this case, 'D'), executing the respective printf. Next, it finds the break and stops executing the switch.

Example: Write a program that calculate the tax payed by women and by men, knowing that women pay 10% tax and men pay 5% more than women.

prog0314.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   float salary, tax = 0;
 5:   char sex;
 6:
 7:   printf("Input the salary: "); scanf("%f", &salary);
 8:   printf("Input your sex (m/f): "); sex = getchar();
 9:   switch(sex){
10:     case 'f':
11:     case 'F': tax = 0.10;
12:               break;
13:     case 'm': 
14:     case 'M': tax = 0.15;
15:               break;
16:   }
17:   printf("Tax: %.2f\n", salary*tax);
18:   return 0;
19: }

This is the most traditional version, using the break instruction after each of the switch options.

Example: Solve the same exercise using a switch clause but without using breaks.

In this case, we'll have to take advantage of switch's own structure, in order to make the calculations correctly.

Read the problem's wording with attention, especially the phrase "knowing that women pay 10% tax and men pay 5% more than women". That way, men pay what the women pay, plus 5%.

Notice the resolution, then:

prog0315.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   float salary, tax = 0;
 5:   char sex;
 6:
 7:   printf("Input the salary: "); scanf("%f", &salary);
 8:   printf("Input your sex (m/F): "); sex = getchar();
 9:   switch(sex){
10:     case 'm':
11:     case 'M': tax = tax + .05;
12:     case 'f': 
13:     case 'F': tax = tax + .10;
14:   }
15:   printf("Tax: %.2f\n", salary*tax);
16:   return 0;
17: }

$ ./prog0315
Input the salary: 100
Input your sex (m/F): m
Tax: 15.00
$ ./prog0315
Input the salary: 100
Input your sex (m/F): f
Tax: 10.00

As you can see, the presented values are correct. Let's then try to understand what's the used strategy.

Initially the tax ratio is placed at 0.0. The tax variable will serve as the ratio accumulator. Since there are no breaks placed into the switch, the instructions to be executed are determined by the entrance case -- both genders have to pay a 10% tax rate, but males go through a 5% addition to taxes.

Example: Write a program that reads the binary operation between two integers and then presents the result of that operation.

This means, the user writes an expression (for example: 7+5) and the program must read the components of an expression and write its result on-screen (7+5=12).

However, the user may represent multiplication as *, x or X. The division could be represented by the characters /, \ or :.

prog0316.c (Bad Programming!)

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   int num1, num2; char op;
 5:   printf("Write an expression: "); scanf("%d %c %d", &num1, &op, &num2);
 6:   switch(op){
 7:     case '+' : printf("%d + %d = %d\n", num1, num2, num1 + num2); break;
 8:     case '-' : printf("%d - %d = %d\n", num1, num2, num1-num2); break;
 9:     case '*' : /* Multiplication */
10:     case 'x' :
11:     case 'X' : printf("%d * %d = %d\n", num1, num2, num1 * num2); break;
12:     case '/' :
13:     case '\\' :
14:     case ':' : printf("%d / %d = %d\n", num1, num2, num1 / num2); break;
15:   }
16:   return 0;
17: }

Note that the character \ is a special character, needing to be preceded by another \ to represent it.

While the program does what was asked of it, it's not well written. I mean, imagine you wanted to alter the form how the expression is written on-screen. You'd need to alter every fucking instance of printf inside of the switch. Ideally, we want to only alter it once with no headaches. that way, we can do this:

prog0316.c

 1: #include <stdio.h>
 2:
 3: int main(){
 4:   int num1, num2, res=0; char op;
 5:   printf("Write an expression: "); scanf("%d %c %d", &num1, &op, &num2);
 6:   switch(op){
 7:     case '+' : res = num1 + num2; break;
 8:     case '-' : res = num1 - num2; break;
 9:     case '*' : /* Multiplication */
10:     case 'x' :
11:     case 'X' : res = num1 * num2; break;
12:     case '/' :
13:     case '\\' :
14:     case ':' : res = num1 / num 2; break;
15:   }
16:   printf("%d %c %d = %d\n", num1, op, num2, res);
17:   return 0;
18: }

In this new version, we invoke printf one, because on the switch, we only care about the operation that was written in order to calculate the corresponding result.

First, we calculate the result through a switch. After choosing the respective case and saving the operation's value in the variable res, all we need to do is exit the switch (through the break instruction) and invoke printf with the respective values.

Note: The program on focus has a small error. In the case the operation is invalid, it writes and places the result 0, fact that would not happen in the previous solution.

The instruction

switch(expression){
  case const_1: instr_1; break;
  case const_2: instr_2; break;
  ........
  case const_n: instr_n; break;
  [default: instructions; ]
}

is equivalent to

if(expression == const_1)
  instr_1;
else
  if(expression == const_2)
    instr_2;
   ......
  else
    if(expression == const_n)
      instr_n;
    else
      instructions; /* default */

Conclusion

So, on this chapter, we learned all about making our programs alter their behavior based on conditions.

We learned how to create if-else and switch clauses, how and where to implement instruction blocks andn how to make use of relational and logic operators.

However, you may have been experimenting with coding and may have noticed that it is very ugly to copy paste code when we want to repeat sections of instructions. It would be cool if we could just cycle through a bunch of code.

Well, stay tuned. We'll teach you about that on the next chapter!

< 2. Basic Data Types

4. Cycles >

Check These Articles