在调用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
都有,因为char
和unsigned char
具有相同的大小。
那么这个演员是不必要的还是Stroustrup不小心?
编辑: libstdc ++手册提到输入字符必须来自基本源字符集 ,但不会强制转换。 我想这是@Keith Thompson的回复所涵盖的,他们都有正面的代表作为signed char
和unsigned char
?
是的, toupper
的参数需要转换为unsigned char
以避免未定义行为的风险。
char
, signed char
和unsigned 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";
该程序不会冒未定义的行为风险。 但是,是的,通常传递给toupper
的char
值(或者在
/
声明的任何函数)需要转换为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_MIN
到UCHAR_MAX
所有值),但不需要这样做。 此外,
中的函数需要接受值为EOF
的参数,通常为-1
。
C ++标准对某些C标准库函数进行了调整。 例如, strchr
和其他几个函数被重载版本替换,这些版本强制执行const
正确性。 对于
声明的函数没有这样的调整。
在C中, toupper
(以及许多其他函数)都会使用int
即使你希望它们采用char
。 此外, char
在某些平台上签名,在其他平台上签名。
在调用toupper
之前toupper
为unsigned char
的建议对于C是正确的。 我不认为在C ++中需要它,只要你传递一个在范围内的 我找不到任何特定于C ++中是否需要的东西。 int
。
如果您想回避问题,请使用
定义的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字符没有影响。 但这种铸造有利于避免意外行为。