ZeePedia

History of C/C++, Structured Programming, Default Function Arguments

<< Dynamic Memory Allocation, calloc, malloc, realloc Function, Dangling Pointers
Classes and Objects, Structure of a class, Constructor >>
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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
img
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:
  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