Friday, July 11, 2008

5.3 Multiple Selection and Switch Statements

We have already seen (Lecture 1) how "if" statements can be strung together to form a "multiway branch". Here's a simplified version of the previous example: ...
...
if (total_test_score >=0 && total_test_score < 50)
cout << "You are a failure - you must study much harder.\n";
else if (total_test_score < 60)
cout << "You have just scraped through the test.\n";
else if (total_test_score < 80)
cout << "You have done quite well.\n";
else if (total_test_score <= 100)
cout << "Your score is excellent - well done.\n";
else
cout << "Incorrect score - must be between 0 and 100.\n";
...
...
Because multiple selection can sometimes be difficult to follow, C++ provides an alternative method of handling this concept, called the switch statement. "Switch" statements can be used when several options depend on the value of a single variable or expression. In the example above, the message printed depends on the value of "total_test_score". This can be any number between 0 and 100, but we can make things easier to handle by introducing an extra integer variable "score_out_of_ten", and adding the assignment: score_out_of_ten = total_test_score / 10;
The programming task is now as follows: (i) if "score_out_of_ten" has value 0, 1, 2, 3 or 4, print "You are a failure - you must study much harder", (ii) if "score_out_of_ten" has value 5, print "You have just scraped through the test", (iii) if "score_out_of_ten" has value 6 or 7, print "You have done quite well", and finally (iv) if "score_out_of_ten" has value 8, 9 or 10, print "Your score is excellent - well done". Here's how this is achieved with a "switch" statement: ...
...
score_out_of_ten = total_test_score / 10;

switch (score_out_of_ten)
{
case 0:
case 1:
case 2:
case 3:
case 4: cout << "You are a failure - you ";
cout << "must study much harder.\n";
break;

case 5: cout << "You have just scraped through the test.\n";
break;
case 6:
case 7: cout << "You have done quite well.\n";
break;

case 8:
case 9:
case 10: cout << "Your score is excellent - well done.\n";
break;
default: cout << "Incorrect score - must be between ";
cout << "0 and 100.\n";
}
...
...
Program 5.3.1
In general, the syntax of a "switch" statement is (approximately): switch (selector)
{
case label1:
break;
...
...
...

case labelN:
break;

default:
}
There are several things to note about such "switch" statements:
The statements which are executed are exactly those between the first label which matches the value of selector and the first "break" after this matching label.
The "break" statements are optional, but they help in program efficiency and clarity and should ideally always be used to end each case. When a "break" is encountered within a case's statement, control is transfered immediately to the first program statement following the entire "switch" statement. Otherwise, execution continues.
The selector can have a value of any ordinal type (e.g. "char" or "int" but not "float").
The "default" is optional, but is a good safety measure.

5.2 "For", "While" and "Do ... While" Loops

We have already been introduced to "for" loops in Lecture 2 and to "while" loops in Lecture 4. Notice that any "for" loop can be re-written as a "while" loop. For example, Program 2.2.2 from Lecture 2, which was: #include
using namespace std;
int main()
{
int number;
char character;

for (number = 32 ; number <= 126 ; number = number + 1) {

character = number;
cout << "The character '" << character;
cout << "' is represented as the number ";
cout << number << " in the computer.\n";
}
return 0;
}
Program 2.2.2
can be written equivalently as #include
using namespace std;

int main()
{
int number;
char character;

number = 32;
while (number <= 126)
{
character = number;
cout << "The character '" << character;
cout << "' is represented as the number ";
cout << number << " in the computer.\n";
number++;
}
return 0;
}
Program 5.2.1
Moreover, any "while" loop can be trivially re-written as a "for" loop - we could for example replace the line while (number <= 126)
with the line for ( ; number <= 126 ; )
in the program above.
There is a third kind of "loop" statement in C++ called a "do ... while" loop. This differs from "for" and "while" loops in that the statement(s) inside the {} braces are always executed once, before the repetition condition is even checked. "Do ... while" loops are useful, for example, to ensure that the program user's keyboard input is of the correct format: ...
...
do
{
cout << "Enter the candidate's score: ";
cin >> candidate_score;
if (candidate_score > 100 candidate_score < 0)
cout << "Score must be between 0 and 100.\n";
}
while (candidate_score > 100 candidate_score < 0);
...
...
Program Fragment 5.2.2a
This avoids the need to repeat the input prompt and statement, which would be necessary in the equivalent "while" loop: ...
...
cout << "Enter the candidate's score: ";
cin >> candidate_score;
while (candidate_score > 100 candidate_score < 0)
{
cout << "Score must be between 0 and 100.\n";
cout << "Enter the candidate's score: ";
cin >> candidate_score;
}
...
...
Program Fragment 5.2.2b

5. Branch and Loop Statements

5.1 Boolean Values, Expressions and Functions
In this lecture we will look more closely at branch and loop statements such as "for" and "while" loops and "if ... else" statements. All these constructs involve the evaluation of one or more logical (or "Boolean") expressions, and so we begin by looking at different ways to write such expressions.

As we have seen, in reality C++ represents "True" as the integer 1, and "False" as 0. However, expressions such as

condition1 == 1
or

condition2 == 0
aren't particularly clear - it would be better to be able to follow our intuition and write

condition1 == True
and

condition2 == False
Furthermore, it is desirable to have a separate type for variables such as "condition1", rather than having to declare them as of type "int". We can achieve all of this with a named enumeration:


enum Logical {False, True}

which is equivalent to


enum Logical {False = 0, True = 1}

This line acts a kind of type definition for a new data type "Logical", so that lower down the program we can add variable declarations such as:


Logical condition1, condition2;

Indeed, we can now use the identifier "Logical" in exactly the same way as we use the identifiers "int", "char", etc. In particular, we can write functions which return a value of type "Logical". The following example program takes a candidate's age and test score, and reports whether the candidate has passed the test. It uses the following criteria: candidates between 0 and 14 years old have a pass mark of 50%, 15 and 16 year olds have a pass mark of 55%, over 16's have a pass mark of 60%:


#include
using namespace std;

enum Logical {False, True};

Logical acceptable(int age, int score);

/* START OF MAIN PROGRAM */
int main()
{
int candidate_age, candidate_score;

cout << "Enter the candidate's age: "; cin >> candidate_age;
cout << "Enter the candidate's score: "; cin >> candidate_score;

if (acceptable(candidate_age, candidate_score))
cout << "This candidate passed the test.\n"; else cout << "This candidate failed the test.\n"; return 0; } /* END OF MAIN PROGRAM */ /* FUNCTION TO EVALUATE IF TEST SCORE IS ACCEPTABLE */ Logical acceptable(int age, int score) { if (age <= 14 && score >= 50)
return True;
else if (age <= 16 && score >= 55)
return True;
else if (score >= 60)
return True;
else
return False;
}
/*END OF FUNCTION */

Program 5.1.1
Note that since "True" and "False" are constants, it makes sense to declare them outside the scope of the main program, so that the type "Logical" can be used by every function in the file. An alternative way to write the above function "acceptable(...)" would be:


/* FUNCTION TO EVALUATE IF TEST SCORE IS ACCEPTABLE */
Logical acceptable(int age, int score)
{
Logical passed_test = False;

if (age <= 14 && score >= 50)
passed_test = True;
else if (age <= 16 && score >= 55)
passed_test = True;
else if (score >= 60)
passed_test = True;

return passed_test;
}
/*END OF FUNCTION */

Defining our own data types (even if for the moment they're just sub-types of "int") brings us another step closer to object-oriented programming, in which complex types of data structure (or classes of objects) can be defined, each with their associated libraries of operations.

Note: The Identifiers "true" and "false" in C++
Note that C++ implicitly includes the named enumeration

enum bool {false, true};

So you can't (re)define the all-lower-case constant identifiers "true" and "false" for yourself. In addition, you can use the type bool in the same way as we used Logical in our example.

2.5 Assignments and Expressions

Shorthand Arithmetic Assignment Statements

We have already seen how programs can include variable assignments such as number = number + 1;
Since it is often the case that variables are assigned a new value in function of their old value, C++ provides a shorthand notation. Any of the operators "+" (addition), "-" (subtraction), "*" (multiplication), "/" (division) and "%" (modulus) can be prefixed to the assignment operator (=), as in the following examples (mostly copied from
Savitch, page 74):
Example:
number += 1;
total -= discount;
bonus *= 2;
time /= rush_factor;
change %= 100;
amount *= count1 + count2;
Equivalent to:
number = number + 1;
total = total - discount;
bonus = bonus * 2;
time = time / rush_factor;
change = change % 100;
amount = amount * (count1 + count2);
The first of the above examples may written in even shorter form. Using the increment operator "++", we may simply write number++;
The operator "++" may also be used as a prefix operator: ++number;
but care must be taken, since in some contexts the prefix and postfix modes of use have different effects. For example, the program fragment x = 4;
y = x++;
results in "x" having the value 5 and "y" having the value 4, whereas x = 4;
y = ++x;
results in both variables having value 5. This is because "++x" increments the value of "x" before its value is used, whereas "x++" increments the value afterwards. There is also an operator "--", which decrements variables by 1, and which can also be used in prefix or postfix form.
In general, assignment statements have a value equal to the value of the left hand side after the assignment. Hence the following is a legal expression which can be included in a program and which might be either evaluated as true or as false: (y = ++x) == 5
It can be read as the assertion: "after x is incremented and its new value assigned to y, y's value is equal to 5".
Boolean Expressions and Operators

Intuitively, we think of expressions such as "2 <>= 9" as evaluating to "true" or "false" ("!=" means "not equal to"). Such expressions can be combined using the logical operators "&&" ("and"), "" ("or") and "!" ("not"), as in the following examples:
Expression:
(6 <= 6) && (5 < 3)
(6 <= 6) (5 < 3)
(5 != 6)
(5 < 3) && (6 <= 6) (5 != 6)
(5 < 3) && ((6 <= 6) (5 != 6))
!((5 < 3) && ((6 <= 6) (5 != 6)))
True or False:
false
true
true
true
false
true
The fourth of these expressions is true because the operator "&&" has a higher precedence than the operator "". You can check the relative precedence of the different C++ operators in a C++ programming manual or text book (see for example
Savitch, page 976-977). But if in doubt use ( ) parentheses, which in any case often make the program easier to read.
Compound Boolean expressions are typically used as the condition in "if statements" and "for loops". For example: ...
...
if (total_test_score >= 50 && total_test_score < 65)
cout << "You have just scraped through the test.\n";
...
...
Once again, there is an important technical point concerning Boolean expressions. In C++, "true" is represented simply as any non-zero integer, and "false" is represented as the value 0. This can lead to errors. For example, it is quite easy to type "=" instead of "==". Unfortunately, the program fragment ...
...
if (number_of_people = 1)
cout << "There is only one person.\n";
...
...
will always result in the message "There is only one person" being output to the screen, even if the previous value of the variable "number_of_people" was not 1.

2.4 Declarations, Constants and Enumerations

As we have already seen, variables have to be declared before they can be used in a program, using program statements such as float number;
Between this statement and the first statement which assigns "number" an explicit value, the value contained in the variable "number" is arbitrary. But in C++ it is possible (and desirable) to initialise variables with a particular value at the same time as declaring them. Hence we can write double PI = 3.1415926535;
Furthermore, we can specify that a variable's value cannot be altered during the execution of a program with the reserved word "const":

Enumerations

Constants of type "int" may also be declared with an enumeration statement. For example, the declaration enum { MON, TUES, WED, THURS, FRI, SAT, SUN };
is shorthand for const int MON = 0;
const int TUES = 1;
const int WED = 2;
const int THURS = 3;
const int FRI = 4;
const int SAT = 5;
const int SUN = 6;
By default, members of an "enum" list are given the values 0, 1, 2, etc., but when "enum" members are explicitly initialised, uninitialised members of the list have values that are one more than the previous value on the list: enum { MON = 1, TUES, WED, THURS, FRI, SAT = -1, SUN };
In this case, the value of "FRI" is 5, and the value of "SUN" is 0.


Where to put Constant and Variable Declarations
Generally speaking, it is considered good practice to put constant declarations before the "main" program heading, and variable declarations afterwards, in the body of "main". For example, the following is part of a program to draw a circle of a given radius on the screen and then print out its circumference:(There is no need to type in this program) #include
using namespace std;

const float PI = 3.1415926535;
const float SCREEN_WIDTH = 317.24;

int drawCircle(float diameter); /* this is a "function prototype" */

int main()
{
float radius = 0;

cout << "Type in the radius of the circle.\n";
cin >> radius;

drawCircle(radius * 2);

cout.setf(ios::fixed);
cout.precision(2);
cout << "The circumference of a circle of radius " << radius;
cout << " is approximately " << 2 * PI * radius << ".\n";
return 0;
}

int drawCircle(float diameter)
{
float radius = 0;

if (diameter > SCREEN_WIDTH)
radius = SCREEN_WIDTH / 2.0;
else
radius = diameter / 2.0;
...
...
}
After the definition of "main()", this program includes a definition of the function "drawCircle(...)", the details of which need not concern us here (we can simply think of "drawCircle(...)" as a function like "sqrt(...)"). But notice that although both "main()" and "drawCircle(...)" use the identifier "radius", this refers to a different variable in "main()" than in "drawCircle(...)". Had a variable "radius" been declared before the "main" program heading, it would have been a public or global variable. In this case, and assuming there was no other variable declaration inside the function "drawCircle(...)", if "drawCircle(...)" had assigned it the value "SCREEN_WIDTH / 2.0", "main()" would have subsequently printed out the wrong value for the circumference of the circle. We say that the (first) variable "radius" is local to the main part of the program, or has the function main as its scope. In contrast, it usually makes sense to make constants such as "PI" and "SCREEN_WIDTH" global, i.e. available to every function.
In any case, notice that the program above incorporates the safety measure of echoing the input. In other words, the given value of "radius" is printed on the screen again, just before the circumference of the circle is output.

2.3 Some Tips on Formatting Real Number Output

When program output contains values of type "float", "double" or "long double", we may wish to restrict the precision with which these values are displayed on the screen, or specify whether the value should be displayed in fixed or floating point form. The following example program uses the library identifier "sqrt" to refer to the square root function, a standard definition of which is given in the header file cmath (or in the old header style math.h). #include
#include
using namespace std;

int main()
{
float number;

cout << "Type in a real number.\n";
cin >> number;
cout.setf(ios::fixed); // LINE 10
cout.precision(2);
cout << "The square root of " << number << " is approximately ";
cout << sqrt(number) << ".\n";

return 0;
}
Program 2.3.1
This produces the output Type in a real number.
200
The square root of 200.00 is approximately 14.14.
whereas replacing line 10 with "cout.setf(ios::scientific)" produces the output: Type in a real number.
200
The square root of 2.00e+02 is approximately 1.41e+01.
We can also include tabbing in the output using a statement such as "cout.width(20)". This specifies that the next item output will have a width of at least 20 characters (with blank space appropriately added if necessary). This is useful in generating tables. However the C++ compiler has a default setting for this member function which makes it right justified. In order to produce output left-justified in a field we need to use some fancy input and output manipulation. The functions and operators which do the manipulation are to be found in the library file iomanip (old header style iomanip.h) and to do left justification we need to set a flag to a different value (i.e. left) using the setiosflags operator: #include
#include
#include
using namespace std;

int main()
{
int number;

cout << setiosflags ( ios :: left );
cout.width(20);
cout << "Number" << "Square Root\n\n";

cout.setf(ios::fixed);
cout.precision(2);
for (number = 1 ; number <= 10 ; number = number + 1) {
cout.width(20);
cout << number << sqrt( (double) number) << "\n";
}

return 0;
}
Program 2.3.2
This program produces the output Number Square Root

1 1.00
2 1.41
3 1.73
4 2.00
5 2.24
6 2.45
7 2.65
8 2.83
9 3.00
10 3.16
(In fact, the above programs work because "cout" is an identifier for an object belonging to the class "stream", and "setf(...)", "precision(...)" and "width(...)" are member functions of "stream". Don't worry too much about this for now - you will learn more about objects, classes and member functions later in the object-oriented part of the course.)

2.2 Data Types

Integers

C++ requires that all variables used in a program be given a data type. We have already seen the data type int. Variables of this type are used to represent integers (whole numbers). Declaring a variable to be of type int signals to the compiler that it must associate enough memory with the variable's identifier to store an integer value or integer values as the program executes. But there is a (system dependent) limit on the largest and smallest integers that can be stored. Hence C++ also supports the data types short int and long int which represent, respectively, a smaller and a larger range of integer values than int. Adding the prefix unsigned to any of these types means that you wish to represent non-negative integers only. For example, the declaration unsigned short int year_now, age_now, another_year, another_age;
reserves memory for representing four relatively small non-negative integers.
Some rules have to be observed when writing integer values in programs:
Decimal points cannot be used; although 26 and 26.0 have the same value, "26.0" is not of type "int".
Commas cannot be used in integers, so that (for example) 23,897 has to be written as "23897".
Integers cannot be written with leading zeros. The compiler will, for example, interpret "011" as an octal (base 8) number, with value 9.
Real numbers

Variables of type "float" are used to store real numbers. Plus and minus signs for data of type "float" are treated exactly as with integers, and trailing zeros to the right of the decimal point are ignored. Hence "+523.5", "523.5" and "523.500" all represent the same value. The computer also accepts real numbers in floating-point form (or "scientific notation"). Hence 523.5 could be written as "5.235e+02" (i.e. 5.235 x 10 x 10), and -0.0034 as "-3.4e-03". In addition to "float", C++ supports the types "double" and "long double", which give increasingly precise representation of real numbers, but at the cost of more computer memory.

Type Casting

Sometimes it is important to guarantee that a value is stored as a real number, even if it is in fact a whole number. A common example is where an arithmetic expression involves division. When applied to two values of type int, the division operator "/" signifies integer division, so that (for example) 7/2 evaluates to 3. In this case, if we want an answer of 3.5, we can simply add a decimal point and zero to one or both numbers - "7.0/2", "7/2.0" and "7.0/2.0" all give the desired result. However, if both the numerator and the divisor are variables, this trick is not possible. Instead, we have to use a type cast. For example, we can convert "7" to a value of type double using the expression "static_cast(7)". Hence in the expression answer = static_cast(numerator) / denominator
the "/" will always be interpreted as real-number division, even when both "numerator" and "denominator" have integer values. Other type names can also be used for type casting. For example, "static_cast(14.35)" has an integer value of 14.
Characters

Variables of type "char" are used to store character data. In standard C++, data of type "char" can only be a single character (which could be a blank space). These characters come from an available character set which can differ from computer to computer. However, it always includes upper and lower case letters of the alphabet, the digits 0, ... , 9, and some special symbols such as #, £, !, +, -, etc. Perhaps the most common collection of characters is the ASCII character set (see for example Savitch, page 978 or just click here).
Character constants of type "char" must be enclosed in single quotation marks when used in a program, otherwise they will be misinterpreted and may cause a compilation error or unexpected program behaviour. For example, "'A'" is a character constant, but "A" will be interpreted as a program variable. Similarly, "'9'" is a character, but "9" is an integer.
There is, however, an important (and perhaps somewhat confusing) technical point concerning data of type "char". Characters are represented as integers inside the computer. Hence the data type "char" is simply a subset of the data type "int". We can even do arithmetic with characters. For example, the following expression is evaluated as true on any computer using the ASCII character set:
'9' - '0' == 57 - 48 == 9
The ASCII code for the character '9' is decimal 57 (hexadecimal 39) and the ASCII code for the character '0' is decimal 48 (hexadecimal 30) so this equation is stating that
57(dec) - 48(dec) == 39(hex) - 30(hex) == 9
It is often regarded as better to use the ASCII codes in their hexadecimal form.
However, declaring a variable to be of type "char" rather than type "int" makes an important difference as regards the type of input the program expects, and the format of the output it produces. For example, the program #include
using namespace std;

int main()
{
int number;
char character;

cout << "Type in a character:\n"; cin >> character;

number = character;

cout << "The character '" <<>Program 2.2.1
produces output such as Type in a character:
9
The character '9' is represented as the number 57 in the computer.

We could modify the above program to print out the whole ASCII table of characters using a "for loop". The "for loop" is an example of a repetition statement - we will discuss these in more detail later. The general syntax is: for (initialisation; repetition_condition ; update) {
Statement1;
...
...
StatementN;
}
C++ executes such statements as follows: (1) it executes the initialisation statement. (2) it checks to see if repetition_condition is true. If it isn't, it finishes with the "for loop" completely. But if it is, it executes each of the statements Statement1 ... StatementN in turn, and then executes the expression update. After this, it goes back to the beginning of step (2) again.
We can also 'manipulate' the output to produce the hexadecimal code. Hence to print out the ASCII table, the program above can be modified to: #include
using namespace std;

int main()
{
int number;
char character;

for (number = 32 ; number <= 126 ; number = number + 1) { character = number; cout << "The character '" <<>Program 2.2.2
which produces the output: The character ' ' is represented as the number 32 decimal or 20 hex.
The character '!' is represented as the number 33 decimal or 21 hex.
...
...
The character '}' is represented as the number 125 decimal or 7D hex.
The character '~' is represented as the number 126 decimal or 7E hex.
Strings

Our example programs have made extensive use of the type "string" in their output. As we have seen, in C++ a string constant must be enclosed in double quotation marks. Hence we have seen output statements such as
cout << "' is represented as the number "; in programs. In fact, "string" is not a fundamental data type such as "int", "float" or "char". Instead, strings are represented as arrays of characters, so we will return to subject of strings later, when we discuss arrays in general.

User Defined Data Types

Later in the course we will study the topic of data types in much more detail. We will see how the programmer may define his or her own data types. This facility provides a powerful programming tool when complex structures of data need to be represented and manipulated by a C++ program.