如何用演算法把一個十進位制數轉為十六進位制數-C語言基礎
這一篇文章要探討的是“如何用演算法實現十進位制轉十六進位制”並不涉及什麼特別的知識點。屬於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;
}