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