Reading

Sections 3.1-3.2 of Problem Solving With C++.

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.
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;
  }
Looking at our ASCII table, we see that uppercase letters have ASCII values from 65 to 90. So c is an uppercase letter boils down to (c >= 65 && c <= 90). Similarly, lowercase letters have ASCII values from 97 to 122. So 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. 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 >= 65 && c <= 90) || (c >= 97 && c <= 122))
    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 >= 65 && c <= 90) || (c >= 97 && c <= 122))
    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 >= 65 && c <= 90) || (c >= 97 && c <= 122))
  {
    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 "x is in [0,1/3]", "x is in [1/3,2/3]", or "x is in [2/3,1]" 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 deosn't distingquish 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 keystroks 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, suppose you had a variable d of type string that contained a day of the week name (e.g. Thursday), and you had an int k that you wanted to set to the number of the day d (e.g. 5 for Thursday). 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!

?: the "if expression" (my favorite!)

As a little aside, I want to mention my favorite C++ operator, :?. The ?: operator is a ternary operator, meaning it takes three arguments, and it allows us to write if-expressions, as opposed to if-statements. The syntax is
condition ? expr1 : expr2
... and it is evaluated as follows: the condition expression is evaluated first, if it is true, expr1 is evaluated and that provides the result of the ?: expression. Otherwise, expr2 is evaluated and that provides the result of the ?: expression. Here's an example of where you might want to use this:
cout << "Your change is " << n << (n == 1 ? "cent" : "cents") << endl;
While not terribly important in C++, I wanted to show you "if" as an expression, because it is a glimpse of a different programming paradigm — functional programming — in which everything is an expression, even loops and if's! BTW: the types of the two result expressions should be the same! (Hopefully you understand why.)

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.