本文將對常用的string函數的用法以及實現做一個總結,將介紹的函數如下:
strcspn、strcpy、strcat、 strchr、strncmp、strcmp、strstr、strlen
一、strcpy函數
1、strcpy概述
函數原型:
char * strcpy ( char * destination, const char * source );
把從src地址開始且含有’\0’結束符的字符串複製到以dest開始的地址空間
src和dest所指內存區域不可以重疊且dest必須有足夠的空間來容納src的字符串。複製發生在兩個重疊的對象中,則這種行爲未定義。如果dest空間不夠,則會引起 buffer overflow。
最終返回指向dest的指針。
2、實現
2.1、實現一
//
//C語言標準庫函數strcpy的一種典型的工業級的最簡實現。
//返回值:目標串的地址。
//對於出現異常的情況ANSI-C99標準並未定義,故由實現者決定返回值,通常爲NULL。
//參數:des爲目標字符串,source爲原字符串。
char* strcpy(char* des,const char* source)
{
assert((des != NULL) && (source != NULL));
char* r=des;
while((*r++ = *source++)!='\0');
return des;
}
2.2、實現二
char * strcpy(char * strDest,const char * strSrc)
{
if ((NULL==strDest) || (NULL==strSrc))
//[1]
throw "Invalid argument(s)";
//[2]
char * strDestCopy = strDest;
//[3]
while ((*strDest++=*strSrc++)!='\0');
//[4]
return strDestCopy;
}
這個代碼有幾個容易出錯的地方需要注意:
錯誤[1]:
- 不檢查指針的有效性;
- 檢查指針的有效性時使用((!strDest)||(!strSrc))或(!(strDest&&strSrc))。在本例中char *轉換爲bool是類型隱式轉換,這種功能雖然靈活,但更多的是導致出錯概率增大和維護成本升高。所以C++專門增加了bool、true、false三個關鍵字以提供更安全的條件表達式。
- 檢查指針的有效性時使用(strDest==0)這種做法。直接使用字面常量(如本例中的0)會減少程序的可維護性。0雖然簡單,但程序中可能出現很多處對指針的檢查,萬一出現筆誤,編譯器不能發現,生成的程序內含邏輯錯誤,很難排除。而使用NULL代替0,如果出現拼寫錯誤,編譯器就會檢查出來。
錯誤[2]:
- return new string(“Invalid argument(s)”);,說明答題者根本不知道返回值的用途,並且他對內存泄漏也沒有警惕心。從函數中返回函數體內分配的內存是十分危險的做法,他把釋放內存的義務拋給不知情的調用者,絕大多數情況下,調用者不會釋放內存,這導致內存泄漏。
- return 0;,說明答題者沒有掌握異常機制。調用者有可能忘記檢查返回值,調用者還可能無法檢查返回值(見後面的鏈式表達式)。妄想讓返回值肩負返回正確值和異常值的雙重功能,其結果往往是兩種功能都失效。應該以拋出異常來代替返回值,這樣可以減輕調用者的負擔、使錯誤不會被忽略、增強程序的可維護性。
錯誤[3]:
忘記保存原始的strDest值,說明答題者邏輯思維不嚴密。因爲如果沒保存strDest,那麼返回的就是’\0’,因爲此時strDest指向末尾’\0’
錯誤[4]:
- 循環寫成while (*strDestCopy++=*strSrc++);忘記檢查指針有效性。
- 循環寫成while (*strSrc!=’\0’) *strDest++=*strSrc++;,說明答題者對邊界條件的檢查不力。循環體結束後,strDest字符串的末尾沒有正確地加上’\0’。
爲什麼要返回strDest了?
返回strDest的原始值使函數能夠支持鏈式表達式,增加了函數的“附加值”。
鏈式表達式的形式如:
int iLength=strlen(strcpy(strA,strB));
又如:
char * strA=strcpy(new char[10],strB);
返回strSrc的原始值是錯誤的。
- 其一,源字符串肯定是已知的,返回它沒有意義;
- 其二,不能支持形如第二例的表達式;
- 其三,爲了保護源字符串,形參用const限定strSrc所指的內容,把const char 作爲char 返回,類型不符,編譯報錯。
3、應用實例
void main( int argc, char * argv[] )
{
char a[20], c[] = "i am teacher!";
try
{
strcpy(a,c);
}
catch(char* strInfo)
{
cout << strInfo << endl;
exit(-1);
}
cout << a << endl;
}
4、strncpy
函數原型:
char *strncpy(char *dest,char *src,size_t n);
複製字符串src中的內容(字符,數字、漢字….)到字符串dest中,複製多少由size_tn的值決定。如果src的前n個字符不含’\0’字符,則結果不會以’\0’字符結束。如果n
5、strcpy_s
strcpy函數,就象gets函數一樣,它沒有方法來保證有效的緩衝區尺寸,所以它只能假定緩衝足夠大來容納要拷貝的字符串。在程序運行時,這將導致不可預料的行爲。用strcpy_s就可以避免這些不可預料的行爲。它加強了對參數合法性的檢查以及緩衝區邊界的檢查,如果發現錯誤,會返回errno或拋出異常
函數原型:
errno_t strcpy_s(char *strDestination, size_t numberOfElements, const char *strSource);
或者,
template <size_t size>
errno_t strcpy_s(char (&strDestination)[size], const char *strSource ); // C++ only
舉個例子:
char szBuf[3];
memset(szBuf,0,sizeof(szBuf));
strcpy_s(szBuf, sizeof(szBuf), "12131"); //新的CRT函數
strcpy(szBuf, "12131"); //老的CRT函數
上述代碼,明顯有緩衝區溢出的問題。 使用strcpy_s函數則會拋出一個異常。而使用strcpy函數的結果則未定,因爲它錯誤地改變了程序中其他部分的內存的數據,可能不會拋出異常但導致程序數據錯誤,也可能由於非法內存訪問拋出異常。
二、strlen函數
1、strlen概述
strlen()函數用來計算字符串的長度,不包括結束字符”\0”。,其原型爲:
unsigned int strlen (char *s);
頭文件:#include
2、strlen實現
2.1、實現一
int strlen(const char *str)
{
int i=0;
while(s[i] != '\0')
++i;
return i;
}
2.2、實現二
int strlen(const char *str)
{
int count=0;
while((*str++)!='\0')
{
count++;
}
return count;
}
2.3、實現三
int strlen(char *s)
{
char *p = s;
while (*p != '\0')
p++;
return p - s;
}
2.4、實現四
int strlen(const char *str)
{
assert(NULL !=str);
if(*str == '\0')
return 0;
return (1+strlen(++str));
}
2.5、實現五
int strlen(const char*str)
{
assert(NULL != str);
return ('\0'!= *str)?(1+strlen(++str)):0;
}
3、strlen與sizeof區別
- strlen() 函數計算的是字符串的實際長度,遇到第一個’\0’結束。如果你只定義沒有給它賦初值,這個結果是不定的,它會從首地址一直找下去,直到遇到’\0’停止。
- sizeof返回的是變量聲明後所佔的內存數,不是實際長度,此外sizeof不是函數,僅僅是一個操作符,strlen()是函數。
- sizeof可以用類型做參數,也可以用函數做參數;strlen只能用char*做參數,且必須是以”\0”結尾的。
- sizeof編譯時計算,strlen運行才計算出來
char str[10];
cout<<strlen(str)<<endl; //結果是不定的
cout<<sizeof(str)<,endl; //結果是10
char str[10]={'\0'};
cout<<strlen(str)<<endl; //結果爲0
cout<<sizeof(str)<,endl; //結果是10
3.1、處理靜態數組
char str[20]="0123456789";
int a=strlen(str); // 結果爲a=10
int b=sizeof(str); // 結果b=20;
3.2、處理指針
char* ss = "0123456789";
int a=sizeof(ss); // 結果4
int b=strlen(ss); //結果10
三、strstr函數
1、strstr概述
strstr函數的原型爲:
const char * strstr ( const char * str1, const char * str2 );
char * strstr (char * str1, const char * str2 );
strstr(str1,str2) 函數用於判斷字符串str2是否是str1的子串。如果是,則該函數返回str2在str1中首次出現的地址;否則,返回NULL。
舉個例子:
/* strstr example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] ="This is a simple string";
char * pch;
pch = strstr (str,"simple");
puts (pch); //輸出simple string
return 0;
}
2、strstr實現
2.1、實現一
這個是我寫的最原始版本,基本沒用庫函數
char *mstrstr(const char *s1, const char *s2)
{
if (!s1 || !s2 || strlen(s2) > strlen(s1)) return NULL;
if(strlen(s2)==0) return (char*)s1;
while (*s1 != '\0')
{
const char *pos = s1;
while (*(s1) == *(s2))
{
s1++,s2++;
if (*s2 == '\0')
{
return (char *)pos;
}
}
s1 == pos ? ++s1: pos;
}
return NULL;
}
2.2、實現二
用了庫函數strncmp。
char *mstrstr(const char *s1, const char *s2)
{
while (*s1&&*s2)
{
if (strncmp(s1, s2, strlen(s2)) == 0)
return (char*)s1;
s1++;
}
return NULL;
}
四、strcmp函數
1、strcmp概述
函數原型爲:
int strcmp ( const char * str1, const char * str2 );
具體比較過程是這樣的,將s1 第一個字符值減去s2 第一個字符值,若差值爲0 則再繼續比較下個字符,直到字符結束標誌’\0’,若差值不爲0,則將差值返回。
因此結果,有以下三種情況:
- 若str1==str2,則返回零;
- 若str1
2、strcmp實現
2.1、實現一
int mstrcmp(const char * str1, const char * str2)
{
assert(NULL != str1&&NULL != str2);
while (str1&&str2)
{
if (*str1 != *str2)
return *str1 - *str2;
str1++, str2++;
}
return 0;
}
2.2、實現二
int mstrcmp(const char * str1, const char * str2)
{
assert(NULL != str1&&NULL != str2);
while (*str1&&*str2&&*str1 == *str2) //到'\0'就停止,不然取值就會有問題
{
str1++, str2++;
}
return *str1 - *str2 ;
}
五、strncmp函數
1、strncmp概述
函數原型爲:
int strncmp ( const char * str1, const char * str2, size_t num );
此函數與strcmp極爲類似。不同之處是,strncmp函數是指定比較num個字符。也就是說,如果字符串s1與s2的前num個字符相同,函數返回值爲0。
2、實現
2.1、實現一
int mstrncmp(const char * str1, const char * str2, size_t num)
{
assert(NULL != str1&&NULL != str2);
while (*str1&&*str2&&num--)
{
if (*str1 != *str2)
return *str1 - *str2;
str1++, str2++;
}
return 0;
}
2.2、實現二
int mstrncmp(const char * str1, const char * str2, size_t num)
{
assert(NULL != str1&&NULL != str2);
while (*str1&&*str2&&*str1==*str2&&num--)
{
str1++, str2++;
}
return *str1-*str2;
}
六、strchr函數
1、strchr概述
1.1、函數原型
const char * strrchr ( const char * str, int character );
char * strrchr ( char * str, int character );
找到在字符串str中最後初選character的位置。
應用舉例:
/* strrchr example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "This is a sample string";
char * pch;
pch=strrchr(str,'s');
printf ("Last occurence of 's' found at %d \n",pch-str+1); //Last occurrence of 's' found at 18
return 0;
}
2、實現
2.1、實現一
順着往後找。
const char * mstrrchr(const char * str, int character)
{
assert(str != NULL);
const char *pos = NULL;
while (*str)
{
if (*str == (char)character)
pos = str;
str++;
}
return pos;
}
2.2、實現二
從後往前來。
const char * mstrrchr(const char * str, int character)
{
assert(str != NULL);
const char *start = str;
while (*str++);
while (str-- != start&&*str != (char)character);
return *str == (char)character?str:NULL;
}
七、strcat函數
1、strcat概述
1.1、函數原型爲:
char * strcat ( char * destination, const char * source );
- destination:是目標字符串,這個字符串應該有足夠的內存來保存source.
- source:要跟destination合併的字符串,這個字符串不能跟destination有內存重疊。
1.2、應用舉例:
/* strcat example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[80];
strcpy (str,"these ");
strcat (str,"strings ");
strcat (str,"are ");
strcat (str,"concatenated.");
puts (str); //these strings are concatenated.
return 0;
}
2、實現
對於strcat,必須要考慮的就是合併後要有足夠的內存來保存新的合併後的字符串。對於這個問題,可以有兩種解決辦法,分別是調用者和實現者:
- 如果由調用者來解決這個問題的話,那麼調用者就提前申請好內存,然後進行合併操作;
- 如果由strcat代碼實現者來解決這個問題的話,那麼在strcat代碼內就必須申請內存來容納增加的字符串.我的想法是用relloc或malloc申請一段內存來保存合併後的字符串。
從strcat官方文檔上的介紹來看,對於strcat的使用,內存問題似乎是調用者需要考慮的。由調用者來解決strcat的內存問題,那strcat的實現就簡單了。
代碼如下:
char * mstrcat(char *dest, const char *src)
{
char *rdest = dest;
while (*dest)
dest++;
while (*dest++ = *src++);
return rdest;
}
3、strcat_s
對於strcat,可以發現,它是由隱患的,如果內存不夠,出現溢出怎麼辦,所以爲了安全起見,除了一個“安全版本”strcat_s,它安全在哪兒了,它首先檢查內存大小師傅足夠,如果夠,才進行合併操作,如果不夠拋出異常。
函數原型:
errno_t strcat_s(char *strDestination,size_t numberOfElements,const char *strSource );
舉個例子:
char str_des[5]="a";
char *str_source = "bcd";
strcat_s(str_des, sizeof(str_des), str_source);
上面剛好5個字符,5個字符內,沒問題,’\0’要佔據一個字符位置。
char str_des[5]="a";
char *str_source = "bcde";
strcat_s(str_des, sizeof(str_des), str_source);
現在超過5個字符了,拋出異常。
注意,str_des一定要初始化,如果當前沒值,可以用如下語句:
char str_des[5];
memset(str_des, 0, sizeof(str_des));
八、strcspn函數
1、函數原型:
size_t strcspn ( const char * str1, const char * str2 );
返回對於str2中任意一個字符最早出現在str1中的位置,也就是str1前n個全部是str2中字符。
2、應用舉例:
/* strcspn example */
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "fcba73";
char keys[] = "1234567890";
int i;
i = strcspn (str,keys);
printf ("The first number in str is at position %d.\n",i+1); //結果是5
return 0;
}
3、實現
實現一
size_t mstrcspn(const char *s1, const char* s2)
{
assert(NULL!=s1&&NULL!=s2);
int i, j;
for (i = 0; s1[i]; i++)
{
for (j = 0; s2[j]; j++)
{
if (s1[i] == s2[j]) return i;
}
}
return i;
}
實現二
size_t mstrcspn(const char * string, const char * control)
{
const char *str = string;
const char *ctrl = control;
unsigned char map[32];
int count;
/* Clear out bit map */
for (count = 0; count<32; count++)
map[count] = 0;
/* Set bits in control map */
while (*ctrl)
{
map[*ctrl >> 3] |= (1 << (*ctrl & 7));
ctrl++;
}
/* 1st char in control map stops search */
count = 0;
map[0] |= 1; /* null chars not considered */
while (!(map[*str >> 3] & (1 << (*str & 7))))
{
count++;
str++;
}
return(count);
}
一個ASCII碼有8位,把它分爲2部分, 高5 位i和低3位j兩部分.
- 高5位表示的範圍有32個.所以申請了 32 大小的數組map。高5位通過*str>>3得到;
- 然後低3位表示 0~7 這8個數值.正好map的每一項是 char (8位)的,所以 後3位就存放到相應的位上。低三位通過*str&7得到。
這就相當於一個二維數組map[i][j]
九、strspn函數
1、函數原型:
size_t strspn ( const char * str1, const char * str2 );
strspn與strcspn恰恰相反,返回str1中最早的一個不是str2中字符的位置,也就是str1前n個都是str2中的字符。
2、應用舉例
/* strspn example */
#include <stdio.h>
#include <string.h>
int main ()
{
int i;
char strtext[] = "129th";
char cset[] = "1234567890";
i = strspn (strtext,cset);
printf ("The initial number has %d digits.\n",i); //結果是3
return 0;
}
3、實現
實現一
size_t mstrspn(const char *s1, const char* s2)
{
assert(NULL!=s1&&NULL!=s2);
int i, j;
for (i = 0; s1[i]; i++)
{
for (j = 0; s2[j]; j++)
{
if (s1[i] == s2[j]) break;
}
if (s1[i] != s2[j]) return i;
}
return i;
}
實現二
size_t mstrspn(const char * string, const char * control)
{
const char *str = string;
const char *ctrl = control;
unsigned char map[32];
int count;
/* Clear out bit map */
for (count = 0; count<32; count++)
map[count] = 0;
/* Set bits in control map */
while (*ctrl)
{
map[*ctrl >> 3] |= (1 << (*ctrl & 7));
ctrl++;
}
/* 1st char NOT in control map stops search */
if (*str)
{
count = 0;
while (map[*str >> 3] & (1 << (*str & 7)))
{
count++;
str++;
}
return(count);
}
return(0);
}
3、兩者合成版本實現
通過條件編譯命令實現,這是完整源碼,果然不一樣
#define _STRSPN 1
#define _STRCSPN 2
#define _STRPBRK 3
#if defined (SSTRCSPN)
#define ROUTINE _STRCSPN
#elif defined (SSTRPBRK)
#define ROUTINE _STRPBRK
#else
#define ROUTINE _STRSPN //默認
#endif
/* Routine prototype */
#if ROUTINE == _STRSPN
size_t __cdecl strspn(
#elif ROUTINE == _STRCSPN
size_t __cdecl strcspn(
#else /* ROUTINE == _STRCSPN */
char * __cdecl strpbrk(
#endif /* ROUTINE == _STRCSPN */
const char * string,
const char * control
)
{
const char *str = string;
const char *ctrl = control;
unsigned char map[32];
int count;
/* Clear out bit map */
for (count = 0; count<32; count++)
map[count] = 0;
/* Set bits in control map */
while (*ctrl) //跟strtok()函數中的使用方式是一樣的
{
map[*ctrl >> 3] |= (1 << (*ctrl & 7));
ctrl++;
}
#if ROUTINE == _STRSPN // strspn()
/* 1st char NOT in control map stops search */
if (*str)
{
count = 0;
while (map[*str >> 3] & (1 << (*str & 7)))
{
count++;
str++;
}
return(count);
}
return(0);
#elif ROUTINE == _STRCSPN //strcspn()
/* 1st char in control map stops search */
count = 0;
map[0] |= 1; /* null chars not considered */
while (!(map[*str >> 3] & (1 << (*str & 7))))
{
count++;
str++;
}
return(count);
#else /* ROUTINE == _STRCSPN */ //strpbrk()
/* 1st char in control map stops search */
while (*str)
{
if (map[*str >> 3] & (1 << (*str & 7)))
return((char *)str);
str++;
}
return(NULL);
#endif /* ROUTINE == _STRCSPN */
}
4、推薦資料
[1]位操作總結
[2]strtok源碼剖析 位操作與空間壓縮
十、總結
知其然,還得知其所以然。平時只是用,沒有探究過如何實現,現在總結一下,收穫頗豐。多學習,多思考,多總結!