ZeePedia

Difference Between References and Pointers, Dangling References

<< Declaration of Friend Functions, Friend Classes
Operator Overloading, Non-member Operator Functions >>
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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:
  1. What is programming
  2. System Software, Application Software, C language
  3. C language: Variables, Data Types, Arithmetic Operators, Precedence of Operators
  4. C++: Examples of Expressions, Use of Operators
  5. Flow Charting, if/else structure, Logical Operators
  6. Repetition Structure (Loop), Overflow Condition, Infinite Loop, Properties of While loop, Flow Chart
  7. Do-While Statement, for Statement, Increment/decrement Operators
  8. Switch Statement, Break Statement, Continue Statement, Rules for structured Programming/Flow Charting
  9. Functions in C: Structure of a Function, Declaration and Definition of a Function
  10. Header Files, Scope of Identifiers, Functions, Call by Value, Call by Reference
  11. Arrays: Initialization of Arrays, Copying Arrays, Linear Search
  12. Character Arrays: Arrays Comparisonm, Sorting Arrays Searching arrays, Functions arrays, Multidimensional Arrays
  13. Array Manipulation, Real World Problem and Design Recipe
  14. Pointers: Declaration of Pointers, Bubble Sort Example, Pointers and Call By Reference
  15. Introduction, Relationship between Pointers and Arrays, Pointer Expressions and Arithmetic, Pointers Comparison, Pointer, String and Arrays
  16. Multi-dimensional Arrays, Pointers to Pointers, Command-line Arguments
  17. String Handling, String Manipulation Functions, Character Handling Functions, String Conversion Functions
  18. Files: Text File Handling, Output File Handling
  19. Sequential Access Files, Random Access Files, Setting the Position in a File, seekg() and tellg() Functions
  20. Structures, Declaration of a Structure, Initializing Structures, Functions and structures, Arrays of structures, sizeof operator
  21. Bit Manipulation Operators, AND Operator, OR Operator, Exclusive OR Operator, NOT Operator Bit Flags Masking Unsigned Integers
  22. Bitwise Manipulation and Assignment Operator, Programming Constructs
  23. Pre-processor, include directive, define directive, Other Preprocessor Directives, Macros
  24. Dynamic Memory Allocation, calloc, malloc, realloc Function, Dangling Pointers
  25. History of C/C++, Structured Programming, Default Function Arguments
  26. Classes and Objects, Structure of a class, Constructor
  27. Classes And Objects, Types of Constructors, Utility Functions, Destructors
  28. Memory Allocation in C++, Operator and Classes, Structures, Function in C++,
  29. Declaration of Friend Functions, Friend Classes
  30. Difference Between References and Pointers, Dangling References
  31. Operator Overloading, Non-member Operator Functions
  32. Overloading Minus Operator, Operators with Date Class, Unary Operators
  33. Assignment Operator, Self Assignmentm, Pointer, Conversions
  34. Dynamic Arrays of Objects, Overloading new and delete Operators
  35. Source and Destination of streams, Formatted Input and Output, Buffered Input/Output
  36. Stream Manipulations, Manipulators, Non Parameterized Manipulators, Formatting Manipulation
  37. Overloading Insertion and Extraction Operators
  38. User Defined Manipulator, Static keyword, Static Objects
  39. Pointers, References, Call by Value, Call by Reference, Dynamic Memory Allocation
  40. Advantages of Objects as Class Members, Structures as Class Members
  41. Overloading Template Functions, Template Functions and Objects
  42. Class Templates and Nontype Parameters, Templates and Static Members
  43. Matrices, Design Recipe, Problem Analysis, Design Issues and Class Interface
  44. Matrix Constructor, Matrix Class, Utility Functions of Matrix, Input, Transpose Function
  45. Operator Functions: Assignment, Addition, Plus-equal, Overloaded Plus, Minus, Multiplication, Insertion and Extraction