十六進制轉八進制的快捷方法——巧用格式化輸入輸出


    最近刷題的時候遇到一個基礎題,就是將16進制數轉為8進制數。咋一看極其簡單,用二進制做中介即可,簡單規划了一下就開始動手了。

問題描述
  給定n個十六進制正整數,輸出它們對應的八進制數。

輸入格式
  輸入的第一行為一個正整數n (1<=n<=10)。
  接下來n行,每行一個由0~9、大寫字母A~F組成的字符串,表示要轉換的十六進制正整數,每個十六進制數長度不超過100000。

輸出格式
  輸出n行,每行為輸入對應的八進制正整數。

題目鏈接:基礎練習 十六進制轉八進制

 

一、二進制作為中介的解法

因為一個十六進制數為4個二進制數組成,而一個八進制數則由三個二進制數組成,所以二進制就成了鏈接八進制與十六進制的橋梁。又因為輸入數據的規模較為龐大,所以我們得申請大量的內存空間。

我們需要申請的內存空間主要分為三部分:存儲輸入的n個數組,存放中間值的二進制數組,以及存放轉換好的八進制的數組。由於題目要求輸出必須在全部輸入后,所以我們有這樣一條流程:

如果只申請一塊內存空間用於存放二進制數,那最少也得申請1.2MB左右的空間,再加上進制轉化過程中的各種繁瑣操作(如必須由后往前讀取比特),還沒開寫,就能想到產出必定是又長又亂,且還滿身補丁的奇葩代碼(博主照這個流程完成的也的確亂的一蹋糊塗,沒通過后就直接重構了,連修改的欲望都沒有了。類似的版本百度一下即可得到)。

 

二、格式化輸入輸出里的轉換說明符

我們知道,在格式化輸入輸出函數中,請求打印變量的指令取決於變量的類型,即轉換說明符指定了如何把數據轉換成可顯示的形式。除了我們常用的%d %f %c之外,還有一些特定的進制數計數法,如:

%i:有符號十進制數; %u:無符號十進制整數; %o:無符號八進制整數; %x:使用0f的無符號十六進制整數; %X: 使用0F的無符號十六進制整數。

所以,在輸入的時候以十六進制輸入,輸出時以八進制輸出,就完成了這個程序的基本框架。

  1. /* 
  2.     printf的格式化進行16進制與8進制的轉換  
  3. */  
  4. #include <stdio.h>  
  5.     
  6. int main (void)  
  7. {  
  8.     int x = 0;  
  9.     
  10.     scanf("%x",&x);  
  11.     printf("%x: %%d = %d , %%o = %o\n",x,x,x);  
  12.         
  13.     return 0;  
  14. }  

    輸入十六進制 'ABC'

    十進制為A*16^2+B*16^1+c*16^0 = 2748,二進制為1010 1011 1100,轉換為八進制為5274,正確。

三、sscanf()

    有了借助於轉換說明符的方法也不行,因為在<=100000的輸出條件下,哪怕是long long long ….也得溢出。所以不能直接就把輸出scanf()到變量里。我們在這兒借助一個數組(字符串),把所有的輸入都存起來,再把輸入流stdin導入到scanf()里。這里我們借助了sscanf()函數。

    Int sscanf( string str, string fmt, mixed var1, mixed var2 ... );

從一個字符串中讀進與指定格式相符的數據

 

四、對字符串的操作

    對於一組長度恐怖的數據進行操作,分解是必須要做的。所以,一次讀取多少個數據,又該怎么讀,就成了下一個要思考的問題。

    在傳統的解法,也即將二進制作為中介的解法中,對十六進制的操作是從最后一位開始的,因此對於十六進制'ABCD',把它分成兩部分放入scanf()中,與一次讀入的結果是不同的。所以對於數組,從低位到高位是可行的方法。

又因為三位十六進制,即12個bit,恰好可以轉化為4個八進制,所以一次讀取的數量應該為3的倍數。具體的數目也得依據讀入該數據的內存大小來定,也即示例代碼中'x'的類型。太大會溢出,太小又不高效。

由此可以看出,當一次讀入9個F時,對應的十進制數超過了687億,遠遠超過了long long 可以表示的范圍,所以,一次讀取六個字符就成了最好的方案。

 

五、程序各流程操作的順序

    如果是從后往前讀取十六進制字符的話,那么讀取到的字符就是反過來的。而輸出是要求正序,所以這樣的話就要求把處理后的結果保存起來,再輸出。但是再次申請內存空間是沒必要的,所以我們得做一些處理,讓程序正序處理字符串。

    整個待處理的十六進制字符串可分為兩部分,一部分是一次六個字符被統一讀入的部分,另一部分是沒有湊夠六個的部分。如果字符長度對六取模的值為零,那么正序后者倒敘的結果都一樣。所以先對開頭的一部分單獨處理,再用一個循環來處理后面的字符串,就成了最直接的方法。

  1. int x = 0;  
  2. j = arr_len[i] % 6;  
  3. if(j != 0)  
  4. {  
  5.     switch(j)  
  6.     {  
  7.         case 1:  
  8.             sscanf(&arr_16[i][0],"%1x",&x);  
  9.             printf("%o",x);  
  10.             break;  
  11.         case 2:  
  12.             sscanf(&arr_16[i][0],"%2x",&x);  
  13.             printf("%o",x);  
  14.             break;  
  15.         case 3:  
  16.             sscanf(&arr_16[i][0],"%3x",&x);  
  17.             printf("%o",x);  
  18.             break;  
  19.         case 4:  
  20.             sscanf(&arr_16[i][0],"%4x",&x);  
  21.             printf("%o",x);  
  22.             break;  
  23.         case 5:  
  24.             sscanf(&arr_16[i][0],"%5x",&x);  
  25.             printf("%o",x);  
  26.             break;  
  27.     }  
  28. }  
  29.     
  30. for(j ;j<arr_len[i];j += 6)  
  31. //接下來循環處理的部分  

    變量j是規則部分開始的下標,所以一個循環可以搞定剩下的部分。

  32. for(j ;j<arr_len[i];j += 6)  
  33. {     
  34.     sscanf(&arr_16[i][j],"%6x",&x);  
  35.     printf("%08o",x);  
  36. }  
  37.     
  38. printf("\n");  

 

六、特殊十六進制數的處理

    上面的模塊以及把整個程序交代的差不多了,不過還是有一處細節要交代一下。我們來看一組測試用例:

    理論上,六位十六進制數可以轉化成八個八進制數。但在實際操作中,有時讀取到的十六進制數太小,以至於不到八位。如果讀取的數是單獨的十六進制數那當然沒問題,但是如果讀取的是一個長十六進制的一部分,那就出錯了。所以在輸出時得把前導零帶上。但是十六進制最開始的幾個字符的前導零得去掉,所以要進行一次判斷。

  1. for(j ;j<arr_len[i];j += 6)  
  2. {     
  3.     sscanf(&arr_16[i][j],"%6x",&x);  
  4.     if(j != 0)  
  5.         printf("%08o",x);  
  6.     else  
  7.         printf("%8o",x);  
  8. }  

 

七、效率比較與源碼

    我在csdn上找了一份該題的另一份源碼,這是借助二進制的一個版本。提交后,各情況如下:

    新提交的為二進制的版本。可以看到二進制的版本用空間換取了時間,時間消耗極少。而sscanf()版本由於調用了標准庫,所以消耗時間較多。

 

    本題源碼地址:十六進制轉八進制源碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM