淺析C語言中關(guān)于字符串的操作
作者:杭州書誠
前言:如果您是學C/C++的,,對于字符串的操作不是很了解,請您耐心讀完,。作為我的朋友,,我很樂意和您分享我最近的知識積累。畢竟,,網(wǎng)上很少有這么全,,這么細的介紹,更少有人愿意花時間收集N個相關(guān)帖子,,讀懂,,并將零散的知識點整理,并思考函數(shù)之間可能的聯(lián)系或改進方法,。如果您覺得不錯,,請您分享,您的支持就是給我繼續(xù)整理的動力,。
一.字符串相關(guān)操作分類介紹(招式篇)
1.字符串拷貝相關(guān)操作,。
a. strcpy:
char *strcpy (char *dest, const char *src);
復制字符串src到dest中。返回指針為dest的值,。
b. strncpy:
char *strncpy (char *dest, const char *src, size_t n);
復制字符串src到dest中,,最多復制n個字符。返回指針為dest的值,。
c. strdup:
char *strdup (const char *s);
得到一個字符串s的復制,。返回指針指向復制后的字符串的首地址。
d. memcpy:
void *memcpy (void *dest, const void *src, size_t n);
從src所指向的對象復制n個字符到dest所指向的對象中,。返回指針為dest的值,。
e .mem**y:
void *mem**y (void *dest, const void *src, int c, size_t n);
從src所指向的對象復制n個字符到dest所指向的對象中,。如果復制過程中遇到了字符c則停止復制,返回指針指向dest中字符c的下一個位置,;否則返回NULL,。
f. memmove:
void *memmove (void *dest, const void *src, size_t n);
從src所指向的對象復制n個字符到dest所指向的對象中。返回指針為dest的值,。不會發(fā)生內(nèi)存重疊,。
2.字符串比較相關(guān)操作
a. strcmp:
int strcmp (const char *s1, const char *s2);
比較字符串s1和字符串s2。返回值是s1與s2第一個不同的字符差值的符號,,0表示相同,,1表示正號,-1表示負號,。
b. strncmp:
int strncmp (const char *s1, const char *s2, size_t n);
比較字符串s1和字符串s2,,最多比較n個字符。返回值是s1與s2第一個不同的字符差值的符號,,0:表示相同,,1:表示正號,-1:表示負號,。
c. stricmp:
int stricmp (const char *s1, const char *s2);
比較字符串s1和字符串s2,,忽略大小寫。返回值是s1與s2第一個不同的字符差值的符號,,0:表示相同,,1:表示正號,-1:表示負號,。
d. strnicmp:
int strnicmp(const char *s1, const char *s2, size_t n);
比較字符串s1和字符串s2,,忽略大小寫,最多比較n個字符,。返回值是s1與s2第一個不同的字符差值,。
e. memcmp:
int memcmp (const void *s1, const void *s2, size_t n);
比較s1所指向的對象和s2所指向的對象的前n個字符。返回值是s1與s2第一個不同的字符差值,。
f. memicmp:
int memicmp (const void *s1, const void *s2, size_t n);
比較s1所指向的對象和s2所指向的對象的前n個字符,,忽略大小寫。返回值是s1與s2第一個不同的字符差值的符號,,0:表示相同,,1:表示正號,-1:表示負號,。
3.字符串大小寫轉(zhuǎn)換相關(guān)操作
a. strlwr:
char *strlwr (char *s);
將字符串s全部轉(zhuǎn)換成小寫,。返回指針為s的值。
b. strupr:
char *strupr (char *s);
將字符串s全部轉(zhuǎn)換成大寫,。返回指針為s的值,。
4.字符串連接相關(guān)操作
a. strcat:
char *strcat (char *dest, const char *src);
將字符串src添加到dest尾部,。返回指針為dest的值。
b. strncat:
char *strncat (char *dest, const char *src, size_t n);
將字符串src添加到dest尾部,,最多添加n個字符,。返回指針為dest的值。
5.字符串子串相關(guān)操作
a. strstr:
char *strstr (const char *s1, const char *s2);
在字符串s1中搜索字符串s2,。如果搜索到,,返回指針指向字符串s2第一次出現(xiàn)的位置;否則返回NULL,。
b. strcspn:
size_t strcspn (const char *s1, const char *s2);
返回值是字符串s1的完全由不包含在字符串s2中的字符組成的初始串長度。
c. strspn:
size_t strspn (const char *s1, const char *s2);
返回值是字符串s1的完全由包含在字符串s2中的字符組成的初始串長度,。
d. strpbrk:
char *strpbrk (const char *s1, const char *s2);
返回指針指向字符串s1中字符串s2的任意字符第一次出現(xiàn)的位置,;如果未出現(xiàn)返回NULL。
e. strtok:
char *strtok (char *s1, const char *s2);
用字符串s2中的字符做分隔符將字符串s1分割,。返回指針指向分割后的字符串,。第一次調(diào)用后需用NULLL替代s1作為第一個參數(shù)。
6.字符串與單個字符相關(guān)操作
a. strchr:
char *strchr (const char *s, int c);
在字符串s中搜索字符c,。如果搜索到,,返回指針指向字符c第一次出現(xiàn)的位置;否則返回NULL,。
b. strrchr:
char *strrchr (const char *s, int c);
在字符串s中搜索字符c,。如果搜索到,返回指針指向字符c最后一次出現(xiàn)的位置,;否則返回NULL,。
c. memchr:
void * memchr (const void *s, int c, size_t n);
在s所指向的對象的前n個字符中搜索字符c。如果搜索到,,返回指針指向字符c第一次出現(xiàn)的位置,;否則返回NULL。
d. memset:
void *memset (void *s, int c, size_t n);
設置s所指向的對象的前n個字符為字符c,。返回指針為s的值,。
e. strnset:
char *strnset (char *s, int ch, size_t n);
設置字符串s中的前n個字符全為字符c。返回指針為s的值,。
f. strset:
char *strset (char *s, int ch);
設置字符串s中的字符全為字符c,。返回指針為s的值。
7..字符串求字符串長度相關(guān)操作
a. strlen:
size_t strlen (const char *s);
返回值是字符串s的長度,。不包括結(jié)束符\0,。
8..字符串錯誤相關(guān)操作
a. strerror:
char *strerror(int errnum);
返回指針指向由errnum所關(guān)聯(lián)的出錯消息字符串的首地址。errnum的宏定義見errno.h,。
9..字符串反置操作
a. strrev:
char *strrev (char *s);
將字符串全部翻轉(zhuǎn),,返回指針指向翻轉(zhuǎn)后的字符串,。
二.記憶方法(記憶心法精要)
最好的方法莫過于多用了,但是具體函數(shù)的名字的記憶也是有技巧的,。
str : 字符串 cmp : 比較 n : 代表范圍 i : 表示不區(qū)分大小寫
rev : 翻轉(zhuǎn) error : 錯誤 len : 長度 mem :內(nèi)存
cat : 連接 lwr : 小寫 upr:大寫
set : 設置(單個字符) chr : 單個字符(查找)
注:
1.只要是關(guān)于內(nèi)存操作(mem…)的,,則必有一個參數(shù)size_t n,表示對操作內(nèi)存的大小。這與strn…函數(shù)在功能上類似,,只是,,strn…函數(shù)一般在沒達到n個字節(jié)之前遇到空字符時會結(jié)束,而mem…遇到空字符并不結(jié)束,。
2.str/mem +[n] chr : 表示在字符串中查找某個字符,,而str/mem +[n] set : 表示把字符串設置成某個字符,n表示作用范圍為前n個,。
3.strdup中返回的指針是new出來的,,用完一定要記得delete。
三.源碼 && 解析 && BUG:(內(nèi)功篇)
1.字符串拷貝相關(guān)操作,。
a. strcpy: 復制字符串src到dest中,。返回指針為dest的值。
char *strcpy(char * dest, const char * src)
{
assert( (dest!=NULL)&&( src!=NULL) );
char *p = dest;
while(*p ++=* src ++);
return (dest);
}
解析:
1.assert:斷言,,即assert(),,()里的一定為真,否則程序報錯,。
2.*src++中,,++后置的優(yōu)先級比*高,所以相當于*(src++),src++的意思是,,先取出src的值,,再++,所以*src++;等同于:*src;src++;
3.將src指針所指地址中所有字符串都復制到dest指針所指地址中,直至遇到空字符才結(jié)束,。并且dest和返回指針都指向拷貝后地址空間的首地址,。
BUG:
沒有對src指針所指地址空間的大小進行檢查(實際是無法進行檢查),所以,,當所需要拷貝的字符串超出src指針所指地址空間的大小時,,內(nèi)存出錯。
案例:
#include"iostream"
using namespace std;
void strcpyTest0()
{
int i;
char *szBuf = new char[128];
for(i=0;i<128;i++)
szBuf[i]=''*'';
szBuf[127]=''\0''; //構(gòu)造一個全部是*的字符串
char szBuf2[256];
for(i=0;i<256;i++)
szBuf2[i]=''#'';
szBuf2[255]=''\0''; //構(gòu)造一個全部是#的字符串
strcpy(szBuf,szBuf2);
cout<<szBuf<<endl;
}
int main(void)
{
strcpyTest0();
return 0;
}
結(jié)果:在此程序中,,雖然輸出了255個#,,但是卻彈出了內(nèi)存錯誤,這個原因很好解釋,,因為szBuf本身只有128個字節(jié),,可卻給它拷貝了256個字節(jié)的內(nèi)容。從其源碼可以看出,strcpy將會把szBuf2中的所有內(nèi)容全部考到szBuf中,,直至遇到了空字符,。所以,切忌,,在用strcpy函數(shù)時要保證szBuf有足夠的內(nèi)存空間,,否則會出錯。
猜測性改進方法1:
char *strcpy(char dest[], const char src[])
{
assert( (dest!=NULL)&&( src!=NULL) );
char *p = dest;
int Length = strlen (dest);
while(*p++=*src++ &&Length--) {}
* (p – 1) = ''\0'';
return (dest);
}
這樣就可以使拷貝到dest中的數(shù)不會超過dest所擁有的空間大小了,。經(jīng)調(diào)試,,確實不會超過dest所指大小,也就沒有了內(nèi)存錯誤,。但是,,strlen只是求得該字符串的長度遇到''\0''就結(jié)束了,假如上述案例中szBuf[127]=''\0'';改為szBuf[10]=''\0'';則szBuf中只能拷貝10個字符,,再加一個''\0''結(jié)尾,,可szBuf明明是有127個字符空間的,所以此法不行,。
猜測性改進方法2:把dest所能存儲的字符長度作為參數(shù)傳進去,這樣就產(chǎn)生了strncpy函數(shù),,具體請見strncpy函數(shù),。
猜測性改進方法3:僅僅傳入需要拷貝的地址,,然后再new出一塊內(nèi)存存放拷貝的字符串,,再返回new出來的內(nèi)存的首地址,最后由調(diào)用函數(shù)delete,這樣就產(chǎn)生了strdup函數(shù),,具體請見strdup函數(shù),。
b. strncpy: 復制字符串src到dest中,最多復制n個字符,。返回指針為dest的值。
char *strncpy (char *dest, const char *src, size_t n)
{
assert( (dest!=NULL)&&( src!=NULL) );
char *p = dest;
while (n && (*p++ = *src++))
n --;
while(n --)
*p++ = ''\0'';//遇空字符結(jié)束后,,將其后所有字符付為空字符
return(dest);
}
解析:
1.若src所指地址空間的字符串個數(shù)<n,,則將src所指地址空間的字符串附給dest后,,再將其后的n-strlen(src)附為空字符,。
2. dest和返回指針都指向拷貝后地址空間的首地址。
BUG:
當n比源字符串空間要小的時候,,strncpy并沒有用”\0”來結(jié)束字符串,。從上述源碼可以看出當n =0時,是不會執(zhí)行while(n--) *p++ = ''\0'';的,,這樣,,如果在前面并沒付”\0”來結(jié)束字符串,則后面也不會再付”\0”來結(jié)束字符串,。
案例:
#include"iostream"
using namespace std;
void strcpyTest1()
{
int i;
char szBuf[128];
for(i=0;i<128;i++)
szBuf[i]=''*'';
szBuf[127]=''\0'';
char szBuf2[256];
for(i=0;i<256;i++)
szBuf2[i]=''#'';
szBuf2[255]=''\0'';
strncpy(szBuf,szBuf2,10);
cout<<szBuf<<endl;
}
int main(void)
{
strcpyTest1();
return 0;
}
結(jié)果可不是:##########,,而是:##########*********************************************************************************************************************
改進方法:
char *strncpy(char * dest, const char * src, size_t n)
{
assert( (dest!=NULL)&&( src!=NULL) );
char *p = dest;
while (n && (*p++ = *src++))
n--;
while(n--)
*p++ = ''\0'';//遇空字符結(jié)束后,將其后所有字符付為空字符
*p =''\0'';
return(dest);
}
注:覺得這個BUG本可以避免的,,這絕對是他們故意的,。
c. strdup: 得到一個字符串str的復制。返回指針指向復制后的字符串的首地址,。
char *strdup (const char *str);
{
char *p;
if (!str)
return(NULL);
if (p = malloc(strlen(str) + 1))
return(strcpy(p,str));
return(NULL);
}
解析:
1.沒有assert語句,,允許str為NULL
2. str為NULL,返回NULL,,否則返回指向復制后的字符串的首地址,。
BUG:
在strdup中申請的內(nèi)存空間,必須要在調(diào)用函數(shù)中釋放,,這樣就使內(nèi)存的申請和釋放分開,,用戶很容易忘記了主動施放內(nèi)存,會導致內(nèi)存泄漏,。
改進方法:
通過命名方式提醒用戶主動釋放內(nèi)存:如在函數(shù)名之前加
ndp_ : need delete pointer 需要主動施放指針
nfp_ : need free pointer 需要主動施放指針
ndpa_ : need delete pointer array 需要主動施放指針數(shù)組
nfpa_ : need delete pointer array 需要主動施放指針數(shù)組
思想借鑒:
鑒于這種思想,,在傳遞參數(shù)時,如果需要傳出的參數(shù)的空間大小在調(diào)用此函數(shù)前是不知道的,,又不想浪費空間,,則可以考慮new — delete。如果再用到上述的命名方法,,則將提醒用戶,,不至于使用戶忘記釋放內(nèi)存。這樣可能會使部分函數(shù)的名字很難記憶,,那么如果把ndp_改為_ndp加在函數(shù)名之后,,則可借助VC assist輕松獲得該函數(shù)。
我有一種想法,,只要大家都能理解這種思想,,并且這么做,這種動態(tài)開辟內(nèi)存的方法也許有可能會改變我們現(xiàn)有的編程模式,。
我現(xiàn)在還不是很明白動態(tài)開辟和釋放在時間性能上的弱點,,也許這種方法會給程序帶來災難,希望知道的人能夠告訴我,這種方式在帶來空間上的不浪費的優(yōu)點的同時會有哪些潛在的危險和不足,。
d. memcpy: 從src所指向的對象復制n個字符到dest所指向的對象中,。返回指針為dest的值。
void *memcpy (void *dest, const void *src, size_t n)
{
assert((dest!=NULL)&&( src!=NULL));
void * p = dest;
while (n --)
{
*(char *)p = *(char *)src;
p = (char *)dest + 1;
src = (char *)src + 1;
}
return(dest);
}
解析:
1. 精妙之處在于指針之間的轉(zhuǎn)換,。傳進來的參數(shù)是兩個void類型的指針,,均指向內(nèi)存中的某一塊地址,而這個地址本身都是四個字節(jié)的,,不管是什么類型的地址都是四個字節(jié),,那么只要經(jīng)過強制轉(zhuǎn)化,則void型指針可以轉(zhuǎn)化為任意類型的指針,。(類型名)指針名,當然它傳出的也是void型的指針,,同理也可以轉(zhuǎn)化成任意類型的,那么這個函數(shù)的功能可就不僅僅是拷貝字符串了,,任何存在類存的東西都能考的,。具體見下案例。
2.dest和返回指針都指向拷貝后內(nèi)存空間的首地址,。
案例:
#include"iostream"
using namespace std;
void strcpyTest1()
{
int A=1;
int B=0xffffffff;
memcpy(&A,&B,1);
cout<<A<<endl;
}
int main(void)
{
strcpyTest1();
return 0;
}
結(jié)果:將會輸出255,,為什么不是0xffffffff呢,因為你只拷貝了一個字節(jié),,而int型的是4個字節(jié),,所以,你只要將memcpy(&A,&B,1);改為memcpy(&A,&B,4);就把B的值付給了A,。當然是-1了,。啊,,不明白為什么,?原碼,補碼,,移碼,,還有有符號數(shù),無符號數(shù)總知道了吧,,還不知道,,那只好去看看唐碩飛老師的組成原理了,就是考驗推薦的參考書之一,。但同時也要注意,,n的值不能超過A和B的范圍,不然要么就是得到的數(shù)據(jù)是不合法的,,要么就是存儲不合法,。
至于越界了會不會崩掉,那還要拼人品,如果你復制的越界內(nèi)存已經(jīng)分配出去了,,將會出現(xiàn)內(nèi)存錯誤,,如果沒有,則可繼續(xù)運行,,但如果編譯器會強制規(guī)定對于某個變量的操作不能超出他的內(nèi)存范圍,,則可能不能通過運行。具體目前我也不是很清楚,,還望知情者點撥一下,。
e .mem**y:從src所指向的對象復制n個字符到dest所指向的對象中。如果復制過程中遇到了字符c則停止復制,,返回指針指向dest中字符c的下一個位置,;否則返回NULL。
void * mem**y(void *dest,const void *src,int c, size_t n)
{
while (n && (*((char *)(dest = (char *)dest + 1) - 1) =
*((char *)(src = (char *)src + 1) - 1)) != (char)c )
n--;
return(n ? dest : NULL);
}
解析:
1.模擬循環(huán)體執(zhí)行:
dest = (char *)dest + 1;
src = (char *)src + 1,;
(char *) (dest-1)= (char *) (src -1);
n && (*((char *) (dest-1)) != (char)c);
2.如此寫法,,不但難懂,且易錯,,最主要的是代碼行數(shù)太少,,影響工資,呵呵……
f. memmove:從src所指向的對象復制n個字符到dest所指向的對象中,。返回指針為dest的值,。不會發(fā)生內(nèi)存重疊。
void *memmove (void *dest, const void *src, size_t n);
{
void * ret = dst;
if (dst <= src || (char *)dst >= ((char *)src + n))
{
while (n--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
}
else
{
dst = (char *)dst + n - 1;
src = (char *)src + n - 1;
while (n--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst - 1;
src = (char *)src - 1;
}
}
return(ret);
}
解析:
1.當目的地址在源地址之后,,且目的地址和源地址之間有重疊區(qū)域時,,將從最后一個字符開始,將源地址區(qū)域字符串付給目的地址,,否則從第一個字符開始將源地址區(qū)域字符串付給目的地址,。這樣就能保證即使在源/目的地址區(qū)域有重疊的情況下也能正確的復制內(nèi)存。
BUG總結(jié)(精要):
1.copy函數(shù)要注意copy后目的地址空間不能越界,,有的是用參數(shù)控制,,有的并不控制,有的內(nèi)存立馬出錯,,有的概率性報錯,,cat 函數(shù)也要注意這些??傊饋砭褪且痪湓?,但凡修改內(nèi)存內(nèi)容的,要注意不能越界,,字符串處理操作除了受參數(shù) size_t n來控制范圍外,,遇到空字符結(jié)束,,而內(nèi)存處理操作只受參數(shù)size_t n控制。
2.mem 函數(shù)都涉及到內(nèi)存空間大小,,操作時一定要注意空間別越界,,越界了報錯是概率性的,可能你那運行沒錯,,客戶一運行就錯了,。memcpy對于目的地址在源地址之后,且目的地址和源地址之間有重疊區(qū)域這種情況下的拷貝是不正確的,,可以用memmove來代替,。
3.strncpy有一個結(jié)尾符不是空字符的BUG,也許人家就是這么設計的,,但你要記得n<strlen(src)時,,n個復制字符之后,并不以空字符結(jié)尾,。
2.字符串比較相關(guān)操作
a. strcmp: 比較字符串s1和字符串s2,。返回值是s1與s2第一個不同的字符差值的符號,0:表示相同,,1:表示正號,,-1:表示負號。
int strcmp (const char *s1, const char *s2)
{
assert((s1 != NULL) && (s2!= NULL));
int ret = 0 ;
while( ! (ret = *( unsigned char *) s1- *(unsigned char *) s2) && * s2)
s1++, s2++;
if ( ret < 0 )
ret = -1 ;
else if ( ret > 0 )
ret = 1 ;
return(ret);
}
解析:
1.逗號語句將本來的兩個語句合為一句,,省去了while循環(huán)的{},精簡代碼行數(shù),。
2.只涉及到讀取內(nèi)存,而不修改內(nèi)存,,故而只要傳入的參數(shù)本身沒有問題,,則不會出現(xiàn)現(xiàn)隱藏BUG。(我是這么覺得的)
b. strncmp:比較字符串s1和字符串s2,,最多比較n個字符,。返回值是s1與s2第一個不同的字符差值的符號,0:表示相同,,1:表示正號,,-1:表示負號,。
int strncmp(const char *s1,const char *s2, size_t n)
{
assert((s1 != NULL) && (s2!= NULL));
if (!n)
return(0);
while (--n && *s1 && *s1 == *s2)
s1++,s2++;
if (*(unsigned char *)s1 - *(unsigned char *)s2 > 0)
return 1;
else if (*(unsigned char *)s1 - *(unsigned char *)s2 <0)
return -1;
else
return 0;
}
解析:略
c. stricmp:比較字符串s1和字符串s2,,忽略大小寫。返回值是s1與s2第一個不同的字符差值的符號,,0:表示相同,,1:表示正號,-1:表示負號,。
int stricmp1(const char *s1, const char *s2)
{
assert((s1 != NULL) && (s2!= NULL));
int ch1, ch2;
do
{
if ( ((ch1 = (unsigned char)(*(s1++))) >= ''A'') &&(ch1 <= ''Z'') )
ch1 += 0x20;
if ( ((ch2 = (unsigned char)(*(s2++))) >= ''A'') &&(ch2 <= ''Z'') )
ch2 += 0x20;
} while ( ch1 && (ch1 == ch2) );
if (*(unsigned char *)s1 - *(unsigned char *)s2 > 0)
return 1;
else if (*(unsigned char *)s1 - *(unsigned char *)s2 <0)
return -1;
else
return 0;
}
解析:略
d. strnicmp:比較字符串s1和字符串s2,,忽略大小寫,,最多比較n個字符。返回值是s1與s2第一個不同的字符差值的符號,,0:表示相同,,1:表示正號,-1:表示負號,。
int strnicmp(const char *s1,const char *s2,size_t n)
{
int ch1, ch2;
do
{
if ( ((ch1 = (unsigned char)(*(s1++))) >= ''A'') &&(ch1 <= ''Z'') )
ch1 += 0x20;
if ( ((ch2 = (unsigned char)(*(s2++))) >= ''A'') &&(ch2 <= ''Z'') )
ch2 += 0x20;
} while ( --n && ch1 && (ch1 == ch2) );
if (*(unsigned char *)s1 - *(unsigned char *)s2 > 0)
return 1;
else if (*(unsigned char *)s1 - *(unsigned char *)s2 <0)
return -1;
else
return 0;
}
解析:略
e. memcmp: 比較buffer1所指向的對象和buffer2所指向的對象的前n個字符,。返回值是buffer1與buffer2第一個不同的字符差值的符號,0:表示相同,,1:表示正號,,-1:表示負號。
int memcmp(const void *buffer1,const void *buffer2,size_t n)
{
if (!n)
return(0);
while ( --n && *(char *)buffer1 == *(char *)buffer2)
{
buffer1 = (char *)buffer1 + 1;
buffer2 = (char *)buffer2 + 1;
}
if (*(unsigned char *)buffer1 - *(unsigned char *)buffer2 > 0)
return 1;
else if (*(unsigned char *)buffer1 - *(unsigned char *)buffer2 <0)
return -1;
else
return 0;
}
解析:略
f. memicmp: 比較s1所指向的對象和s2所指向的對象的前n個字符,,忽略大小寫,。返回值是buffer1與buffer2第一個不同的字符差值的符號,0:表示相同,,1:表示正號,,-1:表示負號。
int memicmp(const char *buffer1,const char *buffer2, size_t n)
{
int ch1, ch2;
do
{
if ( ((ch1 = (unsigned char)(*(buffer1++))) >= ''A'') &&(ch1 <= ''Z'') )
ch1 += 0x20;
if ( ((ch2 = (unsigned char)(*(buffer2++))) >= ''A'') &&(ch2 <= ''Z'') )
ch2 += 0x20;
} while ( --n && (ch1 == ch2) );
if (*(unsigned char *)(buffer1-1) - *(unsigned char *)(buffer2-1) > 0)
return 1;
else if (*(unsigned char *)(buffer1-1) - *(unsigned char *)(buffer2-1) <0)
return -1;
else
return 0;
}
解析:略
注:字符串比較相關(guān)操作只涉及到內(nèi)存的讀取,,而不修改內(nèi)存,,所以,不會導致嚴重的不可預測的不良結(jié)果,,源代碼已經(jīng)很清楚的展現(xiàn)了這些函數(shù)的功能,,我也不愿再多費唇舌。我曾在網(wǎng)上看到過有人說字符串比較相關(guān)操作的返回值是受比較的字符串的第一個不相同的字符之差,,只是我用的VC6.0編譯器是其之差的符號,,但具體可能因編譯器而定,為了代碼擁有更好的可復制性,,建議關(guān)于其返回的比較最好為 return_value > 0, return_value<0 和return_value == 0 ,,不要寫成1 == return_value,-1 == return_value,,可能有潛在的危險,。
三.字符串大小寫轉(zhuǎn)換相關(guān)操作
a. strlwr:將字符串str全部轉(zhuǎn)換成小寫。返回指針為str的值,。
char * strlwr(char *str)
{
char *p = str;
while (*p != ''\0'')
{
if(*p >= ''A'' && *p <= ''Z'')
*p = (*p) + 0x20;
p++;
}
return str;
}
b. strupr:將字符串str全部轉(zhuǎn)換成大寫,。返回指針為str的值。
char * strupr(char *str)
{
char *p = str;
while (*p != ''\0'')
{
if(*p >= ''a'' && *p <= ''z'')
*p -= 0x20;
p++;
}
return str;
}
注:雖然修改了內(nèi)存,,但必在其字符串范圍之內(nèi),,當然也可以設置BUG,就是傳進去需要大小寫轉(zhuǎn)換的字符串并沒有以空字符結(jié)束,,那么可以惡意的修改內(nèi)存了,,只是,,一般沒人不會這么無聊的讓自己的程序崩掉。
四.字符串連接相關(guān)操作
a. strcat: 將字符串src添加到dest尾部,。返回指針為dest的值,。
char * strcat1(char * dest,char * src)
{
char * p = dest + strlen(dest);
strcpy(p,src);
return dest;
}
b. strncat: 將字符串src添加到dest尾部,最多添加n個字符,。返回指針為dest的值,。
char * strncat1(char *dest,const char *src,size_t n)
{
char *p = dest + strlen(dest);
while (n-- && (*p++ = *src++));
*p = ''\0'';
return(dest);
}
注:連接字符串時也要注意是否越界,分析可以參考拷貝類相關(guān)操作,。用的時候多注意下,,不至于導致很恐怖的事。
五.字符串子串相關(guān)操作
a. strstr: 在字符串s1中搜索字符串s2,。如果搜索到,,返回指針指向字符串s2第一次出現(xiàn)的位置;否則返回NULL,。
char * strstr(const char *s1,const char *s2)
{
assert((s1!=NULL) && (s2!=NULL));
if (*s1 == 0)
{
if (*s2)
return (char *) NULL;
return (char *) s1;
}
while (*s1)
{
size_t i = 0;
while (1)
{
if (s2[i] == 0)
return (char *) s1;
if (s2[i] != s1[i])
break;
i++;
}
s1++;
}
return (char *) NULL;
}
b. strcspn: 返回字符串s1的完全由不包含在字符串s2中的字符組成的初始串長度,。即從s1中第一個字符開始,按從前到后的順序到第一個屬于s2中的字符之間的字符個數(shù),。
size_t strcspn1(const char *s1 ,const char *s2)
{
assert((s1 != NULL) && (s2 != NULL));
const char *s = s1;
const char *p;
while (*s1)
{
for (p = s2; *p; p++)
{
if (*s1 == *p)
break;
}
if (*p)
break;
s1++;
}
return s1 - s;
}
c. strspn:返回字符串s1的完全由包含在字符串s2中的字符組成的初始串長度,。即從s1中第一個字符開始,按從前到后的順序到第一個不屬于s2中的字符之間的字符個數(shù),。
size_t strspn1(const char *s1 ,const char *s2)
{
assert((s1 != NULL) && (s2 != NULL));
const char *s = s1;
const char *p;
while (*s1)
{
for (p = s2; *p; p++)
{
if (*s1 == *p)
break;
}
if (*p == ''\0'')
break;
s1++;
}
return s1 - s;
}
d. strpbrk:返回指針指向字符串s1中字符串s2的任意字符第一次出現(xiàn)的位置,;如果未出現(xiàn)返回NULL。
char * strpbrk1(const char *s1 ,const char *s2)
{
assert((s1!=NULL) && (s2!=NULL));
const char *c = s2;
if (!*s1)
return (char *) NULL;
while (*s1)
{
for (c = s2; *c; c++)
{
if (*s1 == *c)
break;
}
if (*c)
break;
s1++;
}
if (*c == ''\0'')
s1 = NULL;
return (char *) s1;
}
e. strtok: 用字符串s2中的字符做分隔符將字符串s1分割,。返回指針指向分割后的字符串,。第一次調(diào)用后需用NULLL替代s1作為第一個參數(shù)。
使用案例:
#include"iostream"
using namespace std;
char string1[] = "A string\tof ,,tokens\nand some more tokens";
char seps[] = " , \t\n";
char *token;
void main(void)
{
cout<<"Tokens: "<<string1;
token = strtok( string1, seps ); /* Establish string and get the first token: */
while( token != NULL )
{
cout<<" token: "<< token <<endl; /* While there are tokens in "string" */
token = strtok( NULL, seps ); /* Get next token: */
}
}
輸出結(jié)果:
Tokens: A string of ,,tokens
and some more tokens token: A
token: string
token: of
token: tokens
token: and
token: some
token: more
token: tokens
請按任意鍵繼續(xù). . .
六.字符串與單個字符相關(guān)操作
a. strchr: 在字符串str中搜索字符c,。如果搜索到,,返回指針指向字符c第一次出現(xiàn)的位置;否則返回NULL,。
char * strchr(const char *str, int c)
{
while (*str && *str != (char)c)
str++;
if (*str == (char)c)
return((char *)str);
return(NULL);
}
b. strrchr: 在字符串str中搜索字符c,。如果搜索到,返回指針指向字符c最后一次出現(xiàn)的位置,;否則返回NULL,。
char * strrchr(const char * str,int c)
{
char *p = (char *)str;
while (*str)
str++;
while (str-- != p && *str != (char)c);
if (*str == (char)c)
return( (char *)str );
return(NULL);
}
c. memchr: 在buffer所指向的對象的前n個字符中搜索字符c。如果搜索到,,返回指針指向字符c第一次出現(xiàn)的位置,;否則返回NULL,。
void * memchr(const void * buffer,int c, size_t n)
{
while ( n && (*(unsigned char *)buffer != (unsigned char)c) )
{
buffer = (unsigned char *)buffer + 1;
n--;
}
return(n ? (void *)buffer : NULL);
}
d. memset: 設置buffer所指向的對象的前n個字符為字符c,。返回指針為s的值,。
void * memset(void * buffer,int c,int n)
{
void *p = buffer;
while (n--)
{
*(char *) buffer = (char)c;
buffer = (char *) buffer + 1;
}
return p;
}
e. strnset: 設置字符串str中的前n個字符全為字符c。返回指針為s的值,。
char * strnset(char * str,int c, size_t n)
{
char *p = str;
while (n-- && *p)
*p++ = (char)c;
return(p);
}
f. strset: 設置字符串str中的字符全為字符c,。返回指針為s的值。
char * strset(char *str,int c)
{
char *p = str;
while (*str)
*str++ = (char)c;
return(p);
}
七.字符串求字符串長度相關(guān)操作size_t
a. strlen: 返回值是字符串str的長度,。不包括結(jié)束符\0,。
size_t strlen (const char * str )
{
const char *p = str;
while( *p++ ) ;
return( (int)(p - str - 1) );
}
注:字符串必須以空字符結(jié)束。strlen往往與sizeof混淆,,其實這是兩個完全不同的概念,,sizeof是返回一個代號所具有的內(nèi)存空間,跟你里面存放的是什么一點關(guān)系都沒有,。
八.字符串錯誤相關(guān)操作
a. strerror:返回指針指向由errnum所關(guān)聯(lián)的出錯消息字符串的首地址,。errnum的宏定義見errno.h。
char *strerror (int errnum)
{
extern char *sys_errlist[];/*聲明了一個指針數(shù)組,,著里面存放的是錯誤信息*/
extern int sys_nerr;
if (errnum >= 0 && errnum < sys_nerr)
return sys_errlist[errnum];/*將錯誤信息的語段返回*/
return (char *) "Unknown error";
}
九.字符串反置操作
a. strrev: 將字符串全部翻轉(zhuǎn),,返回指針指向翻轉(zhuǎn)后的字符串。
char * strrev(char *str)
{
char *right = str;
char *left = str;
char ch;
while (*right)
right++;
right--;
while (left < right)
{
ch = *left;
*left++ = *right;
*right-- = ch;
}
return(str);
}
解析:
++,--用的很靈活,,后置++ /--的優(yōu)先級高于*,,前置++/--的優(yōu)先級和*相同,且均為右集合性,。
強烈建議:
盡量不要寫(*p++)之類的語句,,一個語句就執(zhí)行一小步功能挺好。我曾在《C陷阱與缺陷》里看到作者指出某些C語言編譯器把(*p++)解釋為:(*p)++,你還敢寫么,,真想寫也要寫成*(p++),。如果程序?qū)τ诖a空間的要求不太緊的話,不如分開寫,,因為可以:
1. 增加程序可讀性