前段时间,StackOverflow上有名望的人在评论中写道,在调用char
(和类似函数)之前,有必要将unsigned char
- 参数强制转换为std::toupper
另一方面,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版。)
The reference表示输入需要表示为unsigned char
。
对我来说,这似乎适用于每个char
,因为char
和unsigned char
具有相同的尺寸。
这个演员是不必要的还是Stroustrup不小心?
编辑:libstdc++ manual提到输入字符必须来自basic source character set,但不会投射。我想@Keith Thompson的回复涵盖了这一点,他们都有signed char
和unsigned char
的正面陈述?
答案 0 :(得分:27)
是的,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
值。如果普通char
是签名类型,并且name[0]
返回的值恰好为负数,那么表达式
toupper(name[0])
有未定义的行为。
该语言保证即使普通char
已签名,基本字符集的所有成员都具有非负值,因此初始化
string name = "Niels Stroustrup";
该程序不会冒未定义行为的风险。但是,是的,通常传递给char
的{{1}}值(或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
为32位,则将int
值char
转换为-2
会产生unsigned
。然后将其隐式转换为4294967294
(参数类型),可能产生int
。
-2
可以实施,因此它对负值表现得合理(接受toupper
到CHAR_MIN
的所有值),但不需要这样做。此外,UCHAR_MAX
中的函数需要接受值为<ctype.h>
的参数,通常为EOF
。
C ++标准对某些C标准库函数进行了调整。例如,-1
和其他几个函数被重载版本替换,这些版本强制strchr
正确性。对const
中声明的函数没有这样的调整。
答案 1 :(得分:3)
引用是指可表示的值为unsigned char
,而不是 unsigned char
。也就是说,如果实际值不在0和UCHAR_MAX
之间(通常为255),则行为是不确定的。 (或EOF
,这基本上是int
代替char
的原因。)
答案 2 :(得分:2)
在C中,toupper
(以及许多其他功能)取int
s,即使您希望它们采用char
s。此外,char
在某些平台上签名,在其他平台上签名。
在调用unsigned char
之前强制转换为toupper
的建议对于C是正确的。我认为在C ++中不需要它,只要你传递我找不到任何特定于C ++中是否需要的东西。int
即可。范围。
如果您想回避问题,请使用toupper
defined in <locale>
。它是一个模板,采用任何可接受的字符类型。你还必须传递std::locale
。如果您不知道要选择哪个区域设置,请使用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;
}
答案 3 :(得分:1)
答案 4 :(得分:0)
您可以强制转换函数,而不是将参数转换为unsigned char。您需要包含功能标头。这是一个示例代码:
#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字符没有影响。但这种铸造有利于避免意外行为。