Lesson #6

Expressions and Precedence

Overview

In this lesson I will cover the following topics

  1. The binary operators that provide for the basic arithmetic operations.

  2. The assignment operator.

  3. Expressions and precedence.

  4. Some shortcut operators.

Body

Binary operators

An "operator" is a special symbol that causes the program to perform a certain operation. The values that are operated upon are called "operands". For example, in the statement

  x = y + z;

The values stored in y and z are the operands to the + operator. The + operator is an example of a "binary" operator. It is called binary because it takes two operands. There are several other binary operators in C. The most important are

  x = y + z;  // Addition
  x = y - z;  // Subtraction
  x = y * z;  // Multiplication
  x = y / z;  // Division
  x = y % z;  // Modulus

Addition, subtraction, and multiplication are all as you would expect. The last two require some more explaination.

When two integers are divided the result is just the quotent. The fractional part is thrown away. For example 5/2 is 2, not 2.5 like you might expect. Similarly 15/4 is 3, not 3.75. The significance of this is that the division of integers yields another integer. Furthermore the result is not rounded off. This was done on purpose. It is much faster than computing a result using floating point numbers. Also, believe it or not, it is often exactly what you want.

It is important to know that when you divide floating point numbers (variables of type float, double, or long double), the result is a floating point number. That is, 5.0/2.0 is 2.5 just as you might expect (except that it might really be something like 2.50000000013298213). Notice that I typed an explict decimal place to indicate a floating point value. The C compiler understands this. If you put a "5" in your program, the compiler treats it as an integer. If you put a "5.0" in your program, the compiler treats it as a double.

The modulus operator might be unfamiliar to you. It computes the integer remainder after doing integer division. For example, 11/4 yields a quotent of 2 and a remainder of 3. Thus 11 % 4 is 3 (the remainder). Division and modulus are thus two sides of the same operation. You can see how they relate with this chart.

0/3 is 0 0%3 is 0
1/3 is 0 1%3 is 1
2/3 is 0 2%3 is 2
3/3 is 1 3%3 is 0
4/3 is 1 4%3 is 1
5/3 is 1 5%3 is 2
6/3 is 2 6%3 is 0
7/3 is 2 7%3 is 1
8/3 is 2 8%3 is 2
etc... etc...

C has other binary operators besides the five I've shown here, but these are commonly used and will be enough for the moment.

Unary operators

A unary operator is an operator that has only one operand. The only one we will worry about right now is unary minus. For example

  x = -y;

This takes the value of y, negates it, and puts the result into x. There is also a unary plus, but nobody uses it because it doesn't do anything. It exists in the language purely for symmetry with unary minus.

C has quite a few other unary operators that we will discuss as we come to them.

The assignment operator

First, consider this statement

  x = y;

The assignment operator is a binary operator because it requires two operands. The operand on the right is the value that is being copied. The operand on the left is the place the copy is going. Assignment is special because the left operand can't be just anything. It must refer to something that exists in the program's memory. Consider

  x = y + z;

This is fine. The value of y + z is computed and that value is put into x.

  x + y = z;

Error! I can't put the value of z into x + y because there is no place in memory associated with x + y.

In C techno-babble we say that assignment's left operand must be an "lvalue". The "l" in "lvalue" comes from "left". C is rather unique in the way it handles assignment. In most languages assignment is handled by a statement, not an operator. This is an subtle technical distinction, but it means that C (and C++) need to define the concept of "lvalue" while most other languages can ignore the whole issue. How important is this to us? Not very. However, you will probably eventually get an error message from the compiler that says something like "lvalue required". It would be nice if you had some idea of what it was talking about!

Expressions

It is important to understand that, except for assignment's left operand, none of the operators we've looked at so far modify their operands. Let's look at that again:

  x = y + z;

This computes the sum of y and z, BUT neither y nor z is changed by that. The value of x is changed but that's because assignment has the special feature that it modifies it's left operand (that's why that operand must be an lvalue).

  x = -y;

Here the negation of y is computed and that result is put into x, but the value of y is not changed. Most operators are like this. With only a few special exceptions, they do not modify their operands.

As a result of this, complicated expressions can be created by combining simplier ones using parentheses. For example

  x = (z1 + z2) / (z1 - z2);

Here the left operand of / is the result of computing z1 + z2. The right operand of / is the result of computing z1 - z2. The result of the final division is then put into x. The values of z1 and z2 are not changed by any of this. Only x is changed.

The fact that the operators don't modify their operands is important to make things like this work. What if / modified its operands? In that case, it would be trying to modify z1 + z2. That would be sort of like trying to do

  z1 + z2 = x;

It doesn't make sense! Thankfully that is not what's happening and the expression makes perfectly good sense. You can write expressions that are as large and nasty as you want. Any good C compiler should be able to handle anything no matter how complex it gets. There are no limits.

  x = ((z1 + z2) * (z1 - z2)) / ((x + y) / 2);

Here the left operand of / is the result of multiplying z1 + z2 by z1 - z2. The right operand of / is the result of computing (x + y) / 2. Notice that this statement uses the value of x and assigns to the value of x. This is not a problem. The original value of x is used to compute the new result and then that new value is assigned to x. This is very much like what happens in this statement

  x = x + 1;

Notice also that I used spacing in such a way as to make the grouping of subexpressions clear. I believe that you should include spaces around binary operators, including assignment, but not around unary operators. You should avoid putting spaces after '(' or before ').'

Precedence

I've been writing

  x = y + z;

without any parentheses. How can I get away with that? There are two binary operators in that statement: plus and assignment. Which one is computed first? Does it matter? In general it does matter. First consider:

  x = (y + z);

This is what I want to happen. It is legal and reasonable. Now consider:

  (x = y) + z;

Strangely this happens to also be legal. In C the value of an assignment operation is the value of the left operand after the assignment occured. Thus this statement assigns y to x and then computes the sum of z and the new value of x. The result of that addition is then thrown away. In short, the statement is legal but doesn't make a whole lot of sense.

It happens that the version I want is what I will get. This is because the addition operator has higher "precedence" than assignment. When those two operators compete for the same operands, addition will win. It is done first.

  x = y + z;
  //  ^ Both + and = want this operand. Addition wins. More precedence.

In your mind, visualize the statement like this

  x     =     y+z;

The addition operator bonds to its operands more tightly than assignment.

Now take a look at this statement

  x = a + b / c;

Does this add a to the quotient of b/c or does it divide c into the sum of a+b? In other words are we getting

  x = a + (b / c);

or

x = (a + b) / c;

This is a very important question because, mathematically, the two computations are very different. The answer is that we are getting

  x = a + (b / c);

This is because division has higher precedence than addition. Visualize the statement like this

  x     =     a  +  b/c;

Multiplication and division have the same precedence which is higher than addition or subtraction. Addition and subtraction have the same precedence which is higher than assignment. Every operator has a precedence level. Knowing these levels for the commonly used operators is important. Consult your text for a complete list of all of C's operators and their relative precedence.

But what happens when an expression involves two operators with the same precedence? Check out this example

  x = a + b + c;

In this case you need to consider the associativity of the operators. Checking a precedence chart I see that addition associates from left to right. Thus the above statement becomes

  x = ((a + b) + c);

In other words, the leftmost operation is done first. Now it happens that it doesn't really matter for addition since addition is mathematically associative (it might matter in real life if there was a possibility of overflow). But what about this case

  x = a / b * c;

Multiplication and division have the same precedence. Yet

  x = (a / b) * c;

and

  x = a / (b * c);

are very different! Checking a precedence chart I see that multiplication and division associate from left to right. Thus I get

  x = (a / b) * c;

If I want the other option I can add explicit parentheses. You can always override the precedence or associativity of an operator by using parentheses. Since parentheses never hurt, feel free to include them when you are in doubt about the interpretation of an expression. Nevertheless knowing some of the basic rules is a must.

Sample program

Attached to this lesson is a sample program named prec.c. You should compile that program and try it out. It asks you to enter three integers. It then computes various expressions using those integers and prints out the results. Be sure you understand why the results are what they are.

Notice how in the sample program I declared a single integer named result to hold the results of each computation. Since I print out one result before calculating the next, there is no reason why I can't reuse the result variable. It is very important to understand that the instructions in a program execute in a certain sequence. The machine does one step at a time moving from the first line of the program toward the last.

A few other useful operators

The C language has a few "shortcut" operators for commonly needed operations. Consider the statement

  x = x + 1;

The point of this statement is to add one to x and replace x with the new value. This sort of thing is so common that C has a special version of the assignment operator for it.

  x += 1;

Do not put a space between the '+' and the '='. Unlike the other operators we have seen so far, this operator is two characters.

In addition to +=, there are also similar operators for the other math operations.

  x += y;         // Like x = x + y;
  x -= y;         // Like x = x - y;
  x *= y;         // Like x = x * y;
  x /= y;         // Like x = x / y;
  x %= y;         // Like x = x % y;

Think of these operations as "in place" operations. As with ordinary assignment they modify their left operand. In this case they make the modification by "blending in" the right operand according to the appropriate operation.

For a while these in place operations were peculiar to C. Other languages, like Pascal, don't have them. However, C has been a very influential language. Many newer languages have similar operators because C programmers expect them. They can be very handy. When you get used to them, you will really like them.

Increment and decrement

Adding or subtracting one from the value of a variable is so common that C provides special operators just for that purpose.

  x  = x + 1;     // You're a Pascal programmer, right?
  x += 1;         // Not really the way it would be done in C.
  x++;            // That's more like it!

To subtract one from a variable, use --.

  x--;

These statements look a little strange because there is no equals sign in them. The ++ operator (increment) and the -- operator (decrement) modify their operands directly. In other words you don't want to say

  x = x++;        // Bad.

The increment and decrement operators are actually a bit subtle. It turns out that you can do either

  x++;

or

  ++x;

if you want to increment x. However, there is a difference. The difference shows up if you use ++ (or --) in a more complex expression. This is not something I want to get into right now. We will revisit the issue later when we are talking about pointers. But if you are interested, consider

  x = y++;
  x = ++y;

In the first case the original value of y is assigned to x and then y is incremented afterwards. In the second case, y is incremented first and then the new value is assigned to x. The first case is called "post" incrementing and the second case is called "pre" incrementing. If you didn't follow that, don't worry about. You don't need to understand that right now. For now, just avoid using ++ or -- with any other operators.

Summary

  1. In C you use '+' for addition, '-' for subtraction, '*' for multiplication, '/' for division, and '%' for modulus (remainder). For example:

      x = y / z;   // Computes y divided by z and puts result in x.
    

    When integers are divided the fractional result is thrown away. The result is an integer. For example, 5/2 is 2, not 2.5.

  2. C treats assignment as an operator. However, unlike most other operators, the = operator modifies one of its operands (the left hand one).

  3. Large and complicated expressons can be made by combining operators so that the results of one calculation become an operand of the next. When combining operations, it is important to know which is done first. This depends on the precedence of the operators involved. You can, however, always use parentheses to force a certain order in the computation.

  4. C allows you to combine a simple math operation with an assignment using a shortcut operator. For example

      x += y;    // Like x = x + y;
    

    You can also increment (add one) or decrement (subtract one) a number using ++ or -- like this

      x++;
      y--;
    
© Copyright 2016 by Peter C. Chapin.
Last Revised: January 25, 2016