34

前段时间,在 Stack Overflow 上享有盛誉的人在评论中写道,有必要在调用and (和类似函数)之前将char-argument 转换为。unsigned charstd::toupperstd::tolower

另一方面,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"
}

(引自该书,第 4 版。)

参考资料说输入需要可以表示为unsigned char。对我来说,这听起来像是它适用于每一个charchar并且unsigned char具有相同的大小。

那么这个演员阵容是不必要的,还是 Stroustrup 粗心的?

编辑:libstdc++ 手册提到输入字符必须来自基本源字符集,但不强制转换。我想@Keith Thompson 的回复已经涵盖了这一点,它们都有一个积极的表示signed charunsigned char

4

5 回答 5

35

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

类型charsigned charunsigned char是三种不同的类型。具有与orchar相同的范围和表示。(Plain非常常用,可以表示 -128..+127 范围内的值。) signed char unsigned charchar

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

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

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

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

toupper(name[0])

具有未定义的行为。

该语言保证,即使 plainchar是有符号的,基本字符集的所有成员都具有非负值,所以给定初始化

string name = "Niels Stroustrup";

该程序不会冒未定义行为的风险。但是是的,通常char传递给(或传递给/toupper中声明的任何函数)的值需要转换为,以便隐式转换不会产生负值并导致未定义的行为。<cctype><ctype.h>unsigned charint

这些<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为. 然后将其隐式转换为(参数类型),这可能会产生.unsigned4294967294int-2

toupper 可以实现,因此它对负值的行为是明智的(接受来自CHAR_MINtoUCHAR_MAX的所有值),但不是必须这样做。此外,in<ctype.h>中的函数需要接受带有 value 的参数,该参数EOF通常为-1

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

于 2014-02-16T01:09:24.913 回答
5

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

于 2014-02-16T00:34:44.613 回答
3

在 C 中,toupper(和许多其他函数)int即使您希望它们使用 s,也需要使用chars。此外,char在某些平台上已签名,而在其他平台上未签名。

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

如果您想回避这个问题,请toupper使用<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;
}
于 2014-02-16T00:46:57.047 回答
1

可悲的是,Stroustrup 粗心:-(
是的,拉丁字母代码应该是非负的(并且不需要强制转换)......
有些实现在不强制转换为无符号字符的情况下正确工作......
根据一些经验,它可能会花费几个几个小时来查找此类 toupper 的段错误的原因(当已知存在段错误时)......
还有 isupper,islower 等

于 2014-02-16T03:53:36.120 回答
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 字符没有影响。但是这种强制转换有利于避免意外行为。

于 2016-01-10T08:11:50.730 回答