从 std::vector<unsigned char> 读取二进制数据的最简单方法?

Simplest way to read binary data from a std::vector<unsigned char>?

本文关键字:数据 二进制 读取 最简单 方法 char vector std lt unsigned gt      更新时间:2023-10-16

我有一大块const std::vector<unsigned char>形式的二进制数据,并希望能够从中提取单个字段,例如整数为 4 个字节,布尔值为 1 个字节等。这需要尽可能既高效又简单。例如。它应该能够就地读取数据,而无需将其复制到字符串或数组中)。它应该能够像解析器一样一次读取一个字段,因为数据块没有固定的格式。我已经知道如何确定在每种情况下要读取的字段类型 - 问题是在执行此操作的std::vector之上获得一个可用的界面。

但是,我

找不到一种简单的方法来将这些数据转换为易于使用的形式,从而为我提供有用的读取功能。 std::basic_istringstream<unsigned char>给了我一个读取界面,但似乎我需要先将数据复制到临时std::basic_string<unsigned char>中,这对于更大的数据块来说并不理想。

也许在这种情况下,我可以使用 streambuf 来就地读取数据,但似乎我需要派生我自己的 streambuf 类来做到这一点。

突然想到,我可能只在向量的数据()上使用 sscanf,这似乎比C++标准库替代方案更简洁、更高效。编辑:被提醒sscanf不会做我错误地认为它做的事情,我实际上不知道在C或C++中做到这一点的干净方法。但是我错过了什么吗,如果是这样,那又是什么?

您可以通过

向量operator[]访问向量中的数据。向量的数据保证存储在单个连续数组中,[]返回对该数组成员的引用。您可以直接使用该引用,也可以通过 memcpy 使用该引用。

std::vector<unsigned char> v;
...
byteField = v[12];
memcpy(&intField, &v[13], sizeof intField);
memcpy(charArray, &v[20], lengthOfCharArray); 

编辑 1:如果你想要比这更"方便"的东西,你可以尝试:

template <class T>
ReadFromVector(T& t, std::size_t offset, 
  const std::vector<unsigned char>& v) {
  memcpy(&t, &v[offset], sizeof(T));
}

用法将是:

std::vector<unsigned char> v;
...
char c;
int i;
uint64_t ull;
ReadFromVector(c, 17, v);
ReadFromVector(i, 99, v);
ReadFromVector(ull, 43, v);

编辑2:

struct Reader {
  const std::vector<unsigned char>& v;
  std::size_t offset;
  Reader(const std::vector<unsigned char>& v) : v(v), offset() {}
  template <class T>
  Reader& operator>>(T&t) {
    memcpy(&t, &v[offset], sizeof t);
    offset += sizeof t;
    return *this;
  }
  void operator+=(int i) { offset += i };
  char *getStringPointer() { return &v[offset]; }
};

用法:

std::vector<unsigned char> v;
Reader r(v);
int i; uint64_t ull;
r >> i >> ull;
char *companyName = r.getStringPointer();
r += strlen(companyName);

如果你的向量存储二进制数据,你不能使用 sscanf 或类似的东西,它们处理文本。将字节转换为布尔值非常简单

bool b = my_vec[10];

对于提取以大端序存储的无符号 int(假设您的整数为 32 位):

unsigned int i = my_vec[10] << 24 | my_vec[11] << 16 | my_vec[12] << 8 | my_vec[13];

16 位无符号短短将类似:

 unsigned short s = my_vec[10] << 8 | my_vec[11];¨

如果你能负担得起Qt依赖,QByteArray有一个名为constructor的fromRawData(),它将现有的数据缓冲区包装在QByteArray中而不复制数据。使用该字节数组,您可以馈送QTextStream

我不知道标准流库中有任何这样的功能(当然,除了实现您自己的streambuf),但我希望被证明是错误的:)

您可以使用描述您尝试提取的数据的结构。 您可以将数据从向量移动到结构中,如下所示:

struct MyData {
    int intVal;
    bool boolVal;
    char[15] stringVal;
} __attribute__((__packed__));
// assuming all extracted types are prefixed with a one byte indicator.
// Also assumes "vec" is your populated vector
int pos = 0;
while (pos < vec.size()-1) {
    switch(vec[pos++]) {
        case 0: { // handle int
            int intValue; 
            memcpy(&vec[pos], &intValue, sizeof(int));
            pos += sizeof(int); 
            // do something with handled value
            break;
        }
        case 1: { // handle double
            double doubleValue; 
            memcpy(&vec[pos], &doubleValue, sizeof(double));
            pos += sizeof(double); 
            // do something with handled value
            break;
        }
        case 2: { // handle MyData
            struct MyData data; 
            memcpy(&vec[pos], &data, sizeof(struct MyData));
            pos += sizeof(struct MyData); 
            // do something with handled value
            break;
        }
        default: {
            // ERROR: unknown type indicator
            break;
        }
    }
}

使用 for 循环遍历向量,并使用按位运算符访问每个位组。例如,要访问向量中第一个 usigned 字符的前四位:

int myInt = vec[0] & 0xF0;

要从右边读取第五位,紧跟在我们刚刚读取的块之后:

bool myBool = vec[0] & 0x08;

三个最低有效(最低)位可以像这样访问:

int myInt2 = vec[0] & 0x07;

然后,您可以对向量中的每个元素重复此过程(使用 for 循环)。