標籤

2008年9月9日 星期二

分頁架構

http://www.csie.ntu.edu.tw/~wcchen/asm98/asm/proj/b85506061/chap2/paging.html

設定分頁功能在控制暫存器(control registers)中,有三個和分頁功能有關的旗標:PG(CR0 的 bit 31)、PSE(CR4 的 bit 4,在 Pentium 和以後的處理器才有)、和 PAE(CR4 的 bit 5,在 Pentium Pro 和 Pentium II 以後的處理器才有)。PG(paging)旗標設為 1 時,就會開啟分頁功能。PSE(page size extensions)旗標設為 1 時,才可以使用 4MB 的分頁大小(否則就只能使用 4KB 的分頁大小)。而 PAE(physical address extension)是 P6 家族新增的功能,可以支援到 64GB 的實體記憶體(本文中不說明這個功能)。
分頁目錄和分頁表分頁目錄和分頁表存放分頁的資訊(參考「記憶體管理」的「緒論」)。分頁目錄的基底位址是存放在 CR3(或稱 PDBR,page directory base register),存放的是實體位址。在開啟分頁功能之前,一定要先設定好這個暫存器的值。在開啟分頁功能之後,可以用 MOV 命令來改變 PDBR 的值,而在工作切換(task switch)時,也可能會載入新的 PDBR 值。也就是說,每一個工作(task)可以有自己的分頁目錄。在工作切換時,前一個工作的分頁目錄可能會被 swap 到硬碟中,而不再存在實體記憶體中。不過,在工作被切換回來之前,一定要使該工作的分頁目錄存放在實體記憶體中,而且在工作切換之前,分頁目錄都必須一直在實體記憶體中。 分頁目錄和分頁表的 entry 格式如下:

上表中的各個欄位的說明如下:
分頁表基底位址、分頁基底位址:存放分頁表/分頁的基底位址(以實體位址)。在 4KB 的分頁中,分頁表和分頁的位址都必須是 4KB 的倍數,所以用 20 bits 來表示基底位址的最左邊的(most-significant)20 bits。在 4MB 的分頁中,分頁的位址必須是 4MB 的倍數,因此用 10 bits 表示基底位址最左數的 10 bits。
P(present)旗標:表示這個分頁(或分頁表)目前是否存在記憶體中。若 P = 1,則表示這個分頁或分頁表在記憶體中,可以進行位址轉換。若 P = 0,則表示這個分頁不在記憶體中,若對這個分頁進行存取動作,會導致 page fault(#PF)例外。作業系統在將分頁 swap 到硬碟時,要把 P 設為 0;而在把分頁由硬碟中讀入時,則要把 P 設為 1。
R/W(read/write)旗標:當 R/W = 1 時,表示分頁可以寫入;當 R/W = 0 時,表示分頁只能讀取(read-only)。當 CR0 的 WP 旗標(第 16 bit)設為 1 時,所有的程式都不能寫入唯讀的分頁;但 WP 為 0 時,具有 supervisor 等級的程序就可以寫入唯讀的分頁。在指向分頁表的分頁目錄 entry 中,這個旗標對其指向的分頁表中的每個分頁都有效。
U/S(user/supervisor)旗標:當 U/S = 1 時,表示分頁是一個 user level 的分頁,而 U/S = 0 時,表示分頁是一個 supervisor level 的分頁。和 R/W 旗標一樣,在分頁目錄中,這個旗標對其指向的分頁表中的每個分頁都有效。
PWT(page-level write-through)旗標:在 PWT = 1 時,處理器會對這個分頁(或分頁表)做 write-through caching;而 PWT = 0 時,處理器會對這個分頁(或分頁表)做 write-back caching。在 CR0 的 CD(cache disable,第 30 bit)設為 1 時,這個旗標會被忽略。
PCD(page cache disable)旗標:在 PCD = 1 時,處理器不會對這個分頁(或分頁表)進行 cache;而 PCD = 0 時,則會進行 cache。例如,在分頁是對映一 I/O 記憶體時,就需要把 cache 關閉。在 CR0 的 CD 旗標設為 1 時,這個旗標會被忽略。
A(accessed)旗標:在 A = 0 時,若分頁被存取,則處理器會把它設為 1。在被設為 1 之後,處理器不會自動把它設為 0,只有軟體可以把它清為 0。因此,通常在一個分頁被載入實體記憶體時,作業系統會把 A 清為 0。記憶體管理程式或作業系統可以利用這個旗標來決定 swap 的方式。
D(dirty)旗標:在 D = 0 時,若對分頁進行寫入動作,則處理器會把它設為 1。在被設為 1 之後,只有軟體可以把它清為 0。通常作業系統在載入一個分頁之後,會把 D 清為 0。如此一來,要把這個分頁 swap 到硬碟中時,若 D 仍為 0,則表示分頁沒有被修改過,就不需要再寫回硬碟中了。這個旗標在「指向分頁表的分頁目錄 entry」中沒有作用。
PS(page size)旗標:這個旗標只在分頁目錄 entry 中有作用。當 PS = 0 時,表示這是一個 4KB 的分頁,因此 entry 是指向一個分頁表;當 PS = 1 時,表示這是一個 4MB 的分頁,因此 entry 是指向一個分頁。只有在 CR4 的 PSE(page size extensions,第 4 bit)為 1 時,才能存取 4MB 的分頁。
G(global)旗標:這是在 Pentium Pro 及之後的處理器才有的旗標。在本文中不討論。在 Pentium 和之前的處理器,這個旗標視為保留旗標,必須設為 0。
保留和可用部分:保留部分一律要設成 0,而可用部分則可以自己決定用途。如果 P 為 0,則整個 entry(除了 P 之外)都視為可用部分,可供作業系統存放相關的資訊(例如,可以用來存放分頁在硬碟 swap file 中的位置)。
Translation Lookaside Buffers(TLBs)到記憶體中查分頁目錄和分頁表是非常耗時的工作(需要經由較慢的 memory-bus),而查分頁目錄和分頁表又是非常頻繁的事件(幾乎所有的記憶體存取動作都需要),因此,處理器把最近使用的分頁目錄和分頁表的 entry 存放在叫 Translation Lookaside Buffers(TLBs)的 cache 中。只有 CPL 為 0 的程序才能選擇 TLB 的 entry 或是把 TLB 設為無效。無論是在更動分頁目錄或分頁表之後,都要立刻把相對的 TLB entry 設為無效,這樣在下次取用這個分頁目錄或分頁表時,才會更新 TLB 的內容(否則就可能會從 TLB 中讀到舊的資料了)。 要把 TLB 設為無效,只要重新載入 CR3 就可以了。要重新載入 CR3,可以用 MOV 指令(例如:MOV CR3, EAX),或是在工作切換時,處理器也會重新載入 CR3 的值。此外,INVLPG 指令可以把某個特定的 TLB entry 設成無效。不過,在某些狀況下,它會把一些 TLB entries 甚至整個 TLB 都設為無效。INVLPG 的參數是該分頁的位址,處理器會把 TLB 中存放該分頁的 entry 設為無效。
分頁的規劃分頁機制在多工作業系統中是很重要的。在多工作業系統中,往往同時執行很多個程式,因此,記憶體可能常常會用盡。但是,即使一個程式載入大量的資料到記憶體中,也很少會同時使用到全部的資料。這時候,把暫時不需要的資料寫入硬碟(或其它類似的裝置)中,就可以空出位置載入其它的程式了。不過,為了管理的方便,分頁的大小往往是固定的。例如,在 IA-32 架構下,分頁的大小是 4KB。分頁如果太大,則在 swap 時,常常會 swap 到不需要 swap 的部分;而若分頁太小,則過於破碎,不易管理,也缺乏效率。 在 i486 和之前的處理器中,分頁的大小只有一種選擇:4KB。在大部分情形中,這個大小還算適當。但是,在某些情形下,可能會需要更大的分頁。因此,在 Pentium 和以後的處理器,就增加了 4MB 的分頁大小。然而,4MB 在一般的情形中,實在是太大了,實用性也降低。不過,4MB 的分頁在某些狀況下還是有用的。例如:為了方便管理,可以把作業系統的核心放在 4MB 的分頁中,而一般應用程式則使用 4KB 的分頁。此外,在 Linux 作業系統中還有一種用法:Linux 作業系統的核心部分常常需要使用實體位址,因此在 Linux 中,應用程式和核心是使用不同的分頁目錄。核心的分頁目錄便是將線性記憶體直接對映到實體記憶體中。在這種情形下,就很適合使用 4MB 的分頁模式。不過,要注意一點:4MB 的分頁模式,只有在 Pentium 及以後的處理器才能使用。

2008年9月8日 星期一

C 字串

http://oaunix.hlhs.hlc.edu.tw/~program/hkin/string.htm#a8

簡 介 下一節 到頁頂


C 沒 有 字 串 型 態 , 因 此 我 們 要 用 字 元 陣 列 來 處 理 字 串 , 例 如 :


char string[] = { 'H', 'e', 'l', 'l', 'o' };

i 0 1 2 3 4
word[i] 'H' 'e' 'l' 'l' 'o'

如 果 要 顯 示 這 個 字 串 , 就 要 用 printf 和 for 迴 圈 逐 個 字 元 顯 示 。

例 子 :

void print_string(char string[], int length) {
int i;

for (i = 0 ; i < i =" 0;" st1 =" %s\nst2" st1 =" Hello" st2 =" World">

main() {
printf("Press Enter to quit\n");
while (getchar() != '\n') ;
}


說 明 :


每 次 呼 叫 getchar 函 數 都 會 等 候 按 鍵 , 按 鍵 後 傳 回 該 鍵 。


例 子 :

#include <stdio.h>

void read_line(char string[], int max_length) {
char ch;
int i = 0;

do {
ch = getchar();
string[i] = ch;
i++;
}
while (ch != '\n' && i <= max_length); string[i-1] = '\0'; }

main() { char string[21]; read_line(string, 20); printf("%s", string); }

執 行 結 果 : This is a line. This is a line.

說 明 : 輸 入 字 串 。
void read_line(char string[], int max_length) 用 read_line 函 數 來 輸 入 字 串 , 可 以 控 制 每 個 輸 入 的 字 元 , 比 scanf 更 有 彈 性 , max_length 是 字 元 數 目 上 限 , 用 來 避 免 字 串 溢 滿 。 while (ch != '\n' && i <= max_length) 如 果 按 Enter 鍵 或 i 大 過 max_length 就 離 開 。 string[i-1] = '\0'; 在 字 串 結 尾 加 入 '\0' 。 其 它 字 串 語 法 上一節 下一節 到頁頂 有 時 一 個 字 串 太 長 , 你 想 分 幾 行 來 寫 它 , 可 以 在 一 行 的 結 尾 寫 反 斜 號 , 例 如 : char lower[] = "abcdefghijklmnopqrstuvwxyz"; 可 以 寫 成 : char lower[] = "abcdefghij\ klmnopqrst\ uvwxyz"; 留 意 字 串 會 由 下 一 行 的 開 頭 繼 續 的 , 所 以 如 果 寫 : char lower[] = "abcdefghij\ klmnopqrst\ uvwxyz"; 就 會 變 成 : char lower[] = "abcdefghij klmnopqrst uvwxyz"; 還 有 另 一 種 更 方 便 的 寫 法 , 就 是 把 一 個 字 串 寫 成 多 個 獨 立 的 字 串 , 例 如 : "OneTwoThree" 可 以 寫 成 : "One" "Two" "Three" 所 以 lower 陣 列 也 可 以 寫 成 : char lower[] = "abcdefghij" "klmnopqrst" "uvwxyz";  

實 例 上一節 下一節 到頁頂 實 例 : 字 數 統 計 上一節 下一節 到頁頂 一 般 文 字 處 理 程 式 都 有 字 數 統 計 功 能 。 假 設 每 個 英 文 字 都 是 由 一 列 連 逐 的 英 文 字 母 組 成 , 例 如 : This is a line. 可 分 為 「 This 」 「 is 」 「 a 」 「 line 」 4 個 字 , 而 : This's a line. 其 中 「 This's 」 可 分 為 「 This 」 「 s 」 兩 個 字 , 因 為 單 引 號 不 是 英 文 字 。

例 子 :

int is_alpha(char ch) { return ('a' <= ch && ch <= 'z') ('A' <= ch && ch <= 'Z'); }
int count_word(char string[]) { int i, count, looking_for_word; count = 0; looking_for_word = 1;

for (i = 0 ; string[i] != '\0' ; i++) { if ( is_alpha(string[i]) ) { if (looking_for_word) { looking_for_word = 0; count++; } } else { looking_for_word = 1; } } return count; } main() { char st[] = "Wow! Great job."; printf( "%s\nWords: %i", st, count_word(st) ); }

執 行 結 果 : Wow! Great job. Words: 3

說 明 : 字 元 可 分 為 兩 類 : 「 英 文 字 母 」 (Alphabet) 和 非 英 文 字 母 , 統 計 方 法 就 是 由 左 至 右 逐 個 字 元 處 理 , 如 果 字 元 不 是 英 文 字 母 , 就 等 待 英 文 字 母 出 現 , 一 出 現 就 把 字 數 加 一 , 然 後 就 不 用 再 等 待 , 直 至 字 元 不 是 英 文 字 母 為 止 。 i   0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 string[i]   'W' 'o' 'w' '!' ' ' 'G' 'r' 'e' 'a' 't' ' ' 'j' 'o' 'b' '.' '\0' is_alpha(string[i])   1 1 1 0 0 1 1 1 1 1 0 1 1 1 0 0 looking_for_word 1 0 0 0 1 1 0 0 0 0 0 1 0 0 0 1 1 count 0 1 1 1 1 1 2 2 2 2 2 2 3 3 3 3 3   int is_alpha(char ch) is_alpha 函 數 檢 查 一 個 字 元 是 否 英 文 字 母 。

looking_for_word 如 果 looking_for_word 是 1 , 表 示 正 等 待 著 下 一 個 字 , 即 是 等 待 英 文 字 母 。 if ( is_alpha(string[i]) ) { if (looking_for_word) { looking_for_word = 0; count++; } 如 果 現 正 等 待 著 下 一 個 字 , 而 string[i] 又 是 英 文 字 母 的 話 , 就 把 字 數 加 一 , 與 及 不 用 等 待 下 一 個 字 。 looking_for_word = 1; 如 果 string[i] 不 是 英 文 字 母 , 就 等 待 下 一 個 字 的 來 臨 。  

實 例 : 基 本 字 串 處 理 上一節 下一節 到頁頂 實 例 : 基 本 字 串 處 理 : 字 串 長 度 上一節 下一節 到頁頂

例 子 :
int string_length(char string[]) { int i; i = 0; while (string[i] != '\0') i++; return i; } main() { char st[21] = "012345"; printf("st = %s ; Length = %i", st, string_length(st)); } 執 行 結 果 : st = 012345 ; Length = 6

說 明 : while (string[i] != '\0') i++; 當 字 元 不 是 '\0' , 就 把 該 字 元 計 算 入 長 度 。

實 例 : 基 本 字 串 處 理 : 字 串 複 製 上一節 下一節 到頁頂 複 製 基 本 型 態 的 資 料 可 以 用 等 於 符 號 , 不 過 它 不 適 用 於 陣 列 , 即 是 你 不 能 寫 : string1 = string2; 所 以 複 製 字 串 必 須 逐 個 字 元 複 製 。 例 子 : void string_copy(char to[], char from[]) { int i; i = 0; while ( (to[i] = from[i]) != '\0' ) i++; } main() { char st1[21] = "012345", st2[21] = "abcde"; printf("Before: st2 = %s\n", st2); string_copy(st2, st1); printf("After : st2 = %s\n", st2); } 執 行 結 果 : Before: st2 = abcde After : st2 = 012345 說 明 : while ( (to[i] = from[i]) != '\0' ) i++; 與 「 字 串 長 度 」 例 子 差 不 多 , 也 是 檢 查 字 元 是 否 '\0' 。 這 句 會 先 執 行 : to[i] = from[i] 然 後 才 把 from[i] 的 字 元 與 '\0' 比 較 , 即 是 先 複 製 , 後 檢 查 , 因 此 在 迴 圈 完 結 時 , to 字 串 結 尾 會 有 '\0' 。

實 例 : 基 本 字 串 處 理 : 字 串 比 較 上一節 下一節 到頁頂 你 也 不 能 寫 : if (string1 == string2) 來 比 較 字 串 , 而 正 確 的 做 法 就 是 把 兩 個 字 串 內 的 所 有 對 應 的 字 元 作 比 較 , 如 果 有 一 個 字 元 不 同 的 話 , 就 算 是 不 相 等 了 , 例 如 : char string1[] = "abcde"; char string2[] = "abdde"; 因 為 string1[2] 是 'c' , string2[2] 是 'd' , 所 以 它 們 不 相 等 。 其 實 , 字 串 也 可 以 好 像 數 目 一 樣 , 把 兩 樣 東 西 作 比 較 , 會 有 3 種 情 況 : 等 於 、 大 於 及 小 於 。

什 麼 ? 文 字 也 有 大 小 之 分 嗎 ? 對 啊 , 還 記 得 ASCII 碼 嗎 ? 每 個 字 元 都 有 一 個 碼 , 我 們 可 以 跟 據 這 個 碼 來 決 定 字 元 的 大 小 , 而 比 較 方 法 很 簡 單 , 只 需 把 兩 個 字 串 由 頭 至 尾 逐 個 字 元 作 比 較 便 可 , 所 謂 的 「 由 頭 至 尾 」 , 就 是 由 指 數 0 的 字 元 開 始 , 直 至 出 現 不 同 的 字 元 , 或 者 到 了 字 串 結 尾 。 為 什 麼 不 是 「 由 尾 至 頭 」 呢 ? 可 能 沒 有 實 際 用 途 吧 , 因 為 「 由 頭 至 尾 」 可 以 把 英 文 字 排 列 到 好 像 字 典 一 樣 。 例 如 比 較 以 上 的 string1 和 string2 , 首 先 比 較 指 數 0 字 元 , 兩 者 都 是 'a' , 然 後 比 較 指 數 1 字 元 , 兩 者 都 是 'b' , 然 後 比 較 指 數 2 字 元 , string1[2] 是 'c' , 它 的 ASCII 碼 是 63h , 而 string2[2] 是 'd' , 它 的 ASCII 碼 是 64h , 所 以 'd' 比 'c' 大 , 即 是 string2 比 string1 大 。 我 們 可 以 寫 個 函 數 , 分 別 傳 回 0 、 1 和 -1 來 代 表 等 於 、 大 於 及 小 於 , 例 如 : int string_compare(char st1[], char st2[]); 情況 傳回 st1 等 於 st2 0 st1 大 於 st2 1 st1 小 於 st2 -1  

例 子 : int string_compare(char st1[], char st2[]) { int i; i = 0; while (st1[i] == st2[i] && st1[i] != '\0') i++; if (st1[i] == st2[i]) { return 0; } else { return (st1[i] > st2[i])? 1 : -1;
}
}

main() {
char st1[21] = "abcde", st2[21] = "abdde", st3[21] = "abcdefg";

printf("%s compare %s = %i\n", st1, st2, string_compare(st1, st2));
printf("%s compare %s = %i\n", st1, st1, string_compare(st1, st1));
printf("%s compare %s = %i\n", st2, st1, string_compare(st2, st1));
printf("%s compare %s = %i\n", st1, st3, string_compare(st1, st3));
}

執 行 結 果 :

abcde compare abdde = -1
abcde compare abcde = 0
abdde compare abcde = 1
abcde compare abcdefg = -1


說 明 :



i = 0;
while (st1[i] == st2[i] && st1[i] != '\0') i++;

由 指 數 0 開 始 比 較 , 直 至 出 現 不 相 同 字 元 或 st1[i] 是 結 尾 字 元 , 所 以 執 行 迴 圈 後 , 指 數 i 的 字 元 是 不 相 同 的 字 元 , 或 者 是 '\0' 。 為 什 麼 只 檢 查 st1[i] 而 不 用 檢 查 st2[i] 呢 ? 即 為 什 麼 不 寫 :


while (st1[i] == st2[i] && st1[i] != '\0' && st2[i] != '\0') i++;

因 為 如 果 st1[i] 等 於 st2[i] , 那 麼 st1[i] 不 等 於 什 麼 , 也 代 表 st[2] 不 等 於 什 麼 , 所 以 檢 查 st1 或 st2 也 沒 所 謂 , 不 必 兩 個 都 檢 查 了 。


if (st1[i] == st2[i]) {
return 0;
} else {
return (st1[i] > st2[i])? 1 : -1;
}

如 果 st1[i] 等 於 st2[i] , 就 表 示 兩 個 字 串 是 相 等 的 , 因 此 傳 回 0 , 這 時 候 , st1[i] 和 st2[i] 都 會 等 於 '\0' 。 如 果 它 們 不 相 等 , 就 跟 據 它 們 的 大 小 來 傳 回 1 或 -1 。


abcde compare abcdefg = -1

兩 個 不 同 長 度 的 字 串 也 沒 有 特 別 , 當 比 較 到 "abcdefg" 的 'f' 時 , 剛 好 到 了 另 一 字 串 的 '\0' :

a b c d e \0    
a b c d e f g \0

'f' 的 ASCII 碼 是 66h , '\0' 的 是 0 , 因 此 "abcde" 比 "abcdefg" 小 。


string_compare 函 數 好 像 Perl 的 cmp 運 算 子 。


實 例 : 基 本 字 串 處 理 : 字 串 加 法 上一節 下一節 到頁頂


兩 個 字 串 相 加 . 即 是 把 其 中 一 個 字 串 接 駁 到 另 一 字 串 的 尾 部 。

例 子 :

void string_concat(char st1[], char st2[], char result[]) {
int i, j;

i = 0;
while ( (result[i] = st1[i]) != '\0' ) i++;
j = i;
while ( (result[i] = st2[i - j]) != '\0' ) i++;
}

main() {
char st1[21] = "abcde", st2[21] = "012345", st3[21];

string_concat(st1, st2, st3);
printf("%s + %s = %s\n", st1, st2, st3);
}

執 行 結 果 :

abcde + 012345 = abcde012345


說 明 :



i = 0;
while ( (result[i] = st1[i]) != '\0' ) i++;

先 把 st1 複 製 到 result 。


j = i;
while ( (result[i] = st2[i - j]) != '\0' ) i++;

然 後 複 製 st2 。



實 例 : 基 本 字 串 處 理 : 字 串 位 置 上一節 下一節 到頁頂


尋 找 某 個 子 字 串 在 另 一 字 串 的 開 始 位 置 。

例 子 :

/* Insert "string_length" function here */

int index(char st[], char subst[]) {
int st_start, sti, substi, limit, result, st_length, subst_length;

result = -1;
st_length = string_length(st);
subst_length = string_length(subst);

limit = st_length - subst_length;
if (limit < st_start =" 0" sti =" st_start;" substi =" 0;" substi ="="" result =" st_start;" s =" %i\n" s =" %i\n" s =" %i\n" 012345 =" 0" 012345 =" 3" 012345 =" -1" result =" -1;" limit =" st_length" st_start =" 0" 3 =" limit" substi ="="" i =" 0;" st1 =" %s\n" 3 =" %s\n" 100 =" %s\n" 1 =" %s\n" 0 =" %s\n" st1 =" 012345" 3 =" 012" 100 =" 5" 1 =" Index" 0 =" 說" st_length =" string_length(st);" insert_length =" string_length(insert);" i =" st_length">= start ; i--) {
st[i + insert_length] = st[i];
}

for (i = 0 ; i < before =" %s\n" after =" %s\n" before =" 012345" after =" 01bcd2345" i =" st_length">= start ; i--) {
st[i + insert_length] = st[i];
}

搬 遷 位 置 start 或 之 後 的 字 元 , 包 括 '\0' , 以 便 把 insert 放 到 空 出 來 的 位 置 。

搬遷前 0 1 2 3 4 5 \0      
搬 遷 後 0 1 2 3 4 2 3 4 5 \0
插 入 後 0 1 b c d 2 3 4 5 \0

 


for (i = 0 ; i < insert_length ; i++) {
st[start + i] = insert[i];
}

插 入 insert 。


 


實 例 : 基 本 字 串 處 理 : 字 串 移 除 上一節 下一節 到頁頂


例 子 :

/* Insert "string_length" function here */

void string_remove(char st[], int start, int length) {
int i, limit;

if ( string_length(st) <= start ) return;

i = start + 1;
limit = start + length;
while (st[i] != '\0' && i < limit) i++;

while ( (st[i - length] = st[i]) != '\0' ) i++;
}

main() {
char st1[21] = "012345";

printf("st1 = %s\n", st1);
string_remove(st1, 2, 3);
printf("Index 2, length 3 = %s\n", st1);
}

執 行 結 果 :

st1 = 012345
Index 2, length 3 = 015


說 明 :



i = start + 1;
limit = start + length;
while (st[i] != '\0' && i < limit) i++;

去 到 要 移 除 的 字 串 的 後 一 個 字 元 , 即 i = limit = 5 , 或 去 到 '\0' 。 然 後 把 字 串 剩 餘 的 部 份 , 包 括 '\0' , 複 製 到 start 位 置 。

複製前 0 1 2 3 4 5 \0
複 製 後 0 1 5 \0 4 5 \0

 


while ( (st[i - length] = st[i]) != '\0' ) i++;

把 字 串 剩 餘 的 部 份 複 製 到 start 位 置 。


 


實 例 : 基 本 字 串 處 理 : 字 串 取 代 上一節 下一節 到頁頂


例 子 :

/* Insert "string_length" function here */
/* Insert "string_remove" function here */
/* Insert "string_insert" function here */

void string_replace(char st[], char out[], char in[]) {
int i, out_length;

i = index(st, out);
if (i != -1) {
out_length = string_length(out);
string_remove(st, i, out_length);
string_insert(st, in, i);
}
}

main() {
char st1[21] = "012345";

printf("Before, st1 = %s\n", st1);
string_replace(st1, "123", "OneTwoThree");
printf("After, st1 = %s\n", st1);
}

執 行 結 果 :

Before, st1 = 012345
After, st1 = 0OneTwoThree45


說 明 :



/* Insert "string_length" function here */
/* Insert "string_remove" function here */
/* Insert "string_insert" function here */

string_replace 函 數 會 用 到 string_length 、 string_remove 和 string_insert 函 數 , 其 運 作 原 理 是 : 找 尋 要 移 除 的 字 串 , 如 果 找 到 , 就 移 除 它 , 然 後 插 入 新 的 字 串 。


i = index(st, out);

找 尋 要 移 除 的 字 串 , 傳 回 來 的 子 字 串 位 置 i 會 用 在 string_remove 和 string_insert 函 數 。


if (i != -1)

如 果 找 到 的 話 。



實 例 : 字 典 上一節 下一節 到頁頂


本 節 介 紹 一 個 字 典 程 式 , 它 讓 你 輸 入 英 文 字 , 然 後 會 顯 示 它 的 解 釋 。 你 可 以 定 義 一 個 「 字 典 條 目 」 (Entry) 結 構 :


struct entry {
char word[11];
char definition[31];
}

一 個 條 目 包 括 英 文 字 word 和 解 釋 definition 。 假 設 這 個 字 典 有 5 個 條 目 , 你 可 以 用 結 構 陣 列 來 儲 存 這 個 字 典 :


struct entry dictionary[5];

這 樣 就 可 以 寫 :


dictionary[i].word
dictionary[i].definition

來 表 示 第 i 個 條 目 的 英 文 字 和 解 釋 。

你 可 以 寫 個 函 數 來 搜 尋 這 個 字 典 :


entry_index = lookup(dictionary, word, num_entry);

word 是 要 查 詢 的 英 文 字 , num_entry 是 條 目 數 目 , entry_index 是 word 在 dictionary 陣 列 的 位 置 。

例 子 :

struct entry {
char word[11];
char definition[51];
};

/* Insert "string_compare" function here */

int lookup(struct entry dictionary[], char word[], int num_entry) {
int i;

for (i = 0 ; i < num_entry ; i++) {
if ( string_compare(word, dictionary[i].word) == 0 ) return i;
}

return -1;
}

main() {
struct entry dictionary[5] = {
{"apple", "round fruit with firm juicy flesh"},
{"boy", "male child"},
{"cat", "small furry domesticated animal"},
{"dog", "common domestic animal kept by human"},
{"egg", "the cell from which the young is formed"},
};
char word[11];
int num_entry = 5, entry_index;

printf("Type '9' to quit.\n");
while (1) {
printf("Enter word: ");
scanf("%10s", word);

if (word[0] == '9') break;

entry_index = lookup(dictionary, word, num_entry);

if (entry_index != -1) {
printf("%s\n\n", dictionary[entry_index].definition);
} else {
printf("Sorry, the word is not in my dictionary.\n\n");
}
}
}

執 行 結 果 :

Type '9' to quit.
Enter word: apple
round fruit with firm juicy flesh

Enter word: egg
the cell from which the young is formed

Enter word: fork
Sorry, the word is not in my dictionary.

Enter word: 9


說 明 :


lookup 函 數 會 傳 回 查 詢 字 串 在 字 典 中 的 指 數 , 如 果 找 不 到 字 串 , 就 傳 回 -1 。



總 結 上一節 到頁頂


C 的 字 串 結 尾 字 元 是 '\0' , 使 用 結 尾 字 元 的 好 處 是 不 用 留 意 字 串 長 度 。

2008年9月1日 星期一

不好的早晨

不順,手機忘記帶,蓋子找不到,早上差點來不及,哀
希望下午順利