Fundamental Types Fundamental Types Fundamental Types
Fundamental Types are the basic building blocks for all complex types / data structures like lists, hash maps, trees, graphs, …
Variable
stype variable = value;
type variable {value}; C++11
// declare & initialize 'i': int i = 1; // print i's value: cout << i << '\n'; int j {5}; cout << j << '\n';
Variables of fundamental types are not initialized by default!
int k; // k not initialized! cout << k << '\n'; // value might be anything
Because in C++ you only pay for what you use
(initilization of large memory blocks can be quite expensive)
But: You should almost always initialize variables when declaring them to prevent bugs!
Overview
Booleans
bool b1 = true; bool b2 = false;
char c = 'A'; // character literal char a = 65; // same as above
short s = 7; int i = 12347; long l1 = -7856974990L; long long l2 = 89565656974990LL;
// ' digit separator C++14 long l3 = 512'232'697'499;
unsigned u1 = 12347U; unsigned long u2 = 123478912345UL; unsigned long long u3 = 123478912345ULL;
// non-decimal literals unsigned x = 0x4A; // hexadecimal unsigned b = 0b10110101; // binary C++14
Common Number Representations
- expression
a ⊕ b
returns result of operation ⊕ applied to the values ofa
andb
- expression
a ⊕= b
stores result of operation ⊕ ina
#include <iostream> int main () {
int a = 4; int b = 3;
a = a + b; std::cout << "a: " << a << '\n';
a += b; std::cout << "a: " << a << '\n';
a = a - b; std::cout << "a: " << a << '\n';
a -= b; std::cout << "a: " << a << '\n';
a = a * b; std::cout << "a: " << a << '\n';
a *= b; std::cout << "a: " << a << '\n';
a = a / b; std::cout << "a: " << a << '\n';
a /= b; std::cout << "a: " << a << '\n';
a = a % b; std::cout << "a: " << a << '\n'; }
- changes value by +/- 1
- prefix expression
++x
/--x
returns new (incremented/decremented) value - postfix expression
x++
/x--
increments/decrements value, but returns old value
#include <iostream> int main () {
int a = 4; int b = 3; std::cout << "a: " << a << " b:" << b << '\n';
b = a++; std::cout << "a: " << a << " b:" << b << '\n';
b = ++a; std::cout << "a: " << a << " b:" << b << '\n';
b = --a; std::cout << "a: " << a << " b:" << b << '\n';
b = a--; std::cout << "a: " << a << " b:" << b << '\n'; }
a: 4 b: 3
a: 5 b: 4 a: 6 b: 6
a: 5 b: 5 a: 4 b: 5
2-way Comparisons
result of comparison is either true
or false
int x = 10; int y = 5;
bool b1 = x == 5; bool b2 = (x != 6);
bool b3 = x > y; bool b4 = x < y; bool b5 = y >= 5; bool b6 = x <= 30;
result operator
false equals true not equal
true greater false smaller true greater/equal true smaller/equal
3-Way Comparisons With <=>
C++20
determines the relative ordering of 2 objects:
(a <=> b) < 0 | if a < b |
(a <=> b) > 0 | if a > b |
(a <=> b) == 0 | if a and b are equal/equivalent |
3-way comparisons return a comparison category value that can be compared to literal 0
.
The returned value comes from one of three possible categories: std::strong_ordering
, std::weak_ordering
or std::partial_ordering
.
4 <=> 6 | → std::strong_ordering::less |
5 <=> 5 | → std::strong_ordering::equal |
8 <=> 1 | → std::strong_ordering::greater |
Operators
bool a = true; bool b = false; bool c = a && b; // false logical AND bool d = a || b; // true logical OR bool e = !a; // false logical NOT Alternative Spellings: bool x = a and b; // false bool y = a or b; // true bool z = not a; // false
Conversion to bool
0
is alwaysfalse
;- everything else is
true
;
bool f = 12; // true (int → bool) bool g = 0; // false (int → bool) bool h = 1.2; // true (double → bool)
Short-circuit Evaluation
The second operand of a boolean comparison is not evaluated if the result is already known after evaluating the first operand.
int i = 2; int k = 8; bool b1 = (i > 0) || (k < 3);
i > 0
is already true
⇒ k < 3
is not evaluated, because the result of logical OR is already true
All type sizes are a multiple of sizeof(char)
cout << sizeof(char); // 1 cout << sizeof(bool); // 1 cout << sizeof(short); // 2 cout << sizeof(int); // 4 cout << sizeof(long); // 8
// number of bits in a char cout << CHAR_BIT#include <cstdint>
; // 8
char c = 'A'; bool b = true; int i = 1234; long l = 12; short s = 8;
Sizes Are Platform Dependent
only basic guarantees
sizeof(short)
≥sizeof(char)
sizeof(int)
≥sizeof(short)
sizeof(long)
≥sizeof(int)
e.g., on some 32-bit platforms: int
= long
Integer Size Guarantees C++11
#include <cstdint>
- exact size (not available on some platforms)
int8_t
,int16_t
,int32_t
,int64_t
,uint8_t, …
- guaranteed minimum size
int_least8_t
,uint_least8_t, …
- fastest with guaranteed minimum size
int_fast8_t
,uint_fast8_t, …
Fixed-Width Floating Point Type Guarantees C++23
#include <stdfloat>
// storage bits: sign + exponent + mantissa std::float16_t a = 12.3f16; // 1 + 5 + 10 = 16 bits = 2 B std::float32_t b = 12.3f32; // 1 + 8 + 23 = 32 bits = 4 B std::float64_t c = 12.3f64; // 1 + 11 + 52 = 64 bits = 8 B std::float128_t d = 12.3f128; // 1 + 15 + 112 = 128 bits = 16 B std::bfloat16_t e = 12.3b16; // 1 + 8 + 7 = 16 bits = 2 B
#include <iostream>
#include <limits> int main () {
// smallest negative value: std::cout << "lowest: " <<
std::numeric_limits<double>::lowest() << '\n'
; // float/double: smallest value > 0 // integers: smallest value std::cout << "min: " <<
std::numeric_limits<double>::min() << '\n'
; // largest positive value: std::cout << "max: " <<
std::numeric_limits<double>::max() << '\n'
; // smallest difference btw. 1 and next value: std::cout << "epsilon: " <<
std::numeric_limits<double>::epsilon() << '\n'
; … }

(click for fullscreen view)
- conversion from type that can represent more values to one that can represent less
- may result in loss of information
- in general no compiler warning – happens silently
- potential source of subtle runtime bugs
double d = 1.23456; float f = 2.53f; unsigned u = 120u;
double e = f; // OK float → double
int i = 2.5; // NARROWING double → int int j = u; // NARROWING unsigned int → int int k = f; // NARROWING float → int
Braced Initialization C++11
type variable { value };
- works for all fundamental types
- narrowing conversion ⇒ compiler warning
double d {1.23456}; // OK float f {2.53f}; // OK unsigned u {120u}; // OK
double e {f}; // OK float → double
int i {2.5}; // COMPILER WARNING: double → int int j {u}; // COMPILER WARNING: unsigned int → int int k {f}; // COMPILER WARNING: float → int
Make sure to prevent silent type conversions, especially narrowing unsigned to signed integer conversions — they can cause hard-to-find runtime bugs!
a & b | bitwise AND |
a | b | bitwise OR |
a ^ b | bitwise XOR |
~a | bitwise NOT (one's complement) |
#include <cstdint> std::uint8_t a = 6; std::uint8_t b = 0b00001011; std::uint8_t c1 = (a & b); // 2 std::uint8_t c2 = (a | b); // 15 std::uint8_t c3 = (a ^ b); // 13 std::uint8_t c4 = ~a; // 249 std::uint8_t c5 = ~b; // 244 // test if int is even/odd: bool a_odd = a & 1; bool a_even = !(a & 1);
memory bits: 0000 0110 0000 1011 0000 0010 0000 1111 0000 1101 1111 1001 1111 0100 result: 0 ⇒ false 1 ⇒ true
x << n | returns x 's value with its bits shifted by n places to the left |
x >> n | returns x 's value with its bits shifted by n places to the right |
x <<= n | modifies x by shifting its bits by n places to the left |
x >>= n | modifies x by shifting its bits by n places to the right |
#include <cstdint> std::uint8_t a = 1; a <<= 6; // 64 a >>= 4; // 4 std::uint8_t b1 = (1 << 1); // 2 std::uint8_t b2 = (1 << 2); // 4 std::uint8_t b3 = (1 << 4); // 16
memory bits: 0000 0001 0100 0000 0000 0100 0000 0010 0000 0100 0001 0000
Shifting the bits of an object whose type has N bits by N or by more than N places is undefined behavior!
std::uint32_t i = 1; // 32 bit type i <<= 32; UNDEFINED BEHAVIOR! std::uint64_t j = 1; // 64 bit type j <<= 70; UNDEFINED BEHAVIOR!
Unfortunately, there's a lot of rules (that go back to C) whose purpose is to determine a common type for both operands and the result of a binary operation
Operand A ⊕ Operand B → Result
A Simplified Summary
Operations Involving At Least One Floating-Point Type
long double
⊕ any other type →long double
double
⊕float
→double
double
⊕any integer
→double
float
⊕any integer
→float
Operations On Two Integer Types
- integer promotion is first applied to both operands:
basically everything smaller thanint
gets promoted to eitherint
orunsiged int
(depending on which can represent all values of the unpromoted type) integer conversion is then applied if both operand types are different
- both signed: smaller type converted to larger
- both unsigned: smaller type converted to larger
signed ⊕ unsigned:
- signed converted to unsigned if both have same width
- otherwise unsigned converted to signed if that can represent all values
- otherwise both converted to unsigned