|
|
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 379
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 380
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 381
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 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 (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 382
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 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 383
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 384
// 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 385
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 386
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 387
{
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 388
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. |
|
|
|