Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 2

Data Types and Variables

Recommended resources

Hello! Welcome back to your C++ journey.

In our last lesson, you successfully wrote, compiled, and ran your first C++ programs from the command line. You learned about the critical build processโ€”preprocessing, compilation, and linkingโ€”which turns human-readable source code into an executable file. This gives you direct control over how your program is built, a skill that is non-negotiable for high-performance systems development.

Today, we'll shift our focus from the build process to the code itself. We'll explore the most fundamental building blocks of any program: variables and data types. You'll learn how to store information like numbers and characters in memory, and more importantly, how to differentiate between the various types C++ offers.

For your goal of building a telemetry engine, this knowledge is paramount. Your system will capture millions of events per second, and each event will be a packet of data. Choosing the right data type for timestamps, thread IDs, and event payloads has a direct impact on memory consumption, cache performance, and network bandwidth. Every single byte counts.

What are Variables?

At a high level, a variable is a named piece of storage in the computer's memory. The data type of a variable tells the compiler two crucial things:

  1. How much memory to allocate for the variable.
  2. What kinds of operations are valid for that variable.

Think of it as a set of labeled boxes, where each box is designed to hold a specific kind of item.

C++ Data Types Classification
This chart provides a high-level overview of how data types are categorized in C++. We will be focusing on the **Fundamental Types** in this lesson.

To get a solid introduction to variables and the primary data types, let's start with a video.

Variables in C++

The video 'Variables in C++' by The Cherno provides a clear and concise explanation of what variables are and introduces the most common primitive types. It directly connects the type of a variable to the amount of memory it uses.

Watch the first four minutes of the video (from 0:09 to 3:59). Pay close attention to how a variable is declared and initialized, and the introduction of the int data type.

As the video explained, you declare a variable by specifying its type, giving it a name, and optionally providing an initial value.

The classic way is using the assignment operator (=):
int my_number = 42;

However, a more modern and generally safer approach in C++ is to use uniform initialization with curly braces {}:
int my_number{42};

We recommend using this style as it helps prevent certain types of bugs, like accidental "narrowing" conversions (e.g., trying to fit a large number into a smaller type).


The Fundamental Data Types

C++ provides a set of built-in types for handling integers, floating-point numbers, characters, and boolean values. The primary difference between them is their size in memory, which in turn determines the range of values they can store.

Integer Types

Integers are whole numbers. C++ offers several types to accommodate different ranges.

  • char: The smallest integer type. Typically 1 byte. As its name implies, it's also used to store characters.
  • short: A short integer. Typically 2 bytes.
  • int: The "natural" integer size for the machine. Usually 4 bytes on modern systems.
  • long: A long integer. Its size is platform-dependent, often 4 or 8 bytes.
  • long long: A very long integer. Typically 8 bytes.

You can check the size of any data type using the sizeof operator, which returns the size in bytes.

#include <iostream>

int main() {
    std::cout << "Size of int: " << sizeof(int) << " bytes" << std::endl;
    std::cout << "Size of short: " << sizeof(short) << " bytes" << std::endl;
    std::cout << "Size of long long: " << sizeof(long long) << " bytes" << std::endl;
    return 0;
}

Signed vs. Unsigned Integers

By default, integer types are signed, meaning they can hold both positive and negative values. One bit is used to store the sign. If you know a variable will only ever hold non-negative values (like counts or sizes), you can declare it as unsigned. This uses the sign bit for the value itself, effectively doubling the maximum positive value it can hold.

unsigned int positive_counter{100};

This concept is crucial for understanding data representation at the bit level.

Variables in C++

Let's return to The Cherno's video to see how unsigned works and to get an overview of the other integer and floating-point types.

Watch the rest of the video (from 3:59 to 12:32). Focus on the explanation of the sign bit, the list of integer types, floating-point types (float, double), the boolean type (bool), and finally, the sizeof operator.

Floating-Point Types

For numbers with fractional parts, you use floating-point types:

  • float: Single-precision floating-point number. Typically 4 bytes.
  • double: Double-precision floating-point number. Typically 8 bytes, offering greater precision than float.

When writing a floating-point literal, it's good practice to add an f suffix for floats to distinguish them from doubles:
float pi_approx{3.14f};
double precise_pi{3.1415926535};


Numeric Bases and Data Representation

Your telemetry engine will need to serialize data into a compact binary format. Understanding how numbers are represented in memory is key. Computers work in binary (base-2), but for readability, programmers often use hexadecimal (base-16) as a compact way to represent binary data.

Handling Numeric and Binary Data in C++

The article 'Handling Numeric and Binary Data in C++' explains how decimal, binary, and hexadecimal number systems work and how to use them in C++.

Read the 'Numeric Bases' section. Pay attention to the 0b prefix for binary literals and the 0x prefix for hexadecimal literals.

For example, a 32-bit color value with full red and full alpha might be written in hex as 0xFF0000FF. This is much clearer than the decimal or binary equivalent.


A Deeper Dive for Systems Programming: Fixed-Width Integers

The fundamental types like int and long are problematic for systems programming because their size is implementation-defined. An int might be 32 bits on your desktop but could be different on another platform. This lack of portability is unacceptable when defining data structures for serialization or network protocols.

To solve this, C++ provides fixed-width integer types in the <cstdint> header. These types guarantee their exact size in bits, regardless of the platform.

Fixed-Width Type Description
int8_t 8-bit signed integer
uint8_t 8-bit unsigned integer
int16_t 16-bit signed integer
uint16_t 16-bit unsigned integer
int32_t 32-bit signed integer
uint32_t 32-bit unsigned integer
int64_t 64-bit signed integer
uint64_t 64-bit unsigned integer

For your telemetry engine, you should almost exclusively use these types for your data structures. For instance, a high-resolution timestamp should be a uint64_t. An event type identifier could be a uint8_t. This ensures your data format is consistent everywhere.

The Ultimate Guide to C++ Data Types

The following guide provides an excellent breakdown of the different data types, with a focus on the distinction between platform-dependent types and fixed-width types.

Read the sections 'What Does Fixed-Width Mean?', 'What Does Dynamic-Width Mean?', 'Comparison Table', 'When to Use Each', and the section on 'int_leastN_t vs int_fastN_t'. This will solidify why fixed-width types are essential for your project.

A rule of thumb for systems programming:

  • Use fixed-width types (int32_t, uint64_t, etc.) for data that will be part of a structure, serialized, or sent over a network.
  • It's generally fine to use a plain int for temporary variables like loop counters where the exact size isn't critical.
Test your understanding!

You are defining a struct for a "function call" event in your telemetry engine. It needs to store the following information:

  1. A high-resolution timestamp from a monotonic clock. This will be a very large, non-negative number.
  2. The ID of the thread that the event occurred on. Let's assume you'll support up to 65,535 threads.
  3. The memory address of the function that was called. Memory addresses are integer-like values whose size matches the system's architecture (e.g., 64-bit on a 64-bit OS).
  4. The duration of the function call in milliseconds. This can be a fractional value.

For each piece of data, which C++ data type (from <cstdint> or fundamental types) would be the most appropriate choice and why?

Show answer
  1. Timestamp: uint64_t. A timestamp is a large, always positive value. uint64_t provides a guaranteed 64-bit range, which is essential for high-resolution timing without risk of overflow over long periods.
  2. Thread ID: uint16_t. Since the maximum value is 65,535 and thread IDs are non-negative, uint16_t is a perfect fit. It has a range of 0 to 65,535 and uses only 2 bytes, making it more memory-efficient than int or uint32_t.
  3. Function Address: uintptr_t. This special fixed-width unsigned integer type from <cstdint> is guaranteed to be large enough to hold a pointer. Using it makes your code's intent clear and ensures portability between 32-bit and 64-bit systems.
  4. Duration: float. Since the duration can be fractional, a floating-point type is needed. float (4 bytes) is likely sufficient for milliseconds and is more memory-efficient than double (8 bytes). For a high-frequency telemetry system, saving 4 bytes per event is a significant optimization.

Variable Naming and Initialization

Finally, a few practical rules are essential for writing clean, bug-free code.

  1. Always Initialize Your Variables: Using a variable before it has been given a value leads to undefined behavior. Your program might crash or produce strange results.
  2. Use Meaningful Names: thread_id is much better than t. Good names make code self-documenting.
  3. Follow Naming Conventions: Common C++ conventions include camelCase (e.g., myVariable) or snake_case (e.g., my_variable). Pick one and stick to it for consistency.

C++ FOR BEGINNERS (2025) - Variables, Data Types, Overflow, Sizeof PROGRAMMING TUTORIAL

The video 'C++ FOR BEGINNERS' by CodeBeauty offers excellent practical advice on these topics.

Watch the segment from 6:39 to 11:39, which demonstrates the danger of using an uninitialized variable. Then watch from 15:19 to 22:55, which covers the essential rules for naming variables.


Conclusion

In this lesson, we've drilled down into the fundamental units of data storage in C++. Understanding these concepts is not just academic; it's the first step in engineering a high-performance system.

Key Takeaways:

  • Variables are named storage locations in memory. Their data type determines their size and behavior.
  • The main fundamental types are for integers (char, int, long long), floating-point numbers (float, double), and booleans (bool).
  • The sizeof operator is a key tool for querying the memory size of a type.
  • The unsigned keyword is used for integers that will only ever be non-negative, doubling their positive range.
  • For performance-critical and portable systems like your profiler, you should prefer fixed-width integers from <cstdint> (e.g., uint64_t, int32_t) to ensure consistent data layouts.
  • Always initialize your variables upon declaration to avoid undefined behavior.

Now that you know how to store data, our next lesson, "Control program flow using conditionals (if/else) and loops (for/while)," will teach you how to make your program execute different code paths and perform repetitive tasks. This will allow you to start building logic that acts upon the data you've stored.