在调用toupper(),tolower()等之前,是否需要转换为unsigned char?

不久之前,StackOverflow上有名望的人在评论中写道,在调用std::toupper (和类似的函数)之前,有必要将char -argument转换为unsigned char

另一方面,Bjarne Stroustrup没有提到在C ++ – Programming Language中这样做的必要性。 他只是喜欢使用toupper

 string name = "Niels Stroustrup"; void m3() { string s = name.substr(6,10); // s = "Stroustr up" name.replace(0,5,"nicholas"); // name becomes "nicholas Stroustrup" name[0] = toupper(name[0]); // name becomes "Nicholas Stroustrup" } 

(引自上述书,第4版。)

该引用表明输入需要表示为unsigned char 。 对我来说,这听起来像每个char都有,因为charunsigned char具有相同的大小。

那么这个演员是不必要的还是Stroustrup不小心?

编辑: libstdc ++手册提到输入字符必须来自基本源字符集 ,但不会强制转换。 我想这是@Keith Thompson的回复所涵盖的,他们都有正面的代表作为signed charunsigned char

是的, toupper的参数需要转换为unsigned char以避免未定义行为的风险。

charsigned charunsigned char类型是三种不同的类型。 char具有与signed char unsigned char相同的范围和表示。 (普通char非常常见,能够表示-128 .. + 127范围内的值。)

toupper函数接受一个int参数并返回一个int结果。 引用C标准,第7.4节第1段:

在所有情况下,参数都是一个int ,其值应表示为unsigned char或者等于宏EOF的值。 如果参数具有任何其他值,则行为未定义。

(C ++包含了大部分C标准库,并将其定义推迟到C标准。)

std::string上的[]索引运算符返回一个char值。 如果plain char是有符号类型,并且name[0]返回的值恰好是负数,那么表达式

 toupper(name[0]) 

有未定义的行为。

该语言保证,即使普通char被签名,基本字符集的所有成员都具有非负值,因此初始化

 string name = "Niels Stroustrup"; 

该程序不会冒未定义的行为风险。 但是,是的,通常传递给toupperchar值(或者在 / 声明的任何函数)需要转换为unsigned char ,因此隐式转换为int不会产生负数值并导致未定义的行为。

函数通常使用查找表实现。 就像是:

 // assume plain char is signed char c = -2; c = toupper(c); // undefined behavior 

可以在该表的范围之外索引。

请注意,转换为unsigned

 char c = -2; c = toupper((unsigned)c); // undefined behavior 

不能避免这个问题。 如果int是32位,则将char-2转换为unsigned产生4294967294 。 然后将其隐式转换为int (参数类型),这可能会产生-2

toupper 可以实现,因此它对负值表现得很明智(接受从CHAR_MINUCHAR_MAX所有值),但不需要这样做。 此外, 中的函数需要接受值为EOF的参数,通常为-1

C ++标准对某些C标准库函数进行了调整。 例如, strchr和其他几个函数被重载版本替换,这些版本强制执行const正确性。 对于声明的函数没有这样的调整。

在C中, toupper (以及许多其他函数)都会使用int即使你希望它们采用char 。 此外, char在某些平台上签名,在其他平台上签名。

在调用toupper之前toupperunsigned char的建议对于C是正确的。 我不认为在C ++中需要它,只要你传递一个在范围内的int 我找不到任何特定于C ++中是否需要的东西。

如果您想回避问题,请使用定义的toupper 。 它是一个模板,采用任何可接受的字符类型。 你还必须传递一个std::locale 。 如果您不知道要选择哪个区域设置,请使用std::locale("") ,它应该是用户首选的区域设置:

 #include  #include  #include  #include  #include  int main() { std::string name("Bjarne Stroustrup"); std::string uppercase; std::locale loc(""); std::transform(name.begin(), name.end(), std::back_inserter(uppercase), [&loc](char c) { return std::toupper(c, loc); }); std::cout << name << '\n' << uppercase << '\n'; return 0; } 

引用是指可表示unsigned char的值,而不是unsigned char 。 也就是说,如果实际值不在0和UCHAR_MAX之间(通常为255),则行为未定义。 (或EOF ,这基本上是它采用int而不是char 。)

可悲的是,Stroustrup很粗心:-(
是的,拉丁字母代码应该是非负的(并且不需要演员表)……
一些实现正确工作,无需转换为unsigned char …
根据一些经验,可能需要几个小时来找到这种toupper的段错误的原因(当知道有一个段错误时)……
而且还有isupper,islower等

您可以转换函数,而不是将参数转换为unsigned char。 您需要包含function标题。 这是一个示例代码:

 #include  #include  #include  #include  #include  int main() { typedef unsigned char BYTE; // just in case std::string name("Daniel Brühl"); // used this name for its non-ascii character! std::transform(name.begin(), name.end(), name.begin(), (std::function)::toupper); std::cout << "uppercase name: " << name << '\n'; return 0; } 

输出是:

 uppercase name: DANIEL BRüHL 

正如所料,toupper对非ascii字符没有影响。 但这种铸造有利于避免意外行为。