在上一個例子中,你應該考慮獨立出 String2Matrix() 與 Matrix2String() 兩個 supporting function, 方便你轉換多個 EditBox. 讓我們來仔細看一下 String2Matrix(). 它目前假設我們所輸入的矩陣中,每個 entry 都只有一位數字。現實狀況當然可能會有兩位數、三位數或更多位數。 (Entry 之間的空白,也可能有多個。) 因此在這個練習中,我們將修改 String2Matrix() 這個 function, 來處理這些狀況。
有關從一個字串中,分析 (parse) 出我們所需要的欄位和資料,理論上需要一些較高階的背景知識。 在大三上學期的 System Programming 中,各位將學到如何處理固定欄位的格式 (如:規定每一列的第 1~3字元代表 a[0][0], 第4字元空白, 第5~7字元代表a[0][1], 第8字元空白, 第9~11字元代表 a[0][2])。 在大三下學期的 Compiler, 則會學到自由格式(如本次習題中,數字長不固定,連續空白數亦不定)的處理方式。 Compiler 的課本中,會使用到所謂 Finite State Automata (FSA) 的技巧來解決 parsing 的問題,我們今天先來試著在不需 FSA 的情況下,處理一個簡單的矩陣。
在上一個習題中,由於我們簡單地假設所有 entry 都只是小於10 的非負整數,所以只要把該字元的 ASCII code 減去 48 ('0'所對應的 ASCII code),就得到我們所需要的數字。 但如果是字串 "123" 呢? 你可以使用課本 P.329 FIGURE 6-6 的技巧,將百位數、十位數、個位數分別求出後相加; 或者,你也可以利用 atoi() 這個現成的式,幫你把一個字串轉成整數。 如果你的字串是 Unicode,記得要先用 CT2A() 轉成 ASCII code 再傳給 atoi().
從一個字串中取出一段子字串 (substring),我們使用的是 CString 的一個 member function, 叫 Mid(). 它有兩個參數: nFirst 和 nCount, 會從 index (從0開始算起) 為 nFirst 的位置起,取出長度 nCount 的 substring. 我們接下來的重點,就要仔細算一下,該如何告訴電腦, 分析一個字串的規則,好從中切出對應矩陣 entry 的那段 substring.
當在字串中掃描到一個 '0'~'9' 之間的字元時,我們知道這是一個阿拉伯數字。但該做什麼處置,和這個數字所在的位置有關。 例如,在 "142857" 中,如果掃描到的是第一個字元 '1', 你應該要記下來,這就是 nFirst 的位置,待會兒 Mid() 就要從這兒開始把 substring 抓出來。 至於再往下掃描到 '4', '2', '8', '5', '7' 時,則無須採取任何動作。 當你再進一步掃描到 '7' 後面的那個空白時,就知道數字已告一段落,可以切出來做轉換了。假設那個空白字元在第 i 個位置,那麼所要切出的字串 "142857", 長度就等於 i - nFirst. 至於如果有連續多個空白時,只有第一個空白要做事,後面的空白則跳過即可。
因此若把 String2Matrix() 中的判斷規則改為
if ( (c = str.GetAt(i)) == '\n' )
{
++nRow;
nColumn = 0;
}
else if ( isdigit(c) )
{
if (!previous_char_is_digit)
{
nFirst = i;
previous_char_is_digit = true;
}
}
else // is SPACE or CR
if (previous_char_is_digit)
{
entry = atoi( CT2A( str.Mid(nFirst, i- nFirst) ) );
a[nRow][nColumn++] = entry;
previous_char_is_digit = false;
}
就可以得到下頭這樣的效果:
Note: 如果你發現,最後一個 entry 沒有 parse 出來,那是因為它後頭沒有空白,於是用 Mid() 切出 substring 的動作沒有被驅動。 你應該如何處理這個例外狀況呢?