庫函數 extern char *strcpy(char *dest,char *src)
用法:#include <string.h>
功能:把src所指由NULL結束的字串複製到dest所指的數組中。
說明:src和dest所指記憶體地區不可以重疊且dest必須有足夠的空間來容納src的字串。
返回指向dest的指標。
注意:strcpy不是遇到/0就不複製了 ,複製完成後判斷最後的字元是否為'/0' ,如果是,則退出迴圈!
也就是說,dest是有結束標誌'/0'的!
已知strcpy函數的原型是:
char * strcpy(char * strDest,const char * strSrc);
1.不調用庫函數,實現strcpy函數。
2.解釋為什麼要返回char *。
解說:
1.strcpy的實現代碼
char *strcpy(char *dest, const char *src){
assert((dest!= NULL) && (src != NULL));
char *tmp= dest;
while ((*tmp++ = *src++) != ‘/0’)
/*迴圈結束時 *dest為:'/0' ,不用手動加結束標誌*/
/*nothing*/;
return dest;
}
錯誤的做法:
[1]
(A)不檢查指標的有效性,說明答題者不注重代碼的健壯性。
(B)檢查指標的有效性時使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),說明答題者對C語言中類型的隱式轉換沒有深刻認識。在本例中char *轉換為bool即是類型隱式轉換,這種功能雖然靈活,但更多的是導致出錯機率增大和維護成本升高。所以C++專門增加了bool、true、false三個關鍵字以提供更安全的條件運算式。
(C)檢查指標的有效性時使用((strDest==0)||(strSrc==0)),說明答題者不知道使用常量的好處。直接使用字面常量(如本例中的0)會減少程式的可維護性。0雖然簡單,但程式中可能出現很多處對指標的檢查,萬一出現筆誤,編譯器不能發現,產生的程式內含邏輯錯誤,很難排除。而使用NULL代替0,如果出現拼字錯誤,編譯器就會檢查出來。
[2]
(A)return new string("Invalid argument(s)");,說明答題者根本不知道傳回值的用途,並且他對記憶體流失也沒有警惕心。從函數中返回函數體內分配的記憶體是十分危險的做法,他把釋放記憶體的義務拋給不知情的調用者,絕大多數情況下,調用者不會釋放記憶體,這導致記憶體流失。
(B)return 0;,說明答題者沒有掌握異常機制。調用者有可能忘記檢查傳回值,調用者還可能無法檢查傳回值(見後面的鏈式運算式)。妄想讓傳回值肩負返回正確值和異常值的雙重功能,其結果往往是兩種功能都失效。應該以拋出異常來代替傳回值,這樣可以減輕調用者的負擔、使錯誤不會被忽略、增強程式的可維護性。
[3]
(A)忘記儲存原始的strDest值,說明答題者邏輯思維不嚴密。
[4]
(A)迴圈寫成while (*strDest++=*strSrc++);,同[1](B)。 linux下卻正是如此實現的!!??
(B)迴圈寫成while (*strSrc!='/0') *strDest++=*strSrc++;,說明答題者對邊界條件的檢查不力。迴圈體結束後,strDest字串的末尾沒有正確地加上'/0'。
2.返回strDest的原始值使函數能夠支援鏈式運算式,增加了函數的“附加值”。同樣功能的函數,如果能合理地提高的可用性,自然就更加理想。
鏈式運算式的形式如:
int iLength=strlen(strcpy(strA,strB));
又如:
char * strA=strcpy(new char[10],strB);
返回strSrc的原始值是錯誤的。其一,源字串肯定是已知的,返回它沒有意義。其二,不能支援形如第二例的運算式。其三,為了保護源字串,形參用const限定strSrc所指的內容,把const char *作為char *返回,類型不符,編譯報錯。
Linux 下的定義是這樣的:
/usr/lib/string.h
string.h:
char *strcpy (char *__restrict __dest, __const char *__restrict __src)
__THROW;
/usr/src/linux-2.6.0-test3/lib/string.c
/**
* strcpy - Copy a %NUL terminated string
* @dest: Where to copy the string to
* @src: Where to copy the string from
*/
char * strcpy(char * dest,const char *src)
{
char *tmp = dest;
while ((*dest++ = *src++) != '/0')
/* nothing */;
return tmp;
}
在 string.h 中有 __THROW 這個宏,我們來查看一下在哪裡定義的:
$ grep __THROW /usr/include/*.h |grep define
...
usr/include/malloc.h:# define __THROW throw ()
...
而且幾乎每個預先處理指令都由 __THROW 來處理,可以這樣查看:
$ grep -R __THROW /usr/include/* | grep "#"
linux 裡的和高品質C/C++ 裡的其實是一樣的,除了異常處理哪裡稍不同而已。
kernel linux-2.6.22 /lib/string.c 中 strncpy實現
char *strncpy(char *dest, const char *src, size_t count)
{
char *tmp = dest;
while (count) {
if ((*tmp = *src) != 0)
src++;
tmp++;
count--;
} /*Warning:!!!未設定dest結束:'/0' !!!*/
return dest;
}
注意比較linux下的strcpy與strncpy的不同:
strcpy:while ((*dest++ = *src++) != '/0')
/*運行結束時,*dest的值為:'/0' ,已經有了結束標誌。*/
strncpy: if ((*tmp = *src) != 0) /*為什麼可以用0來判斷???*/
/*運行結束時,*tmp的值不確定 ,不一定有結束標誌(若參數count小於src字元個數),那樣tmp將一直讀取到記憶體中的'/0'為止。*/
為什麼這段代碼的strlen(tmp) 返回6?
#include <stdio.h>
#include <stdio.h>
#include <string.h>
char *strncpy_1(char *dest,const char *src,size_t count)
{
char *tmp = dest;
while(count)
{
if((*tmp=*src)!='/0')
{
src++;
tmp++;
}
count--;
}
if(*tmp!='/0')
*++tmp='/0';
return dest;
}
void print(char *ch)
{
char *tmp ;
for(tmp=ch;*tmp!='/0';tmp++)
printf("%c",*tmp);
printf("/n");
}
int main(void)
{
char *tmp;
char src[]="hello,world!";
char dest[20];
printf("The length of src is : %d/n",strlen(src));
printf("The source string is :/t");
print(src);
tmp = strncpy_1(dest,src,5);
printf("The destination string is :/t");
print(tmp);
printf("The length of dest is : %d/n",strlen(tmp));
return 0;
}