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. Doing this with what we know so far is ugly:
if (x <= 1.0)
{
  if (x >= -1.0)
  {
    cout << "arcsine of " << x << " = " << asin(x) << endl;
  }
  else
  {
    cout << "Error! Value outside of [-1,1]!" << endl;
  }
}
else
{
  cout << "Error! Value outside of [-1,1]!" << endl;
}
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;
}
... which is substantially simpler. More to the point, this does a much better job of reflecting what we're thinking.

&& as an opearator on objects of type bool

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:
ExpressionResult
false && falsefalse
false && truefalse
true && falsefalse
true && truetrue
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.

Boolean operators

The binary logical operators && and || follow short circuit evaluation rules. This means that the left-hand operand expression is evaluated first, and if the truth or falsity of the expression can be determined based only on the left-hand side, the right-hand operand expression is never even evaluated. So, for example, this code:
(x = y) || (x = 1)
is interesting. If y is non-zero, the left-hand operand is evaluated setting x to y's value. The expression when cast as a bool yields true. At this point, the || expression is necessarily true, so the right-hand side is never even evaluated, and the expression results in true. If y is zero, when the left-hand operand is evaluated it results in x being set to zero, and the expression when cast to a boolean yields false. This means the right-hand side of the || must be evaluated as well to determine the value of the || expression. This results in x being set to 1.

So ... right now this is more of a curiousity than anything. However, later in the semester, this behavior becomes very important.

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

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.

ExpressionResult
false || falsefalse
false || truetrue
true || falsetrue
true || truetrue

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.

ExpressionResult
!falsetrue
!truefalse

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). Put it all together and we get:
// Decide: Letter or not a letter?
if ((c >= 65 && c <= 90) || (c >= 97 && c <= 122))
{
	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:

// Decide: Letter or not a letter?
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
{
	cout << "Letter" << endl;
}
else
{
	cout << "Not a Letter" << endl;
}
Check out this complete solution.

Shortcut #1: Dropping {}'s from one-statement blocks

If your then-block (or else-block) consists of a single statement, you can drop the {}'s - but only if it consists of a single statement! This is just a shorthand to save you some keystrokes and a little bit of screen realestate. So, for example, in the previous code fragment we may remove the {}'s, since both blocks consist of a single statement.
  // Decide: Letter or not a letter?
  if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
    cout << "Letter" << endl;
  else
    cout << "Not a Letter" << endl;
Once again, this is just a shortcut to save keystroks and screen space, but you will see this shortcut often. If you try to drop the {}'s when you have more than one thing in the block, you'll run into trouble. In this case:
  // Decide: Letter or not a letter?
  if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
    cout << "Letter" << endl;
    cout << "Way to follow directions!" << endl;
  else
    cout << "Not a Letter" << endl;
your compiler will complain and give you a "parse error". As far as the compiler is concerned, that "else" doesn't belong to any "if"! It's not impressed with the fact that the cout << "Way to follow directions!" << endl; is indented as if it belonged to the else-block. It interprets the above as
  // Decide: Letter or not a letter?
  if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'))
  {
    cout << "Letter" << endl;
  }
  cout << "Way to follow directions!" << endl;
and thinks that we're dealing with an "if" from which the else-block has been dropped. Therefore, it is unhappy when it sees the "else" from out of nowhere!

Shortcut #2: 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:
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, 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:

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:

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, 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 this:

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.

The "if else" 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!

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 ...