Categories
Computer Science / Information Technology Language: C++

Range based for-loop

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;
}

Output: S t r i n g

Categories
Computer Science / Information Technology Language: C++

Dynamic memory allocation in C++

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

  1. The syntax for allocation:
<data_type> *variable_name = new <data_type>;
// Where the data_type is user-defined or C++ built-in.
  1. 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.
  2. The syntax for deallocation:
delete variable_name;
  1. 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

  1. 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. 
  1. 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.
  1. The syntax for deallocation:
delete [] variable_name;
  1. 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;
}

Output: 1 2 3 4

Categories
Computer Science / Information Technology Language: C++

Vectors

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:

Syntax:

vector <data_type> vector_var {value_1, value_2,..., value_n};
// Or
vector <data_type> vector_var = {value_1, value_2,..., value_n};
// Or
vector <data_type> vector_var = ({value_1, value_2,..., value_n});

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.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector <int> whole_numbers (10, 10);
    cout << whole_numbers[11];
    return 0;
}

Output: 0

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.

#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector <int> whole_numbers (10, 10);
    cout << whole_numbers.at(11);
    return 0;
}

Output:

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.

MethodDescriptionReturn typeParameters
sizeReturn size of the vectorsize_tNone
max_sizeReturn the maximum size which the vector is capable of holdingsize_tNone
resizeChange sizevoid(int new_size, int initialisation_value)
initialisation_value is optional and by default is 0
capacityReturn size of allocated storage capacitysize_tNone
emptyTest whether the vector is emptybooleanNone
reserveRequests that the vector capacity is at least enough to contain ‘n’ elements.voidn
shrink_to_fitRequests the container to reduce its capacity to fit its size.voidNone
atAccess element at the specified indexThe data type of the vector elementsIndex value between 0 and size – 1 inclusive
frontAccess first elementThe data type of the vector elementsNone
backAccess last elementThe data type of the vector elementsNone
dataReturns a direct pointer to the memory array used internally by the vector to store its owned elements.Pointer to vector element data typeNone
assignAssigns new contents to the vector, replacing its current contents, and modifying its size accordingly.voidsize_t n: the new size of the vector
value: to fill the vector with
push_backAdd element at the endvoidElement of same data type as that of the vector
pop_backDelete the last elementvoidNone
insertInsert elementsAn iterator that points to the first of the newly inserted elements.Iterator position and value to be inserted
eraseRemoves 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
swapExchanges the content of the container by the content of x, which is another vector object of the same type. Sizes may differ.voidReference to another vector x
clearRemoves all elements from the vector (which are destroyed), leaving the container with a size of 0.voidnone
emplaceConstruct and insert elementIterator to the new element insertedIterator position to insert the new value and the new value
emplace_backConstruct and insert an element at the endvoidThe 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

  1. 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.
  2. 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.
  3. Implement the Selection Sort, Bubble Sort and Insertion sort algorithms on a set of 10 numbers using vectors.
  4. 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.
  5. Implement the Sieve of Eratosthenes and print the first 100 prime numbers. The algorithm is as follows:
    1. Fill vector values[100] with numbers from 1 to 100.
    2. Starting with the second entry in the array, set all its multiples to zero.
    3. Proceed to the next non-zero element and set all its multiples to zero.
    4. Repeat step 3 till you have set up the multiples of all the non-zero elements to zero.
    5. After step 4, all the non-zero entries left in the array would be prime numbers, so print out these numbers.
  6. Write a program to copy the contents of one vector into another in reverse order.
  7. 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.
  8. Find the smallest number, the largest number, and an average of values in a vector of floating-point numbers.
  9. 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.
  10. 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.

Categories
Computer Science / Information Technology Language: C++

Fundamentals of a C++ program

Overview

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:

  1. Expressions must have an integral type and can include only integer constants, character constants, and the defined operator.
  2. The expression can’t use sizeof or a type-cast operator.
  3. The target environment may be unable to represent all ranges of integers.
  4. The translation represents type int the same way as type long, and unsigned int the same way as unsigned long.
  5. 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.
  6. 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;
}

Output:

Line: 5
Line: 23
Line: 25, File: main.cpp
Line: 83, File: new_filename.c

#error

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
Preprocessor directives mind map

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:

int main() {
    // code
    return 0;
}
int main(int argc, char *argv[]) {
    // code
    return 0;
}

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:

  1. Argument count – Count of the arguments (argc).
  2. 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:

std::cout << variable_1 << “\n”;
// Or
std::cout << variable_1 << std::endl;

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.

Example 1
#include <iostream>
#include <string>
using namespace std;
int main() {
    int variable_1;
    cin >> variable_1;
    cout << "variable_1: " << variable_1 << endl;
    string variable_2;
    cin >> variable_2;
    cout << "variable_2: " << variable_2;
    return 0;
}

Output

1
variable_1: 1
asdf
variable_2: asdf

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.

Example 2
#include <iostream>
#include <string>
using namespace std;
int main() {
    int variable_1;
    string variable_2;
    cin >> variable_1 >> variable_2;
    cout << "variable_1: " << variable_1 << endl;
    cout << "variable_2: " << variable_2;
    return 0;
}

Output

1
asdf
variable_1: 1
variable_2: asdf

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 bitsNumber of unique values representable
121
222
424
828
16216
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 NameSize/Precision
charExactly 1 byte
char16_t16 bits
char32_t32 bits
wchar_tCan 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.

Type nameSizeclimits macrosRange
signed short int16 bitsSHRT_MIN / SHRT_MAX[−32,767, +32,767]
signed int16 bitsINT_MIN / INT_MAX[−32,767, +32,767]
signed long int32 bitsLONG_MIN / LONG_MAX[−2,147,483,647, +2,147,483,647]
signed long long int64 bitsLLONG_MIN / LLONG_MAX[−9,223,372,036,854,775,807, +9,223,372,036,854,775,807]
unsigned short int16 bits0 / USHRT_MAX[0, 65,535]
unsigned int16 bits0 / UINT_MAX[0, 65,535]
unsigned long int32 bits0 / ULONG_MAX[0, 4,294,967,295]
unsigned long long int64 bits0 / ULLONG_MAX[0, +18,446,744,073,709,551,615]

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:

TypePrecisionRange
float7 decimal digits1.2 x 10-38 to 3.4 x 1038
double15 decimal digits2.2 x 10-308 to 1.8 x 10308
long double19 decimal digits3.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:

int variable_1 {100};

does the same job as

int variable_1 = 100;

You cannot copy content of this page