If you can wait and not be tired by waiting,
  Or being lied about, don't deal in lies,

Or being hated, don't give way to hating,
 And yet don't look too good, nor talk too wise ...


- Rudyard Kipling, "If"

Precedence and associativity

Precedence

What happens when an expression like
2 + 3 * 5
is evaluated? Do I get 17 or 25? Well, your math classes should've taught you that 17 is the answer, and indeed that's true in C++ as well.

When you have two different operators in an expression and prenthesization does not tell you which operation is performed first...

the relative precedence of operators is what determines which operation is performed first.

Since * has a higher precedence than +, the expression 2 + 3 * 5 is evaluated like 2 + (3 * 5).

Associativity

But what about when both operators are the same, or both have the same precedence? What happens with the following code?
a / b * c

When you have two identical operators in an expression (or two different operators with the same precedence) and prenthesization does not tell you which operation is performed first...

the associativity of the operator(s) is what determines which operation is performed first.

The associativity of * and / (which both have the same precedence) is left-to-right, so a / b * c is evaluated as (a / b) * c. This can matter in C++. For example, what does

3 / 4 * 1.5
evaluate to? If you're not sure, read about type type conversion covered in the previous lecture.

However ...

Programming Tip:

Always use parentheses rather than relying on subtle precedence and associativity rules!

This table lists the operators and their associativities. They are grouped together on lines with operators of the same precedence, and the lines go from highest precedence at the top, to lowest at the bottom. You should know about precedence and associativity, and you should be able to use tables like this to fix precedence and associativity related bugs, but rely on parentheses when you're unsure.

Left-to-right associativity: cin and cout

Associativity matters. An expression like
cout << 3.0 << " percent"
relies on associativity of the << operator to make sense. Why? Of course, cin >> expressions work the same way.

Right-to-left associativity: assignment

Associativity also makes expressions like
a = b = 0
do what you want. Only in this case = is is right-to-left associative, so we get
a = (b = 0)
The key here is that an assignment expression evaluates to the value of the left-hand-side object after the assignment. Therefore:
  1. b gets assigned value zero, the expression has value zero;
  2. then that's what is assigned to a.

Decisions

The ability to make decisions and react to different input is a key part the power of computers. For example, we would certainly expect it to be possible to write a program that works as follows:

A program that reads in a number and

cin >> k;
if (k is even)
{
  cout << "even" << endl;
}
else
{
  cout << "odd" << endl;
}

In C++ (as in English!) "if" is the key to expressing this. The program would be written like the code snippet on the right. Of course we've got to figure out some C++ that will do the "k is even" for us.


int k;
cin >> k;
if ((k % 2) == 0)
{
  cout << "even" << endl;
}
else
{
  cout << "odd" << endl;
}

Scope

We might consider solving the above problem in a slightly different way: We'll assign a variable of type string the value "even" if k is even and "odd" otherwise. Then, after the if-statment, we'll do the printing. We might implement it like this:

if ((k % 2) == 0)
{
  string s;
  s = "even";
}
else
{
  string s;
  s = "odd";
}
cout << s << endl;

However, when we try to compile this the compiler complains as follows:

s is an undeclared identifier.
This is exactly what the compiler would say if we'd never defined s at all. Well, as far as the compiler is concerned when it processes the "cout << s << endl;" statement, we haven't defined s. The problem is caused by the scope of variables in C++.

In C++, a variable only exists from the point at which it is declared to the } that closes off the innermost block in which it was declared.

So the s that we define inside the else-block is invisible, is unknown, does not exist outside of that else-block. In particular, this is true for our cout-statement. The scope of a variable is the portion of the code that "sees" the variable, i.e. that knows it exists. The scope of a variable ends with the innermost block in which it was defined.

To fix up this version of our even/odd programm, we simply need to move the declaration of s outside of the if/else-blocks so that its scope extends to cout statement. This'll work:


string s; // Now, the scope of s is global
if ((k % 2) == 0)
{
  s = "even";
}
else
{
  s = "odd";
}
cout << s << endl;

Relational Operators

The "==" is an example of a relational operator. Relational operators make comparisons of their left and right-hand arguments, and return bool values accordingly. They are:
== (equal)
!= (not equal)
<  (less than)
>  (greater than)
<= (less than or equal to)
>= (greater than or equal to)
As you can see from the operator precedence table, they have lower precedence than the arithetic operators, so things like
2*k > k + 5
do what we'd like them to do - they evaluate the arithmetic expressions on the left and right, then they apply the ">" operator to compare the two values. This means, for example, that instead of writing ((k % 2) == 0) we could write (k % 2 == 0) and get the same result.

So what happens when we compare objects of different type? For example, what happens with k == x, where k is an int, and x is a double? The answer is that the same automatic type conversions are applied as in arithmetic expressions. So, k is implicitly converted to a double, and this double value is compared to x. Thus, 5 == 5.2 is false, since 5 is converted to 5.0 prior to the comparison, and we end up actually doing 5.0 == 5.2.

Blocks

Code in between {}'s forms a block. So the if is followed by a block (the then block) and the else is followed by a block (the else block). You've already seen one example of a block: The block following main(). Anything you can write inside of the main block, you can write inside of any block. This means that our if blocks and else blocks can declare variables, read input, make assignments to variables ... anything.

int main()
{
  // Read type of object: circle or triangle
  string s;
  cout << "Do you have a circle or a triangle? ";
  cin >> s;

  if (s == "circle")
  {
    // Compute area of circle
  }
  else
  {
    // Compute area of triangle
  }

  return 0;
}

So, suppose I wanted to write a program that works as follows:

Compute areas of circles and triangles for the user, first asking the user which kind of object he was interested in.

My program might look something the code on the right, where

Code for circle areaCode for triangle area

double r,Pi;
Pi = 3.14159265358979324;
cout << "Enter the radius of your circle: ";
cin >> r;
cout << "Area equals " << Pi*r*r << endl;

double b,h;
cout << "Enter base length: ";
cin >> b;
cout << "Enter height: ";
cin >> h;
cout << "Area equals " << 1/2.0*b*h << endl;

Each of these "miniprograms" can be placed in its appropriate block, and we get the whole program.

Dropping the Else


int k;
cin >> k;

if (k < 0)
{
  k = -1*k;
}
else
{
  // nothing to do!
}

cout << k << endl;
Sometimes you have a condition which, if it's true, should cause you to do some extra work, but which, if it's false, should have no effect on the program.

For example:

Suppose we read an int from the user and we want to change it to its absolute values and print it out.

We'd probably write something like the code snippet on the right. When there's nothing to do in the else-block, we can simply drop it, as shown below.

Sometimes your if-statement is written so that the then-block is empty and the else-block isn't. You can't simply drop the then-block, so first rewrite your if-statement with a new condition so that the then-block contains the work.


int k;
cin >> k;

if (k < 0)
{
  k = -1*k;
}

cout << k << endl;

Problems

  1. Remainders - this is a toy problem that forces you to face a fundamental problem in computer science: swapping values. This is important to know and understand!
  2. Converting 12-hour clock time to 24-hour clock time. Here is a first try at solving this problem. Can you see what's wrong? (Try input 10:02AM.) Here are two different solutions to this problem: Version 1 and Version 2.
  3. Determining the state of H20 - this gives you a good look at "nested" if statements.
  4. Real & Complex Roots of a Quadratic Polynomial

Do you know ...