4.1 — Introduction to fundamental data types

Bits, bytes, and memory addressing

In lesson 1.3 -- Introduction to variables, we talked about the fact that variables are names for a piece of memory that can be used to store information. To recap briefly, computers have random access memory (RAM) that is available for programs to use. When a variable is defined, a piece of that memory is set aside for that variable.

The smallest unit of memory is a binary digit (also called a bit), which can hold a value of 0 or 1. You can think of a bit as being like a traditional light switch -- either the light is off (0), or it is on (1). There is no in-between. If you were to look at a random segment of memory, all you would see is …011010100101010… or some combination thereof.

Memory is organized into sequential units called memory addresses (or addresses for short). Similar to how a street address can be used to find a given house on a street, the memory address allows us to find and access the contents of memory at a particular location.

Perhaps surprisingly, in modern computer architectures, each bit does not get its own unique memory address. This is because the number of memory addresses are limited, and the need to access data bit-by-bit is rare. Instead, each memory address holds 1 byte of data. A byte is a group of bits that are operated on as a unit. The modern standard is that a byte is comprised of 8 sequential bits.

Key insight

In C++, we typically work with “byte-sized” chunks of data.

The following picture shows some sequential memory addresses, along with the corresponding byte of data:

Memory Addressing

As an aside...

Some older or non-standard machines may have bytes of a different size (from 1 to 48 bits) -- however, we generally need not worry about these, as the modern de-facto standard is that a byte is 8 bits. For these tutorials, we’ll assume a byte is 8 bits.

Data types

Because all data on a computer is just a sequence of bits, we use a data type (often called a “type” for short) to tell the compiler how to interpret the contents of memory in some meaningful way. You have already seen one example of a data type: the integer. When we declare a variable as an integer, we are telling the compiler “the piece of memory that this variable uses is going to be interpreted as a non-fractional number”.

When you assign a value to an object, the compiler and CPU take care of encoding your value into the appropriate sequence of bits for that data type. When you ask for your value back, your number is “reconstituted” from the sequence of bits in memory.

As an example, when you give an integer object the value 65, that value is stored in memory as the sequence of bits 0100 0001. When you need that value, those bits are reconstituted back into the integer value 65.

Fortunately, the compiler and CPU take care of converting values into bit sequences (and reconstructing those values from bit sequences again when needed), so you don’t need to worry about that part. All you need to do is pick a data type for your object that best matches your desired use.

Fundamental data types

C++ comes with built-in support for many different data types. These are called fundamental data types, but are often informally called basic types, primitive types, or built-in types.

Here is a list of the fundamental data types, some of which you have already seen:

Types Category Meaning Example
long double
Floating Point a number with a fractional part 3.14159
bool Integral (Boolean) true or false true
char8_t (C++20)
char16_t (C++11)
char32_t (C++11)
Integral (Character) a single character of text ‘c’
long long (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

This chapter is dedicated to exploring these fundamental data types in detail (except std::nullptr_t, which we’ll discuss when we talk about pointers). C++ also supports a number of other more complex types, called compound types. We’ll explore compound types in a future chapter.

Author's note

The terms “integer” and “integral” are similar, but have different meanings. Integers are positive and negative whole numbers, including 0. The term “integral types” (which means “like an integer”) includes all of the boolean, characters, and integer types (and thus is a bit broader in definition). Integral types are named so because they are stored in memory as integers, even though they behave slightly differently.

The _t suffix

Many of the types defined in newer versions of C++ (e.g. std::nullptr_t) use a _t suffix. This suffix means “type”, and it’s a common nomenclature applied to modern types.

If you see something with a _t suffix, it’s probably a type. But many types don’t have a _t suffix, so this isn’t consistently applied.

4.2 -- Void
3.x -- Chapter 3 summary and quiz

74 comments to 4.1 — Introduction to fundamental data types

  • CJ

    When you say,

    "You can also assign them values on the declaration line:
    int nValue1 = 5, nValue2 = 6;
    int nValue3(7), nValue4(8);

    Which is effectively the same as:
    int nValue1 = 5;
    int nValue2 = 6;
    int nValue3 = 7;
    int nValue4 = 8;"

    Shouldn't the last two lines be int nValue3(7) and int nValue4(8) because they are supposed to be implicit?

  • Tate

    Would initializing the intiger x as it is taken from input work?

    cin >> int x;
  • Ben

    I used the following code:

     int x, y = 5; 

    and it worked just fine, but the tutorial said it would not compile. Now I'm confused..

    • Actually if you read closely, I said it was dangerous, not that it wouldn't compile. It WILL compile, but x will be uninitialized. Most new programmers assume that it will be initialized to 5, which is not the case. That's why this is particularly dangerous.

  • Bob

    What is the fundamental difference between explicit and implicit assignment? Is there any reason to use one over the other? Is there any difference between 'int nValue = 50' and 'int nValue(50)'? Does the compiler treat them differently? Or is the end result always the same regardless; nValue = 50.

  • Ali

    what is the difference between the explicit assignment and the implicit assignment ? what does each one differ than another? where should I use them?

    • As far as I know, when it comes to built-in data types, there's no substantive difference. I ran some timing tests on each and they performed identically in my test cases.

  • CuView
    int main()
       using namespace std;
       int x;
       cin >> x;
       return 0;

    Does the 'x' variables above is initialized or not?
    How to know weather the variables is initialized or uninitialized?

    • When x is declared, it is not intitialized. After the cin statement, x may or may not be initialized depending on whether the user entered a valid number or not. In this example, it would be a good idea to declare x and assign it to 0 immediately:

      int x = 0;

      There is no sure-fire way of telling whether a variable is initialized or not. Consequently, it's a good idea to always initialize your variables when they are declared. That way, you won't have to guess.

  • Argon

    Hi, and thank you for a very informative and easy-to-read tutorial.

    One question to the "define variables along the way".. I have a love for using this type of defining:

    type foo()
    type tDescriptiveName(alternatively a default value); // Description
    type tDescriptiveName(alternatively a default value); // Description
    type tDescriptiveName(alternatively a default value); // Description
    type tDescriptiveName(alternatively a default value); // Description

    [... function code ...]

    Find this more tidy. And if var (witch it often is) are used more than once, "top description" will give a clear meaning.
    Any sense in this?

    • Well, if it's your code you're welcome to do whatever you like. :) But generally, the declare your variables at the top style of declaration is considered deprecated in C++. My personal experience has taught me that it leads to tougher to read/understand code, even when they are commented.

      One issue with declare-at-the-top style of declaration is that you often have to scroll up to find out whether a variable is a local variable (declared in the function) or a function parameter. Declare-when-needed often doesn't suffer from this wasted energy, since the majority of variables in a function will be declared when needed and used immediately thereafter.

      • Bradley

        Your second point, that you need to scroll up to find out whether a variable is a local variable or a function parameter is solved simply by following some common sense programming guidelines. Use prefixes on items to indicate what they are. For example;

        //pv prefix means it is a parameter value
        //lv prefix means it is a local variable
        int add(int pvVal1, int pvVal2)
          int lvResult = pvVal1 + pvVal2;
          return lvResult;

        This method - or something like it - has been the standard at almost every company I have worked with in my 15+ years as a consultant. This notation, along with the use of meaningful variable names, makes most claims to one method of declaring variables being superior to the other pretty meaningless in my opinion.

  • Jesse

    "This is not a bad mistake because the compiler will complain and ask you to fix it."
    Shouldn't this say:
    "This is a bad mistake because the compiler will complain and ask you to fix it."

    • Nope. In my view, anything the compiler catches is not a bad mistake because the compiler points out exactly where the error is. Those tend to get fixed immediately. The bad mistakes are the ones the compiler doesn't catch. Those are the ones that are likely to creep into production code (code released to the public).

  • emmm..

    and why you use:

    using namespace std;

    I don't use it...

    • using namespace std;

      allows you to access anything that lives in the std namespace without having to use the std:: prefix. It's useful for using in conjunction with cin and cout so you can type "cin" or "cout" instead of "std::cin" or "std::cout".

      If you

      #include <iostream.h>

      , then cin and cout are not defined as being in the std namespace, and you won't need it. However, it's better to

      #include <iostream>

      , where cin and cout are defined inside the std namespace. In this case, you can use the using statement to make your code simpler and easier to read.

      • Take a look at this screenshot I took of your comment. What are you meaning by If you #include , *snip* it's better to #include ,

        It's showing the exact same thing. I've noticed this a couple other times in comments mostly but even in the tutorial. Is there supposed to be more showing? (I've checked in FF3 and IE7 and both display the same).

        Other than that little thing this tutorial has been great so far! I really like how you're moving along in the tutorial with small bits of info here and there rather than throwing the whole subject (of say variables) at me all in one big lump!

        Thanks alot for taking the time to create this site.

        • Although I couldn't see your screenshot (I get a page load error), I was seeing blanks after the #include as well. The problem is that this website software (WordPress) assumes you're writing HTML and not code, so unless you tell it otherwise, it thinks <iostream> is an HTML tag of some sort -- so it just eats it.

          I've fixed the above comment.

          • Boxchan

            However, Alex, you really should put the namespace std thing before all the functions, but after the includes. This way, you just have to type it once, becuase it is in the whole script then.

  • Abhishek

    That was easy :D

Leave a Reply

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">