谁能向我解释一下这个十进制到二进制的转换程序



有人可以向我解释计算的工作原理吗? 我不明白的是:

  1. getch(); 函数,该函数的作用是什么?

阿拉伯数字。 有人可以向我解释int decimal_binary(int n)在数学上是如何运作的吗?

#include<stdio.h>
int decimal_binary (int n);
void main()
{
int n;
printf("Enter decimal number: ");
scanf("%d", &n);
printf("n%d", decimal_binary(n));
getch();
}
int decimal_binary(int n)
{
int rem, i = 1, binary = 0;
while(n!=0)
{
rem = n % 2;
n = n/2;
binary = binary + rem*i;
i = i*10;
}
return binary;
}

例如,如果 n = 10 这就是我的计算方式

我不打算解释问题中的代码,因为我从根本上(而且相当强烈地)不同意它的实现。

当我们说"将数字转换为基数 2"时,理解我们并没有真正改变数字很有用。 我们所做的只是改变表示。 计算机程序中的int变量只是一个数字(尽管在内部深处它已经是二进制的)。 当我们以数字字符字符串的形式打印数字时,以及当我们从数字字符字符串中读取数字时,基数也很重要。 因此,任何明智的"转换为基数 2"函数都应该输出一个字符串,而不是一个int

现在,当您要将一个数字转换为基数 2 时,实际上当您要转换为基b 时,对于任何基数"b",基本思想是反复除以b

例如,如果我们想确定一个数字的 10 进制数字,这很容易。 考虑数字 12345。 如果我们将其除以 10,则得到 1234,余数为 5。 余数 5 正是数字 12345 的最后一位数字。 其余数字是 1234。 然后我们可以重复这个过程,将 1234 除以 10 得到 123 余数 4,依此类推。

在我们进一步讨论之前,我希望您仔细研究这个以 10 为基数的示例。 确保您了解,当我们通过将 12345 除以 10 将其拆分为 1234 和 5 时,我们不只是用眼睛看它并摘下最后一个数字。 "除以 10,有余数"的数学运算确实完美地为我们做了分割。

因此,如果我们想使用 10 以外的基数来确定数字的数字,我们所要做的就是反复除以另一个基数。 假设我们试图提出 11 的二进制表示。 如果我们将 11 除以 2,则得到 5,余数为 1。 所以最后一位将是 1。

接下来,我们必须研究五个。 如果我们将 5 除以 2,则得到 2,余数为 1。 所以倒数第二位将是 1。

接下来,我们必须研究两个。 如果我们将 2 除以 2,则得到 1,余数为 0。 所以下一个位将是 0。

接下来,我们必须研究一个。 如果我们将 1 除以 2,则得到零,余数为 1。 所以下一个位将是 1。

现在我们没有什么可处理的了——最后一个除法的结果是0。 我们选择的二进制位依次是 1、1、0 和 1。 但我们先挑掉了最后一点。 因此,重新排列为传统的从左到右顺序,我们有 1011,这是数字 11 的正确二进制表示。

因此,有了我们的理论,让我们看一些实际的 C 代码来做到这一点。 它非常简单,除了一个复杂功能。 由于我们使用的算法总是首先给我们结果的最右边,因此我们将不得不做一些特殊的事情,以便在最终结果中以传统的从左到右顺序结束这些位。

我将把新代码编写为函数,有点像你的decimal_binary。 此函数将接受一个整数,并将该整数的二进制表示形式作为字符串返回。 由于字符串在 C 中表示为字符数组,并且数组的内存分配可能是一个问题,因此我还将让函数接受一个空数组(由调用方传递)来构建返回字符串。 我还将让函数接受第二个整数,给出数组的大小。 这很重要,以便函数可以确保不会溢出数组。

如果到目前为止的解释还不清楚,那么对新函数的调用将是这样的:

#include <stdio.h>
char *integer_binary(int n, char *str, int sz);
int main()
{
int n;
char result[40];
printf("Enter decimal number: ");
scanf("%d", &n);
char *str = integer_binary(n, result, 40);
printf("%sn", str);
}

正如我所说,新函数integer_binary将创建其结果为字符串,因此我们必须声明一个数组result来保存该字符串。 我们将其声明为大小 40,这应该足以容纳任何 32 位整数,还有一些剩余。

新函数返回一个字符串,因此我们使用%s打印其返回值。

这是integer_binary函数的实现。 一开始看起来会有点吓人,但请耐心等待。 它的核心是使用与问题中的原始decimal_binary函数相同的算法,反复除以 2 以挑选生成的二进制数的位。 差异与在字符串而不是int中构造结果有关。 (此外,它还没有处理好所有事情;我们稍后会再进行一两个改进。

char *integer_binary(int n, char *binary, int sz)
{
int rem;
int j = sz - 2;
do {
if(j < 0) return NULL;
rem = n % 2;
n = n / 2;
binary[j] = '0' + rem;
j--;
} while(n != 0);
binary[sz-1] = '';
return &binary[j+1];
}

您可以尝试一下,它可能会开箱即用,但让我们解释一下可能令人困惑的部分。

新变量j跟踪数组中的位置,result我们将放置我们计算的下一个位值。 由于该算法以从右到左的顺序生成位,我们将在数组中向移动j,以便我们从末尾开始填充新位,然后向左移动。 这样,当我们获取最后一个字符串并将其打印出来时,我们将以正确的从左到右顺序获得位。

但是为什么j一开始是sz - 2? 部分原因是 C 中的数组从 0 开始,部分原因是为终止 C 中的数组的空字符''留出空间。 这是一张应该让事情更清晰的图片。 这将是我们完全转换数字 11 后的情况:

0   1   2          31  32  33  34  35  36  37  38  39
+---+---+---+-- ~ --+---+---+---+---+---+---+---+---+---+
result: |   |   |   |  ...  |   |   |   |   | 1 | 0 | 1 | 1 | |
+---+---+---+-- ~ --+---+---+---+---+---+---+---+---+---+
^                               ^   ^           ^
|                               |   |           |
binary                          final return    initial
j  value        j

调用方中的result数组被声明为char result[40];,因此它有 40 个元素,从 0 到 39。sz作为 40 传入。 但是如果我们想j从数组的"右边缘"开始,我们就不能初始化jsz,因为最左边的元素是 39,而不是 40。 而且我们也不能j初始化为sz - 1,因为我们必须为终止''留出空间。 这就是为什么我们将j初始化为sz - 2,或 38。

integer_binary函数的下一个可能令人困惑的方面是行

binary[j] = '0' + rem;

在这里,rem是 0 或 1,这是我们转换的二进制转换的下一位。 但是由于我们正在创建二进制数的字符串表示形式,因此我们希望用'0''1'之一的字符填充binary结果。 但是 C 中的字符由微小的整数表示,您可以对它们进行算术运算。 常量'0'是计算机字符集中字符0的值(ASCII 中通常为 48)。 最重要的是,'0' + 1变成了角色'1'. 因此,如果rem为 0,则变为'0' + rem'0',如果rem为 1,则变为'1'

接下来要讨论的是我使用的循环。 原来使用的decimal_binary函数while(n != 0) {...},但我使用的是do { ... } while(n != 0)。 有什么区别? 正是do/while循环总是运行一次,即使控制表达式为 false。 这就是我们在这里想要的,以便数字0将转换为字符串"0",而不是空字符串""。 (这对integer_binary来说不是问题,因为它在这种情况下返回整数0,但这是它选择int作为返回值的副作用。

接下来我们有这条线

binary[sz-1] = '';

我们已经触及了这一点:它只是填充终止字符串的必要空字符。

最后,还有最后一行,

return &binary[j+1];

这是怎么回事?integer_binary函数应该返回一个字符串,或者在本例中,返回一个指向以 null 结尾的字符数组的第一个字符的指针。 在这里,我们返回一个指针(由&运算符生成)指向结果数组中binary[j+1]元素。 我们必须在j中添加 1,因为我们总是在循环中从中减去 1,因此它始终指示我们存储下一个字符的数组中的下一个单元格。 但是我们退出了循环,因为没有下一个字符要生成,所以我们生成的最后一个字符是j的先前值,即j+1

(因此,这种integer_binary功能在一个方面有点不寻常。 调用方传入一个空数组,函数在空数组中生成其结果字符串,但它返回的指针(指向构造的字符串)通常指向传入数组的开头。 只要调用方按预期使用返回的指针,它就可以正常工作。 但这是不寻常的,如果调用者不小心使用了自己的原始result数组,就好像它会包含结果一样,调用者会感到困惑。

还有一件事:循环顶部的那行if(j < 0) return NULL;是一个双重检查,调用方为我们生成的结果提供了一个足够大的数组。 如果我们生成的数字空间不足,则无法生成正确的结果,因此我们返回一个空指针。 (除非明确检查,否则这可能会导致调用方出现问题,但这是另一天的故事。

到目前为止,integer_binary讨论的都可以工作,尽管我想进行三项改进以解决一些剩余的缺陷:

  1. 如图所示的decimal_binary函数无法正确处理负数。

  2. decimal_binary函数使用j变量的方式有点笨拙。 (笨拙的证据是我不得不花这么多话来解释j = sz-2return &binary[j+1]部分。

  3. 如图所示的decimal_binary函数显然只处理二进制,但我真正想要的(尽管你没有要求它)是一个可以转换为任何基数的函数。

所以这是一个改进的版本。 基于我们已经看到的integer_binary功能,只需几个小步骤即可实现所需的改进。 我将新函数称为integer_base,因为它可以转换为任何基数(好吧,无论如何,任何基数最多为 10)。 在这里:

char *integer_base(int n, int base, char *result, int sz)
{
int rem;
int j = sz - 1;
int negflag = 0;
if(n < 0) {
n = -n;
negflag = 1;
}
result[j] = '';
do {
j--;
if(j < 0) return NULL;
rem = n % base;
n = n / base;
result[j] = '0' + rem;
} while(n != 0);
if(negflag) {
j--;
result[j] = '-';
}
return &result[j];
}

如前所述,这就像integer_binary一样,除了:

  • 我已经改变了j的使用方式。 以前,它始终是我们即将填写的结果数组的下一个元素的索引。 现在,它始终是我们要填写的下一个元素右侧的一个。 这是一个不太明显的选择,但它最终更方便。 现在,我们将j初始化为sz-1,而不是sz-2。 现在,我们在填充结果的下一个字符之前执行递减j--,而不是之后。 现在,我们可以返回&binary[j],而不必记住在该位置减去 1。

  • 我已将终止空字符的插入''移到顶部。 由于我们正在从右到左构建整个字符串,因此将终止符放在第一位是有意义的。

  • 我以一种蛮力但权宜之计的方式处理了负数。 如果我们收到一个负数,我们把它变成一个正数(n = -n),并在其上使用我们的常规算法,但我们设置了一个标志negflag来提醒我们已经这样做了,当我们都完成后,我们将一个'-'字符附加到字符串的开头。

  • 最后,这是最大的问题,新功能适用于任何基础。 它可以在底数 2、底数 3、底数 5、底数 7 或任何最多 10 的底数中创建表示。 真正整洁的是,为了实现这一点,需要很少的修改。 事实上,只有两个:在我之前除以2的两个地方,现在我除以base。 就是这样! 这是我在这个太长的答案一开始就说过的话的实现:"基本思想是反复除以b

  • (实际上,我撒了谎:还有第四个变化,因为我将结果参数从"binary"重命名为"result"。

虽然你可能认为这个integer_base函数看起来不错,但我不得不承认它至少还有三个问题:

  1. 它不适用于大于 10 的基数。

  2. 它偶尔会溢出其结果缓冲区。

  3. 在尝试转换最大的负数时,它有一个模糊的问题。

它仅适用于最多 10 个碱基的原因是这条线

result[j] = '0' + rem;

此行只知道如何在结果中创建普通数字。 对于(比如)以 16 为基数,它还必须能够创建十六进制数字A-F. 实现此目的的一种快速但混淆的方法是将该行替换为

result[j] = "0123456789ABCDEF"[rem];

这个答案已经太长了,所以我不打算讨论这个技巧是如何工作的。

第二个问题是隐藏在我为处理负数而添加的行中:

if(negflag) {
j--;
result[j] = '-';
}

这里没有检查result数组中有足够的空间容纳减号。 如果数组对于没有减号的转换后的数字来说勉强足够大,我们将以j为 0 来点击这部分代码,然后从中减去 1,并将减号填充到result[-1],这当然不存在。

最后,在 2 的补码机上,如果将最负的整数INT_MIN传递给此函数,它将不起作用。 在 16 位 2 的补码机上,问题编号为 -32768。 在 32 位计算机上,它是 -2147483648。 问题是 +32768 不能在 16 位机器上表示为有符号整数,+2147483648 也不能容纳 32 个有符号位。 因此,为了实现也可以处理INT_MIN的完美通用功能,需要进行某种重写。

为了将十进制数转换为二进制数,有一个简单的递归算法可以应用于该数字(递归=重复直到发生某些事情):

  1. 取该数字除以 2
  2. 接受提醒
  3. 而不是重复使用当前数字,原始数字除以 2(考虑到这是一个整数除法,所以 2,5 变成 2),直到该数字不同于 0
  4. 把所有的提醒都从最后一个读到第一个,这就是那个数字的二进制形式

该函数的作用正是这个

  1. 取数字除以 2
  2. 获取提醒并将其添加到变量中binary乘以并i每次乘以 10,以便将第一个提醒作为不太重要的数字,最后一个作为最高有效数字,这与获取所有提醒并从最后一个读取到第一个相同
  3. 另存为nn/2
  4. 然后重复它,直到当前数字n不同于 0

此外,有时在 Windows 中使用getch()以保持命令提示符打开,但不建议

这样做
getchar()

控制台中停止程序。函数背后的数学看起来像这样:

n=7:
7%2=1; //rem=1
7/2=3; //n=3
binary=1;
next loop
n=3:
3%2=1;
3/2=1; //n=1;
binary=11 //1 + 1* 10
final loop
n=1:
1%2=1;
1/2=0; //n=0;
binary=111 //11+1*100

最新更新