在開發視窗應用程式時,處理多行文字是常見需求,而 Delphi 中的 TMemo 元件正是為此設計的。與 TEdit 相比,TMemo 允許使用者輸入和顯示多行文字,這讓它成為處理大量文字內容的理想選擇。在我多年的 Delphi 開發經驗中,TMemo 一直是我處理文字資料的得力助手。
TMemo 的基本特性
TMemo 繼承了 TEdit 的許多特性,如 Font 和 Readonly 屬性的運作方式與 TEdit 完全一致。然而,TMemo 還有其獨特之處—它的核心在於處理多行文字的能力。雖然可以透過 Text 屬性存取 TMemo 的全部文字,但此屬性僅在應用程式執行時可用。
Lines 屬性:多行文字的核心
TMemo 控制元件的文字內容儲存在 Lines 屬性中,這是一組編號的行集合(編號從 0 開始)—第一行的索引為 0,第二行索引為 1,以此類別推。這種設計讓我們能夠輕鬆存取和修改特定行的內容。
var
s: string;
begin
s := memEx.Lines[2]; // 將第3行的值賦給s變數
memEx.Lines[3] := 'Hi'; // 字串常數'Hi'被賦給第4行
// ...
end;
這段程式碼示範瞭如何存取 TMemo 中的特定行。memEx.Lines[2]
讀取索引為 2 的行(實際上是第三行,因為索引從 0 開始),並將其值賦給變數 s。接著,memEx.Lines[3] := 'Hi'
將字串 ‘Hi’ 寫入索引為 3 的行(即第四行)。這種方式讓開發者能精確控制 TMemo 中每一行的內容。
但需要注意的是,程式只能以這種方式存取 TMemo 控制元件中已存在的行。這些行必須透過物件檢視器建立、在程式執行期間從鍵盤輸入,或由相應方法建立。嘗試存取不存在的行會導致執行時錯誤。
使用物件檢視器填充 TMemo
我們可以使用 Lines 屬性在設計時填充 TMemo。在物件檢視器中,點選 Lines 屬性旁的省略號按鈕,會開啟 Lines 編輯器視窗。在此視窗中,您可以輸入文字行,每行結束時按 Enter。完成後,按下 OK 按鈕確認。
Count 屬性:行數計算
Count 屬性回傳 TMemo 中的總行數,這在處理文字內容時非常有用:
k := memEx.Lines.Count;
這行程式碼取得 memEx 中的總行數並將其賦給變數 k。Count 是唯讀屬性,您無法直接修改它。這個屬性在需要迭代處理 TMemo 中的所有行時特別有用,例如在迴圈中使用。
WordWrap 屬性:文字換行
WordWrap 屬性決定文字是否在右邊界處自動換行。當設為 true 時,編輯控制項會在右邊界處自動換行,使文字適合客戶區域。這對於處理長文字行特別有用,可以避免水平捲動。
MaxLength 屬性:限制文字長度
MaxLength 定義了可以輸入到編輯控制項中的最大字元數。如果 MaxLength 為 0,TMemo 不會限制輸入文字的長度。我發現在需要限制使用者輸入的情況下,這個屬性非常實用。
ScrollBars 屬性:捲動列
ScrollBars 屬性決定 TMemo 控制項是否有捲動列。值得注意的是,如果開啟垂直捲動列或同時開啟兩種捲動列,TMemo 會忽略 MaxLength 屬性。
Alignment 屬性:文字對齊
Alignment 屬性改變文字在編輯控制項中的格式化方式,可以設定為左對齊、居中或右對齊。
TMemo 的關鍵方法
在使用 TMemo 的過程中,我發現其提供的方法使文字操作變得極其靈活。以下是一些最常用與實用的方法:
Delete 方法:刪除行
Delete 方法刪除指定索引的行。其他行會自動上移並重新編號:
memEx.Lines.Delete(0); // 刪除第一行(索引為0的行)
這個方法從 TMemo 中移除指定索引的行。在上面的例子中,memEx.Lines.Delete(0)
刪除了索引為 0 的行(即第一行)。刪除後,原來的第二行會變成第一行,以此類別推。所有行的索引都會相應調整。
以下是一個實際應用的例子,只保留奇數行:
var
k, i: Integer;
begin
k := memEx.Lines.Count;
for i := k-1 downto 1 do
if (i mod 2) <> 0 then
memEx.Lines.Delete(i);
end;
這段程式碼從 TMemo 的最後一行開始,向前迭代每一行。對於每一行,它檢查索引是否為偶數(使用 mod 運算元檢查除以 2 的餘數是否不等於 0)。如果是偶數行(索引為奇數),則刪除該行。注意迴圈是從後向前執行的,這是為了避免在刪除行時影響尚未處理行的索引。
Exchange 方法:交換行位置
Exchange 方法交換兩個指定索引行的位置:
memEx.Lines.Exchange(0, 1); // 交換第一行和第二行
這個方法交換了兩行的位置。在上面的例子中,索引為 0 的行(第一行)和索引為 1 的行(第二行)互換了位置。這在需要重新排序文字內容時非常有用,例如在實作簡單的排序演算法時。
Move 方法:移動行位置
Move 方法將一行移至新位置:
memEx.Lines.Move(1, 5);
這個方法將一行移動到新的位置。在上面的例子中,索引為 1 的行(第二行)被移動到索引為 5 的位置。在執行過程中,原本索引為 1 的行會從文字中移除,所有後續行都會上移一個位置,然後該行會被插入到新指定的位置(索引為 5)。
TMemo 實用練習
為了鞏固對 TMemo 的理解,我建議嘗試以下練習:
練習 1:保留特定行數
填充一個 TMemo。編寫一個程式,點選按鈕後,在 TMemo 中只保留:
- 如果行數超過四行,則只保留前四行。
- 如果行數少於四行,則只保留第一行。
練習 2:文字搜尋
填充一個 TMemo 和一個文字編輯框。編寫一個程式,點選按鈕後,輸出到標籤: a) TMemo 中與文字編輯框中相同的行的索引,或者顯示沒有此類別行的訊息。 b) 包含文字編輯框中輸入的符號組合的行的索引,或者顯示沒有此類別行的訊息。
練習 3:特定字詞行交換
填充含有多行文字的 TMemo。其中一行應包含 ‘cat’,另一行應包含 ‘dog’。編寫程式交換這兩行。
練習 4:最長行置頂
填充一個 TMemo。編寫程式找出最長的行並將其移至頂部。
練習 5:數字處理與分類別
填充一個 TMemo,每行一個數字。編寫一個程式,將所有偶數乘以 2 並移至第二個 TMemo 控制項。將所有奇數除以 2 並移至第三個 TMemo。
TMemo 的更多實用功能
除了上述功能外,TMemo 還提供了更多實用的屬性和方法,讓文書處理變得更加靈活。
Clear 方法:清空內容
Clear 方法完全清空 TMemo 的內容:
memEx.Lines.Clear;
這個方法移除 TMemo 中的所有文字行,將其重置為空白狀態。這在需要重新填充 TMemo 或重新開始處理時非常有用。
Append 和 Add 方法:增加新行
如果 TMemo 是空的或剛被 Clear 清空,可以透過呼叫 Append 方法填充它。Append 方法在 TMemo 的末尾增加一個新行:
memEx.Lines.Clear;
for i := 1 to 10 do
begin
memEx.Lines.Append(IntToStr(i));
end;
這段程式碼首先清空 memEx,然後使用迴圈從 1 到 10 逐一將數字(轉換為字串)增加到 TMemo 的末尾。每次呼叫 Append 都會建立一個新行。
Add 方法的功能與 Append 相同,但它還回傳新字串的索引:
b := memEx1.Lines.Add(edtEx1.Text);
lblEx1.Caption := IntToStr(b);
在這個例子中,文字編輯框 edtEx1 中的文字被增加到 TMemo memEx1 中,然後新行的索引被輸出到標籤 lblEx1。Add 方法不僅增加新行,還回傳該行的索引,這在需要知道新增加行位置的情況下很有用。
Insert 方法:插入行
Insert 方法在指定索引處插入一行。其他行會自動移動:
memEx.Lines.Insert(2, ''); // 在索引為2的位置增加空行
這個方法在指定的索引位置插入一個新行。在上面的例子中,一個空行被插入到索引為 2 的位置(即原來的第三行之前)。原來在該位置的行及之後的所有行都會下移一個位置。這種方法在需要在文字中間插入內容時非常有用。
TMemo 中的進階技術:行排序
在實際應用中,我經常需要對 TMemo 中的行進行排序。以下是一個實作行排序的演算法:
- 找出具有最小值的行的索引(字串是按字元逐個比較的,A<B<C..<Z等)
- 交換第一行和最小值行
- 重複這些步驟(N-1)次,其中N是TMemo中的行數
以下是實作這個演算法的程式碼:
CODE_BLOCK_10
這段程式碼實作了一個簡單的選擇排序演算法。函式 INLINE_CODE_3 找出從指定位置開始的最小行的索引。在主程式中,迴圈從 TMemo 的第一行開始,對每一行執行以下操作:找出從當前位置開始的最小行,然後將其與當前行交換。透過這種方式,TMemo 中的行會按字母順序排序。
實用練習(續)
讓我們繼續深入探索 TMemo 的應
常數:程式中的不變數
在程式設計中,常數是一種在執行過程中不會改變值的實體。就像變數一樣,常數也有型別,分為匿名常數和命名常數。
匿名常數
匿名常數是直接在程式中使用的值,沒有特定名稱:
CODE_BLOCK_11
命名常數
命名常數在程式的宣告部分定義,具有名稱和值。其語法為:
CODE_BLOCK_12
命名常數的命名規則與變數相同,型別會自動從值中推導。
CODE_BLOCK_13
命名常數的主要優點在於提高程式碼可讀性和維護性。比如定義 INLINE_CODE_4 後,在程式中使用 INLINE_CODE_5 比直接使用數字 INLINE_CODE_6 更能表達其含義。當需要修改這個值時,只需在一個地方更改,而不必尋找並修改所有出現的數字 INLINE_CODE_6。
自定義類別:提升程式碼可讀性與維護性
Delphi 允許開發者定義自己的類別,這在提升程式碼可讀性和維護性方面非常有用。
自定義類別的基本語法
CODE_BLOCK_14
例如:
CODE_BLOCK_15
上面的例子雖然 INLINE_CODE_8 和 INLINE_CODE_9 底層都是整數型別,但在語意上它們代表不同的度量單位。這種做法增強了程式碼的自我檔案能力,讀者一眼就能看出 INLINE_CODE_10 變數儲存的是重量(單位是公斤),而 INLINE_CODE_11 儲存的是尺寸(單位是釐米)。
自定義類別的另一大優點是便於維護。如果在開發過程中發現當前型別定義不足(例如,發現整數類別對於重量來說不夠精確,需要改用實數類別),只需在一處更改類別定義:
CODE_BLOCK_16
所有使用 INLINE_CODE_8 類別的變數都會自動採用新的類別,無需修改多處程式碼。
猜數字遊戲:實作範例
下面透過一個「猜數字」遊戲來應用我們學到的概念。
遊戲規則
- 電腦從指定範圍內選擇一個隨機數字
- 玩家嘗試猜這個數字
- 每次猜測後,程式會告知猜測結果:猜對了、猜大了或猜小了
程式設計
CODE_BLOCK_17
這個猜數字遊戲實作了以下功能:
- 使用者可以設定數字範圍的上下限
- 程式會在這個範圍內隨機選擇一個數字
- 使用者每次猜測後,程式會提供反饋(猜大了、猜小了或猜對了)
- 提供「偷看」功能,顯示電腦選擇的數字
程式中使用 INLINE_CODE_13 初始化隨機數生成器,這確保每次執行程式時生成的隨機數都不同。INLINE_CODE_14 函式則用來生成範圍內的隨機數。
單維靜態陣列:集合資料的基本工具
陣列是一種集合,包含相同類別的元素,每個元素都有一個唯一的索引或鍵。
陣列宣告語法
CODE_BLOCK_18
陣列宣告包括:
- 陣列名稱
- 關鍵字 INLINE_CODE_15
- 索引範圍,例如 INLINE_CODE_16、INLINE_CODE_17、INLINE_CODE_18
- 元素類別
例如:
CODE_BLOCK_19
這定義了一個包含 10 個實數元素的陣列,索引從 1 到 10。
陣列元素存取
透過索引可以存取陣列元素:
CODE_BLOCK_20
利用索引可以高效處理陣列操作,例如:
CODE_BLOCK_21
陣列作為引數
要將陣列作為程式引數,應先定義陣列類別:
CODE_BLOCK_22
陣列操作例項:填充與尋找最大值
下面是一個填充陣列並尋找最大元素的完整範例:
CODE_BLOCK_23
這個範例展示了陣列的基本操作:
- 設定值範圍
- 用隨機數填充陣列
- 將陣列輸出到視覺元件
- 尋找陣列中的最大值
程式中的 INLINE_CODE_19 程式使用 INLINE_CODE_20 函式生成指定範圍內的隨機數。INLINE_CODE_21 函式則透過一次遍歷找出陣列中的最大值。
陣列排序:選擇排序演算法
排序是將一組物件按特定屬性排列的過程。陣列排序是電腦科學中的重要領域,有許多演算法可用。這裡介紹一種簡單易懂的排序演算法:選擇排序。
選擇排序演算法
選擇排序的基本思路是:
- 找出 N 元素陣列中的最小元素,與第一個元素交換
- 在剩餘的 N-1 個元素中找出最小元素,與第二個元素交換
- 以此類別推,直到只剩一個元素(最大的元素)
選擇排序實作
CODE_BLOCK_24
選擇排序演算法的工作原理:
- 外層迴圈控制當前要放置最小元素的位置
- 內層迴圈在剩餘元素中尋找最小值
- 找到後,如果最小值不是當前位置,則進行交換
選擇排序的時間複雜度為 O(n²),不論輸入如何都需要執行相同數量的比較,因此對於大型陣列來說效率不高。但它的優點是實作簡單,與交換操作次數少,最多進行 n-1 次交換。
陣列操作練習:
I’ll create a comprehensive article on array sorting and selection sort, focusing on practical implementation and the StringGrid control in Pascal. Let me structure this into a complete technical guide.
陣列排序的藝術:選擇排序與實際應用
排序演算法是程式設計中的基本功,它不僅是面試的熱門話題,更是解決實際問題的重要工具。在多年的開發生涯中,我發現即使是最簡單的排序演算法,如果實作得當,也能在許多場景中發揮不可替代的作用。選擇排序雖然不是效能最佳的排序方法,但它的概念直觀與實作簡單,非常適合初學者理解排序的核心原理。
選擇排序的基本概念與實作
選擇排序的核心思想非常直觀:每次從未排序的元素中找出最小(或最大)的元素,將其放到已排序部分的末尾。這個過程不斷重複,直到所有元素都被排序。
以下是選擇排序的基本步驟:
- 在未排序列中找到最小元素
- 將其與未排序列的第一個元素交換
- 將未排序列的範圍縮小,不包含剛排好的元素
- 重複以上步驟,直到所有元素都已排序
讓我們看選擇排序在Pascal中的實作:
CODE_BLOCK_25
這段程式碼實作了選擇排序演算法的三個關鍵部分:
INLINE_CODE_22 程式是主排序函式,它遍歷陣列中的每個位置(除了最後一個,因為當其他所有元素都排好後,最後一個自然就在正確位置)。對於每個位置i,它找出從i到陣列末尾中的最小值,然後與位置i的元素交換。
INLINE_CODE_23 函式負責找出從指定位置__INLINE_CODE_24__到陣列末尾的最小值的索引。它初始化一個變數m為起始位置,然後遍歷後續元素,每當找到比當前最小值還小的元素時,就更新m的值。
INLINE_CODE_25 程式實作了兩個整數值的交換,使用了經典的臨時變數方法。
這種排序方法的時間複雜度為O(n²),對於小型資料集來說效能已經足夠,但對於大型資料集可能需要考慮更高效的排序演算法如快速排序或合併排序。
陣列操作的實用技巧
在實際開發中,陣列排序通常只是更大操作的一部分。以下是一些常見的陣列操作場景及其實作方法:
降序排列陣列
如果要將陣列按降序排列,只需修改__INLINE_CODE_23__函式中的比較運算元:
CODE_BLOCK_26
這段程式碼實作了陣列的降序排列。關鍵差異在於__INLINE_CODE_27__函式中使用了大於(>)比較運算元而非小於(<),這樣每次找到的是剩餘元素中的最大值而非最小值。其餘邏輯與升序排列相同,只是每次將最大值而非最小值移到已排序部分的末尾。
將正數移到陣列前端
這個操作不需要完整排序,只需要確保所有正數在前,非正數在後:
procedure PositivesFirst(var a: array_n_elements);
var
i, j, temp: integer;
begin
j := 1;
for i := 1 to n do
if a[i] > 0 then
begin
temp := a[i];
a[i] := a[j];
a[j] := temp;
j := j + 1;
end;
end;
這段程式碼實作了將陣列中所有正數移到前端的功能。它使用了一個指標j來跟蹤下一個正數應該放置的位置。當找到一個正數時,將其與位置j的元素交換,然後增加j。這種方法只需要一次遍歷就能完成操作,時間複雜度為O(n)。
值得注意的是,這種方法保留了正數之間的相對順序,也保留了非正數之間的相對順序,但不進行完整排序。
交換陣列前後半部分
當需要將陣列的前半部分與後半部分交換時,可以使用以下方法:
procedure SwapHalves(var a: array_n_elements);
var
i, half, temp: integer;
begin
half := n div 2;
for i := 1 to half do
begin
temp := a[i];
a[i] := a[i + half];
a[i + half] := temp;
end;
// 處理陣列長度為奇數的情況
if n mod 2 = 1 then
begin
// 將中間元素移到最後
temp := a[half + 1];
for i := half + 1 to n - 1 do
a[i] := a[i + 1];
a[n] := temp;
end;
end;
這段程式碼實作了陣列前後半部分的交換。首先計算出陣列的中點位置,然後遍歷前半部分,將每個元素與對應的後半部分元素交換。對於陣列長度為奇數的情況,需要特殊處理中間元素,這裡的實作是將中間元素移到陣列末尾。