char хранит не букву, а её числовой код. Буквой значение становится только
при выводе, потому что поток интерпретирует char как символ.
Символ — это число
char letter = 'v';
std::cout << letter << '\n'; // v
std::cout << static_cast<int>(letter) << '\n'; // 118 в ASCIIВ памяти нет маленькой нарисованной буквы. В кодировке, совместимой с ASCII,
латинской v соответствует код 118, поэтому на такой системе char letter = 118;
задаст ровно тот же символ:
char v = 'v';
char v118 = 118;
std::cout << v << v118 << '\n'; // vvТип влияет не только на диапазон значений, но и на смысл операции вывода. Это первый случай в курсе, где одно и то же значение печатается по-разному в зависимости от типа. См. static_cast.
sizeof(char) равен единице по определению — это и есть байт C++.
Арифметика с символами
Раз это число, с ним работают обычные операции:
char lower = 'v';
char upper = static_cast<char>(lower - ('a' - 'A'));
std::cout << upper << '\n'; // VРазность 'a' - 'A' — расстояние между строчными и заглавными в таблице кодов.
Вычитание выполняется уже в int: char проходит целочисленное продвижение,
см. Арифметические преобразования типов. Обратное преобразование в char
приходится писать явно, иначе это сужение.
Формула опирается на устройство кодировки и не является универсальным способом менять регистр: для кириллицы или произвольного Unicode-текста она не работает.
std::cout << sizeof(cx) << '\n'; // 1
std::cout << sizeof(cx + cx) << '\n'; // 4Знаковость не определена
Обычный char может вести себя как знаковый или как беззнаковый — это решает
реализация. При этом char, signed char и unsigned char — три разных
типа, даже если два из них совпадают по поведению.
Практическое правило: char для символов, а для маленьких чисел явно
signed char или unsigned char. Иначе код с char c = 200; ведёт себя
по-разному на разных платформах.
Строки и нулевой байт
Строковый литерал — не char, а массив символов с завершающим нулевым байтом:
const char* greet = "Hello, Vova!\n\0Goodbye, Vova!\n";
std::cout << greet; // Hello, Vova!Печатается только первая половина: вывод идёт до первого нулевого байта, а \0
поставлен посередине. В памяти вторая половина есть, для std::cout строка уже
закончилась.
std::cout << sizeof("A\0B") << '\n'; // 4: 'A', '\0', 'B', '\0'
std::cout << sizeof('A') << '\n'; // 1Видимый текст и размер литерала описывают разные вещи. См. Литералы.
Тонкости
- Присваивать строковый литерал в
char*нельзя — только вconst char*. GCC пропускает это с предупреждением, Clang с-pedantic-errorsвыдаёт ошибку. Разбор — L01 - Разбор - main.cpp. - Один
charне обязан вмещать любой символ Unicode. Кириллическая буква в UTF-8 занимает два байта, поэтомуstd::stringс русским текстом длиннее, чем кажется по экрану. - Для других представлений текста существуют
wchar_t,char8_t,char16_t,char32_t. '0'это код 48, а не ноль. Цифру-символ переводят в число какc - '0'.
Частые ошибки
- Считать, что
std::cout << cнапечатает код символа. - Использовать
charкак маленькое целое и нарваться на разную знаковость. - Применять формулу смены регистра к нелатинскому тексту.
- Считать длину UTF-8 строки в символах, а не в байтах.