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, …
Variables
type 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 ⊕ breturns result of operation ⊕ applied to the values ofaandb - expression
a ⊕= bstores 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/--xreturns 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
0is 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 trueAll 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 doubledouble⊕float→doubledouble⊕any integer→doublefloat⊕any integer→float
Operations On Two Integer Types
- integer promotion is first applied to both operands:
basically everything smaller thanintgets promoted to eitherintorunsiged 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