|
|||||
CS201
Introduction to Programming
Lecture
Handout
Introduction
to Programming
Lecture
No. 30
Reading
Material
Deitel
& Deitel - C++ How to
Program
Chapter.
3
3.17
Summary
13)
Reference
data type
14)
Example
1
15)
Difference
Between References and
Pointers
16)
Dangling
References
17)
Example
2
Reference
data type
Out today's
topic is about references.
This is a very important
topic from the
C++
prospective.
Today we will see what is a
reference, how can we use
them. C++ defines a
thing by
which we can create an alias
or synonym of any data type.
That synonym is
called
reference. How do we declare a
reference? We declare it by using &
operator.
Now it is
little bit confusing. We have
used & as address-of operator
and here we are
using it
for referencing. We will
write as
int
&i;
It means
that i
is a
reference to an integer. Keep
that statement very clear in
your mind. It
is easier
to read from right to left.
A reference is a synonym. If we want to
give two
names to
same thing then we use
reference. Reference has to be
initialized when it is
declared.
Suppose if we have an integer as
i
and we
want to give it second name.
We will
reference
it with j
as:
int
&j = i;
We
declared an integer reference
and initialized it. Now
j
is
another name for i. Does
it
mean
that it creates a new
variable? No, its not
creating a new variable. Its
just a new
Page
378
CS201
Introduction to Programming
name
for the variable which
already exists. So if we try to manipulate
i
and
j
individually,
we will
came to know that we have
been manipulating the same
number.
Lets
take a look at a very simple
example. In the main function we
take an int variable i
and
then we write int
&j = i; Now we
assign some value (say
123) to i. Now
display the
value of
i
using
cout. It will
show its value as 123.
Display the value of j
using
cout.
We
will
not use & operator to display
the value of j. We will
only use it at the time
of
declaration
and later we don't need
it. The & is not
reference operator rather it
acts as
reference
declarator. The value of j
will be
same as of i
i.e.
123.
int
i;
int
&j = i;
i =
123;
cout
<< "\n The value of i = " <<
i;
cout
<< "\n The value of j = " <<
j;
Now
what will happen if we increment
i
as
i++;
and
print the values of i
and
j. You
will
note
that the value of i
and
j
both
have been incremented. We
have only incremented i
but
j
is
automatically incremented. The
reason is that both are
referring to the same
location
in the
memory. j
is
just another name for i.
What is
the benefit of reference and where
can we use it? References
are synonyms and
they
are not restricted to int's,
we can have reference of any
data type. We can also
take
reference
of a class. We wrote a function to
show the use of pointers.
That function is
used to
interchange two numbers. If we
have two integers x
and
y. We want
that x
should
contain
the value of y
and
y
should
get the value of x. One
way of doing this is in
the
main
program i.e.
int x =
10;
int y =
20;
int
tmp;
tmp =
y;
y =
x;
x =
tmp;
The
values of both x
and
y
have
been interchanged. We can
also swap two numbers
using
a
function. Suppose we have a swap
function as swap(int
x, int y) and we
write the above
code in
it, what will happen?
Nothing will be changed in
the calling program. The
reason
is call
by value. So when the main
function calls the function
swap(x,
y).
The values of x
and
y
will be
passed to the swap function.
The swap function will get
the copies of these
variables.
The changes made by the
swap function have no effect on
the original
variables.
Swap function does interchange
the values but that
change was local to
the
swap
function. It did not effect anything in
the main program. The
values of x
and
y
in
the
main
program remains same.
Page
379
CS201
Introduction to Programming
We said
that to execute actual swap
function we have to call the
function by reference.
How we
did that. We did not
send x
and
y
rather we
sent the addresses of x
and
y.
We
used
address operator to get the
addresses. In the main
function we call swap
function as
swap(&x,
&y); In this
case we passed the addresses
of two integers. The prototype of
the
swap
function is swap(int*,
int*) which
means that swap function is
expecting pointers of
two
integers. Then we swap the
values of i
and
j
using
the * notations. It works
and in the
main
program, the values are
interchanged. This was a clumsy
way. We can use
reference
in this
case with lot of ease.
Let us see how we can do
that. Lets rewrite the
swap
function
using references. The prototype
will be as:
swap
(int &i, int &j);
Swap is a
function that is expecting i
as a
reference to an integer and
the second argument
is j
which is
also a reference to an integer.
The calling function has to
pass references.
What
will we write in the body of
the function? Here comes the
elegance of the
references.
In the body we will treat i
and
j
as
they are ordinary integers. We
will take a
temporary
integer and interchange the
values of i
and
j.
swap
(int &i, int &j)
{
int
temp;
temp =
x;
x =
y;
y =
temp;
}
In the
main program, you will
see that the values of
two integers have been
interchange.
What is
the way to call this
function? In the main program, we
will call this function
as
swap(x,
y).
Its an ordinary call but the
function is expecting addresses which
is
automatically
done. The memory locations of
the integers are passed
and the function is
interchanging
the original numbers. This
is one beautiful example in which we
avoided
all
the cumbersome of pointer notation. What
is the downfall of this? As nothing
comes
for
free. In this case when
you are reading the
main function you will
see swap
(x, y)
which
seems a call by value. This
is a rule of C/C++ that when we
pass two variables to
some
function they are passed by
values. You will have to
look for the definition of
swap
function
to realize that it is not call by
value but is call by
reference. Second thing is if
we
have
another swap function, which is receiving
two integers. Can we define
two
functions as
swap(int
x, int y) and
swap(int
&x, int &y)? One
function is receiving two
integers
and other is receiving two
references of integers. Can we do
that? Types are
different
so we can overload. Unfortunately
not, in the main function
the way to call
both
functions is
same i.e. swap(x,
y).
How does the compiler
know that which functions
is
being
called? There is no way for
the compiler to find out.
Therefore there is an
ambiguity
and that is not allowed.
The only thing to realize is
the side effect. Side
effects
are
critical to take care of
whenever you are doing call
by reference. Here in this example
we do
want that two numbers
should be interchanged. There
may be some situation
where we
want to send the references
and don't want that
original data should
be
Page
380
CS201
Introduction to Programming
affected.
These situations arise when we
want to pass a large data
structure to a function.
To
understand this we have to
understand how the function
call executes. We
have
discussed
it before, now lets recap
it. In real world, suppose I
am reading a book. While
reading I
notice a word which I think I
have to look up. I stop
reading, markup the
page
and
then look that word in
dictionary or encyclopedia. While
reading the definition
of
that
word I look another special
word which I need to lookup.
I put a marker here and
go
for
looking the definition of
that new word. Eventually I
understand all the words I
need
to look
up. Now I want to go to the
point at which I left the
study. I close the
dictionary
or
encyclopedia and goes back
to the original book which I was
studying. Now I
understand
all the words in my study
and continue the study
from the point where I
left.
If we
think about it, it was a
function call. We were doing some
work, suddenly we call
a
function
and stop that work
and execution went to the
function. When the execution of
the
function came to end, we
came back to our calling
function and continued with
it.
Computers
do the same work with
stack. So when the program
comes back from
the
function
it should know the point at
which it lefts. We supposed
here a word to look
up,
now
consider that it was a paragraph or
essay I am going to look up.
Now to lookup that
essay in
other books I have to take
the entire paragraph or
essay with me to that
book.
Think
about the stack. On the
stack, the original
condition of the program (state)
has
saved.
Now we put our essay or
paragraph on it and then
opened the other book
and
searched
the book for this essay. In
this way, we want to explain
that the thing we
passed
to the
function from the main was
itself a huge/large thing
(as we resemble it
with
paragraph
or essay). So there was a big
overhead in writing that
thing out into a
temporary
space in memory and then
picking it up and looking it
up.
We can
make this process more
efficient. The issue is that
in this example we do not
want
to change
the paragraph or essay which
we are going to look up. We
only want to look it
up. We
want only to use it but
don't want to change its
words. Unfortunately the
baggage
that
comes with doing this is
that first make a copy of
this (essay) then go with
this copy
and
when the work with it
ends, leave (through away)
the copy and start the
original
work.
This is inefficient.
But if we
took the reference of that
essay and passed the
address of it and went to
the
function
to look it up. There is a
danger that comes with
the address, that is while
looking
up that
essay I underlined different
words and when I came
back to original book I saw
that
these line marks were also
there. Thus we passed something by
value rather we
passed
something by reference. By passing the
reference, we actually pass
the original.
Think
about it in another way. We go to a
library and said the
librarian to issue us a book,
which we
want to take home for study.
Suppose, that book is the
only one copy available
in the
library (or in the world).
The librarian will not
issue the book. Because it is
the
only copy
available in the world. He does
not want to issue this
original book to someone
as
someone can marks different lines
with a pen and thus
can damage the original
book.
The
librarian will do that he
will take a photocopy of
that book and issue it.
Making a
photocopy
of the book and then take
the book is a bothersome
work.
Here we
don't want to damage the
book. We just want to read
it. But can I somehow
take
the
original book? Put it in a
special polythene bag and
give it to you in such a way
that
Page
381
CS201
Introduction to Programming
you
can read it without damaging
it. By doing this we get efficiency
but danger is still
there.
This is actually a call by
reference. We have the
reference (the original book).
If
we do something to
the original book, the
library book will be damaged.
Can we
somehow
prevent this from happening?
And also have the
efficiency of not having to
make a
copy.
Now
come back to the computer
world. Suppose we have a
data structure. There is
a
string of
1000 characters in it. We
want to pass that data
structure to a function. If we
pass it
by value which is sake, the
original structure will not
be affected. We first
will
copy that
string of 1000 characters at some
place, which is normally
made on the stack.
Then
the function will be called.
The function will take
the copy of these 1000
characters
and
will manipulate it. Then it
will give the control
back to the caller program and
will
destroy
that copy of string. For efficiency, we
want that instead of making
a copy of this
string,
its reference should be
written. We have been doing
this with pointers
and
addresses.
So we write there the
address and pass it to the
function. How we can
prevent
the
side effects? There may be
these side effects with
references. So be very careful
while
using
references with function
calls.
Can we do
something to prevent any changes?
The way we do it is by using
the const
key
word.
When we write the const
key
word with the reference, it
means that it is a
reference
to some
thing but we cannot change
it. Now we have an elegant
mechanism. We can get
the
efficiency of call by reference
instead of placing a string of 1000
characters on the
stack, we
just put the address of
the string i.e. reference on the
stack. In the prototype of
the
function, it is mentioned that it takes a
const. This is
a reference that may be to a
char,
int,
double or whatever but it is a const. The
function cannot change it.
The function gets
the
address, does its work
with it but cannot change
the original value. Thus, we
can have
an
efficiency of a call by reference
and a safety of a call by
value. To implement all
this
we could
have used the key
word const
with an
address operator or a pointer but we
can
use a
reference that is an elegant
way. There is no need in the
function to dereference a
reference
by using * etc, they are
used as ordinary variable names.
Example
1
Now
let us have an example. Here we defined a
structure bigone
that
has a string of 1000
characters.
Now we want to call a
function by three different
ways to manipulate this
string.
The first way is the
call by value, which is a default
mechanism, second is the
call
by
reference using pointers and
the third way is call by
reference using
reference
variables.
We declared the prototypes of
these functions. Here we declared
three
functions.
The first function is valfunc
which
uses a call by value. We
simply wrote the
value of
the structure. The function
prototype is as under.
void
valfunc( bigone v1 );
The
second function is ptrfunc
in
which we used call by
reference using pointers.
We
passed a
pointer to the structure to this
function. The prototype of it is as
follows.
void
ptrfunc( const bigone *p1
);
The
third function is reffunc
which
uses the way of calling by
reference using
references.
We wrote
its prototype as
Page
382
CS201
Introduction to Programming
void
reffunc( const bigone &r1
);
Note
that we wrote & sign with
the name of the variable in
the prototype of the
function,
we will
not write it in the call of
the function.
In the
main program, we called these
function. The call to the
valfunc
is a
simple one we
just
passed the name of the
object of the structure i.e. v1. As the
function is called by
using
the call by value the
manipulation in the function
will not affect the original
value.
We wrote
it as:
valfunc (
bo );
In this
call a copy of bo
is
placed on the stack and
the function uses that
copy.
Next we
called the function ptrfunc. We
passed the address of the
structure to ptrfunc
by
using
the & operator. Here we are
talking about the function
call (not function prototype)
and in
function call we write ptrfunc
( &bo ) ; which
means we passed the address
of bo
(the
object of structure) to the function.
The efficiency here is that
it writes only the
address
of the object to the stack
instead of writing the whole
object.
The
call to the third function
reffunc
is
simple and looks like
the call by value. There
is
no
operator used in this call
it is simply written
as:
reffunc (
bo ) ;
Here we
cannot overload the valfunc
and reffunc, their names must be
different.
Otherwise
the calls look same and
become ambiguous.
The
pointer call and reference
call are sending the
references to the original
structures so
these
are dangerous. If we want to
prevent the function from
changing that then we
should
define the function by const
keyword
with its argument pointer or
reference. Then
the
function can not modify
the original value, it can
only read it. So by this we
get the
efficiency
of the call by reference and
the safety of the call by
value.
The
complete code of the example is given
here.
//
Reference parameters for
reducing overhead
// and
eliminating pointer notation
#include
<iostream.h>
// A big
structure
struct
bigone
{
int
serno;
char
text[1000]; // A lot of
chars
} bo =
{123, "This is a BIG
structure"};
// Three
functions that have the
structure as a parameter
void
valfunc( bigone v1 );
// Call
by value
void
ptrfunc( const bigone *p1
);
// Call
by pointer
void
reffunc( const bigone &r1
);
// Call
by reference
Page
383
CS201
Introduction to Programming
// main
program
void
main()
{
valfunc(
bo );
//
Passing the variable
itself
ptrfunc( &bo
);
//
Passing the address of the
variable
reffunc(
bo );
//
Passing a reference to the
variable
}
//Function
definitions
// Pass
by value
void
valfunc( bigone v1 )
{
cout
<< '\n' << v1.serno;
cout
<< '\n' << v1.text;
}
// Pass
by pointer
void
ptrfunc( const bigone *p1
)
{
cout
<< '\n' << p1->serno; // Pointer
notation
cout
<< '\n' <<
p1->text;
}
// Pass
by reference
void
reffunc( const bigone &r1
)
{
cout
<< '\n' << r1.serno;
//
Reference notation
cout
<< '\n' << r1.text;
}
Following
is the output of the above
program.
123
This is a
BIG structure
123
This is a
BIG structure
123
This is a
BIG structure
Difference
Between References and
Pointers
The
reference in a way keeps the
address of the data entity.
But it is not really an
address
it is a
synonym, it is a different name
for the entity. We have to
initialize the
reference
when we
declare it. It has to point
to some existing data type
or data value. In
other
words, a
reference cannot be NULL. So
immediately, when we define a reference,
we
have to
declare it. This rule does
not apply to functions. When we are
writing the
argument
list of a function and say
that it will get a reference
argument, here it is
not
Page
384
CS201
Introduction to Programming
needed to
initialize the reference.
This reference will be
passed by the calling
function.
But in
the main program if we declare a
reference then we have to
initialize it. When a
reference
is initialized, we cannot reassign
any other value to it.
For example, we have ref
that is a
reference to an integer. In the
program we write the line
int
&ref = j ;
Here j
is an
integer which has already
been declared. So we have
declared a reference
and
initialized
it immediately. Suppose we have an
other integer k. We
cannot write in the
program
ahead as ref = k;
Once a
reference has defined, it
always will refer to the
same
integer
location as j. So it
will always be pointing to
the same memory location. We
can
prove
this by printing out the
address of the integer variable
and the address of
the
reference
that points to it.
In programming,
normally we do not have a
need to create a reference variable to
point to
another
data member or data variable that exists,
because creating synonym
that means
two
names for the same
thing, in a way is confusing. We don't
want that somewhere
in
the
program we are using i
(actual
name of variable) and somewhere
ref
(reference
variable)
for manipulating the same
data variable. The main
usage of it is to implement
the
call by reference through an
elegant and clean interface.
So reference variables
are
mostly
used in function calls.
The
difference between pointers and
references is that we can do
arithmetic with
pointers.
We can
increment, decrement and reassign a
pointer. This cannot be done
with
references.
We cannot increment, decrement or
reassign references.
References
as Return Values
A
function itself can return a
reference. The syntax of declaration of
such a function will
be as
under.
datatype&
function_name (parameter list)
Suppose
we have a function myfunc
that
returns the reference to an
integer. The
declaration
of it will be as:
int &
myfunc() ;
Dangling
Reference
The
functions that return reference
have danger with it.
The danger is that when we
return
a value
from such a function, that
value will be reference to
some memory location.
Suppose
that memory location was a
local variable in the function
which means we
declare a
variable like int
x; in
the function and then
returns its reference. Now
when the
function
returns, x
dies
(i.e. goes out of scope). It
does not exist outside
the function. But
we have
sent the reference of that
dead variable to the calling
function. In other
words,
the
calling program now has a
reference variable that points to
nowhere, as the thing
(data
variable) to which it points does not
exist. This is called a dangling
reference. So be
careful
while using a function that
returns a reference. To prevent dangling
reference the
functions returning
reference should be used
with global variables. The
function will
return a
reference to the global variable
that exists throughout the program
and thus there
will be
no danger of dangling reference. It can
be used with static variables
too. Once the
Page
385
CS201
Introduction to Programming
static
variables are created, they
exist for the life of
the program. They do not
die. So
returning their
reference is all
right.
So,
never return a reference to a
local variable otherwise, there will be a
dangling
reference.
Some compilers will catch it
but the most will not.
The reason is that
the
function
that is returning a reference has
defined separately. It does
not know whether the
reference
is to a global or local variable, because
we can do many manipulations in it
and
then
return it. But normally
compilers will catch this
type of error.
Example
2
Let us
look at an example of functions returning references.
First, we declare a global
variable
that is an integer called myNum
and
say it is zero. Then we
declare a function
num
that
returns a reference to an integer.
This function returns myNum, the
global
variable, in
the form of reference. So
now when there will be a
function call, the return
of
the
function will be a reference to
the global variable called myNum. Now we
can write
the
main function. Here we write
myNum =
100 ; This
assigns a value 100 to the
global
variable.
Next we write
int
i ;
i =
num () ;
Now a
reference to myNum
is
returned. We would want to
assign a reference to a
reference
but we can use it as an ordinary variable.
Thus that value is assigned
to i.
Now
look at the next line
which says num
() = 200 ; We know
that the left hand
side of
the
assignment operator can only
be a simple variable name, what we called
l-value (left
hand
side value). It cannot be an expression,
or a function call. But here
in our program
the
function call is on left
hand side of the assignment.
Is it valid? In this case it is
valid,
because
this function called num
is
returning a reference to a global variable. If it
returns
a
reference, it means it is a synonym. It is
like writing myNum =
200 ; The
example
shows
that it can be done but it
is confusing and is a bad idea. We
can put a reference
returning
function on the left hand
side of an assignment statement
but it is confusing and
bad
idea.
Following
is the code of the
example.
/*Besides
passing parameters to a function,
references can also be used
to return values
from a
function */
#include
<iostream.h>
int
myNum = 0;
// Global
variable
int&
num()
{
return
myNum;
}
void
main()
Page
386
CS201
Introduction to Programming
{
int
i;
i =
num();
cout
<< " The value of i = " << i <<
endl;
cout
<< " The value of myNum = "
<< myNum << endl;
num() =
200; // mynum set to
200
cout
<< " After assignment the
value of myNum = " << myNum
<< endl;
}
Following
is the output of the
program.
The
value of myNum = 0
After
assignment the value of myNum =
200
The
references are useful in implementing a
call by reference in an efficient fashion
and
writing
the function very elegantly
without using dereference
operators.
We use
& sign for declaring a reference. In
the program code, how do we
find out that it
is a
reference or an address is being
taken? The simple rule is
that if in the
declaration
line
there is reference symbol (& sign)
with the variable name then
that is a reference
declaration.
These will be like int
&i, float &f and
char
&c etc. In
the code whenever we
have
simply &i, it means we are
taking address. So it's a
simple rule that when, in
the
code, we
see a data type followed by
& sign, it's a reference. And
when the & sign is
being
used in the code with a
variable name then it is the
address of the variable.
In C and
C++ every statement itself
returns a value. It means a
statement itself is a
value.
Normally
the value is the value of
left hand side. So when we
write a =
b;
the value of b
is
assigned to a
and
the value of a
becomes
the value of the entire
statement. Therefore
when we
write a = b = c
;
first b = c
executes
and the value of c
is
assigned to b. Since
b
= c
is a
statement and this statement
has the value of b. Now
a
takes
the value of this
statement
(which happened to be b). So a = b
also
works. Similarly a + b + c
also
works
in the
same way that the
value of c
is
added to b
and
then this result is added to
a.
What
happens when we write cout
<< "The value of integer is " << i
<< endl ;
Here
first extreme right part
will be executed and then
the next one and so on or
the other
way. On
the screen the "The
value of integer is" displayed
first and then the
value of the i
and in
the end new line. So it is
moving from left to right.
When cout
gets
the first part
i.e. "The
value of integer is", this
is a C statement. When this will be
executed, the
sentence
"The value of integer is" is
displayed on the screen. But
what will be its
value?
That
has to do something with the
next << part and is
needed with this << sign. We
know
that we
need cout
on
the left side of << sign. So
actually what happened is
when the first
part of
the statement is executed. When
the statement cout
<< " The value of integer
is"
executed
cout
is
returned. The next part is
<< i
and it
becomes cout
<< i; the
value of i
is
printed
and as a result of the
statement cout
is
returned again which
encounters with <<
endl;
and a
new line is inserted on the
screen and cout
is
returned as a result of
the
statement
execution. The return of the complete
statement remains cout. The
cout
is
stream,
it does not have value
per se. The reference to
the stream is returned. The
same
Page
387
CS201
Introduction to Programming
reference
which we have discussed
today. The same thing
applies to operators like +, -,
*,
/. This
will also apply to = (assignment
operator) and so on. We will
be using lot of
reference
variables there.
Summary
We have
learned a new data type i.e.
reference data type. We said
that reference is
synonym
or alias for another type of
data. Take int's synonym or
double's synonym. In
other
words, it's the second
name of a variable. Then we talk
about some do's and
dont's.
Normally
we do not use two names
for the same variable. It's
a bad idea and leads
to
confusing
the programmer. Then we found the most
useful part of using a reference.
If
we have
to implement call by reference
with function then using
the prototype of the
function
which is expecting references and it
leads to clean programming. You
use the
names of
the arguments without using
any dereferencing operator
like *. The most useful
part is
implementing the call by
reference. Then we looked at the
difference of pointers
and
references. We cannot increment the
reference variable. Arithmetic is not
allowed
with
references but most importantly,
reference variables must be initialized
when they
are
declared. This is import. We
can declare pointers and
later can assign it some
value.
The
use of reference with
classes will be covered
later. We have also seen a
preview of
the
usage of references. In that
preview we have learned new
things that every
statement
itself
has some value and
that value is returned. Use it or
not it's a different issue.
We call
a
function on a single line like
f(x);
may be
f(x)
returns
some value and we did
not use it.
Not a
problem. Similarly if we say a = b;
this
statement itself have some
value whether
we use it
or not. Then we see how
the cout statement is
executed. Every part of
the
statement
returns some value which is
the reference to cout
itself.
It becomes the
reference
to the stream.
How
these references will be
declared and used? We will
cover this with
operator
overloading.
Try to write some programs
using references and
implement call by
reference
using references instead of
pointers.
Tips
·
The
use of reference data type is
the implementation of call by
reference in an
elegant
way.
·
We cannot
do arithmetic with references
like pointers.
·
Reference
variables must be initialized immediately
when they are
declared.
·
To avoid dangling
reference, don't return the
reference of a local variable from
a
function.
·
In functions
that return reference, use
global or static variables.
·
The
reference data types are
used as ordinary variables without
any dereference
operator.
Page
388
Table of Contents:
|
|||||