CSCI 240 Lecture Notes - Part 2


Typecasting

Suppose you accumulate 5 integer test scores in sum. And suppose that sum is 104.

Then to write an expression that is the average, you could write:

sum/5

and the value of that expression is 20 (a bit wrong - we lost the decimal fraction .8)

But if you write:

sum/5.0

Then the value of the expression is 20.8 (correct)

Suppose you don't know how many numbers there are when you write the program. So you can't use 5 or 5.0 because there might be 21 or 3 or 97 numbers. You could use a variable to hold the count of numbers: declare int count and write some instructions that will give it the proper value before you want to calculate the average. We'll see how to do this soon.

You can't write "count.0" to force it to be real (like we wrote 5.0 above). C++ simply does not allow this.

You must use a typecast - to tell C++ that it should temporarily use the variable as if it were some other data type. To do this, put the name of the data type you want in parenthesis before the variable name. It will not change the variable type or value permanently, but it will force C++ to temporarily treat the variable as a different type:

(double) sum/count    // or
sum /(double)count

(It is true that we could avoid the need to type cast here by declaring count to be double instead of int. But there are other situations where you will either want or need to do this.)

You cannot do just any typecast: (char) sum would not make sense and would not compile.


More on Assignment Statements

Common shortcuts for altering the value of a variable:

1. Add a value to a variable:

var  +=  value_to_add;       //var = var + value_to_add
x += 4;	                    //add 4 to x
 

2. Multiply a variable by a value:

var *= val_to_mult_by       //var = var * val_to_mult_by
x *= 4;	                    //multiply x by 4

3. Same for / and - and %

Examples:

int	n = 5;
double	x = 3.5,
	y = 2.0;


n += 2;		// now n is 7

x *= y;		// now x is 7.0

x *= y + 2;	// mult x (7.0) by (y+2) so x is 28.0

Note that the last line is equivalent to:

x = x * (y + 2);

4. Incrementing and decrementing by 1 is quite common; so there is a special even shorter notation:

i++;           means       "increment i by 1"

n--;           means       "decrement n by 1"

So:

i++;	i += 1;	  i = i + 1;	// all the same

j--;	j -= 1;	  j = j - 1;    // all the same

Example: use the new notations to accumulate (i.e. add up a series of values) and also to count the number of  values :

int sum   = 0;   //initializing the sum to 0 is vital
int count = 0;  //ditto for count

int val;	      //no need to initialize this
double average;   //or this

cout << "\nEnter 1st number: ";
cin >> val;

sum += val;
count++;


cout << "\nEnter 2nd number: ";
cin >> val;

sum += val;
count++;


cout << "\nEnter 3rd number: ";
cin >> val;

sum += val;
count++;


//Now calculate an average... 

average = (double) sum/count;

Note the reuse of val - there is no need for a new variable for each new value, since we only need the sum.

Also - in some books or code you will see the notation ++i;  This is also legal but is slightly different in some contexts.  We will not cover this variation or use it in this course.

Example: Suppose we want to print squares of first few integers:

int i = 1;

cout << i*i;       //note use of expression

i++;

cout << i*i;       //now i = 2; displays 4

i++;

cout << i*i; 	     //now i = 3; displays 9
...

Example: Suppose we want to print squares of first few odd numbers:

int i = 1;

cout << i*i;     	//i is 1; displays 1

i += 2;

cout << i*i; 	    //i is 3; displays 9

i += 2;

cout << i*i; 		//i is 5; displays 25
...

Formatted Numeric Output Using cout

All the examples so far that have displayed numeric output have used the simplest possible method:

cout << num;

This will not always display the number in the way you would like. Often you want to control the field width (the number of display columns that the number takes up) or the number of significant figures, or the number of decimal places. cout has a number of moderately awkward ways to control these things.

First, in order to use them you must #include <iomanip>

Then, in the particular cout operation, you include one or more of several special modifiers to the command.

setw( size )

int num1 = 1234;
int num2 = 56789;

cout << setw(6) << num1;

_ _ 1 2 3 4


cout << setw(6) << num1 << setw(6) << num2;

_ _ 1 2 3 4 _ 5 6 7 8 9

setprecision( num )

double val = 123.456;

cout << setprecision(5) << val;

123.46

Stream Format Flags

There are various flags that can be set to specify the kind of formatting to be performed.

To set a flag:

setiosflags( ios:: flag )   -or-   setiosflags( ios::flag1 | ios:: flag2 )

-or-   cout << flag ...

Floating Point Format Flag

There are two ways to format floating point numbers:

  1. fixed -- print with a fixed number of digits after the decimal point

  2. scientific -- print in scientific notation

These will stay set until changed.

double y = 50.0512;

cout << setiosflags( ios:: fixed ) << setprecision(2) << y;

50.05


cout << setiosflags( ios:: fixed ) << setprecision(2);

cout << y;

50.05


cout << fixed << setprecision(2) << y;

50.05


cout << fixed << setprecision(2);

cout << y;

50.05

Justification

There are three ways to justify numbers:

  1. left -- left justify the number

  2. right -- right justify the number (default)

  3. internal -- for a signed number, the sign is left justified and the number is right justified

These will stay set until changed.

The output of the following program is shown below, assuming the user enters 3.14159, with notes about each output line.

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
double d;
double quo = 100.0/3.0;

cout << "100.0/3.0 as double is: " << quo;      // line A

cout << "\nEnter a floating point number: ";
cin >> d;

cout << "Num in native format: " << d;          // line B

cout << "\nW= 5 p = 3: |" << setw(5) 
                          << setprecision(3) 
                          << d << "|";          // line C

cout << "\nW= 5 fixed p = 3: |" << setw(5) 
                                << fixed 
                                << setprecision(3) 
                                << d << "|";    // line D


return 0;
}

Line A: "100.0/3.0 as a double is: 33.3333" Note that with no special formatting commands, 6 figures are displayed, although more could be: 33.3333333... If the division had been 100.0/2.0 the result would have been 50 (not 50.0000).

Line B: "Num in native format: 3.14159"  Again, 6 figures display. If you had entered more digits, still only 6 would display. If you had entered fewer, fewer would display.

Line C: "W= 5 p = 3:  | 3.14|" Here we first use the I/O format manipulators. Before we send the value (num) to display, we tell cout how to display it.

setw() tells cout how many display positions to use for the value. If the value needs more space, it will just take it. If the number requires less space, it will be padded with blanks. You can control left- or right-justification with left or right. See the below. In this example, notice that there is a space before the 3.

Note that you must use setw() for each numeric value you output, even if the width is the same:

cout << setw(5) << num1 << setw(5) << num2;

setprecision() tells cout how many significant figures to display. Here we see 3.14 which is just 3 significant figures. If we had specified setprecision(4) we would see 3.142. (The value is rounded.)

Line D: "W= 5 fixed p = 3: |3.142|"  Here, the fixed tells cout that the precision applies to the number of decimal places, so we see 3 decimal places. Note that all 5 width spaces are used up, so there is no space before the 3.

Unlike setw(), once setprecision() and fixed have been set, they retain their values (or settings) until you change them.

Field Justification and blank padding: left and right

If the field width (setw()) is larger than the number of characters needed, the data will display either all the way to the left or all the way to the right within the field, padded or filled with leading or trailing blanks. Numeric values will display to the right (right-justified) and strings to the left (left-justified). You can alter this behavior using the left and right manipulators:

cout << "xx" << right << setw(20) << "Jim Henry" << "xx";
cout << "\nyy" << setw(10) << left << quo << "yy";

will display:

xx           Jim Henryxx
yy3.333     yy

Control Structures

So far all programs execute from first to last instruction (in sequence top to bottom). So we have one pattern of execution so far:

1. Sequence

To write complex programs, we need to alter the flow of execution.

We will need only 3 new patterns:

2. decide which block of instructions to execute

3. repeat a set of instructions (also called looping)

4. branch to a separate group of instructions and return (or function calling - which we will study in detail later).

Any program - no matter how complex - can be written with just the first three patterns: sequence, repetition, decision.

Writing programs using these three patterns together is called structured programming.

Branch and return is nice and has real advantages in complex programs, but the first three are sufficient for any program.

Conditional Expressions

Both looping and decision statements depend on a conditional expression, which is a logical expression that is either true or false.

The condition is enclosed in parenthesis (there is one exception - the for construct).

It usually uses a relational operator.

==     equality
!=     not equal
>      greater than
<      less than
>=     greater than or equal
<=     less than or equal
!>     not greater than
!<     not less than

(By the way, =>   =<   >!  will not compile.)

Numeric conditions are common:

( sum == 0 )          or        (i < 10)

Other kinds of conditional expressions are possible. For example, we will study more about functions that "become" true or false later. In C++, integer values by themselves have a true or false meaning. Specifically, 0 is false and anything else is true. So it is legal to write:

( n )

Here, if n is 0 then the condition is false; if n is non-zero, the condition is true

Using one = is an assignment statement which will compile and execute, but will not mean what you think it means. 

In short: to compare for two things being equal, you must use two = signs: 

(x == 10)

If you code:

(x = 0)

then you are assigning 0 to x and the truth of the condition will depend on the value stored into x.  In C and C++ the value 0 is considered to be false and any other number is true.

We will revisit this idea later.  For now just remember to use

  • two = when you want to compare and
  • one = when you want to assign.

Decisions

The decision structure allows the program to execute one or another set of instructions - to decide which set to execute.  Or it can decide simply not to do a set of instructions.

So it determines whether or not sections of code are executed.

Example: imagine you need to calculate an average, but there is a possibility that actually no values have been accumulated.  That is, the count of numbers whose average is to be found is 0.

We'd like to be able to code something that expresses:

"if count is 0 then display a message, otherwise calculate and display the average"

We should do this just when we are ready to try to calculate an average. And we can do it in C++ like this:

if (count == 0)        //Note: two = signs for comparison
  {
  cout << "\nNo data entered";
  }
else
  {
  cout << "\nAverage: " << (double) tempSum/count;
  }

There are two basic forms of the if statement

if (condition)
  {
  block of statements to execute if condition is true;
  }
 
if (condition)
  {
  block of statements to execute if condition is true;
  }
else
  {
  block of statements to execute if condition is false;
  }
 

If the block is only 1 statement long, the {} aren't required:

if (cond)
  stmnt;
 
if (cond)
  stmnt;
else
  stmnt;
 

Note the use of  ;

We will study a couple of further variations on if statements later as well as a couple of alternative instructions that implement decisions.


Repetition or Looping

In C++ there are 3 instructions that build loops:


The for Statement.

int i;

for (i = 0; i < 10; i++)
  {
  cout << i;
  }
 
... the pgm continues...

This says:

  1. set i equal to 0
  2. if i is less than 10, execute the body of the loop (the cout); otherwise go to next instruction after the for loop and continue
  3. increment i
  4. go to step 2

This loop would produce the following output:

0123456789

Note that "running the loop" from 0 to 9 executes the body 10 times.

Recall the code to print the squares of the first few odd numbers? Now we can use a for loop to compress it:

int i;
int num = 1;
 
for (i = 1; i <= 5; i++)
  {
  cout << " The square of " << num << " is " << num*num << endl;
  num += 2;
  }

This would produce:

The square of 1 is 1
The square of 3 is 9
The square of 5 is 25
The square of 7 is 49
The square of 9 is 81

Notes:

If you don't code the {}, only the first statement following the for is considered to be part of the loop body.

and in the code above (without the {}), you would get:

The square of 1 is 1
The square of 1 is 1
The square of 1 is 1
The square of 1 is 1
The square of 1 is 1

because although i is incremented, num += 2; was not executed until after the loop finished.

The three expressions in the for loop's () can be thought of as:

The initialization and the "final" part can have more than one instruction - they are separated by commas. For example, if you wanted to initialize two variables, i and j to 0 and to increment both after the body of the loop is executed, it would look like this:

for (i = 0, j = 0; i < 10; i++, j++)
  {
  loop body
  }

Finally, because the exit condition is tested before the loop body is executed, the for loop is classified as a top-driven loop.  If the exit condition is true the first time it is tested, the loop body will not execute at all.


The while Statement

Often we don't know in advance how many times loop should execute, so we can't use for in its role as a counting loop as shown above.

Example: ask the user to enter some numbers and then find their average.

We could:

  1. ask user in advance how many numbers will be supplied (if so we could use a for loop; but this is bad for user).
  2. tell user to enter a special value when done (e.g. negative for test scores). Sometimes no special value can be found.
  3. Ask user after each value if there's another number.

We'll use the while loop to code the second way.

Format of while loop:

while (condition)
  {
  body of loop
  }

This is a top-driven loop. The "condition" is tested before the execution of the code in the body of the loop. It is tested before the body is executed the very first time - and if it is false, the body of the loop will not be executed at all. So the loop could execute 0 times.

Here is the basic logic that will be used for the averages program.

get aTemp from the user

while (aTemp > -999)	//using special value for exit
  {
  process aTemp

  get next aTemp from user
  }

calculate average

Complete averages program

//Program to calculate average temperatures from user input.
//User enters -999 or less to quit

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
int aTemp;
int tempSum = 0;
int tempCount = 0;

cout << "\nEnter a temperature (-999 to quit): ";
cin >> aTemp;

while (aTemp > -999)
  {
  tempCount++;
  tempSum += aTemp;

  cout << "\nEnter next temperature (-999 to quit): ";
  cin >> aTemp;
  }

cout << "\nAverage is " << (double)tempSum/tempCount;

return 0;
}

Notice that there is one flaw with the code. What is it?

Hint: what if user enters -999 as first number?


Another version of the average program

Some applications will not have any natural "special value" - e.g. a speed can be any + or - value.

So we need another way to tell when to stop the looping - that is, a different exit condition.

One way is to ask user each time, and accept an answer (as a string).

//Program to calculate average speeds from user input
//User is asked after each value of there is more data.

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
int aSpeed;
int speedSum = 0;
int speedCount = 0;

string choice = "y";

while (choice == "y")	  // see note below
  {
  cout << "\nEnter a speed: ";
  cin >> aSpeed;

  speedCount++;
  speedSum += aSpeed;

  cout << "\nAnother? (y/n) ";
  cin >> choice;
  }

cout << "\nAverage is " << (double) speedSum / speedCount;

return 0;
}

Remember, though, In this case, as well as other comparisons for equality (e.g. for testing numbers) you must be careful to code two = rather than one. Using one is an assignment statement which will compile and execute, but will not mean what you think it means. In short, again:

to compare for two things being equal, you must use two = signs:

if (x == 10)   
if (s == "jim")

The do...while Statement

This version of a loop instruction ensures the body of the loop always executes at least once.

After each repetition, the exit condition is tested. So this is classified as a bottom-driven loop.

Format:

do                  // note: NO ; here
  {
  .. loop body
  }
while (condition);  // note:  ; required here!!

You could re-write the average program using a do..while without having to initialize the variable choice.

#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
int aSpeed;
int speedSum = 0;
int speedCount = 0;

string choice;

do
  {
  cout << "\nEnter a speed: ";
  cin >> aSpeed;

  speedCount++;
  speedSum += aSpeed;

  cout << "\nAnother? (y/n) ";
  cin >> choice;
  }
while (choice == "y");


cout << "\nAverage is " << (double) speedSum / speedCount;

return 0;
}

Loop Exit Conditions

Be very careful. You must ensure that

The following will loop forever:

i = 1;

while (i < 10)
  {
  n = i * 4 - j / 16;
  cout << n;
  }

Why?

Likewise:

i = 1;

while (i < 10)
  n = i * 4 - j / 16;
  cout << n;
  i++;

Why?

You need to be careful to code loop exit conditions correctly for all three C++ loop constructs.


Symbolic Constants - C Version

Symbolic Constants allow us to associate a value with a symbolic and meaningful name. You use the name wherever you want the value.

In this course we will restrict use of symbolic constants to numeric values.

By convention, symbolic constants are always CAPITALIZED as a signal to the (human) reader of the program that the value is a constant.

Use these whenever you have a fixed value used in several places in a program. This value will not change while the program is running, but the value may change at some later time (example: postal rates - cents per ounce). You would then change the value and recompile the program.

Using a symbolic constant allows a change to this value to be made in just one place rather than finding and changing it in many places in the program. Finding and changing in multiple places is error-prone. Then just re-compile the program.

Also, use symbolic constants for "magic" numbers whose values do not explain their meaning:

Which is clearer?

Total = amt + amt * 0.0625;

Total = amt + amt * TAX_RATE;     //TAX_RATE is a symbolic constant

You create C-style symbolic constants with #define statements at beginning of program (before main). The symbolic names are always capitalized by C and C++ programmers. In this course you should also capitalize them.

Note there are no commas or semi-colons in a #define:

#include <iostream>
#include <iomanip>

using namespace std;

#define TAX_RATE     0.0625      //CAPS by convention
#define INDENT_PRCNT 0.0625      //diff meaning; same value

int main()
{
...

//0.0625 replaces TAX_RATE

total = amt + amt*TAX_RATE; 


//0.0625 replaces INDENT_PRCNT

x = 2 * INDENT_PRCNT;     


//then next is illegal: equivalent to 0.0625 = 0.07 and generates an error

TAX_RATE = 0.07;
...

So:

Changing TAX_RATE in the #define changes it everywhere it occurs in the program (potentially many times), but does not change INDENT_PRCNT anywhere.

Meaning is clearer.

Symbolic Constants - C++ Version

C++ has a alternate mechanism to create a symbolic constant. It is typically declared as a data item, but in the same place as a #define. The declaration is prefaced with the keyword const. The compiler will flag as an error any attempt by program instructions to alter the value of this data item.

#include <iostream>
#include <iomanip>

using namespace std;

const double TAX_RATE = 6.025;
const double PI = 3.14159;

int main()
{
...

total = amt + amt*TAX_RATE;   // ok - as above

...

TAX_RATE = .07;               // illegal - produces compiler error message
...