如何在 c 中将十六进制字符串转换为十进制

How to convert a hex string to decimal in c

提问人:devMe 提问时间:10/26/2023 最后编辑:chqrliedevMe 更新时间:11/10/2023 访问量:195

问:

所以我一直在学习 Kernighan 和 Ritchie 的 C 编程语言。在第二章中,练习 2.3 将十六进制数字字符串转换为等效整数值问题的函数。我看到了很多实现这个目标的方法:我没有复制解决方案,而是尝试实现我自己的解决方案版本。我仍然无法找出最短和最简单的方法来执行此操作并尝试实现可选的“0x”或“Ox”场景。下面是我的代码htoi

如何实现可选的“0x”?

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

char *reverse(char s[]) {
    int len = strlen(s);
    char *r = (char * )malloc(len + 1);;
    int k = 0;
    for (int i = len; i > 0; i--) {
        r[k] = s[i - 1];
        k++;
    }
    r[k] = '\0';
    return r;
}

int check_valid(char *s) {
    int valid = 0;
    int not_valid = 0;

    int len  = strlen(s);
    for (int i = 0; i < len; i++) {
        if ((s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'f') || (s[i] >= 'A' && s[i] <= 'F')) {
            valid = 1;
        } else
            not_valid = 1;
    }

    if (!not_valid) {
        return 1;
    } else
        return 0;
}

int rawint(char c ) {
    if(isalpha(c)) {
        return toupper(c) - 'A' + 10;
    } else 
        return c - '0';
}

int htoi(char *s) {
    char len = strlen(s);
    int power = 1;
    int dec = 0;

    for (int i = 0; i < len; i++) {
        dec += rawint(s[i]) * power;
        power *= 16;
    }

    return dec;
}

int main() {

    char s[] = "7abc";;
    int check = check_valid(s);
    char *r = reverse(s);

    printf("reversed %s\n",r);      
    if (check) {
        int dec = htoi(r);
        printf("%d\n", dec);
    } else {
        printf("not OK");
    }
}
c 十六 进制

评论

0赞 Scott Hunter 10/26/2023
“实现可选的'0x'或'Ox'场景”:究竟是什么?
2赞 Weather Vane 10/26/2023
也许最简单的方法是使用标准的 strtol 函数,而不是编写自己的函数。它将忽略任何前导或被告知基数 16 时。sprintf()0x0X
0赞 DevSolar 10/28/2023
...并且当被告知基数 0(“自己弄清楚”)时也会正确解释它。我强烈建议不要使用自主开发的实现,除非您从事实现标准库的业务。在后一种情况下,请参阅此问题以获取一个有趣的边缘情况 -- “0xz”。
0赞 Weather Vane 10/28/2023
@DevSolar但并非没有例如哪个基地?0x1234
0赞 DevSolar 10/28/2023
@WeatherVane:没错,这将被解释为以 10 为基数。

答:

1赞 chux - Reinstate Monica 10/26/2023 #1

实现可选的“0x”或“Ox”方案。

当然,OP的意思是“实现可选的'0x'或'0X'场景”。(数字,不是字母)。'0''O'

如何实现可选的“0x”?

若要检查有效性,请添加一个测试,如果前导字符与 0x、0X 序列匹配。如果是这样,请跳过 2 。char

int check_valid(char *s){
  // Add
  if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
    s += 2;
  }

错误

检查是否有效,不认为有效。validnot_valid""

// if( !not_valid ){
if(valid && !not_valid ){
    return 1;

提示:与其向下传递字符串 2,不如迭代直到找到空字符

//int len  = strlen(s);
//for(int i = 0; i < len;i++){
for(int i = 0; s[i]; i++){
0赞 greg spears 10/26/2023 #2

下面是您的代码,经过简化和修改,可以根据需要接受可选的“0x”前缀:

由于 htoi() 内部查找表,省去了 rawint()。

通过对 strchr() 返回的指针进行自己的内部检查来省去 check()

更新:sreverse() 已免除对 malloc() 和 strlen() 的调用

htoi() 接受带或不带可选“0x”前缀的字符串

这是可运行的代码,可以看到它是否有效。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

/* sreverse() 
 -- no longer has to allocate memory -- uses single char temp value 
 -- removed call to strlen() with tiny while() loop
*/
char *sreverse(char *s)
{
    int i=0, k=-1;
    char ch;

    while(s[++k]);  //get strlen

    for(i = 0, k-=1; k>i; i++, k--)
    {
        ch = s[k];
        s[k] = s[i];
        s[i] = ch;
    }
    return s;
}

/*
    htoi() 
    Updated:
    - Removed dependency on rawint()
    - Removed call to strlen() with for() loop mod
    - Uses Hex lookup table 'lut' and strchr()
    - Performs its own internal check for valid Hex string on the fly
    - Quits when valid '0x' prefix or end of string is encountered
    - Sets parameter success flag for success/fail
*/
int htoi(char *s, int *success)
{
    int i, dec=0, power = 1;
    char *p, *sr = sreverse(s), lut[]="0123456789ABCDEF";

    *success = 1; /* Assume success until proven otherwise */

    for (i = 0; sr[i]; i++) 
    {
        p = strchr(lut, toupper(sr[i]));
        if(p)
        {
            dec += (int)(p - lut) * power;
            power *= 16;
        }
        else
        {
            /* If we only failed look up because of "0x" prefix, that's a success */
            *success = (!strcmp( &sr[i], "x0"));
            break;
        }
    }
    return dec;
}


#define SAMPLES 6

int main()
{
    /* Samples are with/without "0x" prefix, upper and lowercase, etc */
    char s[SAMPLES][15] = { "7FF", "0xf", "ff", "0xFFFFFFFF", "0x7FG", "Hello" };
    int success, dec, runcount = 0;

    do{
        dec = htoi(s[runcount], &success);
        printf("Result %-7s: %-4d from string: %s\n", success?"SUCCESS":"FAIL", dec, sreverse(s[runcount++]));
    }while(runcount < SAMPLES);

    return 0;
}

输出:

    Result SUCCESS: 2047 from string: 7FF
    Result SUCCESS: 15   from string: 0xf
    Result SUCCESS: 255  from string: ff
    Result SUCCESS: -1   from string: 0xFFFFFFFF
    Result FAIL   : 0    from string: 0x7FG
    Result FAIL   : 0    from string: Hello
2赞 chqrlie 10/28/2023 #3

试图提出自己的解决方案并没有错,但如果你读过本书的第 2 章,你就会看到 K&R 如何实现函数来执行从十进制表示到值的转换。atoiint

书中的代码不接受初始空白字符,也不处理可选的 or 符号,但它确实以非常简洁有效的方式执行转换,一次从字符串中消耗一个数字。+-

您的解决方案 OTOH 为字符串的反向副本分配内存(未释放),并以相反的顺序解析字符串,这会使计算复杂化,并且当您坚持检查 中的正确数字时,前缀(或在本例中为 or 后缀)被拒绝。主要问题是没有正确的语义:它应该直接转换。htoicheck_validx0X0htoi0x7abc31420

您可以通过以下方式修改书中的代码以处理十六进制表示形式和可选前缀:atoi

unsigned htoi(const char s[]) {
    int i = 0;
    unsigned n = 0;

    /* skip an optional 0x or 0X prefix */
    if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X'))
        i = 2;

    for (;;) {
        /* parse the string one digit at a time, stop on the first non digit */
        unsigned digit;
        char c = s[i++];
        if (c >= '0' && c <= '9')
            digit = c - '0';
        else
        if (c >= 'a' && c <= 'f')
            digit = c - 'a' + 10;
        else
        if (c >= 'A' && c <= 'F')
            digit = c - 'A' + 10;
        else
            break;
        n = 16 * n + digit;
    }
    return n;
}

请注意以下备注:

  • type 比此转换更合适,以避免对大值(如unsignedint0xFFFFFFFF
  • s定义为 ,或等效地允许在不发出警告的情况下使用字符串文本和其他常量字符串进行调用。const char s[]const char *shtoi
  • htoi还应该接受初始空格和可选符号,但这留给另一个练习
  • 与本书一样,停在第一个不是正确数字的字符上,它不会检查和拒绝格式错误的字符串。atoihtoi
  • 该代码假定 和 是目标字符集中的连续字符序列。abcdefABCDEF
  • i应该使用 type 进行定义,以适应平台上的极长字符串,其中 .size_tSIZE_MAX > INT_MAX

评论

1赞 chqrlie 10/28/2023
@Fe2O3:谢谢!固定。我想知道谁是DV,为什么?
0赞 chux - Reinstate Monica 10/29/2023
虽然连续 DVer 走了,但击中了每个人,没有明显的评论——嗯,这就是生活。
0赞 Fe2O3 11/5/2023 #4

当你学习编码时,通常很想添加更多的代码来解决问题。随着经验的积累,这种趋势会逐渐消失。我强调了这一点,并指出 OP 的代码有 4 个函数来为正在执行的测试提供服务。将代码分解为“辅助”函数非常好。但是,在这种情况下,可能会发生过多的卸载main()

一个尚未注意到的缺陷是内存泄漏,因为指针返回的指针不是“d”。打字应该在打字之后打字,就像打字在打字之后一样。在一个好的程序中,堆分配和释放必须平衡。reverse()free()free()malloc()')''('

的功能是值得商榷的。很高兴您知道数据验证!应该受到保护吗?或者,它应该用它所给予的任何东西尽力而为吗?一个悬而未决的问题......(建议你应该去走一小段路。下面的代码演示如何使用标准库函数来测试字母是否是字母数字字符的特定子集之一。check_valid()htoi()!not_valid

rawint()作为内联代码可能更好,而不是程序对从 ASCII 转换为其二进制值的每个字符执行函数调用。

最后,执行字符串的另外两次遍历(一次用于测量,一次用于转换字符)。到现在为止,角色已经变得狗耳朵了。htoi()

下面是另一个版本供您考虑(带有测试值套件)。学习是非常有益的。此代码很小且很密集,但有些复杂。每条线都发生了很多事情。这是为了展示另一个应该避免的极端。你希望你的读者理解代码,必须剖析每个符号与其他符号的关系......只是在下雨天玩得开心。

#include <stdio.h>
#include <string.h>

unsigned htoi(const char s[]) {
    char c[2] = {0};
    const char *lut = "0123456789ABCDEFabcdef";
    unsigned d, n = 0, i = 0;

    s += (s[0]=='0' && (s[1]=='x' || s[1]=='X')) ? 2 : 0;
    while( i < 8 && (c[0] = s[i++]) != 0 && lut[d = strcspn(lut, c)] )
        n = 16 * n + (( d<16 ) ? d : d-6);

    return n;
}

int main( void ) {
    char *s[] = {
        "Hello", "7FF", "0xff", "0XFF", "ff",
        "7FFFFFFF", "0xFFFFFFFF",
        "0x12345678", "0x123456789",
        "C0FFEE", "ABBA FOREVER",
        NULL,
    };
    int i = 0;

    do {
        printf( "%13s - %10u - %10u\n", s[i], strtoul(s[i], NULL, 16), htoi( s[i] ) );
    } while( s[++i]);

    return 0;
}

输出:

        Hello -          0 -          0
          7FF -       2047 -       2047
         0xff -        255 -        255
         0XFF -        255 -        255
           ff -        255 -        255
     7FFFFFFF - 2147483647 - 2147483647
   0xFFFFFFFF - 4294967295 - 4294967295
   0x12345678 -  305419896 -  305419896
  0x123456789 - 4294967295 -  305419896 // Which behaviour is correct?
       C0FFEE -   12648430 -   12648430
 ABBA FOREVER -      43962 -      43962

关于返回值的差异,请参阅手册页以获取...这个玩具代码可以改进,是吗?strtoul()