C++ From Beginner to Professional by Example (1)
C++ From Beginner to Professional by Example (1)
TOMÁS CRAGNOLINI
INTRODUCTION
C++
C++ supports the concepts of object-oriented programming (or OOP for short),
which are:
Header files are text files containing declarations and macros. By using an #include
directive these declarations and macros can be made available to any other source file, even
in other header files.
Pay attention to the following points when using header files:
● Header files should generally be included at the start of a program before any
other declarations.
● You can only name one header file per #include directive.
● The file name must be enclosed in angled brackets < ... > or double quotes.
The header files that accompany your compiler will tend to be stored in a folder of their own
—normally called include. If the name of the header file is enclosed by angled brackets < ...
>, it is common to search for header files in the include folder only.
The current directory is not searched to increase the speed when searching for header files.
C++ programmers commonly write their own header files and store them in the current
project folder. To enable the compiler to find these header files, the #include
directive must state the name of the header files in double quotes.
The compiler will then also search the current folder. The file suffix .h is normally used for
user-defined header files.
In addition to standard function prototypes, the header files also contain standard class
definitions. When a header file is included, the classes defined and any objects declared in
the file are available to the program.
Example:
The classes istream and ostream can be used with the cin and cout streams. cin is an object
of the istream class and cout an object of the ostream class.
C++ also includes the C programming language header files
CHAPTER 1
1. Variables
A variable is a data structure that allows storing different kinds of values . Variables are the
names you give to a specific computer's memory location where values are stored. You get
access to the value stored by using variable’s names.
Picture this like a little box where you want to store something and you label that box in order
to know what it contains.
As for a real world example, imagine you have a box where you want to store books of their
genre and you also want to know how many are .
You’d possibly follow this approach: you'd get a box and stick a label on it with the following
writing .
books of fiction
20
When you take that box for whatever purpose you know it contains 20 books according to
the genre “ fiction “ .
// this is a variable called BooksOfFictionBox (represents the box labeled books of fiction)
which stores( represented by the equal sign “=” ) 20 fictional books (quantity of book items)
BooksOfFictionBoxNumber= 20;
Let’s go through this example and make it work following a C syntax program :
1) Type : The type relates to the kind of value you want to store:In the example
20 is used to indicate the number of books. This means numbers are
considered a value type .
2) Variable name: It’s just a way to give a name that best describes what the
value what’s stored . In the books variable you use the name
BooksOfFictionBoxNumber to denote that you’re going to store the number of
books of that genre.
In the example each data type is followed by a variable’s name and the value stored.
In the table below you’ll see included some of the basic types with some modifications , the
value range (numbers that can be stored ) and the printf format specifier. I’ll just give an
explanation of the types not discussed beforehand.
III. Data types Modifiers
If you pay attention there are some new keywords before the basic types:
a) short
b) long
c) unsigned/signed
These are called modifiers just like the const keyword but they affect the value range of the
basic types when used in combination.
1) Short reduces the range capacity in a half. So when you declare a variable
using the short modifier it takes half the storage capacity that a normal int:
● A normal int takes 4 bytes of memory storage capacity.A short int takes 2 bytes of
memory storage capacity.
b) Long doubles the int memory storage capacity : An int data type takes 4 bytes of
memory storage.A long int takes 8.
1) signed means that the value range can include negative and positive numbers
including 0.
2) unsigned means that the value range can only include positive values including 0.
types by default are signed .They do not affect the memory storage capacity but it changes
the value range these variables can contain.
Format specifiers are placeholders that are replaced by the argument values supplied in the
printf() function between others.
In the format string section you add the format specifiers and in the arguments section you
add the values you want to printf(). This function displays the arguments provided on the
screen.
Each placeholder points to the corresponding argument in the exact same order they’re
included.
Example :
#include <stdio.h>
int main () {
printf(“%ld”, lampsQuantity);
return 0;
The amount of memory needed to store an object of a certain type can be ascertained
using the sizeof operator:
sizeof(name)
It yields the size of an object in bytes, and the parameter name indicates the object type or
the object itself. This means if you store a value of type int the computer will allocate 2 or
4 bytes according to the operating system architecture (64 or 32 bytes)
The following graphic describes the size in bytes for each data type. Notice that the graphics
do not always include each of them.
CHAPTER 2
DATA STRUCTURES
I. Declaration :
Examples:
int clients;
II. ASSIGNMENT/INITIALIZATION
The following graphic details the different ways you can use to proceed with value
initialization.
a) Equal Operator Initialization
b) Constructor Initialization:
c) Brace Initialization :
Despite the fact that there are many ways to initialize a variable not every syntax
works for the different operations you might need to carry out.
it is highly recommended to stick to the brace and equal initialization methods.
I. Integer Types
There are four integer types you can use depending on the range value you want to store:
Modifiers:
You can supply suffixes to an integer literal to specify its type (suffixes are case insensitive,
so you can choose the style you like best):
By literals, it is meant data that have not been assigned to any variable .
For Example :
You can combine the unsigned suffix with either the long or the long
long suffix to specify signed-ness and size. The Table shows the possible types
that a suffix combination can take. Allowed types are shown with a check
mark (). For binary, octal, and hexadecimal literals, you can omit the u
or U suffix. These are depicted with an asterisk (*).
If you want to enforce guaranteed integer sizes, you can use integer types in the
<cstdint> library.
The Microsoft C++ compiler features five sized integer types. These types start with
int followed by the number of bits you want the integer to hold – either 8, 16, 32 or 64 bits.
The floating-point types can store real numbers with different levels of precision.
This includes float and double types.
float myFloat = 3.14; // 3.4E +/- 38 (7 digits)
double myDouble = 3.14; // 1.7E +/- 308 (15 digits)long double
long double num= 3.14; // same as double
As with all types, floating-point types take up a finite amount of memory,which is called the
type’s precision. The more precision a floating-point type has, the more accurate it will be at
approximating a real number. C++ offers three levels of precision for approximations:
Floating-point literals are double precision by default. If you need single precision,
use an f or F suffix; for extended precision, use l or L, as shown here:
The conversion between the number stored in the char and the character shown
when the char is printed occurs automatically.
The bool type can store a Boolean value, which is a value that can only be either true or
false. These values are specified with the true and false keywords.You need to include
_Bool if you want to use those values. Otherwise you have to use 0 to indicate false and 1 to
Indicate true .
It is possible to convert the type of an expression explicitly using the cast operator
(type).
This converts the value of an expression to the given type. Explicit type conversion is
also known as casting.
The cast operator (type) is a unary operator and thus has a higher precedence than
the arithmetic operators.
Example:
int a = 1, b = 4;
double x;
x = (double)a/b;
You use the size_t type, also available in the <cstddef> header, to encode size
of objects. The size_t objects guarantee that their maximum values are sufficient
to represent the maximum size in bytes of all objects.
Technically, this means a size_t could take 2 bytes or 200 bytes depending on the
implementation.
In practice, it’s usually identical to an unsigned long long on 64-bit
architectures.
NOTE
You can add the const prefix before any data type declaration.Although you must define the
variable straight away , otherwise the compiler will throw an error.
What’s achieved by the const keyword is that you are not allowed to modify the value
assigned later in the program. the next example will throw an error 👍
Example :
isOn=true
This reassignment won’t produce effects and the compiler will warn you that you're trying to
modify a const variable.
In almost all situations, the compiler can determine the correct type of an
object using the initialization value. This assignment contains redundant
information:
1. Functions
A function is a sequence of program instructions that performs a specific task, packaged as
a unit.
Just like variables you need to declare and define them but there’s also a further step which
is referred to as calling.
I. Declaration
In a function declaration, we must provide the function name, its return type, the number and
the parameters type . A function declaration tells the compiler that there is a function
declared somewhere in the program.
The parameter name is not mandatory while declaring functions. We can also declare the
function without using any of them. Parameters are variables that can only be accessed
inside the function and expect the user to provide the specified value types called
arguments.
II. Function Definition
The function definition consists of actual statements which are executed when the function is
called (i.e. when the program control comes to the function).
A C/C ++ function is generally defined and declared in a single step because it always starts
with the function declaration so we do not need to declare it explicitly. The below example
serves as both a function definition and a declaration.
A function call is a statement that instructs the compiler to execute the function. We use the
function name and parameters in the function call.
In the below example, the first sum function is called and 10,30 are passed to the sum
function. After the function call sum of a and b is returned and control is also returned back
to the main function of the program.
Function call is necessary to bring the program control to the function definition. If not
called, the function statements will not be executed.
// Calling sum function and storing its value in add variable
int main {
int add = sum(10, 30);
printf("Sum is: %d", add);
return 0;
}
Output
Sum is: 40
As we noticed, we have not used explicit function declaration. We simply defined and called
the function.
Function Arguments are the data that is passed to a function that is required by the
parameter types. This is shown in the previous example. The sum function is passed two int
arguments (values) to perform the calculation.
Example:
int function_name(int var1, int var2); //these are the parameters
sum (10, 30); // these are the arguments
V. Function Return Type
Function return type tells what type of value is returned after all the function´s statements
have finished being executed. When we don’t want to return a value, we can use the void
data type.
Example:
int func (parameter_1,parameter_2);
The above function will return an integer value after running statements inside the function.
Note: Only one value can be returned from a C function. To return multiple values, we have
to use pointers or structures.
In C programming language, functions can be called either with or without arguments and
might return values. They may or might not return values to the calling functions.
Working of the C function can be broken into the following steps as mentioned below:
3. Calling the function: Calling the function is a step where we call the function by
passing the arguments in the function.
4. Executing the function: Executing the function is a step where we can run all
the statements inside the function to get the final result.
5. Returning a value: Returning a value is the step where the calculated value after
the execution of the function is returned. Exiting the function is the final step
where all the allocated memory to the variables, functions, etc is destroyed
before giving full control to the main function.
Example 1 :
#include <stdio.h>
// Driver code
int main()
{
return 0; }
Output
Sum is: 40
a) Library Functions
b) User Defined Functions
a) Library Function
A library function is also referred to as a “built-in function”. Built-in functions have the
advantage of being directly usable without being defined, whereas user-defined functions
must be declared and defined before being used.
// Driver code
int main() {
double Number;
Number =100;
For Example:
Functions that the programmer creates are known as User-Defined functions or “tailor-
made functions”. User-defined functions can be improved and modified according to the
need of the programmer.
Example:
#include <stdio.h>
// Driver code
int main(){
// function call
return 0; }
2. LOOPS
Loops in programming are used to repeat a block of code until the specified condition is met.
A loop statement allows programmers to execute a statement or group of statements
multiple times without repetition of code.
c) Update Expression: After execution of the loop body the loop variable is
updated by some value; it could be incremented, decremented, multiplied, or
divided by any value.
The first program shows how you must proceed to print text several times. You’d need to
write as many printf() functions as you need to have the text printed the times you
want.
#include <stdio.h>
int main(){
#include <stdio.h>
int main(){
int i = 0;
return 0; };
3. Introducing Arrays
I. C Array Declaration
In C, we have to declare the array like any other variable before using it. We can declare an
array by specifying its name, the type of its elements, and the size of its dimensions. When
we declare an array in C, the compiler allocates the memory block of the specified size to
the array name.
or
#include <stdio.h>
int main() {
int arr_int[3];
char arr_char[95];
return 0;
}
Initialization in C is the process to assign some initial value to the variable. When the
array is declared or allocated memory, the elements of the array contain some
garbage value. So, we need to initialize the array to some meaningful value. There
are multiple ways in which we can initialize an array in C..
In this method, we initialize the array along with its declaration. We use an initializer
list to initialize multiple elements of the array. An initializer list is the list of values
enclosed within braces { } separated by comma.
If we initialize an array using an initializer list, we can skip declaring the size of the
array as the compiler can automatically deduce the size of the array in these cases.
The size of the array is equal to the number of elements present in the initializer list
as the compiler can automatically deduce the size of the array.
We initialize the array after the declaration by assigning the initial value to each
element individually. We can use for loop, while loop, or do-while loop to assign the
value to each element of the array.
array_name[i] = value i;
}
Example :
#include <stdio.h>
int main(){
int arr1[] = { 1, 2, 3, 4, 5 };
float arr2[5];
return 0;
}
We can access any element of an array in C using the array subscript operator [ ]
and the index value i of the element.
array_name [index];
One thing to note is that the indexing in the array always starts with 0, i.e., the first
element is at index 0 and the last element is at N – 1 where N is the number of
elements in the array.
Program Description :
#include <stdio.h>
int main()
{
return 0;
}
Output
Element at arr[2]: 35
Element at arr[4]: 55
Element at arr[0]: 15
We can update the value of an element at the given index i in a similar way to accessing
an element by using the array subscript operator[] and assignment operator =.
array_name[i] = new_value;
V. Length of Array in C
It returns an integer value that represents the size of the expression or a variable in bytes.
The sizeof operator is primarily used for dynamic memory allocation but it can also be used
to find the length of an array.
Syntax:
● data_type: It is the type of variable in which we want to store the length of the
array.(like int, size_t, etc.).
● Array_name: It is the name of the array you want to find the size of.
Example:
#include <stdio.h>
int main(){
int Arr[] = { 1, 2, 3, 4, 5 };
return 0; }
Output
b) Using Loop
The loop method is used to calculate the length of an array in C. It iterates through all the
elements of an array and increments the count.
Example:
#include <stdio.h>
int i;
int count = 0;
count++;
}
return count; }
int main() {
n = arr_length(arr);
return 0;
}
Output
Note: Please noteThe iterative methods of finding the length of the strings (array of
characters) also cannot be applied to the array of other types as there is no end indicator in
these array types in contrast to the ‘\0’ NULL character marking the end of the string. that
these methods only works when the array is declared in the same scope. These methods
will fail if we try them on an array which is passed as a pointer. This happens due to Array
Decay
Traversal is the process in which we visit every element of the data structure. For C array
traversal, we use loops to iterate through each element of the array.
array_name[i];
Program Description:
// C Program to demonstrate the use of array
#include <stdio.h>
int main(){
arr[2] = 100;
return 0;
}
Output
There are two types of arrays based on the number of dimensions it has. They are as
follows:
The One-dimensional arrays, also known as 1-D arrays in C are those arrays that
have only one dimension.
Syntax of 1D Array in C:
array_name [size];
Program Description:
int main()
{
// 1d array declaration
int arr[5];
return 0;
}
Output:
Elements of Array: 1 0 1 4 9
b) Multidimensional Array in C
Multi-dimensional Arrays in C are those arrays that have more than one dimension.
Some of the popular multidimensional arrays are 2D arrays and 3D arrays. We can
declare arrays with more dimensions than 3d arrays but they are avoided as they get
very complex and occupy a large amount of space.
● Two-Dimensional Array in C
Syntax of 2D Array in C
array_name[size1] [size2];
Here,
Program description:
int main() {
printf("2D Array:\n");
// printing 2d array
printf("%d ",arr[i][j]); }
printf("\n"); }
return 0; }
Output
2D Array:
10 20 30
40 50 60
● Three-Dimensional Array in C
Syntax of 3D Array in C:
int main() {
// 3D array declaration
// printing elements
printf("\n");
}
printf("\n \n");
}
return 0; } ;
Output
10 20
30 40
50 60
0 0
CHAPTER 4
1. C-STYLE STRINGS
String literals can be assigned without size. Here, the name of the string str acts as a pointer
because it is an array.
char str[] = "learning";
We can also assign a string character by character. But we should remember to set the end
character as ‘\0’ which is a null character.
We can assign character by character without size with the NULL character at the end. The
size of the string is determined by the compiler automatically.
Note: After declaration, if we want to assign some other text to the string, we have to
assign it one by one or use built-in strcpy() function because the direct assignment of
string literal to character array is only possible in declaration.
Example :
#include <stdio.h>
#include <string.h>
int main()
{
// declare and initialize string
// print string
printf("%s\n", str);
int length = 0;
length = strlen(str);
return 0;
}
Output
learning
Length of string str is 5
We can see in the above program that strings can be printed using normal printf statements
just like we print any other variable. Unlike arrays, we do not need to print a string, character
by character.
Note: The C language does not provide an inbuilt data type for strings but it has an access
specifier “%s” which can be used to print and read strings directly.
#include <stdio.h>
int main()
{
// declaring string
char str[50];
// reading string
scanf("%s",str);
// print string
printf("%s",str);
return 0;
}
Input
learning
Output
learning
You can see in the above program that the string can also be read using a single scanf
statement. Also, you might be wondering why we have not used the ‘&’ sign with the string
name ‘str’ in the scanf statement! To understand this you will have to recall your
knowledge of scanf.
We know that the ‘&’ sign is used to provide the address of the variable to the scanf()
function to store the value read in memory. As str[] is a character array so using str
without braces ‘[‘ and ‘]’ will give the base address of this string. That’s why we have
not used ‘&’ in this case as we are already providing the base address of the string to
scanf.
// driver code
int main(){
char str[20];
scanf("%s", str);
printf("%s", str);
return 0;
}
Input
Learning takes time and effort and much more than just going for a job.
Output
Learning takes time and effort and much more than just going for a job.
We can use multiple methods to read a string separated by spaces in C. The two of the
common ones are:
1. We can use the fgets() function to read a line of string and gets() to read
characters from the standard input (stdin) and store them as a C string until a
newline character or the End-of-file (EOF) is reached.
Example 1:
#include <stdio.h>
#define MAX 50
int main(){
char str[MAX];
puts(str);
return 0;
}
Input
just type what you want
Output
String is:
just type what you want
Example 2 :
// c program
// scanset characters
#include <stdio.h>
// driver code
int main(){
char str[20];
printf("%s", str);
return 0; }
Input
doing some more printing
Output
doing some more printing
Example 3 :
#include <stdio.h>
int main(){
// printing string
int i = 0;
while (arr[i]) {
printf("%c", arr[i++]);
return 0;
}
Output : hey
STRINGS IN C++
to avoid using char arrays to represent strings C++ included the <string> header file in order
facilitate operation with a chain of characters
if you need to use a string variable you just include the header file and use the string type in
front of a variable.
CHAPTER 5
1. POINTERS
A pointer is a data type that holds the address location in memory of another variable, and it
can also have access to its value . A pointer always points to an object of a specific type.
type * pointer_name ;
First you need a variable of the type the pointer variable will point to.
Once the pointer variable has been initialized it holds the value of the memory location of the
pointed variable.
In the example the location in memory of the variable pencils is used .
In both cases keep in mind that the value of a pointer data type is the memory location of the
given variable (address in memory) and not its value, even though you can access that its
value by dereferencing (see the next title).
Example:
Example:
pointerToPencils = &pencils;
Example:
Pointers can also have the modifiers or keywords like any other data type.
When you work with const pointers you need to distinguish between two different
operations:
1. changing the value of the pointed variable (dereferencing):
See an example :
// int variables
int books=3;
// int variables
int chapter=2;
// pointer variable
// error you cant modify the value but you can assign the pointer
to point to a different variable.
*bookP= 15;
bookP=&chapter;
V. Pointers To Const
Example :
int numExample= 1;
Example Program:
int main () {
// int variables
int books= 3;
// int variables
int chapter=2;
// this will throw an error since you cannot modify the variable
booksP points to.
bookP=&chapter;
bookP=22;
return 0 ;}
Here there are some examples using the auto keyword with pointers:
chapter 6
The new function is a little different from other functions in that you don’t put parentheses
around its parameter. For this reason, it’s actually an operator.
Syntax :
Example:
*ptr=10;
After the computer creates the new integer variable, it stores the address of the integer
variable ptr and right after it you assign a value to that pointer:
Program Example :
#include <iostream>
using namespace std;
*ptr=10;
std::cout<<*ptr<<std::endl;
std::cout<<ptr<<std::endl;
return 0;}
Output:
10
0x73af10
Note
The following code calls new, but also provides a number in parentheses.
That number is put in the memory initially, instead of being assigned to it later.
Once you finish using the pointer variable you must delete it to free space in memory :
#include <iostream>
int main() {
delete phrase;
return 0; }
OUTPUT:
Note
Don’t try to use the pointer after freeing the memory it points to until you set the pointer to
point to something else through a call to new or by setting it to another variable.
Whenever you free a pointer, a good habit is to set the pointer to the value 0 or
nullptr.
This section discusses two smart pointer classes from an overview perspective:
2. A shared_ptr is used when you actually do need to copy pointers. You can copy a
shared_ptr to another pointer.
*ptr1 = 100;
unique_ptr<int> ptr2(&myValue);
std:: cout << "ptr3 value: " << *ptr3 << std::endl;
std:: cout << "ptr3 address: " << ptr3.get() << std::endl;
Notice
1. Unlike most pointers, you can’t simply specify the pointer name and obtain its
address:
● You can use the move() function to move the address of one
unique_ptr to another unique_ptr.
Program Example:
#include <iostream>
#include <memory>
using namespace std;
int main() {
// using move
unique_ptr<int> ptr4;
ptr4 = move(ptr3);
if (ptr3 == nullptr) {
//using get
std::cout << "ptr4 address: " << ptr4.get() <<std:: endl;
return 0;}
Output:
When working with a shared_ptr, you can make one pointer equal to another
pointer. The code demonstrates that both ptr1 and ptr2 point to the same memory location
and have the same value.
Consequently, the resource (not the pointers) is shared between the two pointers.
To make it easier to determine how many references a resource has, you use the
use_count() function.
Of course, now you need some way to remove references when they’re no longer
needed. To perform this task, you use reset().
The code uses ptr2.reset() to remove the second reference to myValue. As shown in the
following output, the use count decreases each time you reset() a pointer.
On the next page I’ve detailed the example in order to make it more readable.
Program Example:
#include <iostream>
#include <memory>
using namespace std;
int main() {
ptr2.reset();
std::cout << "ptr1 use count: " << ptr1.use_count() <<std:: endl;
ptr1.reset();
std::cout << "ptr1 use count: " << ptr1.use_count() << std::endl;
return 0;}
Output:
#include <iostream>
#include <optional>
using namespace std;
if (name == nullopt {
} else {
int main() {
myFunction();
myFunction("lilly");
return 0;}
When you run this application, you see the following output:
Before the function call, mynumber is 30. And after the function call, it’s still 30.
But the function added 10 to its parameter. This means that when the function
modified its parameter, the original variable remains untouched. The two are separate
entities. Only the value 30 went into the function. The actual variable did
not. It stayed in main(). But what if you write a function that you want to modify
the original variable?
A function called ChangesAreGood() that modifies the parameter it receives. (It adds 10 to
its parameter called myparam.) It then print the new value of the parameter.
The main() function initializes an integer variable, mynumber, to 30 and prints its
value. It then calls the ChangesAreGood() function, which changes its parameter
After coming back from the ChangesAreGood() function, main() prints the value
again.
Example :
int main() {
When you run this application, you see the following output:
CHAPTER 6
1. REFERENCING
Example:
int pencils = 3 ;
int &refToPencils = pencils; // its value is now 3
Note
1. Once initialized, a reference remains bound to its initial object. So, it is not
possible to reassign a reference to another object.
Example 1 :
#include <iostream>
#include <cstdio>
using namespace std;
int main() {
}
Output:
Original: 100
Reference: 100
Original: 200
New Value: 200
Reference: 200
References to const cannot be used to change the object they refer to.
they’re read-only references.
Example :
You can modify the value of var countries but not from the refToCountries variable.
If you change the value of countries the changes will be visible in refToCountries ;
Syntax:
a) type variable_name;
Snippet :
int countries =250;
refToCountries++ ;
1. ARRAYS
An array is a data structure containing a series of elements of the same type that have been
placed in contiguous memory locations and can be accessed by their position.
I. TYPES OF ARRAYS
1) SIMPLE ARRAYS
Like any other variable, arrays need to be declared before they can be used. An array
declaration has the following form:
Here, type is the type of the contained elements, name is the identifier of the array
variable, and elements is the length of the array, so it signifies the number of elements
contained within.
It is important not to exceed the declared length of elements since it can produce runtime
errors. Don’t forget to always use elements that match the array’s type.
b) Array Initialization
2) Its length can also be omitted so the following declaration takes the inserted
elements to determine the array’s length.
3) If the number of elements is provided, but the array is initialized with fewer elements,
then the remaining value will be zero-initialized, for example:
From index 2 all values will be assigned to 0.The previous code is equivalent to the
following:
4) You also can skip specifying the array size when you pass an array into a function,
like this:
● But this way to do it is kind of annoying because you have to specify the size each
time you call in to the function. However, you can get around this problem. Look
at this line of code:
Syntax: arrayName[index]
Example:
Use the sizeof operator to obtain the total size in bytes of an array. You can
use a simple trick to determine the number of elements in an array: divide
the size of the array by the size of a single constituent element:
int books[5] = { 1, 2, 3, 4, 5 };
books[2]=8;
h) Building Simple Constant Arrays
It works like any other array. What you can't do is change the numbers inside it.
2) MULTIDIMENSIONAL ARRAYS
int Numbers[5][6] = {
{1,2,3,4,5,6},
{7,8,9,10,12},
{13,14,15,16,17,18},
{19,20,21,22,23,24},
{25,26,27,28,29,30}
};
More member arrays can be added but you can't modify the given values.
You use two for loops, one that takes the array's quantity and an additional one that loops
through the elements contained.
int AddAll(int numbers[5][6]) {
int x,y;
int sum = 0;
for (x = 0; x < 5; x++) {
for (y = 0; y < 6; y++) {
sum += numbers[x][y];}
return sum;}
1. The first points to a character string in memory containing Tom (which is followed by
a null-terminator, \0);
Example Array:
f) Class Arrays
Array elements can also be objects of a class type. The array is known as a class array in
this case. When you declare an array of this type, you only need to state the type of the
array elements.
A class array is initialized as usual by an initialization list. The list contains a constructor call
for each array element.
g) Array of Objects
Once an object is instantiated it can be added to an array just like any other data
structure/data type.
Program example :
#include<iostream>
using namespace std;
class birds{
public:
string name;
string color;
string habitat;
int main () {
1. ARRAY AS ARGUMENTS
The argument is an array, there are two equivalent methods for declaring parameters.
This point is illustrated by the example using strlen() to return the length of a Cstring.
Program Example B:
#include<iostream>
using namespace std;
int main()
{
int arr1[5];
int* newArr[5] {arrNumbers(arr1)};
return 0 ;}
In both cases the parameter str is a pointer that stores the starting address of the
array. Array notation is preferable if you intend to use an index to access the elements of
an array. Calling strlen("REGAL") leads to the following situation:
The C++ return reference functions are mostly used in Standard Templating libraries(STL) to
perform an effective update of a variable.
The following example illustrates a program that updates values in an array using the return
reference in C++:
Program Example :
#include <iostream>
using namespace std;
int main () {
setValues(1) = 5;
setValues(3) = 10;
return 0;
}
In the above example, the C++ return reference function is used to update the values in the
array. The function call of setValues(1) is similar to original_array[1] as the
function returns a reference to the setValues() function and the respective values on the
right-hand side(RHS) of the assignment will be assigned to the array.
Original_array: 1 2 3 4 5
Original_array changed after function: 1 5 3 10 5
In a parameter declaration for multidimensional arrays, you need to state every dimension
with the exception of the first. Thus, a parameter declaration for a two-dimensional array
will always contain the number of columns.
int array[10][10];
// … }
passFunc(array);
int *array[10];
// … }
passFunc(array);
int **array;
passFunc(array);
Remind yourself that every time you use the NEW operator you are allocating objects in the
heap. Not on the stack. The C++ 20 syntax is used.
Deleting an array from the heap
delete[] MyArray;
Whenever you need a large number of pointers, you can define an array whose elements
are pointers. An array of this type is referred to as a pointer array.
Example:
II. Initialization
As usual, an initialization list is used to initialize the array. In the case of a pointer array,
the list contains either valid addresses or the value NULL
.
Example:
The value NULL is automatically assigned to any objects for which the list does not contain
a value. This produces the same result as in the previous example.
CHAPTER 6
The name of the array is a pointer to the array itself. The array name points to the first item.
The following sections discuss arrays and pointers.
Just in case you don't know you can use the following syntax to get the address of any data
structure:
Syntax: &variable_name
Output: 0x7ffc855092b0
Snippet:
#include<iostream>
using namespace std;
int main(){
int arr[5]{1,2,3,4,5};
};
Each of the following statements will print the same address.that’s why it is said that an array
works similar to pointers.
Output:
a) arr: 0x7ffe359cf1d1
b) &arr: 0x7ffe359cf1d1
c) &arr[0]: 0x7ffe359cf1d1
In this case, both ptr and arr are pointers to the array element arr[0].
3. Arithmetic With Arrays and Pointers
To Access Addresses
At first it might seem surprising that you can use the array notation ptr[i] for pointers.
The compiler translates arr[i] to *(arr + i)—in other words: “Start at address
arr, move i objects up, and access the object!” This also applies to ptr[i].
chapter 10
FUNCTIONS
Introduction
A function consists in a piece of code that executes a set of instructions, performs a certain
operation and returns a value(optional ). If nothing is returned, the Void type is used.
You must have seen that every C/C++ program begins its execution in main(). Well main
() is called that way because it's the main function where all your code is runned.
1. Main Function
The main function is an integral part of the programming languages such as C. The main
function in C/C++ is where the execution of a program starts. It is a user-defined function
that is mandatory for the execution of a program because when a C program is executed,
the operating system starts executing the statements in the main() function.
// Statement 1;
// Statement 2;
// and so on..
return;
}
We can write the main function in many ways in C/C++ language as follows:
In the above notations, int means integer return type, and void return type
means that nothing is returned , or (void) or () means that does not take any information
as parameters.
Let’s see the example of the main function in which we have written a simple program for
printing a string.
In the below code, first, we include a header file and then we define a main function inside
which we write a statement to print a string using printf() function. The main function is
called by the operating system itself and then executes all statements inside this function.
Syntax :
void main()
{
// Function body
}
Program Example :
#include <stdio.h>
// code
printf("Hello!");
}
Output : Hello!
Note: The return type of main function according to C standard should be int only. Even if
your compiler is able to run void main(), it is recommended to avoid it
Syntax
int main()
{
// Function body
}
or
int main(void)
{
// Function Body
}
Example :
#include <stdio.h>
int main()
{
printf("Hello Geek!");
return 0; }
Output : Hello Geek!
3. Main Function with the Command Line Arguments
In this example, we have passed some arguments in the main() function as seen in the
below code. These arguments are called command line arguments and these are given at
the time of executing a program.
The first argument argc is the argument count which means it stores the number of
arguments passed in the command line and by default, its value is 1 when no argument is
passed.
The second argument is a char pointer array called argv[] which stores all the
command line arguments passed.
Syntax
Example :
#include <stdio.h>
return 0;
}
chapter 11
1. FUNCTION CREATION
Here I've just detailed the creation process to put functions into work. These are user
defined functions ,which means they’re not included in any other header file. You have
already stepped into one of them in previous chapters ( the printf() function ).
The creation process is detailed below:
I. Declaration
II. Definition
III. Calling :
I. Function Declaration
To declare a function you need to state the returned value type, the function’s name and a
list of parameters.
Parameters can be optional but you must specify the type of each of them.
Function calling can be carried out with or without parameters.
1. Just like when you declare a variable you state its type but in this case what you're
stating is its return type. if the result of the operation returns an integer you must start
the declaration with an Int type. Otherwise the Void keyword must be stated.
2. Next you decide which name the function will take according to some rules related to
the kind of operation , which at the time of writing turns out to be a “sum“.
3. Last step is to insert into the parenthesis the type of values it is going to take to
execute the defined set of instructions (statements) in the definition step, when the
function is called.
This would only be the case if you expect the function to take in values coming from
outside its own code. These are called parameters and they are function variables
belonging to the function itself, which expect to be passed a value called argument to
perform a given operation.
When we define a function, we need to make sure that the signature (the return
value, the name, and the parameters) are the same as the declaration.
You must always use the return keyword unless nothing is returned.
If there’s no value to return, the Void type is used.
int sum ( ){
int a= 10;
int b=11;
int result = a+ b;
return result;
}
// DECLARATION
string sayHello();
// DEFINITION
string sayHello(){
string hello = “hello”;
std::cout<< hello <<std::endl; };
// FUNCTION CALL
sayHello();
To call a function that takes parameters, you would write the name of the function,
followed by parenthesis with the given arguments . Calling a function means to execute the
code / statements defined in the function body.
sum (1,2);
output: 3
Note
An argument is the actual value the caller (calling step) wants to bind to the parameters of
the function. arguments are the values you give to the parameter variables that will be used
by the function to execute its code.
sum (1,2);
Example:
int a , b = 2;
sum (a,b);
Output: 4
IV. Lamda
. In their simplest form a lambda expression can be defined as follows:
[ capture clause ] (parameters) -> return-type
{
definition of method
}
Generally, the return-type in lambda expressions is evaluated by the compiler itself and we
don’t need to specify it explicitly. Also the -> return-type part can be ignored. However, in
some complex cases e.g. conditional statements, the compiler can’t determine the return
type and explicit specification is required.
Refer to the collections chapter to understand what a vector is and analyze the following
code.
Example :
// C++ program to demonstrate lambda expression in C++
#include <bits/stdc++.h>
using namespace std;
void printVector(vector<int> v) {
int main() {
printVector(v);
cout << "First number greater than 4 is : " << *p << endl;
printVector(v);
return a == b; });
v.resize(distance(v.begin(), p));
printVector(v);
return i * j; });
cout << "Factorial of 10 is : " << f << endl;
return i * i; };
Output:
4 1 3 5 2 3 1 7
7 5 4 3 3 2 1 1
7 5 4 3 2 1
Factorial of 10 is : 3628800
Square of 5 is : 25
A lambda expression can have more power than an ordinary function by having access to
variables from the enclosing scope.
We can capture external variables from the enclosing scope in three ways :
Capture by reference
Capture by value
Capture by both (mixed capture)
A lambda with an empty capture clause [ ] can only access variables which are local to it.
Chapter 12
1. Function Overloading
This means that you can define two functions with the same identifier(name) as long as their
parameters and return types are different .
Depending on the arguments you assign when you call a function the compiler will execute
the one that matches them.
#include <iostream>
using namespace std;
std::cout << first << " " << second <<std:: endl; }
std::cout << first << " " << second << " " << sum << std::endl;}
int main(){
Combine("David","Letterman");
Combine(15,20);
return 0;}
2. PARAMETER TYPES
I. DEFAULT PARAMETERS
A function can be defined to take Default Parameters. See the example below:
multiply(10); // Returns 10
Note
The first term, passing by value, means sending the actual value of a variable to a function
when you call it. When working with C++, you accomplish this
task by calling the function with the variable. The second term,
passing by reference, means sending the address of the variable to the function so that
the function can modify the original content of that variable.
a. Pass by Value
When the parameter is a value type, we say that the function is taking
an argument by value or the argument is passed by value.
In this case ,arguments are copied and a new local object is created each time the function
is called.
return total ;}
int main () {
int a =10;
int b=11;
sum(a,b); }
Output: 21
b. Returning by Value
When a function that returns by value reaches a return statement, the program
creates a new object, which is initialized from the value of the expression in the return
statement.
This object is called temporary because its lifetime is valid only while the full expression
in which it is created is executed.
#include <iostream>
using namespace std
int main() {
int x = 3;
int y=5;
int result;
result=multiplication(x,y);
return 0; }
In this program you are passing two const variables to the function multiplication.
If you try to change the value of any of the passed arguments you'll get an increment of
read only parameter error.
#include <iostream>
int main() {
const int x = 3;
const int y=5;
int result=multiplication(x,y);
std::cout<< result<<std::endl; }
x=10*10; // error
x++ //error
int total= x * y; // this is totally fine you are not modifying any
of the variables, you’re using them as an expression to return and
store a value in the Total variable.
return total;}
There is no widespread reason to return by const value since the calling code often
assigns the value to a variable, in which case the const-ness of the variables is going
to be the deciding factor, or passes the value to a next expression, and it is rare for an
expression to expect a const value.
Returning by const value also inhibits the move semantic of C++11, thus reducing
performance.
#include <iostream>
using namespace std;
}
III. AS REFERENCE TYPE
When the parameter type of the function is a reference type, we say that the function is
taking an argument by reference or the argument is passed by reference..
This means that the function has access to the object the calling code provided and can
modify it. it has access to the actual object it doesn't get a copy as it does when passing by
value .
Keep in mind that I don't intend to return the reference, I'm just passing it as an argument .
#include <iostream>
using namespace std ;
};
displayState(state);
Note
Unless the function must modify the variable, always use const references, as we
will see later.
Syntax:
return original_variable;
}
Steps :
2. Perform the required actions with the reference variable inside the function.
// declare function
Example 1:
int original_variable=10;
return original_variable_ref;
}
int& original_variable_ref=original_variable;
In the caller, the function call expression is substituted by the returned reference.
The object referenced by the return value must exist after leaving the function. Otherwise the
program will throw an error.
Example 1 :
The example also illustrates the difference between returning the reference variable itself
and returning a copy of the reference variable,
#include <iostream>
using namespace std;
reference++;
return reference;
}
int main() {
// In the example below , the int copy_number stores the copy of the
reference variable returned by the copy() function.
This occurs because the copy_number is declared as a normal int
variable instead of a reference variable (int&).
This stores the copy_number in a new location of the memory and this
number will be independent of the original variable.
Any changes made to the copy_number variable will not be reflected
in the original variable.
// normal variable
int original= 10;
//Copy_number copied: 11
std::cout<< "Copy_number copied: <<copy_number << std::endl;
//Original number: 11
std::cout << "Original number: " << original << std::endl;
//Copy_Number incremented: 12
//Original number: 11
int& reference_number=copy(original);
std::cout << "Reference copy number: " << reference_number << std::
endl;
//Original number: 12
std:: cout << "Original number: " << original << std:: endl;
reference_number++;
//Original number: 13
output
Example 2 :
// Global variable
int x;
int& retByRef() {
return x;
}
int main() {
retByRef() = 10;
// Print X
cout << x;
return 0;
}
Let us see different examples to understand the working of C++ return by reference,
The following example illustrates the manipulation of address in functions that return a
reference and also explains the concept of using a function that returns a reference on the
left side of an assignment operator.
skip this program from now and come back at the end of the chapter.
An example is detailed below : a function that returns by reference and takes a reference ,
prints it and returns it .
#include <iostream>
using namespace std;
// print reference
// return reference
return original; }
// main function
int main() {
// normal variable
int a = 20;
// reference variable
int& b = return_by_ref(a);
// copy variable
int c=return_by_ref(a);
Address of A: 0x43e9ff814
Address of Original: 0x43e9ff814
Address of B: 0x43e9ff814
Address of Original: 0x43e9ff814
Address of C: 0x43e9ff810
Address of Original: 0x43e9ff814
Address of A: 0x43e9ff814
The important part of using references correctly as return values is to make sure that
the object outlives the reference: the object must always be alive – at least until there is
a reference to it. This is essential to working with references.
Const is extremely important in pass by reference, and any time you use a reference
in the parameters of a function, you should also add const to it (if the function is not
designed to modify it).
#include <iostream>
using namespace std;
// function declaration
// function definition
int main() {
int x =1;
int result ;
result = ReturnX(x);
std::cout<< result<<std::endl;
result++;
std::cout<< result<<std::endl;
std::cout<< x <<std::endl;
std::cout<< &x <<std::endl;
std::cout<< &result<<std::endl;
return 0; };
output :
1
2
1
0xfbe9fffdfc
0xfbe9fffdf8
#include <iostream>
using namespace std;
int& y = const_cast<int&>(x);
++y;
int main() {
int a = 5;
f(a);
cout << a << endl;
output : 6
e. Returning values by const Reference:
The return type of a function can also be a reference type. The function call then represents
an object, and can be used just like an object.
CHAPTER 13
This is related to classes ,which will be discussed in the next chapter. Feel free to
skip this section if you have no knowledge about how classes work and come back to
check how a function works with created objects based on a class.
● The parameter in question is the same type as the object passed to it. The function
that is called is then passed a copy of the object (passing by value)
#include <iostream>
#include<string>
using namespace std;
class Employee {
public :
string name;
string department; };
std::cout<<x.name <<std::endl;
string name = x.name;
return name ; }
int main (){
Employee employee1;
printData(employee1);
return 0; };
● The parameter in question is a reference. The parameter is then an alias for the
argument, that is, the function that is called manipulates the object passed by the
calling function (passing by reference).
As for the example it’s just the same the employee class except that the function parameter
takes reference
std::cout<<x.name <<std::endl;
string name = x.name;
return name ; }
// DayTime.h
// The class DayTime represents the time in hours, minutes and
seconds.
class DayTime {
private:
public:
};
int main()
{
DayTime day1;
DayTime day2;
day1.setTime(10,20,55);
day2.setTime(11,52,41);
day1.isLess(day2);
return 0;};
When the method isLess() is called, the function isLess executes and initializes the
created object, t, with the argument.
day1.isLess(day2) ;
The overhead caused by creating and cleaning up objects can be avoided by passing
arguments by reference. In this case, the parameter is declared as a reference or pointer.
This new declaration of the isLess() method is preferable to the previous declaration.
There is no formal difference to the way the method is called. However, isLess() no
longer creates an internal copy, but accesses directly the object being passed. Of course,
the object cannot be changed, as the parameter was declared read-only.
Returning a copy of an object is time-consuming and only makes sense for small-scale
objects.
Example:
#include<iostream>
using namespace std;
class Example {
public:
int a;
Example Ec;
return Ec;
}
};
int main() {
E1.a = 50;
E2.a = 100;
E3.a = 0;
// values are printed
E3 = E3.add(E1, E2);
return 0; }
Instead of returning a reference, a function can also return a pointer to an object. In this
case too, you must ensure that the object still exists after exiting the function.
Example:
#include <iostream>
#include <sstream>
#include <stdlib.h>
using namespace std;
string *GetSecretCode() {
int main() {
string *newcode;
for (int index = 0; index < 5; index++) {
newcode = GetSecretCode();
std::cout << *newcode << std::endl;
}
return 0;}
When you run this application, you see something like the following output:
Some special code appears in GetSecretCode() that requires explanation. The call
to int randomnumber = rand(); generates a random number. To obtain a random
number and convert it to a string, you add two more include lines:
#include <stdlib.h>
#include <sstream>
The first line provides access to the rand() function. The second line provides access to
the ostringstream type. Here are the three lines that perform the magic:
The first of these creates a random number by calling rand(), which returns an int.
The next line creates a variable of type ostringstream, which is a type that’s handy for
converting numbers to strings. You can use the insertion operator (<<), except that instead
of going to the console, anything you write goes into a string of type ostringstream .
You can add the resulting string onto the code string variable using:
code->append(converter.str());
CHAPTER
This can be done by wrapping the array in a structure or class and creating a variable of
type of that structure or class and assigning values to that array.
After that, passing the variable to some other function and modifying it as per requirements.
Note that array members are copied when passed as parameters, but dynamic arrays are
not. So this solution works only for non-dynamic arrays (created without new or malloc).
a. Simple Arrays
Example 1:
#include<stdio.h>
#include<stdlib.h>
# define SIZE 5
struct ArrayWrapper {
int arr[SIZE];
};
printf("\n");
// Driver code
int main(){
int i;
struct ArrayWrapper obj;
obj.arr[i] = 10;
modify(obj);
printf("\n");
return 0;
}
Example 2 :
#include <stdio.h>
int i;
// Driver program
int main()
{
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8};
fun(arr);
return 0;
}
Output
1 2
#include <stdio.h>
#include <string.h>
// Driver program
int main()
{
char arr[] = "geeksquiz";
fun(arr);
return 0;
}
Output
n = 9
g e e k s q u i z
b) PASS A 2D ARRAY
Example 1: the function print takes an integer array of 2
dimensions. it is passed arr as an argument and uses two declared
variables M and N and two for loops to go through every dimension
and print its values.
#include <stdio.h>
const int M = 3;
const int N = 3;
int i, j;
int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
print(arr);
return 0;
}
Output
1 2 3 4 5 6 7 8 9
Example 2 :
#include <stdio.h>
void print(int *arr, int m, int n)
{
int i, j;
for (i = 0; i < m; i++)
for (j = 0; j < n; j++)
printf("%d ", *((arr+i*n) + j));
}
int main()
{
int arr[][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
int m = 3, n = 3;
return 0;}
Output
1 2 3 4 5 6 7 8
CHAPTER 15
Most programmers declare a function on top of the file and define it after the main function
with the objective of making the code more manageable , more organized and more
readable.
#include <iostream>
using namespace std;
// main function
int main()
{
PrintName("Thomas", "Jefferson");
return 0;
}
// function definition
Although the previous structure of the code is the most preferred you could also
declare and define a function at a different place like in a header file or in any different order
in the same file although some compilers might complain if you don't follow the previous
order .Nevertheless try to stick to the above model since it is the standard followed by most
c++ programmers.
The compiler inserts the code of an inline function at the address where the function is
called and thus avoids jumping to a subroutine. The definition of an inline function is
introduced by the inline keyword in the function header.
An inline function must be defined in the source file in which it is called. You cannot
simply supply a prototype of the function. The code containing the instructions must
also be available to the compiler. It therefore makes sense to define inline functions in
header files, in contrast to “normal” functions. This means the function will be available
in several source files.
a) Inline and Standard Function Calling
3. Lambda
This creates a new object that, when called with the arguments specified in the lambda
expression, executes the body of the function.
Arguments is the list of arguments the function accepts, and body is the sequence of
statements to execute when the function is invoked. The same rules for functions apply.
For example, let's create a lambda that takes two integers and returns their sum:
#include <iostream>
int main()
{
auto sum_numbers = [] (int a, int b) { return a + b; };
std::cout << sum_numbers(10, 20) << std::endl;
}
Output:
30
By default, the body of the lambda can only reference the variables that are defined in
the argument list and inside the body, like for functions.
Additionally, lambdas can capture a variable in the local scope, and use it in their body.
Captured variables entail a list of variable names that can be referenced in the body of
the lambda.
When a variable is captured, it is stored inside the created function object, and it can be
referenced in the body.
By default, the variables are captured by value, so they are copied inside the function
object:
#include <iostream>
int main()
{
int addend = 1;
auto sum_numbers = [addend](int b) { return addend + b; };
addend = 2;
std::cout << sum_numbers(3) << std::endl;
Output:
When we created the lambda, we captured addend by value: it was copied into the
sum_numbers object. Even if we modified the value of addend, we did not change the copy
stored inside sum_numbers, so when sum_numbers is executed, it sums 1 to b.
In some situations, we want to be able to modify the value of a variable in the scope in
which the lambda is created, or we want to access the actual value, not the value that
the variable had when the lambda was created.
In that case, we can capture by reference by prepending & to the variable name.
Note
When we capture by reference, we need to make sure that the variable that's been
captured by reference is still valid when the lambda is invoked, otherwise the body
of the function accesses an invalid object, resulting in bugs.
Prefer to capture by value when it is possible.
#include <iostream>
int main()
{
int multiplier = 1;
auto multiply_numbers = [&multiplier](int b) { return multiplier *
b; };
multiplier = 2;
std::cout << multiply_numbers(3) << std::endl;
}
Output:
6
Here, we capture the multiplier variable by reference: only a reference to it was stored
into multiply_numbers.
When we invoke multiply_numbers, the body accesses the current value of multiplier,
and since multiplier was changed to 2, that is the value that's used by the lambda.
A lambda can capture multiple variables, and each one can be either captured by value
or by reference, independently one from the other.
CHAPTER 16
1. INTRODUCTION TO SCOPE
The scope of a variable in C is the block or the region in the program where a variable is
declared, defined, and used. Outside this region, we cannot access the variable and it is
treated as an undeclared identifier.
1. The global scope refers to the region outside any block or function.
2. The variables declared in the global scope are called global variables.
3. Global variables are visible in every part of the program.
4. Global is also called File Scope as the scope of an identifier starts at the
beginning of the file and ends at the end of the file.
Example :
#include <stdio.h>
int global = 5;
void display()
{
printf("%d\n", global);
}
// main function
int main()
{
printf("Before change within main: ");
display();
Output
file1.c
// filename: file1.c
int a;
int main(void)
{
a = 2;
}
// filename: file2.c
// When this file is linked with file1.c, functions
// of this file can access a
extern int a;
int myfun()
{
printf("%d", a);
}
Output
Note: To restrict access to the current file only, global variables can be marked as static.
b) Local Scope in C
The local scope refers to the region inside a block or a function. It is the space enclosed
between the { } braces.
The variables declared within the local scope are called local variables.
Local variables are visible in the block they are declared in and other blocks nested inside
that block.
Local scope is also called Block scope.
Local variables have internal linkage.
Example :
#include <stdio.h>
// Driver Code
int main()
{
{
int x = 10, y = 20;
{
// The outer block contains
// declaration of x and
// y, so following statement
// is valid and prints
// 10 and 20
Output
x = 10, y = 20
x = 11, y = 41
x = 11, y = 20
Q1. What if the inner block itself has one variable with the same name?
If an inner block declares a variable with the same name as the variable declared by the
outer block, then the visibility of the outer block variable ends at the point of the declaration
by inner block.
● The scope of the variable is the region where it is valid to refer to the variable using
its name.
● The lifetime of the variable is the time between it is allocated memory and it is
deleted from the memory.
CHAPTER …
1) Object
2) Lifetime
The lifetime of an object, on the contrary, is the time during execution wherein the
object is valid to access.
Essentially, an object is only available after you have declared it within a translation
unit.
A translation unit, also referred to as module, comprises the source file you are compiling
and any header files you have included.
Lifetime of An Object
● Block scope: The object is only available in the code block in which it was
defined. The object is no longer visible once you have left
the code block.
● File scope : The object can be used within a single module. Only the
functions within this module can reference the object. Other
modules cannot access the object directly.
#include <iostream>
//GLOBAL
int house =1 ;// global variable. it’s out of the main function
which makes it available everywhere
//LOCAL
// LOCAL
int main () {
int buyers=4; // local scope . this variable can only be accessed in
the main function
return 0 ;}
3) Block Scope
Automatic objects are said to have local storage duration. Note that
function parameters are automatic, even though notationally they appear
outside the function body.
plantName and season both start their object life cycle when the function runs and they
finish when it ends.
● When you create a variable inside the code for a function, that variable will be known
only to that particular function. When you create such variables, they are called local
variables, and people say that they are local to that particular function.
int main(){
PrintName("Thomas", "Jefferson");
return 0;
● Notice in the PrintName() function that you declare a variable called fullname.You
then use that variable in the second line in that function, the one starting withcout.
But you cannot use the variable inside main(). If you try to, as in the following code,
you get a compile error:
int main(){
PrintName("Thomas", "Jefferson");
std::cout << fullname <<std::endl; // error out of scope.
return 0;
● You can declare a variable called fullname inside main(). But, if you do that, this
fullname is local only to main(), whereas the other variable, also called fullname, is
local only to the PrintName() function.
int main(){
PrintName("Thomas", "Jefferson")
string fullname =”main declaration”;
std::cout << fullname << std::endl;
return 0;
SUMMARY
In other words, each function has its own variable; they just happen to
share the same name. But they are two separate variables.
Local variables are in the function scope and can only be accessed by the function.
It is desirable to use local variables over global variables because they enable
encapsulation: only the code inside the function body can access and modify the
variable, making the variable invisible to the rest of the program.
Always use the const qualifier with global variables whenever possible.
Try to avoid using mutable global variables.
4) FILE SCOPE
I. Internal linkage:
When you use the static keyword, you specify internal linkage. Internal
linkage means that a variable is inaccessible to other translation units.
#include<stdlib.h>
#include<stdio.h>
#include <iostream>
#include <cstdio>
using namespace std;
int main() {
addNumber(100); {
printf("total: %d\n", total);}
return 0;
};
total: 200
total: 300
The variable total is static because it’s declared at global scope with the static
keyword. Another feature of being declared at global scope is that total can be
accessed from any function in the translation unit.
Local static variables are declared at function scope, just like automatic variables. But their
lifetimes begin upon the first invocation of the enclosing function and end when the program
exits.
Example :
#include <cstdio>
int main() {
addNumber(100);
addNumber(500);
}
You can alternately specify external linkage, which makes a variable accessible to
other translation units. For external linkage, you use the extern keyword instead of static.
#include <cstdio>
extern int total = 200; // External linkage
--snip--
With extern rather than static, you can access total from
other translation units.
Static members are members of a class that aren’t associated with a particular
instance of the class. Normal class members have lifetimes nested within
the class’s lifetime, but static members have static storage duration.
These members are essentially similar to static variables and functions
declared at global scope.
You must refer to them by the containing class’s name, using the scope resolution operator.
In fact, you must initialize static members at global scope. You cannot initialize a static
member within a containing class definition.
Like other static variables, static members have only a single instance.
All instances of a class with static members share the same member, so if
you modify a static member, all class instances will observe the modification.
Example :
#include <cstdio>
struct test {
int main() {
std::cout<<number1.total<<std::endl;
test::addNumber(100);
std::cout<<number1.total<<std::endl;
test::addNumber(500);
std::cout<<number1.total<<std::endl;
return 0;}
The Test struct contains the static variable total and the static function addNumber.
Because total is a member of the Test struct you don't need the scope resolution
operation (:).
In the main function you can see how the output of the instanced class number1 keeps
changing every time it gets printed.
a) Auto Objects
The storage class auto (automatic) includes all those objects defined within a function
but without the static keyword. The parameters of a function are also auto objects.
You can use the auto keyword during a definition.
When the program flow reaches the definition, the object is created on the stack, but in
Unlike a static type object, the object is destroyed on leaving the block.
auto objects have no specific initial value if they are not initialized explicitly. However,
objects belonging to a class type are normally initialized with default values, which
can be specified in the class definition.
b) Using CPU Registers
To increase the speed of a program, commonly used auto variables can be stored in
CPU registers instead of on the stack. In this case, the register keyword is used to
declare the object.
A register is normally the size of an int variable. In other words, it only makes sense
to define register variables if the variable is not too large, as in the case of types such
as char, short, int or pointers. If you omit the type when defining a register variable,
an int is assumed.
However, the compiler can ignore the register keyword. The number of registers
available for register variables depends on your hardware, although two registers are
normally available. If a program defines too many register variables in a code block, the
superfluous variables are placed in the auto storage class.
Objects with dynamic storage duration are allocated and deallocated on request.
You have manual control over when a dynamic object’s life begins and when it
ends. Dynamic objects are also called allocated objects for this reason.
You declare a pointer to int and initialize it with the result of the new
expression on the right side of the equal sign . The new expression is composed
of the new keyword followed by the desired type int . When the
new expression executes, the C++ runtime allocates memory to store an int
and then returns its pointer.
After allocating storage for the int, the dynamic object will be initialized
as usual.
To deallocate the object pointed to by my_int_ptr, you would use the following:
delete expression:
delete my_int_ptr;
Example Program:
#include <iostream>
const int globalVar = 10; // 1
int main() {
return 0;}
chapter … Namespaces
Namespaces
A namespace starts a scope in which all the names declared inside are part of the
namespace.
Using the directive allows you to import all the identifiers in a namespace.
To create a namespace, we use the namespace keyword, followed by the identifier and
then the code block:
namespace example_namespace {
// code goes here
}
To access an identifier inside a namespace, you prepend the name of the identifier with
the name of the namespace in which it is declared, followed by “:: “ .
You might have noticed that, before we were using std::cout. This is because the C++
standard library defines the std namespace and we were accessing the variable named cout.
Note
● To better organize your code and avoid naming conflicts, you should always put
your code inside a namespace that's specific to either your application or library.
Namespaces can also be used to specify that some code is used only by the
current code.
● Let's imagine you have a file called a.cpp that contains int default_name = 0; and
another file called b.cpp with int default_name = 1;. When you compile the two files
and link them together, we get an invalid program: the same variable has been declared
with two different values, and this violates the One Definition Rule (ODR).
● But you never meant for those to be the same variable. To you, they were some
variables that you just wanted to use inside your .cpp file.
● To tell that to the compiler, you can use anonymous namespaces: a namespace with
no identifier.
● All the identifiers created inside it will be private to the current translation unit
(normally the .cpp file).
● How can you access an identifier inside an anonymous namespace? You can
access the identifier directly, without the need to use the namespace name, which
does not exist, or the using declaration
All the identifiers created inside it will be private to the current translation unit
(normally the .cpp file).
CHAPTER …17
1. CLASSES
A class defines a blueprint or the design of an object.It is used to create objects according to
its defined properties, also called data members, which include fields (variable
declarations) , methods (that's how functions are called) , other data types and data
structures. Multiple objects can be created from the same class and are called instances.
When you divide the class, you put part in the header file and part in the source
code file. The following list describes what goes where:
Header file: Put the class definition in the header file. Properties appear as
part of the class definition within the header. You can include the method
code inside the class definition.
Source file: If your class has methods, and you didn’t put the code in the class
definition, you need to put the code in a source file. When you do, precede the
function name with the class name and the scope resolution operator (::). If
you named the header file the same as the class, you probably want to name
the source file is the same as the class as well but with a .cpp extension.
2. Class Declaration
The definition begins with the keyword class followed by the class name. The data
members and methods are then declared in the subsequent code block:
Classes use access specifiers to determine what´s available for the user to access.
You have already come across the static specifier. I've detailed below the remaining class
(struct) specifiers.
● Private . they cannot be accessed externally. Members declared as private can only
be accessed by the functions inside the class and are not allowed to be accessed
directly from outside.
By default, class members have the private access modifier. On the other hand struct
properties are defined as public by default.
3. Class Definition
When we declare and define a class, we create a new scope called the class scope.
The members defined inside a class are accessible only from/through the same class scope
.
a) Syntax:
Program Example:
// A Class Definition
class Coordinates {
public:
};
To define a method outside the class the scope resolution operator must be used
“:”. Failure to supply the class name results in a global function definition.
Example :
.
5. Accessing The Properties of a Created Object.
Once an object is created it is going to inherit its class data members . The operator to
access data members of a class is the dot (.) operator.
Example :
newYorkPosition.latitude;
newYorkPosition.latitude=120.50;
Note
If you had a lot of members to initialize you’d have to follow this syntax for each of them one
by one .That would then become strenuous and your code would likely become more prone-
error . That’s when constructors come in handy.
When you create a new instance of a class, you may want to do some basic object
setup. Suppose you have a class called Cellphone, with a private property called
quantity and a method called addCellPhone(). The code for addCellPhone()
adds 1 to quantity.
When you create a new instance of Cellphone, you probably want to start quantity at
0. The best way to do this is by adding a special method, a constructor, to your class. This
method has a line of code such as
quantity = 0
When you delete an instance of a class (object) , you might want some cleanup code. It's
essential to delete those other objects. You put cleanup code in a special function called
a destructor. A destructor is a finalization function that the computer calls before
it deletes your object.
Constructors are normally declared in the public section of a class. This allows you to
create objects wherever the class definition is available.
b. Constructor Types
A default constructor is a constructor that takes no parameters. You can have a default
constructor in a class either by coding it or by letting the compiler implicitly build one for you.
Every class that lacks a constructor usually has a default constructor created by the
compiler.
class Coordinates {
public:
};
c. Standard Constructor
Example Program :
#include<iostream>
class Coordinates{
public:
int latitude;
int longitude;
latitude=lat;
longitude=lon;
}
};
int main()
{
Coordinates argentina(20,220);
std::cout<<argentina.latitude;
};
d) Accessing Private Data Members( Setters and Getters)
Access methods offer a far more useful way of accessing the private data members.
Access methods allow data to be read and manipulated in a controlled manner.
Example :
class Coordinates {
private:
public:
void printCoordinates( ){
std::cout<<latitude<<std::endl;
std::cout<<longitude<<std::endl;
}
};
f. Copy Constructor
The copy constructor initializes an object with another object of the same type. It is
called automatically when a second, already existing object is used to initialize it .
Copy constructor with a Coordinates object as parameter copies data of the obj parameter.
Coordinates(Coordinates &obj) {
latitude= obj.latitude;
longitude= obj.longitude;
}
Coordinates argentina(20,220);
Coordinates brazil(argentina);
brazil.latitude = argentina.latitude;
brazil.longitude=argentina.longitude;
g. Assignment Constructor
In contrast to initialization using the copy constructor for assignment requires two existing
objects.
brazil = argentina;
h. Overloading constructors
It's the same as overloading functions . it means that you can define more than one
constructor with the same identifier but different parameters. That way the compiler will know
which function to call according to the arguments provided.
class Simple {
public:
int x,y;
void Write() {
cout << x << " " << y << endl;
}
Simple() {}
Simple(int startx) { x = startx; }
};
7. Initialization Lists
class Rectangle {
private:
int width;
int height;
public:
};
Note how, in this last case, the constructor does nothing other than initialize its
members. Hence, it has an empty body.
Now, if we try to print the width and the height of the Rectangle object, we will notice
that they are correctly initialized to 0:
Rectangle rectangle;
Initializer lists are the recommended way to initialize member variables in C++, and they
are necessary when a data member is const.
When using an initializer list, the order in which the members are constructed is the
one in which they are declared inside the class; not the one in which they appear in the
Initializer list.
class Example {
Example() : second(0), first(0) {}
int first;
int second;
};
When calling the default constructor of the Example class, the first method will be
initialized first, and the second method after it, even if they appear in a different order in
the initializer list.
Note
You should always write the members in the initializer list in the same order as
they are declared; compilers will help you by warning you when the order differs
from the expected one.
8. Private Destructor
What is the use of a private destructor?
Whenever we want to control the destruction of objects of a class, we make the destructor
private. For dynamically created objects, it may happen that you pass a pointer to the object
to a function and the function deletes the object. If the object is referred after the function
call, the reference will become dangling.
Predict the Output of the Following Programs:
class Test {
private:
~Test() {}
};
int main() {}
chapter …18
Destructors
1. Destructors Declaration
Destructors are declared in the public section and follow this syntax:
Syntax: ∼class_name(void);
Just like the constructor, a destructor does not have a return type. Neither does it
have any parameters, which makes the destructor impossible to overload. Each class thus
has one destructor only.
2. Definition
If the class does not define a destructor, the compiler will create a minimal version of
a destructor as a public member, called the default destructor.
∼class_name::∼class_name(){} // Nothing to do
3. Calling Destructors
● For local objects except objects that belong to the static storage class, at the
end of the code block defining the object
#include<iostream>
class Squirrel {
private:
Walnut *MyDinner;
public:
};
Squirrel::Squirrel(){
MyDinner = new Walnut;
MyDinner->Size = 30;
//destructor definition
Squirrel::~Squirrel() {
cout << "Cleaning up my mess!" << endl;
delete MyDinner;
int main() {
The Squirrel class has a property called MyDinner that is a pointer to a Walnut
instance. The Squirrel constructor creates an instance of Walnut and stores it
in MyDinner. The destructor deletes the instance of Walnut. In main(), the code
creates two instances of Squirrel. Each instance gets its own Walnut to eat.
Each Squirrel creates its Walnut when it starts and deletes the Walnut when the
Squirrel is deleted.
Notice in this code that the constructor has the same name as the class,
Squirrel(). The destructor also has the same name, but with a tilde, ~, tacked on
to the beginning of it. Thus, the constructor is Squirrel() and the destructor is
~Squirrel(). Destructors never take parameters and you can’t call them directly,
but the runtime calls them automatically when it’s time to destroy an object.
When you run this application, you can see the following lines, which were spit
up by the Squirrel in its constructor and destructor. (You see two lines of each
because main() creates two squirrels.)
Starting!
Starting!
Cleaning up my mess!
Cleaning up my mess!
If the Walnut class also had a constructor and destructor, and you made the
MyDinner property a variable in the Squirrel class, rather than a pointer, the
computer would create the Walnut instance after it creates the Squirrel instance,
but before it calls the Squirrel() constructor. It then deletes the Walnut instance
when it deletes the Squirrel instance, after calling the ~Squirrel() destructor.
The code performs these steps for each instance of Squirrel.
The code below creates two Squirrels on the heap by using pointers and calling new.
But you could also create them on the stack by declaring them without pointers:
Squirrel Sam;
Squirrel Sally;
If you do this, the application will run fine, provided that you remove the delete lines.
You do not delete stack variables. The computer calls the destructor when the main()
function ends. That’s the general rule with objects on the stack: They are created when
you declare them, and they stay until the function ends.
4. Adding Parameters To Constructors
Like other methods, constructors allow you to include parameters. When you do,
you can use these parameters in the initialization process. To use them, you list
the arguments inside parentheses when you create the object:
Squirrel(string StartName);
The constructor is expecting a string, so you pass a string when you create the
object.
The SquirrelClass example, presents an application that includes all the basic elements
of a class with a constructor that accepts parameters.
Example program :
#include <iostream>
using namespace std;
class Squirrel {
private:
string Name;
public:
Squirrel(string StartName);
void WhatIsMyName();
};
Squirrel::Squirrel(string StartName) {
cout << "Starting!" << endl;
Name = StartName;
}
void Squirrel::WhatIsMyName() {
cout << "My name is " << Name << endl;
}
int main()
{
Squirrel *Sam = new Squirrel("Sam");
Squirrel *Sally = new Squirrel("Sally");
Sam->WhatIsMyName();
Sally->WhatIsMyName();
delete Sam;
delete Sally;
return 0;
}
In main(), you pass a string into the constructors. The constructor code takes the
StartName parameter and copies it to the Name property. The WhatIsMyName()
method writes Name to the console.
a. Explicit Initialization
To initialize an object explicitly, you can state its initial values in parentheses when you call
new using an initialization list .
The values in the initialization list are passed as arguments to the constructor. If the
compiler is unable to locate a suitable constructor, an error message occurs.
class planet
public:
string name;
string size;
string galaxy;
string type;
planet earth(“earth”,”medium”,”oreon”,”water”)
planet *ptr= new planet (“earth”,”medium”,”oreon”,”water” )
The delete keyword calls the destructor of the object pointed to by the pointer
provided to it, and then gives the memory we initially requested back to the operating
system.
It is of absolute importance that, for each new expression, we call the delete expression
only once, with the same returned pointer.
If we forget to call the delete function on an object returned by calling the new function,
we will have two major problems:
• The memory will not be returned to the operating system when we do not need it
anymore. This is known as a memory leak. If this repeatedly happens during the
execution of the program, our program will take more and more memory, until it
consumes all the memory it can get.
We can now see why it is extremely important to define a virtual destructor in the base
class if we derive from it: we need to make sure that the destructor of the runtime
type is called when calling the delete function on the base object. If we call delete on a
pointer to the base class while the runtime type is the derived class, we will only call the
destructor of the base class and not fully destruct the derived class.
Making the destructor of the base class virtual will ensure that we are going to call the
derived destructor, since we are using dynamic dispatch when calling it.
Note
For every call to the new operator, there must be exactly one call to delete with the
pointer returned by new!
This error is extremely common and leads to many errors.
Like single objects, we can also use dynamic memory to create arrays of objects. For
such use cases, we can use the new[] and delete[] expressions:
int n = 15;
Car* cars = new Car[n];
delete[] cars;
The new[] expression will create enough space for n Car instances and will initialize
them, returning a pointer to the first element created. Here, we are not providing the
arguments to the constructor, so the class must have a default constructor.
With new[], we can specify how many elements we want it to initialize. This is different
from std::array and the built-in array we saw earlier because n can be decided at
runtime.
We need to call delete[] on the pointer returned by new[] when we do not need the
objects anymore.
Function Example :
The function will call the new expression in its body to create an instance of the correct
class, and it will then return a pointer to the base class so that the code calling it does
not need to know about the type of logger created:
Logger* createLogger() {
if (are_tests_running()) {
} else {
return logger; }
● Even if we wrote the new expression twice, new will be called only once per
function call.
● This shows us that it is not enough to make sure we type new and delete an equal
number of times; we need to understand how our code is executed.
● There is no call to delete! This means that the code calling the createLogger
function needs to make sure to call delete.
From these two points, we can can see why it is error prone to manage memory
manually, and why it should be avoided whenever possible.
delete logger;
If myOperation does not call delete on the logger, this is a correct use of dynamic
memory. Dynamic memory is a powerful tool, but doing it manually is risky, error prone,
and easy to get wrong.
Fortunately, modern C++ provides some facilities to make all this much easier to do. It is
possible to write entire programs without ever using new and delete directly.
We will see how in the next section.
Since a nested class declaration happens inside the outer class, the outer class has access
to all the declared names as if it were part of the outer class: it can access even private
declarations
On the other hand, a nested class is not associated with any instance, so it can only
access static members.
// Declaration
class Coordinate {
...
struct CoordinateDistance {
float x = 0;
float y = 0;
static float walkingDistance(CoordinateDistance distance);
}
};
Coordinate::CoordinateDistance::walkingDistance(distance);
Since static data members are independent of any objects, access to them should also be
independent of any objects. Static member functions are used for this purpose. For example,
you can call a static member function for a class even though no objects exist in that class.
The static keyword is used to define static member functions.
Definitions outside of the class do not need to repeat the static keyword.
A static member function can be called using any object belonging to the class or,
preferably, using a range operator.
Calling a static member function does not bind the function to any class object. The
this pointer is therefore unavailable, in contrast to normal member functions.
This also means that static member functions cannot access data members and methods
that are not static themselves.
Note
Always use const when declaring a static field. Any instance can access the static
fields of its class; if they are mutable, it becomes extremely hard to track down
which instance is modifying the value. In programs that use multiple threads, it is
common to create bugs by modifying the static fields from different threads at the
same time.
To declare a member function as const, we use the const keyword in the function
declaration after the function name and before its body:
The const member function will be called when an object is declared const; otherwise,
the non-const function is called. Let's examine the following code:
class Car {
std::string& getColor() {}
};
Car car;
car.getColor();
constCar.getColor();
When the this keyword is used in the class context, it represents a pointer whose
value is the address of the object on which the member function is called. It can appear
within the body of any non-static member function.
In the following example, setColorToYellow() and setColorToGreen() perform the
same action. Both set a data member, but the former uses the this keyword to refer to the
current object:
class Car {
std::string color;
void setColorToGreen(){
color = "green"; }
};
Note
Up until now, we have learned how to declare data members, how to use them in
functions with a public specifier, and how to access them. Now, let's explore how to set a
value to them.
class Car {
bool start ;
bool brake;
int km ;
}
Car car1;
Car* ptrToCarOne=&car1;
(*car1).start= false;
(*car1).brake=false;
(*car1).km=20000;
car1->start=false;
car1->brake= false;
car1-> km =10000;
CAR car1;
Note
Example :
Car car1;
car1.start= false;
car1.brake=false;
car1.km=20000;
12. Structs
struct Book {
char name[256];
int year;
int pages;
bool hardcover;};
int main() {
Book neuromancer;
neuromancer.pages = 271;
std::cout<<neuromancer.pages<<std::endl;
Example I :
#include <iostream>
using namespace std ;
class book{
public:
string title;
string edition;
int year;
};
book *book1;
book1= new book ("A christmas Tale","newark",2010);
std::cout<<book1->title<<std::endl;
return 0;
REFERENCE
Example II :
// array of objects
14. Unions
a. Memory Usage
In normal classes, each data member belonging to an object has its own separate memory
space. However, a union is a class whose members are stored in the same memory space.
Each data member has the same starting address in memory. Of course, a union cannot
store various data members at the same address simultaneously. However, a union does
provide for more versatile usage of memory space.
b. Definition
Syntactically speaking, a union is distinguished from a class or a struct only by the keyword
union.
This example defines the union Number and two objects of the same type. The union
Number can be used to store either integral or floating-point numbers.
Unless a private label is used, all union members are assumed to be public. This
is similar to the default setting for structures. This allows direct access to the members n
and x in the union Number.
Example :
CHAPTER…19
1. Enums
a. Unscoped Enums
This statement defines the constants OFF and OUT, setting their value to 0, and the
constants ON and IN with a value of 1. The values for OFF and ON are implicit.
b. Scoped Nums
To access the values you need to use the name of the type followed by the scope operator:
switch(shape) {
case shape::Line: {
printf("You work hard.");}
break;
case shape::Rectangle: {
printf("You are very strong.");
} break;
Chapter …
1. INHERITANCE
A. Derived Classes
To derive a class means to extend the properties of the base class to another class so you
don't have to rewrite the same data members all over again . The new class inherits (shares)
the properties from the main class.
class Vehicle {
public:
int NumberOfWheels;
void Drive() {
cout << "Driving, driving, driving..." << endl;
} };
You then create a class named Toyota and you want it to have the fields and methods
from the vehicle class given that Toyota belongs to a vehicle brand (vehicle
class).
You could define the class toyota with the same data members contained in the
vehicle class but that’d be a lot of overwork to rewrite the same code for each new brand
class you want to define.
That’s when derived classes come into play to facilitate this process,which is called
inheritance.
public:
void MeAndMyToyota() {
cout << "Just me and my Toyota!" << endl; }
};
When you do so, the class you create (Toyota) inherits the properties and methods
from the parent class (Vehicle).
For example, if Vehicle has a public property named NumberOfWheels and a public
method called Drive(), the class Toyota has these members, although you didn’t explicitly
write them in the class.
The definition of the Toyota class doesn’t show a Drive() function. The Drive() function
is inherited from the Vehicle class.So even if it’s not explicitly defined in the derived
class , it has access to this method.
#include <iostream>
using namespace std;
class Vehicle {
public:
int NumberOfWheels;
void Drive() {
cout << "Driving, driving, driving..." << endl; } };
public:
void MeAndMyToyota() {
cout << "Just me and my Toyota!" << endl;}
};
int main() {
Toyota MyCar;
MyCar.MeAndMyToyota();
MyCar.Drive();
return 0;
When you run this application, you see the output from two functions:
Just me and my Toyota!
When you create a class, its methods can access both public and private properties
and methods. Users of the class can access only the public properties and
methods.
When you derive a new class, it cannot access the private members in
the parent class. Private members are reserved for a class itself and not for any
derived class.
When members need to be accessible by derived classes you only have indirect access to
the private data members of the base class so one way to access them is through the
public defined base class methods.these are called setters and getters. you define functions
in the public section of the class that can access the class private data members.
you saw that in the time example where you had get methods that return hour, minute and
second.
I’ll leave another example for practicality
class Employee {
private:
// Private attribute
int salary;
public:
// Setter
void setSalary(int s) {
salary = s;
}
// Getter
int getSalary() {
return salary;
}
};
int main() {
Employee myObj;
myObj.setSalary(50000);
cout << myObj.getSalary();
return 0;
}
Although there’s a specification you can use beyond public and private: protected.
Protected members and private members work the same way from a user perspective,
but derived classes can access both protected and public members.
Private members are hidden from both users and derived classes.
Always use protected members when possible when you plan to derive classes from
a parent class.
The constructor of a derived class is required to create an object of the derived class type.
As the derived class contains all the members of the base class, the base class objects must
also be created and initialized.
hint :
The base class constructor is called first, then the derived class
constructor. The object is thus constructed from its core outwards.
// C++ program to show the order of constructor call
// in single inheritance
#include <iostream>
using namespace std;
// base class
class Parent
{
public:
// sub class
// main function
int main() {
// creating object of sub class
Child obj;
return 0;
}
output
How to call the parameterized constructor of base class in derived class constructor?
#include <iostream>
using namespace std;
// base class
class Parent {
int x;
public:
Parent(int i) {
x = i;
cout << "Inside base class's parameterized constructor" << endl; }
};
// sub class
// main function
int main()
{
When an object is destroyed, the destructor of the derived class is first called, followed by
the destructor of the base class.
You need to define a destructor for a derived class if actions performed by the constructor
need to be reversed. The base class destructor need not be called explicitly as it is executed
implicitly.
#include <iostream>
using namespace std;
class Safe {
private:
int topSecret;
protected:
int secret;
void setTopSecret( int n) { topSecret = n;}
int getTopSecret() const { return topSecret;}
void setSecret( int n){ secret = n;}
int getSecret() const { return secret;}
public:
int noSecret;
//derived class
public:
Castle()
{
// topSecret = 10; // Error, because private
setTopSecret(10); // ok, because protected
secret = 1; // ok, because protected
noSecret = 0; // ok, because public
}
void test()
{
// topSecret = 200; // Error, because private
setTopSecret(200); // ok, because protected
secret = 20; // ok, because protected
noSecret = 2; // ok, because public
}
};
Castle treasure;
treasure.topSecret = 1; // Error: private
treasure.secret = 2; // Error: protected
treasure.setTopSecret(5); // Error: protected
treasure.noSecret = 10; // ok
Note :
Protected declarations should be used with caution. If you change the declaration of a
protected member, every class derived from this class must be examined to ascertain
whether additional modifications are necessary
class Vehicle {
public:
TankLevel getTankLevel() const;
void turnOn();
};
In this example, the Car class inherits from the Vehicle class. Here Vehicle is the
base class, and Car is the derived class.
When defining a class, we can specify the classes it derives from by appending :
followed by one or more classes, separated by a comma:
When specifying the list of classes to derive from, we can also specify the visibility of
the inheritance – private, protected, or public.
The methods of the base class can be accessed as methods of the derived class based on
the following rules:
● Car car;
● car.turnOn();
1. Only the derived class and classes that derive from it know about inheritance;
2. External code sees the two classes as unrelated.
3. When deriving with a private modifier, all the public and protected methods and fields
of the base class are accessible by the derived class as private.
4. The private methods and fields of a class are never accessible outside of that class.
5. Accessing the fields of the base class follows the same rules.
class Fruit {
};
The class Citrus can access the public and protected methods of class
Fruit, whereas class Orange will be able to access both Citrus' and Fruit's
public and protected methods . (Fruit's public methods are accessible
through Citrus).
Note
When inheriting from a class, the base class gets embedded into the derived class. This
means that all the data of the base class also becomes part of the derived class in its
memory representation:
class A {
public:
A(const std::string& name);
};
class B: public A {
public:
B(int number) : A("A's name"), d_number(number) {}
private:
int d_number;
};
}
The copy constructor and the assignment operator generated by the compiler take
care of calling the constructor and operator of the base class.
When, instead, we write our implementation of the copy constructor and the assignment
operators, we need to take care of calling the copy constructor and assignment operator.
chapter … Friend Classes and functions
Syntax:
Note: We can declare friend class or function anywhere in the base class body whether its
private, protected or public block. It works all the same.
2. Friend Function
Like a friend class, a friend function can be granted special access to private and protected
members of a class in C++. They are the non-member functions that can access and
manipulate the private and protected members of the class for they are declared as friends.
A friend function can be:
I. A global function
II. A member function of another class
Syntax:
friend return_type function_name (arguments); // for a
global function. or
We can declare any global function as a friend function. The following example
demonstrates how to declare a global function as a friend function in C++:
Example:
#include <iostream>
using namespace std;
class base {
private:
int private_variable;
protected:
int protected_variable;
public:
base()
{
private_variable = 10;
protected_variable = 99;
}
// driver code
int main() {
base object1;
friendFunction(object1);
return 0;
}
Output:
Private Variable: 10
Protected Variable: 99
In the above example, we have used a global function as a friend function. In the next
example, we will use a member function of another class as a friend function
We can also declare a member function of another class as a friend function in C++. The
following example demonstrates how to use a member function of another class as a friend
function in C++:
Example:
#include <iostream>
using namespace std;
class base;
class anotherClass {
public:
class base {
private:
int private_variable;
protected:
int protected_variable;
public:
base(){
private_variable = 10;
protected_variable = 99;
}
int main()
{
base object1;
anotherClass object2;
object2.memberFunction(object1);
return 0;
}
Output
Private Variable: 10
Protected Variable: 99
Note: The order in which we define the friend function of another class is important and
should be taken care of. We always have to define both the classes before the function
definition. That's why we have used out of class member function definition.
● A friend function is a special function in C++ that in spite of not being a member
function of a class has the privilege to access the private and protected data of a
class.
● The keyword “friend” is placed only in the function declaration of the friend function
and not in the function definition or call.
● A friend function is called like an ordinary function. It cannot be called using the
object name and dot operator. However, it may accept the object as an argument
whose value it wants to access.
● A friend function can be declared in any section of the class i.e. public or private or
protected.
chapter … plymorphism 21
Types of Polymorphism
1. Compile-time Polymorphism
2. Runtime Polymorphism
1. Compile-Time Polymorphism
A. Function Overloading
When there are multiple functions with the same name but different parameters, then the
functions are said to be overloaded. Functions can be overloaded by changing the number
of arguments or/and changing the type of arguments
#include <bits/stdc++.h>
class Geeks {
public:
void func(int x)
{
cout << "value of x is " << x << endl;
}
void func(double x)
{
cout << "value of x is " << x << endl;
}
// Driver code
int main()
{
Geeks obj1;
obj1.func(9.132);
obj1.func(85, 64);
return 0;
}
Output
value of x is 7
value of x is 9.132
value of x and y is 85, 64
This type of polymorphism is achieved by Function Overriding. The function call is resolved
at runtime in runtime polymorphism. In contrast, with compile time polymorphism, the
compiler determines which function call to bind to the object after deducing it at runtime.
A. Dynamic Binding
Dynamic binding is the ability for a reference or a pointer of a base type to point to an
object of a derived type at runtime. Let's explore the following example:
B b;
C c;
A& ref1 = b;
A& ref2 = c;
A* ptr = nullptr;
if (runtime_condition()) {
ptr = &b;
} else {
ptr = &c;
Note
1. To allow dynamic binding, the code must know that the derived class derives from
the base class.
2. If the based class access is private , the derived class will be able to bind the object
to a pointer or reference of the base class.
3. If the inheritance is protected, then the derived class and every class deriving from it
will be able to perform dynamic binding.
4. Finally, if the inheritance is public, the dynamic binding will always be allowed.
This creates the distinction between the static type and the dynamic (or run-time) type.
The static type is the type we can see in the source code.
In this case, we can see that ref1 has a static type of a reference to the A struct.
The dynamic type is the real type of the object: the type that has been constructed in
the object's memory location at runtime.
For example, the static type of both ref1 and ref2 is a reference to the A struct, but
the ref1 dynamictype is B, since ref1 refers to a memory location in which an object of
type B has been created, and the ref2 dynamic type is C for the same reason.
As said, the dynamic type can change at runtime. While the static type of a variable is
always the same, its dynamic type can change: ptr has a static type, which is a pointer
to A, but its dynamic type could change during the execution of the program:
B. Dynamic Dispatch
As said before, C++ supports dynamic dispatch. This is done by marking a method with a
special keyword: virtual.
If a method is marked with the virtual keyword, when the method is called on a
reference or a pointer, the compiler will execute the implementation of the dynamic
type instead of the static type.
C. Function Overriding
#include <bits/stdc++.h>
public:
public:
// Driver code
int main(void)
{
Animal d = Dog(); // accessing the field by reference
// variable which refers to derived
cout << d.color;
}
Output :
Black
D. Virtual Function
A virtual function is a member function that is declared in the base class using the keyword
virtual and is re-defined (Overridden) in the derived class. It tells the compiler to perform
dynamic binding
● They are defined by inserting the keyword “virtual” inside a base class and are
always declared with a base class and overridden in a child class
#include <iostream>
using namespace std;
// Base class
class Shape {
public:
// parameterized constructor
Shape(int l, int w) {
length = l;
width = w;
}
int get_Area() {
return 1;
}
protected:
int length, width;
};
// Derived class
public:
// constructor
int get_Area() {
cout << "Square area: " << length * width << '\n';
};
// Derived class
public:
// constructor
int get_Area(){
cout << "Rectangle area: " << length * width << '\n';
};
int main(){
Shape* s;
s->get_Area();
s->get_Area();
// successfully
Output
● We store the address of each child’s class Rectangle and Square object in s
● Then we call the get_Area() function on it,
● Ideally, it should have called the respective get_Area() functions of the child
classes but
● This happens due to static linkage which means the call to get_Area() is
getting set only once by the compiler which is in the base class.
E. Virtual Methods
In this section, we will take an in-depth look at how to tell the compiler to perform
dynamic dispatch on a method. The way to specify that we want to use dynamic
dispatch for a method is to use the virtual keyword.
class Vehicle {
public:
virtual void turnOn(); };
We need to apply the virtual keyword to the type we are using in the
code. Let's examine the following exercise to explore the virtual keyword.
We can write a function that accepts a reference to a base class, call methods on this base
class, and the methods of the derived classes will be executed:
vehicle.turnOn(); }
}
We can then call the function with many different types of vehicles,
and the appropriate methods will be executed:
Car myCar;
Truck truck;
safeTurnOn(myCar);
safeTurnOn(truck);
Let's create a program using the concept of inheritance using the virtual keyword:
1. First, make sure to add the required header file and namespace to compile the
program.
class Vehicle {
public:
void turnOn() {
std::cout << "Vehicle: turn on" << std::endl;}};
4. Now, in the main function, invoke the Car class and pass the car object in the
myTurnOn() function:
int main() {
Car car;
myTurnOn(car); }
Here, the call will not be dynamically dispatched, and the call to the implementation
of Vehicle::turnOn() will be executed. The reason is that the static type of the variable
is Vehicle, and we did not mark the method as virtual, so the compiler uses static
dispatch.
The fact that we wrote a Car class that declares the method virtual is not important,
since the compiler only sees the Vehicle class being used in myTurnOn(). When a
method is declared virtual, we can override it in a derived class.
To override a method, we need to declare it with the same signature as the parent class:
● return type,
● name,
● parameters (including const-ness and ref-ness),
● const qualifier,
● the other attributes.
When a class overrides a virtual method of the base class, the method of the most
derived class will be executed when the method is called on a base class. This is true
even if the method is called from inside the base class, for example:
struct A {
virtual void foo() {
std::cout << "A's foo" << std::endl;}
};
struct B: A {
virtual void foo() override {
std::cout << "B's foo" << std::endl;}
};
struct C: B {
virtual void foo() override {
} };
int main() {
B b;
C c;
A* a = &b;
a->foo(); // B::foo() is executed
a = &c;
a->foo();
Note
If a method is marked virtual, then the destructor should also be marked virtual.
1. Interfaces in C++
Functions and methods which accepts parameters as interface are a way of saying: in
order to perform my actions, I need these functionalities; it's up to you to provide them.
For example:
class Vehicle {
public:
A pure virtual method is a method that does not have to be defined. Nowhere in the
previous code have we specified the implementation of Vehicle::turnOn(). Because of
this, the Vehicle class cannot be instantiated, as we do not have any code to call for its pure
virtual methods.
We can instead derive from the class and override the pure virtual method. If a class
derives from an abstract base class, it can be either of the following:
• A regular class if it overrides all the pure virtual methods of the base class
public:
virtual void fillTank() = 0;
};
};
In this example, Vehicle is an abstract base class and GasolineVehicle is too, since it
does not override all the pure virtual methods of Vehicle. It also defines an additional
virtual method, which the Car class overrides together with the Vehicle::turnOn()
method. This makes Car the only concrete class, a class that can be instantiated.
The same concept applies when a class is deriving from multiple abstract base classes:
all the pure virtual methods of all the classes that need to be overridden in order to
make the class concrete and thus instantiable.
While abstract base classes cannot be instantiated, we can define references and
pointers to them.
Note
If you try to instantiate an abstract base class, the compiler will give an error
specifying which methods are still pure virtual, thus making the class abstract.
Functions and methods that require specific methods can accept references and
pointers to abstract base classes, and instances of concrete classes that derive from
them can be bound to such references.
It is good practice for the consumer of the interface to define the interface.
A function, method, or class that requires some functionality to perform its actions
should define the interface. Classes that should be used with such entities should
implement the interface.
Since C++ does not provide a specialized keyword for defining interfaces and interfaces
are simply abstract base classes, there are some guidelines that it's best practice to
follow when designing an interface in C++:
• An abstract base class should not have any data members or fields.
The reason for this is that an interface specifies behavior, which should be
independent of the data representation. It derives that abstract base classes
should only have a default constructor.
• All the methods of an abstract base class should be regarding a single functionality.
If our code requires multiple functionalities, separate interfaces can be created,
and the class can derive from all of them. This allows us to compose interfaces
more easily.
Consider disabling the copy and move constructors and assignment operators on the
interface. Allowing the interface to be copied can cause the slicing problem we were
describing before:
Car redCar;
Car blueCar;
With the last assignment, we only copied the Vehicle part, since the copy constructor
of the Vehicle class has been called. The copy constructor is not virtual, so the
implementation in Vehicle is called, and since it only knows about the data members
of the Vehicle class (which should be none), the ones defined inside Car have not been
copied! This results in problems that are very hard to identify.
A possible solution is to disable the interface copy and move construct and assign
operator: Interface(const Interface&) = delete; and similar.
This has the drawback of disabling the compiler from creating the copy constructor and
assign operators of the derived classes.
chapter … 23
MULTIPLE INHERITANCE
1. Introduction
Up to now, we have seen examples of single inheritance: a derived class has a single
base class. C++ supports multiple inheritance: a class can derive from multiple classes.
Let's look at an example:
struct A {
};
struct B {
};
struct C : A, B {
};
It is usually best to have a shallow inheritance hierarchy: there should not be many
levels of derived classes.
When using a multi-level inheritance hierarchy or multiple inheritance, it's more likely
that you'll encounter some problems, such as ambiguous calls.
A call is ambiguous when the compiler cannot clearly understand which method to call.
struct A {
void foo() {}
};
struct B {
void foo() {}
};
struct C: A, B {
void bar() { foo(); }
};
In this example, it is not clear which foo() to call, A's or B's. We can disambiguate that by
prepending the name of the class followed by two columns: A::foo().
2. Abstract classes
If a class comprises pure virtual methods, you cannot create objects of this class type.
The compiler will issue an error message here, as the Coworker class contains the
pure virtual method income(). This avoids calling a method for worker that still
needs to be defined.
A class that does not allow you to create any objects is referred to as an abstract
class.
A class with pure virtual methods is an abstract class.
When a class is derived from an abstract class, it inherits all the methods the base class
contains, particularly the pure virtual methods.
If all of these pure virtual methods are implemented in the derived class, you can then create
an object of the derived class type
A class derived from a class containing pure virtual methods is a concrete class, if it contains
a definition for each pure virtual function.
This means you need to implement the income() method in the Laborer class
shown opposite. Since the hourly wage and the number of hours worked are both defined
for blue-collar workers, it is possible to implement that method.
A class derived from a concrete class can again contain pure virtual methods, due to
additional definitions in the derived class. In other words, an abstract class can be
derived from a concrete class.
An abstract class does not necessarily need to contain pure virtual functions. If the
class contains a protected constructor, objects of the class type cannot be created.
The constructor can only be called then by methods in derived classes. A constructor of
This type normally acts as base initializer, when an object of a derived class type is created.
3. multiple inheritance
I. Introduction
A class can contain not just one but several different base classes. In this case the class is
derived from multiple base classes in a process known as multiple inheritance.
This class Car is used to represent vehicles and the class Home contains characteristic
values for an apartment, such as floor space, number and type of rooms, and typical
operations, such as building, selling, or renting.
Using these two classes you can then derive the MotorHome class. The opposite page
shows the inheritance and definition schemes for the new class. An object of the
MotorHome class contains both the members of Car and the members of Home. More
specifically, the object contains two base sub-objects of type Car and Home.
Since the MotorHome class has two public base classes, it assumes the public interfaces
of both classes. A MotorHome type object not only allows access to the additional
public members but to all the public members of the base classes Car and Home.
When defining a multiply-derived class, the accessibility, private, protected, or
public, must be defined separately for each base class. The MotorHome class could
have the public base class Car and the protected base class Home.
This statement defines the public base class Car and the private base class Home.
This makes all the public members in Home private members of the derived class.
In multiple inheritance each public base class establishes an is relationship. This is
similar to simple inheritance. If the MotorHome class inherits two public base classes,
a motor-home is a special kind of motor vehicle and a special kind of home.
A class can be derived from several classes that have a common base class, however.
This class is then referred to as a multiple indirect base class.
The inheritance graph on the opposite page shows the multiply-derived class SUV,
which was derived from the classes PassCar and Van. Both base classes were themselves
derived from the Car class. This makes Car a multiple indirect base class of the
SUV class.
Ambiguity
An object of the SUV class thus contains the members of Car twice. Access to members
of the Car class results in ambiguity.
Both the base classes PassCar and Van contain a method called getProd(), which
they both inherited from the Car class. In this case the compiler cannot decide which
method is meant.
Ambiguity in the context of multiple inheritance is also possible when several base
classes contain members with identical names. If both the Home class and the Car
class contain a method called getNr(), the getNr() method cannot be correctly
identified in the following statement.
To resolve ambiguity of this kind, you can use the scope resolution operator to determine
which base class is meant.
The getNr() method in the Home class is called first, followed by the getProd()
method inherited by PassCar from the Car class.
class PassCar : public virtual Car {
};
};
I. Declaration
A direct base class is declared virtual when a derived class is defined. You can use the
virtual keyword, which directly precedes the name of the base class.
In the definition scheme shown opposite, the Car class becomes the virtual base class
of PassCar and Van. However, the fact that the base class Car is virtual has no
significance at this point.
A virtual base class takes effect in cases of multiple inheritance. The following definition
Example: class SUV ensures that the SUV class only contains one instance of the virtual
base class Car. An object my of the SUV class gets sufficient memory for only one Car class
sub-object.
ensures that the SUV class only contains one instance of the virtual base class Car.
An object my of the SUV class gets sufficient memory for only one Car class sub-object.
More specifically, the statement does not cause ambiguity.
The following items are important with respect to virtual base classes:
■ A virtual base class stays virtual even if further derivations are built. Each class
derived from PassCar also has the Car class as a virtual base class.
When an object is created in a simply-derived class, the sub-objects of the base classes
are created first on all levels of the class hierarchy. The sub-object whose class is nearer
to the top of the inheritance graph is created first.
The order of the constructor calls is “top down” and follows the inheritance graph.
The activation order used for the constructors in simple inheritance has been generalized
for multiple inheritance.
Inheritance Graph
Again, the inheritance graph, also called sub-object lattice, has an important job to do.
When a derived class is defined, the following rules apply:
■ In cases of multiple inheritance, base classes are entered into the inheritance
graph from left to right in the order in which they were stated when the class was
defined. The graph opposite illustrates this point.
If the class hierarchy does not contain any virtual base classes, the following applies to
the activation order of the constructors.
■ The base class constructors are executed first, top-down and from left to right on
each level.
■ Finally, the constructor belonging to the current class, which is at the bottom of
the inheritance graph is executed.
If we look at the example on the opposite page, this means that the sub-objects of the
Base classes Base1, Base2, and Base3 are created in this order. Then the constructor
of MultiDerived is executed.
The constructor for the class at the bottom end of the inheritance graph uses base initializers
to pass the values to the direct and indirect base classes. If the base initializer definition
is missing in a constructor definition, the default constructor of the base class is
automatically executed.
Initial values are thus passed to the base class constructors “bottom up.”
IV. Constructor Calls in Virtual Base Classes
When an object is created for a multiply-derived class, the constructors of the base
classes are called first. However, if there is one virtual base class in the class hierarchy,
the virtual base class constructor is executed before a constructor of a non-virtual base
class is called.
The constructors of the virtual base classes are called first, followed by the constructors of
non-virtual base classes in the order defined in the inheritance graph.
✓ NOTE
The constructor of a virtual base class is called with the arguments stated for the base
initializer of the last class to be derived, i.e. class at the bottom end of the inheritance graph.
✓ NOTE
The constructor of the virtual base class nearest the top of the inheritance graph is
executed first. This does not necessarily mean the top level of the class hierarchy, since a
virtual base class can be derived from a non-virtual base class.
In our example with the multiply-derived class SUV (Sport Utility Vehicle) the constructor
for the virtual base class Car is called first, followed by the direct base classes
PassCar and Van, and last but not least, the constructor of the SUV class.
The example opposite shows SUV containing a constructor with one base initializer.
Its arguments are passed to the constructor of the virtual base class Car.
For the purpose of initialization, it does not matter whether a class derived directly
from Car contains a base initializer or not. Base initializers for virtual indirect base
classes defined in the constructor of a direct base class are ignored. If the base classes
PassCar and Van also contained base initializers for the virtual base class Car, these
would be ignored too.
If the constructor for the last derived class does not contain a base initializer, the
default constructor is executed for each virtual base class. Whatever happens, a default
constructor must then exist in every virtual base class! Thus, base initializers that happen
to exist in base classes are also ignored.
Chapter ….. 24
1. Introduction To Operators
When you initialize a variable you can start performing operations with it like additions,
subtraction and many more according to the data type you’re working with.
I’ll introduce operators and their use cases.
there’s a wide range of operators that will let you work along the program to achieve its
objectives.
I. Binary Operators
The figure below makes clear why they’re called binary operators because to perform a
calculation you need two operands
a) ARITHMETIC OPERATORS
The following table shows the binary arithmetic operators that can be used in your C
program.
A recommendation is to always use the same type to avoid program errors.
Example :
Program Description: calculate meters based on centimeters , add them and print the
values.
#include <stdio.h>
int main () {
float meters=3.65;
float centimeters=4.25;
printf("%f", centimeterToMeters);
printf("%f",sum);
return 0;
b) ASSIGNMENT OPERATORS
“=”: This is the simplest assignment operator. This operator is used to assign the value on
the right to the variable on the left.
For example:
a = 10;
b = 20;
ch = 'y';
“+=”: This operator is a combination of ‘+’ and ‘=’ operators. This operator first adds the
current value of the variable on left to the value on the right and then assigns the result to
the variable on the left.
Example:
“=”This operator is a combination of ‘-‘ and ‘=’ operators. This operator first subtracts the
current value of the variable on the left from the value on the right and then assigns the
result to the variable on the left.
Example:
(a -= b) can be written as (a = a - b)
If initially value stored in a is 8. Then (a -= 6) = 2.
“*=”This operator is combination of ‘*’ and ‘=’ operators. This operator first multiplies the
current value of the variable on left to the value on the right and then assigns the result to
the variable on the left
Example:
(a *= b) can be written as (a = a * b)
If initially value stored in a is 5. Then (a *= 6) = 30.
“/=”This operator is a combination of ‘/’ and ‘=’ operators. This operator first divides the
current value of the variable on the left by the value on the right and then assigns the result
to the variable on the left.
Example:
(a /= b) can be written as (a = a / b)
If initially value stored in a is 6. Then (a /= 2) = 3.
Program Description :
// C program to demonstrate
// working of Assignment operators
#include <stdio.h>
int main()
// Assigning value 10 to a
// using "=" operator
int a = 10;
printf("Value of a is %d\n", a);
return 0;
}
c) COMPARISON OPERATORS
Relational operators commonly called comparison operators are used to perform
comparison binary operations (you also need two operands) . they always return a boolean
value when evaluating the expressions . So they evaluate to true or false once the task is
finished.
you’ll see them most commonly used in action when writing conditionals ( it’ll be explained
in the next chapter)
#include <stdio.h>
int main () {
return 0;
}
d) LOGICAL OPERATORS
Logical operators comprise the boolean operators && (AND), || (OR), and ! (NOT).
They can be used to create compound conditions and perform conditional execution of a
program depending on multiple conditions.
A logical expression results in a value false or true, depending on whether the logical
expression is correct or incorrect, just like a relational expression.
In other words it takes two expressions . An expression can be defined as any code that
gets executed and ends up being a Value.
Example :
The following table shows variable A and B , their possible boolean values and compares
them using logical operators showing the returned boolean value.
I. && And Operator
The and operator && evaluates an expression and returns a boolean value (true or false) .
it will return true only if both operands are true, so the logical expression:
#include <stdio.h>
int main () {
int x=2;
int y=3;
// returns 1 (true)
printf(“%d”,result);}
#include <stdio.h>
int main () {
int x=2;
int y=4;
printf(“%d”,result);
return 0;
}
III. The NOT operator !
Will return true only if its operand is false. If the variable flag contains the value false (or the
value 0), !flag returns the boolean value true.
// A remainder any value that does not equal 0 . When used along the Not! operator will
return 1. The Value 0 represents False when it comes to Boolean types and 1 represents
True.
Now comes the trick , any values exceeding 0 will be treated as truthy values .
when you apply this operator it returns the opposite boolean value.
Let's check this down below.
#include <stdio.h>
return 0;
};
3. Unary Arithmetic Operators
#include <stdio.h>
int main () {
int num=1;
// adds 1 to num
num ++;
printf("%d\n",num);
num--;
printf("%d\n", num);
num--;
printf("%d\n",num);
return 0;
};
Chapter 25
A control flow statement is a statement that results in a choice being made as to which of two or
more paths to follow.
2. If Statements
Program Description : compares two numbers and if the expression results in numOne
being smaller than numTwo the printf() function executes that is the program executes the
code between the braces if not the program skips the code and just ends without any
further execution.
#include <stdio.h>
int main () {
int numOne=2;
int numTwo=5;
if (numOne<numTwo){
}
return 0;
};
output
3. If-Else Statements
if (condition) {
// block of code to be executed if the condition is true
} else {
// block of code to be executed if the condition is false
}
Program Description : it is the same case as before but the program now executes the
code block in the else statement if the condition is not satisfied.
I changed the numOne value to 5 when the if expression gets evaluated it evaluates to false
but this isn't going to stop the program . in this case the else printf() executes before
reaching the return statement.
#include <stdio.h>
int main () {
int numOne=5;
int numTwo=5;
if (numOne<numTwo){
} else {
return 0;
};
4. If-else-if statements
Imagine that you want to evaluate several conditions. To accomplish this you’d need
to make use of several if /else-if statements . when one expression returns true the block
stops executing. if more than one condition is satisfied the one reached first executes and
the rest is not evaluated.
} else if (condition 2) {
statement 2
}else{
statement 3
};
if (condition 1) {
statement 1
}
if (condition 2) {
statement 2
}
if (condition 3) {
statement 3
}
#include <stdio.h>
int main()
{
int i = 20;
if (i == 10)
printf("i is 10");
else if (i == 15)
printf("i is 15");
else if (i == 20)
printf("i is 20");
else
printf("i is not present");
return 0;
}
The expression gets evaluated and executes the code that matches the case.
The break keyword prevents further execution.
if there’s no matching case the default case gets executed when included in the statement.
switch (expression)
{
case constant 1:
group-of-statements-1;
break;
case constant 2:
group-of-statements-2;
break;
...
default:
default-group-of-statements;
break;
}
#include <stdio.h>
int main()
{
// variable to be used in switch statement
int var = 2;
// declaring switch cases
switch (var) {
case 1:
printf("Case 1 is executed");
break;
case 2:
printf("Case 2 is executed");
break;
default:
printf("Default Case is executed");
break;
}
return 0;
}
7. Ternary Operator
It is kind of similar to the if-else statement as it follows the same algorithm as the if-else
statement but the conditional operator takes less space and helps to write the if-else
statements in the shortest way possible. It is also known as the ternary operator in C as it
operates on three operands
Example program
Description:
#include <stdio.h>
int main()
{
int m = 5, n = 4;
return 0;
}
CHAPTER 26
1. LOOPS
Loops in programming are used to repeat a block of code until the specified condition is met.
A loop statement allows programmers to execute a statement or group of statements
multiple times without repetition of code.
2. For Loops
It is a repetition control structure that allows programmers to write a loop that will be
executed a specific number of times. for loop enables programmers to perform n number of
steps together in a single line.
statement1;
statement2;
...
statementN;
}
#include <stdio.h>
int main () {
return 0;
};
Another iteration statement is the while loop. It is simpler than the for loop. it will execute the
code as long as the condition evaluates to true (expression is true).
Program description : C program to demonstrate while loop
#include <stdio.h>
int main()
{
// Initialization of loop variable
int i = 0;
// loop statements
printf("GeeksforGeeks\n");
return 0;
}
A Similar loop is to the do-while loop, where the condition is checked after the execution
of the body statements, instead of before.This means that the code gets executed at least
once. that's the main difference between the other loops , which won't run the code if the
condition isn’t met (it’s evaluated first)
Program Description: C Program to demonstrate the use of do...while loop
#include <stdio.h>
int main()
{
return 0;
}
It is a jump statement that is used to bring the program control to the start of the loop. We
can use the continue statement in the while loop, for loop, or do..while loop to alter the
normal flow of the program execution. Unlike break, it cannot be used with a C switch case.
a) What is continue in C?
The C continue statement resets program control to the beginning of the loop when
encountered. As a result, the current iteration of the loop gets skipped and the control moves
on to the next iteration. Statements after the continue statement in the loop are not
executed.
b) Syntax of continue in C
The syntax of continue is just the continue keyword placed wherever we want in the loop
body.
continue;
c) Use of continue in C
The continue statement in C can be used in any kind of loop to skip the current iteration. In
C, we can use it in the following types of loops:
● Single Loops
● Nested Loops
Using continue in infinite loops is not useful as skipping the current iteration won’t make a
difference when the number of iterations is infinite.
The continue statement can be used in for loop, while loop, and do-while loop.
Program Example :
#include <stdio.h>
int main()
{
// for loop to print 1 to 8
if (i == 4) {continue;}
printf("%d ", i); }
printf("\n");
int i = 0;
while (i < 8) {
i++;
if (i == 4) {continue;}
printf("%d ", i);}
return 0;}
Output
1 2 3 5 6 7 8
1 2 3 5 6 7 8
● STEP 3A: If the condition is false, the normal execution will continue.
● STEP 3B: If the condition is true, the program control will jump to the start of the
loop and all the statements below the continue will be skipped.
e) FLOWCHART OF CONTINUE IN C
II. The Break Statement
a) Use of break in C
The break statement in C is used for breaking out of the loop. We can use it with any type of
loop to bring the program control out of the loop. In C, we can use the break statement in the
following ways:
● Simple Loops
● Nested Loops
● Infinite Loops
● Switch case
Example 1:
//Break statements in C can be used with simple loops i.e, for loops, while loops, and do-
while loops.
#include <stdio.h>
int main() {
int i = 1;
while (i < 20) {
if (i == 3)
break;
else
printf("%d ", i);
i++;
}
return 0;
};
1. STEP 1: The loop execution starts after the test condition is evaluated.
3. STEP 3 A: If the condition is true, the program control reaches the break
statement and skips the further execution of the loop by jumping to the
statements directly below the loop.
4. STEP 3 B: If the condition is false, the normal flow of the program control
continues.
d) COMPARING BREAK AND CONTINUE STATEMENTS
#include <stdio.h>
{
if (num % 2 == 0)
// jump to even
goto even;
else
// jump to odd
goto odd;
even:
printf("%d is even", num);
// return if even
return;
odd:
printf("%d is odd", num);
}
int main(){
Output:
26 is even
II. Type 2: In this case, we will see a situation similar to as shown in Syntax2
above. Suppose we need to write a program that prints numbers from 1 to 10
using the goto statement. The below program explains how to do this.
● The use of the goto statement is highly discouraged as it makes the program
logic very complex.
● The use of goto makes tracing the flow of the program very difficult.
● The use of goto makes the task of analyzing and verifying the correctness of
programs (particularly those involving loops) very difficult.
● The use of goto can be simply avoided by using break and continue statements.
A range is a series of values that go from one value to another value and include
the values in between. For example, the range a through d is a, b, c, and d. An integer
range of 1 through 5 is 1, 2, 3, 4, and 5.
#include <iostream>
using namespace std;
int main()
{
int range[] = {1, 2, 3, 4, 5};
for (int i : range)
{
cout << i << endl;
}
return 0;
}
CHAPTER… 28
PREPROCESSOR DIRECTIVES
1. Introduction
Think of the preprocessor as just a machine that transforms your code into a temporary,
fixed-up version that’s all ready to be compiled. For example, look at this
preprocessor directive:
#include <iostream>
If the preprocessor sees this line, it inserts the entire text from the file called
iostream (yes, that’s a filename; it has no extension) into the fixed-up version of
the source code. Suppose that the iostream file looks like this:
Just two lines are all that’s in it. (Of course, the real iostream file is much more
sophisticated.) And suppose that your own source file, MyProgram.cpp, has this in
it (as found in the Preprocessor example):
#include <iostream>
int main(){
std::cout << "Hello world!" << std::endl;
return 0;
}
In other words, the preprocessor replaced the #include line with the contents of
that file. Now, the iostream file could have #include lines, and those lines would
be replaced by the contents of the files they refer to. As you may imagine, what
started out as a simple application with just a few lines could actually have hundreds
of lines after the preprocessor gets through with it.
The preprocessor also provides you with a lot of other directives besides #include.
One of the more useful ones is the #define directive. Here’s a sample #define
line:
#define MYSPECIALNUMBER 42
After the preprocessor sees this line, every time it encounters the word MYSPECIALNUMBER,
It replaces it with the word 42 (that is, whatever sequence of letters,
numbers, and other characters follow the definition). In this case, #define creates
a kind of constant where the word is easier to understand than the value associated
with it. But #define also lets you create what are called macros, which are a
sort of script. This line defines the oldmax() macro:
After the preprocessor sees this line, it replaces every occurrence of oldmax()
followed by two arguments with ((x)>(y)?(x):(y)), using the appropriate substitutes
for x and y. For example, if you then have this line
q = oldmax(abc, 123);
q = ((abc)>(123)?(abc):(123));
#ifdef DEBUG
cout << "The value of j is " << j << endl;
#else
cout << j << endl;
#endif
The lines that begin with # are preprocessor directives. The preprocessor has its
own version of if statements. In your code, you can have a line like the following,
with nothing after it:
#define DEBUG
This line defines a symbol (rather than a constant with a value). It works just like
the symbols described earlier, except that it’s not set to be replaced by anything.
You can also define such symbols in the command-line options to GCC or whichever
compiler you use.
Now, when the preprocessor starts going through your application and gets to the
#ifdef DEBUG line, it checks to see whether the DEBUG symbol is defined. If
the symbol is defined, it spits out to its fixed-up file the lines that follow, up
until the #else line. Then it skips any lines that follow that, up until the #endif
line. For the earlier example in this section, if DEBUG is defined, the block of code
starting with #ifdef DEBUG through the line #endif is replaced by the code in the
first half of the block:
But if the DEBUG symbol is not defined, the preprocessor skips over the lines up
until the #else, and spits out the lines that follow, up until the #endif. In this
case, it’s replaced by the code following the #else line:
#include <iostream>
using namespace std;
#ifdef UNIVAC
const int total = 200;
const string compname = "UNIVAC";
#elif defined(HAL2000)
const int total = 300;
const string compname = "HAL2000";
#else
const int total = 400;
const string compname = "My Computer";
#endif
// This is outdated, but you might see it on
// occasion. Don't write code that does this!
#define oldmax(x, y) ((x)>(y)?(x):(y))
#define MYSPECIALNUMBER 42
int main() {
cout << "Welcome to " << compname << endl;
cout << "Total is:" << endl;
cout << total << endl << endl;
// Try out the outdated things.
cout << "*** max ***" << endl;
cout << oldmax(5,10) << endl;
cout << oldmax(20,15) << endl;
cout << MYSPECIALNUMBER << endl << endl;
Welcome to My Computer
Total is:
400
*** max ***
10
20
42
*** Predefined Macros ***
This is file C:\CPP_AIO\BookI\Chapter09
\Preprocessor2\main.cpp
This is line 35
Compiled on Apr 23 2020
Compiled at 15:19:38
*** total ***
79800
class plants {
public:
string name;
string family;
string season;
};
chapter Templates 29
1. Introduction To Templates
class MyHolder {
public:
int first;
int second;
int third;
int sum() {
return first + second + third;
}
};
This class is easy to use; you just create an instance of it and set the values of its
members.
The following code creates ten instances of the class ( the instances are heap- stored ), calls
sum(), and prints the return value of sum():
// class myHolder
myHolder { … }
//pointer of type myHolder
MyHolder *hold;
int loop;
delete hold;
Suppose you discover that this class MyHolder is handy, for having a version of it that
holds floats instead of ints.
You could create a second class just like the first that uses the word float instead
of int, like this:
class AnotherHolder {
public:
float first;
float second;
float third;
float sum() {
return first + second + third;
}
};
This class works the same way as the previous class, but it stores three float
types instead of int types. Instead of typing two different versions of the class, type one
version of the class that you can, effectively, modify when you need a different version.
CoolHolder<int> IntHolder;
IntHolder.first = 10;
IntHolder.second = 20;
IntHolder.third = 30;
This code tells the compiler to create a class by replacing every instance of T
with int in the CoolHolder template. In other words, the compiler creates a
class named CoolHolder<int>. These four lines of code create an instance of
CoolHolder<int> called IntHolder and set its properties. The computer creates
this class at compile time. Remember, types are created at compile time, and this
example code is no exception to this rule.
It's time to put that template into action. The CoolHolder example,
contains a complete application that uses the CoolHolder
template.
};
Program Example :
#include <iostream>
using namespace std;
int main()
CoolHolder<int> IntHolder;
IntHolder.first = 10;
IntHolder.second = 20;
IntHolder.third = 30;
CoolHolder<float> FloatHolder;
CoolHolder<float> FloatHolder;
FloatHolder.first = 3.1415;
FloatHolder.second = 4.1415;
FloatHolder.third = 5.1415;
CoolHolder<int> *hold;
Output :
10
100
3.1415
0
330
660
990
1320
1650
1980
2310
2640
2970
Templates are a way to define functions that can work for many different
types, while still writing them only once.
template<typename T>
T square(T value) {
return value * value;
}
square<int>(10);
program Example:
//This calls the templated function max, specifying int as the type
parameter. We say that we instantiate the templated function max
with type int, and then called that instance.
Note
Since the compiler generates the code from the template definition, it means that
the full definitions need to be visible to the calling code, not only the declaration,
as was the case for functions and classes.
when writing templates that should be accessed by several
files, both the definition and the declaration of the templates must be in the header file.
This restriction does not apply if the template is used only in one file.
I. Classes can also have templated methods. Templated methods are similar to
template functions, but they can access the class instanced data.
template<typename Comparator>
};
The sort method will accept any type and will compile if the type satisfies all the
requirements that the method imposes on the type.
To call the method, the syntax follows the one for calling functions:
MyArray<int> array;
MyComparator comparator;
array.sort<MyComparator>(comparator);
The following program shows how to define template functions prototypes. the syntax
is alike to the one used for declaring and defining non-templated functions
Program Example :
#include <iostream>
using namespace std;
class ImFree {
protected:
T x;
public:
T& getx();
void setx(T);
};
//template declaration
template <typename T>
T &ImFree<T>::getx() {
return x;
// template declaration
int main() {
// setting x to 10
separate.setx(10);
//gettingthe value of x
Note that the getx() method returns a reference instead of a variable of type T.
There’s a good reason for doing it. In the main() function you create
the class based on the template with an integer parameter:
ImFree<int> separate;
However, you can create the class with some other class:
ImFree<SomeOtherClass> separate;
You can think of the static member as being a member of the class itself.
When you include a static member in a template, each class that you create based
on the template gets its own static member. Further, you need to tell the compiler
how to store the static member just as you do with static members of classes that
aren’t created from templates.
#include <iostream>
//You supply the same template header you would use for the class
and then specify the static member type (in this case, T, which is
the template parameter).
public:
static T charge;
};
T Electricity<T>::charge;
int main() {
Electricity<int>::charge = 10;
Electricity<float>::charge = 98.6;
// to show that there’s only a single static member per class, the
code creates an instance of Electricity<int> and sets its static
member to 22
Electricity<int> inst;
inst.charge = 22;
// you can see that the output for the two Electricity<int> lines
are the same and the Electricity<float> output is different.
4. Deriving Templates
If you think about it, you can involve a class template in a derivation in at least
three ways.
You can:
Suppose you have a template called MediaHolder, and the first two lines of its
declaration look like this:
Then you could derive a class from a particular case of this template as in this
header for a class:
Program Example :
#include <iostream>
using namespace std;
class Book {
public:
string Name;
string Author;
string Publisher;
//constructor
};
// magazine class definition
class Magazine {
public:
string Name;
string Issue;
string Publisher;
// constructor
};
public:
T *array[100];
int Count;
array[Count] = item;
Count++;
MediaHolder() : Count(0) {}
};
// derived class bookholder
public:
GenreEnum GenreOfAllBooks;
};
public:
bool CompleteSet;
};
int main() {
MagazineHolder dl;
dl.CompleteSet = false;
cout << dl.Count << endl;
BookHolder bh;
Output :
When you run this example, you see the magazine count of 3 first,
and the book count of 3 second.
#include <iostream>
using namespace std;
public:
T a;
};
public:
T b;
};
// two functions each taking a different class. The first one takes
Base<int> as a parameter. The Second one takes Base<double >. Both
take a pointer to a class.
int main() {
Base<int> base_int;
// same as above
Base<double> base_double;
Derived<int> derived_int;
Derived<double> derived_double;
//it outputs four numbers: two int values and two double values.
TestInt(&base_int);
TestInt(&derived_int);
TestDouble(&base_double);
TestDouble(&derived_double);
return 0;
}
Output :
5. Parameterizing a Template
1) You can put many more keywords inside the parameter beyond just the
boring word typename.
2) For example, suppose you have a class that does some comparisons to
make sure that a product isn’t too expensive for a person’s budget.
3) Each person would have several instances of this class, one for each
product. This class would have a constant in it that represents the maximum
price the person is willing to spend.
4) But there’s a twist: Although you would have multiple instances of this class,
one for each product the person wants to buy, the maximum price would be
different for each person.
We always said that we need to provide the template arguments to the parameters of a
template function or class. Now, in this section, we are going to see two features that
C++ offers to make it easier to use templates.
These features are default template arguments and template argument deduction.
The error happens because we cannot bind a temporary value, like 1, to a non-const
reference.
As we can see, the compiler tries to deduce a type so that when it is substituted in the
parameter, it matches the argument as best as possible.
The compiler cannot always find such a type; in those situations, it gives an error and
it's up to the user to provide the type.
The compiler cannot deduce a type for any of the following reasons:
For example: The compiler cannot deduce a type if it is only used in the return type, or only
used inside the body of the function.
For example:
The compiler cannot find the type T given the parameter that's used to call the function.
Knowing these rules, we can derive a best practice for the order of the template
parameters when writing templates: the types that we expect the user to provide need
to come before the types that are deduced.
The reason for this is that a user can only provide the template arguments in the same
order they have been declared.
Let's consider the following template:
When calling foo (1, 2.23), the compiler can deduce A and B, but cannot deduce C.
Since we need all the types, and the user has to provide them in order, the user has to
provide all of the types:
Let's say we put the types that cannot be deduced before the types that can be
deduced, as in the following example:
We could call the function with foo<float>(1, 2.23). We would then provide the type to
use for C and the compiler would automatically deduce A and B.
Since they need to come last, we need to make sure to put the types that the user is
more likely to want to modify first, since that will force them to provide all the template
arguments up to that parameter.
Note
Let's say the type is not deduced, but, it is provided explicitly, for example:
int x = 0;
do_action<int>(x);
Additionally, some types do not support copying, and we can make our template work
with those types as well.
When we write the body of the template function, the parameter is used as an l-value
reference, and we can write code ignoring whether T is an l-value reference or an
r-value one:
template<typename T>
obj.some_method();
some_function(obj);
//In this case, the template parameter isn’t a type at all — it’s an
integer value, an actual number. Then, inside the class, you use
that number as a constant.
//As you can see in the TestPrice function, the code compares Price
to the MaxPrice constant. So this time, instead of using T for the
name of the template parameter, the code views it as a value, not a
type.
public:
int Price;
void TestPrice(){
};
Complete Program :
#include <iostream>
using namespace std;
public: T member; };
public:
int Price;
}
};
int main() {
SomethingForEveryone<int> JustForMe;
JustForMe.member = 2;
1)
const int FredMaxPrice = 30;
PriceController<FredMaxPrice> FredsToaster;
FredsToaster.Price = 15;
FredsToaster.TestPrice("Toaster");
2)
PriceController<FredMaxPrice> FredsDrawingSet;
FredsDrawingSet.Price = 45;
FredsDrawingSet.TestPrice("Drawing set");
1)
const int JulieMaxPrice = 60;
PriceController<JulieMaxPrice> JuliesCar;
JuliesCar.Price = 80;
JuliesCar.TestPrice("Car");
return 0;
}
Each person gets a different class that reflects the maximum price they’re
willing to pay.
You can see that Fred gets a class called PriceController <FredMaxPrice>.
Julie gets a class called PriceController <JulieMaxPrice>.
And remember, these really are different classes. The compiler created
two different classes, one for each item passed in as a template parameter. Also
notice that the parameters are constant integer values. FredMaxPrice is a constant
integer holding 30. JulieMaxPrice is a constant integer holding 60.
For the first one, PriceController <FredMaxPrice>, the code creates two
instances. For the second onJe, PriceController <JulieMaxPrice>, the code
creates one instance.
In all instances, the code sets the price of the item and
then calls TestPrice() with the item name. If the item is too expensive, the
PriceController outputs a special message.
When working with some older versions of C++, you can’t use certain types, such
as float, for your template. Doing so can cause the build process to fail with all
sorts of odd messages. However, you also see the following message, which tells
you precisely where the problem lies:
Starting with C++ 11, you can use std::nullptr_t; as a parameter type, as in
template <std::nullptr_t N>.
When working with C++ 20, you gain access to
these types as well:
»»Floating-point type
»»Literal class type with the following properties:
• All base classes and nonstatic data members are public and non-mutable.
• The types of all base classes and non-static data members are structural
types. Depending on the compiler, you might also be able to use a
multidimensional array of the structural type.
CHAPTERS 30
CONTAINERS C++ FUNDAMENTALS BOOK
1. Introduction
Containers are used to store collections of elements of a certain type. Usually, the
type of the container is a template parameter There are several container classes, each of
them with different characteristics and features.
2. Sequence Containers
IN Sequence containers the order in which their elements are stored is decided by the
programmer rather than by the values of the elements. Every element has a certain position
that is independent of its value.
List of Sequence containers with included description
Time for insertion and removal of Objects
I. Array
d) Arrays access:
e) Complexity :
These operations are fast: since the elements are contiguous, we can compute the
position in memory of an element given its position in the array, and access that
directly.
#include <iostream>
#include <array>
#include <algorithm>
#include <iterator>
using namespace std;
int main() {
return 0;
}
Output:
a b c d e
III. Vector
The vector container is a data structure of contiguous elements whose size can be
dynamically modified: it does not require to specify its size at creation time.
The vector class is defined in the <vector> header file.
a) Description
A vector stores the elements it contains in a single section of memory. When a new element
is added to the vector, if there is enough space in the section of memory, the element is
added after the last element in the vector. If there isn't enough space, the vector gets a new,
bigger section of memory and copies all the existing elements into this new section, then it
deletes the old section of memory.
b) Elements access:
● By the at () function
● By square brackets []
● Front() can be used to get a reference to the first element,
● Back() returns a reference to the last element.
c) Elements can be appended using:
● Emplace () Suppose you have a vector of Person objects. You can add a new
person at the back in these two similar ways:
persons.push_back(Person("Sheldon", "Cooper"));
persons.emplace_back("Leonard", "Hofstadter");
On a related note, addition and insertion members of containers generally have full support
for moving elements into containers, again to avoid the creation of unnecessary copies .
For example:
● To remove all elements, use clear() . This again does not affect capacity.
Appending or deleting the last element is fast, while inserting or removing other
elements of the vector is considered slow, as it requires moving all the elements to
make space for the new element or to keep all the elements contiguous:
Time Complexity:
Vectors, just like arrays, allow efficient access of elements at random positions.
A vector's size is also retrieved with the size() member function which returns the actual
number of elements in the vector, capacity() instead returns the maximum number of
elements that can be inserted in the current section of memory.
When you remove elements, the size of the vector changes, but its capacity does not. If you
want to reclaim unused memory, you can use shrink_to_fit() .
#include <iostream>
#include <vector>
int main() {
std::vector<int> myvector;
myvector.push_back(100);
// Now, the back of the vector holds 10 as a value, the front holds
100
myvector.push_back(10);
myvector.front() -= myvector.back();
};
Output:
Example 2 :
#include <iostream>
#include <vector>
int main() {
vector<string> names;
names.push_back("Tom");
names.push_back("Dick");
names.push_back("Harry");
names.push_back("April");
names.push_back("May");
names.push_back("June");
cout << names[0] << endl;
cout << names[5] << endl;
return 0;
};
Output:
1. Tom
2. June
Example 3:
#include <iostream>
#include <vector>
class Employee {
public:
string Name;
string HireDate;
int holidays;
vector<string> MyAliases;
MyAliases.push_back(string("Bud The Sailor"));
MyAliases.push_back(string("Rick Fixit"));
MyAliases.push_back(string("Bobalou Billow"));
} ;
vector<int> LuckyNumbers;
LuckyNumbers.push_back(13);
LuckyNumbers.push_back(26);
LuckyNumbers.push_back(52);
vector<int> Default(5);
int i = 0;
*rentry = ++i;
} ;
};
vector<Employee> GreatWorkers;
GreatWorkers.push_back(Employee("George","123100", 50));
GreatWorkers.push_back(Employee("Tom","052002", 40));
Notice that this example relies on an iterated for loop for each of the vectors.
Using an iterated for loop greatly reduces the amount of code you write. Plus, you
don’t have to worry about the size of the vector you’re processing. All you concern
yourself with are the individual entries.
The Default portion of the example is also interesting in that it declares a vector
of a specific size and reverse-fills the vector from the end to the beginning. Consequently,
Default[0] contains the value 5, rather than 1, as you might expect.
To work backward, you must use a standard for loop.
IV. Deque
a) Description
A deque does not have the concept of capacity because it does not have to store its
elements contiguously, so none of the methods related to capacity are available.
b) Elements Access:
● Bracket notation []
c) Elements Insertion:
d) Deletion:
Example:
e) Operations Complexity :
• Insertion : Amortized constant O(1) at the beginning and end; otherwise linear in the
distance from the insertion point to the beginning or end O(N)
• Deletion : O(1) at the beginning or end; otherwise linear in the distance to the beginning
or end O(N)
• Access : O(1)
deque<int> mydek;
mydek.push_front(10);
mydek.push_front(20);
mydek.push_back(30);
mydek.push_back(40);
Then you can loop through the deque, accessing its members with a
bracket, as if it’s an array:
int loop;
You can also grab items off the front or back of the deque. Here’s
an example from the front:
};
Note
DEQUES VERSUS VECTORS
A deque doesn’t use the contiguous approach that vector does. Inserting is easier for deque
because it doesn’t need to shuffle things around. Also, a deque doesn’t have to add new
elements to perform a resize, whereas vector does when it runs out of space. And
finally, deque includes a push_front() method that allows adding an item at the
beginning.
Two functions show up here, front() and pop_front(). The front() function
returns a reference to the item at the front of the deque. The pop_front() function
removes the item that’s at the front of the deque.
V. LISTS
a) Description
When an element is inserted in a list, the predecessor node needs to be updated so that
its successor link points to the new element. Similarly, the successor node needs to be
updated so that its predecessor link points to the new element:
When an element is removed from the list, we need to update the successor link of
the predecessor node to point to the successor of the removed node. Similarly, the
predecessor link of the successor node needs to be updated to point to the predecessor
of the removed node.
The advantage of list is that insertion and removal are fast at any position, if we
already know the node at which we want to insert or remove. The disadvantage of this
is that getting to a specific node is slow.
d) Types of Lists
I. Forward-List
a) Inserting elements :
b) Deletion:
For operations in which you need a pointer to an item in the list, you need to use
an iterator. An iterator is simply a typedef for a pointer to an item in the list;
however, it points to the item in the list, not the original item you added to the
list. Remember, the containers hold copies. Thus, if you do an insert() into a list
and point to an original item, that item won’t be a member of the list, and the
insert() won’t work.
if you do need to do an insert and you’re willing to use iterators to move through the list to
find the location where you want to put the new item, insert() will do the job.
The following table lists the provided algorithms for list (L) and forward_list
(F):
Note
Lists provide sequential access, which means that you can’t drop into the middle of the
list and look at whatever item is stored there (as you can with a vector). If you want to
look at the items in the list, you must start at the beginning or the end and work your
way through it one item at a time. A vector allows random access using brackets, as
in MyVector[3]. This requirement may seem like a disadvantage for the list, but the
ANSI document says that “many algorithms only need sequential access anyway.” Lists
have definite advantages. The list template allows you to splice together multiple lists,
and it has good support for sorting the list, for splicing members out of one list and into
another, and for merging multiple lists.
VI. Sequence Containers in Practice
All the sequence containers we have looked at are empty when they are first created.
When we want to create a container containing some elements, it can be repetitive to
call the push_back() or insert() functions repeatedly for each element.
Fortunately, all the containers can be initialized with a sequence of elements when they
are created.
The sequence must be provided in curly brackets, and the elements need to be comma
separated.
#include <vector>
int main(){
The following subsections give an overview of all the operations supported by vector (V),
deque (D), array (A), list (L), and forward_list (F), divided into categories.
2. Special Container
I. std::bitset
a) Description
A bitset is a container storing a fixed number of bits. The number of bits is specified as a
template parameter. For example, the following creates a bitset with 10 bits, all initialized to
0:
std::bitset<10> myBitset;
The values for the individual bits can be initialized by passing an integer to the constructor or
by passing in a string representation of the bits.
b) Complexity :
Associative Containers
1. Introduction
Associative containers allow for very fast element search. Sequential containers
have some natural ordering that allows you to iterate from the beginning of
the container to the end in a well-specified order. Associative containers are
a bit different. This container family splits along three axes:
2. Iterators
All ordered associative containers support the same set of iterator-related methods as
supported by the vector container:
The complexity for all four ordered associative containers is the same:
Insertion : O(log(N))
Deletion : O(log(N))
Access : O(log(N))
A Set is a container that contains a unique group of sorted elements.Multisets are the same
except that they do not allow duplicates.
II. Sets
A set is similar to a map , but it does not store pairs, only unique keys without values . A
multiset supports duplicate keys. There is only one template type parameter: the key type.
The insert() method takes a single key instead of a pair .
1) Insertion methods:
Insert() function
2) Deletion:
Erase() function
3) Access:
Because the order of the elements is determined by the comparator, they do not take a
position argument like they do for sequential containers. Both insertion and removal are fast.
Since sets are optimized for element lookup, they provide special search functions. The
find() function returns the position of the first element equal to the provided value, or
the position past the end of the set when the element is not found. When we look for an
element with find, we should always compare it with the result of calling end() on the
container to check whether the element was found.
#include <iostream>
#include <set>
int main() {
std::set<int> numbers;
numbers.insert(10);
if (numbers.find(10) != numbers.end()) {
std::cout << "10 is in numbers" << std::endl;}
return 0;
}
Finally, count() returns the number of elements equal to the value provided.
The set and multiset classes are defined in the <set> header file.
#include <iostream>
#include <set>
#include <functional>
int main() {
Output:
Ascending numbers: 1 2 3 4 5
Descending numbers: 5 4 3 2 1
#include <iostream>
#include <set>
using namespace std;
class Emp {
public:
string Nickname;
string SSN;
};
int main() {
set<Emp> employees;
Emp emp1("sparky", "123-22-8572");
employees.insert(emp1);
Emp emp2("buzz", "234-33-5784");
employees.insert(emp2);
Emp emp3("albert", "123-22-8572");
employees.insert(emp3);
// Find an item
iter = employees.find(findemp);
return 0;
}
When you compile and run this example, you see the following output:
1. (sparky,123-22-8572)
2. (sputz,199-19-0000)
3. (buzz,234-33-5784)
4. Finding...
5. (sparky,123-22-8572)
»»Ordering: The items in set are in Social Security number order. This isn’t true
with all containers, but it’s the way a set works.
»»Duplicates: The set ignores any attempt to add two employees with matching
SSN values (even if other properties differ).
You can see in this listing that the code tries to add two employees with the same
SSN values:
and
Later, when the code prints all the items in set, you see only the one for "sparky",
not the one for "albert". set ignored the second employee.
1. Combine two sets to get the union (all the elements in both sets without any
duplicates).
2. Find the common elements to get the intersection (those unique elements
that appear in both sets).
When you #include <set>, you automatically get a couple of handy functions
for finding the union and intersection of some sets. The Sets2 example, shown
in Listing 6-11, demonstrates how you can find the intersection and union of two
sets.
#include <iostream>
#include <set>
#include <algorithm>
using namespace std;
int main() {
set<string> English;
English.insert("Zeus");
English.insert("Magellan");
English.insert("Vulcan");
English.insert("Ulysses");
English.insert("Columbus");
set<string> History;
History.insert("Vulcan");
History.insert("Ulysses");
History.insert("Ra");
History.insert("Odin");
set<string> Intersect;
insert_iterator<set<string> >
IntersectIterate(Intersect, Intersect.begin());
set_intersection(English.begin(), English.end(),
DumpClass(&Intersect);
set<string> Union;
insert_iterator<set<string> >
UnionIterate(Union, Union.begin());
set_union(English.begin(), English.end(),
DumpClass(&Union);
return 0;
}
When you run the code in Listing 6-11, you see this output:
1. ===Intersection===
2. Ulysses
3. Vulcan
4. ===Union===
5. Columbus
6. Magellan
7. Odin
8. Ra
9. Ulysses
10. Vulcan
11. Zeus
But as you can see, something a little bizarre is in the code. Specifically, this part
isn’t exactly simple:
insert_iterator<set<string> >
IntersectIterate(Intersect, Intersect.begin());
The next line is the instance name, IntersectIterate, and the constructor
requires two things:
The variable that these two lines create is an iterator, which is a helper object
that another function can use to insert multiple items into a list. In this case, the
function is set_intersection(). The set_intersection() function doesn’t take
the sets as input; instead, it takes the beginning and ending iterators of the two
sets, along with the IntersectIterate iterator declared earlier.
You can see in Listing 6-11 that those are the five items passed to the
set_intersection()function. After calling set_intersection(), the Intersect object
contains
the intersection of the two sets. set_union() works precisely the same way as
set_intersection(), except it figures out the union of the two sets, not the
intersection.
If you find the code in Listing 6-11 particularly ugly, a slightly easier way to call
set_intersection(), one that doesn’t require you to directly create an instance
of insert_iterator, is available. It turns out that a function exists that will do
it for you. To use this function, you can remove the declarations for IntersectIterate
and UnionIterate, and then instead call set_intersection(), like this:
set_intersection(English.begin(), English.end(),
History.begin(), History.end(),
inserter(Intersect, Intersect.begin()));
History.begin(), History.end(),
inserter(Union, Union.begin()));
1. Introduction
Map and multimap are containers that manage key/value pairs as elements.
It is a data structure that stores key-value pairs.The elements are ordered according to the
key. That is, when iterating over all elements contained in an ordered associative container,
they are enumerated in an order with increasing key values , not in the order these elements
were inserted. For a map there can be no duplicate keys, whereas a multimap supports
duplicate keys. When defining a map , you need to specify both the key type and the value
type. You can immediately initialize a map with a braced initializer:
2. Map
The class template map<Key, Value, Comparator, Allocator> takes four template
parameters. The first is the key type Key. The second is the value type Value.
The third is the comparator type, which defaults to std::less. The fourth
parameter is the allocator type, which defaults to std::allocator<T>.
The elements are sorted automatically according to the provided comparator and
applied to the key, the value does not influence the order of the elements:
Map allows you to associate a single value to a key, while multimap allows you to
associate multiple values to the same key.
The map and multimap classes are defined in the <map> header file.
1) Access:
A way to accessing a value from a key is to use at(), which takes a key and
returns the associated value. If there is no associated value, at() will throw an exception.
A different alternative to get the value associated with a key is to use operator[].
The operator[] returns the value associated with a key, and if the key is not present, it
inserts a new key/value pair with the provided key, and a default value for the value.
Because operator[] could modify the map by inserting into it, it cannot be used on a
const map:
#include <iostream>
#include <map>
int main()
{
std::map<int, int> map;
std::cout << "We ask for a key which does not exists: it is default
inserted: " << map[10] << std::endl;
map.at(10) += 100;
std::cout << "Now the value is present: " << map.find(10)->second <<
std::endl;
}
Map also provides a find() function, which looks for a key in the map and
returns the position of the key/value pair if it exists, or the same result of calling end().
From the position, we can access the key with position->first and the value with
position->second:
#include <iostream>
#include <string>
#include <map>
int main()
{
std::map<int, std::string> map;
map.insert(std::make_pair(1, "some text"));
auto position = map.find(1);
if (position != map.end() ) {
std::cout << "Found! The key is " << position->first << ", the value
is " << position->second << std::endl;
}
}
2) Insertion methods:
To insert values into a map, we can call insert(), providing a pair containing the key
and the value. The function also returns a pair, containing the position at which the element
was inserted, and a Boolean set to true if the element was inserted, or false if an element
with the same key already exists.
The keys of a map are sorted and unique, and map supports all the same operations as set.
In fact, you can think of a set as a special kind of map containing keys and empty values.
Accordingly, map supports efficient insertion, removal, and search, and you have control
over sorting with comparator objects.
The Maps example, shown in Listing 6-4, demonstrates a type of container called
a map. A map works much the same as a vector, except for one main difference:
You look up items in vector by putting a number inside brackets, like this:
But with a map, you can use any class or type you want for the index (called a key),
not just numbers. To create an entry, you use a key (the index) and a value (the
data) as a pair.
#include <iostream>
#include <map>
using namespace std;
int main() {
marriages["Tom"] = "Suzy";
marriages["Harry"] = "Harriet";
cout << marriages["Tom"] << endl;
cout << marriages["Harry"] << endl;
return 0; }
To use map, you declare a variable of class map, supplying two template parameters,
the key class and the value class, which are both strings in the example.
To store a map value, you place a key inside brackets and set it equal to a value:
marriages["Tom"] = "Suzy";
When you run this example, you see the following two strings as output:
Suzy
Harriet
Even though the keys can be any type or class, you must specify the type or class
you’re using it when you set up a map. After you do that, you can use only that type for
the particular map. Thus, if you say that the keys will be strings, you cannot then
use an integer for a key, as in marriages[3] = "Suzy";.
Searching If you want to find out whether a certain key is in an associative container,
you can use these:
1. find() : Returns an iterator to the found element (a key-value pair for maps) or
the end iterator if the given key is not found.
2. count() : Returns the number of keys matching the given key. For map or set ,
this can only be 0 or 1, whereas for multimap or multiset , this can be larger than 1.
5. Multisets
1. The method count can return values other than 0 or 1. The count method
of a multiset will tell you how many elements matched the given key.
2. The method equal_range can return half-open ranges containing more than one
element. The equal_range method of multiset will return a range containing all the
elements matching the given key.
You might want to use a multiset rather than a set if it’s important that you store multiple
elements with the same key.
For example, you could store all of an address’s occupants by treating the address as a key
and each member of the house as an element. If you used a set, you’d be stuck having
only a single occupant.
6. Multimaps
It’s important to realize the difference between map and set. map lets you store information
based on a key, through which you can retrieve a value. Listing 6-7, presented
earlier in the “Performing comparisons” section, shows an example in which the key
is an Emp instance and the value is a Salary instance. But with set, you can achieve
something similar: Listing 6-10 could use a single class containing both Emp and Salary
information. Also, you can see in Listing 6-10 that it’s possible to look up the Emp
instance based on nothing but a Social Security number. So in this sense, the Listing 6-
10 example shows a map in which the key is a Social Security number and the value is
the rest of the employee information. The fact is, you can often accomplish associations
with set, as you can with map. The advantage to set is that you need to store only one
instance for each item, whereas with map, you must have two instances, both a key and
a value. The advantage to map is that you can use the nice bracket notation. The choice
is yours.
CHAPTER UNORDERED CONTAINERS 33
1. Order of Elements
The ordered associative containers store their elements in an ordered fashion. By default,
std::less is used for this ordering, which, unless specialized, relies on operator < of the
Key type.
2. Unordered Containers
It is an efficient data structure storing its elements in buckets. Conceptually, the map
contains an array of pointers to buckets, which are in turn arrays or linked lists of elements.
Through a mathematical formula called hashing , a hash integer number is calculated, which
is then transformed into a bucket index. Two elements resulting in the same bucket index
are stored inside the same bucket.
I. Description
A hash map allows for very fast retrieval of elements. To retrieve an element, calculate its
hash value, which results in the bucket number.
Typically, unordered containers are implemented as hash tables. The position in the
array is determined using the hash function, which given a value returns the position
at which it should be stored.
Ideally, most of the elements will be mapped into different positions, but the hash function
can potentially return the same position for different elements. This is called a collision. This
problem is solved by using linked lists to chain elements that map into the same position, so
that multiple elements can be stored in the same position. Because there might be multiple
elements at the same position, the position is often called bucket.
When an element is added to the set, its hash is computed to decide in which bucket the
element should be added. The elements inside a bucket are stored as nodes of a list.
When a key/value pair is added to the map, the hash of the key is computed to decide
in which bucket the key/value pair should be added:
Figure 5.15: Representation of computing the bucket of an element from the key, and storing
the key/ value pair as nodes in a list.
Unordered associative containers and ordered associative containers provide the same
functionalities, and the explanations in the previous section apply to the unordered
associative containers as well. Unordered associative containers can be used to get
better performances when the order of the elements is not important.
The unordered associative containers allow you to specify your own hasher and your own
definition of how to decide whether two keys are equal by specifying extra template type
parameters . Here are the template definitions for all unordered associative containers:
5. Hash Functions
The Standard provides the following std::hash template (the base template is defined in but
is included also in the headers):
Specializations are provided for several types, such as bool , char , int , long , double ,
and std::string . If you want to calculate a hash of your own object types, you can
implement your own hashing functor class. However, we recommend that you implement a
specialization of std::hash instead.
The following is an example of how you could implement a std::hash specialization for the
Person class defined in the introduction chapter. It uses the standard std::hash
specialization for string objects to calculate the hash of the first and last name. Both hashes
are then combined by a XOR operation (encrypted operations) . Simply XORing values
generally does not give sufficiently randomly distributed integers, but if both operands are
already hashes, it can be considered acceptable:
All unordered associative containers support the same methods as the ordered associative
containers, except reverse iterators, lower_bound() , and upper_bound() . The
following subsections give an overview of all additional operations supported by
unordered_map (UM) , unordered_multimap (UMM), unordered_set (US), and
unordered_multiset (UMS), divided into categories.
5. Using std::unordered_set to create an unordered set
The easiest way to see how an unordered set works is to create one. The UnorderedSet
example, shown in Listing 6-16, demonstrates how to use the various
unordered_set features to maintain a listing of colors.
#include <iostream>
#include <unordered_set>
using namespace std;
int main() {
unordered_set<string> Colors;
Colors.insert("Red");
Colors.insert("Green");
Colors.insert("Blue");
if(Colors.find("Red")!= Colors.end())
cout << "Found Red!" << endl;
auto ReturnValue = Colors.emplace("Red");
if(!ReturnValue.second)
cout << "Red is Already in Set!" << endl;
cout << "There are " << Colors.count("Red")
<< " Red entries." << endl;
ReturnValue = Colors.emplace("Orange");
if(!ReturnValue.second)
cout << "Orange is Already in Set!" << endl;
else
cout << "Orange Added to Set!" << endl;
Colors.erase("Red");
if(Colors.find("Red")!= Colors.end())
cout << "Found Red!" << endl;
else
cout << "Red Missing!" << endl;
return 0;
}
The example begins by creating a new unordered_set, Colors. Notice that this is
a template, so you need to provide a type for the information that the set will hold.
The code uses the insert() function to add three colors to the set.
The find() function enables you to look for a particular value in the set. When the
value is missing, the find() function returns end(), which means that the current
position within the set is at the end.
This example uses the auto data type. ReturnValue is used to detect when a value
that you want to add to the set using emplace() already exists. If the value already
exists, unordered_set refuses to add it when you call emplace(). On the other
hand, if you call insert(), unordered_set will add duplicate entries.
To remove entries from a set, you call erase() with the value you want to remove.
In this case, the example removes the color Red. It then searches for Red using
find().
As you might expect, Red isn’t found this time. The output from this example is as
follows:
1. Found Red!
2. Red is Already in Set!
3. There are 1 Red entries.
4. Orange Added to Set!
5. Red Missing!
You might want to use an unordered_set in a situation in which there is no natural ordering
among the keys and you don’t need to iterate through the collection in such an order. You
might find that in many situations, you could use either a set or an unordered_set.
You could group people by birthday, which would give you 365 groups (well, 366 if you count
February 29 for leap years).
The birthday is like a hash function that returns one of 365 values for each person.
Each value forms a bucket, and all people in the same bucket have the same
birthday.
Then you can search through the bucket to find the person you’re looking for.
As long as the hash function is quick and there aren’t too many elements
per bucket, unordered_sets have even more impressive performance than
their ordered counterparts.
Constructing
1. Key type T
2. The Hash hash function type, which defaults to std::hash<T>
3. The KeyEqual equality function type, which defaults to std::equal_to<T>
4. The Allocator allocator type, which defaults to std::allocator<T>
5. Braced initialization.
1. Container Adaptors
Additional container classes that are provided by the STL library are container adaptors.
Container adaptors provide constrained access policies on top of the containers we
have looked at in this chapter.
The STL provides three container adapters that encapsulate other STL containers
and expose special interfaces for tailored situations. The adapters
are the stack, the queue, and the priority queue.
Container adaptors have a template parameter that the user can provide to specify the
type of container to wrap:
I. Stack
Elements can only be read or removed from the top, so the last inserted element
is the first that gets removed.The access pattern of the stack data structure happens mainly
through three core member functions: push(), top(), and pop(). The push() function is used to
insert an element into the stack, top() used to access the element on top of the stack, and
pop()is used to remove the top element.
the elements are enqueued one after the other, so that elements inserted before are ahead
of elements inserted after. Elements are inserted at the end of the queue and removed at
the start.
The interface of the queue data structure is composed of the push(), front(), back(),
and pop() member functions.
The push() function is used to insert an element into the queue(); front() and back()
return the next and last elements of the queue, respectively; the pop() is used to
remove the next element from the queue.
You can compare a queue to a person who arrived before you will be allowed to enter
before you. A queue needs access to the front and the back, so the underlying container
must support back() , front() , push_back() , and pop_front() .
The standard list and deque support these methods and can be used as underlying
containers. The default container is the deque .
Among STL containers, you can only use deque or list as the underlying
container for a queue, because pushing and popping from the front of a vector.
Stack: You put items on top of a stack one by one — and you take items off
the top of the stack one by one. You can add several items, one after the
other, before taking an item off the top. This process is sometimes called a
Last In, First Out (LIFO) algorithm.
Queue: A queue is like waiting in line at the post office — the line gets longer
as people arrive. Each new person goes to the back of the line. People leave
from the front of the line. Like the stack, the queue also has an alternate
name: it’s a First In, First Out (FIFO) algorithm.
To use the Standard Library to make a stack, you can use a deque, a list, or a
vector as the underlying storage bin.
stack<int> MyStack;
For a queue, you can’t use vector because vectors don’t include operations for
dealing with the front of an item list. So, you can use either deque or list. Here’s
a line of code that uses list:
queue<int> MyQueue;
Push: When you add an item to a stack or queue, you push the item. This
action puts the item on top of the stack or at the back of the queue.
Peek: When you look at the top of the stack or the front of the queue, you
peek. The peek operation doesn’t remove the item.
Pop: When you remove an item from the top of a stack or from the front of
the queue, you pop it off.
To peek at the front of a queue, you call the front() method. For a stack, you call
the top() method.
For pushing and popping, the queue and stack each include a push() function and a
pop() function. The StackAndQueue example, shown in Listing 6-13, demonstrates both a
stack and a queue.
#include <iostream>
#include <stack>
#include <queue>
void StackDemo() {
MyStack.push(5);
MyStack.push(10);
MyStack.push(15);
cout << MyStack.top() << endl;
MyStack.pop();
MyStack.pop();
MyStack.push(40);
MyStack.pop();
}
void QueueDemo() {
cout << "===Queue Demo===" << endl;
queue<int> MyQueue;
MyQueue.push(5);
MyQueue.push(10);
MyQueue.push(15);
MyQueue.pop();
MyQueue.pop();
MyQueue.push(40);
MyQueue.pop();
int main() {
StackDemo();
QueueDemo();
return 0;
When you specify a container to use inside the stack or queue, remember to put a
space between the closing angle brackets.
Otherwise, the compiler reads it as a single insertion operator, >>, and gets confused.
===Stack Demo===
15
10
40
===Queue Demo===
5
10
15
Finally, the priority queue is a queue where the elements are accessed according to
their priority, in descending order (highest priority first).
The interface is similar to the normal queue, where push() inserts a new element and
top() and pop() access and remove the next element. The difference is in the way
the next element is determined. Rather than being the first inserted element, it is the
element that has the highest priority.
The priority queue class is also defined in the <queue> header file (std::priority_queue).
A priority_queue is similar to a queue but stores the elements according to a priority. The
element with highest priority is at the front of the queue. In the case of a night club, VIP
members get higher priority and are allowed to enter before non-VIPs. A priority_queue
needs random access on the underlying container and only needs to be able to modify the
container at the back, not the front. Therefore, the underlying container must support random
access, front() , push_back() , and pop_back() .
The vector and deque are available options, with the vector being the default underlying
container.
A Compare instance can optionally be provided to the priority_queue constructor; if not, one
is default-constructed.
A priority queue (also called a heap) is a data structure that supports push and
pop operations and keeps elements sorted according to some user-specified
comparator object. The comparator object is a function object invokable with
two parameters, returning true if the first argument is less than the second.
When you pop an element from a priority queue, you remove the element
that is greatest, according to the comparator object.
One of the most common discussions you encounter when people start talking
about how to use the container templates is whether to put instances in the containers,
pointers, or references. For example, which of the following should you type?
vector<MyClass>
vector<MyClass *>
vector<MyClass &>
in other words, do you want your container to store the actual instance (whatever
that might mean), a reference to the actual instance, or a pointer to the instance?
To explore this idea, look at the Maps2 example in Listing 6-5. Here, you’re trying
out the different ways of storing things in map: instances, pointers, and references.
#include <iostream>
#include <map>
using namespace std;
class StoreMe {
public:
int Item;
};
bool operator < (const StoreMe & first, const StoreMe & second) {
return first.Item < second.Item;
}
int main() {
instances[key1] = value1;
instances[key2] = value2;
value1.Item = 12345;
pointers[&key10] = &value10;
pointers[&key11] = &value11;
value10.Item = 12345;
cout << (*pointers[&key10]).Item << endl;
return 0;
}
To create the instances of StoreMe, you use the braces notation. You can do that
when you have no constructors. So the line StoreMe key1 = {10}; creates an instance
of StoreMe and puts 10 in the Item property.
To create an individual instance entry, you need both a key and a value instance of
StoreMe. You then use the key to provide a name for the value stored in the map.
Consequently, instances contain two entries consisting of two StoreMe objects each.
Here’s what you see when you run the application:
20
34567
12345
This output doesn’t precisely match expectations because the code changes the
Item property in value1:
value1.Item = 12345;
When the code outputs the value of instances[key1].Item, you see an output of
20, not 12345. That means that the value stored in map is a copy, not the original.
However, when the code changes the value in instances like this:
instances[key1].Item = 34567;
the value portion of the instances entry does change. You see 34567 as output.
Consequently, when working with map instances, you must modify the map entry
directly.
Now that you’ve figured out that map is storing copies of what you put in it, the
idea of storing a pointer should be clear: If you have a pointer variable and then
you make a copy of it, although you have a separate pointer variable, the original
and the copy both point to the same memory location.
That’s the idea behind thesecond part of Listing 6-5. You create pointers like this:
Now this map stores pointer variables. Remember that a pointer variable just holds
a number that represents an address. If two separate pointer variables hold the
same number, it means that they point to the same object at the same address.
Furthermore, because this map is holding pointers, it’s holding numbers, not
instances — something to think about.
To store a value when using pointers, you need to use code like this:
pointers[&key10] = &value10;
Note the use of the ampersand (&) used as a reference operator to store addresses
in map. It’s now possible to change the Item member of one the value objects:
value10.Item = 12345;
that you print using this carefully parenthesized line:
cout << (*pointers[&key10]).Item << endl;
and you see this:
12345
When working with map pointers, you modify the original variable to make a
change because the map entries point to the original variable, rather than make
a copy of it. However, as you can see, using map pointers also makes your code
harder to read.
Don’t worry just now about the bool operator < (const StoreMe & first,
const StoreMe & second)> function.
This function is explained in the “Performing comparisons” section, later in this chapter.
It attempts to declare a map that holds references, but the code generates a compiler
error instead. Try uncommenting the commented line and see the error message.
Here’s an example of what you might see (make sure to add the comment
back in when you’re done):
References are out of the question because the map is making a copy of everything
you put in it.
All C++ containers, not just maps, generally make copies of whatever you stick
inside them as shown in the previous section.
The Vectors3 example, shown in Listing 6-6, replicates the essential functionality of the
Maps2 example shown in Listing 6-5.
#include <iostream>
#include <vector>
using namespace std;
class StoreMe {
public:
int Item;
};
int main() {
vector<StoreMe> instances;
StoreMe value1 = {20};
StoreMe value2 = {40};
instances.push_back(value1);
instances.push_back(value2);
value1.Item = 12345;
cout << instances[0].Item << endl;
instances[0].Item = 34567;
cout << instances[0].Item << endl;
vector<StoreMe*> pointers;
StoreMe value10 = {20};
StoreMe value11 = {40};
pointers.push_back(& value10);
pointers.push_back(& value11);
value10.Item = 12345;
cout << (*pointers[0]).Item << endl;
return 0;
}
Oddly enough, the output from this example is precisely the same as the Maps2
example and for the same reason. Whether the container is a vector or a map
doesn’t matter; both of them hold copies of the objects or pointers you provide.
Consequently, you can remember these two rules about deleting your original
objects:
It’s up to you to decide which method is better. But here are a couple of things to
consider:
»»Keeping instances around: If you don’t want to keep instances lying around,
you can put the instances in the container, and it will make copies.
»»Copy Ability: Some classes, such as classes filled with pointers to other classes
or classes that are enormous, don’t copy well. In that case, you may want to
put pointers in the container.
3. Comparing instances
When you work with classes that contain other classes (such as vector), you need
to provide the class with a way to compare two things. The following sections
describe how to provide comparison capability when working with containers.
Here’s an example;
1. FirstName
2. LastName
3. SocialSecurityNumber.
Next, you create a Salary class that contains payroll information for an employee. This
class has properties MonthlySalary and Deductions.
With these two objects in place, you create a map instance, where each key/value
pair contains an Employee instance for the key and a Salary instance for the
value.
To look up an employee, you would make an instance of Employee and fill in the
FirstName, LastName, and SocialSecurityNumber properties.
You’d create an instance and allow map to find the key that matches the
instance. It’s essential to know whether map is looking for the exact same
instance or one identical to it. When looking for the exact same instance, you
need a pointer to the original object, not a new object that you fill in
I. Performing comparisons
The previous section provides details on two issues you must resolve when comparing
objects. Here’s how to resolve these two issues: If you’re dealing with your
own classes, in addition to setting up a container class, you also provide a function
that compares two instances of your own class. Your comparison function can
determine whether two classes are equal, the first is less than the second, or the first is
greater than the second.
At first, how less than and greater than can apply to things like an Employee
class may not seem apparent. But the idea behind less than and greater than is to
give the container class a way to determine a sort order. For example, you might
choose to sort an Employee class in one of these ways:
If you want the list to sort by name, you would make your function
look strictly at the names. But if you want your list to sort by Social Security
number, you would write your function to compare the Social Security numbers.
The Maps3 example, shown in Listing 6-7, contains a map class with a comparison
function that determines whether two keys are equal.
#include <iostream>
#include <map>
using namespace std;
class Emp {
public:
string Nickname;
string SSN;
Emp(string anickname, string asocial) :
Nickname(anickname),SSN(asocial) {}
};
class Salary {
public:
int YearlyInc;
int Taxes;
Salary(int aannual, int adeductions) :
YearlyInc(aannual), Taxes(adeductions) {}
};
bool operator < (const Emp& first, const Emp& second) {
return first.Nickname < second.Nickname;
int main() {
return 0;
}
When you run this application, you see the YearlyInc member of the Salary value,
where the key is an Employee with the name sparky:
135000
Now notice a couple things about this code. First, to locate the salary for Sparky,
you don’t need the Employee instance for Sparky.
Instead, you create an instance of Employee and set up the Nickname member without
worrying about the SSN member.
Then you retrieve the value by using the bracket notation for map:
The map code uses the less-than function to perform this task. The function
compares only the Nickname members, not the SSN member.
1. Unconventional Containers
Up until now, we've seen containers that are used to store groups of elements of the
same type.
The C++ standard defines some other types that can contain types but offer a different
set of functionalities from the containers we saw previously.
I. String
II. Pair and tuple
III. Optional
IV. Variant
I. Strings
All types and functions for strings are defined in the <string> header file.
Let's examine the following code to understand how the c_str() function works:
#include <iostream>
#include <string>
2. Now, in the main function add a constant char variable named charString with
capacity as 8 characters:
int main() {
const char charString[8] = {'C', '+', '+', ' ', '1', '0', '1', '\
0'};
3. Use the c_str() function and assign the value of strString to charString2:
Output:
C++ 101
C++ Fundamentals
As for vectors, strings have size(), empty(), and capacity() member functions, but
there is an additional function called length(), which is just an alias for size().
The usual comparison operators are provided for strings, thus simplifying the way two
string objects can be compared.
Since strings are like vectors, we can add and remove characters from them.
Strings can be made empty by assigning an empty string, by calling the clear(), or
erase() functions.
Let's look at the following code to understand the usage of the clear() and erase()
functions:
#include <iostream>
#include <string>
int main() {
std::string str = "C++ Fundamentals.";
std::cout << str << std::endl;
str.erase(5,10);
std::cout << "Erased: " << str << std::endl;
str.clear();
std::cout << "Cleared: " << str << std::endl;
}
Output:
C++ Fundamentals.
Erased: C++ Fs.
Cleared:
C++ also provides many convenience functions to convert a string into numeric values
or vice versa. For example, the stoi() and stod() functions (which stand for stringto-
int and string-to-double) are used to convert string to int and double, respectively.
Instead, to convert a value into a string, it is possible to use the overloaded function
to_string().
#include <iostream>
#include <string>
using namespace std;
int main() {
Output:
55
55
55
55
The pair and tuple classes are similar to some extent, in the way they can store a
collection of heterogeneous elements.
The pair class can store the values of two types, while the tuple class extended this
concept to any length.
Pair is defined in the <utility> header, while tuple is in the <tuple> header.
The pair constructor takes two types as template parameters, used to specify the types
for the first and second values. Those elements are accessed directly using the first
and second data. Equivalently, these members can be accessed with the get<0>() and
get<1>() functions.
The make_pair() convenience function is used to create a value pair without explicitly
specifying the types:
std::cout << "Name: " << std::get<0>(nameAndAge) << ", age: " <<
std::get<1>(nameAndAge) << std::endl;
Pairs are used by unordered map, unordered multimap, map, and multimap
containers to manage their key/value elements.
Tuples are similar to pairs. The constructor allows you to provide a variable number
of template arguments. Elements are accessed with the get<N>() function only, which
returns the nth element inside the tuple, and there is a convenience function to create
them similar to that for pair, named make_tuple().
Additionally, tuples have another convenience function that's used to extract values
from them. The tie() function allows for the creation of a tuple of references, which is
useful in assigning selected elements from a tuple to specific variables.
III. std::optional
Let's understand how to use the make_tuple() and get() functions to retrieve data from
a tuple:
#include <iostream>
#include <tuple>
#include <string>
int main() {
Output:
std::optional
Let's imagine our application is using a class named User for managing registered users.
We would like to have a function that gets us the information of a user from their email:
But what happens when a user is not registered? That is, when we can determine that
our system does not have the associated User instance?
Some would suggest throwing an exception. In C++, exceptions are used for exceptional
situations, ones that should almost never happen. A user not being registered on our
website is a perfectly normal situation.
In these situations, we can use the optional template class to represent the fact that
wemight not have the data:
• has_value(): This returns true if optional is currently holding a value, and false if
the variant is empty.
• value(): This function returns the value currently held by optional, or throws an
exception if it's not present.
• Additionally, optional can be used as a condition in an if statement: it will evaluate
to true if it contains a value, or false otherwise.
Let's look at the following example to understand how the has_value() and value()
functions work:
#include <iostream>
#include <optional>
int main() {
// We might not know the hour. But if we know it, it's an integer
std::optional<int> currentHour;
if (not currentHour.has_value()) {
std::cout << "We don't know the time" << std::endl;
currentHour = 18;
if (currentHour) {
}
}
Output:
The optional template comes with additional convenience features. We can assign the
std::nullopt value to optional to make it explicit when we want it empty, and we can
use the make_optional value to create an optional from a value. Additionally, we can
use the dereference operator, *, to access the value of optional without throwing an
exception if the value is not present. In such cases, we will access invalid data, so we
need to be sure that optional contains a value when we use *:
if (not maybeUser) {
std::cout << "The user is not present" << std::endl;
maybeUser = std::make_optional<std::string>("[email protected]");
if (maybeUser) {
std::cout << "The user is: " << *maybeUser << std::endl;
#include <iostream>
#include <optional>
int main(){
std::optional<int> x;
std::cout << x.value_or(10) << std::endl;
x = 15;
std::cout << x.value_or(10)<< std::endl;
Output:
10
15
Let's recall our User class that's composed of an email address, a phone number, and
a physical address. Sometimes, users don't have a phone number and don't want to
provide a physical address, so the only required field we have in User is the email
address:
This constructor allows us to pass in all the information we have on the user. If,
instead of using optional, we used multiple overloads, we would have had four
overloads:
1. Only email
2. Email and phone number
3. Email and address
4. Email with phone number and address
You can see that the number of overloads grows quickly when there are more
arguments that we might not want to pass.
IV. std::variant
variant is a value type that's used to represent a choice of types. The class takes a list of
types, and the variant will be able to contain one value of any of those types.
It is often referred to as tagged union, because similar to a union, it can store multiple
types, with only one present at a time. It also keeps track of which type is currently
stored.
During the execution of a program, variant will contain exactly one of the possible
types at a time.
Like optional, variant is a value type: when we create a copy of variant, the element
that is currently stored is copied into the new variant
To interact with std::variant, the C++ standard library gives us two main functions:
get<Type>(variant) gets the value of the type that's currently stored inside
the variant. Before calling this function, the caller needs to be sure that holds_
alternative<Type>(variant) returns true.
get<Index>(variant) gets the value of the index type that's currently stored inside
variant. Like before, the caller needs to be sure that variant is holding the correct type.
Let's perform the following steps to understand how to use variant in the program:
#include <iostream>
#include <variant>
2. In the main function, add the variant with the value type as string and integer:
int main()
{
std::variant<std::string, int> variant = 42;
3. Now using the two print statements call the variant in different ways:
Output:
42
42
Let's perform the following steps to understand how to use std::visit(visitor, variant) in
the program:
struct Visitor {
};
3. Now, in the main function, call the struct Visitor and pass values as illustrated:
int main() {
.
Let's imagine we have struct UserRegistrationForm, which contains the information
that's needed to let the user register.
Additionally, what should we do when there is an error? With variant, we could have
struct GetUserError storing all the information we have so that our application will
be able to recover from the error and add it to the return type: std::variant<User,
UserRegistrationForm, GetUserError>, or tryGetUserByEmail().
getUserByEmail() by just looking at the function signature, and the compiler will help us
make sure that we handle all the cases.
Alternatively, variant can also be used to represent the various states in which a class
can be. Each state contains the data that's required for that state, and the class only
manages the transitions from one state to another.
2. With at_gate, the airplane stores the gate number at which it is. With taxi, we
store which lane the airplane is assigned and how many passengers are on board.
With flying, we store the speed:
struct AtGate {
int gate;
};
struct Taxi {
int lane;
int numPassengers;
};
struct Flying {
float speed;
};
• startTaxi(): This method takes the lane the airplane should go on and the
number of passengers on board. The airplane can start taxi only if it is at the
gate.
• takeOff(): This method takes the speed at which the airplane should fly. The
airplane can start flying only if it is in the taxi state.
—---------------------------------------------------------
1. ALGORITHMS
Note
Algorithms operate on ranges, so they normally take a pair of iterators: first and
last.
2. Read Only Algorithms
Read-only algorithms are algorithms that inspect the elements stored inside a container
but do not modify the order of the elements of the container.
I. Checking for the Presence of Elements
Returns true if all, none, or respectively at least one of the elements in the range [first, last)
satisfies a unary predicate . If the range is empty, all_of() and none_of() return true ,
and any_of() returns false .
Returns the number of elements in [first, last] that are equal to a given value , or that satisfy
a unary predicate . [Alternatives: all ordered and unordered associative containers have a
count() member.]
II. Example :
#include <iostream>
#include <vector>
#include <algorithm>
int main()
{
std::vector<int> vector = {1, 2, 3, 4};
bool allLessThen10 = std::all_of(vector.begin(), vector.end(), []
(int value) { return value < 10; });
std::cout << "All are less than 10: " << allLessThen10 << std::endl;
Output:
3. Modifying Algorithms
Modifying algorithms are algorithms that modify the collections they iterate on:
I. Example:
The following example uses transform() to double all the elements in a vector using a
lambda expression, then uses transform() to negate the elements using a standard
function object, and finally outputs all the elements to the console using for_each() . This
code snippet additionally needs :
II. Copy, Move, Swap
III. Removing and Replacing
Let's see these algorithms in action:
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main()
{
std::vector<std::string> vector = {"Hello", "C++", "Morning",
"Learning"};
std::vector<std::string> longWords;
std::copy_if(vector.begin(), vector.end(),
std::back_inserter(longWords),
[](const std::string& s) { return s.length() > 3; });
std::cout << "Number of longWords: " << longWords.size() <<
std::endl;
std::vector<int> lengths;
std::transform(longWords.begin(), longWords.end(), std::back_
inserter(lengths), [](const std::string& s) { return s.length(); });
Output:
Number of longWords: 3
Lengths: 5 7 8
3. Mutating Algorithms
Mutating algorithms are algorithms that change the order of elements:
Let's see how we can use them:
#include <iostream>
#include <random>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
Output:
Values: 5 2 6 4 3 1
4. Sorting Algorithms
#include <iostream>
#include <vector>
#include <algorithm>
int main(){
std::sort(vector.begin(), vector.end());
Output:
Values: 1 2 3 4 5 6
Output:
Found: 1
5. Numeric Algorithms
This class of algorithms combines numeric elements using a linear operation in
different ways:
Let's see how we can use accumulate in the following program:
#include <iostream>
#include <vector>
#include <algorithm>
int main(){
Output:
Margin: 4
We have the information of many customers of our application and we want to compute
analytics data on that.
Given a map that has a username as a key and a user account as a value, we would like
to print the balances of the new users in descending order.
A user is considered new if they registered no more than 15 days ago. The struct
representing the user's account is provided and is as follows:
struct UserAccount {
int balance;
int daysSinceRegistered;
};
1. Make sure to include all the required headers for the solution:
#include <iostream>
#include <vector>
#include <iterator>
#include <map>
#include <algorithm>
2. First, we need to extract UserAccount from the map. Remember that the element
map stores a pair containing a key and value. Since we need to transform the
type into UserAccount, we can use std::transform, by passing a lambda that only
returns the user account from the pair. To insert this into vector, we can use
std::back_inserter. Make sure to use a const reference when accepting pair in
the lambda that's passed to transform:
std::vector<UserAccount> newAccounts;
std::transform(accounts.begin(), accounts.end(),
std::back_inserter(newAccounts), [](const std::pair<std::string,
UserAccount>& user) {
3. After we have extracted the accounts in vector, we can use remove_if to remove all
accounts that are older than 15 days:
4. After removing the old accounts, we need to sort the balances in descending
order. By default, std::sort uses an ascending order, so we need to provide a
lambda to change the order:
}
}
5. We can now invoke our function with the following test data:
int main() {
};
computeAnalytics(users);
We divided the most common algorithms into various categories, and we looked at the
most important algorithms in those categories, including find, remove, and sort.
In the next chapter, you will learn how to use the advanced features of C++ to create
dynamic programs.
chapter
1. Streams
output (I/O). Regardless of the source or destination, you can use streams
as the common language to connect inputs to outputs. The STL uses class
inheritance to encode the relationships between various stream types.
All three stream types require two template parameters. The first corresponds
to the stream’s underlying data type and the second to a traits type.
This section covers streams from a user’s perspective rather than from
a library implementer’s perspective.
All STL stream classes that users interact with derive from basic_istream,
basic_ostream, or both via basic_iostream. The headers that declare each type
also provide char and wchar_t specializations for those templates, as outlined
in Table 16-1. These heavily used specializations are particularly useful when
you’re working with human-language data input and output.
The objects in Table 16-1 are abstractions that you can use in your
programs to write generic code. Do you want to write a function that logs
output to an arbitrary source? If so, you can accept an ostream reference
parameter and not deal with all the nasty implementation details.
Often, you’ll want to perform I/O with the user (or the program’s
environment). Global stream objects provide a convenient, stream-based
wrapper for you to work against.
The STL provides several global stream objects in the <iostream> header that
wrap the input, output, and error streams stdin, stdout, and stderr. These
implementation-defined standard streams are preconnected channels between your program
and its executing environment. For example, in a desktop environment, stdin typically
binds to the keyboard and stdout and stderr bind to the console.
Table 16-2 lists the global stream objects, all of which reside in the std
namespace.
So how do you use these objects? Well, stream classes support operations
that you can partition into two categories:
a) Formatted operations Might perform some preprocessing on
their input parameters before performing I/O
a) Formatted Operations
All formatted I/O passes through two functions: the standard stream operators,
operator<< and operator>>.
Listing 16-1 illustrates how to use the output operator to write various
types into cout.
#include <iostream>
#include <string>
#include <bitset>
int main() {
bitset<8> s{ "01110011" };
string str("Crying zeros and I'm hearing ");
size_t num{ 111 };
cout << s;
cout << '\n';
cout << str;
cout << num;
cout << "s\n";
Output :
01110011
Crying zeros and I'm hearing 111s
One very nice feature of the standard stream operators is that they generally
return a reference to the stream. Conceptually, overloads are typically
defined along the following lines:
This means you can chain output operators together. Using this
technique, you can refactor Listing 16-1 so cout appears only once, as
Listing 16-2 illustrates.
#include <iostream>
#include <string>
#include <bitset>
int main() {
bitset<8> s{ "01110011" };
string str("Crying zeros and I'm hearing ");
size_t num{ 111 };
cout << s << '\n' << str << num << "s\n"; u
}
01110011
Crying zeros and I'm hearing 111
Listing 16-3 illustrates how to use the input operator to read two double
objects and a string from cin, then print the implied mathematical operation’s
result to stdout.
#include <iostream>
#include <string>
using namespace std;
int main() {
double x, y;
cout << "X: ";
cin >> x; u
cout << "Y: ";
cin >> y; v
string op;
cout << "Operation: ";
cin >> op; w
if (op == "+") {
cout << x + y; x
} else if (op == "-") {
cout << x - y; y
} else if (op == "*") {
cout << x * y; z
} else if (op == "/") {
cout << x / y; {
} else {
cout << "Unknown operation " << op; |
}
}
To use the program, you type the requested values into the console
when directed. A newline will send the input (as stdin) to cin, as Listing 16-4
illustrates.
X: 3959
Y: 6.283185
Operation: *
24875.1
The result is Earth’s circumference in miles x. Note that you don’t need to provide a decimal
point for an integral value ; the stream is smart enough to know that
there’s an implicit decimal.
You might wonder what happens in Listing 16-4 if you input a non-numeric string
for X or Y . In an error state, the stream ceases to accept input, and the program won’t
accept any more input.
b. Unformatted Operations
When you’re working with text-based streams, you’ll usually want to use
formatted operators; however, if you’re working with binary data or if you’re
writing code that needs low-level access to streams, you’ll want to know
about the unformatted operations.
The istream class has many unformatted input methods. These methods
manipulate streams at the byte level and are summarized in Table 16-3. In
this table, is is of type std::istream <T>, s is a char*, n is a stream size, pos is a
position type, and d is a delimiter of type T.
All fundamental types, in addition to void and nullptr, have input and output
operator overloads, but some have special rules:
char and wchar_t The input operator skips whitespace when assigning
character types.
char* and wchar_t* The input operator first skips whitespace and then
reads the string until it encounters another whitespace or an end-of-file
(EOF). You must reserve enough space for the input.
Bool The input and output operators treat Boolean values as numbers:
1 for true and 0 for false.
Numeric types The input operator requires that input begin with at
least one digit. Badly formed input numbers yield a zero-valued result.
These rules might seem a bit strange at first, but they’re fairly straightforward
once you get used to them.
Avoid reading into C-style strings, because it’s up to you to ensure that you’ve allocated
enough space for the input data. Failure to perform adequate checking results
in undefined behavior and possibly major security vulnerabilities. Use std::string
instead.
V. Stream State
A stream’s state indicates whether I/O failed. Each stream type exposes the
constant static members referred to collectively as its bits, which indicate
a possible stream state: goodbit, badbit, eofbit, and failbit.
To determine whether a stream is in a particular state, you invoke member functions that
return a bool indicating whether the stream is in the corresponding state.
Table 16-5 lists these member functions, the stream state corresponding to
a true result, and the state’s meaning.
To reset a stream’s status to indicate a good working state, you can invoke its clear()
method.
Listing 16-5 illustrates a simple program that uses this technique to generate
word counts from stdin
#include <iostream>
#include <string>
int main() {
std::string word;
size_t count{};
while (std::cin >> word)
count++;
std::cout << "Discovered " << count << " words.\n";
Chapter Manipulators
1. Manipulators
Manipulators are special objects that modify how streams interpret input or
format output. Manipulators exist to perform many kinds of stream alterations.
For example, std::ws modifies an istream to skip over whitespace. Here
are some other manipulators that work on ostreams:
For example, you could replace x in Listing 16-7 with the following:
cout << "Discovered " << count << " words." << endl;
NOTE
As a general rule, use std::endl when your program has finished outputting text
to the stream for a while and \n when you know your program will output more
text soon.
You can also set a stream’s width parameter using the setw manipulator.
A stream’s width parameter has varied effects, depending on the stream.
For example, with std::cout, setw will fix the number of output characters
allocated to the next output object. Additionally, for floating-point output,
setprecision will set the following numbers’ precision.
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
cout << "Gotham needs its " << boolalpha << true << " hero.";
cout << "\nMark it " << noboolalpha << false << "!";
cout << "\nThere are " << 69 << "," << oct << 105 << " leaves in
here."; w
cout << "\nYabba " << hex << 3669732608 << "!";
cout << "\nAvogadro's number: " << scientific << 6.0221415e-23;
cout << "\nthe Hogwarts platform: " << fixed << setprecision(2) <<
9.750123;
Output :
Additionally, if you want a string stream that supports input and output
operations, you can use the basic_stringstream, which has the following
specializations:
The file stream classes provide facilities for reading from and writing to
character sequences. The file stream class structure follows that of the
string stream classes. File stream class templates are available for input,
output, and both.
You have two options for opening a file with any file stream. The first option
is the open method, which accepts a const char* filename and an optional
std::ios_base::openmode bitmask argument. The openmode argument can be
one of the many possible combinations of values listed in Table 16-9.
a. Put the file in binary mode.
In binary mode, the stream won’t convert special character sequences, like end of line (for
example, a carriage return plus a line feed on Windows) or EOF.
The second option for specifying a file to open is to use the stream’s
constructor. Each file stream provides a constructor taking the same arguments
as the open method. All file stream classes are RAII wrappers around
the file handles they own, so the files will be automatically cleaned up
when the file stream destructs. You can also manually invoke the close
method, which takes no arguments. You might want to do this if you know
you’re done with the file but your code is written in such a way that the file
stream class object won’t destruct for a while.
File streams also have default constructors, which don’t open any files.
To check whether a file is open, invoke the is_open method, which takes no
arguments and returns a Boolean.
Output file streams provide output stream semantics for character sequences,
and they all derive from the class template std::basic_ofstream in the <fstream>
header, which provides the following specializations:
The default basic_ofstream constructor doesn’t open a file, and the nondefault
constructor’s second optional argument defaults to ios::out.
Whenever you send input to the file stream, the stream writes the data
to the corresponding file. Listing 16-14 illustrates how to use ofstream to
write a simple message to a text file.
#include <fstream>
int main() {
file << "Lunch time, " << 2 << "x so." << endl;
lunchtime.txt:
Time is an illusion.
Lunch time, 2x so.
Because this combination of flags appends output, any data you send through the output
operator into this file stream gets appended to the end of the file. As expected, the file
contains the message you passed to the output operator .
Thanks to the ios::app flag, the program will append output to lunchtime.txt if it
exists. For example, if you run the program again, you’ll get the following:
output:
Time is an illusion.
Lunch time, 2x so.
Time is an illusion.
Lunch time, 2x so.
The second iteration of the program added the same phrase to the end
of the file.
The default basic_ifstream constructor doesn’t open a file, and the nondefault
constructor’s second optional argument defaults to ios::in.
Whenever you read from the file stream, the stream reads data from
the corresponding file. Consider the following sample file, numbers.txt:
-54
203
9000
0
99
-789
400
Listing 16-15 contains a program that uses an ifstream to read from a text
file containing integers and return the maximum. The output corresponds
with invoking the program and passing the path of the file numbers.txt.
#include <iostream>
#include <fstream>
#include <limits>
int main() {
int value;
cout << "Maximum found was " << maximum << endl; }
output :
Maximum found was 9000
You first initialize an istream to open the numbers.txt text file . Next,
you initialize the maximum variable with the minimum value an int can
take .
Using the idiomatic input stream and while-loop combination ,you cycle through each integer
in the file, updating the maximum as you find higher values x. Once the file stream cannot
parse any more integers, you print the result to stdout y.
Handling Failure
As with other streams, file streams fail silently. If you use a file stream constructor
to open a file, you must check the is_open method to determine
whether the stream successfully opened the file. This design differs from
most other stdlib objects where invariants are enforced by exceptions. It’s
hard to say why the library implementors chose this approach, but the fact
is that you can opt into an exception-based approach fairly easily.
You can make your own factory functions to handle file-opening failures
with exceptions. Listing 16-16 illustrates how to implement an ifstream factory
called open.
#include <fstream>
#include <string>
using namespace std;
if(!file.is_open()) {
err.append(path);
file.exceptions(ifstream::badbit);
return file; {
}
Your factory function returns an ifstream and accepts the same arguments
as a file stream’s constructor (and open method): a file path and an
openmode . You pass these two arguments into the constructor of ifstream
and then determine whether the file opened successfully y. If it didn’t, you
throw a runtime_error . If it did, you tell the resulting ifstream to throw an
exception whenever its badbit gets set in the future {.
}
Referencias