C++ Datatypes

reading

Data is stored in computer memory. We’ll refer to the data in memory as an object.

We can access this data:

  • directly: using a descriptive object name

  • indirectly: using a pointer or a reference to the memory location

Data stored in the object can be:

  • mutable : we can change it—these are variables

  • immutable : it cannot be changed—these are constants

There are several basic types of data:

  • bool : a 1-bit true or false

  • char : a 1 byte character (like 'a')

  • int : an integer

  • float : a single precision floating point number

  • double : a double precision floating point number

Note

A floating point number is any number with a decimal point, like 2.0 or 1.e-20.

Generally, floating point numbers cannot be exactly represented on a computer, since there are infinite numbers on the number line between \([0, 1]\), but the computer only has a finite amount of memory.

Single and double precision refer to how much memory (and therefore how much precision) is used to store the numbers. Usually single precision uses 32 bits and double precision uses 64 bits. More on this later…

There are also modifiers that can be used with many of these like: short, unsigned, long.

A table of C++ datatypes is provided in the CPlusPlus tutorial variables section

Initialization

We can initialize a define a variable as:

int i;
float x;
double slope;

Tip

C++ variable names are case-sensitive, so x and X are distinct.

Warning

Variable names can include letters, numbers, and _, but cannot begin with a number. Additionally, there are some reserved keywords (like int) that cannot be used as variable names.

Danger

It is always a good idea to initialize a variable with a starting value. Otherwise, most compilers will leave it undefined and you can run into problems if you try to use it without first remembering to initialize it.

There are a few ways to initialize. Here are the two we’ll see the most:

int i = 0;
double x{};

Both of these will initialize the variable to 0. The {} notation is more flexible, since it will work with more complicated objects that we will encounter later.

We’ll see a third way to initialize objects when we look at classes (and constructors).

Objects defined with const cannot be changed, so you are required to initialize the immediately:

const double G = 6.67e-8;

Mixing types

You need to be careful when mixing different data types. C++ will implicitly cast objects to the more general type, sometimes with unexpected consequences. Later we’ll see how to explicitly cast data if needed.

Try the following (you’ll need to put this in a main() function and compile it).

// convert from Fahrenheit to Celsius

double T_F {100.0};
double T_C =  (T_F - 32) * (5 / 9);

std::cout << "T_C = " << T_C << std::endl;

Why doesn’t this work as expected?

Sizes

The link above as well as your text tells you how the standard defines the sizes of the different data types.

Important

C++ guarantees a minimum size of the different types, but different compilers or processors may have different defaults.

But we can explicitly determine this with a C++ program using sizeof()—that returns the number of bytes

std::cout << sizeof(double) << std::endl;

The following is guaranteed:

sizeof(char) == 1 <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)

Tip

If you want to guarantee a size for an int, there are datatypes that explicitly require a fixed width, like int32_t: https://en.cppreference.com/w/cpp/types/integer

We can find out a lot about the range and precision of numbers that can be stored with a given type by using std::numeric_limits:

Listing 2 limits_test.cpp
#include <iostream>
#include <limits>

int main() {

    std::cout << "double: " << std::endl;
    std::cout << "  smallest value = " << std::numeric_limits<double>::min() << std::endl;
    std::cout << "  lowest value = " << std::numeric_limits<double>::lowest() << std::endl;
    std::cout << "  largest value = " << std::numeric_limits<double>::max() << std::endl;

    std::cout << "short: " << std::endl;
    std::cout << "  smallest value = " << std::numeric_limits<short>::min() << std::endl;
    std::cout << "  lowest value = " << std::numeric_limits<short>::lowest() << std::endl;
    std::cout << "  largest value = " << std::numeric_limits<short>::max() << std::endl;

}

Notice that there is a new pattern here, the use of <>—this is used in template functions, a topic we will talk a lot about later.

What happens if we exceed the limits of a data type? For floating point, we don’t abruptly transition to 0. (for underflow) but instead start losing digits of precision as subnormal numbers.

For integers, it is more fun:

https://imgs.xkcd.com/comics/cant_sleep.png

Fig. 4 (xkcd)

try it…

Create a program that initializes a short integer and through addition triggers an overflow.

Precision is also important with floating point. Consider the following: what do you expect?

double a = 1.0;
double b = -1.0;
double c = 2.e-15;

std::cout << (a + b) + c << std::endl;
std::cout << a + (b + c) << std::endl;

With floating point, the associate property of addition does not hold.