Beginner's Guide
    First Steps
    Input & Output
    Custom Types – Part 1
    Diagnostics
    Standard Library – Part 1
    Function Objects
    Standard Library – Part 2
    Code Organization
    Custom Types – Part 2
    Generic Programming
    Memory Management
    Software Design Basics

    Fundamental Types Fundamental Types Fundamental Types

    Fundamental Types are the basic building blocks for all complex types / data structures like lists, hash maps, trees, graphs, …

    Variable Declarations

    Variables of fundamental types are not initialized by default!
    int k;  // k not initialized!
    cout << k << '\n';  // value might be anything

    Because in C++ you only pay for what you use (initilization of large memory blocks can be quite expensive)

    But: You should almost always initialize variables when declaring them to prevent bugs!

    Quick Overview

    bool b1 = true;
    bool b2 = false;
    • smallest integer; usually 1 byte
    • on x86/x86_64 signed ⇒ values ∈ [-128,127]
    char c = 'A';  // character literal
    char a = 65;   // same as above

    n bits ⇒ values ∈ [-2(n-1), 2(n-1)-1]

    short s = 7;        
    int   i = 12347;
    long  l1 = -7856974990L;
    long long  l2 = 89565656974990LL;
    
    // ' digit separator C++14 long l3 = 512'232'697'499;

    n bits ⇒ values ∈ [0, 2n-1]

    unsigned u1 = 12347U; 
    unsigned long u2 = 123478912345UL;  
    unsigned long long u3 = 123478912345ULL;  
    
    // non-decimal literals unsigned x = 0x4A; // hexadecimal unsigned b = 0b10110101; // binary C++14
    • float usually IEEE 754 32 bit
    • double usually IEEE 754 64 bit
    • long double usually 80-bit on x86/x86-64
    float       f  = 1.88f;
    double      d1 = 3.5e38;
    long double d2 = 3.5e38L; C++11
    
    // ' digit separator C++14 double d3 = 512'232'697'499.052;

    Arithmetic Operations

    • expression a ⊕ b returns result of operation ⊕ applied to the values of a and b
    • expression a ⊕= b stores result of operation ⊕ in a
    int a = 4; 
    int b = 3; 
    
    a = a + b; a += b;
    a = a - b; a -= b;
    a = a * b; a *= b;
    a = a / b; a /= b;
    a = a % b;
    variable a is set to value 4
    variable b is set to value 3
    
    a: 7 add a: 10
    a: 7 subtract a: 4
    a: 12 multiply a: 36
    a: 12 divide a: 4
    a: 1 remainder of division (modulo)modulo

    Increment/Decrement

    • changes value by +/- 1
    • prefix expression ++x / --x returns new (incremented/decremented) value
    • postfix expression x++ / x-- increments/decrements value, but returns old value
    int a = 4;
    int b = 3;
    
    b = a++; b = ++a;
    b = --a; b = a--;
    a: 4
            b: 3
    
    a: 5 b: 4 a: 6 b: 6
    a: 5 b: 5 a: 4 b: 5

    Comparisons

    result of comparison is either true or false

    int x = 10;                       
    int y = 5;                       
    
    bool b1 = x == 5; bool b2 = (x != 6);
    bool b3 = x > y; bool b4 = x < y; bool b5 = y >= 5; bool b6 = x <= 30;
    
    result  operator
    
    false equals true not equal
    true greater false smaller true greater/equal true smaller/equal
    (a <=> b) < 0 if a < b
    (a <=> b) > 0 if a > b
    (a <=> b) == 0 if a and b are equal/equivalent

    3-way comparisons return a comparison category value that can be compared to literal 0.

    The returned value comes from one of three possible categories: std::strong_ordering, std::weak_ordering or std::partial_ordering.

    4 <=> 6 → std::strong_ordering::less
    5 <=> 5 → std::strong_ordering::equal
    8 <=> 1 → std::strong_ordering::greater

    Boolean Logic

    bool a = true;
    bool b = false;
    bool c = a && b;   // false    logical AND
    bool d = a || b;   // true     logical OR
    bool e = !a;       // false    logical NOT
    Alternative Spellings:
    bool x = a and b;  // false
    bool y = a or b;   // true
    bool z = not a;    // false
    • 0 is always false;
    • everything else is true;
    bool f = 12;   // true   (int → bool)
    bool g = 0;    // false  (int → bool)
    bool h = 1.2;  // true   (double → bool)

    The second operand of a boolean comparison is not evaluated if the result is already known after evaluating the first operand.

    int i = 2;  
    int k = 8;
    bool b1 = (i > 0) || (k < 3);
    i > 0 is already truek < 3 is not evaluated, because the result of logical OR is already true

    Memory Sizes of Fundamental Types

    #include <stdfloat>
    // storage bits: sign + exponent + mantissa
    std::float16_t  a = 12.3f16;   // 1 +  5  +  10 =  16 bits =  2 B
    std::float32_t  b = 12.3f32;   // 1 +  8  +  23 =  32 bits =  4 B
    std::float64_t  c = 12.3f64;   // 1 + 11  +  52 =  64 bits =  8 B
    std::float128_t d = 12.3f128;  // 1 + 15  + 112 = 128 bits = 16 B
    std::bfloat16_t e = 12.3b16;   // 1 +  8  +   7 =  16 bits =  2 B

    std::numeric_limits<type>

    #include <limits>
    // smallest negative value:
    cout << std::numeric_limits<double>::lowest();
    // float/double: smallest value > 0
    // integers: smallest value
    cout << std::numeric_limits<double>::min();
    // largest positive value:
    cout << std::numeric_limits<double>::max();
    // smallest difference btw. 1 and next value:
    cout << std::numeric_limits<double>::epsilon();
    …
    
    std::numeric_limits values and binary representations for signed integers, unsigned integers and floating point numbers

    (click for fullscreen view)

    Type Narrowing Narrowing

    • conversion from type that can represent more values to one that can represent less
    • may result in loss of information
    • in general no compiler warning – happens silently
    • potential source of subtle runtime bugs
    double   d = 1.23456;
    float    f = 2.53f;
    unsigned u = 120u;
    
    double e = f; // OK float → double
    int i = 2.5; // NARROWING double → int int j = u; // NARROWING unsigned int → int int k = f; // NARROWING float → int
    type variable { value };
    • works for all fundamental types
    • narrowing conversion ⇒ compiler warning
    double   d {1.23456};  // OK
    float    f {2.53f};    // OK
    unsigned u {120u};     // OK
    
    double e {f}; // OK float → double
    int i {2.5}; // COMPILER WARNING: double → int int j {u}; // COMPILER WARNING: unsigned int → int int k {f}; // COMPILER WARNING: float → int

    Make sure to prevent silent type conversions, especially narrowing unsigned to signed integer conversions — they can cause hard-to-find runtime bugs!

    Bitwise Operations

    Bitwise Logic Logic

    & b bitwise AND
    | b bitwise OR
    ^ b bitwise XOR
    ~a bitwise NOT (one's complement)
    #include <cstdint>
    std::uint8_t a = 6;  
    std::uint8_t b = 0b00001011;
    std::uint8_t c1 = (a & b);  // 2
    std::uint8_t c2 = (a | b);  // 15
    std::uint8_t c3 = (a ^ b);  // 13
    std::uint8_t c4 = ~a;       // 249
    std::uint8_t c5 = ~b;       // 244
    // test if int is even/odd:
    bool a_odd  = a & 1;
    bool a_even = !(a & 1);
    memory bits:
    0000 0110
    0000 1011
    0000 0010
    0000 1111
    0000 1101
    1111 1001
    1111 0100
    result:
    0 ⇒ false
    1 ⇒ true

    Bitwise Shifts Shifts

    << n returns x's value with its bits shifted by n places to the left
    >> n returns x's value with its bits shifted by n places to the right
    <<= n modifies x by shifting its bits by n places to the left
    >>= n modifies x by shifting its bits by n places to the right
    #include <cstdint>
    std::uint8_t a = 1;
    a <<= 6;  // 64
    a >>= 4;  // 4
    std::uint8_t b1 = (1 << 1);  // 2
    std::uint8_t b2 = (1 << 2);  // 4
    std::uint8_t b3 = (1 << 4);  // 16
    memory bits:
    0000 0001
    0100 0000
    0000 0100
    0000 0010
    0000 0100
    0001 0000

    Shifting the bits of an object whose type has N bits by N or by more than N places is undefined behavior!

    std::uint32_t i = 1;  // 32 bit type
    i <<= 32;   UNDEFINED BEHAVIOR!
    std::uint64_t j = 1;  // 64 bit type
    j <<= 70;   UNDEFINED BEHAVIOR!

    Arithmetic Conversions & Promotions

    Unfortunately, there's a lot of rules (that go back to C) whose purpose is to determine a common type for both operands and the result of a binary operation

    Operand A ⊕ Operand B → Result
    • long doubleany other typelong double
    • doublefloatdouble
    • doubleany integerdouble
    • floatany integerfloat
    1. integer promotion is first applied to both operands:
      basically everything smaller than int gets promoted to either int or unsiged int (depending on which can represent all values of the unpromoted type)
    2. integer conversion is then applied if both operand types are different

      • both signed: smaller type converted to larger
      • both unsigned: smaller type converted to larger
      • signed ⊕ unsigned:

        1. signed converted to unsigned if both have same width
        2. otherwise unsigned converted to signed if that can represent all values
        3. otherwise both converted to unsigned