enum
, union
, struct
, POD structsmain()
function, which serves as the entry point.{}
.#include <iostream>
int main() { // Or, more completely: int main(int argc, char** argv, char** envp)
std::cout << "Hello, world!" << std::endl;
return 0;
}
#include <iostream>
: Includes the Input/Output stream library.int main()
: Entry point of the program.std::cout
: Standard output stream.<<
: Stream insertion operator."Hello, world!"
: Text to be printed.<< std::endl
: Newline character.return 0;
: Indicates successful program execution.To compile the program:
g++ hello_world.cpp -o hello_world
To run the compiled program:
./hello_world [arg1] [arg2] ... [argN]
To check exit code:
echo $?
int x = 5;
char ch = 'A';
float f = 3.14;
x = 1.6; // Legal, but truncated to the 'int' 1.
f = "a string"; // Illegal.
unsigned int y{3.0}; // Uniform initialization: illegal.
Data Type | Size (Bytes) |
---|---|
bool |
1 |
(unsigned ) char |
1 |
(unsigned ) short |
2 |
(unsigned ) int |
4 |
(unsigned ) long |
4 or 8 |
(unsigned ) long long |
8 |
float |
4 |
double |
8 |
long double |
8, 12, or 16 |
int
, short
, long
, and long long
.int age = 30;
short population = 32000;
long long large_number = 123456789012345;
float
, double
, and long double
.float pi = 3.14;
double gravity = 9.81;
Floating-point arithmetic is a method for representing and performing operations on real numbers
Representation: Floating-point numbers consist of three components: sign
Normalized numbers: In normalized form, the most significant bit of the significand is always 1, allowing for a wide range of values to be represented efficiently.
IEEE 754 standard: The most commonly adopted standard for floating-point arithmetic is the IEEE 754 Standard for Floating-Point Arithmetic. This standard specifies the formats, precision, rounding rules, and handling of special values like NaN (Not-a-Number) and infinity.
double epsilon = 1.0; // Machine epsilon.
while (1.0 + epsilon != 1.0) {
epsilon /= 2.0;
}
double a = 0.1, b = 0.2, c = 0.3;
if (a + b == c) { // Unsafe comparison.
// This may not always be true due to precision limitations.
}
double x = 1.0, y = 1.0 / 3.0; double sum = y + y + y;
if (std::abs(x - sum) < tolerance) { // Safer comparison.
// Use tolerance to handle potential rounding errors.
}
char
type.std::string
type.char comma = ',';
std::string name = "John";
std::string greeting = "Hello";
// Concatenate strings.
std::string message = greeting + comma + ' ' + name;
bool
.true
or false
.bool is_true = true;
bool is_false = false;
if (-1.5) // true.
// ...
if (0) // false.
// ...
int x = 5; // Direct initialization.
int y(10); // Constructor-style initialization.
int z{15}; // Uniform initialization (preferred).
using number = double;
using int_array = int*;
auto
and type conversions.In many situations, the compiler can determine the correct type of an object using the initialization value.
auto a{42}; // int.
auto b{12L}; // long.
auto c{5.0F}; // float.
auto d{10.0}; // double.
auto e{false}; // bool.
auto f{"string"}; // char[7].
// C++11.
auto fun1(const int i) -> int { return 2 * i; }
// C++14.
auto fun2(const int i) { return 2 * i; }
In reality, things are much more complex: see, for instance, Explicit type conversion and related pages.
int stack_var = 42; // Stack variable.
int* stack_ptr = &stack_var; // Pointer to stack variable.
int* heap_ptr = new int(42); // Pointer to heap variable.
// ...
delete heap_ptr;
heap_ptr = nullptr;
int x = 5; // Declaration and initialization.
x = 10; // Variable modification.
int y; // Declaration with default initialization.
y = 20; // Initialization after declaration.
const double a = 3.7;
a = 5; // Error!
*
symbol.int number = 42;
int* pointer = &number; // Pointer to 'number'.
// Create a dynamic integer with new.
int* dynamic_variable = new int;
*dynamic_variable = 5;
// Deallocate it.
delete dynamic_variable;
dynamic_variable = nullptr;
int* arr = new int[5]; // Dynamically allocate an integer array.
// Access and use the array beyond its allocated size.
for (int i = 0; i <= 5; ++i) {
arr[i] = i;
}
// Forgot to delete the dynamically allocated array, causing a memory leak.
// delete[] arr;
// Attempt to access memory beyond the allocated array's bounds, causing undefined behavior.
std::cout << arr[10] << std::endl;
&
symbol.int a = 10;
int& ref = a; // Reference to 'a'.
ref = 20; // Modifies 'a'.
int b = 10;
ref = b;
ref = 5; // What's now the value of 'a' and 'b'?
std::array<type>
, std::vector<type>
(to be covered in a sequent lecture).int numbers[5]; // Array declaration.
numbers[0] = 1; // Assigning values to elements.
int* dynamic_array = new int[5];
for (int i = 0; i < 5; ++i) {
dynamic_array[i] = 2 * i;
}
delete[] dynamic_array;
if
... else if
... else
if
, else if
, and else
statements for conditional execution.int x = 10;
if (x > 5) {
std::cout << "x is greater than 5." << std::endl;
} else if (x > 3) {
std::cout << "x is greater than 3 but not greater than 5." << std::endl;
} else {
std::cout << "x is not greater than 5." << std::endl;
}
switch
... case
switch
statement is a control flow structure alternative to using multiple if
... else
statements based on the value of an expression.switch (expression) {
case constant1:
// Code to execute if expression == constant1.
break;
case constant2:
// Code to execute if expression == constant2.
break;
// ... more cases ...
default:
// Code to execute if expression doesn't match any case.
}
for
loopfor
loop is used to execute a block of code a specific number of times. It is often used when the number of iterations is known beforehand.for (initialization; condition; post-iteration operation) {
// Code to execute in each iteration.
}
for (int i = 0; i < 5; ++i) {
std::cout << "Iteration: " << i << std::endl;
}
NB: i
is usually named counter
. A more general concept, called iterator
will provide a generalization for custom objects.
while
loopwhile
loop is used to repeat a block of code as long as the specified condition remains true
. It is useful when the number of iterations is not predetermined.while (condition) {
// Code to execute while the condition is true.
}
int i = 0;
while (i < 5) {
std::cout << "Iteration: " << i << std::endl;
++i;
}
NB: A do { ... } while(...)
loop is similar to a while
loop but guarantees that the block of code will be executed at least once, as the condition is checked after the loop body.
int add(int a, int b) {
return a + b;
}
int result = add(3, 4); // Calling the 'add' function.
void
void
is a data type that represents the absence of a specific type.void greet() {
std::cout << "Hello, world!" << std::endl;
}
void* generic_ptr;
int x = 10;
generic_ptr = &x; // Can point to any data type.
void modify_by_copy(int x) {
// Creates a copy of 'x' inside the function.
x = 20; // Changes the copy 'x', not the original value.
}
void modify_by_ptr(int* ptr) {
*ptr = 30; // Modifies the original value via the pointer.
}
void modify_by_ref(int& ref) {
ref = 40; // Modifies the original value through the reference.
}
int value = 10;
modify_by_copy(value); // Pass by value.
std::cout << value << std::endl; // Output: 10.
modify_by_ptr(&value); // Pass by pointer
std::cout << value << std::endl; // Output: 30.
modify_by_ref(value); // Pass by reference
std::cout << value << std::endl; // Output: 40.
int get_copy() {
return 42; // Return a copy of the value.
}
int* get_ptr() {
int* arr = new int[5];
// ...
return arr; // Return a pointer to the array.
}
int& get_ref() {
static int value = 10; // static means that the variable persists
// through multiple calls to this function.
// Beware: if not static, undefined behavior
// (value gets destroyed).
return value; // Return a reference to 'value'.
}
int result1 = get_copy(); // Return by value.
int* result2 = get_ptr(); // Return by pointer.
result2[2] = 5;
delete[] result2; // Beware: memory leaks.
int& result3 = get_ref(); // Return by reference.
result3 = 20;
const
correctness (1/2)void print_value(const int x) {
// x = 42; // Error: Cannot modify 'x'.
}
const int get_copy() {
const int x = 42;
return x;
}
int result = get_copy();
result = 10; // Safe, it's a copy!
const int age = 30; // Immutable variable.
const int* ptr_to_const = &age; // Pointer to an integer which is constant.
ptr_to_const = &result; // Now pointing to another variable.
*ptr_to_const = 42; // Error: cannot modify pointed object.
Question: how to declare a constant pointer to a non-constant int
?
const
correctness (2/2)const
to indicate read-only data and functions.const
can lead to compiler errors or unexpected behavior.+
, -
, *
, /
, %
+=
, -=
, *=
, /=
, %=
==
, !=
, <
, >
, <=
, >=
, <=>
(C++20)&&
, ||
, !
int x = 5, y = 3;
bool is_true = (x > y) && (x != 0); // Logical expression.
int z = (x > y) ? 2 : 1; // Ternary operator.
x += 2; // 7.
y *= 4; // 12.
z /= 2; // 1.
Pre-increment (++var
):
Post-increment (var++
):
int a = 5;
int b = ++a; // Pre-increment.
// a is now 6, b is also 6.
int c = a++; // Post-increment.
// a is now 7, but c is 6.
void print(int x) {
std::cout << "Integer value: " << x << std::endl;
}
void print(double x) {
std::cout << "Double value: " << x << std::endl;
}
print(3); // Calls the int version.
print(2.5); // Calls the double version.
enum
enum Color : unsigned int {
Red = 0,
Green,
Blue
};
Color my_color = Green;
union
union Duration {
int seconds;
short hours;
};
Duration d;
d.seconds = 259200;
short h = d.hours; // Contains garbage: undefined behavior.
struct
struct Point {
int x;
int y;
};
Point p;
p.x = 3;
p.y = 5;
Actually, in C++ struct
is just a special type of class
. When Referring to C-style structs, a more proper name would be Plain Old Data (POD) structs.
struct Rectangle {
double width;
double height;
};
Rectangle r;
r.width = 10;
r.height = 20;
Rectangle s{5, 10};
Rectangle t = s; // POD structs are trivially copyable.
int x; // Declaration of 'x'.
extern int y; // Declaration of 'y'.
struct X; // Forward-declaration. What if I want to use both X in Y and Y in X?
struct Y { X var; };
int x = 5; // Definition of 'x'.
int add(int a, int b); // Declaration of 'add' function.
int add(int a, int b) { // Definition of 'add' function.
return a + b;
}
.h
or .hpp
) for declarations.cpp
) for definitions.cpp
) for non-template classes.h
or .hpp
) contain declarations and prototypes.// my_module.hpp
int add(int a, int b); // Function prototype.
#pragma once
to prevent multiple inclusions..cpp
) contain the definitions of functions and classes.// my_module.cpp
#include "my_module.hpp" // Include the corresponding header.
int add(int a, int b) {
return a + b;
}
Without header guards, if a header file is included multiple times in a source file or across multiple source files, it can lead to redefinition errors.
#ifndef
, #define
, and #endif
or #pragma once
directives in the header file.Example (file my_module.hpp
):
#ifndef MY_MODULE_HPP__
#define MY_MODULE_HPP__
// ...
#endif // MY_MODULE_HPP__
Modern compilers also support:
#pragma once
// ...
To avoid issues with header file inclusions:
int x = 10;
{ // Manually define a scope.
int y = 20;
// ...
} // Destroy all variables local to the scope.
// Beware: dynamically allocated variables must be deleted manually.
std::cout << y << std::endl; // Error: 'y' is undefined here.
::
operator.namespace Math {
int add(int a, int b) {
return a + b;
}
}
int result1 = Math::add(3, 4); // Accessing a namespace member.
using namespace Math; // Useful, but dangerous due to possible name clashes.
int result2 = add(3, 4);
Anonymous (i.e. unnamed) namespaces are only accessible from the current compilation unit.
cpp
) handles preprocessing directives.g++
, clang++
) translates source code into object files.g++
or clang++
.module.hpp
, module.cpp
, main.cpp
):# Preprocessor.
g++ -E module.cpp -I/path/to/include/dir -o module_preprocessed.cpp
g++ -E main.cpp -I/path/to/include/dir -o main_preprocessed.cpp
# Compiler.
g++ -c module_preprocessed.cpp -o module.o
g++ -c main_preprocessed.cpp -o main.o
ld
) combines object files and resolves external references.g++ module.o main.o -o my_program
Link against an external library:
g++ module.o main.o -o my_program -lmy_lib -L/path/to/my/lib
In this example, the -lmy_lib
flag is used to link against the library libmy_lib.so
. The -l
flag is followed by the library name without the lib
prefix and without the file extension .so
(dynamic) or .a
(static).
For small projects with few dependencies, the following command performs the preprocessing, compilation and linking phase:
g++ module1.cpp module2.cpp main.cpp -I/path/to/include/dir -o my_program
Please keep in mind that different compilers can yield different behaviors and trigger distinct warnings or errors or print them in a less/more human-readable format.
For a demonstration, see this example on GodBolt comparing the output of GCC and Clang on the same code.
./my_program
If linked against an external dynamic library, the loader has to know where it is located. The list of directories where to find dynamic libraries is contained in the colon-separated environment variable LD_LIBRARY_PATH
.
export LD_LIBRARY_PATH+=:/path/to/my/lib
./my_program