C++ Fundamentals

Basic Concepts

  • Data is any information processed or stored by a computer.
  • A Value is a specific piece of data (e.g. 42, 'A', 3.14).
  • An Object is a region of storage (memory) with a type and a value.
  • A Variable is a named object used to store data.
  • Initialization is the process of giving an initial value to an object or variable.
  • Literals: fixed values like 42, 3.14, ‘A’, “Hello”, true, nullptr.
  • Operators: symbols that act on values (+ - * / %, == != < >, && || !, = += -=, etc.).
  • An Expression is a combination of operators, constants and variables that evaluate to a value.
  • A function is a reusable block of code that performs a specific task.

1. Initialization

C++ provides several ways to initialize objects. Each form has different semantics and use cases.

1.1. Default Initialization

int a;
T object;
new T;

Performed when an object is created without any initializer.

  • Built-in types: uninitialized
  • Class types: default constructor is called

1.2. Value Initialization

int a{};
T();
new T();
T object{};
T{};
new T{};

/// @brief Also used in constructors
Class::Class() : member() {}
Class::Class() : member{} {}

Performed when an object is created with an empty initializer.

  • Built-in types: zero-initialized
  • Class types: default constructor is called

1.3 Direct Initialization

T object(arg);
T object(arg1, arg2);
T object{arg};          // since C++11
new T(args...);
static_cast<T>(other);

/// @brief Used in constructors and lambdas:
Class::Class() : member(args...) {}
[arg]() {};

Initializes an object using explicit constructor arguments.

1.4 Copy Initialization

T object = other;
f(other);
return other;
throw object;
catch (T object);

/// @brief Also used for arrays
T array[N] = { /* values */ };

Initializes an object from another object or expression.

1.5 List Initialization (since C++11)

/// @brief Direct List Initialization
T object{arg1, arg2};
new T{arg1, arg2};

Class::Class() : member{arg1, arg2} {}

/// @brief Copy List Initialization
T object = {arg1, arg2};
return {arg1, arg2};
function({arg1, arg2});

/// @brief Designated Initializers (since C++20)
T object{ .des1 = arg1, .des2 = arg2 };
T object = { .des1 = arg1, .des2 = arg2 };

Initializes objects using brace-enclosed initializer lists { }.

1.6 Aggregate Initialization

T object = {arg1, arg2};
T object{arg1, arg2};          // since C++11

/// @brief Designated initializers for aggregates (since C++20)
T object = { .des1 = arg1, .des2 = arg2 };
T object{ .des1 = arg1, .des2 = arg2 };

Initializes aggregate types (no user-defined constructors).


2. Fundamental Data Types

Memory can only store bits. Data type help compiler and CPU take care of encoding the value into the sequence of bits.

  • Fundamental Data Types
  • Compound Data Types

3.1 Primitive Type

Types Category Meaning Example
float, double, long double Floating Point a number with a fractional part 3.14159
bool Integral (Boolean) true or false true
char, wchar_t, char8_t (C++20), char16_t (C++11), char32_t (C++11) Integral (Character) a single character of text ‘c’
short int, int, long int, long long int (C++11) Integral (Integer) positive and negative whole numbers, including 0 64
std::nullptr_t (C++11) Null Pointer a null pointer nullptr
void Void no type n/a
  • sizeof used to get the size of a type in bytes. (pointer has 4 or 8 bytes based on the arch)
  • fixed-width integers are the set of integer types that are guaranteed to be the same size on any architecture #include <cstdint>
    • fast integers: guarantee at least # bits, but pick the type that the CPU can process fastest (even if it uses more memory).
    • least integers: guarantee at least # bits, but pick the type that uses the least memory (even if it’s slower).
  • size_t vs std::size_t is an unsigned integral type that is used to represent the size or length of objects. (<stddef.h>, )
  • scientific notation e/E used to present the times 10 to the power of the equation. e.g. (e.g. 5.9722 x 10²⁴ -> 5.9722e24)
  • Inf represents infinity. Inf is signed, and can be positive (+Inf) or negative (-Inf). (5/0)
  • NaN stands for “Not a Number”. (mathematically invalid)

3.2. Char And String

  • A char variable are interpreted as an ASCII character.
  • 't': Text between single quotes is treated as a char literal, which represents a single character.
  • "text": Text between double quotes (e.g. “Hello, world”) is treated as a C-style string literal, which can contain multiple characters.

4. Constant

  • A constant is a value that is not be changed during the program’s execution. There are two types of constants:
    • Named constants: are constant values that are associated with an identifier.
      • Constant variables
      • Macros with substitution text
      • Enumerated constant
    • Literal are constant values that are not associated with an identifier.Literals are values that are inserted directly into the code.

4.1. Named constants

const double gravity = 9.8; ///< Const variable
#define MY_NAME "Phong"  ///< Object-like macros with substitution text
enum Color {
    RED,    // Assigned 0
    GREEN,  // Assigned 1
    BLUE = 5, // Manually assigned 5
    YELLOW  // Assigned 6 (5 + 1)
}; ///< Enumerated constant

4.2. Literals

return 5;                       ///< 5 is an integer literal -> type: int
bool myNameIsAlex { true };     ///< true is a boolean literal -> type: bool
double d { 3.4 };               ///< 3.4 is a double literal -> type: double
cout << "Hello, world!";        ///< "Hello, world!" is a C-style string literal -> type: const char[14]
cout << 5.0 << '\n';            ///< 5.0 (no suffix) is type double (by default)
cout << 5.0f << '\n';           ///< 5.0f is type float
  • Type of a literal is deduced from the literal’s value.
  • Literal suffixes used to explicitly declare the type for a literal.

4.3. Numeral Systems (decimal, binary, hexadecimal, and octal)

  • Numeral system literals:
    • Decimal (no prefix, 42)
    • Binary (0b101010) b
    • Hexadecimal (0x2A) x
    • Octal (052) - all represent the same value.
  • Can change the output format via use of the std::dec, std::oct, and std::hex I/O manipulators:
  • Can define a std::bitset variable and tell std::bitset how many bits we want to store.
        int bin{};          // assume 16-bit int
        bin = 0x0001;       // assign binary 0000 0000 0000 0001 to the variable
        bin = 0b1;          // assign binary 0000 0000 0000 0001 to the variable
        bin = 0b11;         // assign binary 0000 0000 0000 0011 to the variable
    
        cout << std::bitset<4>{ 0b1010 } << '\n'; // create a temporary std::bitset and print it
    
        // C++14: quotation mark (‘) as a digit separator
        int bin { 0b1011'0010 };        // assign binary 1011 0010 to the variable
        long value { 2'132'673'462 };   // much easier to read than 2132673462
    

4.4. Constant Expression

  • Constant expressions is the expressions whose values can be fully determined at compile time.

  • constexpr is used to declare compile-time constants

  • Benefits:

    • Safer code
    • Optimizations
    • Compile-time evaluation
  • constexpr function is is a function that is allowed to be called in a constant expression, can be evaluated at compile time or runtime.

  • consteval function is a function that must evaluate at compile-time.

    constexpr int square(int x) { return x * x; }
    consteval int cube(int x) { return x * x * x; }
    
    constexpr int a = square(5); // compile-time
    int b = square(5);           // runtime allowed
    
    constexpr int c = cube(5);   // OK
    // int d = cube(5);          // Error if not compile-time
    

5. Operators And Bit Manipulation

5.1. Operators

5.2. Bit manipulation.

  • To define a set of bit flags, use uint8/16/32… or std::bitset
  • Refers: https://www.learncpp.com/cpp-tutorial/bit-flags-and-bit-manipulation-via-stdbitset/
    // For unsigned integers (when no overflow occurs):
    // x << n == x * 2ⁿ
    // x >> n == x / 2ⁿ
      uint8_t x = 5;          // 0b00000101
      x << 1;                 // 0b00001010 = 10
      x << 2;                 // 0b00010100 = 20
    
      uint8_t x = 20;         // 0b00010100
      x >> 1;                 // 0b00001010 = 10
      x >> 2;                 // 0b00000101 = 5
    

6. Control Flows

6.1. Constexpr if statement (C++17)

  • Condition will be evaluated at runtime.
    void main() {
      constexpr double gravity{ 9.8 };
      if constexpr (gravity == 9.8){ // now using constexpr if
        std::cout << "Gravity is normal.\n";
      } else {
        std::cout << "We are not on Earth.\n";
      }
    }
    

6.2. Switch fallthrough and scoping

  • The [[fallthrough]] attribute modifies a null statement to indicate that fallthrough is intentional (and no warnings should be triggered).
  • Initialization is not allowed before case labels because control flow in a switch may jump over the initializer, leaving the variable uninitialized.
  • Declarations without an initializer are allowed before case labels because they only reserve space for the variable in the function’s stack frame (decided at compile time). No runtime initialization code is generated, so nothing can be “skipped” by jumping to a case.
    void main() {
        int x = 2;
        switch (x) {
            int a;       //  allowed (no initializer, just reserves space)
            // int b{5}; //  not allowed (initializer may be skipped)
        case 1:
            a = 10; // safe: 'a' exists, we assign here
            std::cout << "Case 1, a = " << a << '\n';
            [[fallthrough]]; //  intentional fallthrough to case 2
        case 2:
            a = 20; // reassign
            std::cout << "Case 2, a = " << a << '\n';
            break;
        default:
            std::cout << "Default case\n";
            break;
        }
    }
    


7. printf/snprintf

printf() prints formatted output to the console or standard output. snprintf() formats output and stores it in a buffer while limiting the number of bytes written, helping prevent buffer overflows.

printf("data %d", value);

uint8_t buffer[100];
sprintf((char*)buffer, "data %d", value);
printf((char*)buffer);

Integer

Data type Specifier
int8_t / signed char %hhd
uint8_t / unsigned char %hhu
int16_t / short %hd
uint16_t / unsigned short %hu
int32_t / long %ld
uint32_t / unsigned long %lu
int64_t / long long %lld
uint64_t / unsigned long long %llu

Floating point

Data type Specifier
float %f
double %f
long double %Lf
  • %e -> scientific notation
  • %g -> auto select %f or %e

Char / String

Data type Specifier Notes
char %c single character
char* / String %s null-terminated string

Pointer / Address

Data type Specifier Notes
void* %p memory address, hex

Hex / Octal / Binary

Data type Specifier Notes
unsigned int %x / %X hexadecimal
unsigned int %o octal
Arduino only %b binary

Flags, Width, Precision

  • %-10d -> left-justify, width 10
  • %010d -> pad with zeros, width 10
  • %.2f -> 2 decimal digits
  • %*d -> dynamic width


8. Memory Functions

The header provides C-style memory and string manipulation functions. Common functions:

  • memset : Fill a block of memory with the same byte value.
  • memcpy : Copy a block of memory from source to destination.
  • memmove : Copy memory safely when source and destination overlap.
  • memcmp : Compare two blocks of memory.