Типы данных в C/C++ [2.2]
В этом уроке мы разбираем типы данных в C++ и то, как с ними работать в робототехнике. Вы узнаете, что такое статическая типизация, какие бывают фундаментальные типы, как правильно инициализировать переменные, зачем нужны const и constexpr, и как работает auto. В конце есть практические задачи и чек-лист самопроверки.
Главная мысль урока: в C++ тип задается при объявлении переменной и дальше не меняется. Ошибка в выборе типа (например, переполнение int или неточная дробь) легко превращается в ошибку поведения робота. Поэтому важно понимать диапазоны, точность и правила инициализации.
Содержание
1. Цели урока
- Понять, что такое статическая типизация и почему она полезна.
- Познакомиться с фундаментальными типами данных в C++.
- Освоить способы инициализации переменных и понять, что такое narrowing (сужение типов).
- Научиться оценивать размеры и диапазоны типов через sizeof, signed/unsigned.
- Понять природу ошибок округления у float/double и как с ними работать.
- Разобраться в const, constexpr и auto.
2. Статическая типизация в C++
Статическая типизация -- это подход, при котором переменная связывается с типом хранимого значения в момент объявления, и тип не может быть изменен позже. Компилятор проверяет типы заранее и останавливает сборку, если вы пытаетесь сделать невозможное (например, записать дробь в int без явного преобразования).
int speed = 100; // speed -- целое число
// speed = 3.14; // так нельзя без преобразования: тип int не хранит дробную часть
Фундаментальные типы данных
Фундаментальные типы -- это базовые типы языка, из которых строится все остальное.
| Категория | Тип | Значение | Пример |
|---|---|---|---|
| Логические | bool |
true/false | true |
| Символьные | char |
ASCII-коды символов | 'a' |
| Плавающая точка | float, double, long double |
Дробные числа | 2.7 |
| Целые | short, int, long, long long |
Целые числа | 128 |
| Ничего | void |
Нет значения | (нет) |
3. Инициализация переменных
Инициализация -- это задание первого значения переменной. Не путайте с присваиванием: присваивание меняет значение уже существующей переменной.
4 способа инициализации
int var1 = 5; // копирующая инициализация
int var2(5); // прямая инициализация
int var3{5}; // uniform-инициализация (рекомендуется)
int var4{}; // инициализация значением по умолчанию (для int будет 0)
Компактная запись:
int a = 5, b = 6;
int c(7), d(8);
int e{9}, f{10};
Сужение типов (narrowing)
3.14 (double) в int приводит к потере дробной части.int x = 3.14; // компилируется, но x станет 3 (дробная часть потеряна)
// int y{3.14}; // обычно не компилируется: фигурные скобки защищают от narrowing
Почему это важно для робота: если вы храните измерение с датчика (например, 0.1 метра) в int, вы потеряете дробную часть, и управление станет грубым или неверным.
Типичные ошибки
Ошибка: переменная не инициализирована
int a; // значение неопределено
// std::cout << a; // может вывести что угодно
Исправление: задавайте начальное значение, например int a{}; или int a = 0;.
4. Размеры и диапазоны типов
Размер типа зависит от компилятора и архитектуры. Стандарт C++ задает минимальные размеры, но реальные значения могут отличаться. Проверить размер можно оператором sizeof.
Оператор sizeof
sizeof возвращает размер типа или переменной в байтах.
#include
int main() {
std::cout << "int: " << sizeof(int) << " bytes\n";
std::cout << "double: " << sizeof(double) << " bytes\n";
return 0;
}
signed и unsigned
Целочисленные типы бывают знаковыми (signed) и беззнаковыми (unsigned). У знаковых есть отрицательные числа, у беззнаковых -- нет.
signed: от -2^(n-1) до 2^(n-1)-1
unsigned: от 0 до 2^n-1
char и ASCII
Тип char -- особый случай. Формально это целочисленный тип, который хранит ASCII-код символа. ASCII -- таблица кодов от 0 до 127 для символов английского алфавита и некоторых служебных знаков. Символы в C++ пишутся в одинарных кавычках.
#include <iostream>
int main() {
char ch = 'a';
int code = ch; // неявно получаем числовой код
std::cout << "ch: " << ch << "\n";
std::cout << "code: " << code << "\n"; // обычно 97
return 0;
}
Типичные ошибки
Ошибка: переполнение целого типа
Переполнение -- это ситуация, когда значение выходит за диапазон типа и начинает "переворачиваться". На роботе это может дать отрицательную скорость или странные показания.
// Пример идеи (конкретное поведение зависит от типа и платформы):
// unsigned char u = 255;
// u = u + 1; // может стать 0
5. Числа с плавающей точкой: float и double
Что такое IEEE 754
IEEE 754 -- стандарт, который описывает, как компьютер хранит числа с плавающей точкой. Внутри это не "дробь как на бумаге", а двоичное представление с ограниченной точностью.
| Тип | Мин. размер | Точность (приблизительно) |
|---|---|---|
float |
4 байта | от 6 до 9 цифр |
double |
8 байт | от 15 до 18 цифр |
long double |
8, 12 или 16 байт | зависит от платформы |
Ошибки округления
Некоторые десятичные числа (например, 0.1) не представимы точно в двоичной системе, поэтому возникают небольшие ошибки. Они накапливаются при сложении и могут влиять на фильтры, ПИД-регуляторы и интеграторы.
#include <iostream>
#include <iomanip>
int main() {
std::cout << std::setprecision(17);
double d = 0.1;
std::cout << d << "\n"; // 0.10000000000000001 (примерно)
double s = 0.0;
for (int i = 0; i < 10; ++i) s += 0.1;
std::cout << s << "\n"; // 0.99999999999999989 (примерно)
return 0;
}
nan и inf
В IEEE 754 существуют специальные значения: inf (бесконечность) и nan (не число). Они появляются при делении на ноль и других некорректных операциях.
#include <iostream>
int main() {
double zero = 0.0;
double posinf = 5.0 / zero;
double neginf = -5.0 / zero;
double nan = zero / zero;
std::cout << posinf << "\n";
std::cout << neginf << "\n";
std::cout << nan << "\n";
return 0;
}
Типичные ошибки
Ошибка: сравнение double через ==
Из-за ошибок округления два числа, которые "должны быть равны", могут отличаться в последних знаках. Правильнее сравнивать с допуском (epsilon).
#include <cmath>
bool almostEqual(double a, double b, double eps) {
return std::abs(a - b) < eps;
}
6. const, constexpr и auto
6.1. const и constexpr
const и constexpr используются для констант. Константа -- это значение, которое нельзя изменить после инициализации.
const-- константа времени выполнения: значение фиксируется при запуске, менять нельзя.constexpr-- константа времени компиляции: значение должно быть известно компилятору заранее.
constexpr double gravity(9.8);
const double mass{90};
6.2. Ключевое слово auto
auto просит компилятор вывести тип переменной по правой части инициализации. Это удобно, когда тип очевиден или слишком длинный.
auto x = 4.0; // double
auto y = 3 + 4; // int
6.3. Типичные ошибки
Ошибка: const без инициализации
// const int a; // ошибка: константа должна быть инициализирована
const int a = 10; // правильно
Ошибка: auto скрывает неожиданный тип
Иногда auto выводит не то, что вы ожидали. Привычка: смотреть на литералы справа и помнить правила.
auto a = 1; // int
auto b = 1.0; // double
7. Практика: типовые задачи (с решениями)
Базовые задачи по типам
Задача 1: Таблица sizeof
Выведите размер нескольких типов в байтах.
#include <iostream>
int main() {
std::cout << "bool: " << sizeof(bool) << " bytes\n";
std::cout << "char: " << sizeof(char) << " bytes\n";
std::cout << "int: " << sizeof(int) << " bytes\n";
std::cout << "double: " << sizeof(double) << " bytes\n";
return 0;
}
Задача 2: char как число
Выведите символ и его код.
#include <iostream>
int main() {
char ch = 'b';
std::cout << "ch: " << ch << "\n";
std::cout << "code: " << static_cast(ch) << "\n";
return 0;
}
Задачи по float/double
Задача 3: Ошибка округления
Выведите 0.1 с высокой точностью.
#include <iostream>
#include <iomanip>
int main() {
std::cout << std::setprecision(17);
double d = 0.1;
std::cout << d << "\n";
return 0;
}
Задача 4: nan и inf
Получите inf и nan и выведите их.
#include <iostream>
int main() {
double zero = 0.0;
std::cout << (5.0 / zero) << "\n";
std::cout << (zero / zero) << "\n";
return 0;
}
Задачи по const/constexpr/auto
Задача 5: const и constexpr
Создайте одну константу const и одну constexpr.
constexpr double g = 9.8;
const double mass = 90.0;
Задача 6: auto
Проверьте, какой тип выводится для 4.0 и для 3 + 4.
auto x = 4.0; // double
auto y = 3 + 4; // int
8. Чек-лист самопроверки знаний
Отметьте пункты, которые вы действительно понимаете и можете применить без подсказок.
| +/- | Навык | Проверка |
|---|---|---|
| Статическая типизация | Могу объяснить, почему тип задается при объявлении и не меняется | |
| Фундаментальные типы | Знаю, какие типы относятся к bool, char, целым и дробным | |
| Инициализация | Умею использовать =, (), {}, {} и понимаю, чем они отличаются | |
| Narrowing | Понимаю, что такое сужение типов и почему {} помогает ловить ошибки | |
| sizeof | Могу вывести размер типа/переменной и объяснить, почему размеры зависят от платформы | |
| signed/unsigned | Могу объяснить диапазоны: signed от -2^(n-1) до 2^(n-1)-1, unsigned от 0 до 2^n-1 | |
| float/double | Понимаю, почему бывают ошибки округления, и почему опасно сравнение через == | |
| nan/inf | Знаю, откуда берутся nan и inf и почему их надо учитывать в расчетах | |
| const/constexpr | Отличаю константу времени выполнения от константы времени компиляции | |
| auto | Понимаю, что auto выводит тип из инициализатора и умею проверять ожидания |