今天来介绍一下C语言中有关字符串常量的函数及部分函数的模拟实现
注:以下介绍的函数在使用时都需要包含<string.h>的头文件
1、strlen
size_t strlen(const char* str);
作用:返回字符串以字节为单位的长度
例:
·字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包
含 '\0' ,若字符串中含有'\0'则以第一个'\0'为准)。
· 参数指向的字符串必须要以 '\0' 结束。
· 注意函数的返回值为size_t,是无符号的
(4)strlen的模拟实现
strlen的模拟实现可以有三种方式
//计数器方式
int my_strlen(const char * str)
{
int count = 0;
while(*str)
{
count++;
str++;
}
return count;
}
//递归
int my_strlen(const char * str)
{
if(*str == '\0')
return 0;
else
return 1+my_strlen(str+1);
}
//指针-指针的方式
int my_strlen(char *s)
{
char *p = s;
while(*p != ‘\0’ )
p++;
return p-s;
}
2、strcpy
char* strcpy(char* destination,const char* source);
作用:将源字符串(source指向地址中的内容)拷贝到目标空间(destination指向的地址)中
例:
·源字符串必须以 '\0' 结束。
·会将源字符串中的 '\0' 拷贝到目标空间。
·目标空间必须足够大,以确保能存放源字符串。
·目标空间必须可变。
strcpy的模拟实现
char *my_strcpy(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL); //断言判断源字符串地址和目标空间地址是否为空
assert(src != NULL);
while((*dest++ = *src++))
{
;
}
return ret;
}
3、strcat
char * strcat ( char * destination, const char * source );
作用:追加一份目标空间(source)的内容拷贝放在源空间(destination)的末尾,也就是把源空间末尾的'\0'干掉,追加一份source的拷贝,再放上'\0'。
例:
·源字符串必须以 '\0' 结束。
·目标空间必须有足够的大,能容纳下源字符串的内容。
·目标空间必须可修改。
strcat的模拟实现
char *my_strcat(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while(*dest)
{
dest++;
}
while((*dest++ = *src++))
{
;
}
return ret;
}
4、strcmp
int strcmp ( const char * str1, const char * str2 );
作用:用来比较两个字符串是否相同
例:
·第一个字符串大于第二个字符串,则返回大于0的数字
·第一个字符串等于第二个字符串,则返回0
·第一个字符串小于第二个字符串,则返回小于0的数字
strcmp的模拟实现
int my_strcmp (const char * src, const char * dst)
{
int ret = 0 ;
assert(src != NULL);
assert(dest != NULL);
while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
++src, ++dst;
if ( ret < 0 )
ret = -1 ;
else if ( ret > 0 )
ret = 1 ;
return( ret );
}
以上介绍的strcpy、strcat、strcmp在使用过程中都有一个很明显的特点,那就是在使用过程中都是对目标空间或源空间中的内容进行整体操作,比如说拷贝字符串只能将目标空间的所有字符都拷贝过来,那有没有只对字符串中某一段数据进行操刀的函数呢?当然是有的。
以上的strcpy、strcat、strcmp可以统称为长度不受限制字符串函数
接下来就介绍长度受限制字符串函数
5、strncpy
char * strncpy ( char * destination, const char * source, size_t num );
·拷贝num个字符从源字符串到目标空间。
·如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
例:
6、strncpt
char * strncat ( char * destination, const char * source, size_t num );
作用:从目标中地址追加num个字节的数据到源地址中
例:
7、strncmp
int strncmp ( const char * str1, const char * str2, size_t num );
·比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
例:
8、strstr
char * strstr ( const char *str1, const char * str2);
作用:从源字符串str1中查找是否含有目标字符串str2,如果有则返回str2在str1中第一次出现的位置的指针
例:
strstr模拟实现
例:
char * strstr (const char * str1, const char * str2)
{
char *cp = (char *) str1;
char *s1, *s2;
if ( !*str2 )
return((char *)str1);
while (*cp)
{
s1 = cp;
s2 = (char *) str2;
while ( *s1 && *s2 && !(*s1-*s2) )
s1++, s2++;
if (!*s2)
return(cp);
cp++;
}
return(NULL);
9、strok
char * strtok ( char * str, const char * sep );
作用:用于切割字符串
·sep参数是个字符串,定义了用作分隔符的字符集合
·第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标
记。
·strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(strtok函 数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
并且可修改。)
·strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
中的位置。
·strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标
记。
·如果字符串中不存在更多的标记,则返回 NULL 指针。
例:
10、strerror
char * strerror ( int errnum );
作用:将错误码转换成信息
当我们编译过程中出现错误时,编译器并不会直截了当的告诉我们错在哪里,通常会返回一个错误码,就像我们浏览网页时常常会看到404一样,而strerror就是将错误码转换成信息告诉我们
例:
当打开一个不存在的文件时
11、memcpy
void * memcpy ( void * destination, const void * source, size_t num );
作用:从source的位置开始向后复制num个字节的数据到destination的内存位置
·这个函数在遇到 '\0' 的时候并不会停下来。
·如果source和destination有任何的重叠,复制的结果都是未定义的。
例:
这就能清晰看出memcpy和strcpy的区别了,strcpy是专门用来拷贝字符串的,需要扫描到'\0'停止,memcpy遇到'\0'不会停止
memcpy的模拟实现:
void* memcpy(void* dst, const void* src, size_t count)
{
void* ret = dst;
assert(dst);
assert(src);
while (count--) {
//注意!memcpy是以字节为单位拷贝的,所以再复制过程中应该是一个字节一个字节复制,所以这里强制转换为char*型指针
*(char*)dst = *(char*)src;
//同样的,这里的地址变化也应该是一个字节一个字节的变动,上面的强制转换只在上面一条语句中生效,所以这里也要强制转换一下
dst = (char*)dst + 1;
src = (char*)src + 1;
}
return(ret);
}
那是否可以用memcpy进行自我拷贝呢?来试试
居然是可以的,但这种方法是有待商榷的
如图所示,arr[0]和arr[1]的内容已经拷贝到arr[2]和arr[3]中去了,下面arr[2]和arr[3]还需要继续往下面拷贝,这就意味着源地址中的数据可能已经被污染了。但这种问题也依赖于编译器的特性,像我用的是vs 2019就不会出现错误,但如果使用的是其他编译器就未可知了。
为了避免这种问题,我们可以使用一个更靠谱的函数memmove。
12、memmove
void * memmove ( void * destination, const void * source, size_t num );
作用:从source的位置开始向后复制num个字节的数据到destination的内存位置
·和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
·如果源空间和目标空间出现重叠,就得使用memmove函数处理。
例:
memmove的模拟实现
void* my_memmove(void* dest, void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if (dest < src)//前->后
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else//后->前
{
while (num--)
{
*((char*)dest +num)= *((char*)src + num);
}
}
return ret;
}
这里模拟需要注意,当出现空间重叠时,为避免数据污染,要注意目标地址与源地址的前后位置,从而选择是从前往后拷贝还是从后往前拷贝
13、memcmp
int memcmp ( const void * ptr1,const void * ptr2,size_t num );
作用:比较从ptr1和ptr2指针开始的num个字节
·当ptr1指向的内容的值小于ptr2指向的内容的值时,返回一个负数
·当ptr1指向的内容的值等于ptr2指向的内容的值时,返回0
·当ptr1指向的内容的值大于ptr2指向的内容的值时,返回一个正数
个人学习总结,如有谬误,还望指出。