C++ (BEGINNER_TO_BEYOND) --- (Part-2)

 



FUNCTIONS:

Functions allow dividing our code into modular units of executive code and call and reusing these units as we wish.


#include<iostream>

#include<cmath>  //math library is imported

using namespace std;\

int main()

{

    double num{};

    cout<<"Enter the number: ";

    cin>>num;

   

    cout<<"Square root: "<<sqrt(num)<<endl; 

    cout<<"Cube root: "<<cbrt(num)<<endl;

    cout<<"Sine of num: "<<sin(num)<<endl;  //sine value of num

    cout<<"Cos of num: "<<cos(num)<<endl;  //cosine value of num

    cout<<"Ceil of number: "<<ceil(num)<<endl;  //4.3à5

    cout<<"Floor value of number: "<<floor(num)<<endl;  //4.3à4

    cout<<"Round of number: "<<round(num)<<endl;  //4.3à4

   

    return 0;

}


Function Definition:

Functions can call other functions, compiler must know the function details BEFORE it is called.

name: name of the function.

parameters: the variables passed into the funtion

return type: the type of the data that is returned from the function.

body: the statements that are executed when the function is called

 

#include <iostream>

using namespace std;

 

double calc_area(int radius)  //function definition

    {

        const double pi{3.14};

        return (pi*radius*radius);

    }

void show()

    {

        int radius{};

        cout<<"Enter the radius: ";

        cin>>radius;

        cout<<"The area of circle is: "<<calc_area(radius)<<endl;

    } 

int main()

{

    show();

    return 0;

}


Function Prototypes:

1. Tells the compiler what it needs to know without a full function definition

2. Also called forward declarations, placed at the beginning of the program.

3. Also used in our own header files(.h) in library.

4. Ok for small programs.


#include <iostream>

using namespace std;


//Function prototypes

double calc_area(int radius);  //function declaration

void show();  //function declaration

 

int main()

{

    show();

    return 0;

}

 

double calc_area(int radius)

    {

        const double pi{3.14};

        return (pi*radius*radius);

    }

 

void show()

    {

        int radius{};

        cout<<"Enter the radius: ";

        cin>>radius;

        cout<<"The area of circle is: "<<calc_area(radius)<<endl;

    }


Function Parameters:

When we call a function we can pass in data to that function, in function call they are called arguments and in function definition they are called parameters, they must be in same number, order and type while calling the function.

When we pass a data into the function it is passed-by-value, a copy of the data is passed to the function. That means whatever changes you make to the parameter in the function does not affect the argument that was passed in.

return statement immediately exist the function, the return value is the result of the function call

Formal parameters: The parameter defined in the function header.

Actual parameter: The parameter used in the function call, the arguments.

 

#include <iostream>

using namespace std;

double get_num(double num)  //double num is formal paramter

{

    return num;

}

int get_sum(int,int);  //int,int is a formal parameters

 

int main()

{

    cout<<"The num is: "<<get_num(45.6)<<endl;  //when we call the function we give the actual parameter

    cout<<"The sum is: "<<get_sum(3,4)<<endl;

    return 0;

}

int get_sum(int a,int b)

{

    return (a + b);

}


Default Arguments:

#include <iostream>

using namespace std;int get_sum(int,int,int);  //function declared

 

int get_sum(int a,int b = 2,int c = 3)  //default values of parameters

{

    return (a + b + c);

}

int main()

{

    cout<<"Sum by default: "<<get_sum(1)<<endl;  //function willl return 6,cz a = 1 and all are set to defaults

    cout<<"Sum by giving values to parameters: "<<get_sum(1,3,4)<<endl;  //function will return that is                   provided by user

    return 0;

}


Overloading Functions:

We can have functions that have different parameters but have the function name is same; it is a type of polymorphism (that means many forms for the same concept).


#include <iostream>

#include<string>

#include<vector>

using namespace std;

 

void print(int);

void print(double);

void print(string);

void print(string,string);

void print(vector<string>);

 

void print(int a)

{

    cout<<"Printing int: "<<a<<endl;

}

void print(double a)

{

    cout<<"Printing double: "<<a<<endl;

}

void print(string a)

{

    cout<<"Printing String: "<<a<<endl;

}

void print(string a,string b)

{

    cout<<"Printing String: "<<a<<" "<<b<<endl;

}

void print(vector<string> a)

{

    for(auto i : a)

    {

        cout<<"Printing vector: "<<i<<endl;

    }

}

int main()

{

    print(5);  //int

    print(4.56);  //double

    string s{"Anubhav Kumar Suman"};  //C++ string object

    print(s);

    print("Anubhav","Suman");  //C-style String since literals are passed

    print({"I","am","Anubhav","Suman"});  //vector

    return 0;

}


Passing Arrays To Functions:

#include <iostream>

using namespace std;

 

void print_array(const int arr[],size_t);  //const is used so that array can't be modified in the function

void set_array(int arr[],size_t,int);

 

void print_array(const int arr[],size_t len)

{

    for(size_t i{0};i<len;i++)

        cout<<arr[i]<<"\t";

    cout<<endl;

}

void set_array(int arr[],size_t len,int value)

{

    for(size_t i{0};i<len;i++)

        arr[i] = value;

}

int main()

{

    int array[] {21,41,46,78,56};

   

    print_array(array,5);

    set_array(array,5,50);

    print_array(array,5);

    return 0;

}


Pass By Reference:

Sometimes we want to change the actual parameter from within the function body, in order to achieve this we need the location or address of the actual parameter.

We can use reference parameter to tell the compiler to pass in a reference to the actual parameter, the formal parameter will now be an alias for the actual parameter.

The parameter list to the function is not an int named num it's a reference to an int named  num that's what the ampersand does. So now when we use the num in the function body we are referencing to the actual parameter. We don't make the copy of the actual parameter like we have seen before we use to make the copy of the actual parameter and then send that copy to the function parameter.


#include <iostream>

#include<vector>

#include<string>

using namespace std;

 

void pass_by_ref1(int &num);

void pass_by_ref2(string &str);

void pass_by_ref3(vector<string> &v);

void print_vector(const vector<string> &v);

 

void pass_by_ref1(int &num)  //any changes in 'num' here will affect the actual parameter of pass_by_ref1

{

    num = 1000;

}

void pass_by_ref2(string &str)

{

    str = "Changed";

}

void pass_by_ref3(vector<string> &v)

{

    v.clear();  //delete all the vector elements

}

void print_vector(const vector<string> &v)

{

    for(auto i : v)

        cout<<i<<"\t";

    cout<<endl;

}

 

int main()

{

    int a{10};

    int another_num{20};

   

    cout<<"Before calling pass_by_ref1: "<<a<<endl;  //print 10

    pass_by_ref1(a);

    //'num' of funtion works as an alias to 'a'

    cout<<"After calling pass_by_ref1: "<<a<<endl;  //value of a changed to 1000,cz of pass by reference

   

    string name{"Anubhav"};

    cout<<"Before calling pass_by_ref2: "<<name<<endl;

    pass_by_ref2(name);

    cout<<"After calling pass_by_ref2: "<<name<<endl;  //value of  'name' changed to 'changed'

   

    vector<string> vec{"Anubhav","Kumar","Suman"};

    cout<<"Before calling pass_by_ref3: ";

    print_vector(vec);

    pass_by_ref3(vec);

    cout<<"After calling pass_by_ref3: "<<endl;

    print_vector(vec);

 

    return 0;

}


Scope Rules:

Variables that are declared inside a function or a block are called local variables and are said to have local scope. These local variables can only be used within the function or block in which these are declared. 

Global scope a global name is one that is declared outside of any class, function, or namespace. However, in C++ even these names exist with an implicit global namespace. The scope of global names extends from the point of declaration to the end of the file in which they are declared.

Static variable is a variable that is defined locally.


 Inline Function:

Inline code is an assembly code that avoids the function call overhead, inline code is generally faster

Syntax:

inline int pass_by_ref1(int &num);


Recursive Functions:

A recursive function is a function that call itself, either directly or indirectly through another function.

Recursive problem solving :

1. Base case

2. Divide the rest of the problem into subproblems and do recursive call

When we pop the element or say function from the stack in the recursive call that is called unwinding.

 


#include <iostream>

using namespace std;


unsigned long long fact(unsigned long long a)

{

    if(a == 0)

        return 1;

    return a * fact(a-1);  //recursive call

}

 

unsigned long long fibo(unsigned long long a)

{

    if(a <= 1)

        return a;

    return fibo(a-1) + fibo(a-2) ;  //recursive call

}

int main()

{

    cout<<"Factorial of the number is: "<<fact(6)<<endl;

    cout<<"Fibonacci of the number is: "<<fibo(6)<<endl;

    return 0;

}


POINTERS AND REFERENCES;

Pointer is a variable whose value is an address, that address can of some another variable or a function. To use the data that the pointer is pointing to you must know it's type.

For example, we have a complex data that's defined outside a funtion and you want to access that data from within the function, you can't do that because that variable name is out of the scope. So you can pass the data to the function by value and make a copy of it or you can use a reference or a pointer parameter to access that data from within the function.

We read the pointer from right to left, always initialize the pointers (0 or nullptr).


Accessing Pointer:

#include <iostream>

using namespace std;

 

int main()

{

    int num{10};

    cout<<"Value of num: "<<num<<endl;

    cout<<"Address of num: "<<&num<<endl;  //address of num, in hex code

    cout<<"Size of num: "<<sizeof(num)<<endl;

   

    int *p;  //declaring pointer but not initializing

    //it contains a garbage value and that garbage value is the address of some other variable in memory that         could be anything since not initialized

    cout<<"Value of p: "<<p<<endl;

    cout<<"Address of p: "<<&p<<endl;

    cout<<"Size of p: "<<sizeof(p)<<endl;

   

    p = nullptr;  //initializing it, making this pointer pointing nowhere, previously it was pointing to anywhere in the memory

    cout<<"Value of p: "<<p<<endl;   

    return 0;

}


Storing Address in a Pointer:

#include <iostream>

using namespace std;

 

int main()

{

    int num{10};

    double high_num{100.34};

   

    int *ptr_num{nullptr};  //declared and initialized

    double *ptr_high_num{nullptr};

   

    ptr_num = &num;  //storing address of num to ptr_num

    ptr_high_num = &high_num;  //store address of high_num, we have to use pointer of type double otherwise error will occur

   

    cout<<"Value of num: "<<num<<endl;

    cout<<"Value of ptr_num: "<<ptr_num<<endl;

    cout<<"Value of high_num: "<<high_num<<endl;

    cout<<"value of ptr_high_num: "<<ptr_high_num<<endl;

    return 0;

}


Dereferencing a Pointer:

Access the data we're pointer is pointing to called dereferencing a pointer.

If score_ptr is a pointer and has a valid address, then you can access the data at the address contained in the score_ptr using the dereferencing operator '*' .

 

#include <iostream>

#include<string>

#include<vector>

using namespace std;

 

int main()

{

    int score{100};

    int *ptr_score{};

    ptr_score = &score;

   

    cout<<"Value of score: "<<score<<endl;  //print 100

    cout<<"Derefrencing a pointer: "<<*ptr_score<<endl;  //print 100

    cout<<"Without dereferencing the pointer : "<<ptr_score<<endl;  //address of score variable

   

    *ptr_score = 200;  //The left hand side of an assignment statement is an address or a location were we want to store things.

    cout<<"Value of score: "<<score<<endl;  //print 200

    cout<<"Derefrencing a pointer: "<<*ptr_score<<endl;  //print 200

   

    string name{"Anubhav"};

    string *name_ptr{&name};

   

    cout<<"Before name: "<<name<<endl;  //print anubhav

    cout<<"Derefrencing the name_ptr: "<<*name_ptr<<endl;  //print anubhav

    name = {"Suman"};

    cout<<"After name changed: "<<*name_ptr<<endl;  //print suman

   

    vector<string> vec{"Anubhav","Kumar","Suman"};

    vector<string> *vec_ptr{nullptr};

    vec_ptr = &vec;

   

    cout<<"Print what value is at index 0: "<<(*vec_ptr).at(0)<<endl;

   

    for(auto i : *vec_ptr)  //using loop with derefrencing of pointer

        cout<<i<<endl;

   

    return 0;

}


Dynamic Memory Allocation:

All the dynamic allocation occurs in the heap in the memory.

We use new keyword to allocate storage at runtime.


int *int_ptr{nullptr};

int_ptr = new int;

This allocates storage for an integer on the heap and stores it's address into the pointer, now we can use the pointer to access the integer.

When you allocate storage in this manner the storage is on the heap the allocate storage contains garbage data until you initialize it the allocated storage does not have a name so the only way to get to that integer is via pointer.


Finally when you're done using the storage then you must deallocate the storage so that it's again available to the rest of the program.

delete int_ptr;

 

#include <iostream>

#include<vector>

#include<string>

using namespace std;

 

int main()

{

    int *int_ptr{nullptr};

    int_ptr = new int;

    //that storage for an integer is going to be allocated on the heap and it's address will be stored in  int_ptr

    //Since the integer has no name we can only access that integer via int_ptr pointer

    cout<<"Address of integer: "<<int_ptr<<endl;  //Display the memory allocation where that address in on that heap

    delete int_ptr;

   

    size_t size{0};

    double *temp_ptr{nullptr};

    cout<<"Size of the array: ";

    cin>>size;

    temp_ptr = new double[size];

    cout<<"Address of first element of 100 doubles: "<<temp_ptr<<endl;  //That contains the address of the first element of those 100 doubles

    delete [] temp_ptr;

   

    return 0;

}


Relationship Between Arrays and Pointers:

The value of an array name is the address of the first element in the array.

The value of a pointer variable is an address.

int array_name[] {1,2,3,4,5};

int *pointer_name{array_name};

Subscript Notation: array_name[index], pointer_name[index].

Offset Notation: *(array_name + index), *(pointer_name + index)


 #include <iostream>

using namespace std;

 

int main()

{

    int score[] {1,2,3};

    cout<<"Address of first element of array: "<<score<<endl;

   

    int *score_ptr{score};

    cout<<"Address of first elemeny of array: "<<score_ptr<<endl;

   

    cout<<"Array subscript notation--------------"<<endl;

    cout<<score[0]<<endl;  //print 1

    cout<<score[1]<<endl;  //print 2

    cout<<score[2]<<endl;  //print 3

   

    cout<<"Pointer subscript notation-------------"<<endl;

    cout<<score_ptr[0]<<endl;  //print 1

    cout<<score_ptr[1]<<endl;  //print 2

    cout<<score_ptr[2]<<endl;  //print 3

   

    cout<<"Array offset notation--------------"<<endl;

    cout<<*(score)<<endl;

    cout<<*(score + 1)<<endl;

    cout<<*(score + 2)<<endl;

   

    cout<<"Pointer offset notation--------------"<<endl;

    cout<<*(score_ptr)<<endl;

    cout<<*(score_ptr + 1)<<endl;

    cout<<*(score_ptr + 2)<<endl;

 

    return 0;

}


Pointers Arithmetic:

#include <iostream>

#include<vector>

using namespace std;

 

int main()

{

    int scores[] {10,32,45,65,-1};  //-1 is sentinel value use to terminate the loop in pointers

    int *scores_ptr{};

   

    scores_ptr = scores;

   

    while(*scores_ptr != -1)

    {

        cout<<"Values of array: "<<*scores_ptr<<endl;

        //increment a pointer to point to the next array element, always remember the address of the pointer

        //is incremented by the value it holds the data type.

        scores_ptr++; 

    }

   

    cout<<"-------------------------------"<<endl;

    while(*scores_ptr != -1)

    {

        cout<<"Values of array: "<<*scores_ptr++<<endl;  //dereference the pointer then increment it

    }

    return 0;

}

 

================================================================

#include <iostream>

#include<vector>

#include<iostream>

using namespace std;

 

int main()

{

    string s1{"Anubhav"};

    string s2{"Anubhav"};

    string s3{"Suman"};

   

    string *p1{&s1};

    string *p2{&s2};

    string *p3{&s1};

   

    cout<<boolalpha;

    cout<<p1<<" == "<<p2<<" : "<<(p1 == p2)<<endl;  //false,since pointer works on address

    cout<<p1<<" == "<<p3<<" : "<<(p1 == p3)<<endl;  //true,since both points to same address

   

    cout<<*p1<<" == "<<*p2<<" : "<<(*p1 == *p2)<<endl;  //true, cz we dereferenced the pointers

   

    p3 = &s3;

    cout<<*p1<<" == "<<*p3<<" : "<<(*p1 == *p3)<<endl;  //false, cz the value in that particular address is differen     

    return 0;

}

 

================================================================

#include <iostream>

#include<vector>

#include<iostream>

using namespace std;

 

int main()

{char name[] {"Anubhav"};

    char *char_ptr1{nullptr};

    char *char_ptr2{};

   

    char_ptr1 = &name[0];

    char_ptr2 = &name[3];

   

    cout<<"In string "<<name<<","<<*char_ptr2<<" is "<<(char_ptr2 - char_ptr1)<<" char away from "<<*char_ptr1<<endl;  //address is subtracted rather than the value

   

    return 0;

}

 

Const and Pointers:

Pointer to constants:

1. The data pointed to by the pointers is constant and cannot be changed.

2. The pointer itself can changed and point somewhere else.

 

int high_score{100};

int low_score{67};

const int *score_ptr{&high_score};

 

*score_ptr = 86;  //error, value cannot be changed

score_ptr = &low_score;  //but pointer can point to other variable

 

Constant Pointers:

1. The data pointed to by the pointers can be changed.

2. The pointer itself cannot change and point somewhere else.

 

int high_score{100};

int low_score{67};

int *const score_ptr{&high_score};

 

*score_ptr = 86;  //value can be changed

score_ptr = &low_score;  //but pointer cannot point to other variable, cz pointer itself is constant


Constant Pointers to constants:

1. The data pointed to by the pointers is constant and cannot be changed.

2. The pointer itself cannot change and point somewhere else.

 

int high_score{100};

int low_score{67};

const int *const score_ptr{&high_score};

 

*score_ptr = 86;  //error, cz both integer and pointer both are const

score_ptr = &low_score;  //error

 

Pass-By-Reference:

Pass-by-reference with pointer parameters, we can use pointers and the dereference operator to achieve pass-by-reference. The function parameter is  a pointer, the actual parameter can be a pointer or an address of the variable.


#include <iostream>

using namespace std;

 

void double_data(int *ptr)

{

    *ptr *= 2; //*ptr = *ptr * 2,dereferencing gives the value at that address multiplying 2

}

int main()

{

    int value{20};

    int *int_ptr{};

   

    cout<<"Value is: "<<value<<endl;  //print 20

    double_data(&value);  //we don not send the copy of the value since we are dealing with the address

    cout<<"After calling value is: "<<value<<endl;  //print 40, doubled the value

   

    cout<<"===================="<<endl;

    int_ptr = &value;

    double_data(int_ptr);

    cout<<"Now value is: "<<value<<endl;  //print 80, cz value was changed to 40

    return 0;

}

 

#include <iostream>

#include<vector>

using namespace std;void swap(int,int);

void swap(int *a,int *b)

{

    int temp;

    temp = *a;

    *a = *b;

    *b = temp;

}

int main()

{

    int x{100},y{200};

    cout<<"Value of x: "<<x<<endl;

    cout<<"Value of y: "<<y<<endl;

   

    swap(&x,&y);

    cout<<"Value of x: "<<x<<endl;

    cout<<"Value of y: "<<y<<endl;

   

    return 0;

}

 

#include <iostream>

#include<vector>

using namespace std;

 

void display(vector<string> *ptr)

{

    for(auto i : *ptr)

        cout<<i<<endl;

}

 

void display(int *ptr,int sentinel)

{

    while(*ptr != sentinel)

            {

              cout<<*ptr<<endl;  //dereference the pointer then increment it.

              ptr++;  //incrementing the pointer address to point to the next element

            }

}

int main()

{

    vector<string> vec{"Anubhav","Kumar","Suman"};

    display(&vec);

   

    int  arr[] {1,2,3,4,5,-1};

    display(arr,-1);

   

    return 0;

}


Returning a Pointer From a Function:

#include <iostream>

#include<vector>

using namespace std;

 

int *create_array(size_t size,int init_value = 0)  //function returns a pointer

{

    int *new_storage{};

    new_storage = new int[size];

    for(size_t i{0};i<size;i++)

    {

        *(new_storage + i) = init_value;  //offset notation, initialize all the array element to init_value

    }

    return new_storage;

}

 

void display(const int *const arr,size_t size)

{

    for(size_t i{0};i<size;++i)

    {

        cout<<arr[i]<<endl;  //subscript notation

    }

}

int main()

{

    int *my_array{};

    size_t size{};

    int init_value{};

   

    cout<<"How many elements you want in array: ";

    cin>>size;

    cout<<"Element to be initialized to all of them: ";

    cin>>init_value;

   

    my_array = create_array(size,init_value);  //function returns an array pointer, that is stored in my_array

   

    display(my_array,size);

    delete []my_array;

   

    return 0;

}


Pointers Pitfalls:

1. Uninitialized pointers

          int *int_ptr;  //pointing anywhere

          *int_ptr = 100;  //hopefully crash


2. Dangling pointers

a. Pointer that is pointing to released memory, for example 2 pointers point to the same data, 1 pointer releases the data with delete, the other pointer accesses the release data

b. Pointer that points to memory that is invalid, we saw this when we returned a pointer to a function local variable


3. Not checking if new failed to allocate memory

If new fails to allocate the storage in heap then the program throw an exception, we use exception handling to catch those exceptions, dereferencing a null pointer will cause your program to crash.


4. Leaking memory

Forgetting to release allocated memory with delete keyword in heap, if you lose your pointer to the storage allocated on the heap you have no way to get to that storage again.

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Comments

Popular posts from this blog

SQL Course (PART-1)

PYTHON BASICS OF BEGINNER's (PART-2)

Open_CV BASICS