While C++ supports all the loops that C supports (for, while and do-while), C++ provides another loop called a range-based for loop.
The idea of the range-based for loop is to loop through a collection of elements and be able to easily access each element without having to worry about the length of the collection or incrementing and decrementing the loop counter. The syntax is as follows:
for (var_type var_name: collection) {
statement_1;
statement_2;
…
statement_n;
}
// Or
for (var_type var_name: collection)
statement_1;
Here, the var_type is essentially the data type of the elements in the collection. This loop is entry controlled and the minimum number of iterations in this loop is 0 when the collection is empty. With each iteration, the values in the collection get assigned to var_name and this can be used inside of the loop.
Example:
#include <iostream>
using namespace std;
int main() {
int num[] = {1, 2, 3, 4};
for (int i: num)
cout << i << " ";
return 0;
}
Output: 1 2 3 4
Use of auto keyword
We do not have to explicitly provide the type of the variable in the range-based for loop. Instead, we can use the auto keyword. This tells the C++ compiler to deduce the type of the collection elements itself. Consider the following example:
#include <iostream>
using namespace std;
int main() {
int num[] = {1, 2, 3, 4};
for (auto i: num)
cout << i << " ";
return 0;
}
Output: 1 2 3 4
Here the compiler figures out that the variable i has to iterate through a collection of integer elements and hence implicitly considers the data type of i as int.
Iterate through the initialiser list
The range-based for loop doesn’t mandate the collection to be strictly a variable. We can use the loop to iterate through the initialiser list too. This is demonstrated in the following examples:
Example 1
#include <iostream>
using namespace std;
int main() {
for (auto i: {1, 2, 3, 4})
cout << i << " ";
return 0;
}
Output: 1 2 3 4
Example 2
#include <iostream>
using namespace std;
int main() {
for (auto i: "String")
cout << i << " ";
return 0;
}
Despite having classes like vectors in C++, the knowledge of dynamic memory allocation is necessary as the vectors use this in the background and abstract these details to the programmer. Relatively, C++ dynamic memory allocation is simpler than C. As opposed to C’s 4 memory allocation functions (malloc(), calloc(), realloc() and free()), C++ has only 2 keywords (new and delete) to deal with dynamic memory allocation.
Memory handling using new and delete
The new keyword in C++ is conceptually equivalent to calloc() function in C. It allocates the memory from the heap and returns a pointer to the starting of the newly allocated memory.
The delete keyword can be used to deallocate the allocated memory in the heap by the new keyword. It is always recommended to deallocate the memory after use. If not done, memory leaks happen and eventually the application crashes.
For a single entity
The syntax for allocation:
<data_type> *variable_name = new <data_type>;
// Where the data_type is user-defined or C++ built-in.
Explanation:
This allocates a single block of memory in heap with a size equivalent to storing one element of the given data_type.
The data initialised by default is garbage in relatively older compilers. Hence it is always safer to initialise data manually.
The syntax for deallocation:
delete variable_name;
Example
#include <iostream>
#include <string>
using namespace std;
int main() {
int *ptr_val = new int;
cout << ptr_val;
delete ptr_val;
return 0;
}
Output: 0x562f303f7eb0
Allocate storage for an array
The syntax for allocation:
<data_type> *variable_name = new <data_type>[size];
Where,
The data_type is user-defined or C++ built-in.
The size is the number of locations to be allocated.
Explanation:
This allocates a series of size number of contiguous blocks of memory in the heap.
The data initialised by default is garbage in relatively older compilers. Hence it is always safer to initialise data manually.
The syntax for deallocation:
delete [] variable_name;
Example
#include <iostream>
#include <string>
const size_t size = 4;
using namespace std;
int main() {
int *ptr_val = new int[size];
int val = 1;
for (size_t i = 0 ; i < 4 ; i++)
ptr_val[i] = val++;
for (size_t i = 0 ; i < 4 ; i++)
cout << ptr_val[i] << " ";
delete [] ptr_val;
return 0;
}
The vectors are sequence containers to store data of a similar type. Vectors represent arrays that can change in size. Just like arrays, vectors use contiguous storage locations for their elements, which means that their elements can also be accessed using offsets on regular pointers to their elements, and just as efficiently as in arrays. But unlike arrays, their size can change dynamically, with their storage being handled automatically by the container.
Internally, vectors use a dynamically allocated array to store their elements. This array may need to be reallocated in order to grow in size when new elements are inserted, which implies allocating a new array and moving all elements to it. This is a relatively expensive task in terms of processing time, and thus, vectors do not reallocate each time an element is added to the container. Instead, vector containers may allocate some extra storage to accommodate for possible growth, and thus the container may have an actual capacity greater than the storage strictly needed to contain its elements (i.e., its size).
Libraries can implement different strategies for growth to balance between memory usage and reallocations, but in any case, reallocations should only happen at logarithmically growing intervals of size so that the insertion of individual elements at the end of the vector can be provided with amortized constant time complexity.
Therefore, compared to arrays, vectors consume more memory in exchange for the ability to manage storage and grow dynamically in an efficient way.
Declaration
To use a vector in a C++ program, one needs to include the vector library:
#include <vector>
The syntax of the declaration of a vector is as follows:
vector <data_type> vector_var;
Where, data_type can be any valid primary, secondary or user-defined data type permitted by C++. The data type can be a vector itself.
Example:
vector <int> whole_numbers;
The above example just declares the vector but the size of the vector is 0. The size of the vector can also be mentioned at the time of declaration. The syntax for the same is as follows:
vector <data_type> vector_var (size);
Where size should be strictly an unsigned integer.
Example:
vector <int> whole_numbers (10);
The advantage of mentioning the size at the time of declaration is that it is efficient to allocate memory at compile time and also, all the 10 values of the vector whole_numbers in the above declaration will be initialised to 0. The vector library provides a method size() which returns the number of values the vector object holds.
Example 1
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector <int> whole_numbers;
cout << "The size of the vector 'whole_numbers' is "
<< whole_numbers.size();
return 0;
}
Output: The size of the vector ‘whole_numbers’ is 0
Conclusion: If the size is not mentioned, the default size is 0
Example 2
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector <int> whole_numbers (10);
cout << "The size of the vector 'whole_numbers' is "
<< whole_numbers.size();
return 0;
}
Output: The size of the vector ‘whole_numbers’ is 10
Conclusion: The vector gets declared and the mentioned size is allocated to the vector.
Example 3
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector <int> whole_numbers (10);
cout << "The size of the vector 'whole_numbers' is "
<< whole_numbers.size() << endl;
cout << "The elements in the vector are: ";
for (size_t i = 0 ; i < whole_numbers.size() ; i++)
cout << whole_numbers[i] << " ";
return 0;
}
Output:
The size of the vector 'whole_numbers' is 10
The elements in the vector are: 0 0 0 0 0 0 0 0 0 0
Conclusion: If just the size is mentioned, all the values in the vector will be initialized to 0
Initialising vectors
Using initializer lists
When the values to be present in the vector are known at the time of declaration, the following syntax can be used:
Where the values being initialised are strictly of the type data_type.
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector <char> vowels = {'a', 'e', 'i', 'o', 'u'};
for (size_t i = 0 ; i < vowels.size() ; i++)
cout << vowels[i] << " ";
return 0;
}
Output: a e i o u
Conclusion: Vector initialisation using initialiser lists
Using constructor of vector container
When the value to be present in all n indices of the vector is known, one can use the following syntax:
vector <data_type> vector_var (len, val);
The above syntax declares a vector named vector_var of size len and all the indices will be initialized to val.
Example:
vector <int> max_marks (10, 50);
The above syntax declares a vector named max_marks of size 10 and initializes all 10 indices to the value 50.
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector <int> whole_numbers (10, 10);
cout << "The size of the vector 'whole_numbers' is "
<< whole_numbers.size() << endl;
cout << "The elements in the vector are: ";
for (size_t i = 0 ; i < whole_numbers.size() ; i++)
cout << whole_numbers[i] << " ";
return 0;
}
Output:
The size of the vector 'whole_numbers' is 10
The elements in the vector are: 10 10 10 10 10 10 10 10 10 10
Conclusion: If the size and the value is mentioned, all the values in the vector will be initialized accordingly.
Using another vector
A new vector can be initialised with another vector. All the values of the source vector will be deep copied to the newly declared vector. The following is the syntax:
vector <data_type> vector_var (src_vector);
Where the src_vector strictly contains the values of the same data_type as that of the new vector. Any changes made to vector_var are local to vector_var and don’t reflect in src_vector.
Accessing values of a vector
Using subscripts – [ ]
Accessing vector elements could be done similar to accessing array elements – through subscripts. The syntax to do so is as follows:
vector_var[index];
Where the index is strictly unsigned integer which should be between 0 and size - 1 inclusive.
Array accessing using the subscripts provides no bound checking. That is, the programmer can mention an index value greater than size - 1. This causes the return value to be garbage.
Conclusion: The vector element access using subscript notation doesn’t provide bounds checking.
Using at() method
The vector library provides at() method for vector objects to fetch/modify the values in the vector object. The syntax for the same is as follows:
vector_var.at(index);
This behaves in a similar way as that of the subscript access but at() method provides bounds checking. That is, the value of the index should be strictly between 0 and size - 1. Else, the method throws an exception.
Error:
terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check: __n (which is 11) >= this->size() (which is 10)
Conclusion: at() method provides bound checking.
Methods of vector class
C++ provides a rich library of methods to use with vectors. They are listed and described in the below table. For an example, consider a vector named num.
Method
Description
Return type
Parameters
size
Return size of the vector
size_t
None
max_size
Return the maximum size which the vector is capable of holding
size_t
None
resize
Change size
void
(int new_size, int initialisation_value) initialisation_value is optional and by default is 0
capacity
Return size of allocated storage capacity
size_t
None
empty
Test whether the vector is empty
boolean
None
reserve
Requests that the vector capacity is at least enough to contain ‘n’ elements.
void
n
shrink_to_fit
Requests the container to reduce its capacity to fit its size.
void
None
at
Access element at the specified index
The data type of the vector elements
Index value between 0 and size – 1 inclusive
front
Access first element
The data type of the vector elements
None
back
Access last element
The data type of the vector elements
None
data
Returns a direct pointer to the memory array used internally by the vector to store its owned elements.
Pointer to vector element data type
None
assign
Assigns new contents to the vector, replacing its current contents, and modifying its size accordingly.
void
size_t n: the new size of the vector value: to fill the vector with
push_back
Add element at the end
void
Element of same data type as that of the vector
pop_back
Delete the last element
void
None
insert
Insert elements
An iterator that points to the first of the newly inserted elements.
Iterator position and value to be inserted
erase
Removes from the vector either a single element (position) or a range of elements ([first_iterator, last_iterator))
An iterator pointing to the new location of the element that followed the last element erased by the function call.
Iterator to the element to be deleted or 2 iterators: one to the beginning and the other to the end
swap
Exchanges the content of the container by the content of x, which is another vector object of the same type. Sizes may differ.
void
Reference to another vector x
clear
Removes all elements from the vector (which are destroyed), leaving the container with a size of 0.
void
none
emplace
Construct and insert element
Iterator to the new element inserted
Iterator position to insert the new value and the new value
emplace_back
Construct and insert an element at the end
void
The new value to insert
Vector class methods
2D Vectors
It is possible to create multi-dimensional vectors in C++. The syntax of the declaration of a 2D vector is as follows:
vector <vector <data_type>> vector_var;
Exercises
Eleven integer values are entered from the keyboard. The first 10 are to be stored in a vector. Search for the 11th integer in the vector. Write a program to display the number of times it appears in the vector.
Read the size of the vector and declare the vector with the size entered. Read the vector elements and also read a key value. Display the number of times the key has appeared in the vector.
Implement the Selection Sort, Bubble Sort and Insertion sort algorithms on a set of 10 numbers using vectors.
Read the size of the vector and declare the vector with the size entered. Read the vector elements and sort the array using selection sort, bubble sort and insertion sort.
Implement the Sieve of Eratosthenes and print the first 100 prime numbers. The algorithm is as follows:
Fill vector values[100] with numbers from 1 to 100.
Starting with the second entry in the array, set all its multiples to zero.
Proceed to the next non-zero element and set all its multiples to zero.
Repeat step 3 till you have set up the multiples of all the non-zero elements to zero.
After step 4, all the non-zero entries left in the array would be prime numbers, so print out these numbers.
Write a program to copy the contents of one vector into another in reverse order.
Write a program to check if a vector is symmetric. That is, in a vector array_id check if array_id[0] = array_id[n-1], array_id[1] = array_id[n-2] and so on, where n is the size of the vector. Statically initialize the array or take user input.
Find the smallest number, the largest number, and an average of values in a vector of floating-point numbers.
Write a function to find the norm of a matrix. The norm is defined as the square root of the sum of squares of all elements in the matrix.
Write a program to read 10 coordinates. Each coordinate contains 2 floating-point values X and Y. Print the two farthest points and 2 nearest points in the vector.
These notes focus on C++17 which is one of the latest and most widely used versions of C++ in the industry. To go through this set of documents, the pre-requisite is to have a good knowledge of the C programming language whose notes are available in the previous section. This set of notes on C++ only focuses on what C++ has to offer on top of already what C is offering.
The execution of the coding examples in these notes should be done on standard GNU G++ compilers. One can use various IDEs such as VS Code, vi, Codelite etc.
All C programs can be executed in the C++ compiler but not the other way round. Hence, it is evident that C++ supports all the features of C and can use in its programs.
Structure of a C++ program
C++ has ~90 keywords. More the keywords, more the complexity of the language’s grammar. Although, some of the keywords are seldom used. One need not memorise all the keywords as there is online documentation (https://en.cppreference.com/) for things you seldom need. But it is essential to know a subset of these which are most commonly used. The set of keywords in C++ is as follows:
alignas alignof and and_eq asm atomic_cancel atomic_commit atomic_noexcept auto bitand bitor bool break case catch char
char8_t
char16_t
char32_t
class
compl
concept
const
consteval
constexpr
constinit
const_cast
continue
co_await
co_return
co_yield
decltype
default
delete
do
double
dynamic_cast
else
enum
explicit
export
extern
false
float
for
friend
goto
if
inline
int
long
mutable
namespace
new
noexcept
not
not_eq
nullptr
operator
or
or_eq
private
protected
public
reflexpr
register
reinterpret_cast
requires
return
short
signed
sizeof
static
static_assert
static_cast
struct
switch
synchronized
template
this
thread_local
throw
true
try
typedef
typeid
typename
union
unsigned
using
virtual
void
volatile
wchar_t
while
xor
xor_eq
C++ keywords
Preprocessor directives
A preprocessor also called a precompiler, is a program that processes the source code before the compiler gets the code. It looks for preprocessor directives and executes them. Preprocessor directives start from # (pound symbol/ hashtag symbol). Examples of preprocessor directives are as follows:
#includes <header_files.h>
In the C/C++ programming languages, the #include directive tells the preprocessor to insert the contents of another file into the source code at the point where the #include directive is found. Include directives are typically used to include the header files for C functions that are held outside of the current source file.
For example, we use math functions such as pow and log which we call from the source code we write. But the definition of these functions is made in the standard header files present. By including math.h using this preprocessor, the definitions are fetched from the header files and put into the source code before the compilation phase. The object code and binary generated would contain the definition of these functions too.
#include "header_file.h"
When certain function calls we make in our source code are not present in the standard library provided by C/C++, we can declare them in a separate header file and write the corresponding definition in the C/C++ file. This needs to be included in the source code we write. This user-defined header file inclusion is done using the double inverted comma specification in contrast to the angular bracket specification which is done for the built-in header files.
#if, #elif, #else, #endif
The #if directive, with the #elif, #else, and #endif directives, controls the compilation of portions of a source file. If the expression written (after the #if) has a nonzero value, the line group immediately following the #if directive is kept in the translation unit.
Each #if directive in a source file must be matched by a closing #endif directive. Any number of #elif directives can appear between the #if and #endif directives, but at most one #else directive is allowed. The #else directive, if present, must be the last directive before #endif. The #else directive is optional to exist.
The #if, #elif, #else, and #endif directives can nest in the text portions of other #if directives. Each nested, or #endif directive belongs to the closest preceding #if directive.
All conditional-compilation directives, such as #if and #ifdef, must match a closing #endif directive before the end of the file. Otherwise, an error message is generated. When conditional-compilation directives are contained in include files, they must satisfy the same conditions: There must be no unmatched conditional-compilation directives at the end of the include file.
Macro replacement is done within the part of the line that follows an #elif command, so a macro call can be used in the constant expression. The preprocessor selects one of the given occurrences of text for further processing. A block specified in the text can be any sequence of text. It can occupy more than one line. Usually, the text is program text that has meaning to the compiler or the preprocessor.
The preprocessor processes the selected text and passes it to the compiler. If the text contains preprocessor directives, the preprocessor carries out those directives. Only text blocks selected by the preprocessor are compiled.
The preprocessor selects a single text item by evaluating the constant expression following each #if or #elif directive until it finds a true (non-zero) constant expression. It selects all text (including other preprocessor directives beginning with #) up to its associated #elif, #else, or #endif.
If all occurrences of constant expression are false, or if no #elif directives appear, the preprocessor selects the text block after the #else clause. When there’s no #else clause, and all instances of constant expression in the #if block is false, no text block is selected.
The constant expression is an integer constant expression with these additional restrictions:
Expressions must have an integral type and can include only integer constants, character constants, and the defined operator.
The expression can’t use sizeof or a type-cast operator.
The target environment may be unable to represent all ranges of integers.
The translation represents type int the same way as type long, and unsigned int the same way as unsigned long.
The translator can translate character constants to a set of code values different from the set for the target environment. To determine the properties of the target environment, use an app built for that environment to check the values of the LIMITS.H macros.
The expression must not query the environment and must remain insulated from implementation details on the target computer.
#ifdef
In the C Programming Language, the #ifdef directive allows for conditional compilation. The preprocessor determines if the provided macro exists before including the subsequent code in the compilation process. If the definition of the macro exists, the block inside #ifdef is skipped to be sent to the translator and sent to the translator otherwise.
Syntax:
#ifdef macro_definition
Macro_definition
#endif
The macro definition must be defined for the preprocessor to include the C source code in the compiled application. Note that the #ifdef directive must be closed by an #endif directive.
Example:
#include <stdio.h>
#define DEFINED 1
int main()
{
#ifdef DEFINED
printf("DEFINED!");
#endif
return 0;
}
Output: DEFINED!
A common use for the #ifdef directive is to enable the insertion of platform-specific source code into a program.
#ifndef
In the C Programming Language, the #ifndef directive allows for conditional compilation. The preprocessor determines if the provided macro does not exist before including the subsequent code in the compilation process. If the definition of the macro exists, the block inside #ifndef is included to be sent to the translator and skipped otherwise.
Syntax:
#ifndef macro_definition
Macro_definition
#endif
The macro definition must be defined for the preprocessor to include the C source code in the compiled application. Note that the #ifndef directive must be closed by an #endif directive.
Example:
#include <stdio.h>
// #define DEFINED 0
int main()
{
#ifndef DEFINED
printf("NOT DEFINED!");
#endif
return 0;
}
Output: NOT DEFINED!
A common use for the #ifndef directive is to enable the insertion of platform-specific source code into a program.
#define
The #define directive allows the definition of macros within your source code. These macro definitions allow constant values to be declared for use throughout your code. All the occurrences of the name of the constant done using #define throughout the source code are replaced with the defined constant value
Macro definitions are not variables and cannot be changed in the program code. This syntax is generally used while creating constants that represent numbers, strings or expressions.
Syntax
#define CONSTANT value
// OR
#define CONSTANT (expression)
CONSTANT is the name of the constant. It is a common practice to define the constants in all uppercase but there is no explicit rule that this has to be done.
value is the value of the constant.
expression: The expression whose value is assigned to the constant. The expression must be enclosed in parentheses if it contains operators.
Note that the semicolon character should not be put at the end of #define statements. This is a common mistake.
Examples:
#include <iostream>
#define COMPANY "Quantmasters"
int main()
{
std::cout << COMPANY << " is an ed-tech company";
return 0;
}
Output: Quantmasters is an ed-tech company
#include <iostream>
#define NUMBER (10/2)
int main()
{
std::cout << "10 / 2 = " << NUMBER;
return 0;
}
Output: 5
#undef
The #undef directive tells the preprocessor to remove all definitions for the specified macro. A macro can be redefined after it has been removed by the #undef directive. Once a macro is undefined, an #ifdef directive on that macro will evaluate as false.
Syntax
#undef macro_definition
Example:
#include <stdio.h>
#define DEFINED 1
#undef DEFINED
int main()
{
#ifdef DEFINED
printf("DEFINED");
#endif
#ifndef DEFINED
printf("NOT DEFINED");
#endif
return 0;
}
Output: NOT DEFINED
In this example, the DEFINED macro is first defined with a value of 1 and then undefined using the #undef directive. Since the macro no longer exists, the statement #ifdef DEFINED evaluates to false. This causes the subsequent printf function to be skipped.
#line
The #line directive tells the preprocessor to set the compiler’s reported values for the line number and filename to a given line number and filename.
Syntax
#line digit-sequence ["filename"]
Where filename is an optional attribute and digit-sequence is strictly an integer value
Examples:
#include <stdio.h>
int main()
{
printf("Line: %d\n",__LINE__); // printing line number, line 7
// resetting line to 23, although next line number is line 10.
#line 2
printf("Line: %d\n",__LINE__); // printing line number
printf( "Line: %d, File: %s\n", __LINE__, __FILE__ );
// now we use line to reset filename to "new_filename.c"
// line number is set to 83
#line 83 "new_filename.c"
printf( "Line: %d, File: %s\n", __LINE__, __FILE__ );
return 0;
}
The #error directive causes preprocessing to stop at the location where the directive is encountered. Information following the #error directive is output as a message prior to stopping preprocessing.
Syntax
#error message
Examples:
#include <stdio.h>
#define NUM 50
int main()
{
#ifndef NUM
#error NUM not defined
#endif
return 0;
}
The above code doesn’t produce any output.
#include <stdio.h>
#define NUM 50
int main()
{
#ifdef NUM
#error NUM defined
#endif
return 0;
}
Output:
main.c: In function ‘main’:
main.c:6:10: error: #error NUM defined
6 | #error NUM defined
#pragma
This directive is a special purpose directive and is used to turn on or off some features. These types of directives are compiler-specific i.e., they vary from compiler to compiler.
#warning
The #warning directive is similar to a #error directive but does not result in the cancellation of preprocessing. Information following the #warning directive is output as a message prior to preprocessing continuing.
Syntax
#warning message
Examples:
#include <stdio.h>
#define NUM 50
int main()
{
#ifndef NUM
#warning NUM not defined
#endif
return 0;
}
The above code doesn’t produce any output.
#include <stdio.h>
#define NUM 50
int main()
{
#ifdef NUM
#warning NUM defined
#endif
return 0;
}
Output:
main.c: In function ‘main’:
main.c:6:10: warning: #warning NUM defined [-Wcpp]
6 | #warning NUM defined
Overview of preprocessor directives
One of the most used of the above examples is #include directive. The preprocessor sees the #include statement and replaces it with the corresponding header file that it is referring to. The header files usually contain the prototypes and the signatures of the functions the program will use. Then the preprocessor recursively processes the replaced content as well so as to get the definitions of the files declared in the header file. This process’s output is a file that contains the program the programmer has written along with the function signatures and definitions the user has used in the program. This makes it easy for the compiler to do its job.
Sometimes, the programmer might want to compile code to generate platform-dependent binaries such as windows or mac. In this case, the code has to look for the libraries supporting the corresponding operating system. This is a conditional compilation. This is widely used in the context of cross-compilation where the compilation happens on one machine which generates binaries and these binaries are executed on another machine. To achieve this, preprocessor directives such as #if, #elif, #else etc are extensively used.
It is important to note that the C++ preprocessor does not understand C++. It simply follows the preprocessor directives and gets the source code ready for the compiler. The compiler is the program that understands C++.
Comments
The comments are the programmer’s readable explanations so as to understand what a piece of code does. The comments in the C++ source code are the same as those in C. Double forward-slash (//) for single-line comments and multiline comments enclosed in /* and */.
The main() function
Every C++ program must have one and only main() function. A program may contain n files but the main() must be there in any one of them. When a C++ program executes, the main() function is called by the operating system. The logic present in the main() function is executed and the value returned by the main() function is received by the operating system. Conventionally, if the return value is 0, then the program has been executed successfully. If otherwise, there could be an error table maintained to check what went wrong.
There are 2 versions of main(), both of which are accepted as a standard version of main(). They are as follows:
The first version of the main() is mostly used. The second version is used to receive command-line arguments from the operating system. The second version expects 2 pieces of information from the operating system:
Argument count – Count of the arguments (argc).
Argument vector – The 2D character array of the list of arguments (argv).
The common-line arguments handling and usage will be dealt with in the latter part of the notes. Note that the main should always return an integer.
Namespace
As and when the C++ programs we write get more complex, we often use the C++ library code by importing them, libraries that are written by 3rd party developers combined with, of course, our code. A variable or a function may be defined in the standard library and the same variable or a function may be redefined by 3rd party library which causes a conflict where the compiler doesn’t know which variable/function to use when called. This is called naming conflict.
C++ namespace is a feature which acts as a container that groups the code entities. If a programmer wants to use a variable or a function from a particular namespace, one can use the scope resolution operator (::). The syntax to do so is as follows:
<namespace>::<variable/function>
One might find it tedious to use this syntax every single time a variable has to be called or used. C++ provides a workaround to do that. One can use the using namespace directive at the beginning of the program to use a particular namespace. But note that, once the using namespace directive is used, it brings all the variables and functions which are defined in that namespace. This might lead to conflict too.
C++ provides a solution to this problem too. The programmer can mention the specific coding entities being used in the program at the beginning of the program. Only those entities will be imported. The syntax to do this is as follows:
using <namespace>::<entity>
First C++ program
In this topic, we shall see our first C++ program using the concepts we have learned so far. Consider the following program:
#include <iostream>
using namespace std;
int main() {
return 0;
}
This program neither performs any operations nor does it print anything. It simply imports the iostream library, uses the using namespace directive to let the compiler know that it is going to use entities from the std namespace (though it uses none of the entities), and defines the main() function which just returns 0 and exits.
Basic input-output using cin and cout
cin, cout, cerr and clog are defined in the C++ standard. To use these one must include the iostream library. C++ uses stream abstraction to handle IO and devices like keyboard and console. cout is an output stream that defaults to the output console/screen. cerr and clog are also output streams that default to standard error and standard log respectively. cin is an input stream that defaults to the keyboard.
The insertion operator (>>) and the extraction operator (<<) are used with input and output streams respectively.
The insertion operator
The insertion operator inserts the value from the operand from the right to the operand to the left. Consider the following statement:
std::cout << variable_1;
In this case, the insertion operator inserts the value of variable_1 to cout output stream. As the cout is the default console, this statement makes the value of variable_1 displayed on the screen.
Since we are using stream abstraction, we can chain multiple insertions into the same statement which simplifies the basic IO very easy. An example of this is as follows:
std::cout << “The value in variable_1 is: ” << variable_1;
The insertion operator does not automatically add linebreaks to move the cursor to the next line on the console. But it can be achieved in two ways which are depicted in the following examples:
If the end line manipulator (endl) is used, it flushes the stream too but the new line character \n just brings the cursor to the new line.
Examples: Insertion operator
#include <iostream>
using namespace std;
int main() {
cout << “Hello World”;
return 0;
}
Output: Hello world
#include <iostream>
using namespace std;
int main() {
cout << “Hello”;
cout << “World”;
return 0;
}
Output: HelloWorld
Conclusion: The insertion operator does not add any extra characters such as space or newline.
The extraction operator
This operator extracts the information from the operand to the left and stores this information in the operand to the right. Consider the following example:
std::cin >> variable_2;
In this case, the value from cin, which by default is keyboard, is taken and stored in variable_2. The way in which the information is interpreted is based on the type of the variable. If the data type of variable_2 is an integer, the input value is converted to an integer and then passed onto variable_2. If the variable is float, the value is converted to float.
The extraction operator can be chained to read multiple values in a single line. This is illustrated in the following example:
std::cin >> variable_2 >> variable_3;
The above statement reads a value from the keyboard converts the value to the data type of variable_2 and stores it in variable_2. The same process is repeated again for variable_3. So the precedence is from left to right.
Note that the extraction operation could fail if the value entered cannot be interpreted in the data type of the target variable on the right side. For example, if the data type of the variable is an integer and the user enters “Prajwal” which is a string. This will cause the operation to fail.
Conclusion: The input value is interpreted based on the data type of the variable in which it is going to be stored. Hence the input value for variable_1 was an integer and for variable_2, it was interpreted as a string.
Conclusion: The input value can be chained into a single cin statement and it works accordingly.
C++ primitive data types
The computer stores the information in binary representation. And the size of the primitive data types is expressed in bits. The number of bits allocated to a data type is directly proportional to the number of unique values it can store and also the size of memory it needs. Hence it is important to choose the data type necessary for the application being used. Size and precision are compiler dependent. climits library contains the size and precision of the compiler the program is being compiled by.
Size in bits
Number of unique values representable
1
21
2
22
4
24
8
28
16
216
Character types
This is used to represent character types such as the ones in ASCII table. This is often represented in 8 bits (1 byte). From the above table, it is clear that one can represent a maximum of 2^8 = 256 distinct characters using 8 bits. However, some languages such as Mandarin have thousands of characters which cannot be represented in just 8 bits. In order to support these languages, C++ supports wider character types which can be as large as necessary. The following table describes some of the character types C++ supports
Type Name
Size/Precision
char
Exactly 1 byte
char16_t
16 bits
char32_t
32 bits
wchar_t
Can represent the largest available character set
Unicode is a common standard used to represent multiple character sets in any language.
Integer types
This is used to represent whole numbers, both signed and unsigned. There are many versions of this data type. The following table shows the C++ integer data types for both signed and unsigned integers.
In addition to this, it is possible to store both signed and unsigned integers in character data type. This capability of C/C++ is often exploited to efficiently store integers of a shorter range.
Floating-point types
The usual method used by computers to represent real numbers is floating-point notation. There are many varieties of floating-point notation and each has individual characteristics. The key concept is that a real number is represented by a number called mantissa, times a base raised to an integer power called an exponent. The base is usually fixed, and the mantissa and the exponent vary to represent different real numbers. For example, if the base is fixed at 10, the number 123.45 could be represented as 12345 x 10-2. The mantissa is 12345, and the exponent is -2. Other possible representations are 0.12345 x 103 and 123.45×100. We choose the representation in which the mantissa is an integer with no trailing 0s.
In the floating-point notation, a real number is represented by a 32-bit value consisting of a 24-bit mantissa followed by an 8-bit exponent. The base is fixed at 10. Both the mantissa and the exponent are twos complement binary integers. For example. The 24-bit binary representation of 12345 is 0000 0000 0011 0000 0011 1001. And the 8-bit twos complement binary representation of -2 is 1111 1110; the representation of 123.45 is 0000 0000 0011 0000 0011 1001 1111 1110.
The advantage of floating-point notation is that it can be used to represent numbers with extremely large or extremely small absolute values. The floating point has 3 types: float, double and long double. The following table describes these types:
Type
Precision
Range
float
7 decimal digits
1.2 x 10-38 to 3.4 x 1038
double
15 decimal digits
2.2 x 10-308 to 1.8 x 10308
long double
19 decimal digits
3.3 x 10-4932 to 1.2 x 104932
Boolean type
The boolean data type is used to represent true or false values. In C++, 0 is false and any non-zero value is true. C++ also supports ‘true’ and ‘false’ keywords to often use with the boolean data types. Boolean data type usually takes up to 8 bits of memory size.
Declaring and using variables
Apart from using the assignment operator to initialise identifiers with constants, C++ provides one more way to do the same job: