各位看官們,大家好,上一回中咱們說的是字符串概述的例子,這一回咱們說的例子是:字符串復制。閒
話休提,言歸正轉。讓我們一起talk C栗子吧!
看官們,在C語言的標准庫中為我們提供了字符串復制函數,我們只需要包含string.h頭文件就可以使用
字符串復制函數。標准庫提供了四個字符串復制函數:strcpy,strncpy,memcpy,memmove。接下來我們
分別介紹它們的用法和使用時的注意事項。
strcpy函數原型:char * strcpy(char *s1, const char *s2)strcpy函數用法:它把s2中內容復制到s1中,並且返回s1.strcpy注意事項:如果s2中的內容太多,以至於超過s1的容量,那麼會覆蓋掉s1後面內存中的內容。
我們舉一個實際的例子來說明,在程序中定義如下字符串:
char s0[SIZE] = string; char s1[SIZE]=str-1; char s2[SIZE+3]=str-2and123; char s3[SIZE] = {'A','B'};// other element of s3 is char s4[] = {'A','B'}; // there is loss of at the end of string
在執行strcpy前和strcpy後分別顯示這些字符串的內存地址和字符串的內容,結果如下:
addr: 0xbfb08a39 | s0 : string addr: 0xbfb08a41 | s1 : str-1 addr: 0xbfb08a51 | s2 : str-2and123 addr: 0xbfb08a49 | s3 : AB addr: 0xbfb08a39 | s4 : ABstring ----- after running strcpy(s1,s3) ----- addr: 0xbfb08a39 | s0 : string addr: 0xbfb08a41 | s1 : AB addr: 0xbfb08a51 | s2 : str-2and123 addr: 0xbfb08a49 | s3 : AB addr: 0xbfb08a39 | s4 : ABstring ----- after running strcpy(s1,s2) ----- addr: 0xbfb08a39 | s0 : string addr: 0xbfb08a41 | s1 : str-2and123 addr: 0xbfb08a51 | s2 : str-2and123 addr: 0xbfb08a49 | s3 : 123 addr: 0xbfb08a39 | s4 : ABstring
從運行結果中,大家可以看到當使用strcpy(s1,s3)把s3的內容復制到s1中時,s1的內容變成了s3的
內容,其它幾個字符串的內容沒有改變。
當使用strcpy(s1,s2)把s2的內容復制到s1中時,s1的內容變成了s2的內容,而且s3的內容也被改變了。
我們再看看s1和s3的地址相鄰很近,肯定是在復制過程中把s1後面的內存給“污染”了,所以s3的內容
被修改了,s3就這樣中了躺槍。為了大家更加清楚地理解s3是如何中躺槍的,我們對內存中的地址進
行分析:
內存中的地址:0xbfb08a4 1 2 3 4 5 6 7 8 9 a b c d e f地址中的數值: s t r - 2 a n d 1 2 3
大家看看s1的地址從0xbfb08a41開始,占用8個字節,也就是到0xbfb08a48結束,但是執行strcpy操
作時復制了11個字符,這顯然超過了s1占有的8個字節,因此它把後面3個字節的內容也強制占用了,但是
後面3個字節的空間是系統分配給s3的,s3雖然擁有這3個字節的空間,但是裡面的內容已經被s1強制占用
時修改了。s3就這樣中了躺槍。
strncpy函數原型:char * strcpy(char *s1, const char *s2,size_t n)strncpy函數用法:它把s2中的n個字符復制到s1中,並且返回s1.strncpy注意事項:如果s2中的字符數量大於n,那麼只復制n個字符到s1中,而且不會在n個字符後面加上字符串的小尾巴。如果s2中的字符數量小於n,那麼只復制s2中所有的字符,不足的用空字符:進行補充,直到n個字符為止。我們一般的經驗是,在復制時設置n的值比s1的容量小於1,並且手動給s1加上小尾巴。如果n的值大於s1的容量就和strcpy一樣了。引入strncpy就是為了通過n來控制復制的內容,避免類似strcpy復制時的缺陷。因此,我們要用好n這張牌。關鍵時候來個一招招制勝,哈哈。
我們舉一個實際的例子來說明,程序中還使用剛才定義的字符串,在執行strncpy前和strncpy後分別顯
示這些字符串的內存地址和字符串的內容,結果如下:
addr: 0xbfed56a9 | s0 : string //執行strncpy前各個字符串的值 addr: 0xbfed56b1 | s1 : str-1 addr: 0xbfed56c1 | s2 : str-2and123 addr: 0xbfed56b9 | s3 : AB addr: 0xbfed56a9 | s4 : ABstring ----- after running strncpy(s1,s3,SIZE) ----- addr: 0xbfed56a9 | s0 : string addr: 0xbfed56b1 | s1 : AB //s3中字符的數量小於SIZE,所以有小尾巴 addr: 0xbfed56c1 | s2 : str-2and123 addr: 0xbfed56b9 | s3 : AB addr: 0xbfed56a9 | s4 : ABstring ----- after running strncpy(s1,s2,SIZE) ----- addr: 0xbfed56a9 | s0 : string addr: 0xbfed56b1 | s1 : str-2andAB //從s2中復制了8個字符,但是沒有小尾巴 addr: 0xbfed56c1 | s2 : str-2and123 addr: 0xbfed56b9 | s3 : AB addr: 0xbfed56a9 | s4 : ABstring ----- after running strncpy(s1,s2,SIZE-1) ----- addr: 0xbfed56a9 | s0 : string addr: 0xbfed56b1 | s1 : str-2an //從s2中復制了7個字符,手動加上小尾巴 addr: 0xbfed56c1 | s2 : str-2and123 addr: 0xbfed56b9 | s3 : AB addr: 0xbfed56a9 | s4 : ABstring ----- after running strncpy(s1,s2,SIZE+3) ----- addr: 0xbfed56a9 | s0 : string addr: 0xbfed56b1 | s1 : str-2and123 //復制的字符數量大於s1的容量,效果等於strcpy addr: 0xbfed56c1 | s2 : str-2and123 addr: 0xbfed56b9 | s3 : 123 addr: 0xbfed56a9 | s4 : ABstring
我們使用strncpy(s1,s2,n)進行了四次復制操作:
第一次復制時,s2中字符的數量小於n,s1和s2的內容相同,而且給s1中的內容加上了小尾巴。第二次復制時,s2中字符的數量大於n,s1和s2中前n個字符相同,因為s1中的內容沒有小尾巴,所以它把s3中的內容也顯示了出來,因為s1和s3的地址相鄰,而且s3中的字符串有小尾巴。大家可以用我剛才分析內存中地址的方法來看,我就不詳細介紹了。第三次復制時,s2中字符的數量大於n,s1和s2中前7個字符的內容相同,我們給s1手動加上了小尾巴,這是我們推薦的做法。第四次復制時n的值大於了s1的容量,s1和s2的內容雖然相同,但是s1污染了其它的內存空間,這和strcpy的做法一樣,剛才已經分析過,這裡就不再詳細分析了。
接下我們說說memcpy函數:
memcpy函數原型:void * memcpy(char *s1, const char *s2,size_t n)memcpy函數用法:它把s2中的n個字符復制到s1中,並且返回s1.memcpy注意事項:和strncpy的相同,請參考上面strncpy的內容,哈哈!memcpy的用法和strncpy類似,所以咱們就不舉例子說明了,大家可以參考上面strncpy的例子。
strcpy,strncpy和memcpy都有一個共同的缺點:如果s1和s2的內存空間有重疊的部分,那麼使用這些
函數就會產生意想不到的後果,這會給程序埋下“地雷”,一旦爆發後果很嚴重。想挖掉這顆雷,沒有一定的
經驗,很難找出來。為此標准庫提供了memmove。它可以避免這個缺點。
memmove函數原型:void * memmove(char *s1, const char *s2,size_t n)memmove函數用法:它把s2中的n個字符復制到s1中,並且返回s1.memmove注意事項:和strncpy的相同,請參考上面strncpy的內容,哈哈!如果s1和s2的內容有重疊的部分,那麼它可以很好地處理。
我們舉一個實際的例子來說明,程序中還使用剛才定義的字符串,執行這四個復制函數後分別顯示這些字
符串的內存地址和字符串的內容,結果如下:
----- after running memmove(s1,s3,SIZE) ----- addr: 0xbff8aca9 | s0 : string addr: 0xbff8acb1 | s1 : AB //這個是正常的復制操作,沒有任何問題 addr: 0xbff8acc1 | s2 : str-2and123 addr: 0xbff8acb9 | s3 : AB addr: 0xbff8aca9 | s4 : ABstring ----- after running strcpy(s2,s2+1) ----- addr: 0xbff8acc1 | s2 : tr-2and123 //復制時內存有重疊,結果異常 ----- after running strncpy(s2,s2+1,SIZE) ----- addr: 0xbff8acc1 | s2 : tr-2and1123 //復制時內存有重疊,結果正常 ----- after running memcpy(s2,s2+1,SIZE) ----- addr: 0xbff8acc1 | s2 : tr-2and1123 //復制時內存有重疊,結果正常 ----- after running memmove(s2,s2+1,SIZE) ----- addr: 0xbff8acc1 | s2 : tr-2and1123 //復制時內存有重疊,結果正常
對比程序運行結果,大家可以看到當復制字符串時,如果內存有重疊,那麼只有strcpy復制的結果是異常
的,其它幾個字符串復制函數的結果是正常的。其實,新版本的C庫對strncpy和momcpy做了更新 ,使他
們也可以像memmove一樣進行復制,只不過目前還沒有官方的資料來說明,因此,我們按照老的標准來介
紹這四個字符串復制函數。
看官們,今天的內容有些多,我們在最後對這四個復制函數做一些總結:
strcpy復制時會把字符串後面的空字符,也就是我們說的小尾巴復制上,strncpy和memcpy則不會。strcpy通常對有小尾巴的字符串進行操作,strncpy和memcpy則不管字符串是否有小尾巴,它們都可以進行操作,操作時只參考n.str開頭的復制函數,只能對字符串進行復制操作,但是mem開頭的復制函數除了對字符串進行復制操作外,還可以對其它類型的數據進行復制操作,比如自己定義的結構體類型數據。因此,它的使用范圍更加廣一些。
看官們,經過我們對字符串復制函數的介紹,是不是覺得標准庫也有些不可靠呢。其實,標准庫還是很可
靠的,只是有歷史方面的原因。打個比方,現在各種流行網絡用語,字典中是沒有的,流行起來後才收集
到字典中。標准庫類似我們使用的字典。先有了函數,然後才收集到標准庫中的,在收集的時候,雖然發
現了一些庫函數有缺點,但是它們已經被廣泛使用了,所以先收集起來,再提供一些改進後的庫函數。
看官們,我把所有的例子整理成了一個文件,並且添加了詳細的注釋。正文中就不寫代碼了,以免顯得亂,
詳細的代碼放到了我的資源中,大家可以點擊這裡下載使用。
各位看官,關於字符串復制的例子咱們就說到這裡。欲知後面還有什麼例子,且聽下回分解。