C++ 将十六进制字符串转换为有符号整数

160

我想在C++中将十六进制字符串转换为32位有符号整数。

例如,我有一个十六进制字符串"fffefffe"。其二进制表示为11111111111111101111111111111110。其有符号整数表示为:-65538。

如何在C++中进行此转换?这也需要适用于非负数。例如,十六进制字符串"0000000A",其二进制表示为00000000000000000000000000001010,十进制表示为10。


2
请注意,对于 sizeof(int) == 4 的系统,您将只获得 -65538。 - Martin York
3
@Martin York没有提到int。"32位有符号整数"可能是int32_t__int32等。 - Kirill V. Lyadvinsky
10个回答

272

使用 std::stringstream

unsigned int x;   
std::stringstream ss;
ss << std::hex << "fffefffe";
ss >> x;

以下示例的结果为-65538
#include <sstream>
#include <iostream>

int main() {
    unsigned int x;   
    std::stringstream ss;
    ss << std::hex << "fffefffe";
    ss >> x;
    // output it as a signed type
    std::cout << static_cast<int>(x) << std::endl;
}

在新的C++11标准中,有一些新的实用函数可以使用!具体来说,有一组“字符串转数字”函数(http://en.cppreference.com/w/cpp/string/basic_string/stolhttp://en.cppreference.com/w/cpp/string/basic_string/stoul)。这些函数本质上是C语言字符串转数字转换函数的薄包装器,但知道如何处理std::string
因此,对于更新的代码,最简单的答案可能看起来像这样:
std::string s = "0xfffefffe";
unsigned int x = std::stoul(s, nullptr, 16);

注意:下面是我的原始答案,正如编辑所说的那样,这不是一个完整的答案。对于一个功能性的解决方案,请将代码放在上面的行之上 :-).

看起来由于lexical_cast<>被定义为具有流转换语义,因此它不支持"0x"表示法。很遗憾,流无法理解"0x"表示法。因此,boost::lexical_cast和我手动编写的代码都无法处理十六进制字符串。上面的解决方案手动将输入流设置为十六进制,可以很好地处理它。

Boost有一些工具也可以做到这一点,并具有一些良好的错误检查功能。您可以像这样使用它:

try {
    unsigned int x = lexical_cast<int>("0x0badc0de");
} catch(bad_lexical_cast &) {
    // whatever you want to do...
}

如果您不想使用boost,这里有一个轻量级的lexical cast版本,它不进行错误检查:

template<typename T2, typename T1>
inline T2 lexical_cast(const T1 &in) {
    T2 out;
    std::stringstream ss;
    ss << in;
    ss >> out;
    return out;
}

你可以像这样使用它:
// though this needs the 0x prefix so it knows it is hex
unsigned int x = lexical_cast<unsigned int>("0xdeadbeef"); 

2
当我使用那个方法时,最终得到的是一个整数值152144602。 - Clayton
1
@SteveWilkinson:请阅读以“EDIT”开头的段落。它解释了您需要如何使用std::hex - Evan Teran
2
对于 stringstream,应该检查 ss.good() && ss.eof() 来确保没有发生错误。 - atoMerz
@EvanTeran 这个问题有点老了,但希望您还在身边帮助我解决疑惑。如果 0xfffefffe 对应于有符号整数值,为什么您的变量 x 是一个 unsigned int?同样,为什么您必须使用 stoul 而不是 stoi 来使其工作?我尝试了 stoi,但它抛出了一个异常。感谢您提供的任何帮助! - user3266738
1
这个 "stoul" 保存了我的逻辑。 - vincenzopalazzo
显示剩余5条评论

70

如果你想要一个适用于C和C++的方法,你可以考虑使用标准库函数strtol()。

#include <cstdlib>
#include <iostream>
using namespace std;

int main() {
    string s = "abcd";
    char * p;
    long n = strtol( s.c_str(), & p, 16 );
    if ( * p != 0 ) { //my bad edit was here
        cout << "not a number" << endl;
    }
    else {
        cout << n << endl;
    }
}

4
你应该使用 strtoul 而不是 strtol。在使用 strtol 时会出现 + 溢出 的情况。使用 strtoul 将不会出现溢出情况,并且返回的值将被转换为 long 类型以产生正确的结果(-65538)。所以你的答案几乎是正确的 :) - Kirill V. Lyadvinsky
10
因为使用 strtol(或 strtoul)比使用 stringstream 更快。 - Kirill V. Lyadvinsky

27

Andy Buchanan,就坚持使用C++而言,我喜欢你的想法,但我有一些修改:

template <typename ElemT>
struct HexTo {
    ElemT value;
    operator ElemT() const {return value;}
    friend std::istream& operator>>(std::istream& in, HexTo& out) {
        in >> std::hex >> out.value;
        return in;
    }
};

使用方法类似于

uint32_t value = boost::lexical_cast<HexTo<uint32_t> >("0x2a");
那样的话,你就不需要为每种整数类型都编写一个实现。

1
我也会采取这一步骤,但我发现我喜欢限制尖括号的扩散。对于这种情况,我觉得打破“不要重复代码”的规则是有道理的。 :-) - Andy J Buchanan
很不幸需要这样做,但做得很好。已添加到我的个人STL/Boost扩展/修复头文件中。谢谢! - Tim Sylvester
很遗憾,这仅适用于无符号转换。因此,您无法将0xFFFFFFFF转换为-1。 - fmuecke
@fmuecke:这是因为0xFFFFFFFF是有符号整数溢出,这是未定义的行为。 - Billy ONeal

16

使用 strtoul 的工作示例如下:

#include <cstdlib>
#include <iostream>
using namespace std;

int main() { 
    string s = "fffefffe";
    char * p;
    long n = strtoul( s.c_str(), & p, 16 ); 
    if ( * p != 0 ) {  
        cout << "not a number" << endl;
    }    else {  
        cout << n << endl;
    }
}

strtol函数将string转换为long类型。在我的电脑上,numeric_limits<long>::max()返回的是0x7fffffff。很明显,0xfffefffe大于0x7fffffff。因此,strtol返回了MAX_LONG而不是想要的值。在这种情况下,strtoulstring转换为无符号长整型,因此不会出现溢出。

好的,strtol在进行转换之前没有将输入字符串视为32位有符号整数。以下是一个有趣的strtol示例:

#include <cstdlib>
#include <iostream>
using namespace std;

int main() { 
    string s = "-0x10002";
    char * p;
    long n = strtol( s.c_str(), & p, 16 ); 
    if ( * p != 0 ) {  
        cout << "not a number" << endl;
    }    else {  
        cout << n << endl;
    }
}

上面的代码在控制台中打印出-65538


10

以下是我在其他地方找到的一个简单可行的方法:

string hexString = "7FF";
int hexNumber;
sscanf(hexString.c_str(), "%x", &hexNumber);
请注意,您可能更喜欢使用无符号长整型/长整型来接收该值。另一个注意点是,c_str()函数只是将std::string转换为const char*。
因此,如果您已经有一个准备好的const char*,只需直接使用该变量名称即可,如下所示[我还展示了使用无符号长整型变量处理更大的十六进制数字的用法。不要与使用const char*而不是字符串的情况混淆]:
const char *hexString = "7FFEA5"; //Just to show the conversion of a bigger hex number
unsigned long hexNumber; //In case your hex number is going to be sufficiently big.
sscanf(hexString, "%x", &hexNumber);

只要您根据自己的需求使用适当的数据类型,这将完美地运作。


7

今天我遇到了同样的问题,以下是我如何解决它以保留 lexical_cast<> 的方法:

typedef unsigned int    uint32;
typedef signed int      int32;

class uint32_from_hex   // For use with boost::lexical_cast
{
    uint32 value;
public:
    operator uint32() const { return value; }
    friend std::istream& operator>>( std::istream& in, uint32_from_hex& outValue )
    {
        in >> std::hex >> outValue.value;
    }
};

class int32_from_hex   // For use with boost::lexical_cast
{
    uint32 value;
public:
    operator int32() const { return static_cast<int32>( value ); }
    friend std::istream& operator>>( std::istream& in, int32_from_hex& outValue )
    {
        in >> std::hex >> outvalue.value;
    }
};

uint32 material0 = lexical_cast<uint32_from_hex>( "0x4ad" );
uint32 material1 = lexical_cast<uint32_from_hex>( "4ad" );
uint32 material2 = lexical_cast<uint32>( "1197" );

int32 materialX = lexical_cast<int32_from_hex>( "0xfffefffe" );
int32 materialY = lexical_cast<int32_from_hex>( "fffefffe" );
// etc...

当我在寻找一种更好的方式时,发现了这个页面 :-)

祝贺, A。


1
代码存在微不足道的编译错误 - outvalue 未定义(应为 outValue)。 - Gabi Davar

5

只需使用stoi/stol/stoll,例如:

std::cout << std::stol("fffefffe", nullptr, 16) << std::endl;

输出结果:4294901758


只需将“stol”编辑为“stoi”。 - Maf

3
这对我很有用:
string string_test = "80123456";
unsigned long x;
signed long val;

std::stringstream ss;
ss << std::hex << string_test;
ss >> x;
// ss >> val;  // if I try this val = 0
val = (signed long)x;  // However, if I cast the unsigned result I get val = 0x80123456 

3

试一下这个。这个解决方案有一些风险。没有检查。字符串必须只包含十六进制值,且字符串长度必须与返回类型大小匹配。但不需要额外的头文件。

char hextob(char ch)
{
    if (ch >= '0' && ch <= '9') return ch - '0';
    if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10;
    if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
    return 0;
}
template<typename T>
T hextot(char* hex)
{
    T value = 0;
    for (size_t i = 0; i < sizeof(T)*2; ++i)
        value |= hextob(hex[i]) << (8*sizeof(T)-4*(i+1));
    return value;
};

使用方法:

int main()
{
    char str[4] = {'f','f','f','f'};
    std::cout << hextot<int16_t>(str)  << "\n";
}

注意:字符串的长度必须是2的倍数。

std::hexstd::stoul()以及sscanf()都是宽容的,所以如果我传入一个错误的"FK",它会将其解释为15(忽略K并将F视为十六进制数)。在hextob函数中,我将返回值改为std::cerr<<"ERROR: invalid ch='"<<ch<<"'"<<"\n";exit(1);,这样我可以确保输入是完美的。但是我现在使用了另一种方式,对于最多0xFF的情况,int hexaTo(const char* pch) {return hextob(pch[0])*16+hextob(pch[1]);} - Aquarius Power

1

如果您想要将无符号数字转换为不同的进制,那么在C/C++中自己实现是相当简单的,只需使用最少的依赖(语言本身没有提供的唯一运算符是pow()函数)。

在数学术语中,一个正序数d在进制b下,有n个数字位,可以使用以下公式转换为十进制:

math summation

例子:将16进制数字00f转换为十进制的过程如下:

数学求和符号 = 0*16^2 + 0*16^1 + 16*16^0 = 15

C/C++ 例子:

#include <math.h>

unsigned int to_base10(char *d_str, int len, int base)
{
    if (len < 1) {
        return 0;
    }
    char d = d_str[0];
    // chars 0-9 = 48-57, chars a-f = 97-102
    int val = (d > 57) ? d - ('a' - 10) : d - '0';
    int result = val * pow(base, (len - 1));
    d_str++; // increment pointer
    return result + to_base10(d_str, len - 1, base);
}

int main(int argc, char const *argv[])
{
    char n[] = "00f"; // base 16 number of len = 3
    printf("%d\n", to_base10(n, 3, 16));
}

“to_base10” 函数接受任何字符串(char *),但没有检查提供的字符串是否为十六进制。 - Pear

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