|
|||||
CS201
Introduction to Programming
Lecture
Handout
Introduction
to Programming
Lecture
No. 25
Reading
Material
Deitel
& Deitel - C++ How to
Program
Chapter
3
3.16,
3.18, 3.20
Summary
·
Lecture
Overview
·
History
of C/C++
·
Structured
Programming
·
Limitations
of Structured Programming
·
Default
Function Arguments
·
Example
of Default Function Arguments
·
Placement
of Variable Declarations
·
Example
of Placement of Variable
Declarations
·
Inline
Functions
·
Example
of Inline Functions versus
Macros
·
Function
Overloading
·
Example
of Function Overloading
Lecture
Overview
From
this lecture we are starting
exciting topics, which we have
been talking about
many
times in previous
lectures. Until now, we have
been discussing about the
traditional
programming
following top down approach
using C/C++. By and large we
have been
using C
language, although, we also
used few C++ functions like
C++ I/O using cin
and
cout
instead
of standard functions of C i.e., printf()
and
scanf(). Today
and in
subsequent
lectures, we will talk about
C++ and its features.
Note that we are
not
covering Object
Oriented Programming here as it is a
separate subject.
Page
308
CS201
Introduction to Programming
History
of C/C++
C
language was developed by scientists of
Bell Labs in 1970s. It is
very lean and
mean
language,
very concise but with
lot of power. C conquered the
programming world and
took it
by storm. Major operating systems
e.g., Unix was written in C
language.
Going
briefly into the history of
languages, after the Machine
Language (language of 0s
and
1s), the Assembly Language
was developed. Using Assembly
language,
programmers
could use some symbolic
codes, which were easier to
understand by novice
people.
After that high-level
languages like COBOL,
FORTRAN were developed.
These
languages
were more English like and as a
result easier to understand
for us as human
beings.
This was the age of
spaghetti code where programs were not
properly structured
and their
branches were growing in every direction.
As a result, it is difficult o
read,
understand
and manage. These problems
lead to the innovation of
structured
programming
where a problem was broken into
smaller parts. But this
approach also had
limits.
In order to understand those
limits, we will see what is
structured programming
first
before going into its
limitations detail.
Structured
Programming
We have
learned so far, C is a language where
programs are composed of
functions.
Basically,
a problem is broken into small
pieces or modules and each
small piece
corresponds
to a function. This was the
top-down
structured programming approach.
We have
already discussed few rules
of structured programming, which
are still valid
and
will
remain valid in the future. Let's
reiterate those:
- Divide
and Conquer; one
should not write very
long functions. If a function is
getting
longer than two or three
pages or screens then it is
divided into smaller,
concise
and well-defined tasks.
Later each task becomes a
function.
- Inside the
functions, Single
Entry Single Exit rule
should be tried to obey as
much
as
possible. This rule is very
important for readability
and useful in managing
programs.
Even if the developer itself
tries to use the same
function after sometime, it
would be
easier for him to read his
own code if he has followed
the rules properly.
We try to
reuse our code as much as
possible. It is likely that we
may reuse our
code
or functions.
That reuse might happen
quite after sometime. Never think
that your
written
code will not change or
will not be used
again.
- You should
comment
your programs well.
Your comments are only not
used by
other
people but by yourself also,
therefore, you should write
useful and lots of
comments. At
least comment, what the
function does, what are
its parameters and
what
does it return back. The
comments should be meaningful and useful
about the
processing
of the function.
You
should use the principles of
structured programming as the
basis of your
programs.
Page
309
CS201
Introduction to Programming
Limitations
of Structured Programming
When we
design a functional program, the
data it requires to process, is an
entity that lies
outside
of the program. We take care of
the function rather than
the data it is going to
process.
When the problems became complex, we
came to know that we can't
leave the
data
outside. Somehow the data
processed by the program should be
present inside it as a
part of
it. As a result, a new
thought process became
prevalent that instead of
the program
driven by functions, a
program should be driven by
data. As an example, while
working
with
our Word processors when we
want a text to be bold,
firstly that text is
selected and
then we
ask Word to make it bold.
Notice in this example the
data became first and
then
the
function to make it bold. This is
programming driven by data. This
approach
originated
the Object Oriented Programming.
In the
early 1980s a scientist in Bell Labs
Bejarne Stroustrup started
working in
enhancing
C language to overcome the shortcomings
of structured approach.
This
evolution
of C language firstly known to be
C
with Classes, eventually called
C++.
Then
the
follow-up version of C++ is the
Java language. Some people
call Java as C plus plus
minus.
This is not exactly true
but the evolution has
been the same
way.
C++
does not contain the concept
of Classes only but some
other features were
also
introduced.
We will talk about those
features before we talk
about the classes.
Default
Function Arguments
While
writing and calling functions,
you might have noticed that
sometimes the
parameter
values remain the same for
most of the calls and others
keep on changing. For
example, we
have a function:
power(
long x, int n )
Where x
is the number to take power
of and n is the power to
which x is required to be
raised.
Suppose
while using this function
you came to know that
90% of the calls are
for
squaring
the number x in your problem
domain. Then this is the
case where default
function
arguments can play their role. When we
find that there are
some parameters of a
function
that by and large are
passed the same value.
Then we start using default
function
arguments
for those parameters.
The
default value of a parameter is provided inside
the function prototype or
function
definition.
For example, we could declare the default
function arguments for a
function
while
declaring or defining it. Below is
the definition of a very
simple function f()
that
is
called most of
the times with parameters
values of i as 1 and x as 10.5 most of
the times
then by
we can give default values to
the parameters as:
void f (
int i = 1, double x = 10.5
)
{
cout
<< "The value of i is: " <<
i;
cout
<< "The value of x is: " <<
x;
}
Page
310
CS201
Introduction to Programming
Now
this function can be called 0, 1 or 2
arguments.
Suppose
we call this function
as:
f();
See we
have called the function f()
without
any parameters, although, it
has two
parameters.
It is perfectly all right
and this is the utility of
default function arguments.
What do
you think about the output.
Think about it and then
see the output below:
The
value of i is: 1
The
value of x is: 10.5
In the
above call, no argument is
passed, therefore, both the
parameters will use
their
default
values.
Now if we
call this function
as:
f(2);
In this
case, the first passed in
argument is assigned to the
first variable (left most
variable)
i
and
the variable x
takes
its default value. In this
case the output of the
function
will be
as under:
The
value of i is: 2
The
value of x is: 10.5
The
important point here is that
your passed in argument is
passed to the first
parameter
(the
left most parameter). The
first passed in value is
assigned to the first
parameter,
second
passed in value is assigned to
the second parameter and so
on. The value 2
cannot
be
assigned to the variable x unless a
value is explicitly passed to
the variable i. See
the
call
below:
f(1,
2);
The
output of the function will be as
under:
The
value of i is: 1
The
value of x is: 2
Note
that even the passed in
value to the variable i
is
the same as its default
value, still to
pass
some value to the variable
x, variable
i
is
explicitly assigned a
value.
While
calling function, the
arguments are assigned to
the parameters from left to
right.
There is
no luxury or feature to use
the default value for the
first parameter and passed
in
value
for the second parameter.
Therefore, it is important to keep in
mind that the
parameters
with default values on left
cannot be left out but it is
possible for the
parameter
with default values on right
side.
Because
of this rule of assignment of values to
the parameters, while
writing functions,
the
default values are written
from right to left. For
example, in the above example of
function
f(), if the
default value is to be provided to the variable
x
only
then it should be
on the
left side as under:
void f(
int i, double x = 10.5
)
{
Page
311
CS201
Introduction to Programming
//
Display statements
}
If we
switch the parameters that
the variable x
with
default value becomes the
first
parameter
as under:
void f(
double x = 10.5, int i
)
{
//
Display statements
}
Now we
cannot use the default value
of the variable x, instead
we will have to supply
both of
the arguments. Remember,
whenever you want to use
default values inside a
function,
the parameters with default
values should be on the extreme
right of the
parameter
list.
Example
of Default Function
Arguments
// A
program with default arguments in a
function prototype
#include
<iostream.h>
void
show( int = 1, float = 2.3,
long = 4 );
void
main()
{
show();
// All
three arguments default
show( 5
);
// Provide
1st argument
show( 6,
7.8 );
// Provide
1st and 2nd
show( 9,
10.11, 12L ); // Provide all
three argument
}
void
show( int first, float
second, long third )
{
cout
<< "\nfirst = " <<
first;
cout
<< ", second = " <<
second;
cout
<< ", third = " <<
third;
}
The
output of the program is:
first =
1, second = 2.3, third =
4
first =
5, second = 2.3, third =
4
first =
6, second = 7.8, third =
4
first =
9, second = 10.11, third =
12
Page
312
CS201
Introduction to Programming
Placement
of Variable Declarations
This
has to do with the
declaration of the variables inside
the code. In C language, all
the
variables
are declared at the top of
the function or code block
and then we can use
them
later on
in the code. We have already
relaxed this rule, now, we
will discuss it
explicitly.
One of
the enhancements in C++ over
C is that a variable can be declared
anywhere in
the
function. The philosophy of this
enhancement is that a variables is
declared just
before it
is actually used in the
code. That will increase
readability of the
code.
It is not
hard and fast direction
but it is a tip of good
programming practice. One
can still
declare
variables at the start of
the program, function or code
block. It is a matter of style
and
convenience. One should be
consistent in his/her style.
We should
be clear about implications of declaring
variables at different locations.
For
example, we
declare a variable i
as
under:
{
// code
block
int
i;
...
...
}
The
variable i
is
declared inside the code
block in the beginning of it.
i
is
visible inside
the
code block but after
the closing brace of this
code block, i
cannot be
used. Be aware
of this,
whenever you declare a variable inside a
block, the variable i
is
alive inside that
code
block. Outside of that code
block, it is no more there and it
can not referenced
any
further.
Compiler will report an
error if it is tried to access outside
that code block.
You must
have seen in your books
many times, a for loop is
written in the
following
manner:
for
(int i = 0; condition;
increment/decrement statements )
{
...
}
i =
500;
// Valid
statement and there is no
error
The
variable i
is
declared with the for
loop statement and it is
used immediately. We
should be
clear about two points here.
Firstly, the variable i
is
declared outside of the
for
loop
opening brace, therefore, it is
also visible after the
closing brace of the for
loop.
So the
above declaration of i can
also be made as
under:
int
i;
for ( i =
0; condition; increment/decrement
statements)
{
...
}
Page
313
CS201
Introduction to Programming
This
approach is bit more clear
and readable as it clearly
declares the variable i
outside
the
for statement. But again, it
is a matter of style and personal
preference, both
approaches
are correct.
Example
of Placement of Variables
Declarations
//
Variable declaration
placement
#include
<iostream.h>
void
main()
{
// int
lineno;
for(
int lineno = 0; lineno < 3;
lineno++ )
{
int temp
= 22;
cout
<< "\nThis is line number "
<< lineno
<< "
and temp is " << temp;
}
if(
lineno == 4 ) // lineno still
accessible
cout
<< "\nOops";
// Cannot
access temp
}
The
output of the program is:
This is
line number 0 and temp is
22
This is
line number 1 and temp is
22
This is
line number 2 and temp is
22
Inline
Functions
This is
also one of the facilities
provided by C++ over C. In our
previous lectures, we
discussed
and wrote macros few
macros like max
and
circlearea.
While
using macros, we use the
name of the macro in our
program. Before the
compilation
process starts the macro
names are replaced by the
preprocessor with their
definitions
(defined with #define).
Inline
functions also work more or
less in the same manner as
macros. The functions
are
declared
inline by writing inline
keyword
before the name of the
function. This is a
directive
to the compiler and it
causes the full definition
of the function to be inserted
in
each
place the function is called.
Inserting individual copies of functions
eliminates the
overhead
of calling a function (such as loading
parameters onto the
stack).
We see
what are the advantages
and disadvantages of
it:
Page
314
CS201
Introduction to Programming
We'll
discuss the disadvantages
first. Let's suppose the
inline function is called 100
times
inside
your program and that
function itself is of 10 lines in length.
Then at 100 places
inside
your program this 10 lines
function definition is written,
causes the program size to
increase
by 1000 lines. Therefore, the size of
the program increases significantly.
The
increase
in size of program may not be an issue if
you have lots of resources
of memory
and
disk space available but preferably, we
try not to increase the size
of the program
without
any benefit.
Also
the inline directive is a
request to the compiler to
treat the function as
inline. The
compiler
is on its own to accept or
reject the request of
inlining. To get to know
whether
the
compiler has accepted the
request to make it inline or
not, is possible through
the
program's
debugging. But this is bit
tedious at this level of our
programming expertise.
Now
we'll see what are
the advantages of this
feature of C++. While
writing macros, we
knew
that it is important to enclose
the arguments of macros
within parenthesis.
For
example, we
wrote square macro
as:
#define
square(x)
(x) *
(x)
when
this macro is called by the
following statement in our
code:
square( i
+ j );
then it
is replaced with the
definition of the square macro
as:
( i + j ) * ( i + j
);
Just
consider, we have not used
parenthesis and written our
macro as under:
#define
square(x)
x *x
then
the substitution of the macro
definition will be
as:
i + j * i +
j;
But
the above definition has
incorrect result. Because the
precedence of the
multiplication
operator (*) is higher than
the addition operator (+),
therefore, the above
statement
is executed semantically
as:
i + (j * i) +
j;
Hence,
the usage of brackets is
necessary to make sure that
the macros work as
expected.
Secondly,
because the macros are
replaced with preprocessors
and not by compiler,
therefore,
they are not aware of
the data types. They just
replace the macro definition
and
there is
no type checking on the parameters of the
macro. Same macro can be used
for
multiple
data types. For instance,
the above square macro
can be used for long, float,
double
and
char
data
types.
Page
315
CS201
Introduction to Programming
Inline
functions behave as expected like a
function and they don't
have any side
effects.
Secondly,
the automatic type checking for
parameters is also done for
inline functions. If
there is
a difference between data types provided
and expected, the compiler
will report
an error
unlike a macro.
Now, we
see a program code to differentiate
between macros and inline
functions:
Example
of Inline Functions versus
Macros
// A
macro vs. an inline
function
#include
<iostream.h>
#define
MAX( A, B ) ((A) > (B) ? (A) :
(B))
inline
int max( int a, int b
)
{
if ( a > b
)
return
a;
return
b;
}
void
main()
{
int i, x,
y;
x = 23; y
= 45;
i = MAX(
x++, y++ ); // Side-effect:
// larger
value incremented
twice
cout
<< "x = " << x << " y = " << y <<
'\n';
x = 23; y
= 45;
i = max(
x++, y++ ); // Works as
expected
cout
<< "x = " << x << " y = " << y <<
'\n';
}
The
output of this program
is:
x = 24 y =
47
x = 24 y =
46
You
can see that the output
from the inline function is
correct while the macro
has
produced
incorrect result by incrementing variable y two times.
Why is this so?
The
definition of the macro
contains the parameters A
and B two times in its body
and
keeping
in mind that macros just
replace the argument values
inside the definition, it
looks
like the following after
replacement.
( (x++)
> (y++) ? (x++) : (y++)
);
Clearly,
the resultant variable either
x
or
y,
whichever is greater (y
in
this case) will be
incremented
twice instead of
once.
Page
316
CS201
Introduction to Programming
Now,
the interesting point is why this problem
is not there in inline functions.
Inside the
code,
the call to the inline
function max
is
made by writing the
following statement:
i = max(
x++, y++ );
While
calling the inline function,
compiler does the type checking
and passes the
parameters
in the same way as in normal
function calls. The arguments
are incremented
once
after their values are
replaced inside the body of the
function max
and
this is our
required
behavior.
Hence, by
and large it is better to
use inline functions rather
than macros. Still macros
can
be
utilized for small
definitions.
The
inline keyword is only a
suggestion to the compiler.
Functions larger than a few
lines
are
not expanded inline even if
they are declared with
the inline keyword.
If the
inline function is called many times inside
the program and from
multiple source
files
(until now, usually we have
been using only one
source file) then the
inline function
is put in
a header file. That header
file can be used (by
using #include) by multiple
source
files
later.
Also
keep in mind that after
multiple files include the
header file that contains
the inline
function,
all of those files must be recompiled
after the inline function in
the header file is
changed.
Now, we
are going to cover exciting
part of this lecture i.e., Function
Overloading.
Function
Overloading
You
have already seen overloading many times.
For example, when we used cout
to
print
our string and then used it
for int, long, double
and
float
etc.
cout
<< "This is my string";
cout
<< myInt ;
This
magic of cout
that it
can print variables of
different data types is
possible because of
overloading.
The operator of cout
(<<) that is stream insertion
operator is overloaded
for
many data
types. Header file iostream.h
contains
prototypes for all those
functions. So
what
actually is overloading?
"Using
the same name to perform
multiple tasks or different
tasks depending on
the
situation."
Page
317
CS201
Introduction to Programming
cout
is
doing exactly same thing
that depending on the variable type
passed to it, it prints
an int
or a
double
or a
float
or a
string. That
means the behavior is
changing but the
function
cout
<< looks
identical.
As we all
know that computers are
dumb machines and they
cannot decide anything on
their
own. Therefore, if it is printing
variables of different types, we
have to tell it
clearly
and
separately for each type
like int
or
double
etc. In
this separately telling
process, the
operator
used is the same <<. So in a
way that operator of <<
is
being overloaded. For
this
lecture, we will not go into
the detail of operator overloading but we
will limit our
discussion
to function overloading.
Function overloading
has the same concept
that the name of the
function will remain
same
but its behavior may
change. For example, if we want to
take square root of a
number.
That number can be an
integer, float or a double
and depending on the type
of
the
argument, we may need to do
different calculation. If we want to
cater to the two
data
types
int
and
double, we will
write separate functions for
int
and
double.
double
intsqrt ( int i );
double
doublesqrt ( double d );
We can
use the function intsqrt()
where
integer square root is required
and doublesqrt()
where
square root of double variable is
required. But this is an
overhead in the sense
that
we have
to remember multiple function
names, even if the behavior
of the functions is of
similar
type as in this case of square root. We
should also be careful about
auto-widening
that if
we pass an int
to
doublesqrt()
function,
compiler will automatically
convert it to
double
and
then call the funtion
doublesqrt(). That
may not be what we wanted
to
achieve
and there is no way of checking
that we have used the
correct function. The
solution
to this problem is function
overloading.
While
overloading functions, we will write
separate functions for separate
data types but
the
function name will remain
same. Return type can be
different if we want to
change,
for
example in the above case we
might want to return an int
for
square root function
for
ints and
double
for a
square root of a double
typed
variable. Now, we will declare
them
as
under:
int
sqrt (
int i );
double
sqrt ( double d );
Now, we
have two functions with the
same name. How will
they be differentiated inside
the
program?
The
differentiation comes from
the parameters, which are
passed to these functions. If
somewhere
in your program you wrote:
sqrt(
10.5 ), the
compiler will
automatically
determine
that 10.5
is
not an integer, it is either
float
or a
double. The
compiler will look
for
the sqrt()
with
parameter of type float
or a
parameter with type as double. It will
find
the
function sqrt()
with
double parameter and call
it. Suppose in the
subsequent code,
there is
a call to sqrt()
function
as under:
Page
318
CS201
Introduction to Programming
int
i;
sqrt ( i
);
Now,
the compiler will
automatically match the prototype and
will call the sqrt()
with
int
as
parameter type.
What is
the advantage of this
function overloading?
Our program is
more readable after using
function overloading. Instead of having
lot of
functions doing
the same kind of work
but with different names.
How does the
compiler
differentiate, we
have already discussed that
compiler looks at the type
and number of
arguments.
Suppose there are two
overloaded functions as given
below:
int f(
int x, int y );
int f(
int x, int y, int z
);
One
function f()
takes
two int
parameter
and other one takes
three int
type
parameters.
Now if
there is call as the
following:
int x =
10;
int y =
20;
f( x, y
);
The
function f()
with
two int parameters is
called.
In case
the function call is made in
the following way:
int x =
10;
int y =
20;
int z =
30;
f( x, y, z
);
The
function f()
with
three int
parameters
is called.
We have
not talked about the return
type because it is not a distinguishing
feature while
overloading functions.
Be careful about it, you
cannot write:
int
f ( int
);
double f
( int );
The
compiler will produce error
of ambiguous declarations.
So the
overloaded functions are differentiated
using type and number of
arguments
passed to
the function and not by
the return type. Let's
take a loop of some
useful
example. We
want to write functions to print
values of different data
types and we will
use
function overloading for
that.
/* Overload functions
to print variables of different
types */
#include
<iostream.h>
Page
319
CS201
Introduction to Programming
void
print (int i)
{
cout
<< "\nThe value of the integer
is: " << i;
}
void
print (double d)
{
cout
<< "\nThe value of the double
is: " << d;
}
void
print (char* s)
{
cout
<< "\nThe value of the string is: "
<< s;
}
main
(void)
{
int i =
100;
double d
= 123.12;
char *s =
"This is a test
string";
print ( i
);
print ( d
);
print ( s
);
}
The
output of the program is:
The
value of the integer is:
100
The
value of the double is:
123.12
The
value of the string is: This
is a test string
You must
have noticed that automtically
the int
version of
print()
function
is called for i,
double
version is called
for d
and
string
version is called
for s.
Internally,
the compiler uses the
name
mangling technique
to generate a unique
token
that is
assigned to each function. It
processes the function name
and its parameters
within
a logical
machine to generate this unique
number for each
function.
Example
of Function Overloading
/* The
following example replaces strcpy and
strncpy with the single function
name
stringCopy.
*/
// An
overloaded function
Page
320
CS201
Introduction to Programming
#include
<iostream.h>
#include
<string.h>
inline
void stringCopy( char *dest,
const char *src )
{
strcpy(
dest, src );
// Calls
the standard C library
function
}
inline
void stringCopy( char *dest,
const char *src, int len
)
{
strncpy(
dest, src, len ); // // Calls
another standard C library
function
}
static
char stringa[20], stringb[20]; //
Declared two arrays of
characters of size 20
void
main()
{
stringCopy(
stringa, "That" );
// Copy
the string `That' into the
array stringa
stringCopy( stringb,
"This is a string", 4 ); // Copy
first 4 characters to stringb
array
cout
<< stringb << " and " << stringa; //
Display the contents on the
screen
}
Page
321
Table of Contents:
|
|||||