在给toupper打电话之前,我需要转换成unsigned char吗?

前段时间,在StackOverflow上有很高声望的人在评论中写道,在调用std::toupper (和类似的函数)之前,必须将char转换为unsigned char

另一方面,Bjarne Stroustrup没有提到在C ++编程语言中这样做的必要性。 他只是使用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" } 

(从第四版的书中引用)

引用说input需要表示为unsigned char 。 对我来说这听起来像它适用于每个char因为charunsigned char具有相同的大小。

那么这样的表演是不必要的还是Stroustrup不小心呢?

编辑: libstdc ++手册提到input字符必须来自基本的源字符集 ,但不会投。 我猜这是由@Keith Thompson的答复所覆盖,他们都有一个积极的代表作为signed charunsigned char

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

typescharsigned charunsigned char是三种不同的types。 charsigned char unsigned char具有相同的范围和表示forms。 (普通char是非常普遍的符号,能够表示-128 .. + 127范围内的值。)

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

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

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

std::string上的[]索引操作符返回一个char值。 如果plain char是一个有符号的types,并且name[0]返回的值恰好是负数,那么就是expression式

 toupper(name[0]) 

有未定义的行为。

该语言保证,即使对普通char进行了签名,基本字符集的所有成员都具有非负值,所以给予了初始化

 string name = "Niels Stroustrup"; 

该程序不会冒险未定义的行为。 但是,一般来说,传递给toupper (或在<cctype> / <ctype.h>声明的任何函数都需要转换为unsigned char ,所以隐式转换为int不会产生负数价值并导致未定义的行为。

通常使用查找表来实现<ctype.h>函数。 就像是:

 // 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转换为unsigned4294967294 。 然后隐式转换为int (参数types),这可能会产生-2

toupper 可以被实现,所以它对于负值(接受从CHAR_MINUCHAR_MAX所有值)的行为是合理的,但是并不需要这么做。 此外, <ctype.h>中的函数需要接受值为EOF的参数,该值通常为-1

C ++标准对一些C标准库函数进行了调整。 例如, strchr和其他几个函数被重载的版本所替代,强制const正确性。 在<cctype>声明的函数没有这样的调整。

该引用指的是可表示为无符号字符的值,而不是无符号字符。 也就是说,如果实际值不在0到255之间,则行为是不确定的。(或者EOF,这基本上是它取int而不是char的原因)。

可悲的Stroustrup是粗心的:-(
是的,拉丁字母代码应该是非负面的(不需要强制转换)…
一些实现正确的工作,而无需转换为无符号字符…
根据一些经验,可能花费几个小时才能find这种toupper的段错误的原因(当知道段错误时)。
还有上,下等

在C中, toupper (和许多其他的函数)都是int的,即使你期望它们可以带char 。 另外, char在某些平台上被签名,而在其他平台上没有签名。

在调用toupper之前抛出unsigned char的build议对于C来说是正确的。 我不认为在C ++中需要它,只要你传递一个int可以了。 我无法find任何特定于C ++中是否需要的东西。

如果你想避开这个问题,使用<locale>定义的toupper 。 这是一个模板,并采取任何可接受的字符types。 你也必须传递一个std::locale 。 如果您不知道要select哪种语言环境,请使用std::locale("") ,它应该是用户的首选语言环境:

 #include <algorithm> #include <iostream> #include <iterator> #include <locale> #include <string> 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; } 

而不是将参数作为无符号字符强制转换,您可以投射该函数。 您将需要包含function标题。 这是一个示例代码:

 #include <string> #include <algorithm> #include <functional> #include <locale> #include <iostream> 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<int(BYTE)>)::toupper); std::cout << "uppercase name: " << name << '\n'; return 0; } 

输出是:

 uppercase name: DANIEL BRüHL 

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