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