++ won't do, because once we hit 25, the hour goes
back to 1. So, we might want a function inchour
that increments the hour correctly ... which means that the hour
variable with which the function gets called should be modified.
We might be tempted to say something like this:
void inchour(int);
...
void inchour(int h)
{
h++;
if (h == 25)
h = 1;
}
However, since pass-by-value gives us a copy, nothing really gets
changed!
(See diagram on right.)
We need to pass the hour by reference, which
means we get the actual object from the function call, not a copy.
This is indicated in C++ by putting an & after the
type of the argument.
void inchour(int&);
...
void inchour(int& h)
{
h++;
if (h == 25)
h = 1;
}
The visualization here is that the parameter h
is like a wormhole to the argument inchour was called
with. (See diagram on right.)
Now this does what we want. For example, if this function
definition is used in the following code fragment:
int hour = 18;
for(int i = 0; i < 8; i++)
{
cout << "Hour " << i + 1
<< " of my 8 hour day starts at "
<< hour << ":00" << endl;
inchour(hour);
}
This fragment will print out something like this:
Hour 1 of my 8 hour day starts at 18:00 Hour 2 of my 8 hour day starts at 19:00 Hour 3 of my 8 hour day starts at 20:00 Hour 4 of my 8 hour day starts at 21:00 Hour 5 of my 8 hour day starts at 22:00 Hour 6 of my 8 hour day starts at 23:00 Hour 7 of my 8 hour day starts at 24:00 Hour 8 of my 8 hour day starts at 1:00Note that
inchour(6) will not compile! Why? Well
when you pass something like this by reference, that says that
it might get modified, and the constant value 6 is not something
that can be modified! You need to pass variables, not
constants, to functions that take arguemnts by reference rather
than by value.
Important Note: This example is good for showing how pass-by-reference works, but a lousy example for showing when you should be using it! In this case, you should write inchour() as a function that takes its parameter with pass-by-value and returns the new time, rather than modifying its argument.
Terminology note: If a function takes a parameter by reference, the technical term for what kind of object can be an argument is lvalue, which literally means "anything that can appear on the left-hand side of an assignment". Right now, variables are the only lvalues we know about. Another term involved in these kinds of discussions is rvalue, which means anything that can appear on the right-hand side of an assignement — which is a much bigger class of objects. We require lvalues for the left-hand sides of assignments (obviously), for arguments to functions in which the parameter is passed by reference, and also as operands for ++ and -- expressions (and +=, -=. *=, ...).
swap!swap.
For example, suppose we read two int's in from the user and we
want to print out all the integer values from the smaller of the
two up to the larger (comma-separate). If the user is kind enough to enter the
numbers so that the frst is the smaller, we would write:
int a, b; cin >> a >> b; for(int i = a; i < b; i++) cout << i << ','; cout << b << endl;But what if we want to allow the user to enter the two numbers in either order, and have the program work regardless? If we had a function
swap that took two ints and
swapped their values, we could do the following:
int a, b; cin >> a >> b; if (b < a) swap(a,b); for(int i = a; i < b; i++) cout << i << ','; cout << b << endl;... which is a whole lot more convenient than, for example, writing separate for-loops for the a<b case and the b<a case.
The function swap will need to change the values of
the variables it's passed, so they must be passed by reference.
void swap(int&, int&);
...
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
void polar2rect(double,double,double&,double&);
...
void polar2rect(double r, double theta, double &x, double &y)
{
x = r*cos(theta);
y = r*sin(theta);
}
With this defintion, if I had a vector represented by
doubles radius and angle, I
could convert it to rectanglular coordinates stored in variables
x and y by writing:
polar2rect(radius,angle,x,y);
istream, recalling that both cin and
all the ifstream objects we declare are of type
istream.
Suppose I have an integer variable T whose value
I'd like to be
the number of seconds ellapsed between two events, and I'd like
to read that in our hh:mm:ss format. This is
actually a fair bit of work ... not difficult, but time
consuming. Let's further suppose that I'll be reading this from
both the keyboard (using cin) and from a file
(using an ifstream object named
filein)
at various points in my program. It
would be wonderful to have a function readtime
that could take either cin or filein
as an argument, read the ellapsed time in
hh:mm:ss format, and return it in seconds. Since
both are istream objects, it would be natural to
write the function
int readtime(istream);The problem with this, however, is that it doesn't make sense to copy an input stream object. What would it mean? What would happen, for example, if there was an error while reading the copy ... would the original still be OK? That wouldn't be good. On the other hand, we just have a copy, so it can't affect the original. We have a problem! Copying doesn't make sense. So, we must pass stream objects (both
ostream and
istream) by reference. With this in mind, we'd
define:
int readtime(istream&);
...
int readtime(istream& in)
{
int h, m, s;
char c;
in >> h >> c >> m >> c >> s;
return h*3600 + m*60 + s;
}
With these definitions, I can say
int k = readtime(cin);... when I need to read in a time from the keyboard, and I can say:
int m = readtime(filein);... when I need to read in a time from the file to which
filein is attached. Now, my list also mentioned
efficiency as a reason to pass by reference rather than pass by
value. We haven't seen any explicit examples of "large" objects
for which this is important. Our usual types - int, double, char
- are all small, so there is no advantage (actually there's a
small disadvantage!) to using pass by reference instead of pass by
value.
As I ponder how to do this, I realize code that reads a length and converts it to inches consists of a couple of lines, and those lines are the same whether I'm reading the first length or the second. So, in top-down design fashion I say to myself "It sure would be nice to have a function
Program Run data.txt Enter file name: data.txt 3" difference 3' 2" 2' 11"
readLength
that would take care of the reading and converting for me."
And so we might arrive at the following proposed solution.
When I run this on the file
Prototypes Main Definitions int readLength(string fname);int main() { string DFname; cout << "Enter file name: "; cin >> DFname; int L1 = readLength(name); int L2 = readLength(name); cout << L1 - L2 << "\" difference" << endl; return 0; }int readLength(string fname) { ifstream fin(fname.c_str()); int f, i; char c; fin >> f >> c >> i >> c; return 12*f + i; }
data.txt from above,
the result is 0"
difference, which is not what I want.
What happened?
It is well worth running through this with the debugger and/or
with your professor. What you should see is that the first
call to readLength creates the variable
fin, opens data.txt and attaches it
to fin, and reads in the first line, i.e.
3' 2". When the call is over, the stream is
closed and fin, being a local variable, is
destroyed.
then we go through the second call to readLength
and ... the exact same thing happens. In other words,
readLength creates the variable
fin, opens data.txt and attaches it
to fin, and reads in the first line, i.e.
3' 2".
So both calls read in the first line of the file, the second
line is never read, and in main
L1 and L2 both have the same value!
The moral of the story is this: passing a file name to a function only makes sense if you want that one function call to completely process the file. If each function call is only going to process a piece of the file, and if a later call should pick up in the file where the earlier call left off, you can't pass the file's name. Instead, you nead to pass the file stream to the function.
main should
be the one that creates an ifstream object and
attaches it to data.txt, and that
ifstream object is what should then be passed to
readLength in the two function calls.
Now we have a working program, one that prints out
Prototypes Main Definitions int readLength(ifstream &fin);int main() { string name; cout << "Enter file name: "; cin >> DFname; ifstream DFin(name.c_str()); int L1 = readLength(DFin); int L2 = readLength(DFin); cout << L1 - L2 << "\" difference" << endl; return 0; }int readLength(ifstream &fin) { int f, i; char c; fin >> f >> c >> i >> c; return 12*f + i; }
3"
when we run it on the file data.txt from above,
just like it's supposed to. Notice that
readLength takes the parameter fin
by reference. Why? Well first of all, the whole point is
that we have a single filestream and both function calls read
from it. If we have multiple filestreams because a
pass-by-value call made a copy, we'd be back in the same
situation as before: both calls would read the first line of
the file. Moreover, making copies of input and output streams
simply doesn't make sense: many compilers won't even let you
do it. So, input and output streams are always passed by
reference!
istream
is more flexible than ifstreamreadLength function that is potentially useful in
other programs as well. Can we make readLength
even more usefull than it already is? Well, recall that
we discussed that cin has type
istream, and that ifstream objects
like DFin in the previous program are also of
type istream (as well as being of
type ifstream).
This (and the analogous output situation) is our one and only example of
what are called subtypes. How can something be of
type istream and type ifstream at
the same time? Well, you manage to be of type "college
student" and "midshipman" at the same type. It's possible
because "midshipman" is a specific kind of "college student".
In other words, "midshipman" is a subtype of "college student".
The readlength function doesn't use anything specific to
ifstream that other istream objects
don't also have or do. Therefore, we could simply rewrite
readLength so that it takes an
istream object.
Everything works just like before. So what's the advantage? Well, since
Prototypes Main Definitions int readLength(istream &in);int main() { string name; cout << "Enter file name: "; cin >> DFname; ifstream DFin(name.c_str()); int L1 = readLength(DFin); int L2 = readLength(DFin); cout << L1 - L2 << "\" difference" << endl; return 0; }int readLength(istream &in) { int f, i; char c; in >> f >> c >> i >> c; return 12*f + i; }
cin is an istream object
(though not an ifstream object), we can read a
length from the keyboard with readLength by the
function call readLength(cin). So now we have a
readLength that's even more powerful because it
can read from files and from the keyboard.
What's the moral of this story? If you have a function that
reads (or writes) information, and if the function doesn't
use any ifstream (or ofstream)
specific things like .open or .close,
define your function to take an argument of type istream
(or ostream). This way it can read (or write)
to files and to the screen. BTW: remember to pass by reference!
max defined as
int max(int a, int b)
{
if (b > a)
return b;
else
return a;
}
but that my program had three ints,
x, y and z, amongst which I
need the largest. Were I to write
max(x,y,z)the compiler would complain ... the only
max function
it knows about only takes two arguments! However, I could say the
following:
max(max(x,y),z)This is our first example of composition of functions. When the function
max gets called, its two argument
expressions are evaluated. The first is max(x,y),
which evaluates to the larger of the two values, and the second is
simply z. So, what we get out of this is the maximum
of all three values.
The most important thing I can tell you about composing functions, is that there is really nothing to talk about. Function arguments are given in your code by expressions, right? And those expressions are evaluated before calling the function to produce the argument objects that are passed to the function. So, whether or not the argument expressions themselves contain function calls is immaterial — the system works the same.