(这个答案解决了 C++ 方面的问题,但 C 中也存在符号扩展问题。)
处理所有三种char 类型(signed、unsigned 和char)比最初看起来要复杂得多。 0 到 SCHAR_MAX(对于 8 位 char 为 127)范围内的值很简单:
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.
这是可行的,因为任何采用三种 char 类型中的任何一种的函数也可以采用其他两种 char 类型。它导致两个函数可以处理任何类型:
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 添加错误检查,并且会是当您需要近距离使用它们多次时,它们会更短更清晰。