Home
Netbeans Eclipse Qt Java
Games
College of Engineering Aeronautics and Astronautics Agricultural and Biological Engineering Biomedical Engineering Chemical Engineering Civil Engineering Construction Engineering and Management Electrical and Computer Engineering Engineering Education Engineering Professional Education Environmental and Ecological Engineering Industrial Engineering Materials Engineering Mechanical Engineering Nuclear Engineering
EPICS (Engineering Projects In Community Service) First-Year Engineering Program First-Year Engineering Honors Program Global Engineering Program Minority Engineering Program Professional Practice (Co-Op) Program Women in Engineering Program
College Administration Schools Programs All Groups All People ECN Webmail
Purdue Home

ECE 264 Advanced C Programming

2009/01/21

Logic Expressions

C uses boolean logic to control programs' flow.  The following are commonly used logic expressions

  • a > 0 : true if a is larger than zero
  • a && b: true if both a is true and b is true
  • a || b: true if a is true or b is true, or both are true
  • a == b: true if a and b have the same value. Be careful when you use this when a or b (or both) is a floating-point number. Due to limited precision, two floating-point numbers may be slightly different.
       double val = 1e-7;
       int cnt;
       for (cnt = 0; cnt < 10000000; cnt ++)
       {
          val += 1e-7;
       }
       printf("%f %e %d\n", val, val - 1, (val == 1.0));

The output is

       1.000000 9.975017e-08 0

 showing that val is not exactly 1.

  • a <= b: true if a is smaller than or equal to b
  • ! a: true if a is false

You can use a combination of different conditions. For example

      if ((a > 0) && (b < c))

is true if a is greater than 0 and b is smaller than c.

When you have a complex condition, remember to use parentheses for clarity. If the expression is too complex, break it into several conditions. Writing a clear program can help you discover mistakes more easily.

Control Flow

if

If a computer program can do only one thing, the program isn't particularly useful. Imagine that you go to an on-line store and the store sells only one item. You cannot choose anything else. A program is more useful if it can make some decisions; for example, you decide to buy a book on C programming not a book on Java programming and you want the book to arrive sooner (and pay more for shipping).

C provides several ways to control the execution of a program. All of them require decisions based on true-false logic:

if (something is true)
{
    do something
} 
else /* this part is optional */
{
    do something else
}

This "something" can be jumping to another location of the program and executing the code there. We have seen several examples using C's control

int main(int argc, char * argv[])
{
  int val1;
  int val2;
  if (argc < 3)
    {
      fprintf(stderr, "need two numbers\n");
      return -1;
    }
  val1 = atoi(argv[1]);
  val2 = atoi(argv[2]);
  printf("%d + %d = %d\n", val1, val2, add(val1, val2));
  return 0;
}

This uses an if condition. If the value of argc is smaller than 3, the program prints an error message and return -1. Otherwise, the program continues to assign the values to val1 and val2.

for

Another example:

  for (cnt = 0; cnt < argc; cnt ++)
    {
      printf("%s\n", argv[cnt]);
    }

This for block is equivalent to the following

   cnt = 0;
repeat_label:
   if (! (cnt < argc))
   {
      goto done_label;
   }
   printf("%s\n", argv[cnt]);   
   cnt ++;
   goto repeat_label;
done_label:

In general, you should avoid goto because too many goto's can make the program's flow hard to analyze.  You can put more statements before the first semicolon and after the second semicolon, for example

    int sum;
    int cnt;
    for (sum = 0; cnt = 0; cnt < vecSize; sum += vec[cnt], cnt ++)
        ;

This initializes both sum and cnt to zero. In each iteration, sum increases by vec[cnt] and cnt increments by one. It is equivalent to the following code

    int sum;
    int cnt;
    sum = 0;
    for (cnt = 0; cnt < vecSize; cnt ++)
    {
       sum += vec[cnt];
    }

The second is easier to understand (for a person).  A legal (i.e. can be compiled) C program is not necessarily a good program. You should write programs that are easy to read. If a program is easy to read, you have a smaller chance of make mistakes. So far, we format the programs nicely (called indentation). We can change it to

    int sum; int cnt;
            sum = 0;     for (cnt = 0; cnt < 
vecSize; 
                     cnt ++)   {
       sum += 
vec[cnt];
    }

The compiler does not care about the difference but the program is harder to read. Many text editors will indent your C programs, for example emacs and eclipse. Proper indentation will reduce the chance of mistakes.

while

There is one important restriction of using for. We have to know how many iterations to execute in advance. Suppose a program needs a positive number from a user 

  do
    {
      printf("enter a positive number: ");
      scanf("%d", & cnt);
    } while (cnt <= 0);
  printf("That's right; %d is a positive number.\n", cnt);

This will keep asking the user until the user enters a positive number. The following is an example of execution:

enter a positive number: -9
enter a positive number: -7
enter a positive number: 0
enter a positive number: 1
That's right; 1 is a positive number.

In C, do { /* some code */ } while(condition); will execute at least once because the condition is checked after the code.  We can also move the while condition to that top.  In that case, the code may not execute at all. The following example is equivalent to for:

     /* same as
     for (cnt = 0; cnt < vecSize; cnt ++)
     {
         printf("%d\n", vec[cnt]);
     }
     */
     int cnt = 0;
     while (cnt < vecSize)
     {
         printf("%d\n", vec[cnt]);
         cnt ++;
     }

This example shows that while can implement for.

switch-case

Sometimes, you want to distinguish several cases. For example, a computer game has to check whether a user presses up (u), down (d), left (l), and right (r) keys. This can be done by using several if's.

  if (key == 'u')
    {
      /* move up */
    }
  else
    {
      if (key == 'd')
	{
	  /* move down */
	}
      else
	{
	  if (key == 'l')
	    {
	      /* move left */
	    }
	  else
	    {
	      if (key == 'r')
		{
		  /* move right */
		}
	      else
		{
		  /* invalid, error message */
		}
	    }
	}
    }

There is a better way to handle this situation.

  switch (key)
    {
    case 'u':
      /* move up */
      break;
    case 'd':
      /* move down */
      break;
    case 'l':
      /* move left */
      break;
    case 'r':
      /* move right */
    default:
      /* invalid, error message */
    }

Using switch makes the code easier to read. It is necessary to put break before the next case; otherwise, the code in the next case will also be executed.  In the next example,  pressing 'U' and 'u' executes the same code.

  switch (key)
    {
    case 'u': /* no break */
    case 'U':
      /* move up */
      break;
    case 'd':
      /* move down */
      break;
    case 'l':
      /* move left */
      break;
    case 'r':
      /* move right */
    default:
      /* invalid, error message */
    }

Forgetting to add break in correct locations is a common mistake.

Common Mistakes in Flow Control

Brackets and if-else Pairs

Flow control is critical in most programs. Therefore, it is very important to write the control correctly. The following are some common mistakes and how to avoid them.  In C, the following two pieces of code are equivalent

       if (a > 0)
       {
           b = -1;
       }

and

       if (a > 0)
          b = -1;

If there is only one statement controlled by if, it is unnecessary to use brackets. However, the first (using brackets) is better because it prevents you from making the following commom mistake. If you add another statement later, without the bracket, you may add the statement directly and the new statement is no longer controlled by the condition.

       if (a > 0)
          b = -1;
          c = -2;

is equivalent to

       if (a > 0)
       {
           b = -1;
       }
       c = -2; /* not controlled by a's value */

and is different from

       if (a > 0)
       {
           b = -1;
           c = -2; 
       }

Adding the brackets can reduce the chance of mistakes. 

In C, else corresponds to the closest if.  What is the value of z after executing this code?

  int x = 1;
  int y = 2;
  int z = 3;
  if (x > 10)
    if (y > 4)
      z = -1;
  else 
    z = -2;

Is z -2 because if (x > 10) is false? Which of the following two corresponds to the code above?

  if (x > 10)
    {
      /* nothing between this bracket */
      if (y > 4)
	{
	  z = -1;
	}
      else 
	{
	  z = -2;
	}
      /* and this bracket will be executed */
    }
    /* z unchanged since x > 10 is false */

or

  if (x > 10)
    {
      if (y > 4)
	{
	  z = -1;
	}
    }
  else 
    {
      z = -2; /* z is changed to x > 10 */
    }

The answer is z = 3 because the else corresponds to the closest (second) if. This is another reason you should add brackets to ensure that the code is exactly what you want.  You should indent the code because proper indentation helps you visually find the mistakes.  This is very easy since many tools can do it for you, including emacs, eclipse, or a shell program called indent.

Default in switch

You should always add the default condition at the bottom of a switch block. You may think that the cases have covered all possible scenarios. However, it is common that you miss one case. Adding default and printing an error message can help you discover the mistake early.