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 = # //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
Post a Comment