Review of last time

Let’s look at the BMI program we covered last time and take it apart, to review.

/*
   bmi.cpp
   Computes the user's BMI.

   Andy Clifton
   Feb 1 2017
*/
#include <iostream>
using namespace std;

int main() {
    // The formula for BMI is 703 * weight / (height * height) 
    // where weight is in pounds and height is in inches

    int weight;
    cout << "What is your weight, in pounds? ";
    cin >> weight;

    int height;
    cout << "What is your height, in inches? ";
    cin >> height;

    int bmi = 703 * weight / (height * height);
    cout << "Your bmi is " << bmi << endl;
}

What are the comments? What are the directives? What are the declarations? What are the definitions? Which parts are expressions and which parts are statements? (There are a lot of expressions.) What does each line do?

Variables

“Variable” is the technical name for a named box that holds a value. The box lives somewhere in the computer’s memory. Remember the three parts of a variable declaration:

type name = expression;
type name;

More on Expressions and Variables

Expression evaluation: by value (to emphasize)

If we do

int a = 5;
int b = 12 * a;
cout << b;
a = 10;
cout << b;

Will something different be printed the second time? No, because we compute the value of b using a’s current value, and then it is the value that is stored in b. We don’t store the computation 12 * a into b, so when we print b the second time it has no knowledge of how it was initialized from a.

We can change what’s stored in a box after it has been created through several means. We already saw one, reading it in from the user:

int x = 1;
cin >> x; // User's input replaces 1

We can also use assignment to set the value in the box to whatever we want:

int x = 1;
x = 2;

An assignment statement has the form

name = expression;

Note that although assignment looks a lot like an equality from algebra, it’s very different. To emphasize this:

int x = 1;
cout << x << endl;
x = x + 1;
cout << x << endl;

What will this print?

1
2

In algebra, \(x = x + 1\) is nonsense, it reduces to \(0 = 1\). In C++, it means something completely different: the x on the right is the “old” x, while the x on the left is the “new” x. You have to work out the value of the right completely, and only after you’re done, do you change the value in the box.

Expressions

Every expression has a value (that it “evaluates to”), but every expression also has a type. An interesting thing to note is that while we might not know the value of an expression (if, for example, it depends on variables that are read in from the user), but we can always know its type. E.g. in the BMI program, we don’t know the value of bmi until the program runs, but we know its type: int.

Today we’re going to see a couple of types other than int.

Expressions: Operator Terminology

Expressions are built out of literals (1,2), variables (x, bmi) and operators, things like +, -, etc. C++ has the common arithmetic operators, and then some (many!) of its own.

An operator like + in a + b is called a binary operator, because it takes two things (a and b). An operator like -a is called unary, (uno = one) because it takes one. In generally, how many “things” an operator operates on is called its arity.

The “things” that the operator operates on are called its operands: in a + b, a and b are the operands. Sometimes, for binary operates, I’ll refer to them as the “left-hand side” and “right-hand side”.

A unary operator that goes in front of the operand (as in -a) is called a prefix operator; a unary operator that goes after the operand is called postfix.

Operators are what help us build expressions: given two expressions, we may be able to “stick” them together by putting a binary operator in the middle. Similarly, we can attach a unary operator to an expression to make a larger expression.

Operator precedence

Precedence just means, what things get evaluated first if you mix different operators (and if you don’t write any parentheses). You probably know the precedence for arithmetic operators, and C++ uses them: mult/div before add/sub, but negation happens first of all, and note that there’s a (useless) unary + operator

- + (unary)
* /
+ -

But C++ has lots of other operators, so we need more than just simple arithmetic precedence. For example, if I write

1 + 2 < 3 * 4

does this mean (1 + 2) < (3 * 4) or 1 + (2 < 3) * 4, or something else?

In fact, all the comparison operators have lower precedence than the arithmetic operators. Thus, our precedence table is actually

- (unary)
+ (unary)
* /
+ -
< <= > >=
==
!= 

We’ll add more operators to this as we examine them. But note that, as I said before, you can always use parentheses to “break” the precedence rules and force things to be done in a certain order. Sometimes it’s good to use parentheses even when you don’t need to, just to make it absolutely clear what order things are going to happen in. So I might write

(a + b) < (a * b)

even though that’s not strictly necessary.

Operator associativity

Operator associativity refers to whether we evaluate

a + b + c

as (a + b) + c or as a + (b + c). Associativity can be either left or right. Left-associative means we start by putting the parens on the left (or we evaluate from left to right), while right means we start from the right. Fortunately, there’s a simple rule for operator associativity:

Table of operators

As we go along, we’ll be building a table of all the operators. Operators are listed in order of descending precedence

Operator(s) Name Arity Pre/Post Assoc.
- + Unary minus/plus Unary Pre. N/A
++ -- Pre/post inc/dec Unary Both N/A
! Logical NOT Unary Pre. N/A
* / % Times, div, mod Bin. Left
+ - Plus, minus Bin. Left
<< >> Ins/Ext, Shift Bin. Left
< > <= >= Comparison Bin. Left
!= == (In)equality Bin. Left
&& Logical AND Bin. Left
|| Logical OR Bin. Left
= Assignment Bin. Right
+= *= etc Op.-and-assign Bin. Right
?: Conditional 3 N/A

Insertion and Extraction operators

We’ve only used << and >> for printing and reading input, but they were originally arithmetic operators. << has the effect of multiplying its left-hand-side by a power of 2. That is,

$$a\; \mathtt{<<}\; b = a 2^b$$

Similarly,

$$a\; \mathtt{>>}\; b = \frac{a}{2^b}$$

These work by bit-shifting. Every value is represented as a pattern of bits, which are on/off. We’ll talk about binary later.

The modulo operator

There’s an additional arithmetic operator which, unlike the others, only operates on integer-like types (int). The modulo operator is represented by a percent sign %; a % b gives the remainder of a divide by b. For example:

9 % 3 // evaluates to 0, 3 divides 9 evenly
8 % 3 // evaluates to 2, the remainder of 8/3
7 % 3 // evaluates to 1
0 % n // is always 0, anything goes into 0, evenly
n % 0 // error, division by 0
n % 1 // evaluates to 0, always; 1 goes into anything
1 % n // evaluates to 1, always

There’s a relationship between integer division and modulo. If we have two ints, a and b then if we define

int q = a / b;
int r = a % b;

Then it must be the case that

a == q * b + r

We can use the modulo operator to find out whether or not a number is even:

n % 2 == 0 // divides evenly by 2, is even
n % 2 == 1 // does not divide evenly, must be odd

Bools and comparisons

Bool

C++ has some operators that do comparisons: tell us whether something is (for example) less than something else. But before we can talk about these, we need to talk about bool.

C++ has another built-in type for yes/no, true/false values, and it’s called bool (short for Boolean, named for George Boole, mathematician who first investigated the “algebra” of working with true/false as values). You can use it like this:

bool a = true; 
bool b = false; // The only values for bools are true and false

That would be pretty useless, but fortunately, C++ has a bunch of expressions that give back bools. For example, the less-than operator for numbers outputs a bool:

1 < 2 // is true
5 < 3 // is false

(Other comparisons: >, <=, >=)

We can compare things to see if they are equal with ==:

1 == 1 // is true
1 == 5 // is false

Don’t confuse == with =. The first compares two things, and tells you whether they are equal, as a true/false value. The second stores it’s right-hand side into the box named by its left. If you want to, you can think of the = as making the two sides true. That is, if we do:

a = b;  // for any a,b
a == b; // will always be true, immediately after a = b

or if they are not equal with != (the exclamation point is read as “not” in C++):

1 != 1 // is false
1 != 5 // is true

Finally, any int can be converted to a bool: 0 converts to false, and everything else to true. This gives an easy way to check whether an int is 0:

int i = ...;
bool a = (i != 0); // true if i is not 0
bool a = i;        // does the same thing

Comparison operators

While the comparison operators <, etc. for the most part act like you think, there is one thing you can’t do with them (borrowed from math). In math you can write

$$a < b < c$$

to mean, is a less than b, and b less than c? In C++ this means something very different. For example, this

2 < 3 < 2

evaluates to true!

What is happening is that, not only can you convert ints to bools, you can convert bools to ints. false converts to 0, while true converts to 1, and comparisons are evaluated from left to right (left associative). So the above really means

(2 < 3) < 2 
1 < 2
true

This is also why you can do some weird things with bools: true * false, true / true, and even true + 5 are all valid. That doesn’t mean you should use them, because they are confusing. (If you want to be confusing, try sqrt(true).)

Things that can be compared

What kinds of things can we compare? Most everything that you would expect. We can compare ints, and the types that we will look at next time (float and string). Anything that you can think of as being “put in order” probably can be compared using the comparison operators.

Boolean operators

There are some operators which operate purely on bool (although, as mentioned, you can implicitly convert ints to bools, so you can use these to operate on ints; they will always output proper bools, however).

The prefix ! operator negates a boolean value, turning false to true and true to false. It’s the C++ equivalent to the \(\neg\) operator from logic. For example

!(a < b)   // Is it not the case that a < b
!(a == b)  // Equivalent to a != b
!!a        // Equivalent to just a, except that the result will be true/false.

You can read ! as “not”, but sometimes it’s clearer to read it as “whatever is false”. E.g., read !a as “a is false”.

Note that we have the following equivalencies:

Negation Equivalent
!(a == b) a != b
!(a != b) a == b

You should use the simpler versions on the right.

What are the following equivalent to?

Expression Equivalent
!(a < b) a >= b
!(a > b) a <= b
!(a <= b) a > b
!(a >= b) a < b

(You can prove these properly using DeMorgan’s laws, below.)

Some examples:

twins = age1 == age2; // Are two people twins?
reject_login = entered_password != stored_password;

Sometimes I’ll see people writing things like this:

a == true

but this is totally unnecessary. You could just as well write

a

because if a is true then a == true will be true also.

Likewise, instead of writing

a != true // or
a == false

just write

!a

Boolean use of >>

When used for extraction, the >> operator kinda-sorta evaluates to a bool telling you whether or not the extraction was successful. For example, suppose you do:

int x;
bool b = (cin >> x);

If b == true then we successfully read in an int and stored it into x. If b is false, then something went wrong (most likely the user didn’t type anything that could be recognized as an int).

Logical AND and OR

There are also C++ equivalents to the logical \(\land\) and \(\lor\) (AND and OR operators):

You should read && as “and” and || as “or”. Note that the precedence of && is higher than that of ||, so

a || b && c

will be evaluated as

a || (b && c)

Now that we have these, we can translate \(a \lt b \lt c\) into proper C++. Since this means “a is less than b and b is less than c”, we can write it as

a < b && b < c

Note that you can write

a && b && c

and that will work just fine (left-assoc). (Demonstrate this) This means “all of a, b, and c must be true”. While you can mix && and ||, it gets confusing:

a && b || c || d && e && f && g

What?

We can also expand out the or-equals versions of the comparison operators, if we wanted to:

Operator Equivalent
a <= b a < b||a == b
a >= b a > b||a == b

A few other identies include:

Expression Equivalent
a <= b && a != b a < b
a >= b && a != b a > b
a <= b && a >= b a == b
a < b||a > b a != b

You should be on the lookout for redundancies like this, and replace them with their simpler equivalents.

Quiz:

Suppose we are given two numeric intervals, [a1,a2] and [b1,b2] and we want to know whether they overlap at all. E.g., [1,10] overlaps with [5,15], and likewise [1,10] overlaps with [4,6], but [1,10] does not overlap with [11,20]. (Endpoints are included, so [1,10] overlaps [10,15]). Write a boolean expression, using comparisons, && and || which returns true if they overlap, and false if they do not. You can assume that a1 <= a2 and b1 <= b2 (the endpoints are in order).

One way to approach this is to list out all the ways that the two intervals can be arranged:

a1 a2 b1 b2 // No overlap
a1 b1 a2 b2 // Overlap
a1 b1 b2 a2 // Overlap/containment
b1 a1 a2 b2 // Overlap/containment
b1 a1 b2 a2 // Overlap
b1 b2 a1 a2 // No overlap

and then we can just detect non-overlap (disjointedness) and negate:

!(a2 < b1 || b2 < a1)

DeMorgan’s Laws

When we have an expression such as !(a && b) or a && (b || c) we can transform, and sometimes simplify the expression by employing DeMorgan’s Laws. For example, think about what !(a && b) is saying:

It is not the case that both a and b are true.

If not both a and b are true, then one of them must be false. Saying that something is false translates into !. So we have the following transformation:

!(a && b) is equivalent to !a || !b

In otherword, if not both a and b are true, then at least one of a or b must be false.

Similarly, we can transform !(a || b). In English, this means

It is not the case that at least one of a or b is true.

If we don’t allow at least one (and possibly both) to be true, then the only possibility left is that both are false. So we have

!(a || b) is equivalent to !a && !b

An easy way to remember this is that you can move the ! inside, if you exchange && and ||.

As an example, we can take the “overlaps” condition from above and move the NOT inward (whether this makes it “easier” to understand is debateable):

!(a2 < b1 || b2 < a1) 
!(a2 < b1) && !(b2 < a1)
a2 >= b1 && b2 >= a1

There are also DeMorgan’s laws for “distributing” && over || and vice versa. (but note that the pure logical interpretation is not necessarily correct, because of short-circuiting, which we’ll talk about next).

We have the following equivalencies (talk through these)

Expression Equivalent to
a && (b||c) (a && b)||(a && c)
a||(b && c) (a||b) && (a||c)

Note that these can be combined with the !-laws to move NOT inward and distribute at the same time.

Example: We can use DeMorgan’s Laws to transform negated < into <= and likewise for > and >=.

a < b    /* equiv to */  a <= b && a != b
!(a < b) /* equiv to */  !(a <= b) || !(a != b)
a >= b   /* equiv to */  a > b || a == b

Short-circuiting

The && and || operates do what is known as short-circuiting. This means they break the rule that all expressions are evaluated before a statement is executed.

Short-circuiting && gives us a way to write cin >> a >> b that won’t die horribly if the first read fails:

(cin >> a) && (cin >> b);

(The parentheses are required, because >> has higher precedence than &&.)

Trivia question: does the DeMorgan-distributed version of a boolean expression have the same effect, in terms of short-circuiting, as the original?

Operators that act like statements

In the next section, we’re going to look at operators that blur the line between “statement” and “expression”. On the one hand, like statements, these operators will “do something”; you can write them with a semicolon at the end like a statement, and something will happen. But on the other hand, they also evaluate to something, like an expression; you can put them inside a larger expression, unlike a normal statement. Even more interesting, when placed into a larger expression, the “do something” part still happens. So now we can build large expressions that give back values and perform actions.

The Assignment Operator

The first operator that does double-duty is assignment. You can write

a = 5;

as a statement, but you can also write

6 + (a = 4) < 12

and this will evaluate to true, while still updating the value of a! (a = 4 will evaluate to 4, while changing the value of a.)

All assignment operators have the lowest precedence (for now), lower than comparison, lower even than conditional. This is what you expect, because it means that you can write

a = b + 2;

and have it do what you expect, instead of (a = b) + 2 which would be surprising.

All assignment operators are right-associative, in contrast to all the other binary operators which are left associative. This means that in a “chained” assignment like this:

a = b = c = d = 1;

the compiler will evaluate it in the order

a = (b = (c = (d = 1)));

So if we can write

a = (b = 1);

obviously, b = 1 must evaluate to something. It evaluates to the box (variable) named by b – i.e., the left-hand side of the = – (which now contains 1), so 1 will also be stored in a. You might think, what’s the difference between a = 1 evaluating to a, which contains 1, and it evaluating to 1 directly. The answer is, if it evaluates to the box itself, we can do things like this:

(a = 1) = 2;

This is pretty useless (set a to 1, only to immediately set it to 2). Later on we’ll see some more interesting uses.

Assignment shortcuts

A lot of times we want to assign a value into a variable that is a modification of the variable’s current value. E.g., we might write

a = a + 5;

This kind of thing is so common that C++ has several shortcuts defined. In particular, the above can be shorted to

a += 5;

There are OP= variants for most operators: +, -, *, /, etc. All of the bitwise and boolean operators that we’ll discuss in a bit have versions too. The comparison operators don’t, because

a = a < 5;

doesn’t really make sense (is a a bool or an int?)

The almost-complete list of operate-and-assign operators is

*= 
/= 
+= 
-= 
%=
<<=
>>=

The last two aren’t useful for input or output, only for shifting. E.g.,

a <<= 1; // same as a *= 2;

Increment/Decrement Operators

The operations

a = a + 1;

and

a = a - 1;

are so common that C++ provides an even shorter “shortcut” than writing a += 1 or a -= 1: the increment/decrement operators. These are unary operators available in both pre- and post-fix versions. The prefix versions are easiest to understand:

++a; 

is exactly equivalent to

a += 1;

It not only adds 1 to a, but it then evaluates to a‘s new value. Likewise, we have

--a; // equiv. to a -= 1

The postfix versions have the same effect on the variable, but differ in what they evaluate to. While ++a adds 1 to a and then returns a’s new value, a++ adds 1 to a and then returns a’s original value. That is, a++ is equivalent to:

int temp = a;
a += 1;
temp // evaluates to original value

The same for a--: it subtracts 1 from a, but then evaluates to the value of a before the subtraction occurred.

Note that this choice of before/after is only available with ++ and --; with, e.g., a += 5 you will always get the new value of a.