C语言中lwr是谁的缩写,C语言中关于字符串的操作(转)

浅析C语言中关于字符串的操作(转)

前言:如果您是学C/C

的,对于字符串的操作不是很了解,请您耐心读完。作为我的朋友,我很乐意和您分享我最近的知识积累。毕

竟,网上很少有这么全,这么细的介绍,更少有人愿意花时间收集N个相关帖子,读懂,并将零散的知识点整理,并思考函数之间可能的联系或改进方法。如果您觉

得不错,请您分享,您的支持就是给我继续整理的动力。

一.字符串相关操作分类介绍(招式篇)

1.字符串拷贝相关操作。

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的值。不会发生内存重叠。

2.字符串比较相关操作

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.字符串大小写转换相关操作

a. strlwr:

char *strlwr (char

*s);将字符串s全部转换成小写。返回指针为s的值。

b. strupr:

char *strupr (char *s);

将字符串s全部转换成大写。返回指针为s的值。

4.字符串连接相关操作

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.字符串子串相关操作

a. strstr:

char *strstr (const char *s1, const char

*s2);

在字符串s1中搜索字符串s2。如果搜索到,返回指针指向字符串s2第一次出现的位置;否则返回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的任意字符第一次出现的位置;如果未出现返回NULL。

e. strtok:

char *strtok (char *s1, const char *s2);

用字符串s2中的字符做分隔符将字符串s1分割。返回指针指向分割后的字符串。第一次调用后需用NULLL替代s1作为第一个参数。

6.字符串与单个字符相关操作

a. strchr:

char *strchr (const char *s, int c);

在字符串s中搜索字符c。如果搜索到,返回指针指向字符c第一次出现的位置;否则返回NULL。

b. strrchr:

char *strrchr (const char *s, int

c);

在字符串s中搜索字符c。如果搜索到,返回指针指向字符c最后一次出现的位置;否则返回NULL。

c. memchr:

void * memchr (const void *s, int c, size_t

n);

在s所指向的对象的前n个字符中搜索字符c。如果搜索到,返回指针指向字符c第一次出现的位置;否则返回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..字符串求字符串长度相关操作

a. strlen:

size_t strlen (const char *s);

返回值是字符串s的长度。不包括结束符\0。

8..字符串错误相关操作

a. strerror:

char *strerror(int errnum);

返回指针指向由errnum所关联的出错消息字符串的首地址。errnum的宏定义见errno.h。

9..字符串反置操作

a. strrev:

char *strrev (char *s);

将字符串全部翻转,返回指针指向翻转后的字符串。

二.记忆方法(记忆心法精要)

最好的方法莫过于多用了,但是具体函数的名字的记忆也是有技巧的。

str :

字符串cmp :

比较n :

代表范围i : 表示不区分大小写

rev :

翻转error :

错误len :

长度mem :内存

cat :

连接lwr :

小写upr:大写

set :

设置(单个字符)chr :

单个字符(查找)

注:

1.只要是关于内存操作(mem…)的,则必有一个参数size_t

n,表示对操作内存的大小。这与strn…函数在功能上类似,只是,strn…函数一般在没达到n个字节之前遇到空字符时会结束,而mem…遇到空字符并不结束。

2.str/mem [n] chr : 表示在字符串中查找某个字符,而str/mem [n] set :

表示把字符串设置成某个字符,n表示作用范围为前n个。

3.strdup中返回的指针是new出来的,用完一定要记得delete。

三.源码 && 解析

&& BUG:(内功篇)

1.字符串拷贝相关操作。

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 中, 后置的优先级比*高,所以相当于*(src ),src

的意思是,先取出src的值,再 ,所以*src ;等同于:*src;src ;

3.将src指针所指地址中所有字符串都复制到dest指针所指地址中,直至遇到空字符才结束。并且dest和返回指针都指向拷贝后地址空间的首地址。

BUG:

没有对src指针所指地址空间的大小进行检查(实际是无法进行检查),所以,当所需要拷贝的字符串超出src指针所指地址空间的大小时,内存出错。

案例:

#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'; //构造一个全部是*的字符串

char szBuf2[256];

for(i=0;i<256;i )

szBuf2[i]='#';

szBuf2[255]='\0'; //构造一个全部是#的字符串

strcpy(szBuf,szBuf2);

cout<

}

int main(void)

{

strcpyTest0();

return

0;

}

结果:在此程序中,虽然输出了255个#,但是却弹出了内存错误,这个原因很好解释,因为szBuf本

身只有128个字节,可却给它拷贝了256个字节的内容。从其源码可以看出,strcpy将会把szBuf2中的所有内容全部考到szBuf中,直至遇到

了空字符。所以,切忌,在用strcpy函数时要保证szBuf有足够的内存空间,否则会出错。

猜测性改进方法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中的数不会超过dest所拥有的空间大小了。经调试,确实不会超过dest

所指大小,也就没有了内存错误。但是,strlen只是求得该字符串的长度遇到'\0'就结束了,假如上述案例中szBuf[127]='\0';改为

szBuf[10]='\0';则szBuf中只能拷贝10个字符,再加一个'\0'结尾,可szBuf明明是有127个字符空间的,所以此法不行。

猜测性改进方法2:把dest所能存储的字符长度作为参数传进去,这样就产生了strncpy函数,具体请见strncpy函数。

猜测性改进方法3:仅仅传入需要拷贝的地址,然后再new出一块内存存放拷贝的字符串,再返回new出来的内存的首地址,最后由调用函数delete,这样就产生了strdup函数,具体请见strdup函数。

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';//遇空字符结束后,将其后所有字符付为空字符

return(dest);

}

解析:

1.若src所指地址空间的字符串个数

2. dest和返回指针都指向拷贝后地址空间的首地址。

BUG:

当n比源字符串空间要小的时候,strncpy并没有用”\0”来结束字符串。从上述源码可以看出当n

=0时,是不会执行while(n--) *p =

'\0';的,这样,如果在前面并没付”\0”来结束字符串,则后面也不会再付”\0”来结束字符串。

案例:

#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<

}

int main(void)

{

strcpyTest1();

return

0;

}

结果可不是:##########,而

是:##########*********************************************************************************************************************

改进方法:

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';//遇空字符结束后,将其后所有字符付为空字符

*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中申请的内存空间,必须要在调用函数中释放,这样就使内存的申请和释放分开,用户很容易忘记了主动施放内存,会导致内存泄漏。

改进方法:

通过命名方式提醒用户主动释放内存:如在函数名之前加

ndp_: need delete

pointer需要主动施放指针

nfp_: need free

pointer

需要主动施放指针

ndpa_: need delete

pointer array

需要主动施放指针数组

nfpa_: need delete

pointer array

需要主动施放指针数组

思想借鉴:

鉴于这种思想,在传递参数时,如果需要传出的参数的空间大小在调用此函数前是不知道的,又不想浪费空间,则可以考虑new —

delete。如果再用到上述的命名方法,则将提醒用户,不至于使用户忘记释放内存。这样可能会使部分函数的名字很难记忆,那么如果把ndp_改为_ndp加在函数名之后,则可借助VC

assist轻松获得该函数。

我有一种想法,只要大家都能理解这种思想,并且这么做,这种动态开辟内存的方法也许有可能会改变我们现有的编程模式。

我现在还不是很明白动态开辟和释放在时间性能上的弱点,也许这种方法会给程序带来灾难,希望知道的人能够告诉我,这种方式在带来空间上的不浪费的优点的同时会有哪些潜在的危险和不足。

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.

精妙之处在于指针之间的转换。传进来的参数是两个void类型的指针,均指向内存中的某一块地

址,而这个地址本身都是四个字节的,不管是什么类型的地址都是四个字节,那么只要经过强制转化,则void型指针可以转化为任意类型的指针。(类型名)指

针名,当然它传出的也是void型的指针,同理也可以转化成任意类型的,那么这个函数的功能可就不仅仅是拷贝字符串了,任何存在类存的东西都能考的。具体

见下案例。

2.dest和返回指针都指向拷贝后内存空间的首地址。

案例:

#include"iostream"

using namespace std;

void strcpyTest1()

{

int A=1;

int B=0xffffffff;

memcpy(&A,&B,1);

cout<

}

int main(void)

{

strcpyTest1();

return 0;

}

结果:将会输出255,为什么不是0xffffffff呢,因为你只拷贝了一个字节,而int型的是4

个字节,所以,你只要将memcpy(&A,&B,1);改为memcpy(&A,&B,4);就把B的值付给了A。当

然是-1了。啊,不明白为什么?原码,补码,移码,还有有符号数,无符号数总知道了吧,还不知道,那只好去看看唐硕飞老师的组成原理了,就是考验推荐的参

考书之一。但同时也要注意,n的值不能超过A和B的范围,不然要么就是得到的数据是不合法的,要么就是存储不合法。

至于越界了会不会崩掉,那还要拼人品,如果你复制的越界内存已经分配出去了,将会出现内存错误,如果没有,则可继续运行,但如果编译器会强制规定对于某个变量的操作不能超出他的内存范围,则可能不能通过运行。具体目前我也不是很清楚,还望知情者点拨一下。

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.模拟循环体执行:

dest = (char *)dest 1;

src = (char *)src 1;

(char *) (dest-1)= (char *) (src -1);

n && (*((char *)

(dest-1)) != (char)c);

2.如此写法,不但难懂,且易错,最主要的是代码行数太少,影响工资,呵呵……

f.

memmove:从src所指向的对象复制n个字符到dest所指向的对象中。返回指针为dest的值。不会发生内存重叠。

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.当目的地址在源地址之后,且目的地址和源地址之间有重叠区域时,将从最后一个字符开始,将源地址区域字符串付给目的地址,否则从第一个字符开始将源地址区域字符串付给目的地址。这样就能保证即使在源/目的地址区域有重叠的情况下也能正确的复制内存。

BUG总结(精要):

1.copy函数要注意copy后目的地址空间不能越界,有的是用参数控制,有的并不控制,有的内存立马出错,有的概率性报

错,cat 函数也要注意这些。总之起来就是一句话,但凡修改内存内容的,要注意不能越界,字符串处理操作除了受参数 size_t

n来控制范围外,遇到空字符结束,而内存处理操作只受参数size_t n控制。

2.mem

函数都涉及到内存空间大小,操作时一定要注意空间别越界,越界了报错是概率性的,可能你那运行没错,客户一运行就错了。memcpy对于目的地址在源地址之后,且目的地址和源地址之间有重叠区域这种情况下的拷贝是不正确的,可以用memmove来代替。

3.strncpy有一个结尾符不是空字符的BUG,也许人家就是这么设计的,但你要记得n

2.字符串比较相关操作

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循环的{},精简代码行数。

2.只涉及到读取内存,而不修改内存,故而只要传入的参数本身没有问题,则不会出现现隐藏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;

}

解析:略

注:字符串比较相关操作只涉及到内存的读取,而不修改内存,所以,不会导致严重的不可预测的不良结果,源代码已经很清楚的展现了这些函数的功能,我也不愿再多费唇舌。我曾在网上看到过有人说字符串比较相关操作的返回值是受比较的字符串的第一个不相同的字符之差,只是我用的VC6.0

编译器是其之差的符号,但具体可能因编译器而定,为了代码拥有更好的可复制性,建议关于其返回的比较最好为 return_value

> 0, return_value<0 和return_value ==

0 ,不要写成1 == return_value,-1 == return_value,可能有潜在的危险。

···

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值