Sunteți pe pagina 1din 110

CSCI 240 Lecture Notes -- Part 1

A program = data + instructions


Data:

there are several data types (numbers, characters, etc.) each individual data item must be declared and named each individual data item must have a value before use initial values come from o program instructions o user input o disk files program instructions can alter these values original or newly computed values can go to o screen o printer o disk

Instructions:

for data input (from keyboard, disk) for data output (to screen, printer, disk) computation of new values program control (decisions, repetition) modularization (putting a sequence of instructions into a package called a function)

The C++ Language - Introduction


C++ is a superset of the C programming language. Any C code is also C++ code, but some C++ code is not C. Much of what you learn in this course could also be used in a C program. There are lots of new features and capabilities in C++. We will learn some of them (but far from all of them) in this course. The C++ Language is made up of

keywords/reserved words (if, while, int, etc.) symbols: { } = | <= ! [ ] * & (and more) programmer-defined names for variables and functions

These programmer-defined names:


1 - 31 chars long; use letters, digits, _ (underscore) start with a letter or _ are case-sensitive: Num is different than num should be meaningful: studentCount is better than s or sc

C++ Data Types


Each data item has a type and a name chosen by the programmer. The type determines the range of possible values it can hold as well as the operations that can be used on it. For example, you can add a number to a numeric data type, but you cannot add a number to someone's name. (What would "Joe" + 1 mean?)

Commonly-used data types


int (integer numbers)

no decimal point, comma, or $ leading + - allowed range (on our system): about plus or minus 2 billion int constants are written as: 1234 -3 43

To make a variable that your program can use, you must declare it. Declaration Examples:
int x; int x, y; //gives type and name; no value yet //declares 2 ints; no values yet //declares & sets the initial value

int population = 16000;

So you could picture the results of the last two lines as: x ?? y ?? population 16000

Note: It is critically important that variables have values before they are used in a program. In the examples above, x and y have no values even though they have been declared. More accurately, they do have values but those values could be anything between -2 billion and +2 billion. Before you use x or y you would have to give them values. float and double (floating point or real numbers)

These data types are also commonly referred to as real variables (following mathematics usage) when it is not important which one we mean and we don't want to always have to say "float or double".

has decimal point leading + - allowed no comma, $ range (float) plus or minus 10 to 38th (limited to ~ 6 significant digits) range (double) plus or minus 10 to 308th (limited to ~12 significant digits) floating point constants are written as 3.08 -32.0 0.15 9.3e7 (9.3 times 10 to the 7th power)

Declaration Examples:
float pi = 3.1416; //declares, names, sets init value

double x = 3.5, //note comma here y = 1245.6543; //can use > 6 digits OR double x double y float = 3.5; = 1245.6543; // exponential notation: 5280

big = 5.28e3;

char (single characters)


can hold just one character char constants are written with single quotes: 'a'

Declaration examples:
char ch; char choice = 'q';

string (character "strings" or sequences)


can hold 0 to many characters string constants are written with double quotes: "Hello, world"

Declaration Examples:
string s; string MyName = "Amy";

Arithmetic Operations and Examples

The arithmetic operators are:


+ addition - subtraction or unary negation (-5) * multiplication / division (see special notes on division below) % modulus division -- integer remainder of integer division

Note: there is no exponentiation operator. Special Note on Division: Recall 3rd grade division:
3 R1 _______ 4| 13 12 1 4 is divisor 13 is dividend 3 is quotient 1 is remainder

In C++, a division with 2 int operands has an int resulting value:


13/4 --> 3 13%4 --> 1 // int quotient // int remainder

But with 1 or 2 float/double operands, the resulting value is a float or double:


So: 13.0 / 4 --> 3.25 13 / 4.0 --> 3.25

Be aware. Forgetting this can easily cause an error. Arithmetic Expressions are formed by operands and operators. Operands are the values used, operators are the things done to the numbers. Parts of arithmetic expressions are often grouped by () for clarity or to affect the meaning of the expression:
//declare and initialize some variables int x = 11, y = 2; double realnum = 2.0;

expression x+y x*y x*y+x

value 13 22 33

notes

x-y -x + y x/y x%y x / realnum

9 -9 5 1 5.5 unary negation: "minus x" int since both ops are int rem when x divided by y one op is real so result is real

Note: The expressions above by themselves are not valid C++ statements - they would be part of a statement. But each expression (if it is in a valid C++ statement) has a value. Also - the spaces written between operands and operators are not required by C++, but do improve legibility. More complex expressions are evaluated by C++ using Rules of Precedence (like algebra) 1. sub-expressions in () 2. unary 3. * and / - left to right 4. + and - - left to right In the examples below, the red numbers show what operation is done as C++ evaluates and expression:
3 + 5 * 4 / 2 = 3 + 20 / 2 = 3 + 10 = 13

But () can be used to change this (or to make expression more readable)
(3 + 5) * 4 / 2 = 8 * 4 / 2 = 32 / 2 = 16

Assignment Statement/Operation
The symbol "=" is a command. It means: "evaluate the expression on the right and store this value in the variable on the left" In general:

someVariable = some expression;

Examples:
x = y * 3; x = x + y;

Notice that there is always exactly one variable on the left to receive the value of the expression on the right. This is NOT like algebra; it is NOT an equation.
Algebra: x = 1 C++: x = 1; (T or F for a given x) // store 1 into x

Algebra: x = x + 1 (false for all x) C++: x = x + 1; //find x+1; result in x

More Assignment Examples


//declare variables double dAns, x, y; int iAns, i, j; //assign values via numeric literals i j x y = = = = 5; 8; 4.0; 1.5;

//assign values from complex expressions i = i + 2; dAns = x / y; iAns = j/2*2; iAns = 8/5; iAns = 8 % 5; iAns = 5 / 8; iAns = 5 % 8; // 3 // 0 // 5 // 7 // 2.666.. // 8/2*2 = 4*2 = 8 // 1

i = x/y;

// 2

The last needs an explanation. the value of the expression x/y is 2.666.. but i is an int variable and so cannot hold the .666 part. C++ simply drops it (truncates it) and so only the whole number part of the value is stored into i. Notes: 1. All variables in expressions on the right must have defined values or you get random results. This is an example of what I meant before when I said variables must have values before they are used.
int x, y; x = y + 1; int x; int y = 2; x = y + 1; // declared; no init values // ??? in x // declared; no init value // declared; given a value // x has value 3. // It is Ok if x has no value before this

Inadvertent use of un-initialized variables is one of the most common causes of program errors. Learn to be very careful about this. 2. You can do multiple assignments on one line:
x = y = z = 3; //all get value 3

3. Naming Variables: use meaningful names that explain themselves to reader of the program. (You, me, the maintainer of the program after you go to your next job...)
No s cs cav num stnum Yes score class_size class_average student_count student_id

ClassSize ClassAvg NumStudents StudentID

classSize classAvg numStudents studentID

Sample C++ program


#include <iostream> #include <iomanip> using namespace std; int main() { int num1, num2, sum; // a. // b. // c. // d.

num1 = 10; num2 = 15; sum = num1 + num2; return 0; }

// e. // f. // g.

Notes: a. b. c. d. e. f. g. C++ standard libraries that must be included at the beginning of C++ programs So the standard (std) objects can be used from the included libraries program name is always main with int (which stands for integer) before it num1, num2 and sum are the data items (which we often call variables) num1 and num2 get initial values from instructions via assignment (the = is the assignment command) sum gets its value from an arithmetic expression return an integer: main "promised" to provide an integer "answer". In this program (and usually) the "answer" doesn't matter. But we have to keep the promise, so we just return 0. We will discuss this further later. on any line, typing // turns the rest of the line into a comment. A comment is not "seen" by the computer - it is not part of the program. It is used by human readers of the program to explain something in the program. there are no program control or modularization statements this program has no input or output notice the semicolons - where they are and aren't

h.

i. j. k.

Output
We will use cout for output. cout's task is to display information on the screen. The simplest form of cout is:
cout << "Hello";

You can output (for display) several values, including the values calculated and stored into variables, by stringing them together connected by "<<":
cout << "The average is: " << avg; cout << << << << "The temperature is: " temp " and the rainfall today was: " rainAmt; // avg is a variable

Note that I have put each item to display on a separate line for clarity. This is both legal and is usually desirable if you have more than 2 values. There are a couple of special techniques to position cursor before or after you display characters, using an escape sequence. (A \ followed by a char. For example, \n is the newline escape sequence.) Note that this is a backslash.
cout << "\nhello"; cout << "hello\n"; cout << "hel\nlo"; //moves to next line, shows hello //shows hello, then goes to new line //shows hel, then lo on next line

Note: to display a single \, you must code two: \\


cout << "the \\ char is special";

This shows: "the \ char is special" Also, there is an alternate way to move the cursor to the beginning of a new line: the special value "endl":
cout << endl << "Some stuff" << endl; cout << "\nSome stuff\n";

Both will first move the cursor to a new line, then display "Some stuff" and then move the cursor to the next line. Note that if you don't include endl or \n, all your output will be on one line:
cout << "The average is: " << avg; cout << << << << "The temperature is: " temp " and the rainfall today was: " rainAmt; // avg is a variable

This will display something like this:


The average is 77.5The temperature is: 78 and the rainfall today was: 1.32362

Input
We will use cin for input. cin's task is to accept user input and store it into variables. Note that

the C++ language itself has no defined I/O functions or commands; just a standard library that can accomplish I/O. We could use other libraries, including those from the C language or those developed by others. most often when we want user input, we will need to do two things: 1. tell the user what to enter and then 2. get the value entered.

So typically we will do something like this:


int i; ... cout << "Enter the temperature: "; cin >> i; // a. // b. // c.

Notes: a. b. c. d. e. f. we declare an int to hold the value to be typed in we tell (prompt) the user to enter something we get the value that is entered by the user when a cout occurs in a program, the characters are displayed immediately on the screen when cin occurs in a program, the program waits for user to type in a value and press <Enter>. the << and >> are meant to suggest the direction the data is going: o for cout, the prompt is going from the string constant through cout to the screen o for cin the data entered is coming in from the keyboard, through cin, to the variable i for numeric input with cin, if the user types a non-number like "w" or "23e" then results are unpredictable. (For a double or float, "2e5" is ok - it's the same as 200000) the variable receiving the value from cin must be declared (of course) but need not have an initial value. Any previous value in that variable is lost. details about formatting numeric output (number of decimal places, etc.) will be covered later. cin "understands" the different data types we have covered. So you can do cin with int, float, double, char, and string. But you must ensure that the data item getting the value is compatible with what the user is asked to enter. You can't store "jim" into an int, for example. You can store the value 4 into a double, however.

g. h. i. j.

Program Error Types


1. Compile error - invalid C++ statements; so your code cannot be translated to machine language. Examples: 1. integer x, y; 2. num1 = num1 + num2

In example 1 you must use int, not integer. In example 2 the ; at the end of the line is missing 2. Link error: one of the functions used in the program is not found when it's time to run the program. In the program above, there are no such functions, but we could encounter this problem later. 3. Run-time error - program does not run to completion. Example:

divide by zero: set x = 10 and y = 0 and then try to compute x/y

4. Logic error - program runs to completion, but the results are wrong. Example:

you really wanted x - y but coded x + y in calculating an average, you use the wrong number to divide

Fixing 3 and 4 is called debugging. You must learn to understand that the computer did what you said, not necessarily what you meant.

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 sum /(double)count // or

(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 += x += 4; value_to_add; //var = var + value_to_add //add 4 to x

2. Multiply a variable by a value:


var *= val_to_mult_by x *= 4; //var = var * val_to_mult_by //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; x *= y + 2;

// now x is 7.0 // 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++; n--; means means "increment i by 1" "decrement n by 1"

So:
i++; j--; i += 1; j -= 1; i = i + 1; j = j - 1; // all the same // 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; int count = 0; int val; double average; //initializing the sum to 0 is vital //ditto for count //no need to initialize this //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; i++; cout << i*i; i++; cout << i*i; ... //now i = 3; displays 9 //now i = 2; displays 4 //note use of expression

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


int i = 1; cout << i*i; i += 2; cout << i*i; i += 2; cout << i*i; ... //i is 5; displays 25 //i is 3; displays 9 //i is 1; displays 1

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 )

sets a MINIMUM width size - must be an integer value must #include <iomanip> the width is only valid for the variable that is printed

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 )

does 1 of 2 things: 1. If setprecision is used by itself, the value num specifies the total number of digits to display. 2. If setprecision is used in conjunction with the fixed flag, the value num specifies the number of digits to display after the decimal point num - must be an integer value the value is rounded up when it is displayed the precision stays set until it is changed must #include <iomanip>

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 ) -orsetiosflags( 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; cout << "\nEnter a floating point number: "; cin >> d; cout << "Num in native format: " << d; cout << "\nW= 5 p = 3: |" << setw(5) << setprecision(3) << d << "|"; cout << "\nW= 5 fixed p = 3: |" << << << << return 0; } // line B // line A

// line C

setw(5) fixed setprecision(3) d << "|"; // line D

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 yy3.333 Jim Henryxx 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:

for - executes a block of instructions a certain number of times while - repeats a block of instructions 0 to many times, as long as a given condition is true do..while - executes a block of instructions once and then 0 to many more times, as long as a given condition is true. That is, the block is executed at least once. Definitions: 1. "a block of instructions" are those that should be executed repeatedly. They are called the body of the loop. 2. "a given condition" is a logical condition that is true or false (i.e. is i > 10?). This condition is called the exit condition of the loop.

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 The The The The square square square square square of of of of of 1 3 5 7 9 is is is is is 1 9 25 49 81

Notes:

we could print the first 25 squares-of-odd numbers just by changing 5 to 25 in the for statement the loop runs from 1 to (and including 5). We could have run from 0 to < 5, but then the numbers would have been 0 to 4. We could have fixed that by coding i+1 as the first data sent to cout the body of the loop is traditionally indented (you must do this in this course) notice that the body of loop is enclosed in {}. This is required if loop body is more than 1 statement.

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 The The The The square square square square square of of of of of 1 1 1 1 1 is is is is is 1 1 1 1 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 part: this is done only once, before the body is executed the first time the exit condition: this is tested before each execution of the loop body. When it is false, the loop body is not executed any more and whatever instruction is after the for construct is then executed. the "final" part: this is executed after each execution of the loop body. It's as if it were added on to the end of the loop's body.

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. There are a couple of patterns we could use for the averages program. Pattern 1:
get aTemp from the user while (aTemp > -999) //using special value for exit { process aTemp get next aTemp from user } calculate average

Pattern 2:
while (1) { get aTemp from user if (aTemp <= -999) break; //break jumps out of loop process aTemp } calculate average

Notes:

the value of 1 is evaluated in a condition by C++ as "true" (0 is false) some consider breaking out of loop to be bad others consider Pattern 2 to be closer to the way we think break is a new instruction - in this course it should only be used to implement this pattern.

Complete averages program, Pattern 1:


//Program to calculate average temperatures from user input. //User enters -999 or less to quit #include <iostream> #include <iomanip> using namespace std; int { int int int main() aTemp; tempSum = 0; 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; }

As an exercise: convert this to Pattern 2 but notice that there is one flaw. Hint: what if user enters -999 as first number? You should know how to fix this by now.)

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 { int int int main() aSpeed; speedSum = 0; 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 { .. loop body } while (condition); // note: NO ; here

// 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 { int int int main() aSpeed; speedSum = 0; 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

loop exit condition has correct value first time loop exit condition eventually becomes false

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 #define INDENT_PRCNT 0.0625 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; ... //CAPS by convention //diff meaning; same value

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; ... TAX_RATE = .07; ... // illegal - produces compiler error message // ok - as above

CSCI 240 Lecture Notes - Part 3


Cascading if's
Sometimes the two alternatives provided by if aren't enough. Suppose you need to decide if a student is a Freshman, Sophomore, etc. based on the number of credit hours. There are more than two alternatives. You can use this pattern - which is an extension of the simple if:
if (cond1) { action1; } else if (cond2) { action2; } else if (cond3) { action3; }

If none of cond1, cond2, and cond3 are true, no action is taken. If you want some action to be taken when none of the conds are true, use this pattern:
if (cond1) { action1; } else if (cond2) { action2; } else if (cond3)

{ action3; } else { action4; }

// note no (cond) here

action4 is done if and only if none of the conds is true. In the first pattern, zero or one action is done. Never more than one. In the second pattern, one and only one action is done. So for the credit-hour - class standing problem, we could code the following:
int credits; string classStanding; cout<< "Enter number of credit hours: "; cin >> credits; if (credits >= 90) { classStanding = "Senior"; } else if (credits >= 60) { classStanding = "Junior"; } else if (credits >= 30) { classStanding = "Sophomore"; } else if (credits >= 0) { classStanding = "Freshman"; } else { classStanding = "Error: ...."; } cout << "\nClass is: " << classStanding;

Alternative Decision Statements


We have studied and used the if statement to implement the idea of a decision. There are a couple more ways in C++ to implement decisions that you should know about.

switch Statement
The switch statement can cause 0 to many alternative blocks of code to execute based on the value of a scalar variable. Normally, it is used to execute one and only one block of code. Scalar Data Types Any data type whose variables can take on a set of values whose number is no greater than the number of integers representable on the computer is called a scalar data type. int and char are scalars. Strings and floats and doubles are not. Example:
int n; n = some value; switch (n) //go to case that matches n's value { case 1: //if n == 1, these execute stmt1; stmt2; break; //go to stmnt after closing } case 3: stmnt3; break; case 5: stmnt4; stmnt5; break; default: //n not 1, 3, 5: this executes stmnt6; stmnt7; }

The default block is optional. Sometimes you want to do the same thing for several values. You can do this:
char ch; string s; ch = some value; switch (ch) { case 'a': case 'b': case 'c': case 'd': s = "pass"; break;

case 'f': s = "fail"; break; case 'i': case 'n': case 'w': s = "other"; break; default: s = "invalid"; }

If ch is 'b', for example, it will match case 'b' and go there - then just "fall through" the case 'c', case 'd', assign "pass" to s, and then break out of the switch structure.

Note: forgetting a break is a common error.

If you forget a break, execution will "fall through" and execute all code after it until it finally hits a break or finishes all the code in the switch.

Use of the ? and : operators - a "ternary" instruction


A short and somewhat cryptic decision structure is allowed by C++. The general format is: (condition) ? stmnt_to_do_if_true : stmnt_to_do_if_false; Some compilers do not require the () around the condition. As an example:
(x < y) ? x++ : y++;

This is the same as writing:


if (x < y) x++; else y++;

But there is one difference. The expression as a whole becomes the value computed in one of the two "branches". Here are a couple of examples. Note the () around the whole ternary expression:
char ch = 'y'; int x = 3, y = 2; cout << ((ch == 'y') ? "Yes" : "No");

cout << "The value of the smaller of x and y is " << ((x < y) ? x : y);

and this would display "YesThe value of the smaller of x and y is 2" This somewhat terse syntax can be handy sometimes.

Data type bool and flags


C++ defines a data type called bool. (Short for "boolean", from the name of a 19th century Irish logician, George Boole) bool variables can have one of two possible values: true and false This data type comes in handy in writing programs that are easier to understand. For example, consider the following partial example:
bool found = false; while ( !found ) { //some complex code to search for something... //somewhere in it is the following: if (some condition is true) found = true; }

These kinds of variable that can be true or false are often called flags. They typically indicate if some event has happened yet or not, or as a category:
bool okToWithdraw; double bal, request; //get user's bank balance and store into bal //get user's withdrawal request and store into request if (bal - request > 0) okToWithdraw = true; else okToWithdraw = false; ... if (okToWithdraw) // give out money

else // print error message

Scope
The scope of a variable is the location within a program where a variable can be used. With control structures, all of the statements that are contained within a block of code belong to the control structure. Therefore, any variable that is declared within such a block of code only has meaning between the declaration for the variable and the closing curly brace for the block of code. For example:
for( int i = 0; i < 10; i++ ) { cout << endl << i; //this line of code will compile } cout << i; //this line of code will NOT compile

CSCI 240 Lecture Notes - Part 3 continued


Compound Conditions
Sometimes one condition is not enough. Suppose we want to test if a point is inside a rectangle, and we know the x and y coordinates of the point; also we know the left, right, top, and bottom coordinates of the rectangle. So we declare:
int ptX, ptY; int rectLeft, rectRight, rectTop, rectBot;

Now assume all of these variables have valid values and Cartesian coordinates. If we wanted to test if a point is inside the rectangle, then:
if ptX is greater than rectLeft if ptY is greater than rectBot AND if ptX is less than rectRight AND AND if ptY is less than rectTop

THEN (meaning, if ALL of these are true...), the point is in the rectangle.

We can code this in C++, using && for the AND's (newer C++ compilers also allow the word and):
if (ptX > rectLeft && ptX < rectRight && ptY > rectBot { cout << "pt is in rect"; } else { cout << "pt is not in rect"; } && ptY < rectTop)

Note that we required that the point be inside the rectangle because we used > and <. We could also allow the point to be on or inside the rectangle by using <= and >=. How could we check to see if the point lies ON the top edge of the rectangle? (Try it) We can also code OR's of two conditions, using 2 vertical bars: || (again, newer compilers allow or): Example: Suppose you want to ask if the user wants to continue:
cout << "Another? (y/n)"; cin >> choice;

But - you want to accept either "y" or "Y" to indicate "continue". (The user may have hit caps lock, or just carelessly hit shift).
do { ... some processing of one thing... cout << "Another? (y/n)"; cin >> choice; } while (choice == "y" || choice == "Y");

Another example: suppose your program is accepting a number representing a shoe size, and you know that shoe sizes cannot be smaller than 5 or greater than 18. Validate this number - that is, make the user type in a "valid" shoe size. To do this use a compound condition:
cout << "Enter a shoe size"; cin >> size; if (size < 5 || size > 18) { cout << "invalid size....Try again: "; cin >> size; }

This is good, but there is a better way to do error checking: Version 1:


cout << "Enter shoe size (5 to 18): "; //initial prompt cin >> size; while (size < 5 || size > 18) // OR not AND!! { cout << "Shoe size must be in range 5 to 18; try again: "; cin >> size; } cout << "You entered " << size;

This version needs two input lines, and uses two different messages. (The second only if a data entry error has been made.) Sample script:
Enter the shoe size (5 to 18): 3 Shoe size must be in range 5 to 18; try again: 19 Shoe size must be in range 5 to 18; try again: 5 You entered 5

Note clearly the loop exit condition: you want to do the loop only if the user has entered a bad value. So you code "while the size is bad - i.e. less than 5 or greater than 15" If the initial value entered by the user is ok - say 10 - then the loop body won't execute at all because neither of the conditions is true. 10 is not less than 5; 10 is not greater than 18. So both subconditions are false, so the whole thing is false, so the loop is not executed. Don't code "while (size < 5 && size > 18)" because an invalid size would pass as valid: Suppose the user types in 20. 20 < 5 is false. 20 > 15 is true. Since both are not true the whole thing is false and so the loop body would not execute and so the invalid size would pass as valid. Another way to understand it is simply: how could ANY size be both less than 5 and at the same time greater than 18? It's impossible. So ANY shoe size would create one false condition and so the loop body would never be executed for ANY shoe size, valid or invalid. Clearly, compound conditions require careful thought. Version 2:
do { cout << "Enter the shoe size (5 - 18): "; cin >> size; // Note changes to cond!!

if (size >= 5 && size <= 18) break; } while (1); cout << "You entered " << size;

Note the new condition. Whereas before we wanted to execute the loop for an invalid value, now we want to break out of the loop when we have a valid value. This version will work. But there's only one message. So user sees:
Enter the shoe size (5 - 18): 3 Enter the shoe size (5 - 18): 19 Enter the shoe size (5 - 18): 5 You entered 5

It's not 100% clear that the first two tries were wrong. We could put an else to the if and patch it to print out an error message. But Version 1 is probably better.

Combining && and ||


Truth tables:
(true (true (false (false && && && && true) false) true) false) => => => => true false false false

So for (c1 && c2) to be true, both must be true.


(true (true (false (false || || || || true) false) true) false) => => => => true true true false

So for (c1 || c2) to be true, one or the other or both must be true. There are rules of evaluation for these operators: 1. 2. 3. 4. 5. 6. The evaluation looks at one condition at a time (i.e. cond1 op cond2) sub expressions inside () are done first && binds tighter than || (like * and / are done before + and -) otherwise, conditions are evaluated left to right as soon as the truth of the whole is known, evaluation stops but you can provide your own () to change the default rules (or to make the evaluation order clearer to the reader).

Suppose you have:

if (cond1 || cond2 && cond3) The whole thing has to be true or false. The parts are considered in pairs.
Now will C++ look at this as or as (cond1 || cond2) && cond3 cond1 || (cond2 && cond3)

Because && precedes ||, the latter. So 1. Evaluate cond1 (left-to-right rule). 2. If true, done. 3. Else (cond1 is false, so) evaluate (cond2 && cond3). If both are true, the whole condition is true; otherwise it is false. Example: We want to judge num as valid if it is between 0 and 100 or if it is 2000.
if (num == 2000 || num >= 0 && num <= 100) cout << "valid"; else cout << "not valid";

1. Suppose num is 30. Since num is NOT = 2000, we have to evaluate the second half of the condition. Since 30 is >=0 AND is <= 100, the && is satisfied and we have
if (false || true)

Since this is an OR and for an OR, the whole thing is true if either is true - the whole thing is true. 2. Suppose num is 2000. Since the first half is true, we don't have to evaluate the second half. We have:
if (true || anything)

Since this is an OR, it can be true if just one part is true, so the whole thing is true. 3. Suppose num is 150. Since num is NOT = 2000, we have to evaluate the second half of the condition. 150 >= 0 BUT 150 is NOT <= 100. So the && is not satisfied and we have:
if (false || false)

Since both halves are false, the whole thing is false.

CSCI 240 Lecture Notes - Part 4


Three important ideas so far:

program = data + instructions data is characterized by its data type which defines what operations can be performed with it and what values it can hold instructions are written as structures: sequence, repetition, decision

Next important idea:

programs can be broken up into functions, each of which does part of the job.

Functions
As a first introduction, understand that the main() function will become the "boss" function which calls on "worker" sub-functions to do pieces of the work. We will learn to use functions written by others and also how to write and use our own. Sets, or "libraries" of such functions are the main way in which the C++ programming language can be extended or expanded. For example, there are libraries of mathematical functions, libraries of graphical functions, libraries of I/O functions, and many more. These worker sub-functions are relatively small and simple (compared to the whole program, which does a larger, more complicated task than any one of its sub-functions). We can make an analogy of a building contractor: main()) who hires subcontractors (plumber, roofer, electrician - the functions) to do parts of the whole job. (The plumber may hire sub-subcontractors to do part of the plumbing job (diggers to get to the sewer, etc.). We will see that in C++ sub-functions can themselves call on other functions to do part of their work. We decompose a program into functions for several reasons:

to hide irrelevant detail at the main() level, so the the program's primary purpose is clearer to divide a complex problem into a series of simple problems to make subsequent modification of the program easier

to reduce the errors that inevitably come with a single large complex program

Note on terminology: When one function uses another, we say that it calls the other. For example, if main() uses the sqrt() function to calculate a square root, we say that main() calls sqrt() - and that sqrt() is called by main(). So the boss is the caller function and the worker is the called function.

Boss's (caller's) view of a function


The called function is a black box that does a job: 1. It may need to be given certain information to do its job (called arguments). For example, sqrt() needs to be given the number whose square root is to be found 2. It then does its job (from caller's point of view, we don't care how) 3. It may "return" a result or answer to the caller.

Arg1 => Arg2 =>

=> a result

Example: a square root function. Takes (i.e. must be given) a real number as an argument and returns its square root: num => [sqrt fn] => sqrt_of_num In main() (or the calling program), we code:
sqrt_of_num = sqrt(num);

num is the (single) argument - the info that sqrt() needs to get an answer. It is a numeric expression. sqrt is the name of the function sqrt_of_num is the name of a variable (float or double) into which the result of the function call is stored. The result is a value returned by the function. In a sense, we could also say that the function call sqrt(num) becomes a value, and that (in this example) the value is then assigned to sqrt_of_num. In this case, the result was stored in a variable. However, we can use a function result anywhere we can code an expression: e.g.:

cout << sqrt(num);

or

y = (b + sqrt(b*b - 4*a*c))/(2*a);

Notice that from the boss's or caller's point of view, we don't know or care how the function does its work. This leaves the client free to concentrate on overall program logic and structure. The sqrt() function is in a standard library. In some systems, you must
#include <math.h>

to make this library accessible to your program.

Function's view of its boss or caller


NONE. Except the arguments passed. That is, the function has no idea what the boss is doing. It cannot see or use or access the boss's plan, logic, variables, or anything. The function just does its job with the information (arguments) given to it and it may (depending on how it is written) return a single result (an answer).

Advantages:
1. 2. 3. 4. divide and conquer strategy. a function can be called from multiple places in a program. This avoids code duplication. a function can be used in other programs easily (in a library. Ex: sqrt()) makes maintenance easier, since any change (fix a bug, improve the speed) can be done in one place, not everywhere the code occurs 5. makes code more reliable, less prone to bugs - more later

Disadvantages:
1. a little more up-front planning work 2. sometimes a few more lines of code

How to Write a Function


Decide

1. name 2. what the function needs to return (if anything) including the data type of the returned result. 3. what information it needs to do its job (its arguments) 4. what local or temporary variables it needs to do its job (see below) 5. the logic of the function Example 1: we want a function to cube an integer. No error checking. 1. 2. 3. 4. 5. Name: cube Returns: an integer - the cube of a number. Arugments: one argument - the number to be cubed Local variables: an integer to hold the calculated result Logic: trivial
//See Note 1

int cube( int x ) { int result; result = x * x * x; return result; }

// See Note 2

Note 1: int - tells the data type of the value to be returned cube - the name of the function int x - tells the data type of the arg, and gives it a name to be used inside the function. This name may or may not be the name of a variable in the caller. It doesn't matter. Note 2: return value; - here you supply the value to be returned. In this case it must be an integer expression since the function has promised to return an integer. In boss or caller you could use the function cube() as follows:
int num, ans; num = 2; ans = cube(num); cout << ans; ans = cube(ans); cout << ans; ans = cube(3); cout << ans;

// 8 // 512 // 27

cout << cube(ans/3);

// 729

Note that 3 different things were passed to cube() in the first three different calls: num, ans, and 3. Inside cube(), each passed-in value - was called x. cube() does not know the names of the variables in the caller (num, and ans; and 3 is not even the name of a variable). In fact, cube() does not even get access to num, ans, or 3 or 9. It gets copies of their values. It uses the copies to do its work. Since it gets copies, it cannot change the original data passed. (Think about this - what would it mean to alter the value of 3???)

Example 2: similar to 1, but we want to return the cube of half the number passed as the argument.
int cube_half( int x ) { x = x / 2; return x * x * x; }

Here x (the "alias" for the argument passed from the caller) has been changed in the function, but understand that only the copy is changed. The original (back in the calling program) is unchanged: in caller:
num = 4; ans = cube_half(num); cout << ans << num; // displays 8 4

num is still 4. It was not altered by the division within cube_half(). Only the copy of num was halved in cube_half().

Example 3: Sometimes functions don't need to return a value. Most often that's when they just print something. Then we say they return void.

Here's a function to display a page header with page number at the top of the page. (Assume cout's are sending output to the printer here; note \t = tab and \f = newpage for the printer)
void pageHeader(int pagenum) { cout << "\f\tInventory Control System\t\tpage " << pagenum; cout << "\nDescription\t\tQuantity\t\tPrice"; }

Notes: 1. \f is formfeed = new page 2. \t is a tab 3. nothing is returned; so the return statement is not required, but it can be added with no arguments. In a void function, if there is no return statement, control returns to the calling program after the last line in the function has executed. In caller, you'd code:
pageHeader(1);

or, better, keep the current page number in an int variable, initialized to 0 and incremented before printing each page:
int pnum = 0; //these two would probably be in a loop that prints 1 page per repetition pnum++; pageHeader(pnum);

Example 4a: write a function to display two dashed lines on the screen. Like this: ------------------------------------------------------------The number of dashes is determined by the caller by supplying that number as an argument to the function.
void lines(int n) { int i; //NOTE: use of a local variable cout << "\n"; for (i = 1; i <= n; i++) cout << "-";

cout << "\n"; for (i = 1; i <= n; i++) cout << "-"; }

Notes on i:

i here is a local variable. It is used only in the function It is invisible outside the function It does not retain any value between function calls. In fact, it does not exist between calls to lines(). It only exists when lines() is executing. Other functions (including the calling function) can have their own variable named i typical calling statement would be: lines(40);

Example 4b: write a function to display n dashed lines each of which has m dashes in it. So a calling statement would be, for example,
dashes(3, 40); //make 3 lines of 40 dashes

We'll write this as a function dashes() which calls a sub-function (in a loop) oneLine() to do one line:
//display numLines lines of dashes void dashes(int numLines, int numDashes) { int i; for (i = 0; i < numLines; i++) oneLine(numDashes); }

//display one line of num dashes on a new line void oneLine(int num) { int i; cout << "\n"; for (i = 0; i < num; i++) cout << "-"; }

Study this example and be sure you understand how it works.

Notice that each function is quite simple. Notice that the local loop counter variable is called i in each function, but since functions know nothing about each other, this is not a problem. Notice that the loop that does dashes goes from 0 to < num instead of 1 to <= num as in Example 4a - but that the same number of dashes are produced, given the same argument. Notice that the name of the argument to oneLine() is not the same as its alias inside oneLine() etc. etc.

Example 5: Write a function to raise an integer number to an integer power. Assume both arguments are > 0; also assume that the result will fit in a int. Since n-to-the-i could generate a really big value, you would have to be careful that you don't exceed the maximum integer, or perhaps use a special data type with a larger range.
int n_tothe_i(int n, int i) //note 2 args passed { int ndx; int temp; //temp = n to the first power temp = n; //now mult temp by n i-1 times for (ndx = 1; ndx < i; ndx++) temp *= n; return (temp); }

("Play computer" with this to satisfy yourself that it works correctly.) Sample calling statements:
x = n_tothe_i(3, 2); y = n_tothe_i(x, 4); z = n_tothe_i(r, (i*j)); //note use of expr as arg //3 to the 2 power

Example 6: Write a function that returns the (double) average of a sum and count (passed as a double and an int) if the value in count is greater than 0. Have it return 0 if count is 0 or negative. (Yes, I know that n/0 is not defined and shouldn't be set to 0. And that 0/n is 0. This is just for illustration.)

double avg(double sum, int count) { if (count <= 0) { return 0; } else { return sum/count; } }

Note that there are 2 return statements here. The first one executed is the last statement executed in the function. It is an immediate bailout. Some people discourage the use of multiple returns in a function. They argue that in a large function, you might not see all the return statements and so you might misunderstand how the function works. In small simple functions, this is usually not a big problem. However, if you want to have just one return, you could re-write the function as follows. You'll need one local variable:
double avg(double sum, int count) { double temp; if (count <= 0) { temp = 0; } else { temp = sum/count; } return temp; }

One further requirement for using functions: You must tell the C++ compiler a bit about each function you use in your program before you use it. After the #includes (and any #defines), but before int main(), add one line for each function that you are writing and using in this source code file. These lines are called the function prototypes. Each line is of the following form: return-type function-name(arg1-type, arg2-type, ...);

The arguments need only be listed by type - you don't have to write a name here. (But you can include a name if you'd like to remind yourself of what the argument stands for.) If a function takes no arguments, you can just type a set of empty (). Note the semi-colon at the end of each line. It is required for function prototypes. Examples - prototypes for all the previous functions
int cube(int); int cube_half(int); void pageHeader(int); void lines(int); void dashes(int, int); void oneLine(int); int n_tothe_i(int, int); double avg(double, int);

Finally, how do all these pieces go together? Here's a skeleton program that uses cube()
#includes... using statement #defines... int cube(int); //**************************************************** int main() { int ans; ans = cube(5); cout << ans; return 0; } //**************************************************** int cube(int c) { int result; result = c * c * c; return result; }

Notes:

Precede every function with a //***********... to make it easier to find them, and to remind you that each function is a separate entity and cannot see anything in any other function. Code main() first - all of it, including its opening "{" and its closing "}" - and then any other functions you write, in any order you feel is useful. Make the order of the prototype statements match the order of the actual functions in the listing - just to make them easier to find..

CSCI 240 Lecture Notes - Part 4B -- Function Summary Sheet


Functions are chunks of code designed to do a particular task. They are called from other functions (like main()) to perform the task. The calling function often passes information used by the function to accomplish its task (via arguments). You may need to define additional variables (local variables) inside the function to accomplish its task. Local variables are invisible outside the function. They do not retain their values between calls to the function (actually, there is a way to make this happen, but we do not cover it here). The function often returns an answer to the caller. In a sense, the function "becomes" the value returned. When you design a function, from the caller's point of view, decide:

the task of the function the name of the function the return data type of the function the information needed by the function (arguments)

From the point of view of writing the function, decide:


how to accomplish the task, given the arguments provided what local variables may be needed

You must make the following true of the function you write and use:

the data type of the value returned matches the function's declared return type the arguments supplied in a function call match those specified in the function's header in terms of number, order, and data type the arguments supplied in a function call have defined values before the call is made

Consider the following function:


int cube(int x) { return x*x*x; }

It returns an int Its name is cube It takes one int argument, the number to be cubed The name of the argument inside the function is x. You can use it as if it were a declared and initialized variable in the body of the function The value returned is a int Calling statements for this function might be:
n = cube(3); n = cube(y); //pass a literal //pass a variable

n = cube(i*j); //pass an arith. expr. cout << cube(k);

When a function is called, copies are made of the supplied arguments, and the flow of control is transferred to the function, which then can use the arguments as if they were local variables. Any changes to an argument are made to the copy, not to the original in the calling program. The code in the function executes until a return statement is encountered or control passes out of the end of the function. If a function is declared to return a value, it must execute a return statement with a value of the declared return type. The value supplied after the return statement is the value returned. Control then returns to the calling program. A function may have more than 1 return statement, but only one is executed per function execution (the first one executed). A function may have no return statement if it does not return a value (i.e. declared as void). Remember, too:

main() is a function. It's always the first one called when the program starts. No function can see anything about anything (including variables) in any other function. All it knows from the "outside world" is the values of the arguments passed to it.

CSCI 240 Lecture Notes - Part 5 - Notes on Allegro C++ Graphics


Graphics functionality is not a part of the C++ language. However, graphics functionality can be provided by writing a library of functions which access the computer's graphics hardware. These libraries of functions can be provided by a compiler vendor such as Borland or Microsoft and included in a commercial product, or can be written by knowledgeable individuals and sold or provided free as software that can be added to your development system. In this course, we will use a (free) software graphics package called Allegro. It has been installed on the CSCI lab systems, and is provided as part of the 240 Course CD so you can install it on your personal computer. Since the graphics functions in Allegro are not part of C++, programs written using it will not run on computers that do not have certain Allegro files installed. Other graphics libraries will likely have similar functions, but their names and arguments will be different in some or most details.

GRAPHICS VS. TEXT


Text

Text resolution is 80 x 25 (usually): each cell can contain one of 255 possible characters Usually output to screen is sequential by column and row; top/left to bottom/right. You can skip to next line by writing \n.

Graphics

graphics commands only work in "graphics mode" (see below) graphics resolution can vary according to the computer type and graphics card. A standard resolution is 640 by 480 (usually; this is known as standard VGA); resolutions of up to 1280 x 800 are common. Each cell is a single dot or pixel and can be one of a number of colors (16 colors up to millions of colors are possible in most systems). graphics output can be done at any one of the dot locations. there are also special graphics functions for text output in graphics mode.

Graphics Mode Programs using Quincy

See the document entitled Creating a Graphics Program in Quincy for details on how to create a graphics mode program in Quincy. The remainder of this document will cover concepts of graphics programming and a set of graphics functions you can use in your programs. Note that one or two functions presented in the Creating a Graphics... document are used in the examples below.

SCREEN COORDINATE SYSTEM (standard VGA)


Upper left corner is (0, 0) Upper right corner is (639, 0) Lower left corner is (0, 479) Lower right corner is (639, 479) VGA can support 16, 256, or up to 16 million colors per pixel, depending on the hardware. Other screen resolutions are possible (800 x 600, etc.) In Allegro, you can set the screen resolution to one of several allowed values. The grInit() function presented in the Creating a Graphics... document will set the screen to 640 x 480. (See the set_gfx_mode() function call in grInit().) After calling grInit(), you can find the current screen resolution (width and height are ints) by using the Allegro values SCREEN_W and SCREEN_H which act like symbolic constants in your program. Even though you may know the screen resolution, it is good practice to use these constants in positioning graphics output whenever possible. This way, if you change the program to use a different resolution, you can keep the relative position of various graphics elements (for example, we will use these to center certain graphics elements). For example, suppose you want to position a dot in the middle of the screen, for any resolution. The function to use is putpixel (screen, x, y, color); where

"screen" is the Allegro predefined variable representing the visible graphics screen x is the x-coordinate of the dot (int) y is the y-coordinate of the dot (int) color is an int representing a color. By default, there will be 256 colors (8 bits per pixel).

So, after grInit() call:


putpixel( screen, SCREEN_W/2, SCREEN_H/2, 50 );

You can use other numbers 0 .. 255 for the color.

SKELETON GRAPHICS PROGRAM


#include "allegro.h" int grInit(void); void wait_for_keypress(); int main() { int rc; //Initialize graphics mode - see Creating a Graphics... document //If grInit fails, it returns 1. So we just quit the program. no point in continuing. rc = grInit(); if (rc == 1) { return 0; } //Allegro draw-a-dot function putpixel( screen, SCREEN_W/2, SCREEN_H/2, 50 ); //Freeze the display until user hits a key. This also releases the graphics //screen. See Creating a Graphics... document. wait_for_keypress(); return 0; } END_OF_MAIN();

// <-- ABSOLUTELY REQUIRED EXACTLY HERE

//You must include the code for grInit() and wait_for_keypress() here.

GRAPHICS TEXT DISPLAY

There are facilities for text output in graphics mode. We will use one simple predefined font provided by Allegro. Text can be output at any screen location.
textout( screen, font, msg, x, y, color ); color //displays msg at x, y in

where

"screen" is the Allegro predefined variable representing the visible graphics screen "font" is the Allegro predefined variable representing the default font msg is your string variable (or a string constant) representing the text to display x and y are the coordinates of the upper left corner of the bounding rectangle for the text. color is the color of the text

For example:
textout(screen, font, "Hello", 10, 20, 50);

Will display the word "Hello" in light green at screen coordinates 10, 20. To find the width of a string in pixels (not characters), use:
w = text_length( font, msg );

Where

"font" is the Allegro predefined variable representing the default font msg is your string variable (or a string constant) representing the text

This function returns the width of msg in pixels To find the height of a string in pixels, use:
h = text_height( font );

Where

"font" is the Allegro predefined variable representing the default font no msg is required: the height is the height of the font as a whole.

This function returns the font height in pixels So to display centered text:
textout( screen, font, msg, SCREEN_W/2 - text_length(font, msg)/2, 100, 50);

Where

"screen" is the Allegro predefined variable representing the visible graphics screen "font" is the Allegro predefined variable representing the default font SCREEN_W/2 = midscreen text_length(font, msg)/2 = half the width of the text 100 is the y-coordinate for the text 50 represents the (light-green) color

But actually, Allegro provides a self-centering function:


textout_centre( screen, font, msg, x, y, color );

Where x is the center of the string, so the center goes at x, y. So you could just code:
textout_centre( screen, font, msg, SCREEN_W/2, 100, 50 );

Note the British spelling of centre. Allegro allows the creation and use of different fonts, but these topics will not be covered here.

GEOMETRIC OBJECTS
Lines:
line( screen, x1, y1, x2, y2, color );

where

"screen" is the Allegro predefined variable representing the visible graphics screen x1 = upper left endpoint x coordinate y1 = upper left endpoint y coordinate x2 = lower right endpoint x coordinate y2 =lower right endpoint y coordinate color is the integer controlling the line color

To do a big "X" on the screen:


line(screen, 0, 0, SCREEN_W, SCREEN_H, 50); line(screen, 0, SCREEN_H, SCREEN_W, 0, 50);

Circles:
circle( screen, x, y, radius, color );

Where x and y are the coordinates of the center of the circle; the other arguments should be clear by now.

To center a circle on the screen:


circle( screen, SCREEN_W/2, SCREEN_H/2, SCREEN_H/2, 50 );

Rectangles:
rect( screen, x1, y1, x2, y2, color );

Where x1, y1 and x2, y2 are the coordinates of the upper left and lower right corners To draw a series of nested rectangles:
for( i = 10; i < SCREEN_H/2; i += 10 ) { rect(screen, SCREEN_W/2 - i, SCREEN_H/2 - i, SCREEN_W/2 + i, SCREEN_H/2 + i, 100); }

FILLING A REGION
The floodfill() function will fill a bounded region of the screen with a solid color. To do the floodfill(), you need to supply the coordinates of any point inside the region and the color you want to use in the fill. floodfill() will change all the background color pixels to the color you specify until it comes to pixels of a different color. Note that if your region's boundary has even a tiny "hole" in it - where the hole is the background color - the flooding will spill out of the region and fill a large part - or all - of the screen.
floodfill( screen, x, y, color );

Where

"screen" is as usual x and y are the place to start filling from color is the fill color

Problem: to draw three rectangles of width = Rwd and height = Rht across the middle of the screen, equally spaced, margins = 1/2 of the spacing, and flood fill each of them.
w = SCREEN_W; h = SCREEN_H; //y coord of rect tops and y coord of rect bottoms

top = h/2 - Rht/2; bot = h/2 + Rht/2; //size of space between rects space = (w - 3*Rwd)/3; //draw the three rectangles in green rect( screen, space/2, top, space/2 + Rwd, bot, 50 ); rect( screen, 1.5*space + Rwd, top, 1.5*space + 2*Rwd, bot, 50 ); rect( screen, 2.5*space + 2*Rwd, top, 2.5*space + 3*Rwd, bot, 50); //calc pt at 1,1 offset from top left of each rect; floodfill( screen, space/2 + 1, top + 1, 100 ); floodfill( screen, 1.5*space + Rwd +1, top + 1, 150 ); floodfill( screen, 2.5*space + 2*Rwd + 1, top + 1, 200 );

CSCI 240 Lecture Notes - Part 6


More on the char data type
We have seen that C++ has a built-in data type to represent single characters. It is one of the fundamental data types of the language. Here's a surprise - internally, a character is represented by a numeric code in the range of 0 to 255. (32 to 127 are standard; some of 0 to 31 and all of 128 to 255 depend on the computer you use). This is why char is a scalar - there are only 256 possible values. Since each char is associated with an integer value, in C++ chars can be treated as integers. This is unusual since usually C++ does not permit us to mix data types. We will see some examples of this below. To review: The data type name is char. To declare a char variable:
char ch;

A char literal is written with single quotes around it: 'a' cout knows how to display char data:

char ch = 'a'; cout << ch;

Some special cases: A backslash char is written: '\\' - single quote, backslash, backslash, single quote A single quote char is written as: '\'' - single quote, backslash, single quote, single quote A double quote char is written as: '\"' - single quote, backslash, double quote, single quote The common "escape sequence" characters (newline, tab, etc.) are also written with single quotes, but are still typed with the leading \: '\n'
ch = '\n'; //stores newline char in ch

Other chars with no keyboard representation are written in base 8 using three digits: '\007'. This is not covered further in this course.

The ASCII Character Set


The American Standard Code for Information Interchange (ASCII) is a standard mapping of integer values to characters, used by most computer systems (exception - IBM mainframes). Some useful ones to know (i.e. memorize these for this course)
blank = 32 digit '0' = 48 digit '9' = 57 'A' = 65 'Z' = 90 'a' = 97 'z' = 122

Note: '1' is 49, '2' is 50, etc. so if you know '0' you know all the digits. 'B' is 66, 'C' is 67, 'b' is 98, 'c' is 99, etc. so you can figure out any letter, too. Since chars are really small ints (internally) we can do some useful tricks with them by treating them as integers.
char ch = 'A'; int i; //print Ascii value of ch (65). Note the typecast cout << (int) ch;

// print ASCII values of A..Z for (i = 'A'; i <= 'Z'; i++) { cout << "\nThe ASCII value of " << (char) i << " is " << i; }

will print:
The ASCII value of A is 65 The ASCII value of B is 66 The ASCII value of C is 67 ...

Character Functions
There is a large set of standard C++ functions that deal exclusively with characters. They can be found in the <ctype.h>, which is automatically included in most IDEs, but if it's not, just add a #include <ctype.h> at the top of the source code file. One subset of the standard C++ character functions test whether a character *IS* of a certain type: capital, lower case, digit, punctuation, whitespace, etc. For example, if you want to test if a character entered by the user is a digit, you can call
if ( isdigit(ch) ) //ch is a char variable { cout << ch << " is a digit"; } else { cout << ch << " is NOT a digit"; }

Some of the other "is" functions: Function isdigit isalpha isupper islower Purpose This function tests if the passed in character is a digit. It returns true if the character is a digit and false otherwise. This function tests if the passed in character is alphabetic. It returns true if the character is a alphabetic and false otherwise. This function tests if the passed in character is an uppercase alphabetic character. It returns true if the character is an uppercase character and false otherwise. This function tests if the passed in character is a lowercase alphabetic character. It returns true if the character is a lowercase character and false otherwise.

isalnum isspace ispunct

This function tests if the passed in character is an alphanumeric character. It returns true if the character is alphanumeric and false otherwise. This function tests if the passed in character is a whitespace character (space, tab, newline, etc...). It returns true if the character is a whitespace character and false otherwise. This function tests if the passed in character is a punctuation character. It returns true if the character is punctuation and false otherwise.

Another subset of the character functions changes a character. Two of those functions change an alphabetic character to the opposite case. They are:
toupper tolower

The toupper function takes a character as its argument and returns a character. If the passed-in character is a lowercase alphabetic character, then the uppercase version will be returned. If the passed-in character is not a lowercase alphabetic character, it is returned unchanged. The tolower function takes a character as its argument and returns a character. If the passed-in character is an uppercase alphabetic character, then the lowercase version will be returned. If the passed-in character is not an uppercase alphabetic character, it is returned unchanged. Suppose you have a single char (not a string) from the user. If it is 'q' or 'Q', that signals quit. Before we had to code something like:
while (ch != 'q' && ch != 'Q') { // do something }

Now we could do this:


while (toupper(ch) != 'Q') { // do something }

In other words, ch is converted to uppercase and the result of that is compared to 'Q'. Notice, too, that we can compare chars with !=, = =, <=, etc. just like numbers (since they are small ints).

CSCI 240 Lecture Notes - Part 7

Arrays - Part 1
Up to now, we have used simple data types (int, char, etc.) except for strings. Now we look at the first data structure. A data structure is a compound data type; that is, it is a single entity made up of numerous parts. It's kind of like a numbered list of "things" where every "thing" must be of the same data type all ints or all chars, etc... Arrays can help solve problems that would otherwise be quite difficult and can sometimes make solutions clearer. As an example of a problem that is difficult without arrays, consider the following: Problem: read n values and calculate an average. For each number, print whether it is above, below, or equal to the average. The first part of the problem is easy to solve. Values are read from the user and then the average is calculated. The second part isn't so easy because in the process of reading the values from the user we "lose" the previous values and therefore don't have anything to compare with the average except for the last value entered.

Solution 1
If we know there are exactly n numbers, we can declare nvariables to hold all of the values. So if there are 3 numbers:
int n1, n2, n3; double avg; cout << "Enter the first number: "; cin >> n1; cout << "Enter the second number: "; cin >> n2; cout << "Enter the third number: "; cin >> n3; avg = (n1 + n2 + n3) / 3.0; if (n1 > avg) { cout << "n1 is above"; } else if (n1 < avg) { cout << "n1 is below"; } else

{ cout << "n1 is equal to avg"; } //same pattern of code repeated for n2 and n3

Problem: what if there are 50 or 500 or 5000 numbers? This solution, awkward when n = 3, is terrible when n get big.

Solution 2
Read the numbers twice. That is: read in all the numbers and calculate average, read in all the numbers again, this time checking each as it is read against the previously calculated average. If input is from keyboard, then the user has to enter each number twice. Accurately, with no mistakes. If input is from an explicitly opened file (which we will be able to do later), it will work, but is inefficient because the file has to be read twice. Note: this will not work with I/O redirection - we can't re-open the file and read from the beginning again.

Solution 3:
Use arrays to store all the values as they are read and thus "remember" them. First some information on how to create and manipulate arrays. An array is a data structure consisting of an ordered set of data values of the same type. Think of it as a numbered list, but all items in the list must be the same kind of thing (the same data type). When defining an array in a program, we have to specify three things:

what kind of data it can hold (ints, chars, doubles, strings, etc.) a name how many values it can hold (i.e. the maximum number it can hold)

The generic format for declaring an array:


data_type array_name[ # of items to hold ];

So, to declare an array that can hold up to 10 integers:


int numArray[10];

This reserves space for 10 integers. It does NOT place any values into the array.

Each position in the array is called an array element. The elements in this array are numbered from 0 to 9. (Not 1 to 10.) Notice that when you declare an array, you specify how many items it can hold. And that these elements are numbered starting at 0 and ending at one less than the number of elements. This array holds 10 integers, which are numbered from 0 to 9. You must use a literal number or a previously declared const int or #defined constant in an array declaration. No regular variables. In our code when we need to refer to a particular element in an array, we use subscript notation: [n] n can be:

an integer literal: [3] an integer variable: [n] an integer expression: [n+1] any scalar expression: ['A'] (= to [65] //ASCII value of 'A') any function that returns an integer value

NOTE: that in this example, the ['A'] example refers to a non-existent array element - since 'A' is 65 and the array only has room for elements up to [9]. This would almost certainly result in a program bug. If we had declared numArray to hold 100 values, it would be perfectly fine). Note that [n] is never used by itself. It is used to specify which array element to refer to, but we also need to specify which array: So to refer to the value of a particular array element, we write:
array_name[ arrayelement # ]

So, for example, to assign values to (i.e. store values into) an array:
numArray[0] numArray[1] numArray[2] ... numArray[9] = 1; = 3; = 5; = 19;

Or:
for (i = 0; i < 10; i++) { numArray[i] = (i*2) + 1; }

Notice how the variable i, used as a subscript, runs from 0 to "less than" 10, i.e. from 0 to 9. To get values from an array, the same notation is used:
cout << numArray[2]; thirdOdd = numArray[2]; nthOddSquared = numArray[n-1] * numArray[n-1];

Example: To add up the 1st ten elements in an array:


sum = 0; for (i = 0; i < 10; i++) { sum += numArray[i]; }

Remember:

[n] specifies which position you are referring to numArray[n] evaluates to the value stored in the array at position n elements are numbered starting at 0 when you declare an array to hold n elements, its subscripts go from 0 to n-1

Now we can solve the problem posed earlier. Assume that a negative number is the end-of-data signal.

Solution 3 code
#include <iostream> #include <iomanip> using namespace std; const int ARSIZE = 100; int main() { int numArray[ARSIZE]; int numberElements = 0, i, num, sum = 0; double avg; //read numbers from the user and put them into the array cout << "Enter an integer number (negative to quit): ";

//Note use of constant //number of elements in the array //subscript for processing the array //number from the user //sum of the numbers

cin >> num; while ( num >= 0 ) { numArray[numberElements] = num; numberElements++; cout << "Enter an integer number (negative to quit): "; cin >> num; } //Add up elements. Note that numberElements is the total number of values that //were entered by the user before the negative number for ( i = 0; i < numberElements; i++ ) { sum += numArray[i]; } //Calculate the average of the numbers avg = (double) sum / numberElements; //Now go through array and print whether the number is < > = the average for ( i = 0; i < numberElements; i++ ) { if ( numArray[i] < avg ) { cout << numArray[i] << " is less than the average of " << avg << endl; } else if ( numArray[i] > avg ) { cout << numArray[i] << " is greater than the average of " << avg << endl; } else { cout << numArray[i] << " is equal to the average of " << avg << endl; } } return 0; }

More Notes on arrays:


Arrays can be initialized when they are declared:

int ar[5] = { 1, 3, 5, 7, 9 }; int ar2[5] = {2, 4, 6}; int ar3[] = {1, 4, 9, 16}; positions 0..3 char vowels[] = {'a', 'e', 'i', 'o', 'u'};

//exactly enough values //the rest are 0 //allocates 4 elements at //5 elements

double sqrts[4] = {1.0, 1.414, 1.732, 2.0};

Executable program statements, such as ones that assign computed values, or data read from the keyboard or a disk file can also be used to initialize an array. Just because an array was declared to hold a specific number of values, the entire array does not have to be used. It's typical to declare an array to hold more values than are expected and have the program keep track of how many array elements are actually used as values are put into the array. This is what happened in Solution 3. Sometimes it is more natural to start at 1, so you could just not use ar[0]:
int monthlenAr[13] = {0, 31, 28, 31, ...};

Then for January, use monthlenAr[1], for February use [2], etc.

Other Misc. Operations


To increment the ith element:
ar[i]++; ar[i] += 1; ar[i] = ar[i] + 1;

To add n to the ith element:


ar[i] += n; ar[i] = ar[i] + n;

To copy the contents of the ith element to the kth element:


ar[k] = ar[i];

To exchange the values in ar[i] and ar[j]: First, understand that you must declare a "temporary" variable to hold one value, and that it should be the same data type as the array elements being swapped:

int temp; temp = ar[i]; ar[i] = ar[j]; ar[j] = temp; //save a copy of value in i //copy value from j to i //copy saved value from i to j

Sample Exercises:
1. Given a character in variable ch, determine if it is a vowel, i.e. if ch matches one of the values in:
char vowels[] = {'a', 'e', 'i' 'o', 'u'};

2. An array of 10 elements called AR exists. Find and print the smallest element in it. 3. A Fibonnacci sequence consists of a series of integers in which each is equal to the sum of the of the previous two numbers. The first two values are 1 and 1. So the series is: 1, 1, 2, 3, 5, 8, 13 ... Write a program to store the first 50 Fibonnaci numbers in an array using executable statements (ie. don't initialize the array when it's declared). Then fill a second array with the squares of these numbers. 4. Write a program to read numbers from the keyboard until the user enters a negative number. Once all of the numbers have been entered, print them out in reverse order.

CSCI 240 Lecture Notes - Part 8


Memory Organization
All variables are stored in memory (often called RAM - random access memory). Memory is measured using different units. The most common are:

bits - can hold a 0 or a 1 - nothing more. A 2 is too big. bytes - can hold 8 bits, or a decimal value from 0 to 255 words - depends on the system. Usually 2 or 4 bytes. 2 bytes can hold a value form 0 up to about 65,000. 4 bytes can hold a value up to about 4 billion.

Note: in the C++ compiler that Quincy uses, the data type int occupies 4 bytes. But since its values can be positive or negative, its values range from about -2 billion to about +2 billion because the leading bit is not part of the number, but rather the sign.

An "unsigned int" can also be declared. It has a range from 0 to about 4 billion (no sign bit always positive). There are also a "long int" or an "unsigned long int" which may have still different sizes depending on systems. These are official C++ data types. sizeof (see below) can be used to get the sizes of the different data types, which means that it is then possible to calculate the ranges of values that can be held. The uncertainty of the size of various data types in C and C++ is a problem. It was never defined as part of the language and it's too late to change now. More modern languages such as Java define the size and therefore the range of numeric data types so there is no uncertainty. Usually memory is "addressed" in terms of bytes. Memory addresses range from 0 to the maximum amount on the computer. We rarely have to know the actual address of a variable, but we do need to understand the idea of addresses in memory and the fact that variables take up a certain amount of space in memory. Size of different data types (Quincy C++):

char = 1 byte int = 4 bytes float = 4 bytes double = 8 bytes string = depends on number of chars

To find the size of a data type or a variable if you don't know it, use the sizeof operator:
sizeof (int); sizeof x; //an expression that evaluates to 4 //evaluates to amount of memory x takes up

Note: sizeof looks a bit like a function, but it's not, really. So to summarize: each variable has an address (which we usually don't know or care) and a size.

Arrays and Functions


First, understand that for any array, the array starts at some given address (say 1000). Each element in the array comes after the preceding one in memory, one after another.

A Surprise!

When an array is passed to a function, the function can change values in the array. This is unlike anything we have done before when passing arguments to a function. Before, values were passed as a copy. An array is passed to a function by passing its name.
//prototype: note the notation for an array of int: int[] void incrAr(int[]); //***************************************** int main() { int i; int ar[4] = {1, 2, 3, 4}; incrAr(ar); for (i = 0; i < 4; i++) { cout << setw(3) << ar[i]; } return 0; } //***************************************** void incrAr(int A[]) { int i; for (i = 0; i < 4; i++) { A[i]++; // this alters values in ar in main() } }

// 2, 3, 4, 5

So - why does passing an array to a function allow the function to alter the array? What is different about passing an array and passing a simple variable such as an int or double or float?

The unsubscripted name of an array is the address of the array.


So passing the name of the array to the function is passing the location in memory where the array was created and therefore the function "knows" where to get and place values.

In the example above, when we called incrAr(ar); we were giving the address of the array ar to the function. So any change the function makes to the array is to the (original) array in the calling function, not to a copy of the array. Since the function has the address of the array, it can operate directly on it - it "knows where it is". So, to summarize, the rules for passing arrays to a function are different than the rules for passing simple data types.

Rules for passing arrays:


1. The function prototype. Declare an array argument as "data-type []"
int doSomething( double [] );

Note: a number can be included in the [], but usually the effective length of the array will be passed as a 2nd argument. (Discussed later under #4.) 2. In the function header itself, same as above, but also include a name for the array argument
int doSomething( double anArray[] )

3. Inside the function, use the subscripted local name as given in the argument list:
int doSomething( double anArray[] ) { double j; j = anArray[3]; anArray[5] = 22.5; return j*2; }

Note: as explained above, any assignment to an array element will change the actual value in the array passed by the calling program. 4. As mentioned above, it is typical (there are exceptions) to pass a second argument to a function that processes an array, to tell it how many elements there are to be processed. This is sometimes referred to as the "effective size" of the array. For example, suppose you want to call a function to load values into an array. When you call this function, you don't know how many values there will be. So you design the function to pass the array and return the number of values loaded.
int loadAr( int[] ); // note the return type

Then later, let's say you want to print the array. You design a function to pass the array and the number of array elements you want to print (i.e. the value returned from loadAr()).
void printAr( int[], int )

returns void - since there's no "answer" to report back. 1st arg is the array 2nd arg is the number of elements to process. This was returned by loadAr() and (presumably) stored into a variable so it could be passed to printAr().
int numArray[100]; int arSize; arSize = loadAr( numArray ); printAr( numArray, arSize ); .... void printAr( int ar[], int size ) { int i; for( i = 0; i < size; i++ ) { cout << ar[i] << endl; } }

It's important to recognize that if a single array element is passed to a function, it is passed by value (i.e. a copy is passed) since an element of an array is a simple data item. So if a function is called:
myFn( ar[i] );

Then myFn() cannot alter ar[i] no matter what it does, since ar[i] is a simple integer (or whatever type ar was declared as). And, since ar[i] is an integer, the argument in the function prototype for myFn() would need to be int (not int[]):
void myFn(int);

To reiterate: It is good to recall that unlike arrays, when a simple argument is passed, a copy of the value of the argument is passed. Code in a function can't directly alter the original value of its

argument (in the calling function) since it has no idea where it is. It has a copy. It does not have the address. Examples: Function cannot alter original value of the argument passed:
void fn( int ); int main() { int num = 5; cout << num; fn(num); cout << num; return 0; } void fn(int i) { cout << i; // 5 i = i + 1; cout << i; } // copy is now 6 // still 5 // 5

Notice that a function can be used to indirectly alter a value. If a function is written so it returns a value:
int fn(int i) { cout << i; // 5 i = i + 1; cout << i; return (i); } // now 6

and (for example) main() calls it like this and assigns the return value to a variable:
cout << num; num = fn(num); cout << num; //5 //num altered by assignment //now 6

The variable num is altered. But understand clearly that the function didn't alter it - main() altered it by assigning a value to it.

Sample Exercises:
1. Suppose you have declared two arrays, A and B in a program. Array A already has values in it, and the number of values is stored in N. Write a function to copy the N values in A to B. Write the calling statement also. 2. Do the same, but store the values from A into B in reverse order. 3. Write a function to add up and return the sum of all the numbers in an array of int up to but not including the first 0 value. (Assume there is a 0 value in the array.)

Sorting an Array of Values: Selection Sort


Given an array of variables and a way of comparing them (which we can do with char, int, double, and float, and later with strings), it is often useful to be able to sort them in the array. That is, no matter what order they were entered, we can sort them so they end up in numeric (or alphabetical) order. There are many ways (algorithms) to do this. Some are simple and some are complex. Some are slow and some are fast. We will look at one way that is fairly simple and fairly fast, called selection sort. Imagine you have an list of numbers:
8 4 2 5 3 7 6 5 9

Look through the whole list and find the smallest. (the 2) Then exchange the smallest with the first item on the list and put a mark after the first number:
2 | 4 8 5 3 7 6 5 9

Note the | which denotes that everything to the left is (a) already sorted and (b) is smaller than any value on the right. Now look through the right-hand part of the list (to the right of the |) and find the smallest in that part. (the 3) Exchange it with the first number to the right of the | and move the mark to after the 2nd number

Repeat this:
2 3 4 4 4 4 | 5 5 5 5 | 5 5 8 8 | 6 7 7 7 | 6 6 6 7 5 5 8 8 9 9 9 9 // either 5; doesn't

2 3 matter 2 2 3 3

Now you can see you're done, but if you blindly continue, the results will be the same. We will now turn this idea into a flowchart to represent computer code, with certain subfunctions to help control the complexity. (In lecture)

Two-Dimensional Arrays
So far we have used one-dimensional arrays. In C++, you can create multi-dimensional arrays. A 2-dimensional array can be imagined as a grid of cells or elements. To access or specify one of the elements, you need two subscripts - a row number and a column number, if you like. In memory, the array elements are laid out in a linear manner (since memory is addressed linearly). So everything in the first "row" will come before everything in the second "row" and so on. Declaration: data_type array_name[ # of rows ][ # of columns ];
int twoDArray[10][5];

this reserves memory for 50 elements. Let's initialize it to contain some multiplication facts.
int row, col; for (row = 0; row < 10; row++) { for (col = 0; col < 5; col++) { twoDArray[row][col] = row*col; } }

We would then have:

0 0 0 0

0 1 2 3

0 2 4 6

0 0 3 4 6 8 9 12

<- row 0 ... ... ...

etc.

Understand that these are arranged in memory as just a linear string of values from a beginning address to an ending address. All of each row is stored before the next. So if you were to look in memory, you would see:
0 0 0 0 0 0 1 2 3 4 0 2 4 6 8 0 3 6 9 12

2D arrays are passed to functions much like single-dimensional arrays are passed. The main difference is that you have to declare the number of elements in a row explicitly. That is, you have to tell C++ how many columns are in a row. Otherwise C++ would not know where one row ends and the next begins - remember all you pass is the address of the beginning of the array. So where would array[2][2] be? It would depend on how many elements (columns) are in a row. So you'd declare the above array - passed as an argument - as follows:
void aFn(int ar[][5]) { code to process the array goes here }

Note that the first dimension can be left empty. Alternately, you can supply it as well: (int ar[10][5]) in the function header above. You will study more about multidimensional arrays in later courses.

CSCI 240 Lecture Notes - Part 9


We have seen that when passing simple variables to a function, the function cannot change those variables. We have also seen that when passing arrays to a function, the function can change the values in the array - in part because the thing passed is the address of the array. It turns out that in C++ a function can change the values of simple variables passed to it. We have not told you the whole truth - yet. The whole truth is that in C++ there are two slightly different ways to arrange this.

When you (the programmer) design a new function, you are in control of when and how you will do this and when you won't. When you use an existing function, someone else has already decided. So you must understand both ways to properly supply the arguments if you want to use other people's functions. Why would you want to have a function change the value of an argument passed to it? Suppose the purpose of the function was to calculate two or more answers (results). With the pass-by-value mechanism we have used up to now, you can't change even one simple variable, and you can return at most one value. What if you want two or three answers? As a simple example, suppose you want to write a function that calculates both the quotient and the remainder of an integer division. We would like to be able to call it something like this:
divide( dividend, divisor, quotient, remainder );

and have the answers stored into quotient and remainder by the function. We can do that! One way is by using call-by-reference, the other is call-by-address. We will cover only call-by-reference in this course. It is new in C++ and is a bit easier. Call-byaddress is used in C and is also supported in C++. You should know both (sorry!) because both are used in C++ programming.

Call-by-Reference
To pass an argument to a function by reference so that the function can change it, you must

declare a variable of the proper type in the calling program (to hold the value value that will be passed and changed) use an & in the function prototype and header after the data type of the argument alter the argument (using its local name in the function) directly

Here is an example: Caller:


//prototype void fn( int&); //variable declaration int num;

//function call fn(num);

Function:
void fn( int &i ) { //actually stores 10 into num back in the caller i = 10; }

Notice that if you look at the function call itself, you can't tell if it is pass-by-value or pass-byreference. Only by looking at the function prototype or header can you tell that the argument is passed by reference so it can be altered. Likewise, if you look in the function body you can't tell. The only way to tell is to look at the function prototype or header. That's what the C++ compiler looks at. When it sees that & it will (quietly) arrange to pass the address of the variable to the function, so the function can access the original. So pass-by-reference is really passing an address, but it's done automatically when you use the &. Here is the divide() function and calling code mentioned above: Caller:
//prototype void divide( int, int, int&, int& ); //declarations int num1 = 20, num2 = 6, quo, rem; //function call divide( num1, num2, quo, rem); //now quo has a value of 3 and rem has a value of 2

Function:
void divide( int dividend, int divisor, int& q, int& r ) { q = dividend / divisor; r = dividend % divisor; }

Notice that we passed the divisor and dividend by value since we just wanted to use their values and not alter them. We passed the quotient and remainder by reference since the purpose of the function is to alter both of these values. Notice, too, that quotient and remainder were not initialized by the caller since their values are never actually used in the function, they are just assigned values. But the divisor and dividend were initialized since their values were used to compute the answers. For example, remember swap() as explained in lecture? It could be written as a function as follows:
void swap( double a[], int top, int small ) { double temp; temp = a[top]; a[top] = a[small]; a[small] = temp; }

In this case we had to pass the array and two integers to serve as the subscripts of the elements we wanted to swap, because the only way to use a function to change a part of the array was to pass the whole thing. We would call it as follows:
swap( ar, top, s );

Now, however, we could write it using reference arguments:


void rSwap( double & val1, double & val2) { double temp; temp = val1; val1 = val2; val2 = temp; }

And it would be called by code like this:


rSwap( ar[top], ar[s] );

In fact, any two double in memory could be swapped using this function now, not just doubles in a particular array.
double first = 4.7; double second = 6.8;

rSwap(first, second);

Now first has 6.8 and second has 4.7

More Examples of Call By Reference


Example 1: suppose we have a program that prints (on paper) a multi-page report. We want a function to print a title or header at the top of each new page, complete with page number. We will declare a page number variable in main(), but would like the function to increment it automatically each time it is called.
void pgHeader(int&); int main() { int pageNum = 1; while (...) { // more code ... if (time to print a pg hdr) pgHeader(pageNum); } return 0; } void pgHeader( int& pnum ) { cout << "The Title " << pnum; //increment pageNum in main() pnum++; }

Note: we could alternately have designed pgHeader() to return an incremented page number (the old pass-by-value way). For comparison, here it is:
int pgHeader(int); int main() { int pageNum = 1;

while () { // code to do something useful if (it's time to print a page header) pageNum = pgHeader(pageNum); } return 0; } int pgHeader(int pnum) { cout << "The Title " << pnum++;

pnum;

// increment copy

return (pnum); // new value of page number }

Example 2: suppose we have a sum and a count, and want to write a function to calculate an average. However, sometimes the count will be zero, so we also want the function to tell us if it was able to successfully accomplish its task. We could design the function to return a value indicating success or failure, and to pass back the calculated average (when count != 0). We will return 0 to indicate failure and 1 to indicate success.
int calcAvg( int sum, int count, double &avg ) { if (count == 0) return (0); else { avg = (double)sum / count; return (1); } }

And the calling function:


int total, count, retCode; double avg; //get values into total and count retCode = calcAvg(total, count, avg); if ( retCode == 0 ) cout << "no average calc'd"; else

cout << "Average is " << avg;

Here, the function is returning one value (the return code) and passing back one value - the calculated average, when the calculation is performed, i.e. when count is not 0. If the count is 0, the avg argument is not altered. Study these examples until they all seem clear to you. Notice the patterns that are common to each function call. This may take some time. You will probably find it helpful to come back to them several times. Each time, you will be able to understand them more quickly. After you have done this, try writing your own similar functions.

Sample Exercises
Here are a few sample exercises to try. 1. Write a function that takes an array of integers and passes back the highest and lowest value in the array. 2. Write a function that takes an array of doubles and passes back the sum and the average of the numbers in the array. 3. Write a function that takes an integer number and passes back the sum of the integers up to that number. For example, passing in 5 would pass back 1 + 2 + 3 + 4 + 5 = 15

Local Reference Variables


We will not have much use for this topic in this course, but you should be aware of it: it is possible to declare a special variable type that can hold a reference to another simple variable:
int a = 4; int& b = a; b = 5;

What does this mean? Line 1: declare a simple int variable a, and give it a value of 4. Line 2: declare a reference variable, b, and make it "refer to" an int variable, a. The int & denotes a new data type, one that can hold a reference to an int. In a sense, b is an alias for a. Line 3: when we assign 5 to b, we really are assigning 5 to what b refers to, which is a. a now has the value 5. We can make reference variables of any simple data type. You may revisit this topic in future courses.

CSCI 240 Lecture Notes - Part 10


The string data type
The data type called string which we have used a little is really an example of a C++ class. We will study more about classes at the end of the course. The point is, it is not really a data type in the same sense as int, double, char, etc... Using the string class, we did not know how strings were represented (stored) internally. (How much memory do they take up? Do they grow and shrink? Is there a maximum size? etc...) Thus we did not have to specify and control certain internal details, and this simplified things somewhat, as compared to...

Standard C/C++ strings


The C++ language does not have a string data type. But there is a way to represent and manipulate sequences of characters (which are usually called strings, just as a short way to refer to them). 1. There is no such thing as a string data type in standard C++. 2. strings are created as arrays of char. 3. A null character (ASCII 0 or '\0') in the array signals the effective end of the array of char; that is, it marks the end of the string. Characters in the array beyond the null are ignored by the functions designed to work on these strings. 4. A set of standard string manipulation functions are provided in the interface defined in <string.h>. (This contains a set of function prototypes to functions in the C++ standard libraries.) Since a string is an array of char, you can manipulate it with array notation (subscripts). However, it is often easier to use the string functions - which mostly use an address-based notation rather than array notation. They do not use a reference notation. We will look at both approaches.

How to you create a string?


1. Define an array of char.

char s[80];

Note that we hereby reserve 80 bytes of memory for this string. We are not obligated to use it all, but we better not overfill it or we will be in trouble (because we would overwrite memory after it). 2. Put some chars in it and end it with a null. For now, let's look at the string as an array. So we could do this:
s[0] s[1] s[2] s[3] s[4] s[5] = = = = = = 'H'; 'e'; 'l'; 'l'; 'o'; '\0'; //H as a char

Note that because chars and ints are interchangeable in C++, so we could also assign the integer 0 to s[5] since that is the ASCII value for the null character. However, we cannot do this to insert the null character:
s[5] = '0'; //Char '0' is ASCII 48, not ASCII 0

So we have an 80-byte array, and we use only the first 6 bytes. However, we can now print it using cout:
cout << s; //prints: Hello

If we had forgotten to put in the terminating null, this cout could print something like:
Hello. %x6^^* #8, ,d @ b,,.....

That is, it would print bytes in memory starting at s, interpreted as chars, until it found (by chance) a null (zero-valued byte). There are simpler ways to initialize a string: 1. Based on the fact that a string is an array of char, we can do the following:
char s[80] = {'H', 'e', 'l', 'l', 'o', '\0'}; //here we must use '\0'

in this case, the rest of the array would contain all 0's. 2. There is a shortcut that is allowed by C++:
char s[80] = "Hello";

in this case also, the rest of the array would contain all 0's. 3. Looking ahead, there is a string function that will copy one string value into another:
char s[80]; strcpy(s, "Hello");

in this case, the rest of the array (after the terminating null) would contain whatever was there before the strcpy(). Since a string is an array of chars, we can look at individual chars in the string using array notation.
cout << s[2]; //displays: l

Suppose we want a function that will return the Ith character of a string. We could write it as follows:
char IthChar(char s[], int i) { return s[i]; }

And we could call it as:


cout << IthChar(s, 2); // displays: l

Note how we designed the function:


it returns a char it takes 2 arguments: o an array of char (the string) o the subscript of the char desired

Note also what would happen if we used:


char ch; ch = IthChar(s, 10); ch = IthChar(s, j); //or //if j has a value > 5

It would return some random byte (char) that happened to be in memory at 10 bytes (or j bytes) beyond s - at s[10] or s[j]. But the actual effective string is just "Hello". So we don't know what is in s[10] or s[j]. We could find out, of course, if we cout-ed the character returned, but it would be meaningless. It was never assigned a value, so its value is random.

One way to fix the function so we couldn't make this mistake is to re-design the function to return a code (0 = fail, 1 = ok, for example), and to pass back the char (if found). We need to know if the subscript i is beyond the end of the string (the null).
int IthChar(char s[], int i, char &ch) { int p; for( p = 0; p <= i; p++ ) { // if null is before ith pos in s, return(fail) if( s[p] == '\0' ) return 0; } //if we're here, we got to ith char without encountering '\0' ch = s[i]; return 1; }

This considers s simply as an array of char, recognizes that the 0 terminates the effective string, and makes use of array notation to do all this. We could (again, looking ahead) use one of the standard C string functions to help. In particular, there's a function called strlen() that can help:
int IthChar(char s[], int i, int &ch) { if (i > strlen(s)-1) //?? check this carefully return 0; //fail ch = s[i]; return 1; } //pass back ith char //success

Let's check the if-condition carefully. Suppose s has the string "dog"
subscript: string: 0 d 1 o 2 g 3 \0 4 ? etc.

strlen(s) is 3 so we want this condition to be true if i is 3 or more (and then return 0). Another way to say it is that we want the condition to be false if i is 0, or 1, or 2, but not if it's more than that (so that we can go on and pass back one of the chars in the string). So if (i > 2) - for this example - we want the condition to be true so that we will execute the return(0).. Since strlen(s) is 3, we can code strlen(s)- 1 to represent 2. Alternately (verify this yourself) we could use >= strlen(s).

Suppose we want to write a function to change a string to all uppercase.


void strUpper( char s[] ) { int i; //used as a subscript here for ( i = 0; s[i] != '\0'; i++ ) s[i] = toupper(s[i]); }

The ANSI C and C++ String Library functions.


C++ has standard library functions to manipulate strings which are stored as null-terminated arrays of char. Every vendor who markets a C++ compiler implements these functions, which are then callable from any C++ program. With C++ standard string functions you have to:

create memory for each string by declaring a char array be sure (one way or the other) that the array of chars has a null terminator be sure that you don't overflow the array

Here is a summary of a small subset of the string functions:


The arguments called s1 and s2 are names of arrays of char. A "valid string" is one with a null terminator. strcpy(s1, s2) This function will copy s2 into s1. s1 may or may not have a valid string in it. s2 must be a valid string (either a character array with a null terminator OR a string literal). s2 must not be bigger than s1 or array overflow will result. strcat(s1, s2) This function concatenates s2 to the end of s1. Both s1 and s2 must be valid strings, but s2 could be a string literal. There must be room in s1 for the entire result. int strlen(s1) This function returns the integer length of s1. This is the number of chars in s1 but DOES NOT include the null terminator. int strcmp(s1,s2) This function compares the two string arguments alphabetically. It returns:

a positive value if s1 is alphabetically greater than s2 a negative value if s1 is alphabetically less than s2 0 if s1 is alphabetically equal to s2

For example, suppose you have: char s1[9], s2[9]; and both have valid strings in them. Suppose you want to concatenate s2 to the end of s1. You could do a test:
if (strlen(s1) + strlen(s2) < 9) { strcat(s1, s2); } else { cout << "not enough room"; }

More examples will be given in lecture.

CSCI 240 Lecture Notes - Part 11


A Last Clarification on Arrays
We have noted that arrays are passed differently than simple variables. In fact, that's a lie. In C++, everything is passed by copy. For example: 0. When we code
fn(i);

where i is a simple variable, we are passing a copy of the value of i. 1. When we code
fnA(ar);

where ar is the name of an array, we are really passing a copy of the address of the array. 2. When we code
fnB(&i);

where i is the name of a simple variable, we are again passing a copy of the addressof i. 3. When we code
fnC(x);

where fnC() defines its argument as a reference argument, C++ is actually supplying a copy of the address of x when the function is called. But in all cases, copies are passed. Passing reference variables is a little different. While it is true that a copy of the address is passed, the compiler generates code so that if you assign something to that reference argument, the value-referred-to gets changed (not the address in the reference argument). Again: any assignment to the passed-in reference argument alters the data at the address passed. So although the function gets a copy of the address of the data, the function cannot change the value of that copy (of the address).

Structures
A structure is a custom data type defined by the programmer. It can hold a collection of variables of different data types (unlike arrays, in which all the data elements are the same type). The data items in a structure are usually related (different kinds of information about a person, or about a part, or about an account, etc...). Each data item in a structure is called a member. Sometimes these members are also called fields. There are several minor variations in structure declarations. We will use the following:
struct new-type-name { data-type data-name1; data-type data-name2; ... };

Write this before your main() function and before any function prototypes that use this new type. Then it will be "known" to the compiler for the rest of the compilation. If you declare a struct inside main(), for example, it will only be "known" inside main() - and thus unusable in any other function.

Then to declare a variable of this new type, declare in any function that needs one:
new-type-name aVar;

A specific example:
struct Acct { char acctNum[10]; char acctName[40]; char acctType; double acctBal; }; int main() { Acct myAcct; ... }

//declare a single variable of this new type.

Note that the struct definition does not create any variable at all. It is just a template or a pattern. It takes up no memory and has no address. You still must declare one or more variables of this new type before you can use it. Note also that after the above declarations, although we have a variable, it has no defined values in any of its members. Again, the struct definition can be placed before the main() function so it is visible to all functions in the program. However, instances of variables of this type should normally be declared in the appropriate function or passed as arguments when necessary. To reference a member of a structure, there is a "dot notation"
myAcct.acctBal = 1000.00;

sets the acctBal member in the structure myAcct to the value 1000.00.
strcpy(myAcct.acctName, "Jane Doe");

sets the acctName member of myAcct to "Jane Doe". Note that we have an array of char (i.e. a string) as a structure member. Because it is a string, we have to use appropriate means to manipulate it. We could not do this:
myAcct.acctName = "Jane Doe"; // NO NO

We could do this (but why bother?):


myAcct.acctName[0] myAcct.acctName[1] myAcct.acctName[2] ... myAcct.acctName[8] = 'J'; = 'a'; = 'n'; = '\0';

It is allowed to do compile time initialization (similar to other variables) as follows:


Acct anAcct = {"123456789", "Jane Doe", 'S', 1000.00};

Note that the data type of each member is clear. The order must match the structure definition. Omitted members (only at the end, if any) are set to 0's (for strings, null However, this kind of initialization is rarely very useful, except for small test programs. We can use sizeof on whole structures:
sizeof( Acct ); sizeof myAcct; //the type //a variable of that type

Both evaluate to the number of bytes occupied by the structure.

Arrays of structs
It is perfectly acceptable and sensible to create an array of structs:
Acct ar[20] //an array that can hold 20 Acct structs

Refer to them as:


//for the whole struct ar[i] //for the acctBal member of the struct in ar[i] ar[i].acctBal

To pass an array of structs to a function, you can use the array notation.
void fn( Acct empAr[] )

{ empAr[3].acctBal = 300.00; }

//for example

The calling statement would be:


fn( array_of_structs_name );

Returning Structures from Functions


It is allowed to create a structure as a local variable in a function and then return it to the calling program: Declare a function:
Acct fn();

then in main:
Acct rec; rec = fn(); // rec has valid values created by fn()

and the function:


Acct fn() { Acct r = {"123", "John Doe", 'C', 200.01}; return r; }

Passing Structures to Functions


We can pass structures by copy (value) or by reference. We usually pass by reference because:

structures tend to be large, and making the copy wastes time and memory usually we will want to change some member or members of the structure

But if a structure is small and/or need not be modified, we can pass by value. Suppose we want to write a function, incrBal(), to increment the account balance by some percentage (monthly interest or some such).

Call-by-Reference
void incrBal( Acct &anAcct, double percentage ) { anAcct.acctBal = anAcct.AcctBal * (1.0 + percent): }

The call would look like this:


incrBal(oneAcct, 0.06);

In the case of this particular task, since there is just one thing in the structure being altered, we could take another approach: just pass a reference to the acctBal member and write a new version of the function as:
void incrBal( double &bal, double percent ) { bal = bal * (1.0 + percent); }

and call it like:


incrBal(oneAcct.acctBal, 0.06);

CSCI 240 Notes - Input and Output


This section of the Notes contains several topics on Input and Output used in various parts of the course.

cin
cin allows the user to enter values into variables from standard input (the keyboard or via I/O redirection). It can accept one or more values, depending on how many << are used. Its behavior is slightly different depending on the data type being read. When you code
int i; cin >> i;

cin attempts to read an integer from standard input (let's assume the keyboard). It waits until the user presses <Enter> and then attempts to convert the keystrokes entered into an int, and then store it in the variable whose name is supplied. So if the user enters 123<Enter> the characters '1', '2', and '3' are converted by cin to the integer 123 and this value is stored into the memory occupied by i. You can read several values with one cin. Suppose you want the user to enter integer values:
cin >> int1 >> int2 >> int3;

You would separate the numbers with any whitespace character when entering the values.

cin and Input Errors


If the data supplied by the user does not match what is expected by the program, errors can occur. Sometimes you will get no indication of them, except that the program will not work as expected. For example, if the user is supposed to enter a number, but enters: 2w3<Enter> cin stops at the first non-digit, and converts just what has been entered up to there; in this case the '2'. So the value 2 would be stored. No error would be signaled. In this case of 2w3 - the w, the 3, and the \n are still "waiting" to be processed by subsequent input instructions - which are probably expecting something else - maybe a string representing someone's name. So the "w3" would be read as the name.

The Keyboard Buffer


Keystrokes (chars) entered by user are stored in a special area in memory called the keyboard buffer. cin gets chars from there. Suppose user types _123<Enter> //the _ represents a space
So in kbd buffer is: Or '_' 32 '1' 49 '2' 50 '3' 51 '\n' 10 //as chars //as ASCII values

cin takes chars from the start (the left), removing them as it processes them. Typing adds chars to the end (the right). cin for numeric values (float, double, int, long int, etc.) works like this for keyboard input:

starts its work when user presses <Enter> scans past leading "white space" (blanks, tabs, newlines) stops on first non-valid char converts digits (and +,-,.) into specified internal numeric format, and stores result at address given by argument does not check for input errors; just stops on non-valid character

cin for a character will scan past leading whitespace and then take the next char from the keyboard buffer if there is one. If it is empty, it lets the user type chars until <Enter> is pressed. Then it gets the 1st char in the buffer.

Reading Multi-Word Strings


We have used cin to read strings (both char arrays and the string class). However, it is flawed for most purposes because it stops reading when it hits any whitespace character. So if the string you want to read consists of words separated by spaces, you'll only get the first word. You'd have to code additional cins or << to get additional words in the string. For example, to read a first, middle, and last name, you could code:
cin >> fName >> mName >> lName;

But if you don't know how many words are in a line, you are in trouble. You can use a "method of cin" to read a line of text (up to the \n character) in one operation. This is object-oriented terminology which will be covered late in the course. For now, just know how to do this:
cin.getline( strVar, maxlen );

Where

strVar is a string class variable or char array maxlen is a number specifying the maximum number of characters to store (if you are storing into a char array, there must be room for a terminating null. So if you specify 80 as the max, only 79 chars from input will be stored plus the null).

If you want to stop inputting characters when you hit some other character, that character can be a third argument. For example, to stop on a '.', code:

cin.getline( str, 80, '.' );

Incidentally, there is another method that will read the next character in the keyboard buffer whether it is a whitespace character or not:
ch = cin.get();

Where ch is a char variable. Note that cin.get() returns the value value of the char it reads.

File Input and Output


You may know that you can run a normal program to write output to a file or read input from a file using I/O redirection. When you do this, the user of the program must type the name(s) of these files on the command line. Also, you are limited to one input and one output file. However, you can also have a program itself connect to and read or write from one or more input and/or output files. The techniques for doing this are based on object oriented principles which are not covered until late in this course, but the techniques are nonetheless simple to use. Files are objects. You can think of an object as a special data type. The data type has a set of special functions associated with it that only work on variables of the particular type. The functions are called methods (just a different terminology). We will discuss the methods open(), fail(), getline(), and close() below. So - how do we do this? 1. First, you need to
#include <fstream>

2. Then, for each file you want to use, you must declare a variable. The variable type (or the object type) is either:

ifstream (for input file stream) ofstream (for output file stream)

The word stream is used because files are treated as a stream or sequence of values, one after another.
ifstream inFile; ofstream outFile;

3. Then you must "open" or connect the program to the file.

inFile.open( "test.txt" );

or
outFile.open( "output.txt" );

Because inFile is of type ifstream, the open method prepares the file to be read. Because outFile is of type ofstream, the open method prepares the file to be written. (If there is already a file called output.txt it will be overwritten.) There are some optional arguments to open() that are covered more fully in the textbook, Chapter 14. For example, you can arrange to append written data to an existing file. 4. Check that the open() succeeded. To do this, code:
if ( inFile.fail() ) // or outFile.fail() { cout << "open for test.dat failed"; // exit the program exit(1); }

Note: on some systems, the exit() method may require <stdlib.h>. Alternately, if the open is in main() you could just return. 5. Now you can read and write information from/to the file. For example:
inFile >> num; outFile << "Hello";

5a. Reading strings with embedded spaces presents some problems, since an ifstream will stop at the first space. You can use the getline() method (function) to read such strings - see the example below. Alternately, you can use multiple input operations if you know how many "words" you want to read: Example: if your input looks like this: Chicago IL
inFile >> str1; inFile >> str2;

then str1 will have "Chicago" and str2 will have "IL". This will be true even if there are extra spaces or even newline chars between the two words.

6. Close the files. On some systems (especially for input files) this may not be necessary, but it is considered good programming practice:
inFile.close(); outFile.close();

Example: the program below copies the contents of one file to another. It reads and writes strings. When a string is read from a file, the newline character is not put into the string data item in the program, so when we write each string out to the second file, we have to add it back in. Also, we use the regular console cout to echo the data to the screen (in addition to writing it to the file). Notice that there are two versions of the read-write loop. The first uses a sentinel value to signal end-of-input (a line with the single character 'x'); the second tests a special return value from getline:
#include <fstream> #include <iostream> using namespace std; int main() { ifstream in; ofstream out; char s[80]; //Note the two \\ needed to specify one \ in the string. //Otherwise, the \t would be taken as a tab and the \o would be an undefined //escape sequence in.open( "c:\\temp\\original.dat" ); if (in.fail()) { cout << "open for original.dat failed"; // exit the program exit(1); } out.open("c:\\temp\\newCopy.dat"); //read-write loop version 1: //assumes that the last line in the file is "x" This code will NOT //copy the "x" line in >> s; while ( strcmp(s, "x") != 0 ) { cout << s << "\n";

out << s << "\n"; in >> s; } //read-write loop version 2: //uses the function getline(). See note below. This version will copy //the last line (the "x") since it reads data until all of the //records have been read from the file. while ( in.getline(s, 80) ) { cout << s << "\n"; out << s << "\n"; } in.close(); out.close(); return 0; }

Note: version 1 above stops reading chars at any whitespace. So a line consisting of several words separated by spaces would require several reads (in >> s). getline() reads a whole line up to a newline character (or other delimiter which can be specified as an optional third argument) so you can easily read such a line. So getline() reads one line from the file in and stores it into s. The function returns:

true when it can read a line and false when it can't read a line (because there are no more lines)

So the return value is used to exit the loop upon end-of-file.

I/O of Structures - Block I/O


To read and write structures to disk, you could read and write the individual data members in order. However, this is inefficient and awkward. It is much neater and more efficient to input and output a whole structure at a time, with one operation. Assume you have a structure defined and created in your program. Let's say the structure name is Person and the structure variable is person:
struct Person { char name[40];

int id; }; Person person;

Supply a
#include <fstream> // Note: just fstream (not if- or of- stream)

Writing a structure
1. Declare the file and open it for output:
ofstream outfile; //Note the "ios::binary" outfile.open( "people.txt", ios::binary );

Don't neglect to check for failure. Use the fail() method of ofstream:
if( outfile.fail() ) { ... }

2. Populate person with data:


strcpy(person.name, "Joe"); person.id = 1234;

3. Write the structure to disk:


outfile.write( (char *)&person, sizeof(Person) );

Note the typecast of &person to a char *. &person is just an address, but the write method needs a char * argument. &person doesn't say what kind of data the address is pointing to, but the function insists that it be a pointer-to-char. So the typecast does that. So the first argument tells write where to find the data to write, but notice that it doesn't say "this is a Person thing". It just says where the Person begins. The second argument tells write how many bytes to write out from that beginning address. In this way, write will output the correct number of bytes for just one Person. 4. Do steps 3 and 4 as often as needed to create a file of Persons. 5. Close the file:

outfile.close();

Reading a structure
Assuming you have written a file of Person structures with a program based on the information in the previous section, you can write a program to read these structures back from disk in a similar way. 1. Declare the file and open it for input:
ifstream infile; // Note the ios: stuff infile.open("people.txt", ios::binary);

Don't neglect to check for failure. Use the fail() method of ifstream:
if (infile.fail()) { ... }

2. Read the data until end of file:


infile.read((char *)&person, sizeof(Person)); while (infile) { // do whatever with this person: print it or ... infile.read((char *)&person, sizeof(Person)); }

Note that the value of infile is tested as the loop exit condition. infile will be true after a successful read and false when end-of-file is reached. 3. Close the file:
infile.close();

Building an Array of Structures from input


You can declare an array of structs and then fill it with structs read from disk. 1. Declare the array:
Person people[80];

2. Open the file as usual, but the read commend will look like this:

infile.read((char *)&people[i], sizeof(Person));

Of course, the subscript i will need to be initialized to 0 and will need to be incremented after each read so that the next read puts the next struct into the next array element.

CSCI 240 Lecture Notes - Part 12


Object-Oriented Programming
Object-Oriented Programming (OOP) is the next step in modern programming practice after functional programming (60's) and structured programming (70's). OOP is complex. The first of three main principles of OOP is that of the class, which is the basis of OOP. A class is a kind of a package that consists of functions and the data they work on. However, in OOP, functions are called methods, and the individual variables defined in a class are called data members. An object is an instance of a class that contains data and methods. So, a class is a template for the creation of multiple instances of that class, each of which is called an object. The generic format for a class:
class class_name { method_prototype_1; method_prototype_2; ... data_type data_member_1; data_type data_member_2; ... };

A class for a bank account might resemble:


class Acct { //The class variables char acctNum[10]; char acctName[40];

char acctType; double acctBal; //The class methods Acct( char [], char [], char, double ); void printAcct(); }; //the "constructor"

//display the account information

The constructor is a special method that is used to supply initial values for the data members. Before the class can be used, code must be written for the "constructor" and the printAcct() method:
Acct::Acct( char newAcctNum[], char newAcctName[], char newAcctType, double newAcctBal ) { strcpy( acctNum, newAcctNum ); strcpy( acctName, newAcctName ); acctType = newAcctType; acctBal = newAcctBal; } void Acct::printAcct() { cout << "Account Number: " << acctNum << " << endl; if( acctType == 'C' ) { cout << "Account Type: } else { cout << "Account Type: } cout << " }

Account Name: " << acctName

Checking";

Savings";

Account Number: " << acctBal << endl;

Now before we get to a program that uses these, a few notes: The "constructor" is the method called when you create an instance of a class. Here, its arguments are stored into the data members of the object. So it is a kind of special initialization method. The name of the constructor is always the same as the name of the class. The notation of Acct:: in front of the constructor and the method are used to specify what class the methods belong to, since the code for the methods are not defined inside the class definition.

It would be possible to have some other class with a printAcct() method, for example. It would be defined as OtherClass::printAcct(). This way the compiler can tell which class a given method definition belongs to. Now that the Acct class is defined, we can write a program that uses it. Just type in the class definition before int main() (the actual methods can go before or after main).
int main() { Acct account = Acct( "123456", "Byrnes, Amy", 'C', 1000000.00 ); account.printAcct(); return 0; }

That's it. Clearly, there's some work up front to design and define the Acct class, but once that's done, the program becomes simpler. Note that to add another Acct, we need 1 more variable, 1 initialization statement, and 1 (simple) method call to print it. Notice the way the method is called - it's just like the structure dot notation - in this case, the objectname.methodname(args if any). Also, notice that the call to printInfo() does not need any arguments! The Acct account "knows" its account number, account name, etc... (they are data members of account). So since the code in printAcct() (which is inside account) can "see" the data members (which are also inside account), no arguments are needed. It's like main is telling the Acct account to print itself. In fact, in OOP, calling a method of an object is called "sending a message to the object". In this case, it's the "print yourself" message. Another thing: each object of the Acct class has its own set of variables (data members). So calling the printAcct() method for different Acct objects will cause different information to be printed:
int main() { Acct account1 = Acct( "987654", "Amonte, Tony", 'S', 5000000.00); Acct account2 = Acct( "321098", "Belfour, Ed", 'C', 6000000.25); account1.printAcct(); account2.printAcct(); return 0; } //prints the account information for Amonte, Tony //prints the account information for Belfour, Ed

Additional Methods for class Acct


Now that we have the Acct class, we can add functionality to it by adding some useful methods. All that needs to be done is to add a prototype to the class definition and then code the new method.

Guarding the Data


One important advantage of OOP is that since the data for an object is kept inside that object, we can make sure that the data is always valid and consistent by making checks in the object's code. For example, suppose we wanted to be sure that all of our Accts don't have negative balances. We could write the constructor so that we would initialize the salary values no matter what the passed-in values were:
Acct::Acct( char newAcctNum[], char newAcctName[], char newAcctType, double newAcctBal ) { strcpy( acctNum, newAcctNum ); strcpy( acctName, newAcctName ); acctType = newAcctType; if( newAcctBal < 0 ) { acctBal = 0; } else { acctBal = newAcctBal; } }

Now it's impossible for an Acct to have a negative balance.

Access Methods and Private/Public


There is also another action that can be taken to guard the data. Typically, we do not want outside methods or functions to be able to access the individual data members directly. To guard against the access, we will make some class items public and others private. Public data and methods are "visible" and "accessible" to code outside the object - that is, to code that is not in the object's methods. Private data and methods are "invisible" and "inaccessible" to code outside the object. This is the default access. The new definition for the class might resemble:

class Acct { private: char acctNum[10]; char acctName[40]; char acctType; double acctBal; public: Acct( char [], char [], char, double ); void printAcct(); }; //the "constructor"

//display the account information

However, it is possible that an outside method or function will need access to the private data members or methods. To grant the access... We will write some public methods to allow other code access to the account number, account name, etc..., but we write the code to guard against invalid values. A new version, with a couple of new public access methods follows:
class Acct { private: char acctNum[10]; char acctName[40]; char acctType; double acctBal; public: Acct( char [], char [], char, double ); void printAcct(); void void void void }; void Acct::set_acctNum( char newAcctNum[] ) { if( strcmp( newAcctNum, "" ) != 0 ) { strcpy( acctNum, newAcctNum ); } } void Acct::set_acctName( char newAcctName[] ) { if( strcmp( newAcctName, "" ) != 0 ) { //the "constructor"

//display the account information

set_acctNum( char [] ); set_acctName( char [] ); set_acctType( char ); set_acctBal( double );

strcpy( acctName, newAcctName ); } } void Acct::set_acctType( char newAcctType ) { if( newAcctType == 'C' || newAcctType == 'S' ) { acctType = newAcctType; } else { acctType = 'C'; } } void Acct::set_acctBal( double newAcctBal ) { if( newAcctBal < 0 ) { acctBal = 0; } else { acctBal = newAcctBal; } }

Now main() (or any other function with access to an Acct object) can reset one or more of the data members - but never to an invalid value:
account1.set_acctType( 'C' ); account2.set_acctBal( 100.01 );

It's also possible that an outside method or function just wants to retrieve the value in one of the data members. This can be easily achieved by writing a set of getXXXX access methods that simply return the value in the specific data member:
double Acct::get_acctBal() { return acctBal; } char Acct::get_acctType() { return acctType; }

You get the idea (hopefully). Now, to use them:

cout << account1.get_acctBal(); char type; type = account1.get_acctType();

Most classes will have a set of getXXX and setXXX access methods. The getXXX methods usually have no arguments and simply return the value of a particular data member. The setXXX methods usually take one or more arguments and do some kind of validity or consistency checking of their argument(s) before assigning the argument's value to a data member. They may or may not print an error message, return a success/error code, or "throw an exception" (another error handling system in C++).

Method Overloading
Sometimes it's convenient to have different versions of a method that do the same thing. That is, several methods with the same name, but which take different arguments. One of the most common types of overloading is to overload the constructor. As an example, take the constructor for the Acct class. It takes 4 arguments. But let's suppose you have made the following declaration:
struct minimalAcct { char number[10]; double balance; };

and in your program you have created and initialized a couple of these:
minimalAcct min1 = { "24680", 50.00 }, min2 = { "97531", 2670.34 };

Now you could do the following:


Acct account3 = Acct( min1.number, "Flintstone, Fred", 'S', min1.balance );

But it would be nice if you could just do this:


Acct account4 = Acct( min1, "Flintstone, Fred", 'S' );

And - guess what? - You can. Just write a new constructor:


Acct::Acct( minimalAcct acctInfo, char newAcctName[], char newAcctType )

{ strcpy( acctNum, acctInfo.number ); strcpy( acctName, newAcctName ); acctType = newAcctType; if( acctInfo.balance < 0 ) { acctBal = 0; } else { acctBal = acctInfo.balance; } }

Of course, anytime you write any new method, you must write the prototype of the method in the class definition. So in a program that uses Accts, you are now free to create Accts using either of the constructors:
Acct account5 = Acct( "147852", "Olczyk, Eddie", 'C', 65412.90 ); Acct account6 = Acct( min2, "Rubble, Barney", 'S' );

The rule for creating different methods with the same name is that the number, order, or data type of the arguments must be different in each case (so that the compiler can tell which one to call). The return value has nothing to do with it. So you could have the following:
void dog(int, int) int dog(int, double); void dog(double, int); double dog(double); etc.

Here's another example. Suppose you have a private int data member in an object called num, and you want to write a setNum() method. That's easy:
void ClassType::setNum( int i ) { num = i; // you might have some "guarding" code also }

Now you also realize that sometimes the calling program will have the numeric value they want to use stored in a double. So, for the convenience of the calling program, you can write:
void ClassType::setNum( double i ) { num = (int) i; }

The informal rule is that functions with the same name should do the same thing. You can sometimes see the following:
int length(); void length(int);

The first is used to return (i.e. get) the length of something:


int len = obj.length();

The second is used to set the length of that thing.


obj.length( 10 );

Although they both have something to do with length, they do very different things, so this is not a good use of this feature.

S-ar putea să vă placă și