C++ has a vast and complicated type system, but strangely, it doesn’t have that many builtin types:
- void
- bool
- char
- short
- int
- long
- long long
- float
- double
- long double
Integer types
char
, short
, int
, long
and long long
are integer types. That means that they can hold whole numbers up to some maximum value. The maximum value can theoretically change a lot from implementation to implementation, but nearly all modern architectures uses the same values, which are:
type | min | max | bits |
---|---|---|---|
char | -128 | 127 | 8 |
short | -32768 | 32767 | 16 |
int | -2.1 billion | 2.1 billion | 32 |
long | -9.8 * 10^18 | 9.8 * 10^18 | 64 |
long long | -a lot | a lot | 128 |
Since these limits could be different, it’s recommended to include <std/types.h>
and use int8_t, int16_t, int32_t, int64_t and int128_t. That should always work the same, assuming the compiler even supports the used type.
All the integer types can also be made “unsigned”. Unsigned types cannot have negative values, but in return the maximum value doubles (+1). An “unsigned char” usually have a max value of 255. Again, it’s better to use types from <std/types.h>
: uint8_t, uint16_t, uint32_t, etc.
Signed and unsigned types have one more difference: Overflow on signed numbers have undefined effects. What his means is that this code is not guaranteed to work:
char x = 127;
x++;
if (x < 0) print("X wrapped around\n");
The compiler is allowed to assume that x will never wrap around, and may decide that the if statement can never return true and can be optimized away.
The same is not true for unsigned numbers. For unsigned numbers, wraparound is a defined operation, so the following code will work as expected:
uint8_t x = 255;
x++;
if (x == 0) print("X wrapped around\n");
floating-point types
float
, double
and long double
are floating point “decimal” numbers. They still store numbers as bits, but a few bits are reserved for an exponent. This allows floating point numbers to store very large and very small numbers, but not always precisely. Generally float
is enough for most calculations, but double
has better precision, and long double
is sometimes better still.
Note that Proffieboards have hardware for doing float
calculations, but it does not have hardware for doing double
calculations. This means that even though a double is only twice as precise as a float, it may be hundreds of times slower.
Examples:
float a = 1000.0;
float b = sqrt(a); // 31.622..
a = b * b; // approximately 1000.0
When comparing floats, you almost always need to make an approximate comparison rather than an exact one. Here’s an example how how this is done:
// this might not work
if (a == 1000.0) do_something();
// this should work
float epsilon = 0.000001;
if (abs(a - 1000.0) < epsilon) do_something();
bool
Bool is a type that can only have two values: true or false. In some cases a bool can be stored in a single bit, but because you can’t take the address of a bit, that doesn’t really work in C++, so C++ normally uses a byte (uint8_t) to store a bool.
void
Void isn’t a real type. It’s the absence of a type. It’s generally used for functions that don’t return anything.
combinations
The types above are the basics on which everything is built. However, you can use arrays, structs, classes, pointers and function types to combine these into more complicated types / chunks of memory. This includes strings, vectors, and all the other stuff in the std:: library, which is a part of C++, but they are not “builtin” types.
Next up: memory, arrays and pointers.