Lecture
Handout
Introduction
to Programming
Lecture No. 10
Reading Material
Deitel
& Deitel – C++ How to Program Chapter
3
3.7,
3.11, 3.12, 3.14, 3.17
Contents
• Scope of Identifiers
• Functions
- Call by Value
- Call by Reference
Header
Files
You
have already been using a header file from day-zero. You know that we used to
write
at the top before the start of the main() function <iostream.h>, with
‘.h’ as an
extension,
you might have got the idea that it is a header file.
Now
we will see why a Header file is used.
In
the previous lecture, we discussed a little bit about Function Prototypes. One
thing is
Declaration
and other is Definition. Declaration can also be called as 'Prototype'.
Normally,
if we have lot of functions and want to use them in some other function or
program,
then we are left with only one way i.e. to list the prototypes of all of them
before
the body of the function or program and then use them inside the function or
program.
But for frequent functions inside a program, this technique increases the
complexity
(of a program). This problem can be overcome by putting all these function
prototypes
in one file and writing a simple line of code for including the file in the
program.
This code line will indicate that this is the file, suppose 'area.h' containing
all
the
prototypes of the used functions and see the prototypes from that file. This is
the basic
concept
of a header file.
So
what we can do is:
- Make our own header file which is usually a
simple text file with '.h' extension ('.h'
extension
is not mandatory but it is a rule of good programming practice).
- Write function prototypes inside that file.
(Recall that prototype is just a simple line
of
code containing return value, function name and an argument list of data types
with
semi-colon at the end.)
- That file can be included in your own program
by using the ‘#include’ directive and
that
would be similar to explicitly writing that list of function prototypes.
Function
prototypes are not the only thing that can be put into a header file. If you
remember
that we wrote a program for calculating Area of a Circle in our previous
lectures.
We used the value of 'pi' inside that and we have written the value of 'pi' as
3.1415926.
This kind of facts are considered as Universal Constants or Constants within
our
domain of operations . It would be nice, if we can assign meaningful names to
them.
There
are two benefits of doing this. See, We could have declared a variable of type
double
inside the program and given a name like 'pi':
double pi
= 3.1415926;
Then
everywhere in the subsequent calculations we can use 'pi'.
But
it is better to pre-define the value of the constant in a header file ( one set
for all) and
simply
including that header file, the constant ‘pi’, is defined. Now, this meaningful
name
‘pi’ can be used in all calculations instead of writing the horrendous number
3.1415926
again and again.
There
are some preprocessor directives which we are going to cover later. At the
moment,
we will discuss about ‘#define’ only. We define the constants using this
preprocessor
directive as:
#define
pi 3.1415926
The
above line does a funny thing as it is not creating a variable. Rather it
associates a
name
with a value which can be used inside the program exactly like a variable. (Why
it
is
not a variable?, because you can’t use it on the left hand side of any
assignment.).
Basically,
it is a short hand, what actually happens. You defined the value of the ‘pi’
with
‘#define’
directive and then started using ‘pi’ symbol in your program. Now we will see
what
a compiler does when it is handed over the program after the writing process.
Wherever
it finds the symbol ‘pi’, replaces the symbol with the value 3.1415926 and
finally
compiles the program.
Thus,
in compilation process the symbols or constants are replaced with actual values
of
them.
But for us as human beings, it is quite readable to see the symbol ‘pi’.
Additionally,
if
we use meaningful names for variables and see a line ‘2 * pi * radius’, it
becomes
obvious
that circumference of a circle is being calculated. Note that in the above
statement,
‘2 * pi * radius’; 2 is used as a number as we did not define any constant for
it.
We
have defined ‘pi’ and ‘radius’ but defining 2 would be over killing.
Scope
of Identifiers
An
'Identifier' means any name that the user creates in his/her program. These
names can
be
of variables, functions and labels. Here the scope of an identifier means its
visibility.
We
will focus Scope of Variables in our discussion.
Suppose
we write the function:
void
func1()
{
int
i;
.
. . //Some
other lines of code
int
j = i+2; //Perfectly
alright
.
. .
}
Now
this variable ‘i’ can be used in any statement inside the function func1(). But
consider
this variable being used in a different function like:
void
func2()
{
int
k = i + 4; //Compilation error
.
. .
}
The
variable ‘i’ belongs to func1() and is
not visible outside that. In other words, ‘i’ is
local
to func1().
To
understand the concept of scope further, we have to see what are Code Blocks? A
code
block begins with ‘{‘ and ends with ‘}’.Therefore, the body of a function is
essentially
a code block. Nonetheless, inside a function there can be another block of
code
like 'for loop' and 'while loop' can have their own blocks of code
respectively.
Therefore,
there can be a hierarchy of code blocks.
A
variable declared inside a code block becomes the local variable for that for
that block.
It
is not visible outside that block. See the code below:
void
func()
{
int
outer; //Function
level scope
. . .
{
int
inner; //Code
block level scope
inner
= outer; //No
problem
.
. .
}
inner
++; //Compilation
error
}
Please
note that variable ‘outer’ is declared at function level scope and variable
‘inner’ is
declared
at block level scope.
The
‘inner’ variable declared inside the inner code block is not visible outside it
. In other
words,
it is at inner code block scope level. If we want to access that variable
outside its
code
block, a compilation error may occur.
What
will happen if we use the same names of variables at both function level scope
and
inner
block level scope? Consider the following code:
Line
1. void increment()
2.
{
3.
int
num; //Function
level scope
4.
.
. .
5.
{
6.
int
num; //Bad
practice, not recommended
7. . . .
8.
num
++; //inner
num is incremented
9. . . .
10.
}
11.
}
Note
that there is no compilation error if the variable of the same name ‘num’ is declared
at
line 6 inside the inner code block (at block level scope). Although , there is
no error in
naming
the variables this way, yet this is not recommended as this can create
confusion
and
decrease readability. It is better to use different names for these variables.
Which
variable is being used at
line 8? The answer is the ‘num’ variable declared for
inner
code block (at block level scope). Why is so? It is just due to the fact that
the outer
variable
‘num’ (at function level scope) is hidden in the inner code block as there is a
local
variable of the same name. So the local variable ‘num’ inside the inner code
block
over-rides
the variable ‘num’ in the outer code block.
Remember,
the re-use of a variable is perfectly alright as we saw in the code snippet
above
while using ‘outer’ variable inside the inner code block. But re-declaring a
variable
of
the same name like we did for variable ‘num’ in the inner code block, is a bad
practice.
Now,
is there a way that we declare a variable only once and then use it inside all
functions.
We have already done a similar task when we wrote a function prototype
outside
the body of all the functions. The same thing applies to declaration of
variables.
You
declare variables outside of a function body (so that variable declarations are
not
part
of any function) and they become visible and accessible inside all functions of
that
file.
Notice that we have just used a new word ‘file’.
A
file or a source code file with extension ‘.c’ or ‘.cpp’ can have many
functions inside.
A
file will contain one main()
function maximum and rest of the functions as many as
required.
If you want a variable to be accessible from within all functions, you declare
the
variable
outside the body of any function like the following code snippet has declared
such
a variable ‘size’ below.
#include
<iostream.h>
.
. .
//
Declare your global variables here
int
size;
.
. .
int
main( … )
{
.
. .
}
Now,
this ‘size’ is visible in all functions including main(). We call this as 'file scope
variable'
or a 'global variable'. There are certain benefits of using global variables.
For
example,
you want to access the variable ‘size’ from anywhere in your program but it
does
have some pitfalls. You may inadvertently change the value of the variable
‘size’
considering
it a local variable of the function and cause your program to behave
differently
or affect your program logic.
Hence,
you should try to minimize the use of global variables and try to use the local
variables
as far as possible. This philosophy leads us to the concept of Encapsulation
and
Data
Hiding that encourages the declaration and use of data locally.
In
essence, we should take care of three levels of scopes associated with
identifiers:
global
scope, function level scope and block level scope.
Let's
take a look of very simple example of global scope:
#include
<iostream.h>
//Declare
your global variables here
int
i;
void
main()
{
i
= 10;
cout
<< “\n” << “In main(), the value of i is: “ << i;
f();
cout
<< “\n” << “Back in main(), the value of i is: “ << i;
}
void
f()
{
cout
<< “\n” << ”In f(), the value of i is: “ << i;
i
= 20;
}
Note
the keyword ‘void’ here, which is used to indicate that this function does not
return
anything.
The
output of the program is:
In
main(), the value of i is: 10
In
f(), the value of i is: 10
Back
in main(), the value of i is: 20
Being
a global variable, ‘i’ is accessible to all functions. Function f() has changed
its
value
by assigning a new value i.e. 20.
If
the programmer of function f() has changed the value of ‘i’ accidentally taking
it a
local
variable, your program’s logic will be affected.
Function Calling
We
have already discussed that the default function calling mechanism of C is a
'Call by
Value'.
What does that mean? It means that when we call a function and pass some
arguments
(variables) to it, we are passing a copy of the arguments (variables) instead
of
original
variables. The copy reaches to the function that uses it in whatever way it
wants
and
returns it back to the calling function. The passed copy of the variable is
used and
original
variable is not touched. This can be understood by the following example.
Suppose
you have a letter that has some mistakes in it. For rectification, you depute
somebody
to make a copy of that letter, leave the original with you and make corrections
in
that copy. You will get the corrected copy of the letter and have the unchanged
original
one
too. You have given the copy of the original letter i.e. the call by value
part.
But
if you give the original letter to that person to make corrections in it, then
that person
will
come back to you with the changes in the original letter itself instead of its
copy.
This
is call by reference.
The
default of C is 'Call by Value'. It is better to use it as it saves us from
unwanted side
effects.
Relatively, 'Call by Reference' is a bit complex but it may be required
sometimes
when
we want the actual variable to be changed by the function being called.
Let's
consider another example to comprehend 'Call by Value' and how it works.
Suppose
we
write a main()
function and another small function f(int) to
call it from main().
This
function
f(
)
accepts an integer, doubles it and returns it back to the main()
function. Our
program
would look like this:
#include
<iostream.h>
void
f(int); //Prototype
of the function
void
main()
{
int
i;
i
= 10;
cout
<< “\n” << ” In main(), the value of i is: “ << i;
f(i);
cout
<< “\n” << ” Back in main(), the value of i is: “ << i;
}
void
f (int i)
{
i
*= 2;
cout
<< “\n” << “ In f(), the value of i is: “ << i;
}
The
output of this program is as under:
In
main(), the value of i is: 10
In
f(), the value of i is: 20
Back
in main(), the value of i is: 10
As
the output shows the value of the variable ‘i’ inside function main() did
not change, it
proves
the point that the call was made by value.
If
there are some values we want to pass on to the function for further
processing, it will
be
better to make a copy of those values , put it somewhere else and ask the
function to
take
that copy to use for its processing. The original one with us will be secure.
Let's
take another example of call by value, which is bit more relevant. Suppose we
want
to
write a function that does the square of a number. In this case, the number can
be a
double
precision number as seen below:
#include <iostream.h>
double
square (double);
void
main()
{
double
num;
num
= 123.456;
cout
<< “\n” << “ The square of “
<< num << “ is “ << square(num);
cout
<< “\n” << “ The current value of num is “ << num;
}
double
square (double x)
{
return
x*x;
}
'C' does not have built-in mathematical
operators to perform square, square root, log and
trigonometric
functions. The C language compiler comes along a complete library for
that.
All the prototypes of those functions are inside ‘<math.h>’. In order to
use any of
the
functions declared inside ‘<math.h>’, the following line will be added.
#include
<math.h>
Remember,
these functions are not built-in ones but library is supplied with the C-
compiler.
It may be of interest to you that all the functions inside ‘<math.h>’ are
called
by
value. Whatever variable you will pass in as an argument to these functions,
nothing
will
happen to the original value of the variable. Rather a copy is passed to the
function
and
a result is returned back, based on the calculation on that copy.
Now, we will see why Call by
Reference is used.
We
would like to use 'call by reference' while using a function to change the
value of the
original
variable. Let's consider the square(double)
function again, this time we want the
original
variable ‘x’ to be squared. For this
purpose, we passed a variable to the square()
function
and as a result, on the contrary to the ‘Call by Value’, it affected the
calling
functions
original variable. So these kinds of functions are ‘Call by Reference’
functions.
Let
us see, what actually happens inside Call by Reference?
As
apparent from the name ‘By Reference’, we are not passing the value itself but
some
form
of reference or address. To understand this, you can think in terms of
variables
which
are names of memory locations. We always access a variable by its name (which
in
fact is accessing a memory location), a variable name acts as an address of the
memory
location
of the variable.
If
we want the called function to change the value of a variable of the calling
function, we
must
pass the address of that variable to the called function. Thus, by passing the
address
of
the variable to the called function, we convey to the function that the number
you
should
change is lying inside this passed memory location, square it and put the
result
again
inside that memory location. When the calling function gets the control back
after
calling
the called function, it gets the changed value back in the same memory
location.
In
summary, while using the call by reference method, we can’t pass the value. We
have
to
pass the memory address of the value.
This introduces a new mechanism which is
achieved
by using ‘&’ (ampersand) operator in C language. This ‘&’ operator is
used to
get
the address of a variable. Let's look at a function, which actually is a
modification of
our
previous square()
function.
#include <iostream.h>
void
square(double);
void
main()
{
double
x;
x
= 123.456;
cout
<< “\n” << “ In main(), before calling square(), x = “ << x;
square(&x);
//Passing
address of the variable x
cout
<< “\n” << “ In main(), after calling square(), x = “ << x;
}
void
square(double* x) //read
as: x is a pointer of type double
{
*x =
*x * *x; //Notice
that there is no space in *x
}
Here
*x means whatever the x points to and &x means address of the variable x.
We will
discuss
Pointers in detail later.
We
are calling function square(double*) with
the statement square(&x)
that is actually
passing
the address of the variable x , not its value. In other words, we have told a
box
number
to the function square(double*) and
asked it to take the value inside that box,
multiply
it with itself and put the result back in the same box. This is the mechanism
of
‘Call
by Reference’.
Notice
that there is no return statement of square(double*)
as we
are putting the changed
value
(that could be returned) inside the same memory location that was passed by the
calling
function.
The
output of the program will be as under:
In
main(), before calling square(), x = 123.456
In
main(), after calling square(), x = 15241.4
By
and large, we try to avoid a call by reference. Why? Mainly due to the
side-effects, its
use
may cause. As mentioned above, it will be risky to tell the address of some
variables
to
the called function. Also, see the code above for some special arrangements for
call by
reference
in C language. Only when extremely needed, like the size of the data to be
passed
as value is huge or original variable is required to be changed, you should go
for
call
by reference, otherwise stick to the call by value convention.
Now
in terms of call by reference, we see that there are some places in ‘C’ where
the call
by
reference function happens automatically. We will discuss this later in
detail. For the
moment,
as a hint, consider array passing in ‘C’.
Recursive Function
This
is the special type of function which can call itself. What kind of function it
would
be? There are many problems and specific areas
where you can see the repetitive
behavior
(pattern) or you can find a thing, which can be modeled in such a way that it
repeats
itself.
Let
us take simple example of x10, how will we calculate it? There are many ways of
doing
it. But from a simple perspective, we can say that by definition x10 = x
* x9. So
what
is x9? It is x9 = x * x8 and so on.
We
can see the pattern in it:
xn = x
* xn-1
To
compute it, we can always write a program to take the power of some number. How
to
do
it? The power function itself is making recursive call to itself. As a
recursive function
writer,
you should know where to stop the recursive call (base case). Like in this
case,
you
can stop when the power of x i.e. n is 1 or 0.
Similarly,
you can see lot of similar problems like Factorials. A factorial of a positive
integer
‘n’ is defined as:
n!
= (n) * (n-1) * (n-2) * ….. * 2 * 1
Note
that
n!
= (n) * (n-1)!
and (n-1)!
= (n-1) * (n-2)!
This
is a clearly a recursive behavior. While writing a factorial function, we can
stop
recursive
calling when n is 2 or 1.
long
fact(long n)
{
if
(n <= 1)
return
1;
else
return n * fact(n-1);
}
Note
that there are two parts (branches) of the function: one is the base case (
which
indicates
when the function will terminate) and other is recursively calling part.
All
the problems can be solved using the iterative functions and constructs we have
studied
until now. So the question is: do we need to use recursive functions? Yes, it
adds
little
elegance to the code of the function but there is a huge price to pay for
this. Its use
may
lead to the problems of having memory overhead. There may also be stacking
overhead
as lots of function calls are made. A lot of functions can be written without
recursion
(iteratively) and more efficiently.
So
as a programmer, you have an option to go for elegant code or efficient code,
sometimes
there is a trade-off. As a general rule, when you have to make a choice out of
elegance
and efficiency, where the price or resources is not an issue, go for elegance
but
if
the price is high enough then go for efficiency.
‘C’
language facilitates us for recursive functions like lot of other languages but
not all
computer
languages support recursive functions. Also, all the problems can not be solved
by
recursion but only those, which can be separated out for base case, not
iterative ones.
Tips
- Header file is a nice mechanism to put
function prototypes and define constants
(global
constants) in a single file. That file can be included simply with a single
line
of
code.
- There are three levels of scopes to be taken
care of, associated with identifiers: global
scope,
function level scope and block level scope.
- For Function calling mechanism, go for ‘Call
by Value’ unless there is a need of ‘Call
by
Reference’.
Apply the recursive function where there is a
repetitive pattern, elegance is required
and
there is no resource problem.
Post a Comment
Don't Forget To Join My FB Group VU Vicky
THANK YOU :)