Что такое unsigned char в C++ и чем он отличается от обычного char?
Ключевое отличие: знаковость и представление чисел
Ваша путаница абсолютно нормальна — это один из самых частых вопросов у новичков. Главное отличие между char и unsigned char кроется в том, как они интерпретируют биты памяти.
1. Размер и диапазон значений
Оба типа занимают ровно 1 байт (8 бит на подавляющем большинстве платформ). Но диапазон значений разный:
unsigned char: от 0 до 255 (все 8 бит используются для хранения положительных чисел).signed char(или простоchar, если компилятор считает его знаковым): от -128 до 127 (один бит отводится под знак).
Важно: стандарт C++ не specifies, является ли обычный char знаковым или беззнаковым — это зависит от компилятора и платформы. На Windows (MSVC) он обычно знаковый, на ARM-системах — беззнаковый. Поэтому ваш код выдаёт -1: число 255 в знаковом char интерпретируется как -1 (дополнительный код).
2. Когда использовать каждый тип
Золотое правило:
char— для текстовых символов (буквы, цифры, знаки препинания). Если вы работаете с ASCII-строкой или вызываете функции вродеstd::isalpha(), используйтеchar.unsigned char— для сырых байтов (бинарные данные, пиксели изображений, байты из сети, криптография). Он гарантированно беззнаковый и не вызывает неопределённого поведения при арифметике.
3. Примеры кода
Пример 1: Путаница с знаковым char
#include <iostream>
#include <cstring>
int main() {
// Проблема: знаковый char может давать отрицательные значения
char buf[] = {0xFF, 0x00, 0x7F}; // -1, 0, 127
for (char c : buf) {
std::cout << (int)c << " "; // Вывод: -1 0 127
}
// Правильно: используем unsigned char для байтов
unsigned char ubuf[] = {0xFF, 0x00, 0x7F};
for (unsigned char c : ubuf) {
std::cout << (int)c << " "; // Вывод: 255 0 127
}
return 0;
}
Пример 2: Работа с бинарными данными
#include <iostream>
#include <fstream>
// Функция для чтения файла как массива байтов
std::vector<unsigned char> readFile(const std::string& path) {
std::ifstream file(path, std::ios::binary | std::ios::ate);
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<unsigned char> buffer(size);
if (file.read(reinterpret_cast<char*>(buffer.data()), size)) {
return buffer;
}
return {};
}
int main() {
auto data = readFile("image.png");
// data[0] и data[1] — сигнатура PNG (0x89, 0x50)
if (data.size() > 1 && data[0] == 0x89 && data[1] == 'P') {
std::cout << "Это PNG-файл!" << std::endl;
}
return 0;
}
4. Важные моменты
- При сравнении
charсunsigned charкомпилятор может выдавать предупреждения — избегайте этого. - Функции
std::memcpy,std::freadработают сvoid*, но внутренне оперируют байтами — используйтеunsigned char*для каста. - В C++17 появился
std::byte, который ещё строже предназначен для работы с сырыми данными, ноunsigned charостаётся стандартом де-факто.
Итог: если вы работаете с текстом — берите char. Если с байтами, цветами, бинарными протоколами — берите unsigned char.
Дополнение: char vs unsigned char в контексте стандартной библиотеки
Стоит отметить, что многие функции стандартной библиотеки C++ ожидают именно char* или const char* для строк, но при этом работают с байтами. Например:
std::cout << str;— ожидаетconst char*, а неunsigned char*.std::stringхранитchar, но это не мешает ей содержать бинарные данные (хотя это не рекомендуется).- Функции
std::isprint,std::toupperпринимаютint, но для корректной работы сunsigned charнужно приводить кunsigned charперед вызовом, иначе отрицательные значения вызовут неопределённое поведение.
Пример безопасного вызова:
#include <iostream>
#include <cctype>
int main() {
unsigned char c = 0xC0; // 192, не ASCII
// Неправильно: std::isalpha(c) — UB, если c > 127
// Правильно:
if (std::isalpha(static_cast<unsigned char>(c))) {
std::cout << "Буква" << std::endl;
}
return 0;
}
Поэтому unsigned char — это не просто "беззнаковый char", а тип для безопасной работы с байтами в диапазоне 0-255. Используйте его осознанно.