QBasic代碼的優化(完全版)

 說明:本教程是在VirtuaSoft網頁上獲的(http://www.chainmailsales.com/virtuasoft)。由[email protected]粗略翻譯爲中文,水平有限,錯誤在所難免,如果大家閱讀時候發現錯誤請來信指出!

Optimizes QBasic Programming Guide
Danny Gump 撰寫

本文是爲使用QB的程序設計者編寫的指南,目的在於幫助他們優化程序代碼獲得更高的效率。如果你有任何問題,請通過[email protected]或者[email protected]聯繫Danny。另外請注意:這不是爲初學者編寫的教程。因爲需要深厚的QB程序編寫的背景。

第一部分 數學計算

a. 因式分解
b. 整數的使用
c. \ vs./
d. 預先設定的數據表

  a. 因式分解

  如果可能的話,多步的數學計算應該被完全分解合併同類項以至於計算機只需花費最少的步驟來計算出最後的結果。我曾經用QB編寫了一個紋理映射庫,在把一個象素輸出到屏幕之前需要43步來計算結果。這導致了該程序運行得非常的緩慢,但是對全部得整式分解合併同類項之後,我把步驟減少到24步,因此該程序速度大概提高了兩倍,儘管對實時實現的多邊形來說它仍然很慢!例子:

  之前:
  以下內容爲程序代碼:

   POKE perx2*x1&*pery2\dx\dy+perx2*x4&*pery\dx\dy+perx*...
     x2&*pery2\dx\dy+perx*x3&*pery\dx\dy+320*pery2*...
     y1&*perx2\dx\dy+320*pery2*y2&*perx\dx\dy+320*...
    pery*y4&*perx2\dx\dy+320*pery*y3&*perx\dx\dy, color
   
  之後:
    以下內容爲程序代碼:

     POKE ((x1&*pery2+x4&*pery)*perx2+(x2&*pery2+x3&*pery)*...
    ...perx)\dx\dy+320*(((y1&*perx2+y2&*perx)*pery2+...
    ...(y4&*perx2+y3&*perx)*pery)\dx\dy), color

  不用擔心你需要理解上面的等式;它們僅僅用來展示因式分解合併同類項項後長度的減少。這樣你可以看見因式分解合併同類項後式子是何等的簡潔,不僅僅計算時間減少了,代碼也變短了。這對一個程序設計者來說是一箭雙鵰的結果。我同時也發現了通過因式分解一組計算,得到的數據更加的精確,數據也更加的平滑。所以看來應該是三個好處。

  b. 整數的使用

  QB有四種變量類型:整型,長整型,單精度小數,雙精度小數。自電腦被設計使用二進制整數計算數字以來,似乎僅僅在BASIC中整數對於電腦來說比浮點數更易於使用。小數不是直接的保存在內存中,而且要花去許多步驟來加載一個小數到內存。當需要小數會進行大量的計算,這些步驟能夠顯著的延緩程序的運行速度。因爲內存中二進制是由整數表示(2的非負冪),小數實際作爲分數部分儲存(2的負冪)。而計算這個小數,要消耗大量時間因爲這些值不是真正存在於內存而是通過BASIC計算生成。儘可能的使用整數將消除進行這些不必要計算的時間,所以增快了程序運行的速度。

  c. \ vs./

  每個使用BASIC的程序設計者都知道運算符/表示什麼,但是除非你寫過很長一段時間程序,不然你有可能用不上這個運算符\。運算符\用來整除(和運算符/相反,運算符/表示浮點除法)。就像上面文章提到的那樣,儘可能的使用整數,整數也是符合那種原則。

  但是爲什麼要在用2除8這樣的算式中使用運算符\呢?當然,它們都是整數,這一點我們都知道。但是沒有關係因爲我們不是在程序運行時候唯一處理這個的。8/2對電腦來說它並不認爲是整數的除法,它認爲是8.0000000/2.0000000。它會把整數轉換爲浮點數來進行除法,然後把結果又轉回整數。這樣怎麼會有效率呢?

  我們來看另外一個例子:9除以4。數學課上這會是9/4=2.25。但是在計算機運算中將是9.0000000/4.0000000=2.2500000或者9\4=2。現在你明白了,整除是不會產生小數結果的,而且它自動截取或者將答案取整。所以當使用它的時候要小心,因爲有時候你想要最精確的結果而有時候卻要極大程度的提高運行數度。這是你的選擇。

  d. 預先設定數據表(Premade Tables)

  有時候你可能想使用一些BASIC的內置函比如SIN或者COS來輸出小數答案。但是爲什麼要使用小數當你能使用整數來取代那些緩慢浮點計算的時候?這個時候應該用一張取特定整數值的表來解決問題。但這會降低精度嗎?不會的,它涉及任何顯而易見一些角度。到現在爲止你大概想知道當需要小數的時候如何使用整數來進行計算吧.這種情況用於讓你的表有100倍,1000倍或者10000倍的函數爲某種輸入正常返回值。之後由這些100,1000,10000簡單的整除得到最後的計算結果。例子:

以下內容爲程序代碼:

table:
cosine(0)=100:cosine(10)=98
cosine(20)=94:cosine(30)=87
cosine(40)=77:cosine(50)=64
cosine(60)=50:cosine(70)=34
cosine(80)=17:cosine(90)=0

start:
? 500*cosine(0) \100    500
? 500*COS(0)    500
? 500*sine(45) \100     353
? 500*SIN(45)   353.5533906

  瞧,你看到了。在整型值和小數值之間沒有太大的背離,所以這可以被用來爲任何數學運算來輸出整數,比如旋轉圖形的時候。
       
第二部分 直接存取(POKE和PEEK)

a. 決不要使用PSET和POINT
b. 整數 vs. 長整數
c. 內存塊操作(Memory Segments)

  a.決不要使用 PSET

  QBasic中PSET語句非常緩慢。因爲它太一般化以至於它能在每種圖形模式下工作。而POKE就比較特殊,這是顯然的。簡單的給出內存塊中地址,可以直接超作內存地址的值。POINT和PSET差不多的緩慢。PEEK應該被用來在任何可能的地方代替POINT。

  我將提示你在什麼地方應該使用或者不使用POKE或者PEEK,這樣你就不會像我剛意識到這一點的時候那樣糊塗了。你應該堅持在13H以下的任何圖形模式中使用PSET和POINT。我這樣建議是因爲這種情況下很難控制內存的分配和尋址。一些地方每一象素是4bits,另外的是2bits或者1bit。一些是通過屏幕交換頁面(screen page)來分配交換內存,而其他地方直接使用64K以上的內存來進行內存分配和尋址。(如果你不知道你在做什麼的話這是非常糟糕和凌亂的)而在使用13H的顯示模式中字符或者字符色彩顯示是每象素1byte,這樣就很容易尋址。(內存尋址將在後面涉及)

b. 整數 vs. 長整數

  如果你已經嘗試過在使用13h(320*200*256)的顯示模式下來填充(POKE)色彩直接寫色彩值,你可能會注意到在屏幕的中下部,會在一個值大於32767的時候有整數變量將會溢出。你可能認爲應該使用長整型來避免溢出,其實不是那樣。對於使用過彙編的人來說,你會注意到16bit的整數沒有負值,所以他們能夠超出BASIC的32767直接使用到65535。實際上,這只是部分的正確。彙編的16-bit數是有負值的。例如,-5=-5+65535=65531(模運算)。彙編中的一個負數和該數加上相應類型數的2的多少次方的結果一樣。這樣就可以在13h下用來填充(POKE)直接寫屏幕的底部了。例如要POKE 32768,簡單的運算32768-65536=-32768,你可以這麼寫語句:POKE -32768,color就達到目的了。

c. 內存塊的超作(Memory Segments)

下面是對編程很重要的內存分塊地址和偏移量:

        Segment  Offset
Graphics     &HA000   0
Text         &HB800   0
ASCII characters   &HFFA6   14

分塊地址通過這種方式得到:DEF SEG=[Segment #]而偏移量使用:PEEK([Byte]+[Offset])

第三部分 PALETTE調色板色彩相關

a. PALETTE USING方法
b. 直接從硬件輸出
c. 直接從硬件讀入

a. PALETTE USING方法

  PALETTE USING是QB的一條語句,是用來立刻寫所有的256顏色到PALETTE中來代替每次寫一個的。這樣做就增加了速度。一般使用一個256個元素的長整型數組中放置色彩值,每個元素都有色彩的PALETTE的RGB值。(65536*red+256*green+blue 值是0-63)。
  
PALETTE USING 這樣使用:

以下內容爲程序代碼:

DIM PaletteVariable(255) AS LONG
...
PALETTE USING PaletteVariable(0)
...

但是爲什麼當直接寫硬件的時候速度有了大概60倍的提高?

b. 直接硬件輸出

  這是最根本的初始化PALETTE方法。它可以一眨眼就完全改變整個256色的PALETTE。它如此的快以至於很輕鬆的得到旋轉PALETTE等效果。但是下面這一部分稍微有一些難學,但是一旦理解,就有巨大的價值。如下方法使用:

以下內容爲程序代碼:

OUT &H3C8, ColorNumber
OUT &H3C9, RedValue
OUT &H3C9, GreenValue
OUT &H3C9, BlueValue
  
  這裏的COLOR值,和PALETTE USING中的一樣,從0到63。當使用這個方法的時候, 像上面一樣,很容易讓一個有256個元素的長整型數組來用作PALETTE USING。同時,一些直接取地址值的方法應該被用來進一步的增加程序的速度和效率。下面是一個例子:

以下內容爲程序代碼:

DIM PalVar(255) AS LONG
...
DEF SEG=VARSEG(PalVar(0))
BLOAD "Palette.pal", VARPTR(PalVar(0))
FOR Temp%=0 to 255
OUT &H3C8, Temp%
OUT &H3C9, PEEK(VARPTR(PalVar(Temp%)))
OUT &H3C9, PEEK(VARPTR(PalVar(Temp%))+1)
OUT &H3C9, PEEK(VARPTR(PalVar(Temp%))+2)
NEXT

這個例子載入了一個獨立的PALETTE文件,但是如果你在程序中定義了所有的色彩那就不需要了。如果你沒有旋轉PALETTE,這個方法仍然是不需要的;PALETTE USING方法可以做的很好。

c. 直接硬件讀入

這和B的方式是相反的過程。通過這個超作,你可以直接從硬件讀入PALETTE的值。使用方法如下:

以下內容爲程序代碼:

OUT &H3C7, ColorNumber
RedValue = INP(&H3C9)
GreenValue = INP(&H3C9)
BlueValue = INP(&H3C9)
  



這個對當前屏幕色彩校正和屏幕淡化來說是非常有用的。

第四部分 DEBABELIZING

a. 真彩色 -> 8bit位色彩
b. 降低圖像分辨率
c. 在程序中使用圖像調色板
d. 配置調色板

a. 真彩色 -> 8bit位色彩

如果你想使用掃描的圖象或者讓程序有高質量的視覺表現,就必須首先用一些圖形處理程序打開它們然後存爲8位的圖片。因爲在QBasic中大部分是使用13H屏幕模式來處理色彩。它僅僅有256(8位)種色彩而大部分的掃描儀是24位(真彩)。只有這樣你才能成功地用QBasic來打開圖片而不會出錯。

b. 降低圖像分辨率

大多數繪圖程序有重新調整圖片的選項。爲了使圖片適合13H,你必須確定圖片的分辨率是320*200或者比這個低。所以使用一些繪圖程序重新調整解析度來配合你程序的需要。如果你想重新調整一個QBasic中已有的圖片,載入屏幕然後使用下面這個公式:

以下內容爲程序代碼:

FOR x% = startx% TO endx%
    FOR y% = starty% TO endy%
       PSET (x%, y%), POINT(x% * Factorx%, y% * Factory%)
    NEXT
NEXT

或者

以下內容爲程序代碼:

DEF SEG = &HA000
FOR x& = startx& TO endx&
    FOR y& = starty& TO endy&
       POKE x& + 320 * y&, PEEK(x& * Factorx& + 320 * (y&*Factory&))
    NEXT
NEXT
  
說明:Factorx和Factory是圖像被壓縮的係數。

c. 在程序中使用圖像調色板

所有的8位圖片格式資料在有調色板的繪圖程序都是公開的。我沒有每個文件存儲格式存儲色彩的所有的細節(可以到www.wotsit.com尋找)但是我可以在使用BMP格式方面提供幫助。你可以試試我網頁上的讀入程序,地址在:www.gnt.net/~gump的“Files->Utilities”下面。這個讀入程序中使用了一個長整型數組pal來存儲圖片的調色板信息。完全的存儲了所有256個元素,從數組的array[0]到array[255]來作爲一個調色板色彩文件當你重新載入的時候你就有你自己的調色板了。
c. 配置調色板

現在你可以在QBasic中載入圖片了,但是每個圖片有不同的色彩信息應該怎麼辦呢?如果嘗試載入在屏幕上一次載入2個或者更多的圖片,確實會擾亂色彩(除非是灰度圖象,它們有一樣的灰度色彩模版)。 所以你怎麼在一個相同的調色板中放置更多的圖片?如果要這麼做的話,你必須使用AdobePhotoshop或者其他的允許配置調色板的圖象處理程序來處理了。你讓你的調色板所有的值擴展4倍來配置,因爲所有圖形文件格式保留它們調色板的值在0到255之間同時對non-SuperVGA的屏幕是在0-63之間。之後你參考本章第一部分,增加使用配置的調色產來改變色彩爲256這一個步驟就可以了。

第五部分 子圖形畫面(SPRITES)

a. 什麼是子圖形畫面?
b. 實現子圖形的子程序編寫

a. 什麼是子圖形畫面?

子圖形畫面就是程序中的一些既不是前景,也不是背景的圖像。一般來說,移動的圖像就是子畫面。在遊戲超級瑪莉和超人(MegaMan)中可以看見這些例子。

b. 實現子圖形的子程序編寫(在屏幕模式 13H中)

在你進一步閱讀之前,爲了使你的進一步學習更有效,請你確定你已經閱讀過有關POKE、PEEK和INTEGER使用的相關的教程。自從不再使用Commodore128和它的內建的字圖形程序,我們就需要通過自己設計來解決這一問題。QBasic的GET和PUT既不能在圖像周圍保留黑色區域(Black Box)(PEST)也不能顯示黑色只能讓圖形失真(XOR),所以那樣是沒有辦法的。要繪製透明背景的子圖像而不是把畫面塗黑,就需要一像素一像素的繪製,同時在繪製之前校驗是否是黑的。最簡單的方法就是將每個單元按一個整數以GET-PUT的方式來保存該圖像,例如,DIM picture(32001) as INTEGER。下面是整數GET-PUT數組的格式:

array%(0) =按位bit表示的圖像寬度
array%(1) = 以像素pixel表示的圖像高度
array%(2)
...   = 一系列像素值的列表(每個單元兩個值)
array%(n)

爲了得到圖像的像素寬度,應該把每個數組單元以8爲單位加以分開,例如width%=array%(0)\8。而圖像的高度等於數組元素1。開始着手繪製子圖像的時候,你必須定義數組的內存塊區,例如DEF SEG=VARSEG(ARRAY%(0))。這是因爲你將要從數組直接向內存地址寫值。在你的調色板裏,黑色的值是color # 0,所以這個實現子圖像的子程序在碰到它的時候應不繪製任何像素。以上基本的準備已經講完了,只需要自己具體的子圖像實現程序和所有需要的值,就可以製作你自己程序中的子圖像了。下面你看到的是一個完成得的子圖像的實例,它已經包含在VirtuaSoft的圖形庫之中。

以下內容爲程序代碼:

SUB sprite (x%, y%, file$)
DIM picture(32001) AS INTEGER
DEF SEG = VARSEG(picture(0))
BLOAD file$, VARPTR(picture(0))
FOR tempx% = 0 TO picture(0) \ 8 - 1
    FOR tempy% = 0 TO picture(1) - 1
       temp% = PEEK(VARPTR(picture(2)) + tempx% + (picture(0) \ 8&) * tempy%)
       IF temp% > 0 AND tempx% + x% >= 0 AND tempx% + x% < 320 AND tempy% + y% >= 0 AND tempy% + y% < 200 THEN
  PSET (x% + tempx%, y% + tempy%), temp%
       END IF
    NEXT
NEXT
DEF SEG = &HA000
END SUB

說明:x%和y% 是傳給子程序的值用來表示子圖像左上角的座標位置,file$用來存儲數組。

如果你將要使用這些代碼在你的程序中,希望你能在榮譽出品(Credits)一列中提到VirtuaSoft或者通過電子郵件寄到[email protected]給我們一份你作品的拷貝這樣我們將因爲自己的教程或者代碼給大家帶來的幫助和便利倍感榮耀。

第六部分  雙緩衝

a. GET-PUT 存儲方式
b. 使用GET-PUT進行雙緩衝操作

a. GET-PUT 存儲方式

GET和PUT是現存對圖像存儲最有效的方法。這種方式基本上知道圖像的寬和高而且列出了圖像中所有色彩的值。下面是在屏幕模式13H中以GET-PUT方式存儲圖像爲一個整型數組的分解解說。

array%(0) = 用位bit表示的圖象寬
array%(1) = 用像素表示的圖像高度
array%(2)
...       = 像素值列表
array%(n)

  爲了使用雙緩衝技術,數組的0元素將被賦值2560,因爲13H以每8位表示一個像素,一共有320個像素,那麼320x8=2560。數組的1元素被賦值200像素用來表示高。然後所有剩下的元素是每個元素有兩像素。(因爲整數是16位而一個像素是8位),所以數組看上去就是下面這個樣子:

array%(0) = 2560
array%(1) = 200
array%(2) = pixel at 0,0, pixel at 1,0
...
array%(n) = pixel at 318,199, pixel at 319,199

b. 使用GET-PUT來進行雙緩衝操作

既然你是在13H模式下使用GET-PUT數組的高手,你應該懂得足夠多來直接從內存向數組存放數據。它基本上和向屏幕寫數據差不多,除了內存塊地址不同之外。下面是向數組存放數據與向屏幕寫數據之間的比較的異同:

以下內容爲程序代碼:


SCREEN:
DEF SEG = &HA000
POKE x% + 320& * y%, color%

GET-PUT ARRAY:
DEF SEG = VARSEG(array%(0))
POKE VARPTR(array%(2)) + x% + 320& * y%, color%

沒有太大的差別吧,僅有的是你定義不同的內存地址同時需要考慮數組的偏移(13H的屏幕模式中偏移是0,所以之前沒有使用什麼偏移)。用這種方法來雙緩衝,完全的直接通過GET-PUT數組來在你的屏幕上繪製圖像,這樣在屏幕上輸出最終圖像:PUT(0,0),array%,PSET。如果你想知道如何通過GET-PUT方式直接向數組寫入線和圓這些,我將開始製作一個包含這些功能的庫將最終在我的Web站點發布。

第七部分 BSAVE和BLOAD

BLOAD和BSAVE是直接操作內存地址,而不像OPEN,所以你需要定義在內存中開始的地址以便於文件知道從什麼地方開始讀寫。對於BSAVE來說,同時需要告訴它開闢的文件具體有多大多少字節。BSAVE和BLOAD的使用語法如下:

以下內容爲程序代碼:


     DEF SEG = Segment
   BSAVE "FileName", Offset, NumBytes

     DEF SEG = Segment
   BLOAD "FileName", Offset

說明:NumBytes從0開始計數,就是比正式的數值少1。

要從圖像圖形屏幕模式直接存儲圖像,要使用內存塊區&HA000和地址偏移量0;而在文字圖形模式使用內存塊區&HB800和0;對於數組來說使用VARSEG(array%(0))和VARPTR(array%(0))。BLOAD和BSAVE能快速的存取文件但是也因爲這一點存在問題。因爲你必須在你使用它們之前對你在做什麼很清楚。下面是一些常見的因爲錯誤的使用這種快速的方法而導致電腦的崩潰的原因。

1.某個文件被存入一個太小的數組。BLOAD不知道內存中是什麼。它繼續寫直到完成爲止。請確定所有的相關的數組能夠容納下該文件的大小。

2.該文件沒有被適當的讀入數組。你必須確定你在BLOAD中使用了正確的偏移。如果數組有不止一個元素(array[0]),你需要使用VARPTR(array(0))。同時,DEF SEG必須被設置指向第一個元素(array[0])。如果數組是多維的,確定BLOAD考慮了這個問題。如果這個數組沒有定義元素,就不需要在BLOAD中做比VARPTR(array)更多的其他事情。

3.文件沒有正確的存儲。就像上面解釋的那樣,確定你爲數組分配了適當的內存塊區地址和偏移量

(完)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章