6

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

练习 2-3,编写函数 htoi(s),将一串十六进制数字(包括可选的0xor 0X)转换为其等效的整数值。允许的数字是0through 9athroughfA through F

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

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

4

8 回答 8

12

递归不是必需的。您只需在字符串上向后循环(即从单位列开始),将单个数字转换乘以它的基数位置乘数求和。这是伪代码,不处理可选的 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() 的简单实现留给了你 :)

于 2009-04-25T01:27:28.270 回答
5

米奇的基本想法是对的,但让我们更详细地了解一下。

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

位数×16 0(即1)
位数×16 1(即16)
位数×16 2(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(含)之间的值。我将把它作为练习留下,并给出一个提示:“数组”。

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

于 2009-04-25T01:38:14.653 回答
4

对于那些熟悉数学的人来说,从左到右处理字符串更简单,并且可以说更具可读性。该战略正在意识到,例如,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;
}

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

玩得开心!

于 2009-04-25T02:14:11.883 回答
2

十六进制数实际上是什么意思?让我们以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

请注意,这通常不能直接完成,并且通常涉及逻辑或和位移(|<<)。好玩的东西。

于 2009-04-25T02:32:59.140 回答
1

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

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

#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:我保证提高我的英语以便更好地解释,对不起。

于 2009-04-25T06:55:06.343 回答
1

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

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

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

为了理智,您可能想将整个事情折叠到一个案例中。

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

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

于 2009-04-25T01:55:24.690 回答
1

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

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

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;
}
于 2009-04-25T03:53:41.567 回答
-2

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

/* 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
于 2009-04-25T01:53:28.517 回答