C++ Full Course 1 PDF
C++ Full Course 1 PDF
C++ Full Course 1 PDF
1 Introduction to
Programming
1
LaunchPad
But, an approach in natural language could be vague. For example consider I say, numbers between
1 and 61.
(a) It could be misunderstood by as the numbers including 1 and 61.
(b) Same problem in case of excluding 1 and including 61 (or) including 1 and excluding 61.
(c) Again, we need to tell what is meant by go on dividing by numbers.
So, natural language may lead to ambiguity and hence we need better ways to present out approach.
To express thoughts unambiguously, flowcharts and pseudocodes are used.
Flowcharts :
A flowchart is a diagrammatic representation of an approach, where each step is put into a shape element
(box, circle, etc.) and these elements are connected in the order of their execution.
Let’s look back at the same problem of finding if a number ‘n’ is a prime number or not? We have understood
that the approach would be to divide all numbers between 1 and n, by n. Now, let us draw a flowchart for
it :
Start
Read N
i=2
No Print
Is i < N ?
"IS PRIME"
i=i+1 Yes
Is N
No Yes Print
divisible
by i? "NOT PRIME"
End
Pseudocode :
It resembles programming language, but is more generic in syntax. Its advantage is that programmers who
code in different languages can explain their approach using pseudocode. Let’s see some pseudocodes
and their syntax:
2
LaunchPad
Examples of Pseudocode :
1. Find Circumference and Area of a circle.
(i) Read R
(ii) PI = 3.14
(iii) Circumference = 2*PI*R
(iv) Area = PI*R*R
(v) Print Circumference
(vi) Print Area
Read N
If N >=90 then
Print “A”
3
LaunchPad
4
LaunchPad
Now, when we are sure about our approach and we have expressed it unambiguously using
flowchart/pseudocode, it is time to tell it to the machine. We code it using a programming language.
This code is then converted into an executable and could be run on machine without the help of source
code.
5
LaunchPad
This nearly completes the code. However, C++ Compiler doesn’t understand printing operation. So we
tell it to import everything that there in ‘iostream’ (think it as a dictionary) where the operation
printing is define. To achieve this operation Line 1 is written. After doing that, C++ knows that printing
can be done using ‘cout’. So if you use anything, that’s not understandable by C++ compiler, you need
to first define its meaning and then use it.
6
LaunchPad
Line 5 : It tells compiler that x is a variable that will contain an integer. So we’ll say the data type
for x is int.
Line 7 : Read from keyboard(cin) to x.
Ø Primitive data types: These are basic data types which are used to represent single values.
Primitive data types are predefined.
Ø Non Primitive data types: These are made up of Primitive data types. These are not defined by the C++
programming language, but are created by the user like arrays and strings. We will learn about them
later!
Ø Data type Modifiers: These modifiers are used to increase or decrease the effective range
of certain data types. They have lesser or greater memory usage depending on the
increase/decrease in effective range. The 4 data type modifiers are - long, short, signed, unsigned.
Data Type
Primitive Non-Primitive
String
Void Boolean Numeric
Array
Character Integral etc.
7
LaunchPad
The following code, gives an insight to most commonly used data types for this course.
# include <iostream>
using namespace std;
int main(){
int num = 10; //num stores an integer
char alpha = ‘a’; //alpha stores a character. Note the quotes.
float pi = 3.14;
//pi is a floating point variable that stores value 3.14
double h = 6.626e-34;
//floating point numbers can also be written in scientific notation
//6.626e-34 = 6.626 * 10^-34
bool isPrime = true;
//isPrime is a var CAN store just 2 values either 0 or 1
}
Decision Making : Decision making is the integral part of most of the algorithms. This is achieved by
the use of the ‘If-else’ statements!
We will understand it using an example. Consider writing a program to find largest of 3 numbers.
//Largest of 3 Numbers
# include <iostream>
using namespace std;
int main() {
int no1;
int no2;
int no3;
//Read 3 numbers
cin >> no1 >> no2 >> no3; //chaining or cascading
/*
Same as doing
8
LaunchPad
Here the condition in the parenthesis of ‘if’ is evaluated. If the condition comes out to be true then
the code in the ‘if’ block is executed but if it comes out as false then the ‘else’ block code is executed.
If more than two conditions are there which needs to be checked then the else if ladder is used and
hence multiple condition can be evaluated.
These conditions can also be nested which means that we can have an ‘if-else’ statements inside an
‘if’ block.
// Largest of 3 Numbers using nested if...else
# include <iostream>
using namespace std;
int main(){
int no1, no2, no3; //3 variables in one row
if (no1 > no2){
if (no1 > no3){
cout << no1 << “ is Largest” << endl;
}
else {
cout << no3 << “ is Largest” << endl;
}
}
else {
//this means no2 is larger than no1
if (no2 > no3){
cout << no2 << “ is Largest” << endl;
}
9
LaunchPad
else {
cout << no3 << “ is Largest” << endl;
}
}
}
Looping : Most often while writing code, we need to perform a task over and over again till our work is
done! This can be achieved by the concept of loops. In loops, we check a condition of ‘Is the work done?’
every time before doing the work. Thus, eventually when the work gets finished we break out of that loop.
Note that it is very important to have a breaking condition in the loop else the loop would continue forever
and it will become an infinite loop!
Let us try to convert 2nd pseudocode into C++ code. The task is to add N numbers where N will be given by
user.
//Add N numbers input by user
# include <iostream>
using namespace std;
int main(){
int N;
cin >> N;
int sum = 0;
int x;
int i = 1; //number to be read
10
LaunchPad
3 Arrays
Array Declaration and Usage
Suppose that you want to find the average of the marks for a class of 30 students, you certainly do not
want to create 30 variables: mark1, mark2, ..., mark30. Instead, You could use a single variable, called
an array, with 30 elements.
An array is a list of elements of the same type, identified by a pair of square brackets [ ]. To use an
array, you need to declare the array with 3 things: a name, a type and a dimension (or size, or length).
The syntax is:
type arrayName[arraylength]; // arraylength can only be a literal/constant
// I recommend using a plural name for array, e.g., marks, rows, numbers.
int marks[5]; // Declare an int array called marks with 5 elements
double numbers[10]; // Declare an double array of 10 elements
const int SIZE = 9;
float temps[SIZE]; // Use const int as array length
// Some compilers support an variable as array length, e.g.,
int size;
cout << “Enter the length of the array: “;
cin >> size;
float values[size];
Take note that, in C++, the value of the elements are undefined after declaration.
You can also initialize the array during declaration with a comma-separated list of values, as follows:
// Declare and initialize an int array of 3 elements
int numbers[3] = {11, 33, 44};
// If length is omitted, the compiler counts the elements
int numbers[] = {11, 33, 44};
// Number of elements in the initialization shall be equal to or less than
length
int numbers[5] = {11, 33, 44};
// Remaining elements are zero. Confusing! Don’t do this
11
LaunchPad
int a2[SIZE] = {21, 22, 23, 24, 25}; // All elements initialized
for (int i = 0; i < SIZE; ++i) cout << a2[i] << " ";
cout << endl; // 21 22 23 24 25
int a3[] = {31, 32, 33, 34, 35}; // Size deduced from init values
int a3Size = sizeof(a3)/sizeof(int);
cout << "Size is " << a3Size << endl; // 5
for (int i = 0; i < a3Size; ++i) cout << a3[i] << " ";
cout << endl; // 31 32 33 34 35
12
LaunchPad
You can refer to an element of an array via an index (or subscript) enclosed within the square bracket
[ ]. C++’s array index begins with zero. For example, suppose that marks is an int array of 5 elements,
then the 5 elements are: marks[0], marks[1], marks[2], marks[3], and marks[4].
// Declare & allocate a 5-element int array
int marks[5];
// Assign values to the elements
marks[0] = 95;
marks[1] = 85;
marks[2] = 77;
marks[3] = 69;
marks[4] = 66;
cout << marks[0] << endl;
cout << marks[3] + marks[4] << endl;
Array Name: a
Array Length: n
Index : 0 1 2 3 n−1
To create an array, you need to known the length (or size) of the array in advance, and allocate
accordingly. Once an array is created, its length is fixed and cannot be changed. At times, it is hard to
ascertain the length of an array (e.g., how many students in a class?). Nonetheless, you need to
estimate the length and allocate an upper bound. This is probably the major drawback of using an
array. C++ has a vector template class (and C++11 added an arraytemplate class), which supports
dynamic resizable array.
You can find the array length using expression sizeof(arrayName)/sizeof(arrayName[0]),
where sizeof(arrayName) returns the total bytes of the array and sizeof(arrayName[0]) returns the
bytes of first element.
C/C++ does not perform array index-bound check. In other words, if the index is beyond the array’s
bounds, it does not issue a warning/error. For example,
const int size = 5;
int numbers[size]; // array index from 0 to 4
// Index out of bound!
// Can compiled and run, but could pose very serious side effect!
numbers[88] = 999;
cout << numbers[77] << endl;
13
LaunchPad
This is another pitfall of C/C++. Checking the index bound consumes computation power and depicts
the performance. However, it is better to be safe than fast. Newer programming languages such as
Java/C# performs array index bound check.
int main() {
int marks[] = {74, 43, 58, 60, 90, 64, 70};
int sum = 0;
int sumSq = 0;
double mean, stdDev;
for (int i = 0; i < SIZE; ++i) {
sum += marks[i];
sumSq += marks[i]*marks[i];
}
mean = (double)sum/SIZE;
cout << fixed << "Mean is " << setprecision(2) << mean << endl;
return 0;
}
14
LaunchPad
To compile the program under GNU GCC (g++), you may need to specify option -std=c++0x or -std=c++11:
g++ -std=c++0x -o TestForEach.exe TestForEach.cpp
// or
g++ -std=c++11 -o TestForEach.exe TestForEach.cpp
Multi-Dimensional Array
For example
int mat[2][3] = { {11, 22, 33}, {44, 55, 66} };
0 1 2 3
Row 0 a[0] [0] a[0] [1] a[0] [2] a[0] [3] ...
Row 1 a[1] [0] a[1] [1] a[1] [2] a[1] [3] ...
15
LaunchPad
For 2D array (table), the first index is the row number, second index is the column number.
The elements are stored in a so-called row-major manner, where the column index runs out first.
Example :
/* Test Multi-dimensional Array (Test2DArray.cpp) */
#include <iostream>
using namespace std;
void printArray(const int[][3], int);
int main() {
int myArray[][3] = {{8, 2, 4}, {7, 5, 2}}; // 2x3 initialized
// Only the first index can be omitted and implied
printArray(myArray, 2);
return 0;
}
// Print the contents of rows-by-3 array (columns is fixed)
// Functions coming soon!
void printArray(const int array[][3], int rows) {
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < 3; ++j) {
cout << array[i][j] << " ";
}
cout << endl;
}
}
16
LaunchPad
Example :
You can use cin and cout to handle C-strings.
cin << reads a string delimited by whitespace;
cin.getline(var, size) reads a string of into var till newline of length up to size-1, discarding the newline
(replaced by ‘\0’). The size typically corresponds to the length of the C-string array.
cin.get(var, size) reads a string till newline, but leaves the newline in the input buffer.
cin.get(), without argument, reads the next character.
/* Test C-string (TestCString.cpp) */
#include <iostream>
using namespace std;
int main() {
char msg[256]; // Hold a string of up to 255 characters (terminated by '\0')
17
LaunchPad
4 Functions
Say, we want to compute factorial of two different numbers. For that the program would be :
01: //factorial of a number
02:
03: # include <iostream>
04: using namespace std;
05:
06: int main(){
07: int n1, n2;
08: cin >> n1 >> n2;
09:
10: int fact1 = 1;
11: int fact2 = 1;
12:
13: //computing factorial of n1
14: for(int i = 2; i <= n1; ++i) fact1 = fact1 * i;
15:
16: //computing factorial of n2
17: for(int i = 2; i <= n2; ++i) fact2 = fact2 * i;
18:
19: cout << “Factorial of “ << n1 << “ “ << fact1 << endl;
20: cout << “Factorial of “ << n2 << “ “ << fact2 << endl;
21: }
18
LaunchPad
Here the code for computing factorial has been written only once [20, 27]1 but used twice [12], [13].
[6] is a function declaration that provides the following 4 information about the function.
(a) Function name : factorial
(b) Return type : int
(c) Number of arguments : 1
(d) Function arguments : a variable of int type
Note that it does NOT tell us anything about what the function does. It just tell us how it looks.
Function definition starts from [20]. It tells the compiler what the function does and how it does so.
Had the function definition done before function calling [12], the function declaration becomes optional.
1
[i] represents ith line number
19
LaunchPad
Now we can define function formally. A function is a set of code which is referred to by a name and can be
called (invoked) at any point in the program by using the function’s name. It is like a subprogram where we
can pass parameters to it and it can even return a value.
By the return type we mean that we specify the data type of the return value. It may also happen sometimes
that the function does not return anything. In that case the return type of the function should be ‘void’.
Ø If no return type is mentioned, by default int is assumed.
Ø If no return statement exists in a function which is expected to return some value, some garbage value
is returned.
Ø You cannot return a value from a function with void return type.
Function Calling :
Now just making the function would not execute it even when the program is run. To execute any
function we need to invoke it. This is called calling the function. Function can be called from the main
function and also from other functions. The called function must either be declared or be defined
before the calling function. Otherwise, the calling function cannot find the called function, and it will
lead to a compile time error. If there are any parameters to be given to the function, they are also
passed within the parenthesis. If the function returns any value it can be stored in a variable. Now let
us write two functions and see how they can be called!
When the function is called, we can pass the parameters of the same data type as specified in its
definition. Note that while calling the function we only pass in the names of the variables or values
and don’t specify its data type!
01: //sum function
02: # include <iostream>
03: using namespace std;
04:
05: int add(int num1, int b){
06: return num1 + b;
07: }
08:
09: int main(){
10: int num1 = 3;
11: int num2 = 5;
12: int sum = add(num1, num2);
13: cout << sum << endl;
14: return 0;
15: }
20
LaunchPad
In the above code, we have written a function for adding two numbers. An important point to note
here is that the variables present in the main function are different from those that are present in the
add function, though they might have same names. To understand this better let us learn about the
concept of variables and their scope.
Variables and their Scope :
During runtime a memory pool is created by the program to store all the variables, codes for functions, etc.
In this memory pool there are majorly two types of memory – the stack memory and the heap memory.
Any variables that are statically declared (their size if known at compile time) are stored in the stack
memory, whereas any variables/objects that are created dynamically at runtime (their size is not known at
compile time), are created in the heap memory. All the functions that are called occupy their area on the
stack where all their variables are stored. The functions keep stacking one above the other in the order of
their calls. When the function returns, it is erased from the stack and all its local variables are destroyed.
The variables that are present in one function are local to only that function. It cannot be accessed outside
that function. For instance a variable made in the main function is not available to the add function unless
we pass it as a parameter to it. However, using pass by reference, a variable defined in one function can be
accessed in other function.
Consider the following example of swapping to numbers using function.
01: //call by reference
02: # include <iostream>
03: using namespace std;
04:
05: void swp1(int x, int y){
06: int tmp = x;
07: x = y;
08: y = tmp;
09: }
10:
11: void swp2(int& x, int& y){
12: //x and y are references...call by reference
13: int tmp = x;
14: x = y;
15: y = tmp;
16: }
17:
18: int main() {
19: int a, b;
21
LaunchPad
Note the difference in line 5 and line 11. In line 11, & is used to refer to an already existing VARAIBLE.
Also, there is no change in the calling of function.
Difference between Pass by Value v/s Pass by Reference:
22
LaunchPad
5 Recursion
Recursion is a technique (just like loops) where the solution to a problem depends on the solution to
smaller instances of that problem. In cases where we need to solve a bigger problem, we try to reduce that
bigger problem in a number of smaller problems and then solve them. For instance we need to calculate 28.
We can do this by first calculating 24 and then multiplying 24 with 24. Now to calculate 24, we can calculate 22
which in turn depend on just 21. So for calculating 28, we just need to calculate 21 and then keep on
multiplying the obtained numbers until we reach the result.
So by dividing the problem in smaller problems we can solve the original program easily. But we can’t go on
making our problem smaller infinitely. We need to stop somewhere and terminate. This point is called a
base case. The base case tells us that the problem doesn’t have to be divided further. For instance, in the
above case when power of 2 equals to 1 we can return 2. So power being equal to 1 becomes our base case.
int power(int x,int n)
{
if(n==1)
{
return x;
}
int smallPow=power(x,n/2);
if(n%2==0) {
return smallPow*smallPow;
}
else {
return smallPow*smallPow*x;
}
}
23
LaunchPad
An important point to note here is that in the base case we need to have a return statement compulsorily
even if the return type of the function is void. This is because even in the base case if we don’t have the
return statement, the code after the base case would begin to execute and the recursion would execute
infinitely. Thus to terminate recursion, we need to have a return statement in the base case.
For factorial of 5:
Now we know that every recursive function includes a base case and a call to itself. So every time the
function calls itself we have two opportunities to do our work. First is before calling itself and second is
after the call. First opportunity helps to do work on the input whereas second opportunity lets us to do
work on the result for the smaller problem. To understand this better let us write code for printing a series
of increasing and decreasing numbers.
void printID(int n)
{
if(n==0)
return;
cout<<n<<endl;
printID(n-1);
cout<<n<<endl;
}
Every time a recursive function is called, the called function is pushed into the running program stack over
the calling function. This uses up memory equal to the data members of a function. So, using recursion is
recommended only upto certain depth. Otherwise too much memory would be wasted in stack space for
program.
24
LaunchPad
Backtracking :
When we solve a problem using recursion, we break the given problem into smaller ones.
Let’s say we have a problem PROB and we divided it into three smaller problems P1, P2 and P3. Now
it may be the case that the solution to PROB does not depend on all the three subproblems, in fact we
don’t even know on which one it depends.
Let’s take a situation. Suppose you are standing in front of three tunnels, one of which is having a bag
of gold at its end, but you don’t know which one. So you’ll try all three. First go in tunnel 1, if that is
not the one, then come out of it, and go into tunnel 2, and again if that is not the one, come out of it
and go into tunnel 3. So basically in backtracking we attempt solving a subproblem, and if we don’t
reach the desired solution, then undo whatever we did for solving that subproblem, and again try
solving another subproblem.
Basically Backtracking is an algorithmic paradigm that tries different solutions until finds a solution
that “works”. Problems which are typically solved using backtracking technique have following property
in common. These problems can only be solved by trying every possible configuration and each
configuration is tried only once.
?
dead end
dead end
?
Start ? ?
dead end
dead end
?
success!
25
LaunchPad
26
LaunchPad
Place Queen 1
at (1, 1)
Place Queen 3
at (3, 2)
So, clearly, we tries solving a subproblem, if that does not result in the solution, it undo whatever
changes were made and solve the next subproblem. If the solution does not exists ( like N=2),
then it returns false.
27
LaunchPad
4. Rat in a Maze :
Given a maze, NxN matrix. A rat has to find a path from source to des-ti-na-tion. maze[0][0]
(left top corner)is the source and maze[N-1][N-1](right bot-tom cor-ner) is des-ti-na-tion. There
are few cells which are blocked, means rat can-not enter into those cells. The rat can move
only in two directions: forward and down.
Backtracking Algorithm :
If destination is reached
Print the solution
Else
{
a) Mark current cell in solution matrix as 1.
b) Move forward in horizontal direction and recursively check if this move leads to a solution.
c) If the move chosen in the above step doesn’t lead to a solution then move down and check of this
move leads to a solution.
d) If none of the above solutions work then unmark this cell as 0 (Backtrack) and return false.
}
28
LaunchPad
6 Pointers
Pointers, References and Dynamic Memory Allocation are the most powerful features in C/C++
language, which allows programmers to directly manipulate memory to efficiently manage the
memory - the most critical and scarce resource in computer - for best performance. However, “pointer”
is also the most complex and difficult feature in C/C++ language.
Pointers are extremely powerful because they allows you to access addresses and manipulate
their contents. But they are also extremely complex to handle. Using them correctly, they could
greatly improve the efficiency and performance. On the other hand, using them incorrectly could
lead to many problems, from un-readable and un-maintainable codes, to infamous bugs such as
memory leaks and buffer overflow, which may expose your system to hacking. Many new
languages (such as Java and C#) remove pointer from their syntax to avoid the pitfalls of pointers,
by providing automatic memory management.
Although you can write C/C++ programs without using pointers, however, it is difficult not to
mention pointer in teaching C/C++ language. Pointer is probably not meant for novices and dummies.
Pointer Variables :
A computer memory location has an address and holds a content. The address is a numerical number
(often expressed in hexadecimal), which is hard for programmers to use directly. Typically, each address
location holds 8-bit (i.e., 1-byte) of data. It is entirely up to the programmer to interpret the meaning
of the data, such as integer, real number, characters or strings.
To ease the burden of programming using numerical address and programmer-interpreted data,
early programming languages (such as C) introduce the concept of variables. A variable is
a named location that can store a value of a particular type. Instead of numerical addresses, names (or
identifiers) are attached to certain addresses. Also, types (such as int, double, char) are associated
with the contents for ease of interpretation of data.
Each address location typically hold 8-bit (i.e., 1-byte) of data. A 4-byte int value occupies 4 memory
locations. A 32-bit system typically uses 32-bit addresses. To store a 32-bit address, 4 memory locations
are required.
The following diagram illustrate the relationship between computers’ memory address and content;
and variable’s name, type and value used by the programmers.
29
LaunchPad
Declaring Pointers :
Pointers must be declared before they can be used, just like a normal variable. The syntax of declaring
a pointer is to place a * in front of the name. A pointer is associated with a type (such as int and double)
too.
type *ptr; // Declare a pointer variable called ptr as a pointer of type
// or
type * ptr;
// or
type *ptr ; // I shall adopt this convention
For Example,
int * iPtr;
// Declare a pointer variable called iPtr pointing to an int (an int pointer)
// It contains an address. That address holds an int value.
double * dPtr; // Declare a double pointer
Take note that you need to place a * in front of each pointer variable, in other words, * applies only to
the name that followed. The * in the declaration statement is not an operator, but indicates that the
name followed is a pointer variable. For example,
int *p1, *p2, i ; // p1 and p2 are int pointers. i is an int
int *p1, p2, i ; // p1 is a int pointer, p2 and i are int
int *p1, *p2, i ; // p1 and p2 are int pointers, i is an int
30
LaunchPad
You can use the address-of operator to get the address of a variable, and assign the address to a
pointer variable. For example,
int number = 88 ; // An int variable with a value
int * pNumber ;
// Declare a pointer variable called pNumber pointing to an int (or int
pointer)
pNumber = &number;
// Assign the address of the variable number to pointer pNumber.
int * pAnther = &number;
// Declare another int pointer and init to address of the variable number
As illustrated, the int variable number, starting at address 0x22ccec, contains an int value 88.
The expression &number returns the address of the variable number, which is 0x22ccec. This address
is then assigned to the pointer variable pNumber, as its initial value.
The address-of operator (&) can only be used on the RHS.
For Example,
int number 88;
int * pNumber = &number;
// Declare and assign the address of variable number to pointer pNumber (0x22ccec)
cout << pNumber << endl ;
// Print the content of the pointer variable, which contain an address (0x22ccec)
cout << *pNumber << endl;
// Print the value “pointed to” by the pointer, which is an int (88)
*pNumber = 99;
// Assign a value to where the pointer is pointed to, NOT to the pointer
variable
31
LaunchPad
Take note that pNumber stores a memory address location, whereas *pNumber refers to the value
stored in the address kept in the pointer variable, or the value pointed to by the pointer.
As illustrated, a variable (such as number) directly references a value, whereas a pointer indirectly
references a value through the memory address it stores. Referencing a value indirectly via a pointer
is called indirection or dereferencing.
The indirection operator (*) can be used in both the RHS (temp = *pNumber) and the LHS (*pNumber
= 99) of an assignment statement.
Take note that the symbol * has different meaning in a declaration statement and in an expression.
When it is used in a declaration (e.g., int * pNumber), it denotes that the name followed is a pointer
variable. Whereas when it is used in a expression (e.g., *pNumber = 99; temp << *pNumber;),
it refers to the value pointed to by the pointer variable.
Uninitialized Pointers :
The following code fragment has a serious logical error!
int * iPtr;
*iPtr = 55;
cout << *iPtr << endl;
Null Pointers :
You can initialize a pointer to 0 or NULL, i.e., it points to nothing. It is called a null pointer. Dereferencing
a null pointer (*p) causes an STATUS_ACCESS_VIOLATION exception.
32
LaunchPad
int * iPtr = 0 ;
// Declare an int pointer, and initialize the pointer to point to nothing
cout << *iPtr << endl;
// ERROR! STATUS_ACCESS_VIOLATION exception
int * p = NULL ; // Also declare a NULL pointer points to nothing
Reference Variables
C++ added the so-called reference variables (or references in short). A reference is an alias,
or an alternate name to an existing variable. For example, suppose you make peter a reference (alias)
to paul, you can refer to the person as either peter or paul.
The main use of references is acting as function formal parameters to support pass-by-reference. In
an reference variable is passed into a function, the function works on the original copy (instead of a
clone copy in pass-by-value). Changes inside the function are reflected outside the function.
A reference is similar to a pointer. In many cases, a reference can be used as an alternative to pointer,
in particular, for the function parameter.
33
LaunchPad
For example,
/* Test reference declaration and initialization (TestReferenceDeclaration.cpp) */
#include <iostream>
using namespace std;
int main() {
int number = 88; // Declare an int variable called number
int & refNumber = number;
// Declare a reference (alias) to the variable number
// Both refNumber and number refer to the same value
cout << number << endl; // Print value of variable number (88)
cout <<refNumber<< endl; // Print value of reference (88)
34
LaunchPad
For Example,
/* References vs. Pointers (TestReferenceVsPointer.cpp) */
#include <iostream>
using namespace std;
int main() {
int number1 = 88, number2 = 22;
35
LaunchPad
A reference variable provides a new name to an existing variable. It is dereferenced implicitly and
does not need the dereferencing operator * to retrieve the value referenced. On the other hand,
a pointer variable stores an address. You can change the address value stored in a pointer. To retrieve
the value pointed to by a pointer, you need to use the indirection operator *, which is known as explicit
dereferencing. Reference can be treated as a const pointer. It has to be initialized during declaration,
and its content cannot be changed.
Reference is closely related to pointer. In many cases, it can be used as an alternative to pointer.
A reference allows you to manipulate an object using pointer, but without the pointer syntax of
referencing and dereferencing.
The above example illustrates how reference works, but does not show its typical usage, which is
used as the function formal parameter for pass-by-reference.
36
LaunchPad
The output clearly shows that there are two different addresses.
Pass-by-Reference with Pointer Arguments
In many situations, we may wish to modify the original copy directly (especially in passing huge object
or array) to avoid the overhead of cloning. This can be done by passing a pointer of the object into the
function, known as pass-by-reference. For example,
/* Pass-by-reference using pointer (TestPassByPointer.cpp) */
#include <iostream>
using namespace std;
void square(int *);
int main() {
int number = 8;
cout << “In main(): “ <<&number << endl; // 0x22ff1c
cout << number << endl; // 8
square(&number); // Explicit referencing to pass an address
cout << number << endl; // 64
}
void square(int * pNumber) { // Function takes an int pointer (non-const)
cout << “In square(): “ << pNumber << endl; // 0x22ff1c
*pNumber *= *pNumber; // Explicit de-referencing to get the value pointed-to
}
The called function operates on the same address, and can thus modify the variable in the caller.
37
LaunchPad
int main() {
int number = 8;
cout << “In main(): “ <<&number << endl; // 0x22ff1c
cout << number << endl; // 8
square(number); // Implicit referencing (without ‘&’)
cout << number << endl; // 64
}
Again, the output shows that the called function operates on the same address, and can thus modify
the caller’s variable.
Take note referencing (in the caller) and dereferencing (in the function) are done implicitly. The only
coding difference with pass-by-value is in the function’s parameter declaration.
Recall that references are to be initialized during declaration. In the case of function formal parameter,
the references are initialized when the function is invoked, to the caller’s arguments.
References are primarily used in passing reference in/out of functions to allow the called function
accesses variables in the caller directly.
38
LaunchPad
int main() {
int number = 8;
const int constNumber = 9;
cout << squareConst(number) << endl;
cout << squareConst(constNumber) << endl;
cout << squareNonConst(number) << endl;
cout << squareNonConst(constNumber) << endl;
39
LaunchPad
int main( ) {
int number1 = 8
cout << “In main ( ) & number1: “ <<&number1 << endl; // 0x22ff14
int & result = squareRef (number1) ;
cout << “In main ( ) &result : “ <<&result << endl; // 0x22ff14
cout << result << endl; // 64
cout << number1<< endl; // 64
int number2 = 9;
cout << “In main ( ) &number2: “ <<&number2 << endl; // 0x22ff10
int * pResult = squarePtr(&number2);
cout << *pResult << endl; // 81
cout << number2 << endl; // 81
}
40
LaunchPad
You should not pass Function’s local variable as return value by reference
/* Test passing the result (TestPassResultLocal.cpp) */
#include <iostream>
using namespace std;
int * squarePtr(int) ;
int & squareRef(int) ;
int main( ) {
int number = 8;
cout << number << endl; //8
cout << *squarePtr(number) << endl; // ??
cout << squareRef(number) << endl; // ??
}
This program has a serious logical error, as local variable of function is passed back as return value by
reference. Local variable has local scope within the function, and its value is destroyed after the
function exits. The GCC compiler is kind enough to issue a warning (but not error).
It is safe to return a reference that is passed into the function as an argument. See earlier examples.
41
LaunchPad
int * squarePtr(int);
int & squareRef(int);
int main( ) {
int number = 8;
cout << number << endl; // 8
cout << *squarePtr(number) << endl; // 64
cout << squareRef(number) << endl; // 64
}
Summary :
Pointers and references are highly complex and difficult to master. But they can greatly improve the
efficiency of the programs.
For novices, avoid using pointers in your program. Improper usage can lead to serious logical bugs.
However, you need to understand the syntaxes of pass-by-reference with pointers and references,
because they are used in many library functions.
q In pass-by-value, a clone is made and passed into the function. The caller’s copy cannot be modified.
q In pass-by-reference, a pointer is passed into the function. The caller’s copy could be modified
inside the function.
q In pass-by-reference with reference arguments, you use the variable name as the argument.
q In pass-by-reference with pointer arguments, you need to use &varName (an address) as the
argument.
42
LaunchPad
7 Dynamic Memory
Allocation
New and Delete Operators :
Instead of define an int variable (int number), and assign the address of the variable to the int pointer
(int *pNumber = &number), the storage can be dynamically allocated at runtime, via a new operator.
In C++, whenever you allocate a piece of memory dynamically via new, you need to use delete to
remove the storage (i.e., to return the storage to the heap).
The new operation returns a pointer to the memory allocated. The delete operator takes a pointer
(pointing to the memory allocated via new) as its sole argument.
For example,
// Static allocation
int number = 88;
int * p1 = &number; // Assign a “valid” address into pointer
// Dynamic Allocation
int * p2; // Not initialize, points to somewhere which is invalid
cout << p2 << endl; // Print address before allocation
p2 = new int;
// Dynamically allocate an int and assign its address to pointer
// The pointer gets a valid address with memory allocated
*p2 = 99;
// The pointer gets a valid address with memory allocated
*p2 = 99;
cout << p2 << endl; // Print address after allocation
cout << *p2 << endl; // Print value point-to
delete p2; // Remove the dynamically allocated storage
43
LaunchPad
To initialize the allocated memory, you can use an initializer for fundamental types, or invoke a
constructor for an object. For example,
// use an initializer to initialize a fundamental type (such as int, double)
int * p1 = new int(88);
double * p2 = new double(1.23);
You can dynamically allocate storage for global pointers inside a function. Dynamically allocated
storage inside the function remains even after the function exits. For example,
// Dynamically allocate global pointers (TestDynamicAllocation.cpp)
#include <iostream>
int main( ) {
allocate( );
cout << *p1 << endl; // 88
cout << *p2 << endl; // 99
delete p1; // Deallocate
delete p2;
return 0;
}
44
LaunchPad
The main differences between static allocation and dynamic allocations are:
1. In static allocation, the compiler allocates and deallocates the storage automatically, and handle
memory management. Whereas in dynamic allocation, you, as the programmer, handle the memory
allocation and deallocation yourself (via new and delete operators). You have full control on the
pointer addresses and their contents, as well as memory management.
2. Static allocated entities are manipulated through named variables. Dynamic allocated entities are
handled through pointers.
C++03 does not allow your to initialize the dynamically-allocated array. C++11 does with the brace
initialization, as follows:
// c++11
int * p = new int[5] {1, 2, 3, 4, 5};
45
LaunchPad
Irrespective of n, print function runs exactly 10 times. So, we ll say, time complexity is T(N) = O(1).
This is Big-O notation
T(N) here denotes the time complexity when represented in terms of N, the input size.
int print(int n){
for(int i = 0; i < n; ++i){
cout << “Coding Blocks”;
}
}
As N increases linearly, printing also happens with the same order. So time complexity is proportional
to N.
T(N) = O(N)
int print(int n){
for(int i = 0; i < n; ++i){
for(int j = i; j < n; ++j)
cout << “Coding Blocks”;
}
}
46
LaunchPad
Lets us assume n = 4
When i is 0 1 2 3
Total printing done = Total time inner loop runs = Sum of first 4 natural numbers
T(N) = Sum of first N natural Numbers = N ( N + 1) / 2 = N 2 / 2 + N / 2
To get the Big-O notation, just keep the term with the highest power of N, N2/2 in our case
Remove all constants associated with this term, in our case 1 / 2 will be removed
Remaining term is the proportionality factor. Hence, we will say time complexity is Big-O of N square.
T(N) = O(N2)
No matter which element’s value you’re asking the function to print, only one step is required. So we
can say the function runs in O(1) time; its run-time does not increase. Its order of magnitude is
always 1.
47
LaunchPad
Let’s look at that practically. Assume the index length N of an array is 10. If the function prints the
contents of it’s array in a nested-loop, it will perform 10 rounds, each round printing 10 lines for a total
of 100 print steps. This is said to run in O(N²) time; it’s total run time increases at an order of
magnitude proportional to N².
void print_array(int arr[], int n){
int x = 1;
for(int i = 0; i < size; ++i){
for(int j = 0; j < size; ++j){
cout << x << “ “ << arr[j] << “ “;
}
++x;
cout << endl;
}
}
Exponential: O(2N)
O(2N) is just one example of exponential growth (among O(3 N), O(4 N), etc.). Time complexity at an
exponential rate means that with each step the function performs, it’s subsequent step will take
longer by an order of magnitude equivalent to a factor of N. For instance, with a function whose
step-time doubles with each subsequent step, it is said to have a complexity of O(2 N). A function
whose step-time triples with each iteration is said to have a complexity of O(3N) and so on.
Let’s say we want to search a database for a particular number. In the data set below, we want to
search 20 numbers for the number 100. In this example, searching through 20 numbers is a non-issue.
But imagine we’re dealing with data sets that store millions of users’ profile information. Searching
through each index value from beginning to end would be ridiculously inefficient. Especially if it
had to be done multiple times.
48
LaunchPad
A logarithmic algorithm that performs a binary search looks through only half of an increasingly smaller
data set per step.
Assume we have an ascending ordered set of numbers. The algorithim starts by searching half of the
entire data set. If it doesn’t find the number, it discards the set just checked and then searches half of
the remaining set of numbers.
Index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Value 8 10 14 20 27 32 33 34 37 44 51 52 55 56 86 87 94 95 97 98
9
Index 0 1 2 3 4 5 6 7 8 9
Value 8 10 14 20 27 32 33 34 37 44
5
Index 0 1 2 3 4
Value 8 10 14 20 27
3
Index 0 1
Value 8 10
1
Index 0
Value 8
1
As illustrated above, each round of searching consists of a smaller data set than the previous,
decreasing the time each subsequent round is performed. This makes log n algorithms very scalable.
void binary_search(int arr[], int size, int key){
int startIndex = 0;
int endIndex = size - 1;
while(startIndex <= endIndex){
int mid = (startIndex + endIndex) / 2;
if (arr[mid] == key) {
return key;
} else if(arr[mid] > key){
// search in the left
endIndex = mid - 1;
}
else {
// search in the right
49
LaunchPad
startIndex = mid + 1;
}
}
return -1;
}
EFFICIENCY DECREASES
SQUARED TIME – O(n2)
TIME TO SOLVE THE PROBLEM
O(nlog(n))
INCREASES
Merge Sort
Space Complexity :
Total amount of computer memory required for the execution of an algorithm is when expressed in
terms of input size is called space complexity. Auxiliary space is the extra space or temporary space
used by an algorithm. Space complexity only includes auxiliary space and NOT the space required by
the input.
50
LaunchPad
9 Object Oriented
Programming
Programming languages help a programmer to translate his ideas into a form that the machine
can understand. The method of designing and implementing the programs using the features of a
language is a programming paradigm. One such method is the Procedural programming which focuses
more on procedure than on data. It separates the functions and the data that uses those functions
which is not a very useful thing as it makes our code less maintainable. This leads us to another
technique called the object oriented programming.
What is OOPs ?
Object means any real world entity like pen, car, chair, etc. Object oriented programming is the
programming model that focuses more on the objects than on the procedure for doing it. Thus we can
say that object oriented programing focuses more on data than on logic or action. In this method the
program is made in a way as real world works. In short it is a method to design a program using objects
and classes.
Advantages of OOPs :
1. It is more appropriate for real world problems.
2. OOP provides better modularity
Data hiding
Abstraction
3. OOPs provides better reusability
Inheritance
4. OOP makes our code easy to maintain and modify.
Polymorphism
Concepts in OOPs : One of the major shortcomings of the procedural method is that the data and
the functions that use those data are separated. This is not preferred as if a data member I changed
then all the functions relating to it have to be changed again and again. This leads to mistakes and
maintaining the code becomes tough.
How to solve?????
The best way to solve this problem would be to wrap the data and all the functions related to them in
one unit. Each of this unit is an object.
51
LaunchPad
Objects are the basic units of Object-oriented programming. Objects are the real world entities
about which we code. Objects have properties and behavior. For instance take an example of a car
which has properties like its model and has four tyres and behavior of changing speed and applying
brakes.
State of the objects is represented by data members and their behavior is represented by methods.
Now many objects share common properties and behavior. So a group of such properties and behavior
is made that can generate many instances of itself that have similar characteristics. This group is
called a class.
Classes are the blueprints from which objects are created. Like there are many cars which share
common properties and behavior. The class provides these properties and behavior to its objects
which are the instances of that class.
For example a vehicle class might look like this:
class Vehicle {
double price;
int numWheels;
int yearofManufacture;
public:
char brand[100];
char model[100];
double getPrice(){
return price;
}
void printDescription(){
cout << brand << “ “ << model << “ “ << price << “ “ << “ “ <<
numWheels << endl;
}
};
52
LaunchPad
Static and Non Static Properties : Static properties are those that are common to all objects and
belong to the class rather each specific object. So each object that we create doesn’t have their copy.
They are shared by all the objects of the class. We need to write the static keyword before it in order
to make it static.
For e.g.: static int numStudents;
Here the number of students in a batch is a property that isn’t specific to each student and hence is
static.
But the properties like name, Roll Number etc can have different values for each student and are
object specific and thus are non static.
An important point to note is that whenever we create a new object only the non static data member
copies are created and the static properties are stored within the class only! This could be considered
a very memory efficient practice as static members of a class are made only once.
A general student class might look like this:
class Student{
static int numStudents;
char name[10];
int rollNo;
};
Access Modifiers :
Private: If we make any data member as private it is visible only within the class i.e. it can be accessed
by and through the methods of the same class. So we can provide setters and getters function through
which they can be accessed outside the class.
For instance in our student class we would like to keep the roll numbers for each student as private as
we may not want any other person to modify those roll numbers by making them public. So we will
make these private and provide the getter method to access the roll numbers of the students outside
the student class.
class Student {
private: int rollNo;
public: int qetRollNo( ) {
return this.rollNo;
}
}
53
LaunchPad
An important point to note here is that it is better to make a variable private and then provide getters
and setters in case we wish allow others to view and change it than making the variable public.
Because by providing setter we can actually add constraints to the function and update value only if
they are satisfied (say if we make the marks of a student public someone can even set them to
incorrect values like negative numbers. So it would be wise to provide a setter method for marks of
the student so that these conditions are checked and correct marks are updated).
Methods : After having discussed about the data members of a class let us move onto the methods
contained in the class relating to the data members of the class. We made many members of the
class as private or protected. Now to set, modify or get the values of those data members,
public getter and setter methods can be made and called on the objects of that class. The methods are
called on the object name by using the dot operator.
Now let us look at various modifiers that can be used to modify the types of methods and the
differences between them.
Static v/s Non Static Methods : Like data members, methods of a class can also be static which means
those methods belong to the class rather than the objects for the class. These methods are directly
called by the class name.
As the static methods belong to a class we don’t need any instance of a class to access them.
An important implication of this point is that the non static properties thus can’t be accessed by the
static methods as there is no specific instance of the class associated with them (the non static
properties are specific to each object). So, non static members and the ‘this’ keyword can’t be used
with the static functions. Thus these methods are generally used for the static properties of the class
only!
The non static methods on the other hand are called on an instance of a class or an object and can thus
access both static and non static properties present in the object.
The access modifiers work the same with the methods as they do with the data members. The public
methods can be accessed anywhere whereas the private methods are available only within the same
class. Thus private methods can be used to work with the data members that we don’t wish to expose
to the clients.
Now let us add some methods to our student class.
class Student{
static int numStudents;
char name[10];
int rollNo;
public:
char * getName(){
return name;
54
LaunchPad
}
int getRollNo(){
return this.rollNo;
}
int getNumStudents(){
return numStudents;
}
}
Constructor : As we have our student class ready, we can now create its objects. Each student will be
a specific copy of this template. The syntax to create an object is:
int main(){
Student s; //s is created in the memory
The method called constructor, is a special method used to initialize a new object and its name is
same as that of the class name.
Even though in our student class we haven’t created an explicit constructor there is a default
constructor implicitly there. Every class has a constructor. If we do not explicitly write a constructor for
a class, the C++ compiler builds a default constructor for that class.
We can also create our own constructors. One important point to note here is that as soon as we
create our constructor the default constructor goes off. We can also make multiple constructors each
varying in the number of arguments being passed (i.e. constructor overloading). The constructor that
will be called will be decided on runtime depending on the type and number of arguments
specified while creating the object.
Below is our own custom constructor for our student class.
class Student{
char name[10];
int rollNo;
Student(char n[]){
strcpy(name, n);
}
Student(char name[], int rollNo){
strcpy(this->name, n);
this->rollNo = r;
}
}
55
LaunchPad
The ‘this’ keyword : Here this is a keyword that refers to current object. So, this.name refers to the
data member (i.e. name) of this object and not the argument variable name.
In general, there can be many uses of the ‘this’ keyword.
1. The ‘this’ keyword can be used to refer current class instance variable.
2. The this() can be used to invoke current class constructor.
3. The ‘this’ keyword can be used to invoke current class method (implicitly)
4. The ‘this’ can be passed as an argument in the method call.
5. The ‘this’ can be passed as argument in the constructor call.
6. The ‘this’ keyword can also be used to return the current class instance.
There is also a special type of constructor called the Copy Constructor. C++ doesn’t have a default copy
constructor but we can create one of our own. Given below is an example of a copy constructor.
class Student{
char name[20];
int rollNo;
After leaning so much about the objects and constructors let us summarize the initialization of an
object in simple steps.
As any new object is created, following steps take place:
1. Memory is allocated on heap and its reference is stored in stack.
2. The data members are initialized to their default values. (Only the non static properties are
copied!)
3. The ‘this’ keyword is set to the current object.
4. The instance initialize is run and the fields are initialized to their respected values.
The constructor code is executed.
56
LaunchPad
The final memory map of the instance of our student class is given below:
Encapsulation-The First Pillar of OOPs : We learnt that one of the major disadvantages of the procedural
paradigm was that the functions and the data were separated which made the maintainability of the code
poor! To overcome this we made classes which contained both the data members and their methods.
This wrapping up of data and its functions into a single unit (called class) is known as Encapsulation.
A class classifies its members into three types: private, protected and public. Thus Data Hiding is implemented
through private and protected members. These private and protected members can be accessed or set
using public functions.
Also, the outside world needs to know only the essential details through public members. The rest of the
implementation details remain hidden from the outside world which is nothing but Abstraction. Like for
instance if we wish to know the aggregate marks of a student, we need not know how the aggregate is
calculated. We are just interested in the marks and for that we only need to know that we need to call a
public function called ‘totalMarks’ on any student object.
We have an additional benefit that comes bounded with encapsulation which is Modularity!
Modularity is nothing but partitioning our code into small individual components called modules. It makes
our code less complex and easy to understand. Like for example, our code represents a school. A school has
many individual components like students, teachers, other helping staff, etc. now each of them are complete
units in themselves yet they are part of the school. This is called modularity.
After having the overview of benefits of encapsulation let us dive into the details of data hiding and
abstraction.
Let us suppose that we make all the data members of our student class as public. Now accidently if any of
our client changes the roll numbers of the student objects, all are data would be ruined. Thus public data
is not safe. So, to make or data safe, we need to hide our data by making it either private or protected! This
technique is called information hiding. The private data can be accessed only via a proper channel that is
through their access methods which are made public.
To use a class, we only need to know the public API of the class. We only need to know the signature of the
public methods that is their input and output forms to access a class. Thus, only the essential details are
sown to the end user and all the complex implementation details are kept hidden. This is Abstraction.
After all sometimes ignorance is bliss!
57
LaunchPad
10 Linked Lists
We have used arrays to store multiple values under a single name. But using arrays has its disadvantages
that :
Ø They are static and hence their size cannot be changed
Ø It needs contiguous memory to store its values
Ø It is difficult(costly) to insert and delete elements from an array
To overcome these problems, we use another data structure called linked lists.
Linked list is a linear data structure that contains sequence of elements such that each element has a
reference to its next element in the sequence. Each element in a linked list is called “Node”.
Each Node contains a data element and a Node next which points to the next node in the linked list.
In a linked list we can create as many nodes as we want according to our requirement.
This can even be done at the runtime. The memory allocation is done at the run time and is known as
DYNAMIC MEMORY ALLOCATION. Thus, linked list uses dynamic memory allocation to allocate
memory at the run time.
General Implementation : The linked list class contains a structure Node and a Node head.
The Node head points to the starting of the linked list. Every Node contains a data element and a Node
next which points to the next node in the linked list. The last node points to null.
struct Node{
int data; //Data of the Node
Node* next; // pointer to t
}
58
LaunchPad
Doubly Linked List : These are linked lists in which every node points to the next node as well as the
previous node.
Circular Linked List : This is a type of singly linked list in which the last node points to the starting
node.
Applications :
1. Process Queue’s in operating system are actually doubly linked list of processes in ready state
where the process at the front of linked list denotes the one to be operated next.
2. Any situation where insertion and deletion operations are more as compared to retrievals.
59
LaunchPad
Big O efficiency :
Insertion O(n)
Deletion O(n)
Searching O(n)
60
LaunchPad
Stack :
This is LIFO (Last In First Out), as the last element that is added, is the first to be removed off.
Removal from in between is not allowed, i.e. Peaking is not allowed in ideal stack.
Stack operations:
q Push : Addition of an element to stack.
q Pop : Removal of element from stack.
61
LaunchPad
Queue :
Suppose your friend just sent you 100 messages. If you wish to read the 1st message first, then the 2nd
message and so on, then you are basically reading it in a queue fashion.
This follow FIFO(First In First Out) method. Another example of queue is getting a token on a railway
platform. The person who came first will receive the token first. And not to foreget the queue outside
ATMs after demonetization.
Functions on queue :
q Enqueue/push : enter an element to the queue.
q Dequeue/pop : remove an element from the queue.
q IsFull : check whether the queue is full or not.
q IsEmpty : check whether the queue is empty or not.
q Size : return the size of the array.
Queue like stack can be implemented using Linked List , arrays, or vectors. etc
62
LaunchPad
Observation :
The search function above works only for an integer array. However the functionality search, is logically
separate from array and applicable to all data types, i.e. searching a char in a char array or searching is
applicable in searching in a linked list.
Hence, data, algorithms and containers are logically separate but very strongly connected to each
other.
Generic Programming enables programmer to achieve this logical separation by writing general
algorithms that work on all containers with all data types.
Separating Data
A function can be made general to all data types with the help of templates.
Templates are a blueprint based on which compiler generates a function as per the
requirements.
To create a template of search function, we replace int with a type T and tells compiler that T is
a type using the statement template <class T>
template <class T>
int search(T arr[], int n, T elementToSearch) {
for (int pos = 0; pos < n; ++pos) {
if (arr[pos] == elementToSearch) {
return pos;
}
}
return -1;
}
Now the search function runs for all types of arrays for which statement 4 is defined.
Now the search function runs for all types of arrays for which statement 4 is defined.
63
LaunchPad
int arrInt[100];
char arrChar[100];
float arrFloat[100];
Book arrBook[100], X; //X is a book
search(arrInt, 100, 5); //T is replaced by int
search(arrChar, 100, ‘A’); //T is replaced by char
search(arrFloat, 100, 1.24); //T is replaced by float
search(arrBook, 100, X); //T is replaced by Book.
So, one function can be run on different data types. This makes our function general for all types of
data.
Note
In the actual code that is produced after compilation, 4 different functions will be produced based on
the template with T replaced accordingly.
You could use <typename T> or <class T> in the template statement 1. The keywords typename and
class serve the same purpose.
Separating Algorithm
The search function will not work for Book objects if computer doesn’t know how to compare
2 books. So our function is limited in some sense. To work it for all data types, we use a concept
of comparator (also see predicate).
Let’s rewrite the templated search function again
template <class T, class Compare>
int search(T arr[], int n, T elementToSearch, Compare obj) {
//compare is a class that has () operator overloaded
for (int pos = 0; pos < n; ++pos) {
if (obj(arr[pos], elementToSearch) == true) {
//obj compares elements of type T
return pos;
}
}
return -1;
}
64
LaunchPad
To use the search function for integers, you shall now write:
//defining a class compare
class compareInt {
public:
bool operator()(int a, int b) {
return a == b ? true : false;
}
};
//calling search templated function
compareInt obj;
//obj is the object of class compareInt
search(arrInt, 100, 20, obj); //T replaced with int
//Compare replaced with compareInt
The same search function now operators for Books just by writing a small compare class.
However, search function still works only for arrays. However the functionality of searching extends
to list equally. To make it general for all containers(here array) we introduce a concept of iterators
in our discussion.
Separating Containers
Iterators
Visualise iterators as an entity using which you can access the data within a container with
certain restrictions.
65
LaunchPad
Output Iterator An entity through which you can write into the container and move ahead.
Container like printer or monitor will have such an iterator.
Forward Iterator Iterator with functionalities of both Input and Output iterator in single direction.
Singly linked list will posses a forward entity since we can read/write only in the forward direction.
Bidirectional Iterator Forward iterator that can move in both the directions.
Doubly linked list will posses a bidirectional iterator.
Random Access Iterator Iterator that can read/write in both directions plus can also take jumps.
An array will have random access iterator. Since, you can jump by writing arr[5], which means jump to
the 5th element.
Entity that does this, behaves like a pointer in some sense.
To write search function that is truly independent of data and the underlying container, we use iterator.
template<class ForwardIterator, class T, class Compare>
ForwardIterator search(ForwardIter beginOfContainer, ForwardIter endOfContainer,
T elementToSearch, Compare Obj) {
while (beginOfContainer != endOfContainer) {
if (obj((*beginOfContainer), elementToSearch) == true) break; //iterators are like
pointers!
++beginOfContainter;
}
return beginOfContainer;
}
Here, beginOfContainer is a ForwardIterator, i.e., beginOfContainer must know how to read/write
and move in forward direction.
So, if a container has at least ForwardIterator, the algorithm works. Hence, it works for list, doubly
linked list and array as well thus achieving generality over container.
66
LaunchPad
Summary
1. Using templates, we achieve freedom from data types
2. Using comparators, we achieve freedom from operation(s) acting on data
3. Using iterators, we achieve freedom from underlying data structure (container).
67