Headlines News :
Home » , , » Learn C++ in 12 days - Day 3

Learn C++ in 12 days - Day 3

Written By Ente Malayalam on Friday, May 24, 2013 | 5/24/2013

DAY  - 3
            In yesterday’s class we were dealing with procedural programming in C++. In today’s class we will just continue with the same thing.
            As you know, the procedural approach mainly deals with functions. So lets again start with the basic syntax of a function. The syntax of a function is
            Return_Type Function_Name(Function_Arguments)            {..definition..}
Lets not worry about the definition. Let’s start from the RHS ie) function arguments and move forward.
Consider the following function:
#include <iostream>
using namespace std;
void volume(int length, int width, int height)  {
        cout << "Vol of a box with length = " << length << '\t' ;
        cout << "Width = " << width << '\t';
        cout << "Height =  " << height << '\t' <<  "is = ";
        cout << length * width * height << endl;
}
int main()  {
        int l = 10, w = 2, h = 5;
        volume(l, w, h);
}
Sticking on to our C rules, we know that whenever we invoke the function volume, I need to strictly pass three arguments. Else it is going to throw me an explicit compile time error. But in C++, we have the flexibility of invoking a particular function by passing lesser number of arguments. ie_ I can invoke it as
            volume(l, w);
            volume(l);
            volume();
This is achieved using something known as Default Arguments.

Default Arguments :
            Whenever we have to invoke a particular function with a lesser number of arguments, we need to provide default values which can be considered by your compiler in case of missing arguments. The default arguments should be provided in the prototype declaration of that particular function. If the function is defined above main, then we can provide it in the definition itself. Lets try to modify the above example and see how it works.
#include <iostream>
using namespace std;
void volume(int length = 1, int width = 1, int height = 1)      {
        cout << "Vol of a box with length = " << length << '\t' ;
        cout << "Width = " << width << '\t';
        cout << "Height =  " << height << '\t' <<  "is = ";
        cout << length * width * height << endl;
}
int main()  {
        int l = 10, w = 2, h = 5;
        volume(l, w, h);
        volume(l, w);
        volume(l);
        volume();
}
Usually whenever a function is invoked, the compiler maps the passed parameters with the function from LHS to RHS. Ie) leading edge to trailing edge.
In the first case, when function is invoked with all the three arguments, compiler simply ignores the default values and considers the user passed parameters. In the second case, when one argument is missing, the compiler maps the first and second argument passed by the user to the first and second argument in the function and assumes that the trailing argument is missing. He checks if you have provided default values for the trailing argument. Since you have done it, he is perfectly happy.
            In the second case, compiler simply maps the single argument passed by the user to the left most parameter and assumes that the 2 trailing arguments are missing. He checks for default values. Since you have provided it, he is perfectly happy.
            In the third case, since the user have not passed any arguments, compiler considers all the default values for the further manipulation. This is how the concept of default arguments works in the above program and ultimately the output printed will be
Vol of a box with length = 10   Width = 2       Height =  5     is = 100
Vol of a box with length = 10   Width = 2       Height =  1     is = 20
Vol of a box with length = 10   Width = 1       Height =  1     is = 10
Vol of a box with length = 1    Width = 1       Height =  1     is = 1

In the above function I have provided default value for all the arguments. in reality, it is not necessary that I pass default values for all the arguments. if you are not providing default values for all the arguments, you need to specify a specific order while providing it. In the sense, you cannot pass it randomly. As I told you before, when ever user passes some argument, the compiler maps it from LHS to RHS. So if ever some arguments are missing, compiler always considers it as the trailing argument. So when we provide default values, it is a must that we provide it from RHS to LHS. ie) from trailing edge to leading edge. Compilers always do this check of default arguments even before any of the function is called. If default arguments are not provided correctly, at the point of declaration itself, compiler will throw you an error.
Summarizing, following can be the working conditions with function calls for the above volume program.

1.      void volume(int length, int width = 1, int height = 1);
a.       volume(l, w, h);
b.      volume(l, w);
c.       volume(l);
d.      volume(); //only for this call, the program will through you an error.
2.      void volume(int length, int width , int height = 1);
a.       volume(l, w, h);
b.      volume(l, w);
c.       volume(l); // for this call compiler will throw error
d.      volume(); // for this call also compiler will throw error
But the following function declarations are going to throw you error at the point of prototype declarations itself.
1.      void volume(int length = 1, int width = 2, int height );
2.      void volume(int length = 1, int width , int height );
3.      void volume(int length , int width = 1, int height );
4.      void volume(int length = 1, int width , int height = 1);
That s all about default arguments. Lets dig into few more details of our function arguments.
All this time we were worrying about the values of arguments. Now lets talk a little about how the parameters are passed to the function. If you notice, in the previous function, I am passing all the three arguments by value to the function. Other than pass by value you might have learnt about one more passing technique in C right. what is that ? pass by address or collection by pointers right.
Both the above techniques can be compared in terms of two things. One is memory and the other one is reflecting the changes back in the calling function. Lets take the case of memory. As long as programs are simple like our volume program, memory cannot be compared at all since even pointers occupy 4 bytes of memory. But when we are passing large sized user defined variables, pass by address of course aids in saving some memory since in pass by value the entire memory will be duplicated.
Secondly, if you consider returning, as long as it is a single variable whose value have to be reflected in called function, a return statement will serve our purpose. But in functions like swap where we have to reflect the changes in two variables to the calling function, a single return statement is not going to serve our purpose. In this case again our pass by address is going to save us in that the changes made in the called function will be automatically reflected back in the calling function.
Due to backward compatibility both the above methods are perfectly legal in C++. In addition to this, C++ have a better way of argument passing which is termed as Call by reference. To understand this, first we should know, what exactly a reference is.
Reference Variable:
            Reference variable can be defined in two different ways. One is from user’s point of view and the other is from implementation point of view.
            From user’s point of view, reference can be considered as an alias or an alternate name for an already existing variable. The syntax for reference is
            data_type &new_variable_name = old_variable_name;
Let us see and example and lets try to analyze how reference variable works.
//illustrating reference in memory
#include <iostream>
using namespace std;
int main()      {
        int a = 10;
        int *b = &a;
        int &c = a;

        cout << &a << endl;
        cout << a << endl;
        cout << &b << endl;
        cout << b << endl;
        cout << *b << endl;
        cout << &c << endl;
        cout << c << endl;
        return 0;
}
Let’s try to put the memory picture of the above program as below

& = 910
a / c = 10
b
& = 914
 







           

If you notice you can see that, &a and &c remains the same or atleast printed the same giving an impression to the outsider that reference doesn’t occupy any memory. But it is not true. To justify this, we need to get the definition of reference from implementation point of view.
From implementation point of view
“Reference is an internal constant pointer which gets de-referenced automatically whenever you use it “ which means reference do occupy memory of 4 bytes. Since it is internally treated as a constant pointer, reference need to be initialized at the time of declaration itself and once aliased to a particular variable, it cannot be changed further down in the program.
Now lets try to justify the behavior of the above program.
When I write
int &c = a,
internally the pointer c is holding the address of a. As I told you, reference gets automatically dereferenced when ever I use it.ie)  whenever we write c in the above program, it will be automatically treated as *c. that was the reason it was showing us 10 when I try to print c. in the same manner when I gave &c, it was taken as
            &(*c)
& and * are complement operator. They cancels each other. So cout statement prints the contents of c which is the address of a.
This same reference can be used in call by reference technique in C++. Let s try to modify the above volume program with call by reference.
#include <iostream>
using namespace std;
void volume(int &length, int &width, int &height)       {
        cout << "Volume of a box with length = " << length << '\t';
        cout << "width = " << width << '\t';
        cout <<  "Height = " << height << '\t' << "is = ";
        cout << length * width * height << endl;
}
int main()      {
        int l = 10, w = 20, h = 4;
        volume(l, w, h);
}
Comparing all the three techniques :
Here the disadvantage of memory overhead associated with pass by value is avoided. We could have used pointers instead of this reference to save memory but it is ur responsibility to dereference it. Another thing is pointer may point to anything or it may not point to anything ie) NULL. So it may result in unexpected behaviour if used.
We started with our syntax of function right. Let’s move a little more towards left. The next thing which is going to come into picture is function name.

In your C, whenever you wrote functions, u ensured that each and every function carries distinct name right. Even while writing related functions : in the sense function which are doing similar job, for eg : let s say swap on integer, float and character, you used to provide three distinct names like swap_int(int, int), swap_float(float, float) and swap_char(char, char).  Isn’t it  a headache to the programmer to remember all these names even though they are doing the same job. Keeping these things in mind, C++ designers introduced a new concept where in we can group functions performing similar job under a single name. But for your compiler to identify between different functions we need to ensure that each and every function differs in terms of arguments. In the sense, arguments can differ either in terms of type, order, number, consteness of pointer, consteness of reference and ultimately scope (namespace scope and global scope or class scope). This feature of C++ is known as
Function overloading:
            Lets try to include this concept in our volume program.
#include <iostream>
using namespace std;
void volume(int side)   {
        cout << "Volume of cube with side = " << side << "is = " << side * side * side;
}
void volume(int radius, float height)   {
        cout << "vol of a cyl = " << 3.14 * radius * radius * height << endl;
}
void volume(int l, int w, int h)        {
        cout << "vol of prism = " << l * w * h << endl;
}
void volume(float r)    {
        cout << "vol of a sphere = " << (4 / 3) * 3.14 * r * r * r << endl;
}





int main()      {
        int cube_side = 10;
        int cyl_rad = 20;
        float cyl_hgt = 12.2;
        int prism_len = 1, prism_wid = 2, prism_hgt = 3;
        float sph_rad = 2.2;
        volume(cube_side);                                    //volume of a cube = a * a * a
        volume(cyl_rad, cyl_hgt);                       //volume of a cyllinder = pi * r * r * h
        volume(prism_len, prism_wid, prism_hgt);        //volume of a prism = l * w * h
        volume(sph_rad);                                //volume of a sphere = 4/3 * pi * r * r * r
}
How it works internally? Function overloading works internally using a new principle termed as name mangling or name decoration. Let’s see what it is.
            In C++, every function name we use (whether you use function overloading or not) will internally converted into a distinct name by your compiler based on its arguments and scope. This property is known as name mangling. Remember it does it purely based on argument and name and not based on return type. Because of this same reason, we cannot overload functions based on return type.
            C language doesn’t support name mangling. That s why we cannot implement function overloading in C.
Try out for the following ambiguities in function overloading.
1.
//illustrating the ambiguities which can arise in function overloading
#include <iostream>
using namespace std;
void fun(float a)       {
        cout << "in flt fun" << endl;
}

void fun(int a) {
        cout << "in int fun" << endl;
}
int main()      {
        fun(1.1);
}
//Ambiguity arises due to confusion in typecasting. Avoid it by specifying 1.1f
2.
//illustrating the ambiguities which can arise in function overloading

#include <iostream>
using namespace std;
void fun(int &b)        {
        cout << "in flt fun" << endl;
}
void fun(int a) {
        cout << "in int fun" << endl;
}
int main()      {
        int a = 10;
        fun(a);
}
//cannot overload it like this since function call syntax for both the functions are the same.
If you notice here, you can see that a single function name is used for multiple functionalities. Or I can say, a single person existing in many forms. I have already mentioned the technical term for it right. Polymorphism.
            In function overloading binding (resolution of which function call to be related to which function definition) occurs during compile time, we call function overloading as one type of compile time polymorphism.
Since I have introduced polymorphism to you, let me tell you, abstraction, encapsulation, inheritance and polymorphism is considered to be the major features of object oriented programming languages.
Finally let me talk about your function as a whole. You know why we use functions and how your function works right. Let us brush it up first
Why do we go for functions? It has so many advantages and thing of our interest is saving memory space. This is bcos, instead of writing it as functions, if the code is getting repeated in our program, it ties up the stack memory as well as the code segment memory. Generally, when the compiler sees a fn call, it normally generates a jump to the fn. At the end of the fn it jumps back to the instruction following the call.
The above sequence may save memory but it takes some extra execution time. There need to have an instruction for the jump to the fn, the instructions for saving registers, instructions for pushing arguments onto the stack in the calling program and removing them from the stack in the fn, instructions for storing registers and an instruction to return to the calling program. Even if it s a very small fn we do all the above steps.
Usually when the function is large, since we can save much amount of memory, we compromises on the time overheads generated by the above situation. But if the function size is small, the amount of memory we are going to save is very less. At the same time we have to compromise on time overheads also. This situation is not that desirable.
This execution time in short fn could have been saved if I had a provision of putting the code in the fn body directly in line with the code in the calling program. Ie) each time there s a fn call in the source file, the actual code from the fn is inserted instead of a jump to the fn.
Normal function                                                          inline function

Fn 1                                                                                         fn 1
                                                                                       Fn definiton
Fn 1                             fn definition
                                                                                                Fn1                              fun
Fn 1                                                                                   fn definition                 definition
                                               
                                                                                                Fn 1
                                                                                          Fn definition
C++ designers implemented a technique which used the above logic and it is known as inline functions. Inline function is a request to the compiler. It requests the compiler to first check if the function size is small and if small, instead of jumping to the function definition, the function call will get replaced by function definition. If big, the inline request will be neglected by the compiler.
The syntax for inline functions is
inline return type function name(function arguments)           {}
            If ever the functions are considered as inline functions, since it is text replacement, it will increase the text segment size.
            Have you studied any text replacement like this in your C language? Yes right. Macros. But both these things are completely different.
            First of all macro is a pre processor directive and so cannot be ignored. Ie) if declared as macro, the name for sure will be replaced by the preprocessor. But inline functions are just request to the compiler and there are changes, the request gets neglected. Some of the situations where the inline request gets neglected is
  1. If the function size is pretty large.
  2. If the function declaration and function definition is in different translation unit.
  3. If there are large loops in the function
  4. If the declaration is above the main and definition is below the main
  5. If the function is recursive.
  6. If there is a function pointer pointing to the inline function.. This is because function pointer need to have the address of the function where as since inline function are simple text replacement it wont be having any address.

Another difference between the macro’s and inline functions is, since the macro is taken care of by the pre processor, there wont be any type checking occurring here where as since inline functions are taken care of by the compiler, proper type checking will be taking place there.  For example, lets say I have written a macro for finding the square of an integer. For this macro, even if I pass character instead of integer, there wont be any error at all where as if it is inline function since compiler is doing the replacement it will check if you have passed integer itself for the function. Else it will throw you and explicit error.

That s all about the functions in the procedural approach of C++ .  From tomorrow we will start off with the object oriented approach in C++.

Reference :
            C++ Complete Reference : Herbert Schildt
            C++ Primer Plus : Stephen Prata




Share this article :

0 comments:

Post a Comment

 
Support : Creating Website | Johny Template | Maskolis | Johny Portal | Johny Magazine | Johny News | Johny Demosite
Copyright © 2011. Education World - All Rights Reserved
Template Modify by Creating Website Inspired Wordpress Hack
Proudly powered by Blogger