(Этот ответ касается стороны C ++, но проблема расширения знака существует и в C).
Обработка всех трех типов char
(signed
, unsigned
и char
) более сложна, чем кажется на первый взгляд. Значения в диапазоне от 0 до SCHAR_MAX
(что 127 для 8-битного char
) просты:
char c = somevalue;
signed char sc = c;
unsigned char uc = c;
int n = c;
Но когда somevalue
находится за пределами этого диапазона, только пропуск unsigned char
дает вам согласованные результаты для "одинаковых" значений char
во всех трех типах:
char c = somevalue;
signed char sc = c;
unsigned char uc = c;
// Might not be true: int(c) == int(sc) and int(c) == int(uc).
int nc = (unsigned char)c;
int nsc = (unsigned char)sc;
int nuc = (unsigned char)uc;
// Always true: nc == nsc and nc == nuc.
Это важно при использовании функций из ctype.h , таких как isupper
или toupper
, из-за расширения знака:
char c = negative_char; // Assuming CHAR_MIN < 0.
int n = c;
bool b = isupper(n); // Undefined behavior.
Обратите внимание, что преобразование через int неявное; у этого же UB:
char c = negative_char;
bool b = isupper(c);
Чтобы исправить это, пройдите через unsigned char
, что легко сделать, обернув ctype.h функциями через safe_ctype :
template<int (&F)(int)>
int safe_ctype(unsigned char c) { return F(c); }
//...
char c = CHAR_MIN;
bool b = safe_ctype<isupper>(c); // No UB.
std::string s = "value that may contain negative chars; e.g. user input";
std::transform(s.begin(), s.end(), s.begin(), &safe_ctype<toupper>);
// Must wrap toupper to eliminate UB in this case, you can't cast
// to unsigned char because the function is called inside transform.
Это работает, потому что любая функция, принимающая любой из трех типов символов, может также брать другие два типа символов. Это приводит к двум функциям, которые могут обрабатывать любые типы:
int ord(char c) { return (unsigned char)c; }
char chr(int n) {
assert(0 <= n); // Or other error-/sanity-checking.
assert(n <= UCHAR_MAX);
return (unsigned char)n;
}
// Ord and chr are named to match similar functions in other languages
// and libraries.
ord(c)
всегда дает неотрицательное значение - даже если передано отрицательное char
или отрицательное signed char
- и chr
принимает любое значение, которое производит ord
и возвращает точно такое же char
.
На практике я, вероятно, просто использовал бы unsigned char
вместо того, чтобы использовать их, но они лаконично оборачивают приведение, предоставляют удобное место для добавления проверки ошибок для int
-to- char
, и были бы короче и понятнее, когда вам нужно использовать их несколько раз в непосредственной близости.