【问题标题】:Hex to Decimal conversion [K&R exercise]十六进制到十进制转换 [K&R 练习]
【发布时间】:2009-04-25 01:23:38
【问题描述】:

我正在学习 C,但我无法弄清楚 K&R 练习之一,清单:

练习2-3,写函数 htoi(s),将字符串转换为 十六进制数字(包括 可选的0x 或0X) 转换成它的等价物 整数值。允许的位数 是0 到9、a 到f 和A 通过F。

我想我这里需要做一些递归,我只是猜想我对编号类型以及它们的各种转换方法等不是很了解。

谁能给我一些关于如何最好地理解它的指示,我不是在找人来牵我的手,而是指导我找到一种正确理解的方法,这样我就可以尽可能以最优雅的形式写出来,而不是printf("%x", skill);

【问题讨论】:

  • 标题说“十进制到十六进制”。练习 2-3 内容为:“十六进制数字到其等效整数值”

标签: c kernighan-and-ritchie


【解决方案1】:

不需要递归。您只需要在字符串上向后循环(即从单位列开始),将单个数字转换乘以它的基数位置乘数相加。这是伪代码,不处理可选的 0x 前缀(并且不检查溢出的可能性):

long total = 0;
long multiplier = 1;
for (int i = string.length - 1; i >= 0 i--)
{
   digit = ConvertSingleHexDigittoInt(string[i]);
   total += digit * multiplier;
   multiplier *= 16;
}

我把 ConvertSingleHexDigittoInt() 的简单实现留给你了 :)

【讨论】:

  • 投反对票的人请留言说明原因。谢谢。
  • -1,因为对于专门的 C 初学者问题,这不是一个有效的 C 答案。
  • 我不想让海报变得简单。我试图通过帮助他们来鼓励他们自己解决问题。我认为这不值得一票否决。
  • 用张贴者自己的话来说:有人能给我一些关于如何最好地理解它的指示吗,我不是在找人来握我的手,而是引导我找到一种正确理解的方式,所以我可以尽可能用最优雅的形式写出来”
  • 您明确表示您的示例是伪代码,而不是 C,因此 +1 以抵消反对意见。
【解决方案2】:

Mitch 的基本想法是对的,但让我们更详细一点。

十六进制数字只是以 16 为基数,这意味着数字(从右到左)的值如下

digit × 160(即1)
数字 × 161(即 16)
数字 × 162 (256)

等等。因此,例如,0xE 是 14。

您需要的是从字符串的 右 端开始的循环。假设字符串是 s,length(s) 是字符串的长度。在伪代码中,你想要

value = 0
r = 1   // ask yourself "what values does r take as this proceeds?"
for i from length(s)-1 to 0   // Ask yourself "why length(s)-1?"
   value = value + (digitval(s[i])*r)
   // get ready for the next digit
   r = r * 16

digitval(char c) 需要是一个函数,可以将“0123456789ABCDEF”中的 checact 转换为 0 到 15(含)之间的值。我将把它留作练习,并带有一个提示:“arrays”。

小心一个额外的问题;因为您可能有一个前导的“0”或“0x”,所以您需要确保处理这些情况。

【讨论】:

  • 这看起来和我原来的答案几乎一样! ;)
  • 是的,然后你就将游戏送人了。你不好玩。
【解决方案3】:

对于那些熟悉数学的人来说,从左到右处理字符串更简单,并且可以说更具可读性。该策略是意识到,例如,1234 = (((1 x 10) + 2) x 10 + 3) x 10 + 4

换句话说,当您从左到右处理每个数字时,将前一个总数乘以基数,有效地将其“向左移动”一个位置,然后添加新数字。

long decFromHexStr(const char *hexStr)
{
    int i;
    long decResult = 0;  // Decimal result

    for (i=0;  i < strlen(hexStr);  ++i)
    {
        decResult = 16 * decResult + decFromHexChar(hexStr[i]);
    }
    return decResult;
}

有经验的程序员可能会使用指针来遍历字符串,而不是将其视为数组:

long decFromHexStr(const char *pHex)
{
    long decResult = 0;

    while (*pHex != '\0')
    {
        decResult = 16 * decResult + decFromHexChar(*pHex++);
    }
    return decResult;
}

既然你正在学习,那么值得研究一下编码风格并决定你是否觉得它有帮助,这样你就会尽早养成良好的习惯。

玩得开心!

【讨论】:

  • 我只是觉得 r-l 版本很有吸引力,因为基数步骤与定义相匹配,所以我可以做 r *= base。
  • 你也可以把 l-r 方法分成两个步骤: result *= base; result += nextDigit,这有点模仿您在将数字输入计算器时看到的行为。我想这是一个品味问题:一个是 6 个,另一个是 C>>1 个。 :-)
【解决方案4】:

十六进制数的实际含义是什么?让我们以 15FA 为例。这意味着

1 * 16^3 + 5 * 16^2 + 15 * 16^1 + 10 * 16^0

请注意,A 代表 10,B 代表 11,依此类推,直到 F 代表 15。 16^0 也等于 1。

所以我们需要做的就是计算上面表达式的值!最简单的方法可能是按以下顺序进行:

10 * 1
15 * 16
5  * 256   //256  = 16 * 16
1  * 4096  //4096 = 16 * 16 * 16

如果有更多的数字,这可以继续下去。您真正需要的只是一个循环和几个变量。

还有另一种方法可以通过分解上述表达式来解释,如下所示:

((1 * 16 + 5) * 16 + 15) * 16 + 10

如果您愿意,请尝试每种方法。

更多高级信息:

基本上,计算机的所有数字和计算都使用以 2 为底(也称为二进制)。就连字符串“1A6DC0”也是用1和0编码的,最终在屏幕上显示为字母和数字。

有时您可以利用计算机使用二进制这一事实,但通常您无需考虑这一点。

例如,当你这样做时

x = (11 + y) * 6;

您不必担心 11 和 6 在某个阶段会表示为一系列高电压和低电压。它就像您期望的那样工作。在十进制(我们使用的数字系统)和二进制之间转换是一个简单的过程,计算机可以轻松完成,因此它们会自动为我们执行此操作,以使我们的工作更轻松。

但是,在十六进制和二进制之间进行转换时,有一个快捷方式。由于四个二进制数字与一个十六进制数字相同,您可以简单地将每个十六进制数字单独转换为二进制,然后将它们串在一起。

例如,15FA 会像这样展开:

1 -> 0001
5 -> 0101
F -> 1111
A -> 1010
15FA -> 0001 0101 1111 1010

请注意,这通常不能直接完成,并且通常涉及逻辑或和位移位(| 和&lt;&lt;)。有趣的东西。

【讨论】:

  • 旁注:因式分解(从左到右)方法需要的乘法步骤少得多,因此效率更高。
【解决方案5】:

我可能没有做出很大的贡献,上面有很好的答案。不过我会试一试的。

正如其他人在我之前所做的那样,我将一些功能留给您实现。

int htoi(const char* x)
{

        unsigned int current_position;/*current position is to be defined*/
        int prefixed=0;                                                         
        int dec=0;
        char* y = x;

        if (x && x+1 && (*(x+1)=='x' || *(x+1)=='X')){  /*Is 0x or 0X prefix present?*/
                prefixed= PREFIXED;             
        }

        if (prefixed) y+=2; /*Jumps over 0x or 0X*/     


        while (*y){
                /*getPos(const char*) and singleHexToDec(const char*,unsigned int) functions to be implemented*/
                current_position=getPos(y);
                dec+=singleHexToDec(y,current_position); 
        }
        return dec;
}

【讨论】:

    【解决方案6】:

    试着用我粗鲁的英语解释:(

    我的代码(假设所有输入都是正确的。避免防御性编程)

    #include <stdio.h>
    
    
    enum { SZ = 11 };
    
    unsigned int htoi(const char *s);
    
    
    int main()
    {
    
      char buff[SZ];  //Max 11 char: 0x XX XX XX XX '\0' (2 + 8 + 1)
    
      while(fscanf(stdin, "%s", buff) != EOF)
        printf("%X\n", htoi(buff) ); 
    
      return 0;
    }
    
    
    unsigned int htoi(const char *s)
    {
      unsigned int i, r = 0;
    
      for(i = (s[1] == 'x') ? 2 : 0; s[i] != '\0'; i++)
        r = ( r << 4 ) +  ( (s[i] > '9') ? 0x9 : 0x0 ) + ( s[i] & 0xF );
    
      return r;
    }
    

    好的,首先,赋值r = 0。 然后,当我们开始 for-bucle 时,我们给索引变量 i 一个初始值。我们必须检查字符串是否为 0x 格式。我们只需要检查位置 1 就可以知道我们是在处理 0x 格式的输入字符串还是没有它。

    现在,我们有一个指向第一个正确字符的索引!对于每个迭代,我们向左移动 4 位。我们获得 4 个零。添加新十六进制数字的完美间隙!示例:

    Input: 0xBE1234
    
    Is s[1] == 'x' ? true then i = 2;
    r = 0;
    
    iter 1: r = 0x0; r = 0x0; r = 0xB;
    iter 2: r = 0xB; r = 0xB0; r = 0xBE;
    iter 3: r = 0xBE; r = 0xBE0; r = 0xBE1;
    iter 4: r = 0xBE1; r = 0xBE10; r = 0xBE12;
    iter 5: r = 0xBE12; r = 0xBE120; r = 0xBE123;
    iter 6: r = 0xBE123; r = 0xBE1230; r = 0xBE1234
    

    可能这有点复杂:

     r = ( r << 4 ) + ( (s[i] > '9') ? 0x9 : 0x0 ) + ( s[i] & 0xF );
    

    首先,我们置换 4 位,与每 16 位乘法相同,但效率更高。然后,我们查看是否有一个大于 '9' 的 ASCII 字符。如果这是真的,我们正在与 A、B、C、D、E、F 或 a、b、c、d、e、f 合作。请记住,我们假设我们有正确的输入。 好的,现在看一下ASCII表:

    A = 0100 0001  -  a = 0110 0001
    ...
    F = 0100 0110  -  f = 0110 0110
    

    但我们想要这样的东西:

    A = 0000 1010  -  a = 0000 1010
    ...
    F = 0000 1111  -  f = 0000 1111
    

    我们是怎么做到的?置换后,我们用掩码 s[i] & 0xF 清除 4 个最高有效位:

    s[2] == 'B' == 0100 0010
    s[2] & 0xF == 0000 0010
    

    并添加 9 以适应整数值(仅在 { 'A'...'F', 'a' ... 'f' } 中的 s[i] 的情况下)

    s[2] & 0xF + 0x9 = 0000 0010 + 0000 1001 = 0000 1011 (0xB)
    

    最后,我们将位移的 r 值相加并赋值给 r。第二次迭代的执行顺序(s[3]):

    r == 0xB, s[3] == 'E' == 0100 0101 (start iter 2)
    (r << 4) == 0xB0, s[3] == 'E' == 0100 0101 (displacement r << 4 )
    (r << 4) == 0xB0, (s[3] & 0xF + 0x9) == 0000 1110 == 0xE (clear most significant bits of s[3] and add 0x9)
    r = (r << 4) + ( s[3] & 0xF + 0x9 ) == 0xBE == 1011 1110 (add all and assign to r)
    

    如果我们有一个像 s[4] 这样的数字字符会发生什么?

    s[4] == '1' == 0011 0001
    s[4] & 0xF == 0000 0001
    

    位移r四个位置,加0(无),加上逻辑运算结果s[i] & 0xF,最后赋值给r。

    r == 0xBE, s[4] == '1' == 0011 0001 (start iter 3)
    (r << 4) == 0xBE0, s[4] == '1' == 0011 0001 (displacement r << 4 )
    (r << 4) == 0xBE0, (s[4] & 0xF + 0x0) == 0000 0001 (clear most significant bits of s[4] and add 0)
    r = (r << 4) + s[4] & 0xF == 0xBE1 == 1011 1110 0001 (add all and assign)
    

    请记住,我们移位 4,因此我们不会对数字位进行网格划分,因为我们添加了具有四个零间隙的较低有效位。

    PD:我保证提高我的英语以便更好地解释,对不起。

    【讨论】:

      【解决方案7】:

      传统的方法是从左到右转换。累加器在开始时设置为零,然后乘以 16,然后将每个新数字的等效值添加到循环中。

      对于需要带有可选前导 0x 的十六进制数字的 htoi() 函数,首先跳过这些字符(如果存在)。直接检查s[0] 和s[1] 的值可能是那里最清晰的方法。

      如果您知道数字是 ASCII 格式,那么您可以使用 s[i] - '0' 和 s[i] - 'A' + 10 等表达式将第 i 个数字转换为其整数值。

      你可能想把整件东西折叠成一个盒子。

      编辑: 将 *s 更改为 s[i] 以与从本练习的角度来看指针来自未来的观察保持一致。

      请注意,还有其他几种方法可以将单个数字转换为值。例如,您可以在所有数字的向量中查找它们(例如strchr("0123456789ABCDEF",s[i])),构建一个由字符代码索引的单个查找表,其中每个位置的每个数字的值(digitvalue[s[i]] 在int digitvalue[256] 之后适当初始化),使用switch (s[i]) 语句和case 标签作为另一个答案中建议的每个可能的数字,或者使用我上面建议的范围检查和算术。需要考虑的是选择哪个,以及为什么。请注意,这可能不是一个显而易见的选择,如果 ASCII 不是您选择的字符集,则最佳答案可能会有所不同。

      【讨论】:

        【解决方案8】:

        昨天我写了一个这样的函数。你可以在下面看到我的代码。

        /* Converting a hex string to integer, assuming the heading 
           0x or 0X has already been removed and pch is not NULL */
        int hex_str_to_int(const char* pch) {
        
            int value = 0;
            int digit = 0;
        
            for (; *pch; ++pch) {
        
                if (*pch >= '0' && *pch <= '9') {
                    digit = (*pch - '0');
                } else if (*pch >= 'A' && *pch <= 'F') {
                    digit = (*pch - 'A' + 10);
                } else if (*pch >= 'a' && *pch <= 'f') {
                    digit = (*pch - 'a' + 10);
                } else {
                    break;
                }
        
                // Check for integer overflow
                if ((value *= 16) < 0 || (value += digit) < 0) {
                    return INT_MAX;
                }
            }
        
            return value;
        }
        

        这里是测试代码:

        int main(void) {
        
            printf("%d %d\n", hex_str_to_int("0"), 0x0);
            printf("%d %d\n", hex_str_to_int("A"), 0xA);
            printf("%d %d\n", hex_str_to_int("10"), 0x10);
            printf("%d %d\n", hex_str_to_int("A1"), 0xA1);
            printf("%d %d\n", hex_str_to_int("AB"), 0xAB);
            printf("%d %d\n", hex_str_to_int("100"), 0x100);
            printf("%d %d\n", hex_str_to_int("1A2"), 0x1A2);
            printf("%d %d\n", hex_str_to_int("10A"), 0x10A);
            printf("%d %d\n", hex_str_to_int("7FFFFFF"), 0x7FFFFFF);
            printf("%d %d\n", hex_str_to_int("7FFFFFF1"), 0x7FFFFFF1);
            printf("%d %d\n", hex_str_to_int("7FFFFFF2"), 0x7FFFFFF2);
            printf("%d %d\n", hex_str_to_int("7FFFFFFE"), 0x7FFFFFFE);
            printf("%d %d\n", hex_str_to_int("7FFFFFFF"), 0x7FFFFFFF);
            printf("%d %d\n", hex_str_to_int("80000000"), 0x7FFFFFFF + 1);
            printf("%d %d\n", hex_str_to_int("80000001"), 0x7FFFFFFF + 2);
        
            printf("%d %d\n", hex_str_to_int("10AX"), 0x10A);   
            printf("%d %d\n", hex_str_to_int("203!"), 0x203);
        
            return 0;
        }
        

        它输出以下值:

        0 0
        10 10
        16 16
        161 161
        171 171
        256 256
        418 418
        266 266
        134217727 134217727
        2147483633 2147483633
        2147483634 2147483634
        2147483646 2147483646
        2147483647 2147483647
        2147483647 -2147483648
        2147483647 -2147483647
        266 266
        515 515
        

        【讨论】:

        • 这是错误的。这将“a”视为 0 而不是 10,同样将“f”视为 5 而不是 15。
        • 这是错误的。这会将“100”转换为值 1,而不是 256。也就是说,您的循环是从最后一个到第一个的十六进制数字,每次添加一个数字时将该值乘以 16。
        • 整数溢出检查的价值有限,无法启动。字符串"1" 是一个有效的、未溢出的转换,但由于1 &lt; '1' 是溢出的第二个条件中的测试而溢出。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-05-05
        • 1970-01-01
        • 2018-07-26
        • 1970-01-01
        • 2013-06-13
        • 2015-08-10
        相关资源
        最近更新 更多