我们平时写代码对字符进行操作时,常常会用到库函数里的字符操作符,那这些库里的操作符究竟是怎么计算的,现在我们来对这些库函数进行剖析,并且来模拟实现一下
常见的库函数字符串操作符
1、strcmp
2、strcpy
3、strcat
4、strstr
5、strchr
6、strlen
7、memcpy
8、memmove
1、strcmp
先来看看库函数里的strcmp描述
strcmp返回值是int行,如果string1大于string2,则返回的是一个大于0的数,如果string1小于string2,则返回的是一个小于0的数,string1等于string2,则返回
strcmp的源码如下:
int __cdecl strcmp (
const char * src,
const char * dst
)
{
int ret = 0 ;
while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
++src, ++dst;
if ( ret < 0 )
ret = -1 ;
else if ( ret > 0 )
ret = 1 ;
return( ret );
}
我们要看的是while循环这个语句, ! (ret = *(unsigned char *)src - *(unsigned char *)dst)意思是拿指针变量src所指向的字符值(即*src)减去指针变量dst所指向的字符值(即*dst),差值赋给ret,再取非运算,最后与*dst进行与运算
接下来我们来对strcmp模拟实现一下
#define _CRT_SECURE_NO_WARNINGS 1
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
int my_strcmp(const char *str1,const char *str2)
{
assert(str1);
assert(str2);
while(*str1 == *str2)
{
if(*str1 == '\0') //当两个字符相等并且其中一个为'\n'时,字符串中所有字符比较完
{
return 0;
}
str1++;
str2++;
}
return *str1-*str2; //若str1大于str2,返回正值,小于就返回负值
}
int main()
{
char *p = "abcdef";
char *q = "abcde";
int ret = my_strcmp(p,q);
printf("%d\n",ret);
system("pause");
return 0;
}
模拟strcmp函数的返回值若大于0,说明str1大于str2,小于0是str1小于str2,相等为0;
strcpy函数
先来看看库函数里的strcmp描述
strcpy是将一个字符串的内容拷贝到另外一个字符串中,每一个函数返回目标字符串。没有返回值是保留显示一个错误。
strcmp的源码如下:
#include <assert.h>
char *strcpy(char *dst, const char *src) {
assert((dst != NULL) && (src != NULL));
char *tmp = dst;
while ((*dst++ = *src++) != '\0')
{
;
}
return tmp;
}
#define _CRT_SECURE_NO_WARNINGS 1#include <windows.h>#include <assert.h>#include <stdio.h>#include <string.h>char *my_strcpy(char *str,const char* src){char *tmp = str; //保留str起始位置assert(str);assert(src);while(*str++ = *src++){;}return tmp;}int main(){char arr1[] = "abcdefghikj";char arr2[] = "123456789";char* ret = my_strcpy(arr2,arr1);printf("%s\n",ret);system("pause");return 0;} 上面程序可以将arr1中的字符拷贝到arr2中。但是程序能不能完整的通过呢?tmp保留目标字符串的其实位置
答案是可以拷贝过去,但会出现堆栈溢出
在执行while(*str++ = *src++)这一句时,当src的长度大于str时,*src++指向j,而*str++指向的内容不是本数组的内容,所以在访问这一块空间时会出现越界,导致溢出!所以在对目标数组的访问时,应当给目标数组指定大于源数组大小空间。
char arr1[] = "abcdefghikj"; char arr2[20] = "123456789";
strcat函数
先来看看库函数里的strcmp描述
strcat函数简单来说就是将一个字符串追加到另一个字符串的后面,
例如:
将world追加到hello 的后面strcat源码:
Char* strcat ( char * dst , const char * src ) { char * cp = dst; while( *cp !=’\0’) cp++; while( (*cp++ = *src++)!=’\0’ ) ; return( dst ); }
模拟实现strcat
注意,源目标字符串不能改变,所以最好加const来修饰。上面的strcpy也一样!#define _CRT_SECURE_NO_WARNINGS 1 #include <windows.h> #include <assert.h> #include <stdio.h> char *my_strcat(char *str,const char *src) { char *tmp = str; //将str先给一个char* 变量tmp,tmp保留目标字符串的其实位置 assert(str); assert(src); while(*str) //将str++到最后'\0'处 str++; while(*src) *str++=*src++; //将源字符串的元素一个一个的追加到目标字符串空间,知道源字符串追加完 return tmp; } int main() { char arr1[20] = "hello "; //注意目标字符串要有大小,不然容易发生溢出 char arr2[] = "world!"; char* ret = my_strcat(arr1,arr2); printf("%s\n",ret); system("pause"); return 0; }
strstr函数
先来看看库函数里的strcmp描述
strstr函数是判定str2是不是str1的子串,如果是就返回str1中首次出现str2的地方,如果不是就返回NULLstrstr模拟实现:
注意,所有的字符串都只是比较,不能改变内容,所以在创建时都要加const#define _CRT_SECURE_NO_WARNINGS 1 #include <windows.h> #include <stdio.h> #include <string.h> #include <assert.h> char *my_strstr(const char* str1,const char* str2) { const char *tmp = str1; //tmp保留目标字符串的其实位置 assert(str1); assert(str2); while(*tmp) { const char *s1 = tmp; //只是查找不改变tmp的内容 const char *s2 = str2; while((*s1)&&(*s2)&&(*s1 == *s2)) //当s1,s2不等于0,并且找到s1中和s2相等的第一个元素时才能查找一下个 { s1++; s2++; } if(*s2 == 0) { return (char *)tmp; //tmp是const修饰过得,要将tmp转换为char*型 } tmp++; } return NULL; } int main() { char *p = "abcdefg"; char *q = "def"; char *ret = my_strstr(p,q); printf("%s\n",ret); system("pause"); return 0; }
这里要是一个字符串中有几个另一个字符串,则返回的是第一次出现时的位置
例如:str1 = "abcdefabcdef" str2 = "cdef";返回的位置是第三个元素的位置
strchr函数
先来看看库函数里strchr的描述
strchr和strstr一样,strchr是在str1里找一个字符,如果有则返回第一次出现该字符的位置,如果没有则返回NULL
直接来看strchr的模拟实现:
和strstr一样,如果出现多次,则返回第一次出现的位置#define _CRT_SECURE_NO_WARNINGS 1 #include <windows.h> #include <stdio.h> #include <assert.h> char *my_strchr(const char *str1,int ch) { const char *tmp = str1; assert(str1); while(*tmp) { const char *s1 = tmp; if(*s1 == ch) return (char *)tmp; tmp++; } return NULL; } int main() { char *p = "abcdef"; int ch = 'd'; char *ret = my_strchr(p,ch); printf("%s\n",ret); system("pause"); return 0; }
strlen函数先看看库函数里strlen的描述
strlen是测量字符串的长度的函数,在遇到\0时停下了。
在求字符串长度时,通常都是用sizeof和strlen,两个在对字符串操作时的计算不一样,strlen不包括'\0',而sizeof包括'\0'
上面的求字符串长度用了两种方式,第一种是常用的使用指针的方式来计算长度,第二种用递归的形式来计算,递归计算起来调用时间比指针长,在长度较长时不建议用递归来计算。#define _CRT_SECURE_NO_WARNINGS 1 #include <windows.h> #include <stdio.h> #include <assert.h> int my_strlen(char *arr) { /*assert(arr); //用指针加1来计算字符串长度 int count = 0; while(*arr++) { count++; } return count;*/ if(*arr!='\0') { return 1+my_strlen(arr+1); //一递归的形式计算长度 } return 0; } int main() { char arr[]="abcdef"; int ret = 0; ret = my_strlen(arr); printf("%d\n",ret); system("pause"); return 0; }
memcpy函数
先来看看库函数里memcpy的描述
memcpy 函数用于 把资源内存(src所指向的内存区域) 拷贝到目标内存(dest所指向的内存区域);拷贝多少个?有一个size变量控制 拷贝的字节数
memcpy模拟实现:
上面的程序将指定的长度5,起始位置为首元素位置,拷贝到第四位往后的位置.但是这种以内存拷贝的方式拷贝时,会出现重叠,上面的代码最后的程序结果应该是abcabcabijk#define _CRT_SECURE_NO_WARNINGS 1 #include <windows.h> #include <stdio.h> #include <assert.h> void *my_memcpy(void *str,const void *src,size_t n) { char *pstr = (char *)str; char *psrc = (char *)src; assert(str); assert(src); while(n--) { *pstr++ = *psrc++; } return str; } int main() { char p[20] = "abcdefghijk"; int num = 5; my_memcpy(p+3,p,num); printf("%s\n",p); system("pause"); return 0; }
用图示的方法来解读一下
这就是memcpy在拷贝函数是的过程,为了解决这个重叠的问题引进了memmove函数
memmove函数
先来看看库函数里的memmove描述
memmove函数和memcpy函数功能一样,memmove解决了重叠的问题
memcpy在拷贝的过程中是从前面一个一个拷贝,直到拷贝结束,这样才造成的重叠,那如果从后向前拷贝是不是就不会有重叠的情况了呢?#define _CRT_SECURE_NO_WARNINGS 1 #include <windows.h> #include <stdio.h> #include <assert.h> void *my_memcpy(void *str,const void *src,size_t n) { void *pstr = str; assert(str); assert(src); while((((char *)src)<pstr) && (pstr<(char *)src+n)) { pstr = (char *)pstr+n-1; src = (char *)src+n-1; while(n--) { *(char *)pstr = *(char *)src; pstr = (char *)pstr -1; src = (char *)src -1; } } return str; } int main() { char p[20] = "abcdefghijk"; int num = 5; my_memcpy(p+3,p,num); printf("%s\n",p); system("pause"); return 0; }
用图示的方法模拟实现时看到可以实现不重叠,那代码运行结果是不是和模拟的结果一样呢
猜想没错,看了计算机在用memmove时就是从后向前拷贝,什么时候可以用向前拷贝呢
当(((char *)src)<pstr) && (pstr<(char *)src+n),也就是说目标位置位于起始位置和起始位置加上要拷贝的大小的位置中间时,适合用从后向前拷贝,在其他时候前后都行。