使用位运算符在C++中将4个字符转换为整数

6
我需要做的是以二进制模式打开包含存储数据的文件,这些数据将被解释为整数。我看过其他示例,例如Stackoverflow-从char*数组中读取“整数”大小字节。,但我想尝试采用不同的方法(可能只是固执,或者愚蠢 :/)。我首先在十六进制编辑器中创建了一个简单的二进制文件,如下所示。 00 00 00 47 00 00 00 17 00 00 00 41 如果将这12个字节分成3个整数,则应该等于71、23和65。
在以二进制模式打开此文件并将4个字节读入字符数组后,如何使用位运算使char[0]的位成为int的前8位,直到每个char的位都成为int的一部分?
 
My integer = 00        00        00        00  
 +           ^         ^         ^         ^
Chars      Char[0]  Char[1]   Char[2]   Char[3]
             00        00        00        47


So my integer(hex) = 00 00 00 47 = numerical value of 71

此外,我不知道我的系统的字节序在这里起到什么作用,所以有什么需要注意的吗?

以下是我目前的代码片段,我不知道接下来该怎么做。


std::fstream myfile;
    myfile.open("C:\\Users\\Jacob\\Desktop\\hextest.txt", std::ios::in | std::ios::out | std::ios::binary);
    if(myfile.is_open() == false)
    {
        std::cout << "Error" << std::endl;
    }
    char* mychar;
    std::cout << myfile.is_open() << std::endl;
    mychar = new char[4];
    myfile.read(mychar, 4);

我最终计划处理从文件中读取浮点数和可能的自定义数据类型,但首先我需要更熟悉使用位运算。

谢谢。


2
撇开固执的态度,你可以通过读取一个整数并使用ntohl()函数来完成这个任务。 - David Gelhar
你的二进制文件是“大端序”的。只要你按照大端序的方式将其字节转换为整数,就不需要担心计算机的字节序问题。 - aschepler
直到你将代码移植到另一个系统并尝试从大端系统读取数据,那时你就会遇到问题。 - Thomi
@Thomi:因此,David调用了ntohl()来防止这种情况。 - Cameron
2个回答

21

您需要使用位左移运算符:

typedef unsigned char u8;  // in case char is signed by default on your platform
unsigned num = ((u8)chars[0] << 24) | ((u8)chars[1] << 16) | ((u8)chars[2] << 8) | (u8)chars[3];

它的作用是将左边的参数向左移动指定的位数,从右边添加零作为填充。例如,2 << 1 是4,因为2在二进制中表示为10,将其向左移动一个位置得到100,这是4。

这可以用更通用的循环形式来表示:

unsigned num = 0;
for (int i = 0; i != 4; ++i) {
    num |= (u8)chars[i] << (24 - i * 8);    // += could have also been used
}
您的系统的字节序在这里并不重要; 您知道文件中表示的字节序是恒定的(因此可移植), 因此当您读取这些字节时,您知道该如何处理它们。在CPU/内存中整数的内部表示可能与文件中的表示不同,但在代码中对它进行逻辑位操作与系统的字节序无关; 最不重要的位始终在右侧,而最重要的位始终在左侧(在代码中)。这就是为什么移位操作跨平台——它在逻辑位级别上操作的原因 :-)

干净利落的回答。谢谢,这帮助我解决了字节序问题。 - Dig The Code
它是否能正确处理符号?不应该使用“unsigned char”吗? - ar2015
1
@ar2015:你说得完全正确,这段代码无法处理有符号的字符(signed chars),因为它们会在移位之前被隐式转换成相同数值的int类型。我会添加一些强制转换。 - Cameron
(24 - i * 8) 需要记住运算符优先级,并假设特定的实现定义。我更喜欢在这里使用 ((sizeof(unsigned) - i) * CHAR_BITS) - Caleth
@Caleth:虽然我同意在库代码中使用CHAR_BITS更好,但为了保持示例简单,我认为假设8位字符相当安全;其他类型也必须进行更改,否则它们的位数可能小于32位。至于运算符优先级,毫无疑问乘法比减法具有更高的优先级吧?这不仅适用于C++... - Cameron

2
您曾经考虑过使用Boost.Spirit来制作二进制解析器吗?当您开始时,可能会遇到一些学习曲线,但如果您想以后扩展程序以读取浮点数和结构化类型,则将拥有一个非常好的基础。
Spirit文档非常齐全,并且是Boost的一部分。一旦您理解其内部细节,您可以做的事情真的很令人难以置信。因此,如果您有一些时间来尝试它,我真的建议您看一看。
否则,如果您希望您的二进制文件“可移植”-即您希望能够在大端和小端机器上读取它,则需要某种字节顺序标记(BOM)。这将是您读取的第一件事,在此之后,您只需逐字节读取整数即可。最简单的方法可能是将它们读入联合体中(如果您知道要读取的整数的大小),如下所示:
union U
{
    unsigned char uc_[4];
    unsigned long ui_;
};

将数据读入uc_成员中,如果需要更改字节序,请交换字节顺序并从ui_成员中读取值。除了更改字节序之外,不需要进行任何移位等操作。

希望对您有所帮助。

rlc


我一直在使用很多boost(线程,文件系统,随机数,数学),所以我可能会尝试更熟悉spirit类。 - contrapsych

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