Introduction to C++¶
Part 2 · Programming languages · Chapter 6 · Lecture notes, volume 2 (PDF) · Exercises: Introduction to C++
Introduction¶
In the chapter «Pseudo-code» algorithms are written in a language meant for humans: it is precise, but it is not executed by a computer. To run an algorithm we have to translate it into a programming language. In this chapter we introduce the programming language C++, which we use to implement the algorithms of the course.
The chapter follows the same path as the chapter «Pseudo-code»:
-
the main characteristics of the language and the structure of a program;
-
variables, data types and the input-output stream;
-
operators, conditional statements and loops;
-
arrays, whose size can be fixed or decided during the execution;
-
functions, with the
mainfunction as the entry point of every program; -
a complete algorithm, the search of the maximum value in an array, written in pseudo-code and in
C++; -
the bitwise operators, which act directly on the binary representation of integer numbers.
Every fragment of code of the chapter has been compiled with the g++ compiler (standard C++17) and executed: the outputs reported in the examples are the ones printed by the programs.
In pseudo-code the entries of an array \(a[1..n]\) are indexed from \(1\) to \(n\); in C++ the entries of an array of size \(n\) are indexed from \(0\) to \(n-1\). The entry \(a[i]\) of the pseudo-code corresponds to the entry a[i-1] of the C++ code.
6.1 Main characteristics of the programming language¶
The main characteristics of C++ are the following.
-
C++is an imperative language that gives direct access to the memory and to the hardware resources of the computer. It is highly efficient for low-level system programming while also supporting high-level abstractions, making it versatile for both performance-critical applications and modern software development. -
C++supports functions, which enable a structured and modular approach to programming. Functions allow code reuse and better organization. -
The
C++programming language includes a predefined set of reserved symbols used to declare variables, define functions and specify executable instructions. The source code consists of a sequence of atomic operations executed in order, one after the other, operating on variables of specific data types. -
The source code in
C++can contain comments, which serve as documentation for the code and are ignored by the compiler. InC++, a single-line comment starts with the reserved symbol “//”. Multi-line comments are enclosed between the reserved symbols “/*” and “*/”.
Example 6.1: comments in C++
This is an example of a single-line comment:
This is an example of a multi-line comment:
6.1.1 The structure of a program¶
Before describing the single instructions, we look at the skeleton that every program shares. A C++ program is a text file (with extension .cpp) that is translated by a compiler into an executable file. The execution of the program starts from the main function, the entry point of every program.
Example 6.2: the skeleton of a program
#include <iostream>
using namespace std;
int main()
{
// The instructions of the program are written here
cout << "Hello, world!" << endl;
return 0;
}
If the source code is saved in the file hello.cpp, it is compiled and executed with the commands
and the output is
In the following examples we write only the instructions that matter: unless they contain a complete program, the fragments of code are meant to be placed inside the curly braces of main.
Each instruction of a C++ source code must end with the reserved symbol “;”.
6.2 Variables and data types¶
The source code operates on variables, and every variable has a data type.
-
Information is categorized into different data types. The four fundamental families of data types are:
-
The data type “
int” represents an integer, which is a whole number written without a decimal point. This includes positive and negative whole numbers, as well as zero. -
The data type “
bool” represents a Boolean value:trueorfalse. -
The data type “
float” represents a floating-point number, which includes numbers written with a decimal point. The data type “double” represents a floating-point number with double precision, providing greater accuracy and range thanfloat. -
The data type “
char” represents a single character, such as a letter, digit, or symbol, enclosed in single quotes (e.g.,'A','7','!').
-
-
A sequence of characters enclosed in double quotes (e.g.,
"Ada") is stored in a variable of type “string”, provided by the standard library<string>. -
A variable acts as a container that stores a value of a specific data type for later use. Each variable has a unique name and holds a single value at any given time.
-
Variables are assigned values using the assignment operator “
=”. When a new value is assigned, the previous value is overwritten.
Variables must be declared before use, specifying their data type.
Example 6.3: variable declarations and value assignments
int age = 25; // Integer variable
bool isRaining = false; // Boolean variable
float smallNumber = 3.14; // Floating-point variable
double largeNumber = 123456.789; // Double precision floating-point variable
char character = 'a'; // Char variable
string name = "Ada"; // String variable
age = 26; // The previous value 25 is overwritten
cout << age << " " << isRaining << " " << smallNumber << " "
<< largeNumber << " " << character << " " << name << endl;
Output:
The Boolean value false is displayed as 0, and the number 123456.789 is displayed as 123457: by default, cout displays at most six significant digits (the value stored in the variable does not change, and the number of displayed digits can be chosen, as we see now).
6.3 Input-output stream¶
To display the values of the variables on the screen, a program uses the input-output stream library.
- The include “
#include <iostream>” allows to use the input-output stream library inC++.
-
The values of variables can be displayed as output text on the screen using the output stream “
std::cout”. The insertion operator “< <” is used to send data to the output stream. -
Multiple values can be displayed in a single
coutstatement by chaining them with the “< <” operator. Text should be enclosed in double quotes “"”, while variable values are written directly. -
The manipulator “
std::endl” is used withstd::coutto insert a new line on the screen and flush the output buffer.
Example 6.5: screen printings
Output:
-
The statement “
using namespace std;” simplifies the use of standard library functions by eliminating the need to prefix them withstd::. From now on we always use it, and we writecoutandendl. -
The include “
#include <iomanip>” gives the manipulators “fixed” and “setprecision(k)”. When they are written together in acoutstatement, they display the floating-point numbers that follow with exactly \(k\) digits after the decimal point.
6.4 Operators¶
Operators are symbols that perform operations on variables and values. They are classified, according to their functionality, into five main categories: arithmetic, assignment, increment and decrement, relational and logical operators.
6.4.1 Arithmetic operators¶
Arithmetic operators, used to perform basic mathematical operations:
-
+(Addition) \(\rightsquigarrow\) Adds two values. -
-(Subtraction) \(\rightsquigarrow\) Subtracts one value from another. -
*(Multiplication) \(\rightsquigarrow\) Multiplies two values. -
/(Division) \(\rightsquigarrow\) Divides one value by another. -
%(Modulus) \(\rightsquigarrow\) Returns the remainder of a division.
Example 6.6: arithmetic operators
int a = 10, b = 3;
cout << "Addition: " << (a + b) << endl;
cout << "Subtraction: " << (a - b) << endl;
cout << "Multiplication: " << (a * b) << endl;
cout << "Division: " << (a / b) << endl; // Integer division
cout << "Modulus: " << (a % b) << endl; // Remainder of division
Output:
-
The division operator
/performs integer division when used with integers: the result10 / 3is3. To obtain a floating-point result, at least one operand must be of data typefloatordouble. For example,10.0 / 3returns3.33333instead of3. -
A value of type
intis converted into a value of typedoubleby writing “(double)” before it (type conversion). For example, ifaandbare variables of typeint, the expression(double)a / bperforms the floating-point division.
Example 6.7: integer and floating-point division
int a = 10, b = 3;
cout << a / b << endl; // Integer division: 3
cout << 10.0 / 3 << endl; // Floating-point division
cout << (double)a / b << endl; // Conversion of a to double
cout << fixed << setprecision(2) << (double)a / b << endl; // Two decimals
Output (the last line requires #include <iomanip>):
6.4.2 Assignment operators¶
Assignment operators, used to assign values to variables:
-
=(Assignment) \(\rightsquigarrow\) Assigns a value to a variable. -
+=(Addition assignment) \(\rightsquigarrow\) Adds a value to a variable and assigns the result. -
-=(Subtraction assignment) \(\rightsquigarrow\) Subtracts a value from a variable and assigns the result. -
*=(Multiplication assignment) \(\rightsquigarrow\) Multiplies a variable by a value and assigns the result. -
/=(Division assignment) \(\rightsquigarrow\) Divides a variable by a value and assigns the result. -
%=(Modulus assignment) \(\rightsquigarrow\) Applies the modulus operation and assigns the result.
Example 6.8: assignment operators
int x = 10; // Assignment
cout << "Initial value: " << x << endl;
x += 5; // Equivalent to: x = x + 5
cout << "After += 5: " << x << endl;
x -= 3; // Equivalent to: x = x - 3
cout << "After -= 3: " << x << endl;
x *= 2; // Equivalent to: x = x * 2
cout << "After *= 2: " << x << endl;
x /= 4; // Equivalent to: x = x / 4
cout << "After /= 4: " << x << endl;
x %= 3; // Equivalent to: x = x % 3
cout << "After %= 3: " << x << endl;
Output:
6.4.3 Increment and decrement operators¶
Increment and decrement operators:
-
++(Increment) \(\rightsquigarrow\) Increases the value of a variable by 1. -
- -(Decrement) \(\rightsquigarrow\) Decreases the value of a variable by 1.
-
The increment operator
++can be used in two forms:-
pre-increment (“
++x”): increments the value before returning it; -
post-increment (“
x++”): returns the value first, then increments it.
The same logic applies to the decrement operator (
- -xandx- -). -
Example 6.9: increment and decrement operators
int x = 5;
cout << "Initial value: " << x << endl; // Displays 5
cout << "Pre-increment (++x): " << ++x << endl; // Displays 6
cout << "Post-increment (x++): " << x++ << endl; // Displays 6
cout << "Value after post-increment: " << x << endl; // Displays 7
cout << "Pre-decrement (--x): " << --x << endl; // Displays 6
cout << "Post-decrement (x--): " << x-- << endl; // Displays 6
cout << "Value after post-decrement: " << x << endl; // Displays 5
Output:
6.4.4 Relational operators¶
Relational operators, used to compare values and return a Boolean result:
-
==(Equal to) \(\rightsquigarrow\) Checks if two values are equal. -
!=(Not equal to) \(\rightsquigarrow\) Checks if two values are different. -
<(Less than) \(\rightsquigarrow\) Checks if one value is smaller than another. -
>(Greater than) \(\rightsquigarrow\) Checks if one value is larger than another. -
<=(Less than or equal to) \(\rightsquigarrow\) Checks if one value is smaller than or equal to another. -
>=(Greater than or equal to) \(\rightsquigarrow\) Checks if one value is larger than or equal to another.
-
Relational operators return a Boolean result:
-
true(displayed as1) if the condition is satisfied; -
false(displayed as0) if the condition is not satisfied.
These operators are commonly used in conditional statements and loops.
-
Example 6.10: relational operators
int a = 10, b = 5;
cout << "a == b: " << (a == b) << endl; // Equal to
cout << "a != b: " << (a != b) << endl; // Not equal to
cout << "a < b: " << (a < b) << endl; // Less than
cout << "a > b: " << (a > b) << endl; // Greater than
cout << "a <= b: " << (a <= b) << endl; // Less than or equal to
cout << "a >= b: " << (a >= b) << endl; // Greater than or equal to
Output:
6.4.5 Logical operators¶
Logical operators, used for logical operations, often in conditional statements:
-
&&(Logical AND) \(\rightsquigarrow\) Returnstrueif both conditions are true. -
||(Logical OR) \(\rightsquigarrow\) Returnstrueif at least one condition is true. -
!(Logical NOT) \(\rightsquigarrow\) Reverses the Boolean value.
- Like the relational operators, logical operators return Boolean values,
true(1) orfalse(0), and are commonly used in conditional statements and loops.
Example 6.11: logical operators
bool condition1 = true;
bool condition2 = false;
cout << "condition1: " << condition1 << endl;
cout << "condition2: " << condition2 << endl;
// Logical AND: true only if both conditions are true
cout << "condition1 && condition2: " << (condition1 && condition2) << endl;
// Logical OR: true if at least one condition is true
cout << "condition1 || condition2: " << (condition1 || condition2) << endl;
// Logical NOT: reverses the Boolean value
cout << "!condition1: " << !condition1 << endl;
cout << "!condition2: " << !condition2 << endl;
Output:
6.5 Conditional statements¶
The relational and logical operators produce the conditions that decide which instructions are executed.
-
Conditional statements allow to execute different blocks of code based on specific conditions. The
ifstatement is the most fundamental control structure of this type. -
The
ifstatement evaluates a condition inside parentheses(). If the condition is true, the block of code inside the curly braces{}is executed. If the condition is false, the program skips the block and executes the block of code inside the curly braces{}of theelsepart, if present.
Example 6.12: conditional statement
int number = 10;
// Check if number is positive
if (number > 0)
{
cout << "The number is positive." << endl;
}
else
{
cout << "The number is non-positive." << endl;
}
Output:
6.5.1 Ternary operator (conditional operator)¶
-
The ternary operator
?:provides a compact way to write simpleif-elsestatements. -
It is the only operator in
C++that takes three operands. -
Syntax:
condition ? value_if_true : value_if_false -
If the condition is true, the expression evaluates to
value_if_true; otherwise, it evaluates tovalue_if_false.
Example 6.13: ternary operator
int number = 10;
// Using if-else statement
string result1;
if (number > 0)
result1 = "positive";
else
result1 = "non-positive";
cout << "The number is " << result1 << endl;
// Using ternary operator (compact form)
string result2 = (number > 0) ? "positive" : "non-positive";
cout << "The number is " << result2 << endl;
Output:
Example 6.14: finding the maximum of two numbers
int a = 15, b = 20;
// Using if-else statement
int max1;
if (a > b)
max1 = a;
else
max1 = b;
cout << "Maximum (if-else): " << max1 << endl;
// Using ternary operator (compact form)
int max2 = (a > b) ? a : b;
cout << "Maximum (ternary): " << max2 << endl;
Output:
Example 6.15: checking even or odd
int n = 7;
// Using ternary operator
string parity = (n % 2 == 0) ? "even" : "odd";
cout << n << " is " << parity << endl;
Output:
Example 6.16: assigning the absolute value
int x = -5;
// Using ternary operator to compute absolute value
int absValue = (x >= 0) ? x : -x;
cout << "Absolute value of " << x << " is " << absValue << endl;
Output:
Example 6.17: nested ternary operators
int score = 85;
// Nested ternary operators to assign a grade
string grade = (score >= 90) ? "A" :
(score >= 80) ? "B" :
(score >= 70) ? "C" :
(score >= 60) ? "D" : "F";
cout << "Score: " << score << ", Grade: " << grade << endl;
Output:
The conditions are evaluated from top to bottom: score >= 90 is false and score >= 80 is true, so the grade is "B". For readability, complex conditions are better handled with if-else statements.
6.6 Loops¶
Conditional statements choose whether a block of code is executed; loops decide how many times it is executed.
- Loops are used to execute a block of code repeatedly while a condition is met. They are essential for automating repetitive tasks and reducing redundant code. In
C++, the most commonly used loops are thewhileloop and theforloop.
6.6.1 The while loop¶
- The
whileloop executes a block of code as long as the condition inside the parentheses()evaluates to true. When the condition is false, the loop terminates.
Example 6.18: while loop
int i = 1; // Initialization
while (i <= 5) // Condition
{
cout << "Number: " << i << endl;
i++; // Increment
}
Output:
The condition i <= 5 is evaluated six times: five times it is true, and the sixth time (i equal to 6) it is false and the loop terminates.
6.6.2 The for loop¶
- The
forloop is a compact loop structure that combines initialization, condition, and increment (or decrement) in one statement.
Example 6.19: for loop
for (int i = 1; i <= 5; i++) // Initialization, condition, increment
{
cout << "Number: " << i << endl;
}
The output is the same as in the while loop of Example 6.18.
6.6.3 The break and continue statements¶
-
The
breakstatement immediately terminates the loop. -
The
continuestatement skips the rest of the current iteration and moves to the next one.
Example 6.20: break and continue
for (int i = 1; i <= 10; i++)
{
if (i % 2 == 0)
{
continue; // Skip the even numbers
}
if (i > 7)
{
break; // Stop the loop at the first odd number greater than 7
}
cout << i << " ";
}
cout << endl;
Output:
The even values of i are skipped by continue; when i is equal to 9 the break statement terminates the loop, and the value 10 is never reached.
6.7 Arrays¶
Loops become really useful when they visit collections of values. A collection of variables of the same data type is stored in an array.
6.7.1 Fixed-size arrays¶
-
An array whose values are known when the program is written can be declared by listing its values inside curly braces. The size of the array is the number of listed values, and it cannot change during the execution.
-
Each element of an array is accessed using its index, starting from zero, inside square brackets “
[]”: the elements of an array of size \(n\) areb[0],b[1], …,b[n-1].
Example 6.21: fixed-size arrays
int b[] = {9, 12, 3, 16}; // Array of 4 integers
int n = 4;
b[2] = 5; // Assign a new value to the third element
for (int i = 0; i < n; i++)
{
cout << b[i] << " ";
}
cout << endl;
Output:
- A two-dimensional fixed-size array is declared with two sizes, the number of rows and the number of columns, and its values are listed row by row:
int c[2][3] = {{3, 1, 4}, {1, 5, 9}};declares an array with \(2\) rows and \(3\) columns, andc[1][2]is the element in the second row and third column, equal to \(9\).
6.7.2 Dynamic-size arrays¶
-
A dynamic-size array is a collection of variables of the same data type, stored in contiguous memory locations. The size of the array is determined during the execution of the algorithm.
-
The reserved symbol “
*” is used to declare a pointer, which stores the memory address of the first element of a dynamic-size array. Thenewoperator is used to allocate the memory of a dynamic-size array, and it must specify the data type of the elements along with the size of the array inside square brackets “[]”. -
Each element of a dynamic-size array is accessed using its index, starting from zero, inside square brackets “
[]”. A dynamic-size array requires memory deallocation using thedelete[]operator to free memory and prevent memory leaks.
Figure 6.1: A dynamic-size array of size \(n\): the pointer a stores the address of the first element, and the elements are indexed from \(0\) to \(n-1\).
Example 6.22: dynamic-size arrays
int n = 5; // Size of the array
int* a = new int[n]; // Memory allocation of an array of size n
for (int i = 0; i < n; i++)
{
a[i] = i * 2; // Example of value assignment
}
for (int i = 0; i < n; i++)
{
cout << a[i] << " ";
}
cout << endl;
delete[] a; // Free allocated memory
Output:
-
Dynamic-size arrays can be multidimensional. For example, the reserved symbol “
**” is used to declare a pointer to a pointer, which stores the memory address of the first element of a dynamic-size two-dimensional array: an array of \(m\) pointers, each pointing to a row of \(n\) elements. -
The
newoperator is also used to allocate the memory of dynamic-size two-dimensional arrays: first the array of the \(m\) row pointers, then each row. Each element of a dynamic-size two-dimensional array is accessed using two indices, both starting from zero, inside square brackets “[][]”. The memory is freed in the reverse order: first each row, then the array of row pointers.
Example 6.23: dynamic-size two-dimensional arrays
int m = 3; // Number of rows
int n = 4; // Number of columns
int** matrix = new int*[m]; // Memory allocation of a matrix of size m times n
for (int i = 0; i < m; i++)
{
matrix[i] = new int[n]; // Allocate each row
}
for (int i = 0; i < m; i++)
{
for (int j = 0; j < n; j++)
{
matrix[i][j] = i * j; // Example of value assignment
cout << matrix[i][j] << " ";
}
cout << endl;
}
for (int i = 0; i < m; i++) // Free allocated memory
{
delete[] matrix[i]; // Free each row
}
delete[] matrix; // Free row pointers
Output:
6.8 The main and custom functions¶
As in pseudo-code, an algorithm is written as a function that receives the input and returns the output.
-
Functions allow programmers to write modular and reusable code by grouping instructions into a single unit. A function can be called multiple times to execute the same task, reducing redundancy and improving code organization.
-
Functions typically return a value and accept multiple parameters as input. When calling a function, the arguments are passed to the parameters.
-
A function in
C++consists of:-
a return type: specifies the type of the value returned by the function (
voidif nothing is returned); -
a function name: the unique identifier of the function;
-
a parameter list (optional): specifies the input values of the function;
-
a body: contains the block of code executed when the function is called;
-
a return statement (optional): used to return a value if the function returns a value.
-
-
The
mainfunction serves as the entry point of aC++program. It is executed first when the program starts running. EveryC++program must contain exactly onemainfunction. Its return value0tells the operating system that the program terminated correctly. -
A function must be defined (or declared) before the point where it is called: for this reason the custom functions are written before
main. -
C++source code can also be compiled and executed online, for example at the following address: www.mycompiler.io/it/online-c++-compiler
Example 6.24: functions
#include <iostream>
using namespace std;
// Function definition
int addition(int a, int b)
{
return a + b; // Returns the sum
}
// Function definition
int multiplication(int a, int b)
{
return a * b; // Returns the product
}
int main()
{
int sum = addition(5, 10); // Function call
cout << "Sum: " << sum << endl;
int prod = multiplication(5, 10); // Function call
cout << "Prod: " << prod << endl;
return 0;
}
Output:
6.8.1 Parameters passed by value and by reference¶
A function returns at most one value. When a function has to produce more than one result, or has to modify a variable of the caller, its parameters are passed by reference.
-
By default, a parameter is passed by value: the function receives a copy of the argument, and the changes made to the copy do not affect the variable of the caller.
-
A parameter is passed by reference by writing the reserved symbol “
&” after its data type (for exampleint& x): the parameter is another name for the variable of the caller, and every change made by the function is visible to the caller. -
An array is passed to a function through the pointer to its first element (for example
int *a), together with its size: the function works directly on the elements of the array of the caller.
Example 6.25: parameters passed by value and by reference
#include <iostream>
using namespace std;
void incrementByValue(int x)
{
x = x + 1; // Modifies only the local copy
}
void incrementByReference(int& x)
{
x = x + 1; // Modifies the variable of the caller
}
void fillWithZeros(int n, int* a)
{
for (int i = 0; i < n; i++)
{
a[i] = 0; // Modifies the array of the caller
}
}
int main()
{
int k = 5;
incrementByValue(k);
cout << "After incrementByValue: " << k << endl;
incrementByReference(k);
cout << "After incrementByReference: " << k << endl;
int b[] = {4, 7, 1};
fillWithZeros(3, b);
cout << "Array: " << b[0] << " " << b[1] << " " << b[2] << endl;
return 0;
}
Output:
6.9 A complete algorithm: the maximum value in an array¶
We now put all the pieces together: a function, a loop, a conditional statement and a dynamic-size array.
We consider the problem of finding the maximum value in an array \(a\) of \(n \ge 1\) integer numbers.
- This is the pseudo-code of the algorithm
FindMaximumof the chapter «Pseudo-code»: the entries are indexed from \(1\) to \(n\).
Algorithm: FindMaximum
-
This is the
C++implementation: the entries are indexed from \(0\) to \(n-1\), so the initialization usesa[0]and the loop starts fromi = 1. The function assumes \(n \ge 1\), as the pseudo-code does: for \(n = 0\) the elementa[0]does not exist.#include <iostream> using namespace std; // Function to find the maximum value in an array int ArrayMaxValue(int n, int *a) { int max = a[0]; // Initialize with the first element for (int i = 1; i < n; i++) { if (a[i] > max) { max = a[i]; // Update the maximum } } return max; } int main() { int n = 7; // Size of the array int* a = new int[n]; // Memory allocation of an array of size n int values[] = {9, 12, 3, 16, 19, 11, 12}; for (int i = 0; i < n; i++) { a[i] = values[i]; } int max = ArrayMaxValue(n, a); cout << "Maximum value: " << max << endl; delete[] a; // Free allocated memory return 0; }Output:
Example 6.26: step-by-step execution
In each table, the column \(i\) is the index of the C++ loop and the column \(i+1\) is the corresponding index of the pseudo-code. The first row is the initialization max = a[0].
Instance 1: \(a = [9, 12, 3, 16, 19, 11, 12]\), \(n=7\)
\(i\) (C++) |
\(i+1\) (pseudo-code) | a[i] |
a[i] > max? |
max |
|---|---|---|---|---|
| – | – | – | – | \(9\) |
| \(1\) | \(2\) | \(12\) | Yes | \(12\) |
| \(2\) | \(3\) | \(3\) | No | \(12\) |
| \(3\) | \(4\) | \(16\) | Yes | \(16\) |
| \(4\) | \(5\) | \(19\) | Yes | \(19\) |
| \(5\) | \(6\) | \(11\) | No | \(19\) |
| \(6\) | \(7\) | \(12\) | No | \(19\) |
Return: max \(= 19\)
Instance 2: \(a = [3, 7, 2, 9, 1]\), \(n=5\)
\(i\) (C++) |
\(i+1\) (pseudo-code) | a[i] |
a[i] > max? |
max |
|---|---|---|---|---|
| – | – | – | – | \(3\) |
| \(1\) | \(2\) | \(7\) | Yes | \(7\) |
| \(2\) | \(3\) | \(2\) | No | \(7\) |
| \(3\) | \(4\) | \(9\) | Yes | \(9\) |
| \(4\) | \(5\) | \(1\) | No | \(9\) |
Return: max \(= 9\)
6.10 Bitwise operators¶
The integer numbers handled by a program are stored in binary, as described in the chapter «Representation of numbers». C++ provides operators that act directly on these binary digits.
-
Bitwise operators perform operations on the individual bits of integer values. They are essential for low-level programming, optimization, and working with binary representations.
-
The bits are numbered from the right, starting from position \(0\) (the least significant bit).
Bitwise operators in C++:
-
&(Bitwise AND) \(\rightsquigarrow\) Returns 1 only if both corresponding bits are 1. -
|(Bitwise OR) \(\rightsquigarrow\) Returns 1 if at least one corresponding bit is 1. -
(Bitwise XOR) \(\rightsquigarrow\) Returns 1 if the corresponding bits are different. -
~(Bitwise NOT) \(\rightsquigarrow\) Flips all bits (0 becomes 1, 1 becomes 0). -
< <(Left shift) \(\rightsquigarrow\) Shifts the bits to the left by a specified number of positions. -
> >(Right shift) \(\rightsquigarrow\) Shifts the bits to the right by a specified number of positions.
6.10.1 Bitwise AND (&)¶
-
The bitwise AND operator compares each bit of two numbers and returns 1 only if both bits are 1.
-
Truth table for AND:
| Bit A | Bit B | A & B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
Example 6.27: bitwise AND
int a = 12; // Binary: 1100
int b = 10; // Binary: 1010
int result = a & b; // Binary: 1000 (decimal: 8)
cout << "a & b = " << result << endl; // Output: 8
Bit-by-bit operation:
| Decimal | |||||
|---|---|---|---|---|---|
a |
1 | 1 | 0 | 0 | 12 |
b |
1 | 0 | 1 | 0 | 10 |
a & b |
1 | 0 | 0 | 0 | 8 |
6.10.2 Bitwise OR (|)¶
-
The bitwise OR operator compares each bit of two numbers and returns 1 if at least one bit is 1.
-
Truth table for OR:
| Bit A | Bit B | A | B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
Example 6.28: bitwise OR
int a = 12; // Binary: 1100
int b = 10; // Binary: 1010
int result = a | b; // Binary: 1110 (decimal: 14)
cout << "a | b = " << result << endl; // Output: 14
Bit-by-bit operation:
| Decimal | |||||
|---|---|---|---|---|---|
a |
1 | 1 | 0 | 0 | 12 |
b |
1 | 0 | 1 | 0 | 10 |
a | b |
1 | 1 | 1 | 0 | 14 |
6.10.3 Bitwise XOR ()¶
-
The bitwise XOR (exclusive OR) operator compares each bit of two numbers and returns 1 if the bits are different.
-
Truth table for XOR:
| Bit A | Bit B | A B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
Example 6.29: bitwise XOR
int a = 12; // Binary: 1100
int b = 10; // Binary: 1010
int result = a ^ b; // Binary: 0110 (decimal: 6)
cout << "a ^ b = " << result << endl; // Output: 6
Bit-by-bit operation:
| Decimal | |||||
|---|---|---|---|---|---|
a |
1 | 1 | 0 | 0 | 12 |
b |
1 | 0 | 1 | 0 | 10 |
a b |
0 | 1 | 1 | 0 | 6 |
6.10.4 Bitwise NOT (~)¶
-
The bitwise NOT operator flips all bits of a number (0 becomes 1, 1 becomes 0).
-
This is a unary operator (it operates on a single operand).
-
The result depends on the number of bits used to represent the integer. In the example we use the data type
unsigned char, which stores the natural numbers from \(0\) to \(255\) with \(8\) bits; the conversion(int)displays the result as a number instead of a character.
Example 6.30: bitwise NOT
unsigned char a = 12; // Binary (8 bits): 00001100
unsigned char result = ~a; // Binary: 11110011 (decimal: 243)
cout << "~a = " << (int)result << endl; // Output: 243
Bit-by-bit operation (using the 8-bit representation):
| Decimal | |||||||||
|---|---|---|---|---|---|---|---|---|---|
a |
0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | 12 |
~a |
1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 243 |
Indeed, \(243 = 255 - 12\): flipping all the \(8\) bits of a number \(x\) gives \(255 - x\).
6.10.5 Left shift (< <)¶
-
The left shift operator shifts all bits to the left by a specified number of positions.
-
Zeros are added on the right side.
-
Shifting left by \(k\) positions is equivalent to multiplying by \(2^k\) (as long as the result can be represented by the data type).
Example 6.31: left shift
int a = 5; // Binary: 0101
int result = a << 2; // Shift left by 2 positions: 10100 (decimal: 20)
cout << "a << 2 = " << result << endl; // Output: 20
// Equivalent to: 5 * 2^2 = 5 * 4 = 20
Bit-by-bit operation:
| Decimal | Equivalent | |||||||
|---|---|---|---|---|---|---|---|---|
a |
0 | 0 | 0 | 1 | 0 | 1 | 5 | \(5\) |
a < < 1 |
0 | 0 | 1 | 0 | 1 | 0 | 10 | \(5 \times 2^1\) |
a < < 2 |
0 | 1 | 0 | 1 | 0 | 0 | 20 | \(5 \times 2^2\) |
a < < 3 |
1 | 0 | 1 | 0 | 0 | 0 | 40 | \(5 \times 2^3\) |
6.10.6 Right shift (> >)¶
-
The right shift operator shifts all bits to the right by a specified number of positions; the bits that exit on the right are lost.
-
For non-negative integers, zeros are added on the left side.
-
For non-negative integers, shifting right by \(k\) positions is equivalent to the integer division by \(2^k\), that is, to \(\lfloor x / 2^k \rfloor\).
Example 6.32: right shift
int a = 20; // Binary: 10100
int result = a >> 2; // Shift right by 2 positions: 00101 (decimal: 5)
cout << "a >> 2 = " << result << endl; // Output: 5
// Equivalent to: 20 / 2^2 = 20 / 4 = 5
Bit-by-bit operation:
| Decimal | Equivalent | |||||||
|---|---|---|---|---|---|---|---|---|
a |
0 | 1 | 0 | 1 | 0 | 0 | 20 | \(20\) |
a > > 1 |
0 | 0 | 1 | 0 | 1 | 0 | 10 | \(20 / 2^1\) |
a > > 2 |
0 | 0 | 0 | 1 | 0 | 1 | 5 | \(20 / 2^2\) |
a > > 3 |
0 | 0 | 0 | 0 | 1 | 0 | 2 | \(\lfloor 20 / 2^3 \rfloor\) |
In the last row \(20 / 2^3 = 2.5\), and the right shift gives the integer part \(2\): the bit \(1\) in position \(0\) of a > > 2 is lost.
6.10.7 Practical applications¶
The bitwise operators give short and fast solutions to common tasks. In the following examples, a mask is a number whose binary representation has the bit \(1\) only in the positions we want to act on: the mask 1 < < k has the bit \(1\) only in position \(k\).
Example 6.33: checking if a number is even or odd
int n = 15;
if (n & 1)
cout << n << " is odd" << endl;
else
cout << n << " is even" << endl;
// Explanation: n & 1 checks the least significant bit
// If it is 1, the number is odd; if it is 0, the number is even
Output:
Example 6.34: swapping two numbers without a temporary variable
int a = 5, b = 9;
cout << "Before swap: a = " << a << ", b = " << b << endl;
a = a ^ b; // a now holds a XOR b
b = a ^ b; // b = (a XOR b) XOR b = a
a = a ^ b; // a = (a XOR b) XOR a = b
cout << "After swap: a = " << a << ", b = " << b << endl;
Output:
The swap works because x x is \(0\) and x 0 is x for every integer x, and the XOR is commutative and associative.
Example 6.35: checking if a specific bit is set
int n = 12; // Binary: 1100
int position = 2; // Check the bit at position 2 (from right, starting at 0)
if (n & (1 << position))
cout << "Bit " << position << " is set (1)" << endl;
else
cout << "Bit " << position << " is not set (0)" << endl;
// Explanation: (1 << position) creates a mask with only bit at position set
// n & mask checks if that bit is 1 in n
Output:
Example 6.36: setting a specific bit to 1
Example 6.37: clearing a specific bit to 0
int n = 14; // Binary: 1110
int position = 1; // Clear the bit at position 1
n = n & ~(1 << position); // Set bit at position 1 to 0
cout << "Result: " << n << endl; // Output: 12 (Binary: 1100)
The mask ~(1 < < position) has the bit \(0\) in the given position and the bit \(1\) in all the other positions.
Example 6.38: toggling a specific bit
Key points of the chapter
-
A
C++program is compiled (for example withg++) and its execution starts from themainfunction; every instruction ends with “;”. -
Every variable is declared with its data type before use; the division between two
intvalues is the integer division. -
Conditional statements (
if,else,?:) choose which instructions are executed; loops (while,for) repeat them. -
The arrays of
C++are indexed from \(0\) to \(n-1\), while the arrays of the pseudo-code are indexed from \(1\) to \(n\); a dynamic-size array is allocated withnewand freed withdelete[]. -
A function receives its parameters by value (a copy) or by reference (
&); an array is passed through the pointer to its first element, together with its size. -
The bitwise operators act on the binary representation of the integer numbers:
x < < kmultiplies \(x\) by \(2^k\) and, for \(x \ge 0\),x > > kcomputes \(\lfloor x/2^k \rfloor\).
Exercises on this chapter: Introduction to C++