一个爱历史的程序猿

我知道这个世界很大。但只有疯狂到相信自己能改变世界的人,才能改变世界。

导航

 

在这里插入图片描述
这一篇文章要探讨的是“如何用算法实现十进制转十六进制”并不涉及什么特别的知识点。属于C语言基础篇

在翻找素材的时候,发现一篇以前写的挺有意思的代码,这篇代码里面涉及的知识点没有什么好讲的,也没有什么特别的逻辑概念在里面,仅仅只是想要分享一下个人对于编程方面的一些思考和坚持而已。


先看代码:

#include <stdio.h>
#include <string.h>
int Judge(int n);
int Detection(void);

int main(void)
{
	char x[16] = {'0','1','2','3','4','5','6','7','8',
			'9','a','b','c','d','e','f'};//十进制与十六进制对应的数组

	printf("请输入一个十进制的数:");
	int n = Detection();//输入数据检查再赋值
	int k = Judge(n);//检测数组需要的长度
	char y[k];//创建数组储存十六进制数
	memset(y,'\0',sizeof(y));//清空数组

	int i,j;
	j = 0;
	while( n >= 16 )//把转换好的十六进制数依次输入数组
	{
		i = (n % 16);//先是求余
		y[j] = x[i];//把得到的余数转为十六进制数(例如“11”转“b”)
		j++;//数组下标移位
		n /= 16;//求商再赋值给它自己(方便下个循环再除)
		if(n < 16)
			y[j] = x[n];
	}

	//此时数组y内的十六进制数是倒过来储存的
	
	printf("你输入的数转换成十六进制为:0x");//先输出“0x”前缀
	while(j >= 0)//把储存了十六进制数的数组倒着输出
	{
		if(j > 0)//判断是不是最后一个,是的话换行
			printf("%c", y[j]);
		else
			printf("%c\n",y[j]);
		j--;
	}

	return 0;
}

int Judge(int n)//这个函数的作用是用来判断需要定义的数组大小的
{
	int k = 1;
	int m = n;
	while(m > 16)//如果小于16,那么1位就行了
	{
		m /= 16;//如果大于16先除与16
		k++;//加一位
	}
	return k;
}

int Detection(void)//这个是第一篇博客里面的那个代码的封装版,用来保证输入的数为合法
{
	int n;
	while(1)
	{
		if(scanf("%d", &n) != 1 || getchar() != '\n')
		{
			printf("你输入的数据有误,请再输一遍:");
			while(getchar() != '\n');
		}else
			break;
	}
	return n;
}

这篇代码所要实现的功能很简单,就是把十进制转换为十六进制输出,当然也是有前提的,就是不能用那些转换符(例如%x)或者用一些现有的函数,需要自己写一个算法来实现转换。至于这个算法也不难,无非就是了解一下在数学上面如何把十进制转为十六进制,然后把那个过程用代码来实现罢了,网上已经有很多说明了。十进制转其他进制最常用的办法就是不断对其要求的进制数求余,然后余数反转。

大概类似于这个样子的过程(灵魂画师附体)
在这里插入图片描述


这个过程在算法上面具体体现为先获取用户的十进制输入,然后不断的对其与16进行求商求余,把得到的余数反转输出,即为所要求的十六进制数。在这个过程中,由于需要把余数反转输出,没法每求一个余数就输出一个。所以就需要一个数组来存储这些已经求得的余数,然后再反向输出。在这整个输出的过程中,所涉及算法的难度并不大。但如果仅仅只是这样的话,我也没有必要专门写一篇文章来分享这个代码了。
在这里插入图片描述


我在写这个代码的时候,遇到一个问题,是关于数组的。既然我在输出之前需要把余数用数组储存起来,那么我这个数组需要定义多大呢?由于用户的输入并不确定,所以最终所得到的十六进制数也不确定,这样也就没有办法事先知道我需要的数组大小。定义大了浪费空间,定义小了又不够放。当初在做这道题的时候,老师给我们的建议是定义个“char x[20]”就可以了。

我当时的第一反应就是,老师这是在给我们降低难度啊,这么随便的吗?万一用户输入的数据转换为十六进制之后超过20位呢?且不说浪费不浪费空间的问题,你这明显就是存在着bug啊!怎么也得要给个1024吧,20哪里够了。不过后来想想,如今的操作系统也就64位,转换成十六进制的话,也就16个数就可以表示完了,连20都给多了,四舍五入刚好取整嘛。而且也没有bug的存在,也就浪费了4个字节的内存而已。要是我当时意识到这点,我可能就会直接“char x[16]”完事。也就没有后面的什么事了。

不过在当时的我看了来,这简直就是一个要逼死强迫症的bug啊!我可以容许浪费空间,也可以容许效率低下,但是绝不能放任bug不管啊。所以,我那天苦思冥想,最终认识到,我需要的是一个可变数组。这个数组要能够实现我放多少东西进去它就能存多少东西,我拿多少东西出来它就能缩小多少。为了实现这个需求,我上网找了一下可变数组的实现方法。但是不外乎两种情况,一种是在说C语言中没有可变数组,另一种就是在用代码实现可变数组。只可惜我当时的技术水平有限,实在是看不懂那些天书,而且那些大佬的代码一长,就不写注释的了,通篇博客就一整篇代码,一点介绍性的文字都没有,别说我当时没那个技术看不懂啊,我就是现在看的懂也没耐心看你这么一整篇没有注释的代码啊!简直神烦(这也是我想要写博客的一个原因)。


所以我当时在这模凌两可的网络环境下面,我认为是有可变数组的,只是藏着某个函数库里面而已,只是属于深度C而已,只是我还没有学到而已,于是那一整个下午我就都在探索可变数组。

直到后来我才知道,在C语言里面,本身就不存在什么可变数组,在C++中倒是存在可变数组的概念,网络上面的所谓可变数组,不过是利用了指针来存储好原数组的内存位置然后当再次需要改变数组大小的时候,在原位置上面创建或者把原数据拷贝到新的内存地址上面再返回新地址的值给原指针而已。说白了,就是假的,假的。根本就不存在什么可变数组。C语言本身就不支持可变数组这个功能。


直到的最后,当然是没有成功啦,本来就是不存在的东西,不过现在想想用储存原数组地址这个方法来实现可变数组用在我这篇代码中或许也很合适,虽然我用的方法是在数据输入之后先判断一下需要的数组大小,然后再创建数组的方法。不过那也是我当时的权宜之计而已,
在这里插入图片描述


有意思的是当初我在不知道C语言中有没有可变数组的时候,曾想过用“指针对于非法内存的访问”来实现可变数组。具体表现可以看下面的代码

int main(void)
{
	int i = 0;
	char a[i];//根本没有分配空间,用“i”代替0是为了防止编译器报警告
	char *c = a;//用一个指针来存储数组“a”的地址值
	int j = 3;
	a[j] = '7';//这里用“j”来代替3是为了防止编译器报警告

	printf("%c ", c[3]);//这里用“c”来代替“a”输出也是为了防止编译器报警告
	printf("\n");

	return 0;
}

在这段代码里面数组“a”我根本没有分配空间给它,但是我却给它第3个位置“a[3]”赋值,居然编译通过了,运行也没有问题。这就是利用了指针对于非法内存的访问来实现的,而且这样也可以满足我的需求,我这个数组即没有大小限制的而且也丝毫不浪费内存,这不就是我想要的可变数组吗?

但别以为这是什么好东西,恰恰相反在编程开发的时候,我们应该要去杜绝这种情况的发生。这段代码在不同的电脑下面或者在不同的程序区域下面运行都有可能不一样,你可以尝试把它封装成一个独立函数,然后试着在另外一篇小规模的代码上面调用试试看,可能在程序的开始调用它,程序就会报段错误,也有可能会在程序的尾部调用它,程序就报段错误了。也有可能整个程序执行完到退出,也没有报段错误。什么时候报段错误,完全取决于CPU在执行你这个程序的过程中什么时候访问到你正在使用的那块非法内存。

你的代码一运行就报错不可怕,最可怕的就是这种随机性报错的,你无法准确定位到你的错误位置。想象一下,你代码原本运行的好好的,你突然想给它来个优化,就好像把原本的int形数据改为char形的节省一下空间,或者改一下别的无关痛痒的细节,改完之后吧,你一编译运行,报错了,你就找啊找啊找啊,就是找不到在哪里有错误。
然后你就很不甘心的把代码又改回去,但是你忽略了一个细节,你原本是先定义一个char类型的数据“a”然后再定义一个int类型的数据“b”的,但是你为了好看,你在优化的时候把它们改成了先定义“b”再定义“a”了。但是你在改回去的过程中觉得不可能是这里的问题啊,就没管了,结果编译运行后,还是报错。
这时候你对着整篇代码从头看到尾,又从尾看到头,你看了好几遍,但就是不知道到底是哪里出了问题,明明跟原来的代码一摸一样啊,怎么就还是报错呢?这时候打死你也不会相信,就是因为你改变了那两个变量的定义顺序了,使得整个程序在内存中的储存结构发生了改变,这就造成了程序在运行的过程中访问到了原本不会访问的内存,而那块内存正是你用的非法内存。

所以当时我在把这个思路运用到代码里面的时候,就出现了很多奇奇怪怪的问题,程序有时候行,有时候不行。有的时候甚至会出现同一个代码在我的电脑上面运行还可以一放到别人的电脑上面就有不行了这种情况,更夸张的是有时候上午运行可以,下午运行又报错了。搞了半天,放弃了。直到后来知道了野指针的概念之后,才渐渐知道为什么会出现那种情况。对于野指针的分析,以后有机会我可能还会单独写一篇文章来说明吧,也可能不会。虽然野指针还是挺重要的一个概念的,不过也挺简单,好像也没什么好写的。


好了,就写到这里吧,确实没有什么知识点好说的,只是单纯的想要分享一下以前的一些有趣的事情而已。而且如果你刚好需要做一道编程题叫做”请用算法编程实现十进制数转XX进制数“的时候,这篇代码刚好可以套用嘛!不过不建议抄作业啦!参考参考就可以了,毕竟真没有什么难度的这题,注释代码里面都写明白了,应该也不会存在看不懂的现象,过些天我会尝试性的把这个例子改为用“储存原数组地址来实现可变数组”的方法来实现,然后再更新上来。

最后附上这个例子的精简版代码:

#include <stdio.h>
#include <string.h>
int Judge(int n);
int Detection(void);

int main(void)
{
	char x[16] = {'0','1','2','3','4','5','6','7','8',
			'9','a','b','c','d','e','f'};//十进制与十六进制对应的数组

	printf("请输入一个十进制的数:");
	int n = Detection();//输入数据检查再赋值
	int k = Judge(n);//检测数组需要的长度
	char y[k];//创建数组储存十六进制数
	memset(y,'\0',sizeof(y));//清空数组

	int i,j;
	for(j=0; n > 0; j++)//把转换好的十六进制数依次输入数组
	{
		i = (n % 16);
		y[j] = x[i];
		n /= 16;
	}

	printf("你输入的数转换成十六进制为:0x");
	while(j >= 0)//把储存了十六进制数的数组倒着输出
		printf("%c", y[--j]);
	printf("\n");
	return 0;
}

int Judge(int n)//这个函数的作用是用来判断需要定义的数组大小的
{
	int k;
	for(k=0; n>0; ++k)
		n /= 16;
	return k;
}

int Detection(void)//这个是第一篇博客里面的那个代码的封装版,用来保证输入的数为合法
{
	int n;
	while(1)
	{
		if(scanf("%d", &n) != 1 || getchar() != '\n')
		{
			printf("你输入的数据有误,请再输一遍:");
			while(getchar() != '\n');
		}else
			break;
	}
	return n;
}

原博客始发于CSDN,在如今博客界的转载抄袭泛滥的环境下,原创不易,点个赞再走呗。以下是博客首页的链接。


零BUG是原则性问题。