【问题标题】:Why std::(i)ostream treat signed / unsigned char as a text and not an integer?为什么 std::(i)ostream 将有符号/无符号字符视为文本而不是整数?
【发布时间】:2016-04-25 14:34:25
【问题描述】:

这段代码没有做它应该做的事情:

#include <iostream>
#include <cstdint>

int main()
{
    uint8_t small_integer;
    std::cin >> small_integer;
    std::cout << small_integer;
}

原因很简单:uint8_tunsigned char 的类型定义,流将这种类型视为文本:
Visual C++ 2015 实现

template<class _Traits> inline
    basic_istream<char, _Traits>& operator>>(
        basic_istream<char, _Traits>& _Istr, unsigned char& _Ch)
    {    // extract an unsigned char
    return (_Istr >> (char&)_Ch);
    }

还有一个类似的代码,将char 转换为operator &lt;&lt;

我的问题:

  1. 标准是否要求这种行为(流式操作符将有符号/无符号字符视为字符类型而不是整数)? 如果是那么:
    1. 这种违反直觉的语义背后的基本原理是什么?
    2. 这是否应该被视为缺陷,是否有建议更改此语义?

我可能应该添加一点解释,为什么我认为它违反直觉。 尽管类型名称包含单词 char,但 signedunsigned 部分指定了特定的整数语义,并且这些类型通常用作字节大小的整数。甚至标准都通过它们定义了int8_t/uint8_t

UPD:问题是关于unsigned charsigned char 的流式运算符重载的行为。

【问题讨论】:

  • 这很烦人。我使用了自己的 to_string 函数,它将 (u)int8_t 视为整数,同时将 char 视为字符。我为 uint8_t、int8_t 和 char 添加了单独的特化,因为我假设它对于那些不是三种不同类型的完全有效。
  • 不是您的问题的答案,但已提议使用std::byte 来解决此问题。
  • @Praetorian,虽然这确实是一个有趣的提议,但它解决了一个完全不同的问题。
  • @Matt 我会假设编译器无法区分 int8_tchar 作为参数列表中的类型。您的to_string 是否真的按照您的预期工作?
  • 如果int8_tuint8_tchar 的typedef 则不能工作,但如果它们是signed charunsigned char 的typedef 则可以工作。

标签: c++ language-lawyer


【解决方案1】:

标准 (n3797) 规定如下:

27.7.2.2.3 basic_istream::operator>>

template<class charT, class traits> 
basic_istream<charT,traits>& operator>>(basic_istream<charT,traits>& in, charT& c);

template<class traits> 
basic_istream<char,traits>& operator>>(basic_istream<char,traits>& in, unsigned char& c);

template<class traits> 
basic_istream<char,traits>& operator>>(basic_istream<char,traits>& in, signed char& c);

12 效果:行为类似于 in 的格式化输入成员(如 27.7.2.2.1 中所述)。在构造哨兵对象后从 in 中提取一个字符,如果有可用的,并存储在 c 中。否则,函数调用 in.setstate(failbit)。

27.7.3.6.4 字符插入功能模板

// specialization 
template<class traits> 
basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>& out, char c); 

// signed and unsigned 
template<class traits> 
basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>& out, signed char c); 

template<class traits> 
basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>& out, unsigned char c); 

1 效果:表现为 out 的格式化输出函数 (27.7.3.6.1)。构造一个字符序列 seq。如果 c 的类型为 char 且流的字符类型不是 char,则 seq 由 out.widen(c) 组成;否则 seq 由 c 组成。确定 seq 的填充,如 27.7.3.6.1 中所述。将 seq 插入到 out 中。调用 os.width(0)。

所以第一个问题的答案是:是的,标准要求operator &gt;&gt;operator &lt;&lt; 对于charunsigned charsigned char 的行为完全相同,即它们读/写单个字符,而不是整数。不幸的是,标准没有解释原因。我希望有人能阐明 2 和 3。

【讨论】:

    【解决方案2】:
    1. 标准是否要求这种行为?如果是的话:

    您已经回答了这个问题。是的,标准定义了 iostreams 应该如何处理有符号和无符号字符。

    1. 这种违反直觉的语义背后的基本原理是什么?

    因为signed charunsigned char字符类型,所以它们总是被iostreams 类视为字符。

    线索在名称中:signed char 是有符号字符类型。 unsigned char 是无符号字符类型。其他整数类型的名称中包含 int(即使它有时是可选的,例如 shortlong unsigned 分别与 short intlong unsigned int 相同)。

    标准不需要说明为什么这是正确的,因为它不是设计文档或 C 和 C++ 历史的基本原理,而是规范。

    如果您想要一个行为类似于只有 8 位的整数的类型,那么您需要创建自己的类型(例如,使用枚举类型或保存值的结构)并定义相关的运算符重载。

    1. 这是否应该被视为缺陷,是否有建议更改此语义?

    不,我不这么认为。它们一直是字符类型,要更改它会破坏太多代码。

    【讨论】:

      猜你喜欢
      • 2014-08-20
      • 2020-06-02
      • 1970-01-01
      • 2015-05-11
      • 2022-01-20
      • 2011-06-14
      • 1970-01-01
      相关资源
      最近更新 更多