Topics to Cover

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)

Relational operators has lower precedence than arithmetic operators.

As you can see from the operator precedence table, they have lower precedence than the arithmetic operators, so things like
2*k > k + 5
will evaluated as (2*k) > (k+5). As another example, instead of writing ((k % 2) == 0) we could write (k % 2 == 0) and get the same result.

String comparison

You can indeed compare two strings. A dictionary order (more correctly, according to the ASCII code) is used for comparing two strings. In particular,

string s = "hello";
string t = "banana";

if( s > t )
  cout << "bigger" << endl;
else
  cout << "smaller" << endl;
The above code will output "bigger", since "hello" will appear later than "banana" in the dictionary.

Comparing objects of different type?

So what happens when we compare objects of different type?

Logical operators

There are three boolean operators, && (and), the || operator (or), and the ! (not) operator.

And operator: &&

ExpressionResult
false && falsefalse
false && truefalse
true && falsefalse
true && truetrue
At first glance, && looks like something mysterious and new. It's not. It's an operator just like +, *, and >>, all of which we've used a lot already.

It takes two objects of type bool and evaluates to an object of type bool. We have an intuitive idea of what "and" means, and it coincides with C++'s technical, exact definition of what && means:

So, something like x <= 1.0 && x >= -1.0 makes sense because x <= 1.0 evaluates to a bool and x >= -1.0 evaluates to a bool, and then the && operates on the two bool values.

Or operator: ||

ExpressionResult
false || falsefalse
false || truetrue
true || falsetrue
true || truetrue
The || operator is much like &&, it takes two objects of type bool and evaluates to an object of type bool. The result is true if either or both are true, and false otherwise.

Not operator: !

ExpressionResult
!falsetrue
!truefalse
The ! operator is a unary operator. Instead of operating on left and right-had values, it operates on a single value - the value following it on the right. It evaluates to the not of the value following, so if the value following is true, the ! expression is false.

Short Circuit Evaluation

Suppose you wanted to write the following expression:

(x == 3) || (x > y) 
However, you wrote the code as follow by mistake:
(x = 3) || (x > y) 
Interestingly, the second expression always evaluates true! One single letter makes huge difference!

Explanation: short circuit evaluation

Let's see what's going on. The expression expr1 || expr2   follows short circuit evaluation rules, which means the following:

Now consider our example:

(x = 3) || (x > y) 

Using logical operators

Consider a code fragment that reads in a value from the user and returns the "arcsine" or inverse sine of the value. We'll use the asin function from the cmath library to compute our value. Now, the arcsine is only defined for values between negative 1 and 1, so if the user enters a value outside of this range we should print an error message.

We want to be able to say in our test condition that x must be less than or equal to 1 AND greater than or equal to -1. In C++ the operator && is "and". So our program fragment becomes:

if (x <= 1.0 && x >= -1.0)
{
  cout << "arcsine of " << x << " = " << asin(x) << endl;
}
else
{
  cout << "Error! Value outside of [-1,1]!" << endl;
}

Common pitfall

It's wrong to write -1.0 <= x <= 1.0 for testing whether x is between -1.0 and 1.0.

Explanation: Since the two operators are the same, applying the left-to-right associativity for the operator <=, the above expression is evaluated as (-1.0 <= x) <= 1.0

In summary, -1.0 <= x <= 1.0 is always true!

Combining boolean operations

Things get especially interesting when you combine boolean operators. For example: Suppose you want to write a program that reads a character from the user and prints "Letter" or "Not a Letter" depending on whether or not the user entered a character that's a letter. Your program will, more or less, look like this:
// Read char
char c;
cout << "Enter a letter: ";
cin >> c;

// Decide: Letter or not a letter?
if ( c is a letter )
{
	cout << "Letter" << endl;
}
else
{
	cout << "Not a Letter" << endl;
}
But, of course, the problem is the test condition c is a letter. Letters come in two flavors, uppercase and lowercase. So a refinement of our program would be:
// Read char
char c;
cout << "Enter a letter: ";
cin >> c;

// Decide: Letter or not a letter?
if ( c is an uppercase letter  ||  c is a lowercase letter )
{
	cout << "Letter" << endl;
}
else
{
	cout << "Not a Letter" << endl;
}
From the ASCII table we would see that uppercase letters range from 'A' (65) to 'Z' (90). So c is an uppercase letter boils down to (c >= 65 && c <= 90). Similarly, because lowercase letters range from 'a' (97) up to 'z' (122), we know that c is a lowercase letter boils down to (c >= 97 && c <= 122).
Not recommendedRecommended -- better readability
// Decide: Letter or not a letter?
if ((c >= 65 && c <= 90) || (c >= 97 && c <= 122))
{
	cout << "Letter" << endl;
}
else
{
	cout << "Not a Letter" << endl;
}
// Decide: Letter or not a letter?
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
{
	cout << "Letter" << endl;
}
else
{
	cout << "Not a Letter" << endl;
}
Note that I wrapped my uppercase and lowercase tests in parentheses, because I wanted to be sure that the &&'s were evaluated before the ||. Is that necessary? (Think about how you would decide this!) Even if it isn't, adding the ()'s makes the meaning clear to everyone.

In fact, we can avoid having to even look at an ASCII table by replacing the actual ASCII constants 65, 90, 97, and 122 with the corresponding char constants for the letters 'A', 'Z', 'a', and 'z'. This also makes it much more clear how the code works, and we recommend you to write your code this way.

Check out this complete solution.

Shortcut: The else if

An example will probably make this clearer than any abstract discussion. Suppose I have a variable x of type double that I know is between 0 and 1, and suppose I want to print out for the user depending on the value of x. Our first shot would probably be the code on the left below. However, our "cout" statements are if-blocks and else-blocks consisting of a single statement, so Shortcut #1 tells us we can drop the {}'s surrounding those statments, which would give us the code on the right below:
First shotDropping {}
if (x <= 1.0/3.0)
{
  cout << x << " is in [0,1/3]" << endl;
}
else
{
  if (x <= 2.0/3.0)
  {
    cout << x << " is in [1/3,2/3]" << endl;
  }
  else
  {
    cout << x << " is in [2/3,1]" << endl;
  }
}

if (x <= 1.0/3.0) 
  cout << x << " is in [0,1/3]" << endl; 
else 
{
  if (x <= 2.0/3.0) 
    cout << x << " is in [1/3,2/3]" << endl; 
  else 
    cout << x << " is in [2/3,1]" << endl;
}
However, an "if" statement is a single statement, so Shortcut #1 tells us that we can drop the {}'s surrounding the scond "if" statement, which would give us the code on the left below. However, the C++ compiler is not sensitive to to formatting, so it doesn't distinguish between "if-newline-else" and "if-space-else", so we could rewrite things to look like the code on the right below.
Dropping {} further Reformatting the code

if (x <= 1.0/3.0)
  cout << x << " is in [0,1/3]" << endl;
else
  if (x <= 2.0/3.0)
    cout << x << " is in [1/3,2/3]" << endl;
  else
    cout << x << " is in [2/3,1]" << endl;

if (x <= 1.0/3.0)
  cout << x << " is in [0,1/3]" << endl;
else if (x <= 2.0/3.0)
  cout << x << " is in [1/3,2/3]" << endl;
else
  cout << x << " is in [2/3,1]" << endl;
People refer to the "else if" as if it were a new programming construct (which in some languages, it is), but you see it is just the same code with the same meaning as our original ... it just looks a little prettier, saves keystrokes, and takes up a little less screen real estate. There's nothing new here! All we did was to take advantage of Shortcut #1 and to use some nice formatting. It sure looks prettier though. Once again, this is just a shortcut to save keystrokes and screen space, and to make your code easier to read - but you will see this shortcut often.

Example

The "else if" formatting is particularly nice when you have disjoint cases to distinguish. For example:
Look at the difference between the code with no shortcuts and the code as an "else if". It makes a huge difference in terms of how easy the code is to understand!

Mandatory Practice Problems

  1. Write a program that checks if a given input is a letter or not.
    $ ./a.out
    Enter a letter: k
    Letter 
    
    $ ./a.out
    Enter a letter: !
    Not a letter 
    
    Check out the solution.

Other Practice Problems

  1. Solving for the unknown quantity in F = m a. For this problem, Solution 1 is easier to come up with but more complicated to implement. Solution 2 is more subtle, but cleaner and less complicated to program.
  2. Date conversion program
  3. Roots of a quadratic polynomial done right! In this version, we distinguish between two real roots, one real root, and two complex roots.

Do you know ...