Jump to: navigation, search

Chapter 5 - Equality operators, relational operators, and control structures

So far, we have seen how a computer can perform one simple task after another, a concept known as sequence. To be useful, however, it must be able to make decisions (selection between alternate code paths). Less obviously, it must be able to perform a task repeatedly (this is known as iteration).

Computers make decisions by exploring the relationship between numbers, using relational operators. So we must first tackle these relational operators, and then we can see how they are used for decision-making.

C has a number of operators for comparing one number to another, and they all share one common characteristic: they yield a truth value. A truth value is either 0 or 1. 0 means false, and 1 means true.

Equality operators

Equality

The most basic relation, and perhaps the easiest to understand, is that of equality. We ask the question "do these two numbers have the same value?"

You might expect the operator that tests for equality to be the = sign, but it isn't, quite. That symbol is already used for assignment, and unfortunately there are contexts in which the two operators could each make sense. So we need a slightly different symbol, and so C uses two = signs, like this: ==. There must not be a space between them. Think of it as a single word.

The == operator, like all C's equality and relational operators, yields a value of 0 for falsity, or a value of 1 for truth.

int main(void)
{
  int firstnumber = 6;
  int secondnumber = 17;
  int thirdnumber = 6;

  firstnumber == secondnumber; /* a comparison, not an assignment! */

  firstnumber == thirdnumber; /* another comparison */

  return 0;
}

The above program has two conditional expressions:

firstnumber == secondnumber;

which yields 0, and

firstnumber == thirdnumber;

which yields 1.

We can assign the result of an equality test to an object, like this:

#include <stdio.h>

int main(void)
{
  int firstnumber = 6;
  int secondnumber = 17;
  int thirdnumber = 6;
  int firstresult;
  int secondresult;

  firstresult = firstnumber == secondnumber;
  putchar(firstresult + '0');
  putchar('\n');

  secondresult = firstnumber == thirdnumber; /* another comparison */
  putchar(secondresult + '0');
  putchar('\n');

  return 0;
}

The first equality test compares the value of firstnumber, 6, with the value of secondnumber, 17. Since these are not equal, the result of the equality test is 0 (false). This value 0 is assigned to firstresult. Then we add '0' to this result (to turn it into a printable digit character), and of course '0' + 0 is '0', so the digit character '0' is printed. Then we write a newline.

The second equality test compares the value of firstnumber, 6, with the value of thirdnumber, which is also 6. Since these values are equal, the result of the equality test is 1 (true). This value 1 is assigned to secondresult. Then we add '0' to this result (again, to turn it into a printable digit character). Because digit characters are guaranteed to have consecutive code points, '0' + 1 is '1', so the digit character '1' is printed, followed by a newline.

Inequality

Just as we sometimes need to know whether two numbers are equal, we also sometimes need to know whether they are not equal. To test this, we use the inequality operator. It is written as != and, again, there must be no space separating the two parts of the symbol (together, they form a single 'word'). This operator takes two values and compares them, this time for inequality. If the two numbers are unequal, the inequality operator yields 1. Think of 1 as meaning yes and 0 meaning no. The question is: are these numbers different to each other? If the answer is yes, we get 1 as the result. Otherwise we get 0, meaning no.

Relational operators

The less-than operator

When we need to know whether a number has a lower value than another number, we use the less-than operator. This time, it's a single symbol, the < symbol, which is normally to be found to the right of the M on your keyboard (you will almost certainly need to use the SHIFT key to get access to it).

For the == and != operators, the order of operands doesn't matter, because two numbers are either equal or not, no matter which way round they are. But if we want to know whether a number is less than another number, we need to tell the operator which number we are asking about. The easiest way to understand this is to see some examples:

6 < 4; /* 6 is NOT less than 4, so this expression is false, giving 0 */
4 < 6; /* 4 IS less than 6, so this expression is true, giving 1 */
6 < 6; /* 6 is NOT less than 6, so this expression is false, giving 0 */
The greater-than operator

As you might expect, the greater-than operator is a mirror-image of the less-than operator, so it is written as > (normally on the key next to the less-than operator).

Let's use the same examples as above, in the same order, and we'll see the difference between less-than and greater-than very clearly:

6 > 4; /* 6 IS greater than 4, so this expression is true, giving 1 */
4 > 6; /* 4 is NOT greater than 6, so this expression is false, giving 0 */
6 > 6; /* 6 is NOT greater than 6, so this expression is false, giving 0 */
Negations

C also supplies us with "not greater than" and "not less than" operators, but it may be more helpful to think of them as "less than or equal to" and "greater than or equal to" operators. Let's deal with them both in the same way as above:

/* less than or equal */
6 <= 4; /* 6 is NOT less than or equal to 4, so this expression is false, giving 0 */
4 <= 6; /* 4 IS less than or equal to 6, so this expression is true, giving 1 */
6 <= 6; /* 6 IS less than or equal to 6, so this expression is true, giving 1 */

/* greater than or equal */
6 >= 4; /* 6 IS greater than or equal to 4, so this expression is true, giving 1 */
4 >= 6; /* 4 is NOT greater than or equal to 6, so this expression is false, giving 0 */
6 >= 6; /* 6 IS greater than or equal to 6, so this expression is true, giving 1 */

C also provides another negation operator, the NOT operator, which is a single exclamation mark. Unlike the other relational operators, it takes only one operand.

If the operand has the value 0, the NOT operator yields the value 1 (true). In all other cases, it yields 0 (false).

! 0; /* this gives 1. Why? Because 0 is false, so NOT 0 is true, so !0 is true (1). */
! 1; /* this gives 0. Why? Because 1 is non-zero, so it's not false, so it's true, so NOT 1 is not true, so it's false, so !1 is false (0). */
! 6; /* this also gives 0. Why? Because 6 is non-zero, so it's not false, and the rest of the reasoning is as given above. */

Although these operators can be used in expressions as shown above, this isn't how they are normally employed. Rather, they are used in conjunction with C's control structures to direct the flow of control of the program.

Control structures

Every C program has a flow of control, which can loosely be thought of as the order in which things happen. In every program we have seen so far, the flow of control begins in the main function, and proceeds from top to bottom of that function, taking an occasional diversion into some other function but otherwise progressing in a linear fashion. When the flow of control does divert to another function, it progresses in precisely the same way, from top to bottom, via other called functions if need be.

But there is more to it than that. We can use relational operators to make decisions about the flow of control, via the if control structure.

The if control structure

An if statement allows us to determine whether or not to execute a block of code. The clearest way to explain this is via a program:

#include <stdio.h>

int main(void)
{
  int input = getchar();
  if(input == '6')
  {
    putchar('Y');
  }
  else
  {
    putchar('N');
  }
  putchar('\n');
  return 0;
}

The above program waits for you to enter some data at the keyboard. (When running this program, press one of the number keys, '0' to '9', and then press the ENTER key.)

The program then uses the equality operator to test the value you entered for equality with the digit character '6'. If that's the key you pressed first, then the equality comparison will yield a value of 1 (meaning true). Once this value has been determined, the if keyword is used to test this value's truth. If the value is true (i.e. not 0) then the next statement (or block of statements marked off with an opening brace { and a closing brace }), will be executed. Otherwise, the statement (or block of statements enclosed in braces) following the else will be executed.

Thus, if you typed '6' as your first character, this program will print the letter Y. Otherwise, it will print the letter N.

The else part is optional. The following program doesn't use it:

#include <stdio.h>

int main(void)
{
  int input = getchar();
  if(input == '6')
  {
    putchar('Y');
    putchar('o');
    putchar('u');
    putchar(' ');
    putchar('t');
    putchar('y');
    putchar('p');
    putchar('e');
    putchar('d');
    putchar(' ');
    putchar('6');
  }
  putchar('\n');
  return 0;
}

If you did in fact type '6' as your first character, then this program will print out: You typed 6

But if you didn't, then it won't. In either case, it will print the newline character, thus completing the line of text.

What a horrible program, though! Surely we can do better than that?

Well, yes. Here's a slightly cleaner version, using functions to reduce the work:

#include <stdio.h>

void put_2(int ch0, int ch1);

int main(void)
{
  int input = getchar();
  if(input == '6')
  {
    put_2('Y', 'o');
    put_2('u', ' ');
    put_2('t', 'y');
    put_2('p', 'e');
    put_2('d', ' ');

    putchar('6');
  }
  putchar('\n');
  return 0;
}

void put_2(int ch0, int ch1)
{
  putchar(ch0);
  putchar(ch1);
  return; /* void function - no return value */
}

As this program demonstrates, we can pass more than one datum to a function. If we do so, however, we must separate each argument using a comma, both in the call and in the function definition.

By doing this, we massively reduce the amount of work that main has to do. But we can do better still:

#include <stdio.h>

void put_2(int ch0, int ch1);
void put_4(int ch0, int ch1, int ch2, int ch3);

int main(void)
{
  int input = getchar();
  if(input == '6')
  {
    put_4('Y', 'o', 'u', ' ');
    put_4('t', 'y', 'p', 'e');
    put_2('d', ' ');

    putchar('6');
  }
  putchar('\n');
  return 0;
}

void put_2(int ch0, int ch1)
{
  putchar(ch0);
  putchar(ch1);
  return;
}

void put_4(int ch0, int ch1, int ch2, int ch3)
{
  put_2(ch0, ch1);
  put_2(ch2, ch3);
  return;
}

Here is the call graph for the above program:

By dealing with several characters in each function call, we reduce the amount of work we have to do in main. This program is a lot cleaner, so we've found a temporary solution for our output problem.

Unfortunately, the solution isn't much use to us if we have to deal with numerical data. Let's consider the problem of accepting four digits from the standard input stream, and adding them all together. If their sum is 9 or less, we're okay, because we can just add '0' to the answer and send that value to putchar. But what if their sum is greater than 9? It could be as high as 36 (because we want four single-digit values).

We could use if to find out what tens digit we should print.

#include <stdio.h>

int main(void)
{
  int in0 = getchar() - '0';
  int in1 = getchar() - '0';
  int in2 = getchar() - '0';
  int in3 = getchar() - '0';
  int sum = in0 + in1 + in2 + in3; /* yes, you can chain these +s together */
  int tens = 0;

  if(sum > 9)
  {
    tens = 1;
    if(sum > 19)
    {
      tens = 2;
      if(sum > 29)
      {
        tens = 3;
      }
    }
  }

  if(tens > 0) /* only display a tens digit if we need to */
  {
    putchar('0' + tens);
  }

  /* We've dealt with any tens, so now we only need the units, which
     is what's left if we divide by ten. Hence we use the remainder
     operator.
   */
  putchar('0' + sum % 10);

  putchar('\n');
  return 0;
}

This works. (To use it, just type four digits, e.g. 5937, followed by the ENTER key. It will add up the four digits and display the result, in this case 24.) But there is a cleverer way we can do this. We still use the if keyword, but in a function that calls itself, dividing the problem up into smaller chunks as it goes.

#include <stdio.h>

void print_integer(int);

int main(void)
{
  int ch0 = getchar() - '0';
  int ch1 = getchar() - '0';
  int ch2 = getchar() - '0';
  int ch3 = getchar() - '0';
  int sum = ch0 + ch1 + ch2 + ch3;
  print_integer(sum);
  putchar('\n');
  return 0;
}

void print_integer(int n)
{
  if(n > 9)
  {
    print_integer(n / 10);
    n = n % 10;
  }
  putchar(n + '0');
}

The print_integer function is unusual in that it calls itself. This technique, which is called recursion, is a useful one if you're careful. The idea is to break up the problem of writing a multi-digit number into smaller sub-problems.

Here is the call graph for the program:

The arrow that points from print_integer right back to print_integer is an indication that the program is recursive in nature.

The recursion works like this: we know how to deal with the problem of printing the last digit in a number: we just add '0' to it and we're done. So let's split the number into two parts: the last digit, and everything else. The last digit can be found using the remainder operator: n % 10. And everything else can be found by using the division operator, dividing by 10. For example, if our input is 9875, then the sum is 29. 29 / 10 is 2 remainder 9. So we farm out the problem of displaying the 2 (the result of division by ten) to print_integer, and then deal with the 9 ourselves.

So, when we call print_integer with 29, the first thing that happens is that print_integer is called again, with 2. This isn't greater than 9, so we just print 2 + '0', which is the '2' digit character. Then we return back to our first invocation of print_integer, where we display '0' + 9, i.e. the '9' digit character. And then we return back to main.

Don't worry if you don't quite get it yet. We'll come back to recursion later on.

The while control structure

When we need to repeat a block of code several times, we use the while keyword to create a loop of code.

while(condition)
{
  /* statements go here */
}

The condition is just an expression that evaluates to 0 or not, as the case may be. For as long as it evaluates to some number other than 0, the statements inside the curly braces are executed. (If there's only one statement, you don't need the curly braces, but they are generally considered to be a good idea anyway.) As soon as the conditional expression evaluates to 0, control passes beyond the while loop. An example program may help to make this clearer:

#include <stdio.h>

int main(void)
{
  int countdown = getchar(); /* type a digit character, then press ENTER */

  while(countdown >= '0')
  {
    putchar(countdown);

    countdown = countdown - 1;
  }
  putchar('\n');
  return 0;
}

If you play nice and type a digit character, this program will count down from that character to 0. For example, if you type in an 8, the program will display 876543210 and then a newline character.

Note the line:

countdown = countdown - 1;

Mathematically, this line makes no sense, of course. But in C, it means: find out what value the countdown object has at the moment; subtract one from that value; then store the result of the subtraction in the countdown object.

The line after that is the closing brace that marks off the while loop. Control now returns to the top of the while loop, and the condition is tested again. If the condition is still true, the loop body executes again. This keeps on happening until eventually, at the point where the condition is tested, that condition turns out to be false. In that eventuality, the loop is not executed any more. Control now passes beyond the loop, to the putchar call that displays the newline character.

(What happens if you don't play nice? Try running the program with non-digit inputs.)

The do/while control structure

The while loop conducts its truth test at the top of the loop. It may be that the condition is false right from the outset, in which case the loop won't be executed at all. This isn't always what you want. Sometimes, you need the loop to be executed at least once, in which case you can use the do/while construct, which places the test at the end:

#include <stdio.h>

int main(void)
{
  int countdown = getchar(); /* type a digit character, then press ENTER */

  do
  {

    putchar(countdown);
    countdown = countdown - 1;

  } while(countdown >= '0');

  putchar('\n');
  return 0;
}

To give a more concrete example of a loop, let's consider the problem of printing a table of square numbers. A square number is the result of multiplying a number by itself. So here is a program that will display the first twenty square numbers.

#include <stdio.h>

void print_integer(int);

int main(void)
{
  int number = 0;
  int square;

  while(number < 20)
  {
    square = number * number;

    print_integer(number);
    putchar(' ');
    print_integer(square);
    putchar('\n');

    number = number + 1;
  }
  return 0;
}

void print_integer(int n)
{
  if(n > 9)
  {
    print_integer(n / 10);
    n = n % 10;
  }
  putchar(n + '0');
}

Running this program produces the following output:

0 0
1 1
2 4
3 9
4 16
5 25
6 36
7 49
8 64
9 81
10 100
11 121
12 144
13 169
14 196
15 225
16 256
17 289
18 324
19 361

This table is poorly presented, but it does at least give the right numbers, and the loop stops when it's supposed to. We will deal with the presentation problem later on.

Things to do

To test your understanding of loops, modify the square numbers program so that it prints the results from 19 down to 0 rather than from 0 up to 19. How should you initialise the number object? What should your loop condition be? How should you change the way in which the number object is updated in each pass through the loop?

Summary

In this chapter, you learned about C's relational operators, and how they can be used to modify the flow of control of a program.

In the next chapter, we will discover a way to deal with collections of numbers as a single entity.

Progress

Terminology
  • sequence, selection, and iteration
  • control structure
  • recursion
  • code point
  • object
  • type
  • value
  • definition
  • assignment
  • initialisation
  • standard input
  • standard output
  • standard error
  • preprocessor
Syntax
  • comments
  • types
  • operators
    • assignment operators
      • the = operator
    • additive operators
      • the + operator
      • the - operator
    • multiplicative operators
      • the * operator
      • the / operator
      • the % operator
    • equality and relational operators
      • the == operator
      • the != operator
      • the < operator
      • the > operator
      • the <= operator
      • the >= operator
      • the ! operator
  • control structures
    • if/else
    • while
    • do/while
Standard library functions, by header
Personal tools