Clipper 教程:開源 Clipper(s) 指南/基礎語言教程
請注意:如果有人要貢獻,Clipper 教程應該只包含可以在所有相容 Clipper 的編譯器上執行的“標準”程式碼。特定於編譯器的程式碼應該放在其他地方,或者在框中明確標明。
這裡的程式碼應該是簡單的控制檯模式應用程式,只使用最簡單的輸入/輸出形式,並且只關注演算法,因為建立和管理使用者介面的方法將在第 5 章“建立使用者介面”中描述。
讓我們嘗試突出顯示 helloworld 的各個部分
function MAIN
* This is an example
clear
?"Hello, the weather is fine today"
return nil
(在 GeoCities 中,此程式碼以及所有其他程式碼來源都是由線上服務 CodeColorizer 在 http://www.chami.com/colorizer/ 突出顯示的,或者,也可以使用 http://tohtml.com/Clipper/ 上的服務 - 它們看起來比 Wikibook 的突出顯示要好一些)。
我們將對 helloworld 程式的每一行進行註釋。
function MAIN
第一行定義了一個名為 MAIN 的函式。使用 Harbour 時,定義這樣的函式不是強制性的,如果省略了它,編譯時會發生錯誤,但我將保留它以確保示例在所有編譯器上都能正常工作。在點提示符(hbrun/xbscript)下輸入命令時,不能定義函式。
每種 xBase 語言都是不區分大小寫的,這意味著以下所有行都是一樣的
function MAIN FUNCTION main FuNcTiOn mAiN
當然,只有當你使用此特性來提高程式碼可讀性時,它才有利。
我們將在後面學習如何定義和使用函式和過程。
* This is an example
第二行是註釋。對程式進行註釋有助於你在以後修改它們時更容易理解。如果程式碼沒有註釋,修改它們將是一項非常艱鉅的任務。如果你要修改別人編寫的程式碼,而他們沒有進行註釋,那麼修改會更加困難:僅使用程式碼來弄清楚一個程式的功能非常困難,即使是最乾淨的程式語言,如果沒有適當的註釋,也可能會變得難以理解。
你可以使用多種不同的風格編寫註釋:使用星號 (*)、雙斜槓 (//)、雙和號 (&&) 或一對斜槓-星號 (/*) 和星號-斜槓 (*/), 如下所示
* This is a comment... // ...and so is this... && ...and this. /* This is an example of the fourth commenting style, which may span over several lines.*/
第二種和第四種註釋風格來自 C 程式語言,第一種和第三種是 Clipper/xBase 標準特有的。xbscript 不接受任何這些註釋,但 hbrun (Harbour) 接受最後三種。
clear
不,此命令的目的是不是為了使原始碼清晰(那太容易了!編寫清晰的原始碼是我們自己的責任,為了做到這一點,我們必須對其進行良好的註釋),而是為了清除螢幕。:-)你也可以使用 clear screen 或 cls,儘管這兩個命令與 clear 不完全相同(區別將在我們能夠GET到這一點的時候變得清晰 - 然後,你也可以欣賞這段話中的雙關語 - 但更有可能不會)。
什麼是命令?
Harbour 命令是透過 #command 宏指令定義的 (http://harbour.edu.pl/clipper/en/ng122d3c.html)。我們遇到的第一個命令是在檔案 std.ch 中由以下行定義的
#command CLS => Scroll() ; SetPos(0,0) #command ? [<explist,...>] => QOut( <explist> )
? "Hello, the weather is fine today"
? 是一個命令,表示列印。在 BASIC 程式語言中,它也是其列印命令的縮寫(xBase 的語法與 BASIC 程式語言非常相似)。
在這種情況下, ? 列印字串“Hello, the weather is fine today”。請注意,字串可以用單引號(或撇號)、雙引號或方括號括起來:以下所有都是相同的
? "Hello, the weather is fine today" ? 'Hello, the weather is fine today' ? [Hello, the weather is fine today]
最後一行
return nil
Return 用於標記函式的結束。我們將在後面解釋 return 到底做了什麼。
如今,Harbour 不需要顯式定義 main 函式/過程。helloworld 的以下版本可以編譯,並且與前面的版本完全相同
&& hello world without commands and main
Scroll() ; SetPos(0,0)
QOut( "Hello world" )
Harbour 直譯器和虛擬機器
上面的程式儲存在一個名為 hello.prg 的文字檔案中,可以使用以下命令執行
hbrun hello.prg
hbrun 是 Harbour 的直譯器。這意味著如果我們執行它,我們就會看到一個螢幕,螢幕的最後一行有一個“點提示符”,我們可以在那裡輸入並執行指令。例如,輸入
? "Hello world"
將把 Hello world 列印到螢幕上,但只有在 hbrun 直譯器本身關閉後才會列印。要退出直譯器並列印問候語,請輸入
QUIT
總而言之,hbrun 允許你體驗在舊的 dBase “點提示符”模式下是如何處理資料庫的 - 也就是像這樣:http://manmrk.net/tutorials/database/dbase/IntrodBASEIIIPlus.htm
在包含以下行的 printA.prg 上執行編譯器 harbour(Windows 上的 harbour.exe)
? "A"
將輸出一個 printA.c 檔案。該檔案的底部內容如下
HB_FUNC( PRINTA )
{
static const HB_BYTE pcode[] =
{
36,1,0,176,1,0,106,2,65,0,20,1,7
};
hb_vmExecute( pcode, symbols );
}
也就是說,作為中間步驟,Harbour 編譯器會編譯 pcode,或用於虛擬機器的 bytecode。
如果編譯是用 /gh 選項完成的
hbmk2 /gh printA.prg harbour /gh printA.prg
結果將是一個名為 printA.hrb 的檔案,這是一個“Harbour 可移植物件”檔案,可以使用以下命令執行
hbrun printA.hrb
這樣,hbmk2 和 hbrun 就像一對工具,與 Java 編譯器 javac 和 Java 直譯器 java 完全等效。
順便說一下,虛擬機器並不是一個新概念。如果我們看一下復古計算領域,我們會發現,在 20 世紀 70 年代後期和 80 年代初期,加州大學聖地亞哥分校有一個基於 pcode 的可移植作業系統,即 UCSD P-System,它是用 UCSD Pascal 編寫的,可以在 6502、8080、Z-80 和 PDP-11 上執行 http://www.threedee.com/jcm/psystem/index.html(它實際上是 MS-DOS、PC DOS 和 CP/M 的競爭對手 - http://www.digitalresearch.biz/CPM.HTM 和 http://www.seasip.info/Cpm/index.html)。
此外,在 1979 年,Joel Berez 和 Marc Blank 開發了 Z-machine 作為執行 Infocom 的文字冒險遊戲的虛擬機器,這個名字毫不掩飾地表明,他們主要是在考慮 Zork。Z-machine 的程式語言被稱為 ZIL (Zork Implementation Language)。ZIL 到 Microsoft .NET 的現代編譯器是 ZILF https://bitbucket.org/jmcgrew/zilf/wiki/Home,並且有一個 Z-machine.NET 可用 https://zmachine.codeplex.com/。更多資訊請訪問 http://inform-fiction.org/zmachine/index.html。
除了 JVM (Java 虛擬機器) 之外,Microsoft .NET CLR (公共語言執行時) 也是另一個現代的虛擬機器。
hbmk2 是為了支援所有 shell、所有平臺上的所有編譯器而建立的,也是為了替代舊的 'bld.bat' 解決方案,同時保持與現有 hbmk 指令碼功能和選項的相容性。
有關如何在不使用 hbmk2 的情況下獲得可執行檔案的相關資訊,可以在 http://www.kresin.ru/en/hrbfaq.html 上找到。
請記住,必須更新 PATH 環境變數(在 Windows 中)以包含包含編譯器的目錄:在命令提示符視窗中本地使用命令 SET PATH=C:\hb32\bin;%PATH% 或全域性更新它:“開始”→“控制面板”→“系統”→“高階系統設定”→“環境變數...”→ 選擇“路徑”,然後單擊“編輯...”
Clipper 支援的資料型別列表如下:
- A Array(陣列)
- B (Code) Block(程式碼塊)
- C Character(字元)
- D Date(日期)
- L Logical(邏輯)
- M Memo(備註)
- N Numeric(數值)
- O Object(物件)
- U NIL(空值)
這些字母可以用作變數名的字首,以便“一目瞭然”地指示其型別。這樣,oBox 就是一個物件的名稱,aNames 是一個數組,dBirthday 是一個日期,等等。邏輯變數的另一種選擇是新增 is 字首,例如 isOk 或 isExisting(比 lOk 和 lExisting 更具可讀性)。這種命名約定稱為 匈牙利命名法,它也適用於函式以及它們的概要:ACOPY( <aSource>, <aTarget>, [<nStart>], [<nCount>], [<nTargetPos>] ) 是將陣列元素從一個數組複製到另一個數組的函式,它接受兩個陣列 aSource 和 aTarget,以及三個可選的數值引數 nStart、nCount 和 nTargetPos;CToD(cDate) 接收一個字元引數並將其轉換為日期,DBCreate() 是一個建立資料庫的函式。
Memo 型別只能在資料庫中使用(它是指向 DBF 表的輔助檔案的連結,如 維基百科 DBF 條目 中所述)。
以下是 VALTYPE() 函式返回的結果列表。TYPE() 函式也返回:
- U NIL、區域性或靜態
- UE 語法錯誤
- UI 不確定的錯誤
Harbour 有一個更長的列表 (http://www.fivetechsoft.com/harbour-docs/harbour.html)
- HB_ISARRAY
- HB_ISBLOCK
- HB_ISCHAR
- HB_ISDATE
- HB_ISDATETIME
- HB_ISHASH
- HB_ISLOGICAL
- HB_ISMEMO
- HB_ISNIL
- HB_ISNULL
- HB_ISNUMERIC
- HB_ISOBJECT
- HB_ISPOINTER
- HB_ISPRINTER
- HB_ISREGEX
- HB_ISSTRING
- HB_ISSYMBOL
- HB_ISTIMESTAMP
例如,ISPOINTER() (http://www.marinas-gui.org/projects/harbour_manual/ispointer.htm) 被標記為:CA-Cl*pper 中不可用。它與 HB_IT_POINTER 和 HB_ISPOINTER 一起用於 Harbour 的內部機制(C 級 API:http://www.fivetechsoft.com/harbour-docs/clevelapi.html#ispointer)。
讓我們來看一個展示最常用資料型別的小程式。
我想在這個例子中展示 π 的計算而不是 √2 的計算,但是 xBase 缺少 ArcTan 函式。我們可以透過從外部庫匯入它或者自己提供它來解決這個問題。(這兩種方法都應該在這個教程中進行研究)。
最後兩種資料型別與前面幾種略有不同:“Memo” 在資料庫外部使用時不是很有用,而陣列不能在資料庫中使用。
&& example of compilation command
&& A:\>c:\hb32\bin\hbmk2 -quiet -oc:\hbcode\test.exe test.prg -run
&& please note that this example has not a MAIN function
&& an example of string concatenation
? "Hello" + " " + "World!"
&& let us do now a few numerical computations, integer numbers are a good starting point
? 5+13
? 12*8
? 3/2
SET DECIMALS TO 15
sqrt2=sqrt(2) && computing the square root of 2...
? sqrt2 && ... and printing it
&& ? caporetto
&& if the line above was not commented, the compiler would issue the two following lines
&& Error BASE/1003 Variable does not exist: CAPORETTO
&& Called from TEST(8)
&& as xBase is not good at history, let us revise it: http://www.historyofwar.org/articles/battles_caporetto.html, http://www.firstworldwar.com/battles/caporetto.htm
&& we can then assign the correct date to this war
caporetto := ctod("10-24-1917")
a := date() && system date
? a + 1 && will print tomorrow's date
? a - caporetto && this will tell us how many days have passed since Caporetto's battle (difference between two dates)
SET DECIMALS TO 2
? (a - caporetto) / 365
?? " years have passed since Caporetto's battle"
? 1+1=3
&& it will print ".F.", that is, "FALSE" (of course...)
&& The following two instructions should be discussed to expand on the subject of operator precedence
? 1/2+2^3*sqrt(25)-3^2
? 1/2+2^3*(sqrt(25)-3^2)
SQR 函式已從 Harbour 中刪除,並由 SQRT 替換。嘗試使用它會導致連結錯誤“undefined reference to 'HB_FUN_SQR'”。
從下面的例子(它可以執行)我們可以看出,Harbour 是一種 弱型別 程式語言:一個變數,例如我們例子中的 a,可以是數字,然後變成文字字串。
a := 1+1
? a
a := "abc"
? a
存在一個問題:如果變數型別發生了改變,而程式設計師沒有意識到這一點,他可能會發出一些指令,在執行時會導致錯誤。如果我們在上面的程式中新增另一行程式碼
?a+1
,Harbour 會嘗試將數字 1 新增到字串 "abc" 中,結果將是錯誤
錯誤 BASE/1081 引數錯誤:+.
字串本質上是一個字元列表。它們用雙引號括起來(例如,"Hello")。請注意區別
123 => a number "123" => a string
讓我們看看如何使用 Stuff() 函式來更改字串中的字元
sTest:="fun"
?sTest, STUFF(sTest,2,1,"a")
&& => fun fan
以下是處理字串時最常用的函式列表
Lower() Convert uppercase characters to lowercase Upper() Converts a character expression to uppercase format Chr() Convert an ASCII code to a character value Asc() Convert a character to its ASCII value Len() Return the length of a character string or the number of elements in an array At() Return the position of a substring within a character string
然而,在 Clipper 和 Clipper 工具庫中,也可以找到非常奇特的函式,例如這些函式
AsciiSum() Finds the sum of the ASCII values of all the characters of a string Soundex() is a character function that indexes and searches for sound-alike or phonetic matches CHARONE() Search and remove repeating characters CHARONLY() Remove non-standard characters CHAROR() "OR" bytes in a string
讓我們看看我們可以用這些函式做什麼。
我們展示了與使用者按下的鍵對應的 ASCII 碼 (http://www.asciitable.com/),在按下 ESC 鍵時結束程式。
#include "Inkey.ch"
DO WHILE .T.
WAIT "" TO cChar
?? " ", Asc( cChar )
IF ( LastKey() == K_ESC )
EXIT
ENDIF
ENDDO
inkey.ch 是 Inkey() 函式的 eader 檔案,它用於從鍵盤緩衝區獲取以下鍵碼。
資料型別的第一種分類將它們分為 簡單(或 標量)型別和 結構化(或 複合、聚合或 複合)型別。
在 xBase 中,簡單資料型別 是數字、邏輯值、字串和日期,即可以在資料庫表中作為欄位型別的型別。備註不是,因為它們是指向外部檔案的指標。
標量資料型別可以組合起來構建結構化資料型別:陣列、關聯陣列(雜湊表)、表或資料庫(它們是儲存在磁碟上的“資料型別”)。w:Primitive_data_type
程式碼塊和物件是特殊資料型別。
&& First we print the values of two expressions with the exponentiation operator
? 3**3**3 && is 19683
? 3**(3**3) && is 7625597484987
&& Then we let our compiler tell us if they are the same
? 3**3**3 = 3**(3**3) && result is .F.
&& We've a look at how our compiler deals with overflow and underflow...
? 9**9**9
&& ***********************
? 1/9**9**9
&& 0.00
? -(9**9**9)
&& ***********************
在這個例子中,我們看到冪運算子 ** 是左結合的(與數學中冪運算為右結合運算相反)。
3**3**3 和 3**(3**3) 給出兩個不同的結果,這意味著括號改變了運算子優先順序:括號內的東西先計算。
即使運算子是左結合的,9**9**9 的結果也是一個相當大的數字(確切地說,是 196627050475552913618075908526912116283103450944214766927315415537966391196809,你可以很容易地用一些任意精度計算器程式(如 GNU bc,它在每個 Linux 發行版中都可用,也可以從 http://gnuwin32.sourceforge.net/packages/calc.htm 下載到 Windows)或手工計算來驗證),它超出了 Harbour 為一個數字分配的資源(這稱為溢位),因此它會列印 23 個 * 來表示這個錯誤。如果我們嘗試計算它的倒數(這稱為下溢),它也會給出零(0.00)。溢位和下溢錯誤發生在計算結果分別太大或太小,無法用編譯器為我們嘗試將結果儲存的變數分配的位數來表示。
日期也是如此。那麼
date()-1000000
(從當前日期減去 100 萬天)的結果是什麼?讓我們看看
&& today my outputs are:
?date()
&& ==> 05/15/19
?date()-365000 && more or less 1000 years
&& ==> 01/13/20
set century on && we change an option...
?date()-365000 && ... and try ageing
&& ==> 01/13/1020
?date()-1000000 && more or less 2700 years (i.e. the results should be around 700 B.C.)
&& ==> 00/00/00
?ctod("31-12-9999")+1
&& ==> 00/00/00
在這裡,最後兩個輸出("00/00/00")表示無效日期,就像上面看到的 23 行星號表示無效的數字結果一樣。可接受的日期是那些可以儲存在 dBase 表(即 .dbf 檔案)中的日期,在這些檔案中,日期是一個 8 個字元的字串,格式為 "YYYYDDMM"(4 個字元用於年,2 個字元用於月,2 個字元用於日)。因此,負年(公元前)或超過 5 位數的年是不被接受的。
Harbour 使用 SET 命令更改其行為之前,只會顯示兩位數的年份,並且在使用這些大年份時會給出錯誤的結果。原因很簡單:在資料庫應用程式(庫存管理、預訂系統)中,我們無需處理幾個世紀的時間間隔,而 Harbour 處理日期的例程在嘗試這樣做時無法正常工作。在其他領域,例如 w:Archaeoastronomy 情況就是如此,那些處理這些問題的人(例如,檢查公元前 2000 年卡納克冬至日出的精度或巨石陣的方位)會使用他們自己的特殊例程來計算日期。
在使用計算機進行計算時,還有一個難題。看看
store 7 to n && an alternate way of an assignment (dates back to dBase II)
?sqrt(sqrt(n))^2^2=n
&& the result is .F.
set decimals to 15
?sqrt(sqrt(n))^2^2
&& 7.000000000000003
現在,如果我們從一個數字中提取兩次平方根,然後將結果平方兩次,我們應該得到起始數字。事實上,計算機並非如此精確,檢查兩個浮點數是否相等可能會產生意外的結果。
如果你想了解更多資訊,可以看看每個計算機科學家應該瞭解的關於浮點數運算的知識,地址為 https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html(它是 Sun Microsystems 一本書《數值計算指南》的一部分),以及十進位制算術常見問題解答,地址為 http://speleotrove.com/decimal/decifaq.html。
幸運的是,xBase 語言中使用的變數的精度以及在常見的商業應用程式中,導致精度損失的平方根、對數和其他數學運算相當罕見,這些情況在一定程度上減輕了這個問題。
SET OPTIONS
我們已經看到了 SET DECIMALS TO n 和 SET CENTURY ON/OFF 命令。但實際上,有很多選項可以設定。SETMODE(<nRows>, <nCols>) 函式在使用 hbrun 或 xbscript 時特別有用:它的目的是確定視窗的大小。
我們可以列印比較的結果,它是一個布林值(表示為 .T. 或 .F.),但是它們與特殊的單詞一起使用,以分支或重複執行一條或多條指令。
當布林表示式的結果為真(其 xBase 符號為 .T.)或假(.F.)時,我們就有一個布林表示式。獲得布林結果的最簡單方法是使用比較(關係)運算子:=,==, !=,<>,#,>,<,>= 和 <=. 始終記住比較運算子 =(等於)、==(完全等於)和賦值運算子 := 之間的區別。
實際上,布林表示式的“真正”(數學)定義與上一段中所述的不同。一個“真正”的布林表示式只包含兩個布林值 .T. 和 .F. 以及布林運算子(.AND.、.OR.、.NOT.)。
布林代數自 1937 年克勞德·夏農的著作以來就被應用於電子數位電路。請參閱 http://www.allaboutcircuits.com/textbook/digital/chpt-7/introduction-boolean-algebra/ (以及該線上教科書的後續頁面)以瞭解布林代數在電路(包括微處理器)中應用於位時的含義。在這裡,我們正在更高層次上使用它。
考慮《哈姆雷特》第三幕第一場:“[…]生存還是毀滅?這是一個問題—— 究竟哪樣更崇高,在心智中忍受 殘酷命運的冷箭毒矢……”。如果王子哈姆雷特問布林(但他不可能,因為這發生在 14 世紀或 15 世紀,而布林出生於 1815 年),他的回答肯定會讓他困惑。為什麼呢?
to_be=.T. && is it true?
?to_be .OR. .NOT. to_be
&& .T.
to_be=.T. && is it false?
?to_be .OR. .NOT. to_be
&& .T.
正如哈伯告訴我們的,在布林代數中,答案是“真”。請檢視此頁面:https://vicgrout.net/2014/07/18/to-be-or-not-to-be-a-logical-perspective/.
已經開發出許多圖形化方法來檢視程式的流程控制是如何轉移的:這些方法包括 **流程圖** (https://www.lucidchart.com/pages/what-is-a-flowchart-tutorial)、**資料流圖** (https://www.lucidchart.com/pages/data-flow-diagram)、**Nassi-Shneiderman 圖** 以及邁克爾·A·傑克遜的 **JSP** 和 **JSD** 圖(這幾乎肯定不是你想到的第一個邁克爾·傑克遜)。
以下示例從鍵盤獲取一個數字並將其打印出來。如果數字為 0,它會對該值進行註釋(我將此示例作為典型的 _虛無主義_ 程式設計)。流程圖顯示了為什麼 _條件執行_ 有時被稱為 _分叉_。
Function MAIN()
LOCAL number
INPUT "Key in a number: " TO number
IF number = 0
?? "Congratulations, you keyed the fabolous number "
ENDIF
? number
RETURN
此示例列印兩個不同的註釋,無論輸入數字除以 2 的餘數是否為 0。
Function MAIN()
LOCAL number
INPUT "Key in a number: " TO number
IF number % 2 = 0
? "You keyed in an even number"
ELSE
? "You keyed in an odd number"
ENDIF
RETURN
此示例表明可以執行多個命令。
Function MAIN()
LOCAL number
INPUT "Key in a number: " TO number
IF number % 2 = 0
? "You keyed in an even number"
? "I can prove it:"
? "the result of ",number," % 2 = 0 is ", number % 2 = 0
ELSE
? "You keyed in an odd number"
? "I can prove it:"
? "the result of ",number," % 2 = 0 is ", number % 2 = 0
ENDIF
RETURN
此示例添加了另一個關鍵字 ELSEIF,以表明選擇並非一定是二分的。這是一個鏈式條件。只有一個分支將被執行。
function MAIN
LOCAL nNumber := 0
//
INPUT "Key in a number: " TO nNumber
//
?? "Your number is "
IF nNumber < 50
? "less than 50"
ELSEIF nNumber = 50
? "equal to 50"
ELSE
? "greater than 50"
ENDIF
以下示例檢查系統時間是否早於 18 點(下午 6 點),並根據一天中的時間列印相應的問候。
? Time()
// CToN source is numconv.prg, library is libct.
IF CToN( SubStr( Time(), 1, 2 ) ) < 18
? "Good day"
ELSE
? "Good evening"
ENDIF
有三個邏輯運算子:.AND.、.OR. 和 .NOT.
這是一個具有較長條件的示例,使用 .OR. 運算子協調更多比較。該示例非常愚蠢,但它展示瞭如何使用分號在下一行繼續執行指令。
WAIT "Key in a letter: " TO char
IF char = "a" .OR. char = "A" .OR. char = "e" .OR. char = "E" .OR. ;
char = "i" .OR. char = "I" .OR. char = "o" .OR. char = "O" .OR. ;
char = "u" .OR. char = "U"
? char, " is a vowel"
ELSE
? char, " is not a vowel"
ENDIF
但是,使用子字串比較運算子 **$**(或正則表示式,我們將在後面學習的更高階主題)可以更簡潔地實現相同的功能。
WAIT "Key in a letter: " TO char
IF char $ [aeiouAEIOU]
? char, " is a vowel"
ELSE
? char, " is not a vowel"
ENDIF
DO WHILE、EXIT、LOOP、ENDDO 是用於 _在條件為真(即其結果為 .T.)時重複執行一系列語句(迴圈體)_ 的關鍵字。
迴圈有幾種形式:**先測試迴圈**、**後測試迴圈** 以及 **確定迭代**,後者透過 _計數控制迴圈_(通常稱為 for 迴圈)實現。在實踐中,我們也可能有一個 **中測試迴圈**,但沒有特定的語法...我們需要在現有的迴圈體中新增一個 EXIT 語句來獲得這個迴圈。我們將鍵入的下一個程式對數學家來說很有趣(真正的程式設計師不怕數學,你知道這個著名的格言嗎?)
這裡就是它
&& Function MAIN LOCAL number, sum, n
&& An anonymous contributor renamed the variable "num" into "number", increasing this short program readability, but the line above would give
&& Error E0030 Syntax error "syntax error at 'LOCAL'"
Function MAIN
LOCAL number, sum, n
CLS
? "Let's sum up the first n odd numbers."
INPUT "How many numbers shall I sum up? " TO n
sum=0
number=1
DO WHILE number <= 2*n
sum=sum+number
number=number+2
ENDDO
? "The sum requested is ", sum
如您所見,此迴圈語句類似於 IF 語句:它們都以 END 對應語句結尾,它們都包含一個邏輯表示式。
此迴圈語句將持續執行,直到其條件保持為真(將評估為 .T.)。
它重複執行的兩個指令是 sum=sum+num 和 num=num+2。第二個是根本性的:如果沒有它或它出錯(例如,如果鍵入了 num=num/2),條件將不會評估為 .F.,程式將不會停止執行(這被稱為無限迴圈)。當這種情況發生時,同時按下 Ctrl 和 C 鍵。這應該說服計算機將注意力轉移到你身上,而不是執行迴圈。
上面的程式很好地說明了如何使用一個毫無創造力的機械計算器來解決求解前 n 個奇數之和的問題。有關其創造性方法的說明可以在以下地址找到:http://betterexplained.com/articles/techniques-for-adding-the-numbers-1-to-100/(以及關於高斯在小學炫耀的典型軼事)。
WHILE 迴圈語句被稱為在迴圈頭部具有控制表示式,與 Pascal 的 REPEAT-UNTIL 迴圈語句(後測試迴圈)相反,後測試迴圈在迴圈尾部具有條件控制(這些是義大利計算機科學俚語中的先測試迴圈和後測試迴圈,它們很有趣,不是嗎?)。xBase/Clipper 如何說 REPEAT-UNTIL?它沒有。以下是如何模擬它(從諾頓指南中複製和貼上)
LOCAL lMore := .T.
DO WHILE lMore
loopbody
lMore := condition
ENDDO
如您所見,我們首先將一個變數設定為 true,進入迴圈,並在迴圈末尾指定條件,以確保其主體至少執行一次。
這是一個示例,等效於 tutorialspoint.com 中的 pascal_repeat_until_loop 頁面上的示例。
LOCAL a := 10
LOCAL lMore := .T.
DO WHILE lMore
? 'value of a: ', a
a := a + 1
lMore := ( a != 20 )
ENDDO
但是上面的程式碼更改了條件。如果我們不想發生這種情況,我們可以簡單地否定我們在 Pascal 程式碼中使用的條件
然後變為
LOCAL a := 10
LOCAL lMore := .T.
DO WHILE lMore
? 'value of a: ', a
a := a + 1
lMore := .NOT. ( a = 20 ) && or, in alternative, lMore := ! ( a = 20 )
ENDDO
現在讓我們看看這種型別的迴圈
DO WHILE .T.
? "I won't stop."
ENDDO
只要“真”為真,此迴圈就會列印“我不會停止”。因此,它被稱為無限迴圈,因為結束條件永遠不會滿足。這是計算機科學課程中研究的第一個 _**控制缺陷**_ 示例。根據定義,每次您指定一個同義反復作為要檢查的條件時,您都會陷入無限迴圈。檢測到這個和其他控制缺陷和錯誤並不總是很明顯——事實上,這是一個涉及使用稱為 **偵錯程式** 的特定軟體的過程。
在本節中,我們將學習如何使用 FOR 迴圈列印乘法表,如何巢狀迴圈,如何格式化控制檯螢幕上的輸出以及縮排程式碼以提高可讀性的重要性。
FOR...NEXT 結構(計數控制迴圈)在我們知道希望迴圈體執行多少次時很有用。我們現在將使用它來列印並修改乘法表。
CLEAR
FOR I := 1 TO 8
FOR J := 1 TO 8
?? I*J
NEXT
NEXT
(請注意,在此示例中,我們在另一個迴圈的主體內部有一個迴圈:這被稱為巢狀迴圈)。好吧,這可以工作,但它的輸出不太好……此外,我們習慣上將乘法表列印到 10,因為我們使用的是十進位制計數系統……讓我們再試一次!
CLEAR
FOR I := 1 TO 10
FOR J := 1 TO 10
@i,j*4 SAY I*J PICTURE "999"
NEXT
NEXT
這樣我們就可以更漂亮地列印它……順便說一句,在巢狀迴圈和分支語句時,為了使程式更易於閱讀,對縮排進行排列非常重要……在綠洲中,有 “原始碼美化器”,例如 dst314.zip。 Harbour 為我們提供了 hbformat 工具,https://vivaclipper.wordpress.com/tag/hbformat/.
PICTURE "999" 是指定我們想要輸出格式的指令——正如我們指定了三個數字,輸出中的數字將像它們是三位數一樣列印,因此在我們的乘法表中對齊它們。
作為巢狀迴圈的第二個示例,以下是我編寫過的最令人討厭的程式之一。它要求使用者提供意見,然後始終回覆其相反。它甚至在使用者說他還有話要告訴他的時候退出,並在使用者說他沒有什麼要補充的時候繼續要求輸入。由於程式很難回答使用者斷言的相反,因此此程式只是使用 SubStr 函式將輸入字串反轉打印出來(SubStr(<cString>, <nStart>, [<nCount>]) --> cSubstring - 從字元字串 cString 中提取子字串,在索引 nStart 處,長度為 nCount)。
使用者輸入透過 ACCEPT 命令(COBOL 的回憶)獲得,控制流程使用 DO WHILE 迴圈(來自 FORTRAN)和 FOR ... TO ... STEP 迴圈(來自 BASIC)。
LOCAL cContinue := "N"
DO WHILE cContinue = "N"
ACCEPT "What's your opinion? " TO cOpinion
?""
?? "NO!! According to me, on the contrary: "
FOR i := Len( cOpinion ) TO 1 STEP -1
??SubStr( cOpinion, i, 1 )
NEXT
WAIT "Tell me, do you have any other opinions to discuss with me? (Y/N) " TO cContinue
cContinue := Upper( cContinue )
IF cContinue = "N"
? "Ok, then. "
ELSE
? "In this case let me go."
ENDIF
ENDDO
我們如何處理如此尷尬的客戶?只需嘗試輸入 " _remmargorp revelc yrev a era uoy_ ",解決方案就清晰了。在義大利,有一種表達方式來稱呼總是與他的對話者唱反調的人:他被稱為“bastian contrario”。他們的工作方式就像這個程式一樣。
考慮這段簡短的程式碼
i = 2
myMacro = "i + 10"
i = &myMacro && will execute "i+10"
? i && will print "12"
& 是一個運算子,它將評估(執行時編譯)表示式,並允許在字串中進行文字替換(請參閱 https://harbour.github.io/ng/c53g01c/ng110bc2.html)。讓我們使用它來製作一個簡單的計算器。
ACCEPT "Please enter the first number: " TO cNumber1
ACCEPT "Please enter the second number: " TO cNumber2
WAIT "Enter operation: " TO cOperation && we get a single character for the operation
? Number1+cOperation+Number2+"=" && we print the operation and an equal sign
? &(Number1+cOperation+Number2) && and the result on the following line, evaluating the string as it appeared on the line above
這是一個與該程式互動的示例
Please enter the first number: 12
Please enter the second number: 23.5
Enter operation: -
12-23.5=
-11.5
陣列是資料的有序分組,其元素由稱為索引的數字標識。在許多程式語言中,索引(或“索引”)從 0 開始,但在 xBase 中,它們從 1 開始。由於在數學中,索引寫在變數旁邊,略低於該行,因此有時它們被稱為 _下標_,因此,陣列有時被稱為 _帶下標的變數_。
考慮這個第一個陣列示例。我們建立了兩個陣列,一個包含一年中月份的名稱,另一個包含它們的天數,然後我們以有序的方式列印它們。使用關聯陣列可以以更優雅的方式獲得相同的結果。
&& C:\hb32\bin\hbmk2 -quiet -oc:\hbcode\months.exe months.prg -run
DECLARE months [12]
? LEN (months) && the function LEN() shows how many items an array has
&& We shall now load some data into our arrays
PRIVATE month_names: = {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
PRIVATE months: = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} && for simplicity we do not consider leap years
&& Let us make some output (using a FOR loop - see below)
FOR I: = 1 TO LEN (months)
? month_names [i], "is", months [i], "days long."
NEXT
在下一個示例中,我們將展示 xBase 對 _動態陣列_ 的高階支援(在像 C 這樣的低階語言中實現它們需要指標算術和記憶體管理函式)。
注意: 此示例將在本筆記中多次出現。 通常當我們介紹語言的新功能時,我們會編寫一個新程式,以不同的方式執行與該程式相同的操作。 這是我能給你的最好的教學建議之一。 由於我沒有提供練習,請考慮使用新的語言功能重新編寫某些程式的不同方法,例如,給你一個想法:使用程式碼塊在螢幕上顯示乘法表,這個程式有多難重寫呢?
&& compile with ''hbmk2 averaging.prg -lhbmisc''
PROCEDURE MAIN
LOCAL n, sum := 0, average && let's initialize the variables
aNumbers := {} && and an array without elements
? "enter a list of integer numbers, and a non-number when you are finished"
WHILE .T. && we begin an endless loop
ACCEPT "next element: " TO item
IF IsDec( item ) && the function IsDec() from the libmisc library checks if we have entered a number
AAdd ( aNumbers, item ) && if we did, then we add the number as a new element to the array
sum := sum + &item && (and we update the sum of the elements in our array)...
ELSE
EXIT && ...if we did not, we quit this loop
ENDIF
ENDDO
average := sum / Len( aNumbers ) && we compute the average of the values in the array
?
? "to average the array you must correct each element by adding to it the corresponding value below"
?
FOR n := 1 to LEN ( aNumbers )
?? AVERAGE - aNumbers[n]
NEXT
RETURN
以下是 hbmisc 中的函式列表:https://harbour.github.io/doc/hbmisc.html。
上面的程式並不十分令人滿意:由於使用了 IsDec() 函式,它只能處理正整數。 如果我們想讓它接受 -8 和 2.78 這樣的數字怎麼辦? 我們可以想到三種解決方案:編寫一個我們自己的函式來檢查字串是否代表一個數字(IsDec() 的改進版本),使用正則表示式測試輸入,使用 xBase 函式進行錯誤處理。 我們將在本教程的下一部分中使用最後一種技術。
FOR EACH n in aNumbers && FOR EACH is a more modern control flow statement Harbour syntax
?? AVERAGE - &n
NEXT
除了一個將元素新增到陣列的函式外,我們還有一個刪除它們的函式
aFruitSalad:={"peaches", "plums"}
AAdd ( aFruitSalad, "onions" ) && we add onions
ADel(aFruitSalad, 3) && on second thought, onions don't mix well: we remove them
local f; for each f in aFruitSalad; ?? f," " ; next && we print our ingredients
&& => peaches plums NIL
Harbour provides a function, hb_ADel, which accepts a third Boolean element and (if it's .T.) shrinks the array.
多維陣列是透過將陣列巢狀在其他陣列中建立的。
到目前為止展示的所有程式,如果使用者輸入了意外的輸入(簡單來說,它們崩潰),都將失敗並顯示執行時錯誤。 被稱為“健壯”的程式不會。 檢查可能發生錯誤的條件並採取措施防止崩潰是一項通常用 if-then-else 完成的任務。
BEGIN SEQUENCE ... END[SEQUENCE] 是一種控制結構,它執行一些程式碼,如果程式碼產生了錯誤,可以選擇執行一些語句。 在我們的示例中,如果將使用者輸入轉換為數字產生了錯誤,我們將呼叫 EXIT 退出 while 迴圈並繼續我們的程式。
PROCEDURE MAIN
LOCAL n, sum := 0, average && let's initialize the variables
local bError := errorblock ( { |oError| break ( oError ) } )
aNumbers := {} && and an array without elements
? "enter a list of integer numbers, and a non-number when you are finished"
WHILE .T. && we begin an endless loop
BEGIN SEQUENCE
ACCEPT "next element: " TO item
AAdd ( aNumbers, &item ) && if we did, then we add the number as a new element to the array
sum := sum + &item && (and we update the sum of the elements in our array)...
RECOVER
EXIT && ...if we did not, we quit this loop
END SEQUENCE
ENDDO
average := sum / Len( aNumbers ) && we compute the average of the values in the array
?
? "to average the array you must correct each element by adding to it the corresponding value below"
?
FOR n := 1 to Len ( aNumbers )
?? average - aNumbers[n]
NEXT
RETURN
Clipper 5 (C5) 提供了四類
Error Provides objects with information about runtime errors Get Provides objects for editing variables and database fields TBrowse Provides objects for browsing table-oriented data TBColumn Provides the column objects TBrowse objects
Clipper 5.3 Norton Guide 列出了這些類
Error class Provides objects with information about runtime errors CheckBox class Provides objects for creating checkboxes Get class Provides objects for editing variables and database fields ListBox class Provides objects for creating list boxes MenuItem class Provides objects for creating menu items PopUpMenu class Provides objects for creating pop-up menus PushButton class Provides objects for creating push buttons RadioButto class Provides objects for creating radio buttons RadioGroup class Provides objects for creating radio button groups Scrollbar class Provides objects for creating scroll bars TBColumn class Provides objects for browsing table-oriented data TBrowse class Provides the column objects TBrowse objects TopBarMenu class Provides objects for creating top menu bars
函式是一段程式碼,每次呼叫它都會返回一個結果。 讓我們考慮一個將攝氏度轉換為華氏度的函式(libct 提供了相同的函式,但我們出於教育目的重新編寫了它)
FUNCTION cels2f( tempCelsius )
tempFahrenheit := (tempCelsius*1.8 + 32)
RETURN tempFahrenheit
FUNCTION MAIN()
? cels2f(100)
? cels2f(0)
但請看以下內容
/**
* by choosing better variable names this function becomes self-explanatory
*/
function convert_Celsius_to_Fahrenheit(Celsius_temperature)
return Celsius_temperature * 1.8 + 32
FUNCTION MAIN()
? convert_Celsius_to_Fahrenheit(100)
? convert_Celsius_to_Fahrenheit(0)
考慮以下 HB_Random() 函式的使用示例
PROCEDURE Main
// Random number between 0.01 and 0.99
? HB_Random()
// Random number between 0.01 and 9.99
? HB_Random(10)
// Random number between 8.01 and 9.99
? HB_Random(8,10)
// Random integer number between -100 and 100
? HB_RandomInt(-100,100)
RETURN
一個好的函式應該充當一個“黑盒子”,也就是說,你可以使用它而不必擔心它是如何工作的。 通常在這個時候給出的一個例子是駕駛汽車:你可以使用它而不必擔心發動機是如何工作的(你認識有人在買新車之前會檢查發動機的活塞嗎?)。 其他人設計了發動機。 但是可能存在問題:如果我們將攝氏度轉換為華氏度的函式更改為如下所示
FUNCTION cels2f( tempCelsius )
tempFahrenheit := (tempCelsius*1.8 + 32)
number := number + 1
RETURN tempFahrenheit
FUNCTION MAIN()
number := 0
? cels2f( 100 )
? cels2f( 0 )
? number
我們將得到一個副作用。 該程式的輸出是
212.0
32.0
2
也就是說,我們的新函式不僅返回了一個值,還將變數number增加了 1。
Clipper Norton Guides 上寫著,“CA-Clipper 中的過程與使用者定義的函式相同,區別在於它始終返回 NIL”(http://www.oohg.org/cl53/ng10a778.html),因此過程實際上只是一種依賴於這些副作用來完成工作的函式。
函式的效用在於它們提高了程式的可讀性和可維護性。 將程式拆分成函式是一種良好的設計方法,它允許將一個複雜的問題分解成更小、更簡單的問題(自頂向下)。
Harbour 有四種不同型別的子程式/子程式
- FUNCTION 和
- PROCEDURE(它是一個沒有返回值的 FUNCTION),
- method(它沒有關聯的關鍵字,就像每種面向物件的程式語言一樣)和
- code block。
主函式是 C 或 C++ 中編寫的程式的指定起點(Java 使用一個靜態 main 方法)。 由於過程是一個返回 NIL 的函式,因此我們可以在程式中使用 procedure main 而不是 function main。 使用 Harbour,它不再是必需的。
變數有名稱、型別、作用域和生命週期(http://www.dbase.com/Knowledgebase/dbulletin/bu05vari.htm),可以顯式宣告為(下面的列表從w:Harbour (software)中逐字複製)
- LOCAL:僅在宣告它的例程中可見。 值在例程退出時丟失。
- STATIC:僅在宣告它的例程中可見。 值在例程的後續呼叫中保留。 如果在定義任何過程/函式/方法之前聲明瞭一個 STATIC 變數,它將具有 MODULE 作用域,並在同一個原始檔中定義的任何例程中可見,它將保持其生命週期,直到應用程式生命週期結束。
- PRIVATE:在宣告它的例程及其所有呼叫的例程中可見。
- PUBLIC:對同一個應用程式中的所有例程可見。
在我們使用函式和過程之前,它們沒有意義。
/* Work on the following.
* hbformat was run on this piece of code
* need to provide comments, nest more functions and procedures to help figuring what goes on with scope modifiers
*/
STATIC x := 9
? x
A()
? x
B()
? x
C()
? x
D()
? x
E()
? x
PROCEDURE A
LOCAL x := 10
? "x from A=", x
RETURN
PROCEDURE B
PRIVATE x := 5
? "x from B=", x
RETURN
PROCEDURE C
PUBLIC x := -1
? "x from C=", x
RETURN
PROCEDURE D
? "x from D before updating value=", x
x := 12
? "x from D=", x
RETURN
PROCEDURE E
? "x from E=", x
RETURN
執行它時,我們會得到一個奇怪的輸出
9
x from A= 10
9
x from B= 5
9
x from C= -1
9
x from D before updating value= -1
x from D= 12
9
x from E= 12
9
該程式設定了一個變數x和五個不同的過程,A、B、C、D、E。 前三個過程在自身內部定義了一個變數x,為它分配一個值並列印它。 第四個函式將一個新值分配給一個名為x的變數,而沒有宣告它。 第五個函式顯示了某個x變數的值,它恰好是第四個x變數的值。 這裡要記住的主要事實是,兩個變數並不相同,即使它們的名稱相同,只要它們有不同的作用域,這個特性被稱為遮蔽。
x := 9
? x
A()
? x
B()
? x
C()
? x
D()
? x
E()
? x
PROCEDURE A
LOCAL x := 10
? "x from A=", x
RETURN
PROCEDURE B
PRIVATE x := 5
? "x from B=", x
RETURN
PROCEDURE C
PUBLIC x := -1
? "x from C=", x
RETURN
PROCEDURE D
? "x from D before updating value=", x
x := 12
? "x from D=", x
RETURN
PROCEDURE E
? "x from E=", x
RETURN
9
x from A= 10
9
x from B= 5
9
x from C= -1
-1
x from D before updating value= -1
x from D= 12
12
x from E= 12
12
由於靜態變數似乎需要更多資訊(它們的工作原理類似於 C 的static 變數),以下是一個示例
// adapted from "An example of static local variable in C" (from Wikipedia Static_variable)
FUNCTION func()
static x := 0
/* x is initialized only once, the first time func() is called */
x := x+1
? x
RETURN
FUNCTION main()
func() // calls func() a first time: it prints 1
func() // value 1 was preserved; this time it prints 2
func() // prints 3
func() // prints 4
func() // prints 5
請參閱 tutorialspoint.com 中的 pascal_variable_scope 和http://aelinik.free.fr/c/ch14.htm
變數作用域是面向物件程式設計的預兆,因為它預示著某些封裝概念。
如果你查看了函式列表,你會注意到 xBases 沒有三角函式。 並不是說它們通常缺少它們:這些語言的目標是資料庫應用程式,例如“會計系統和航空公司預訂系統”(參見w:Database application。)在其中三角函式並不十分重要。 但是現在我們問自己如何計算 Ludolphine 數 π 的近似值(有關此“搜尋”的更多資訊,請訪問http://www.mathpages.com/home/kmath457.htm),我們可以使用 CT 庫(參見http://vouch.info/harbour/index.html?hbct.htm),或匯入 C 標準庫函式 atan。
&& compile with ''hbmk2 computepi.prg -lhbct''
PROCEDURE MAIN
SET DECIMAL TO 14
? 4 * ATAN (1)
我們從 Alexander Kresin 的Harbour for beginners 獲得靈感(http://www.kresin.ru/en/hrbfaq_3.html)。
#pragma BEGINDUMP
#include <extend.h>
#include <math.h>
HB_FUNC( ATAN )
{
double x = hb_parnd(1);
hb_retnd( atan( x ) );
};
#pragma ENDDUMP
SET DECIMAL TO 14
? 4 * ATAN (1)
需要注意的是,extend.h 檔案(*CA-Cl*pper Extend System 的相容標頭檔案)包含以下警告
/* DON'T USE THIS FILE FOR NEW HARBOUR C CODE */ /* This file is provided to support some level of */ /* Harbour compatibility for old Clipper C extension code */
事實上,這個例子僅僅是為了說明開源交叉編譯器(這裡指的是 Harbour)是如何使用 C 庫函式進行擴充套件的,這給了它們很大的靈活性。並不是說計算 π 本身對一般的 xBase 程式設計師來說有什麼用(順便說一下,CT 庫包含一個直接計算 π 的函式)。我們不會關心如何計算它,而是會轉向一些更常規的東西。
FizzBuzz 是一個在面試中經常被問到的一個小問題。在 https://micheleriva.medium.com/about-coding-the-fizzbuzz-interview-question-9bcd08d9dfe5 頁面上可以找到很好的討論。和以往一樣,即使像這樣簡單的問題,也有很多解決方法,可以這樣描述:
Write a program that prints the numbers from 1 to 100. But for multiples of three print “Fizz” instead of the number and for the multiples of five print “Buzz”. For numbers which are multiples of both three and five print “FizzBuzz”.
首先,要儘可能多地思考解決問題的方法。另外,你可能會被要求在沒有電腦的情況下證明你的解決方案有效:這樣做也是閱讀他人編寫的程式,甚至檢查你自己編寫的程式的最佳方法之一。你可以拿一張紙和一支筆,檢查程式指令如何改變變數,以及它們產生什麼輸入,方法是在表格中排列變數的內容,並像電腦一樣進行操作。我聽說這被稱為 w:Trace table、追蹤演算法、手工追蹤、幹執行。目前我推薦你看一個 YouTube 教程,它應該能讓你瞭解這個東西是如何工作的:https://www.youtube.com/watch?v=UbANyxE7pGE (Trace tables tutorial GCSE Computer Science)。
以以下 FizzBuzz 的簡單解決方案為例
FOR I = 1 TO 100
FLAG = 0
IF I % 5 = 0
FLAG = 1
?? "Fizz"
ENDIF
IF I % 3 = 0
FLAG = 1
?? "Buzz"
ENDIF
IF FLAG = 0
?? I
ENDIF
?? ", "
NEXT
這個例子介紹了 標誌變數,它是一個用來檢查程式是否執行了特定指令或指令集的變數。
對資料進行排序是計算機科學中的一個重要應用。事實上,在古代(指的是 50 年代),計算機的一個重要部件是一個叫做 卡片分類機 的硬體,它被用來對穿孔卡片進行排序,參見 http://www.columbia.edu/cu/computinghistory/sorter.html 上的一個例子,但是 Hollerith 在 19 世紀 80 年代建造的統計機器已經有了自己的 分類箱,而法語中“計算機”一詞 ordinateur,甚至可以字面翻譯為 分類器。
讓我們從一段程式碼開始
// we'll declare and load random numbers from 1 to 100 in an array
DECLARE arrayToSort [12]
FOR I = 1 TO 12
arrayToSort [I] = Int( HB_Random( 1,101 ) )
NEXT
// we review our array
FOR I = 1 TO 12
?? arrayToSort [I]
NEXT
// we'll do Exchange Sort as described in http://www.ee.ryerson.ca/~courses/coe428/sorting/exchangesort.html
// where there is also a nice animation showing how it works! :)
for i := 2 TO Len(arrayToSort)
for j := Len(arrayToSort) TO i step -1
IF arrayToSort [j - 1] > arrayToSort [j]
TEMP := arrayToSort [j - 1]
arrayToSort [j - 1] := arrayToSort [j]
arrayToSort [j] := TEMP
ENDIF
NEXT
NEXT
?
? "Sorted array:"
?
// we look at our sorted array
FOR I = 1 TO 12
?? arrayToSort [I]
NEXT
從上面的版本中,我們可以(也應該)將排序程式分離到一個過程中,這樣
PROCEDURE ExchangeSort(array)
// we'll do Exchange Sort as described in http://www.ee.ryerson.ca/~courses/coe428/sorting/exchangesort.html
// where there is also a nice animation showing how it works! :)
for i := 2 TO Len(array)
for j := Len(array) TO i step -1
IF array [j - 1] > array [j]
TEMP := array [j - 1]
array [j - 1] := array [j]
array [j] := TEMP
ENDIF
NEXT
NEXT
RETURN
FUNCTION MAIN
// we'll declare and load random numbers from 1 to 100 in an array
DECLARE arrayToSort [12]
FOR I = 1 TO 12
arrayToSort [I] = Int( HB_Random( 1,101 ) )
NEXT
// we review our array
FOR I = 1 TO 12
?? arrayToSort [I]
NEXT
ExchangeSort(arrayToSort) // we call the procedure and pass to it our array called arrayToSort
?
? "Sorted array:"
?
// we show the array is sorted
FOR I = 1 TO 12
?? arrayToSort [I]
NEXT
通常,第一個介紹的排序演算法(即使在我的高中課本中)是氣泡排序,這是一個糟糕的選擇。這裡我們使用交換排序,它同樣糟糕,但至少我們做了一點變化。
當一個函式呼叫自身時,我們就稱之為遞迴。這個定義真的就這麼簡單。
這樣說的話,它似乎不是一個好主意,因為遞迴函式似乎會將程式陷入無限迴圈。實際上,遞迴函式不僅僅是呼叫自身,它還包含一個停止執行的條件,並提供一些實際結果。
第一個例子是階乘函式,我們從字典中獲取它的定義 (https://www.dictionary.com/browse/factorial):“ 數學。給定正整數乘以所有較小正整數的乘積:四階乘 (4!) 的數量 = 4 ⋅ 3 ⋅ 2 ⋅ 1 = 24。符號:n!,其中 n 是給定的整數”。
FUNCTION main
? factorial(10)
RETURN 0
FUNCTION factorial(n)
IF n = 0
RETURN 1
ELSE
RETURN n * factorial(n - 1)
ENDIF
RETURN -1
https://github.com/vszakats/harbour-core/blob/master/doc/codebloc.txt
網路上的網站說 Clipper 5 文件將它們稱為 無名可分配函式。
讓我們回到我們的攝氏度到華氏度的溫度轉換函式
FUNCTION cels2f( tempCelsius )
tempFahrenheit := (tempCelsius*1.8 + 32)
RETURN tempFahrenheit
並將其重寫為程式碼塊
Cels2F := { | celsius | Fahrenheit := (Celsius * 1.8 + 32) } // our first codeblock
? Eval(Cels2F,100)
? Eval(Cels2F,0)
在第一行,我們將一個程式碼塊分配給一個名為 Cels2F 的變數。程式碼塊包含在花括號中。首先是一個要傳遞給程式碼塊的變數,放在豎線 (|) 之間。多個變數應以逗號分隔。然後我們可以編寫指令(多條指令應以逗號分隔)。
之後,我們兩次使用 Eval 函式,分別傳遞兩個引數:我們為程式碼塊分配的名稱和要傳遞給它的引數。除了 Eval,其他函式也可以評估程式碼塊:AEval() 和 dbEval()。Ascan() 和 Asort() 可以傳遞程式碼塊來改變它們的執行行為。
由於程式碼塊不能包含命令,因此我們不能在它們中使用 ? 命令,而必須使用相應的 QOut() 函式。
假設我們想編寫一個 Unix cat 命令的克隆,這意味著我們將有一個程式,我們將向它傳遞一個命令列引數,即檔名,並輸出檔案的內容。
PROCEDURE MAIN(file)
? MEMOREAD(file)
RETURN
就是這樣!事實上,它相當有限,更有可能是一個 DOS type 命令(甚至 CP/M type 命令)的克隆!程式該檔案的程式 MAIN 接收一個引數,並從命令列獲取它。然後將它傳遞給 MEMOREAD 函式(描述:將文字檔案的內容作為字元字串返回),並將結果傳送到控制檯輸出。
可以使用低階函式訪問檔案,這些函式包括:FOPEN()、FCREATE()、FWRITE()、FREAD()、FREASDSTR()、FERASE()、FERROR()、FSEEK()、FCLOSE() 和 FRENAME()。
我們應該如何編寫一個使用這些函式的示例?這裡有一個想法
- 使用 FCREATE() 建立一個新檔案,並使用 FWRITE() 向其中寫入文字。
- 使用 FSEEK() 將指標定位到檔案末尾。
- 使用 FREAD() 讀取檔案內容。
- 使用 FCLOSE() 關閉檔案。
- 使用 FRENAME() 重新命名檔案。
- 最後,使用 FERASE() 刪除重新命名的檔案。
但是,Harbour 提供了類似於 DBF 檔案操作文字檔案的函式 (https://vivaclipper.wordpress.com/tag/memoread/)。

