Selection

Introduction

This lab will examine relational expressions and the use of the if control structure in making decisions.

Topics Covered in this Lab:
• relational expressions
• equality operator vs. assignment operator
• if control structure structure
• embedded if control structures
• compound statements
• dangling-else problem
• What is a relational expression?
• What happens if the assignment operator is used instead of that for equality?
• What is a compound statement?
• What is the dangling-else problem?
Demonstrable Skills Acquired in this Lab:
• use of relational operators
• construction of if statements

Operators in C++ Begin by creating a new directory sp04, changing into it, and creating a new file sp04t.cpp there; in this file, create the following function, which uses the addition and assignment operators of the previous lab. Code Illustration
int main ()
{
int a;  // reserve a spot labeled "a" in memory for a whole number
int b;  // reserve a spot labeled "b" in memory for a whole number

cout << "Enter two numbers: ";
cin >> a >> b;  // get next two numbers (whitespace between) from keyboard

int sum = a + b;  // add & place result in a third spot in memory "sum"
cout << "The sum of the numbers is " << sum << ".\n";

return 0;
}  // end function main

This lab will, among other things, introduce two new families of operators: relational and logical.

Relational Operators

The family of relational operators allow two values to be compared. There are six relational operators in C++: <, >, <=, >=, ==, and !=; the usage for each follows.

 a < b a is less than b a > b a is greater than b a <= b a is less than or equal to b a >= b a is greater than or equal to b a == b a is equal to b a != b a is not equal to b

Where the arithmetic operators return arithmetic results, the relational operators return logical results, those being either true or false. (Logical values are also referred to as boolean values.) To illustrate this, extend function main with the following. Code Illustration
cout << "The value returned by the expression 3 < 5 is " << (3 < 5) << endl;
cout << "The value returned by the expression 5 < 3 is " << (5 < 3) << endl;

Execute the program and note that the value returned by the first (true) expression is 1, while the value returned by the second (false) expression is 0. By themselves, the relational operators would be of limited use; consequently, they are typically used within other constructs, such as C++'s if control structure.

Selection with the if Control Structure

Every programming language provides means for a machine to make decisions by selecting from alternative courses of action. In C++, the simplest of these means is the if control structure; its format follows.

 if (EXPRESSION) STATEMENT;

The EXPRESSION above is evaluated as either true or false; when found to be true, the statement is executed. To illustrate this, extend function main with the following to use relational operators in expressions. Code Illustration
if (a > b)
cout << a << " is greater than " << b << endl;
if (a < b)
cout << a << " is less than " << b << endl;
if (a == b)
cout << a << " is equal to " << b << endl;

Execute the program several times with different data and observe that only one of the if control structures is executed; this makes sense, since only one of the conditions can be true for any two specific numbers.

Assignment and Equality

A very common mistake is to accidentally use the assignment operator = in place of the relational operator ==. Unfortunately, the compiler does not recognize this as an error since C++ expressions may involve any operator. (In some languages, assignment is implemented as a statement rather than as an expression; this makes it possible for the compiler to catch the same type of error in those languages.) Extend function main to see what problems arise from this particular mistake. Code Illustration
if (a = b)
cout << "The value of the expression (a = b) is true.\n";
cout << "The numbers are now " << a << " and " << b << endl;

Several things must be explained in order to understand what has happened. First, recall that it was demonstrated earlier that C++ associates the value 1 with true and the value 0 with false; in fact, any value other than 0 is associated with true. Second, recall that assignment has been referred to as an operator, like the arithmetic operator + or the relational operator <. Every operator in C++ returns a value. Arithmetic operators return the obvious sum, difference, product, quotient, or remainder as their value. Relational operators have been shown to return either true or false. Assignment operators return the value which was assigned; from an operator standpoint, the fact that a value was moved in memory is even considered a side effect. Consequently, when the program is run with a non-zero value for b, the expression above evaluates to true; the lone value of zero for b causes an evaluation of false. Verify that this is the case by executing the program again with zero as well as non-zero values for b. Finally, add the following and execute the program yet again to observe how truth is associated with numeric values. Code Illustration
if (3)
cout << "3 is true!\n";
if (-3)
cout << "-3 is true!\n";
if (0)
cout << "0 is true!!\n";

The if-else Control Structure

When the program is ran as it stands now, nothing is printed by if control structures whose expressions have a value of zero. The computer can be directed to execute an alternative statement when an expression is false by using else. The if-else statement has the following format.

 if (EXPRESSION) STATEMENT1; else // EXPRESSION is not true STATEMENT2;

Modify the if control structure for (a = b) in function main to incorporate an else as follows. Code Illustration
if (a = b)
cout << "The value of the expression (a = b) is true.\n";
else
cout << "The value of the expression (a = b) is false.\n";

cout << "The numbers are now " << a << " and " << b << endl;

Execute the program several times with different values for a and b. Be sure to enter zero for b at least once so that the else statement is executed.

Logical Operators

Relational operators are very useful in the expressions of if control structures, but they have their limits. How would one express the notion of between? In algebra, the fact that b is between a and c might be expressed as a < b < c. Unfortunately, this notation will not work as desired in C++. To see why not, extend function main with the following. Code Illustration
int c;
cout << "Enter three numbers: ";
cin >> a >> b >> c;
if (a < b < c) // incorrect test for b on interval [a, c]
cout << "The value of expression (a < b < c) is true.\n";
cout << "The value of expression (a < b) is " << (a < b) << endl;
cout << "The value of expression (b < a) is " << (b < a) << endl;

Execute the program with the values 3, 4, and 5; the result is true, as desired. Execute the program again with the values 5, 4, and 6; the result is also true, which obviously is not desired to be the case. The associativity of operators explains what has happened. When an expression involves two or more operators of the same precedence, they are evaluated one at a time according to their associativity, left-to-right or right-to-left. Arithmetic, relational, and logical operators have left-to-right associativity; assignment operators have right-to-left. When the expression a < b < c is evaluated, the first < compares a and b. If a is less than b, this sub-expression evaluates to true, or 1; if a is not less than b, the sub-expression evaluates to false, or 0. It is this 1 or 0 which is then compared to c; as long as c is greater than one, the entire expression will be true for any values of a and b. Test this.

Logical operators are useful in building complex expressions needed for notions like between. There are three logical operators in C++, && (and), || (or), and ! (not); the usage for each follows.

 expression1 && expression2 true only if both of the expressions are true expression1 || expression2 true if either of the expressions is true ! expression true if the expression is not true

Truth Tables for Logical Operators

Note: p and q are typically used to represent expressions that result in true or false.

 Logical And Logical Or Logical Not p q p && q p q p || q p !p true true true true true true true false true false false true false true false true false true false false true true false false false false false false

Like the relational operators, the logical operators return the logical results true or false. To illustrate this, extend function main with the following. Code Illustration
if (a < b  &&  b < c)
cout << b << " is the middle value.\n";

Execute the program with the values 3, 4, and 5; the computer displays the message recognizing 4 as the middle value. Execute the program again with the values 5, 4, and 6; this time, no message is displayed. Since 5 is not less than 4, the entire expression is false. Add another if control structure to handle this situation. Assuming three unique values, how many more if control structures are necessary to correctly identify any of a, b and c as the middle? Extend function main to include them.

Nested if-else Control Structures

As soon as one of a set of mutually exclusive if control structure conditions is determined to be true, there is no point in executing the remaining if control structure conditions (as they must all be false). In this situation, it is more efficient to use a nested if-else structure in which successive ifs are the bodies of successive elses. The middle problem is one such situation. Nested if-else control structures of this type have the following format.

 if (EXPRESSION_1) STATEMENT_1; else if (EXPRESSION_2) STATEMENT_2; else if (EXPRESSION_3) STATEMENT_3; else // no expression was true STATEMENT_4;

Copy the existing if control structures for determining the middle of three values and rewrite them as a nested if-else structure.

Nesting Control Structures

The nesting of if control structures can be used to make decisions based on previous ones. For example consider a doctor asking health-related questions. The doctor will ask subsequent questions based on responses to previous ones. This behavior reflects reasoning like this: Pseudocode
If question 1's answer is yes,
If question 2's answer is yes,
Otherwise, when question 2's answer is no,
Otherwise, when question 1's answer is no,

When this reasoning is translated into C++ syntax, the following arrangement of nested if control structure results.

 if (EXPRESSION_1) if (EXPRESSION_2) STATEMENT_1; else // EXPRESSION_2 is not true STATEMENT_2; else // EXPRESSION_1 is not true STATEMENT_3;

Consider the case of a doctor who is investigating a patient's risk for heart disease. He has asked for the patient's age already, and based upon the response he may or may not ask for the patient's cholesterol level. If the patient is 50 years old or older, he will ask; if the patient is younger than 50, he will not. If he asks about cholesterol level and the patient replies that it is over 200, the doctor will inform the patient that he is at risk for heart disease. If the patient replies that it is under 200, the doctor will tell the patient that he should stay active and eat right to prevent heart disease. If he did not ask about cholesterol level, he will tell the patient to take an aspirin and call him in the morning.

Comment out everything in function main save the return statement. Extend the program so that it will now perform the task outlined above as if it were the doctor. Pseudocode
// prompt for age
if // age is 50 or older
{
// prompt for cholesterol level
if // level is over 200
// print "You are at high risk for heart disease."
else // the level must be no more than 200
// print "Continue to eat right and stay healthy to prevent heart disease."
}  // end if age is over 50
else // the age must be no more than 50
// print "Take an aspirin and call me in the morning."

Note the use of braces to surround multiple actions to be taken when the age is 50 or older; without them, only the first statement (prompting for the cholesterol level) would be executed when the age is 50 or older. The braces and the enclosed statements are collectively referred to as a compound statement and may be used anywhere in place of a simple statement.

The Dangling-Else Problem

Finally, consider yet another nested if-else structure.

 if (EXPRESSION_1) if (EXPRESSION_2) STATEMENT_1; else // misleading else!! STATEMENT_2;

The indentation of this example suggests that STATEMENT_2 is to be taken if EXPRESSION_1 is false; however, this is not the case. The else in the preceding example is referred to as a dangling else; in situations like this, else clauses are always paired with the closest corresponding if control structure, regardless of indentation (which is ignored by the compiler). A compound statement can be used to achieve the desired behavior.

 if (EXPRESSION_1) { if (EXPRESSION_2) STATEMENT_1; } // end if EXPRESSION_1 else // EXPRESSION_1 is not true STATEMENT_2;

Experiment with various modifications to the following example to become familiar with the behavior of the dangling else. Create a new copy of the code with each modification (so you will have multiple copies with slight modifications to compare). Use the following code as a starting point. You should make changes to it, and see what effects those changes have, in order to learn how it works.
if (3 > 4)
if (4 > 5)
cout << "expressions 1 & 2 are both true.\n";
else // dangling else
cout << "only expression 1 is false?\n";