Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Data Types

As we mentioned on the last page, C++ is a statically typed language which means the type of data must be known (or deducable) to the compiler. C++ has a large selection of types available to use, some are language primitives and others are defined in the standard library. In this page we will look at four categories of types, scalar integrals, floating point, compound and special types.

Scalar Types

Scalar integrals are types encoded as whole numbers. This not only includes integers types but C++ character and Boolean types.

Integer Types

An integer is a whole number. C++ has a few different integer types which have diffenent bit widths. The default int is 32-bits wide on most platforms. By default integer types are signed ie. they can represent both positive and negative numbers. If you need unsigned numbers we can use the unsigned qualifier.

const int x = -5;
const unsigned int y = 5;

If you need integers of a different sizes you can either use size qualifiers with the int type to dictate the minimum size the integer can be. All of these can be used in combination with the unsigned qualifier.

Type Full Type Minimum Size Signed Value Range Unsigned Value Range
char char at least 8 -128 to 127 0 to 255
short short int at least 16 -32,768 to 32,767 0 to 65,535
int int at least 16 -32,768 to 32,767 0 to 65,535
long long int at least 32 -2,147,483,648
to
2,147, 483,647
0 to
4,294, 967,296
long long long long int at least 64 -9,223,372,036,854,775,808
to
9,223,372,036,854,775,807
0 to
18,446,744,073,709,551,615

You can also use fixed width integer types (FWIT). FWIT have the form std::intN_t or std::uintN_t where N is the exact number of bits wide. The standard library define FWIT (signed and unsigned) for 8, 16, 32, 64 bits widths.

The bit width of an integer dictates how many values the integer can represent. As of C++20, all integers must be represented by 2s-complement which means that for signed numbers the range of values is \(-2^{N-1}\) to \(+2^{N-1}-1\) eg. -128 to 127 for an 8-bit number and for an unsigned number the range is \(2^N-1\) eg. values 0 to 255 for an 8-bit number.

In addition to these integer types there are std::size_t and std::ptrdiff_t which are the unsigned and signed types respectively that have the max bit width available on a given architecture, eg. 64 bits on 64-bit architecture. std::size_t is the type used when index arrays or getting the size of objects. The odd name for std::ptrdiff_t is because this is the type returned after pointer arithmetic however, it is really the largest signed integer type.

Literals

You can specify the type/width of an integer using a literal suffix from the table below with the u suffix being able to be used in combination with the other two.

KeywordDescription
u or Uunsigned
l or Llong
ll or LLlong long

Additionally you can write integer literals in a different base form by changing the prefix of the literal.

const auto decimal = 42;
const auto octal = 052;
const auto hex = 0x2a;
const auto Hex = 0X2A; // capital hex digits
const auto binary 0b101010;

Integers can also be separated using a ' to make large numbers easier to read.

const auto x = 1'234'567'890;

Character Types

You'll notice that we have included the char type in the integer list above. This is because character types in C++ are represented using numbers, specifically char represents ASCII code points. Character literals are specified with single quotes like the example below.

const char x = 'a';
const auto y = 'b';

Boolean Type

C++'s Boolean type is called bool and can either hold the value true or false. Booleans are used mostly in conditional and loop statements eg. if and while.

bool x = false;
auto y = true;

Tip

The C language; C++'s mother language, originally did not have a native Boolean type with Boolean expressions return 1 for true and 0 for false. Later in the 1999 standard of C (C99), the _Bool type was introduced to support Booleans.

Floating Point Types

C++ has three floating point types, all of which are based on the IEEE-754 standard. Floating point numbers are used to represent decimal numbers ie. numbers that can store fractional components. These types are the float, double and long double; with float represent single precision (32-bit) numbers, double being double precision (64-bit) numbers and long double being an extended or quadruple precision (128-bit) floating point number.

With auto, floating point values being initialized as a double by default and float and long double literals being specified by f and l literal suffixes.

const auto f = -0.06f;
const auto d = 47.5768;
const auto l = -655456.457567l;

We can also initialize floating points using exponential form:

const auto f = -6e-2f;
const auto d = 475768e4;
const auto l = -655456457567le7l;

Arithmetic Operations

Integral and floating point types are categorized as arithmetic types which mean they support the common arithmetic operations like addition, subtraction etc.

auto main() -> int {
    // addition
    const auto sum = 4 + 6;

    // subtraction
    const auto diff = 10 - 5.5;

    // multiplication
    const auto mul = 5 * 3.2;

    // division
    const auto idiv = 10 / 3;
    const auto fdif = 13.5 / 2.4;

    // remainder
    const auto = 23 % 4;

    return 0;
}

Tip

  • Division between two integrals performs integer division and truncates towards 0 while if one argument is a floating point then floating point division is performed.
  • Remainder is only valid between integral types.

Compound Data Types

Compound data types store multiple pieces of data or are data that can take multiple values.

Enumerations

Enumerations or enums are a construct that allows you to define a type whose value is restricted to a set of named variants or enumerators. These named constants have an underlying integral type. Specifying the underlying type is optional ie. omit the : type in the enum declaration.

enum class colour : char {
    red,
    green,
    blue
};

const auto c = colour::red;

Tuple

Tuples allow you to pack multiple pieces of data of different types into a single structure. Tuples have a fixed size/number of elements that cannot grow or shrink once declared. Tuples in C++ are not language types but are provided by the standard library in the <tuple> header and is called std::tuple. We create a tuple using brace initialization (top) or using the helper function std::make_tuple().

const auto t = std::tuple { 5u, 5.34f, -345, "abc", false };
const auto u = std::make_tuple(5u, 5.f, -345, "abc", false);

Tuples can be accessed using std::get<I>(t) with I being the index of the value we want to access and t is the tuple object.

const auto e = std::get<2>(t);  // e := -345

You can also destructure tuples into its constituent values like so.

const auto [v, w, x, y, z] = t;

There is a specialization of tuples called std::pair which holds just two values. The values of a pair can be extracted using the same methods as tuples but they also have public members std::pair::first and std::pair::second which allows you to access the data.

const auto p = std::pair {5, 'a'};
const auto [x, y] = p;
const auto z = p.second;

Special Types

C++ has a handful of special types that you won't use as directly as types but are fundamental to the language.

The first is the void type is an incomplete type that is used to indicate that a function does not return a value.

auto foo(const auto i) -> void {
   i + 5; 
}

The other type is std::nullptr_t which is the type of nullptr the value of a pointer pointing to nothing.

Array Types

C++ array type is a fixed sized container where elements are all of the same type. The array type is called std::array and is found in the <array> header. Array elements can be accessed using the subscript operator [] or the array::at() method with indices starting at 0. The subscript element access does not perform bounds checking while array::at() does, meaning the later will throw and exception if an out of bounds index is used while the former will crash the program... sometimes.

const auto a = std::array { 1, 2, 3, 4, 5 };
const auto e1 = a[0]; // valid
const auto e2 = a.at(5); // exception std::out_of_range