uint8_t无法使用cout打印。

221

我在使用C++处理整数时遇到了一个奇怪的问题。

我写了一个简单的程序,将一个值赋给一个变量,然后打印出来,但是它并没有按照预期的工作。

我的程序只有两行代码:

uint8_t aa = 5;

cout << "value is " << aa << endl;

该程序的输出为value is

即对于aa,它打印了空白。

当我将uint8_t更改为uint16_t时,上面的代码运行得很好。

我使用的是64位Ubuntu 12.04(Precise Pangolin)操作系统,我的编译器版本是:

gcc version 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)

2
可能是 uint8_t iostream behavior 的重复问题。 - phuclv
[c++编程] cout无法打印unsigned char - phuclv
2
你真的会认为这应该“只是工作”。安息吧,C++类型系统。这不是类型的整个意义吗? - Matt Messersmith
8个回答

233
它实际上并不打印空白,而是很可能打印ASCII值为5的字符,这是不可打印的(或看不见的)。有许多不可见的ASCII字符代码,其中大部分的值都低于32,而这个值实际上就是空白。
你需要将a转换为unsigned int以输出数值,因为ostream& operator<<(ostream&, unsigned char)会尝试输出可见字符的值。
uint8_t a = 5;

cout << "value is " << unsigned(a) << endl;

30
由于C风格的强制类型转换不被看好,使用 static_cast 是否更好? - Tim Seguine
47
应该将其转换int。强制类型转换是一种方法,但不是唯一的方法。+aa 也可以实现。 - Pete Becker
7
int(var)和(int)var不是同一件事吗? - paulm
9
使用type(var)与(type)var是一样的,这与C语言中的强制类型转换是相同的-尝试使用const等进行转换,将删除它!请参见链接的问题。 - paulm
16
回答:“不,对于C ++,不鼓励使用C样式的强制类型转换,有很多原因。”针对“不是int(var)和(int)var实际上是一回事吗?”的回答,确实让人觉得您没有意识到int(var)(int)var有完全相同的含义。 int(var)在正是那些情况下被不鼓励使用,此时(int)var也是如此,由于它们的含义完全相同。 (我能理解为什么您会在这里尝试使用它,所以我并不是说您需要使用static_cast。我只是认为这里的注释过程变得有点不必要混乱。) - user743382
显示剩余21条评论

79

在任何原始数据类型的变量前添加一元的"+"运算符,将会给出可打印的数值,而不是ASCII字符(在char类型的情况下)。

uint8_t aa = 5;
cout<<"value is "<< +aa <<endl; // value is 5

2
很好,但为什么C++不将uint8_t视为数值值的unsigned char - R1S8K
1
@R1S8K 这是因为 uint8_t 只是 unsigned char 的类型定义,而 unsigned char 本身像 char 一样由 ostream 处理,并打印其 ASCII 值。 - Harsh
@R1S8K 嗯,最小的整数类型应该是 short int,它占用 2 个字节。还有一些其他变体的整数类型。 - Harsh
@Harsh 是的,你说得对。我在Arduino IDE中进行了一个小测试,声明了一个变量long并将其初始化为一个小数、一个大数和空值,结果都一样,没有从RAM/Flash中取出任何东西!但是除了设置和循环函数之外,我没有编写任何代码,我认为必须有一些使用变量的代码,如果它被分配了像const dataType variableName[] PROGMEM = {data0, data1, data3…​};这样的东西,就会从RAM或Flash中计算。 - R1S8K
这是如何工作的? - WeZZard
显示剩余7条评论

61

uint8_t 很可能是 unsigned char 的一种类型定义。对于 unsigned charostream 类有一个特殊的重载,即它打印编号为5的字符,而这个字符是不可打印的,因此留下了空格。


34
我希望标准将std :: uint8_t作为单独的类型处理,而不仅仅是一种typedef。在与流对象一起使用时,将字符语义应用于这些类型没有任何合理的理由。 - antred

21
  • 利用ADL(参数相关名称查找):

#include <cstdint>
#include <iostream>
#include <typeinfo>

namespace numerical_chars {
inline std::ostream &operator<<(std::ostream &os, char c) {
    return std::is_signed<char>::value ? os << static_cast<int>(c)
                                       : os << static_cast<unsigned int>(c);
}

inline std::ostream &operator<<(std::ostream &os, signed char c) {
    return os << static_cast<int>(c);
}

inline std::ostream &operator<<(std::ostream &os, unsigned char c) {
    return os << static_cast<unsigned int>(c);
}
}

int main() {
    using namespace std;

    uint8_t i = 42;

    {
        cout << i << endl;
    }

    {
        using namespace numerical_chars;
        cout << i << endl;
    }
}

输出:

*
42
  • 还有一种自定义流操纵符也是可能的。

  • 一元加运算符也是一个不错的习惯用法(cout << +i << endl)。

  • 9
    “KISS原则”仍然是一种有效的范例吗? - πάντα ῥεῖ
    1
    @πάνταῥεῖ 当然,但不要忘记:一切都应该尽可能简单,但不能过于简单 - oblitum
    4
    @πάνταῥεῖ 好的,那就继续在 C++ 代码中使用大量的 C 风格转换吧,每个人都可以自由选择适合自己的环境来提高生产力。 - oblitum
    5
    @πάνταῥεῖ 真的吗?函数式类型转换与C风格类型转换一样。将一个转换为另一个在离开C领域方面没有任何帮助,可以查看:https://dev59.com/E2445IYBdhLWcg3wnrki#4775807。 Pete-Becker 也在您的回答中发表了评论,但您似乎错过了他的最后评论。 - oblitum
    6
    这个解决方案非常优雅和有效,因为它使用模板。实际上,这是我发现的唯一适合我的解决方案。 不过需要注意的是,第一个函数有一个问题,因为“os”绑定到单个类型,因此,有符号或无符号值将被发送到operator<<()的错误版本。修复很简单: return std::is_signed<char>::value ? os << static_cast<int>(c) : os << static_cast<unsigned int>(c); - Georges
    显示剩余12条评论

    20

    这是因为输出运算符将 uint8_t 视为 charuint8_t 通常只是 unsigned char 的别名),因此它会打印 ASCII 码(这是最常见的字符编码系统)为 5 的字符。

    详见例如这个参考资料


    为什么?C编译器将其视为数字。我认为在这一点上,C++是不同的。 - R1S8K
    @PerchEagle 如果您阅读链接的参考资料,您将会看到该运算符被重载用于signedunsigned字符(超出了在C++中实际上是第三个独立类型的普通char)。因此,如果uint8_tunsigned char的别名(非常可能),那么它就会被使用。 - Some programmer dude
    你能否在这个线程上检查我的答案,并告诉我我的答案是否正确? https://dev59.com/_mUp5IYBdhLWcg3wAj3y,我的答案在最后一个之前。非常感谢您。 - R1S8K

    8

    coutaa 作为 ASCII 值为 5char 处理,这是一个不可打印的字符,尝试在打印之前进行类型转换为 int


    变量大小将从1字节增加到4字节。我想要一个1字节的变量。有什么帮助吗? - Sadegh J

    7

    std::ostreamchar之间的operator<<()重载是一个非成员函数。您可以明确使用成员函数将char(或uint8_t)视为int

    #include <iostream>
    #include <cstddef>
    
    int main()
    {
       uint8_t aa=5;
    
       std::cout << "value is ";
       std::cout.operator<<(aa);
       std::cout << std::endl;
    
       return 0;
    }
    

    输出:

    value is 5
    

    3
    正如其他人之前所说,问题出现在标准流中将有符号字符和无符号字符视为单个字符而不是数字。
    以下是我的解决方案,只需进行最少的代码更改:
    uint8_t aa = 5;
    
    cout << "value is " << aa + 0 << endl;
    

    在任何数字(包括浮点数)中添加"+0"都是安全的。

    对于整数类型,如果sizeof(aa) < sizeof(int),它将更改结果的类型为int。如果sizeof(aa) >= sizeof(int),则不会更改类型。

    这种解决方案还适用于准备将int8_t打印到流中,而其他一些解决方案则不太好:

    int8_t aa = -120;
    
    cout << "value is " << aa + 0 << endl;
    cout << "bad value is " << unsigned(aa) << endl;
    

    输出:

    value is -120
    bad value is 4294967176
    

    顺便说一句,由pepper_chico和πάντα ῥεῖ提供的ADL解决方案真的很棒。


    变量大小将从1字节增加到4字节。我需要一个1字节的变量。有任何帮助吗? - Sadegh J
    聪明,我有一个针对uint8_t和float的模板化<<运算符,这个运算符完美地工作。 - trexxet

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接