|
|||||
CS201
Introduction to Programming
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
·
Header
Files
·
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
Page
91
CS201
Introduction to Programming
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.
Page
92
CS201
Introduction to Programming
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
...
Page
93
CS201
Introduction to Programming
{
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
Page
94
CS201
Introduction to Programming
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;
Page
95
CS201
Introduction to Programming
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.
Page
96
CS201
Introduction to Programming
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:
Page
97
CS201
Introduction to Programming
#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
Page
98
CS201
Introduction to Programming
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'.
Page
99
CS201
Introduction to Programming
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
Page
100
CS201
Introduction to Programming
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'.
Page
101
CS201
Introduction to Programming
Apply
the recursive function where
there is a repetitive pattern, elegance
is required
-
and
there is no resource problem.
Page
102
Table of Contents:
|
|||||