在给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
因为char
和unsigned char
具有相同的大小。
那么这样的表演是不必要的还是Stroustrup不小心呢?
编辑: libstdc ++手册提到input字符必须来自基本的源字符集 ,但不会投。 我猜这是由@Keith Thompson的答复所覆盖,他们都有一个积极的代表作为signed char
和unsigned char
?
是的, toupper
的参数需要转换为unsigned char
来避免未定义行为的风险。
typeschar
, signed char
和unsigned char
是三种不同的types。 char
与signed 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
转换为unsigned
值4294967294
。 然后隐式转换为int
(参数types),这可能会产生-2
。
toupper
可以被实现,所以它对于负值(接受从CHAR_MIN
到UCHAR_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 ++中需要它,只要你传递一个 我无法find任何特定于C ++中是否需要的东西。 int
可以了。
如果你想避开这个问题,使用<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字符没有影响。 但是这种铸造对避免意外行为是有益的。