字元串是一種重要的資料類型,有零個或多個字元組成的有限串行。
定義子串: 串中任意個連續的字元組成的子序列,并規定空串是任意串的子串,任意串也是其自身的子串,如字元串"adereegfb"中它本身、空串、諸如"ader"連續的字元串都是它的子串。子序列則不要求字元連續,但順序要與主串保持一緻,若有"abcd"與"ad"則兩者的最長公共子序列為"ad"。在動态規劃中計算最長公共子序列和最長公共子串中一定要能區分這兩個概念!
在C語言中并沒有顯示的字元串類型,它有如下兩種風格的字元串:
字元串常量: 以雙引号擴起來的字元序列,規定所有的字元串常量都由編譯器自動在末尾添加一個空字元
字元數組: 末尾添加了'\0'的字元數組,一般需要顯示在末尾添加空字元。
注意到通過字元數組初始化和字元串常量初始化并不完全相同的。因為字元串常量包含一個額外的空字元用于結束字元串,用它來初始化建立數組時,末尾會自動添加空字元。是以c1的長度是3,後兩者的長度是4,并且字元數組c2和c3都被稱為C風格字元串,而字元數組c1不是C風格字元串。
規定C風格的字元串都是以NULL空字元('\0')作為終結符結尾。由于它是字元串的終止符,但它本身并不是字元串的一部分,是以字元串的長度并不包括NULL位元組,如strlen函數。而且C标準庫中提供的各種字元串處理函數都要求提供的字元串或字元數組必須以空字元結束,否則會出現不可預料的結果。如:
C标準庫中頭檔案<code><string.h></code>定義了兩組字元串函數(C++中用<code><string></code>表示)。
第一組函數的名字以str開頭,它主要處理以'\0'結尾的字元串,是以字元串内部不能包含任何'\0'字元。
第二組函數的名字以mem開頭,主要考慮非字元串内部含有零值的情形,它能夠處理任意的位元組序列,操作與字元串函數類似
除了memmove函數外,其他函數都沒定義重疊對象間的行為
為了提高程式在不同機器上的移植性,利用typedef定義新類型名,即<code>typedef unsigned int size_t</code>。 程式員必須要保證目标字元數組的空間能夠足以存放結果字元串(有可能存在字元數組溢出的危險)
字元串處理類
如下表為字元串處理函數說明,變量s,t的類型是<code>char *</code>, cs和ct的類型是<code>const char *</code>;n的類型為<code>size_t</code>,c的類型為<code>int</code>。

記憶體操作類
按照位元組數組的方式操作對象,提供一個高效的函數接口(提供位元組流的通路)。其中s,t類型是<code>void *</code> , cs,ct的類型是<code>const void *</code>; n類型為<code>size_t</code>,c類型為<code>int</code>。
總結起來,頭檔案< string.h>實作了如下函數:
長度計算、長度不受限和受限的複制、連接配接和比較版本的函數
基礎字元串查找(查找一個字元、一組字元和比對一個子串)、進階字元串查找(查找子串字首位置、傳回token标記)
處理任意位元組序列的記憶體操作如複制、比較、查找和初始化等函數
A strlen/strcmp/strcpy/strcat等函數
代碼實作和測試如下:
實作時要注意一個細節,指針的自增運算與循環條件結束的問題。例如為什麼在<code>strcat</code>實作中使用該函數周遊到字元串結束符會出現問題。這是因為該語句首先判斷<code>*pdst != '\0'</code>,不論是否滿足條件都會執行指針的自增運算,也就是說都執行到了'\0'字元的下一個位置了,如果還需要利用到尾字元這樣就出錯了。
再例如<code>while(*s++ = *t++);</code>: 它首先是<code>*s = *t;</code>然後再判斷<code>*s != '\0';</code>若滿足繼續循環,否則退出循環。但是務必注意的是不論繼續循環與否接下來要執行<code>s +=1,t += 1;</code>
B 字元串查找函數
有以下幾個查找函數:
strchr/strrchr/strspn/strcspn/strpbrk
strstr/strtok
在子串比對的算法中,使用暴力查找算法時間複雜度為O(m*n),KMP算法的時間複雜度為O(m+n)。解釋下KMP算法的思路: 先計算next數組(部分比對表),再基此表在文本串進行查找比對。
舉一個例子,例如模式串<code>ABCDABD</code>不比對<code>...ABCDABE...</code>。考慮到前六個字元<code>ABCDAB</code>是比對的,且B的比對值是2(說明有個字首和字尾相同,記最長的字首長度,本例中是字尾串<code>AB</code>和字首串AB),則文本串中後面的<code>AB</code>無需再次比較了,因為模式串有字首AB和它比對,于是模式串移動4位。
那麼在KMP算法中這種移位是怎麼實作的?
首先我們構造一個next數組,得到該模式串以每個字元結尾其字首串和字尾串的最長比對下标。 next數組可轉化為給定一個字元數組,求該字元串的最大相等k字首和k字尾,求出這個最大的k
也就是說我們判斷的是<code>pat[j+1]</code>(字首字元)與<code>pat[i]</code>(目前字元)是否比對。這其實是一個動态規劃問題,即已知<code>dp[0..i-1]</code>求出<code>dp[i]</code>。即當<code>pat[j+1] == pat[i]</code>時比對了<code>dp[i]=j+1</code>;不相等呢回退指針看看有沒有比對的字元或者到達不比對的标志
C memset/memchr/memcmp/memcpy/memmove等函數
記憶體操作函數是按照字元數組的方式操作對象,注意這幾個函數中的size大小均為對應類型的大小乘以元素的個數。下面重點講述以下幾個函數:
memset : 為記憶體塊做初始化工作,一般是在對定義的字元數組初始化為某個字元或者其他類型的預設值。
可以看到幾個特點
初始化字元數組時用strlen,初始化其他類型時用sizeof。這是因為sizeof傳回數組或類型為其配置設定好的空間大小,并不關心裡面存儲的資料;而strlen值關心存儲的資料内容,并且字元串長度不包括結束符
memset一般是用來對較大結構體或數組進行初始化或清除操作,使用對應類型預設值
memcpy : 從源起始位置拷貝n個位元組到目标的起始位置,用于複制任意可讀寫類型的空間。
它不允許兩記憶體區域出現重疊。相比strcpy隻能用于複制字元串來說,memcpy是可以複制任何内容;并且它的複制指定了拷貝的位元組長度,操作更安全。那麼當src和dst以任何形式出現了重疊,就會出現資料覆寫的問題,這樣的結果是未定義的,如:
dst目的位址空間在src源位址空間右面(<code>src + n > dst</code>)
src目的位址空間在dst源位址空間右面(<code>dst + n > src</code>)
那麼如何處理呢,這就要用到memmove函數了
memmove : 它可以保證源串在被覆寫之前将重疊區域的位元組先拷貝到目标區域中(先處理重疊部分即可)。
它的思路是:
若dst小于src有重疊時(<code>dst+n > src</code>),仍從頭開始複制
若src小于dst有重疊時(<code>src+n > dst</code>),從尾部開始複制
代碼實作如下:
以上為字元串處理的完整實作,如有問題歡迎指正_
http://chuansongme.com/n/183586
http://chuansongme.com/n/181884
http://chuansongme.com/n/202680