SECTION 1 - "FULLY FUNCTIONAL" (ch. 7: 7.1 - 7.3 and 7.7 - 7.8 ONLY)

REFERENCE PARAMETERS

-Recall that we can use functions which return a single value, using the return statement, and we can use functions that return no values, using a void type

-Recall the awkwardness of reading multiple inputs from the user/file - we couldn't use a function, since such a function would need to return multiple values

-NOW, we will learn to create functions which return multiple values - (THEY WILL NOT USE A RETURN STATEMENT TO DO SO!!)

-(We've actually used reference parameters before: with file objects!)

example 1:

(getting two integers from the user)

function definition:

void GetNums(int& N1, int& N2)

{

cout << "Enter two integers: \n";

cin >> N1 >> N2;

}

function prototype:

void GetNums(int&, int&);

function call:

//assuming declaration of:

int Num1,Num2;

GetNums(Num1,Num2);

How it works:

-N1 and N2 are reference parameters- they "refer" back to the original arguments Num1 and Num2

-When the function uses N1 and N2, the memory locations of Num1 and Num2 are actually changed

-recall that with previous use of parameters, local copies of values were made, and any changes to parameters changed the local copy only. Such parameters are called value parameters

example 2:

(uses both value and reference parameters)

-the 'half-adder' problem (7.1) - performs binary addition of two bits:

0+0 = 0, carry =0

0+1 = 1, carry = 0

1+0 = 1, carry = 0

1+1 = 0, carry=1

-This returns two pieces of information: the sum and the carry, so need reference parameters:

function definition:

void HalfAdd(int A, int B, int& Sum, int& Carry)

{

Sum = (A || B) && !(A && B);

Carry = A && B;

}

function prototype:

void (int, int, int&, int&);

function call:

#include <iostream.h>

int main(void)

{

int Bit1, Bit2, SumBit, CarryBit;

cout << "Enter two binary digits (single bits): \n";

cin >> Bit1 >> Bit2;

HalfAdd(Bit1,Bit2,SumBit,CarryBit);

cout << "Sum = " << SumBit << endl;

cout << "Carry = " << CarryBit << endl;

return 0;

}

Examine how the two types of parameters differ in this example (value vs. reference)

Trace thru with differing input

Can you write the I/O using function

Summarize so far:

value parameters - a local copy of the actual argument's value is made - changes to parameter stay local, and the copy disappears when function ends

reference parameters - each parameter refers to the actual argument's memory - changes occur to the actual memory

important! - items passed as reference parameters must be variable of same type as defined! problems ranging from syntax errors to unexpected return values can result, otherwise. (can't pass constants by reference, but can by value)

Guidelines for defining functions -

-The form of a function is determined by the specification of the problem it must solve. Therefore, you must fully solve the problem the function is to implement before you know what the form of the function will be

-Three forms of functions:

  1. Function returns no values: type is void, all parameters are value
  2. Function returns one value: type is of value returned, all parameters are value
  3. Function returns multiple values: type is void, reference parameters are used for return values, value parameters are used for input values (if any)

Practice with parameters:

The Steve programs!

//steve #1

#include <iostream.h>

#include <iomanip.h>

void One(int&,int&,int,int);

void Two(int, int, int&, int&);

int main(void)

{

int a=1,b=2,c=3,d=4;

One(a,b,c,d);

cout << a << setw(4) << b << setw(4) << c << setw(4) << d << endl;

Two(a,b,c,d);

cout << a << setw(4) << b << setw(4) << c << setw(4) << d<< endl;

return 0;

}

void One(int& p, int& q, int r, int s)

{

p++; q++; r++; s++;

cout << p << setw(4) << q << setw(4) << r << setw(4) << s << endl;

}

void Two(int w, int x, int& y, int& z)

{

w=x=y=z=0;

cout << w << setw(4) << x << setw(4) << y << setw(4) << z << endl;

}

output:

2 3 4 5 //from one

2 3 3 4 //from main

0 0 0 0 //from two

2 3 0 0 //from main

//steve #2

#include <iostream.h>

#include <iomanip.h>

void One(int&, int&, int&, int&);

void Two(int, int&, int&, int&);

int main(void)

{

int a=1,b=2,c=3,d=4;

One(a,b,c,d);

cout << a << setw(4) << b << setw(4) << c << setw(4) << d << endl;

Two(d,c,b,a);

cout << a << setw(4) << b << setw(4) << c << setw(4) << d<< endl;

return 0;

}

void One(int& d, int& c, int& b, int& a)

{

a=11; b=22; c=33; d= 44;

cout << a << setw(4) << b << setw(4) << c << setw(4) << d << endl;

}

void Two(int p, int& q, int& r, int& s)

{

int a = 17;

p++;q++;r++;s++;

cout << p << setw(4) << q << setw(4) << r << setw(4) << s << endl;

}

output:

11 22 33 44 //from one

44 33 22 11 //from main

12 23 34 45 //from two

45 34 23 11 //from main (note: a=17 does not affect 'a' in main)

---

A USEFUL IMPLEMENTATION OF REFERENCE PARAMETERS - THE SWAP FUNCTION:

-The 'swap' function exchanges the values of two variables (we'll use this a LOT, soon!)

-IPO:

INPUT: two variables (of same type) First, Second

OUTPUT: two variables (of same type) First (containing original value of second), Second (containing original value of First)

PROCESS:

Temp=First

First=Second

Second=Temp

function definition:

void Swap(int& First, int& Second)

{

int Temp;

Temp=First; //replace 1st 2 lines: int Temp=First

First=Second;

Second=Temp;

}

-This function works to swap any two variables of type int

-But what if we need to swap variables of other types? Must declare/define other functions, which are identical, except for data type.

-We can overload the name - the compiler will know which swap to use by the data type of the parameters

-e.g., here is the header file for a swap library (swap.h):

void Swap(int&, int&);

void Swap(char&, char&);

void Swap(double&, double&);

void Swap(float&, float&);

//etc.

-The library implementation file would contain a fully-defined swap function for each type - only parameter and temp types would differ

-Note that we have been using overloading w/o knowing it! e.g., all the math operators work on varying data types, automatically - same with relational operators, boolean operators, i/o operators, etc.

-This is a very important feature of C++ that we'll expand further in the future!

---

OBJECT LIFETIME/STORAGE CLASSES

-Recall that a block of statements (block) is delimited by { }'s (this includes not only function bodies, but also loop bodies, if-else bodies, etc)

-During program execution, a data object is defined:

-These two steps are called the construction of the object

-When the object is disassociated with a memory location, it is called the destruction of the object

-The period of time from construction to destruction is the object's lifetime

-To determine the lifetime of an object, we must know when construction and destruction occur, which can be determined by knowing the object's storage class

-There are 2 types of storage classes for C++ data objects:

  1. static - these objects are constructed when the program begins and destroyed when it ends, therefore the lifetime of a static object is the lifetime of the program. (We have not seen such objects, yet)
  2. automatic - these objects are constructed when execution enters the block in which they are declared and are destroyed when execution exits that block. Thus, the lifetime of such objects is the time from entering a block to exiting that block. (All objects we've used have been automatic - this is the default storage class)
    Because an automatic object exists only during its block's execution, it can only be accessed by statements in that block. This leads to the idea of local objects, as we've seen.

-Watch out:

while (something)

{

int I=0;

}

-This causes I to be constructed and destructed each loop iteration, which can slow down execution (This is another reason to declare variables at the beginning of functions/main)

-However, if I is declared static:

static int I=1;

then it will be constructed (and initialized) only once, even if it's in a loop!

-Even if I is in a function, whatever value it had when the function terminated will persist until that function is called again, and can then be used with the previous/stored value

example:

A function which returns the sum from 1 to 1 the 1st time it's called, returns the sum from 1 to 2 the 2nd time it's called, returns the sum from 1 to 3 the 3rd time it's called, etc.

-In general, it must return the sum from 1 to N, when called for the Nth time.

-This means it must know how many times it's been called! (We don't pass this info in!)

int NextSum(void)

{

static int

CallNumber=0;

CallNumber++;

return CallNumber *(CallNumber+1) / 2; //Gauss' formula

}

call:

for (x=1; x <= 100; x++)

cout << x << ": " << NextSum() << endl;

output:

1: 1

2: 3

3: 6

4: 10

99: 4950

100: 5050

-The use of static objects will become more useful as programming ability grows

---

The register specifier

-Recall that a register is a memory location in the CPU.

-Obviously, accessing data in a register is faster than accessing it from RAM.

-Thus, it can speed up a program to direct the compiler to use the register for certain data objects, instead of normal memory (some compilers do this when possible, anyway - "optimizing")

-To specify a register data object:

register int I; (or initialize it if you want: register int I=0;)

-Register variables are useful for loop counters

-Only objects of the automatic storage class can be register variables - not static

-However, don't declare ALL data objects as register, as there is only limited # of registers to use!