int, double, char, bool, std::string, const and auto; with integer division, overflow and an ADC conversion example.
A variable is a box in memory that holds a value and gives it a name. C++ is a statically typed language: the type of every variable is known at compile time and never changes afterwards. This strictness feels tiring at first, but it catches mistakes before the program even runs.
Basic types
| Type | What does it hold? | Typical size |
|---|---|---|
| int | Integer | 4 bytes |
| double | Decimal number (precise) | 8 bytes |
| float | Decimal number (less precise) | 4 bytes |
| char | A single character | 1 byte |
| bool | true or false | 1 byte |
| std::string | Text | varies |
Sizes can vary between platforms; you can measure them with your own compiler using sizeof(int). In embedded systems the size has to be exact. In that case you use types from the <cstdint> header such as std::uint8_t, std::int16_t and std::uint32_t.
ADC example
A microcontroller's 10-bit ADC reads a number between 0 and 1023. If the reference voltage is 3.3 V, we use the formula V = reading * Vref / 1023 to convert the reading into a voltage.
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <string>
int main() {
const double vref = 3.3;
const int adcMax = 1023;
int reading = 512;
double voltage = reading * vref / adcMax;
std::cout << std::fixed << std::setprecision(3);
std::cout << "ADC: " << reading << " -> " << voltage << " V\n";
std::cout << "7 / 2 = " << 7 / 2 << '\n';
std::cout << "7 / 2.0 = " << 7 / 2.0 << '\n';
std::uint8_t counter = 250;
counter += 10;
std::cout << "uint8_t: " << static_cast<int>(counter) << '\n';
std::string board = "STM32";
auto length = board.size();
std::cout << board << " (" << length << " characters)\n";
return 0;
}
ADC: 512 -> 1.652 V
7 / 2 = 3
7 / 2.0 = 3.500
uint8_t: 4
STM32 (5 characters)
What this output teaches us
const
A value defined with const cannot be changed. Making constants such as the reference voltage const both prevents accidental changes and makes your intent clear.
Integer division
The result of 7 / 2 is 3, because when both sides are int the result is also an int and the fraction is thrown away. If at least one side is a double (e.g. 7 / 2.0), the result is 3.5. This is the most common mistake when calculating an average.
Overflow
std::uint8_t holds at most 255. Adding 10 to 250 would give 260, but this type wraps modulo 256, leaving 4. For unsigned types this "wrap-around" is well defined; signed int overflow, however, is undefined behavior and very dangerous.
auto
auto tells the compiler "work out the type from the right-hand side". board.size() returns a std::size_t; you don't need to memorize the type's name. Still, when the type matters, writing it out explicitly improves readability.
Local variables are not zeroed automatically. If you write int x; and read it right away, you will find a garbage value inside. Always give an initial value: int x = 0; or int x{0};.
When you initialize with braces (int n{3.7};), the compiler notices that the decimal part would be lost and reports an error. This prevents silent data loss.
Try it yourself
- For a 12-bit ADC (
maximum 4095), let the reading be 2048 and the reference 5 V; calculate and print the voltage. - Calculate the average of two
intvariables. First the wrong way (with integer division), then the right way. - Assign 255 to a
std::uint8_tvariable, add one and print the result.
// Mini quiz
Click an option; you will see right away, with the reason, whether it is correct or not.
Mark it when you finish: your progress is saved and you earn +20 XP.
Tip: ← → keys let you move between lessons.