在C语言的标准库中提供了很多针对字符串的库函数,这篇文章我们会学习并模拟实现几个简单的库函数
求字符串长度函数strlen
strlen函数我们在之前已经用过很多次了,同时也模拟实现过,但是都不是模仿标准库中的strlen来实现,首先我们在cplusplus中找到strlen函数的介绍
从介绍中我们知道strlen的返回值是一个size_t的无符号整型,参数是一个const修饰的字符指针。而strlen的功能是求字符串的长度,他以'\0'为结束标志,返回的是 '\0' 之前的字符个数。当我们传过去的字符串内容没有 '\0' ,strlen函数会一直向后访问知道找到 '\0' 。所以我们在使用strlen函数时一定要确定字符串结尾有结束标志。
strlen的模拟实现代码如下:
size_t my_strlen(const char* str)
{
assert(str);
if (*str != '\0')
{
return 1 + my_strlen(str + 1);
}
else
{
return 0;
}
}
长度不受限制的字符串函数
strcpy
strcpy的两个参数分别是char* destination 要拷贝的目标地址,char* source 拷贝的源头,返回的是目标地址,不过这个函数我们在使用时一般不会用一个指针来接收返回值。 这个函数的特点就是遇到 '\0' 结束,拷贝的是 '\0' 及以前的字符。如果source传过来的字符串中没有 '\0' ,这个函数就会一直向后访问拷贝,直到遇到 '\0' ,与strlen类似,所以我们在使用这个函数时要做好参数的设置。
使用的时候要注意的是,要拷贝的字符串一定要有结束标志,目标空间一定要足够,目标空间一定要可修改,不能传const修饰的或者常量的指针。同时要注意不要更改source的内容。
char* my_strcpy(char* dest, const char* source)
{
assert(dest&&source);
char* ret=dest;
while (*dest++=*source++)
{
;
}
}
strcat
strcat是一个字符串追加函数,两个参数 destination和source分别是被追加的字符串起始地址和追加的字符串的地址,要注意目标空间一定要足够放得下追加后的字符串,同时目标空间一定要可修改。
模拟实现这个函数的第一步便是要找到destination中字符串的结尾,然后从这个位置开始追加,追加后会补一个'\0',可以理解为把source字符串的'\0'也追加上去了。
char* my_strcat(char* dest, const char* source)
{
assert(dest && source);
char* ret = dest;
while (*dest != '\0')
{
dest++;
}
while (*dest++ = *source++)
{
;
}
}
在我们实现的这个函数是没办法实现自己给自己追加(dest和source有重叠部分),因为追加的时候覆盖原字符串,导致'\0'也会被覆盖,死循环。
strcmp
strcmp函数是一个字符串比较函数,两个参数是要比较的两个字符串。如果第一个字符串小于第二个字符串,就返回小于0的整型,如果两个字符串内容相等则返回0,否则返回大于0的整型。这个函数是逐字符比较,比较的是两个字符的ASCII码值。在使用时也要注意检查参数有没有结束标志。
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2&&*s1!='\0')
{
s1++;
s2++;
}
return *s1 - *s2;
}
长度受限制的字符串函数
strncpy
strncpy相对于strcpy多了一个num参数,表示要拷贝的字符个数,拷贝完之后会补一个'\0',要注意num不要大于source的长度,当num大于source长度时会用'\0'来补充。
char* my_strncpy(char* dest, const char* source,size_t num)
{
assert(dest && source);
char* ret = dest;
while (*source!='\0'&&num--)
{
*dest = *source;
dest++;
source++;
}
if (num > 0)
{
while (num--)
{
*dest++ = '\0';
}
}
*dest = '\0';
return ret;
}
strncat
与strcat相比多了一个num参数,表示要追加的字符,注意事项一样,同时,strcat再追加完num个字符后会在后面补一个'\0'。num长度不能大于source长度。
char* my_strncat(char* dest, const char* source, size_t num)
{
assert(dest, source);
char* ret = dest;
while (*dest != '\0')
{
dest++;
}
while (num--)
{
*dest++ = *source++;
}
*dest = '\0';
return ret;
}
strncmp
比较两个字符串前num个字符的大小。
int my_strncmp(const char* s1, const char* s2, size_t num)
{
assert(s1 &&s2);
while (*s1 == *s2 &&num--)
{
s1++;
s2++;
}
return *s1 - *s2;
}
查找子串函数strstr
可以看到这个函数有两个参数,作用是再str1中查找是否有字串str2,如果有,返回str1中字串的起始地址,如果没有字串,返回空指针。
这个函数模拟实现的思路就是遍历str1,如果有字符与str2首字符相同,则比较是不是字串。要注意的是,遍历str1时要用两个指针,一个用来遍历str1并在比较时记录当前位置,一个用来判断是不是字串。
char* my_strstr(const char* s1, const char* s2)
{
assert(s1 && s2);
char* begin = s1;
char* cmp1 = begin;
char* cmp2 = s2;
while (*begin != '\0')
{
if (*begin == *s2)
{
cmp1 = begin;
cmp2 = s2;
while (*cmp2 != '\0'&&*cmp1==*cmp2)
{
cmp1++;
cmp2++;
}
if (*cmp2 == '\0')//匹配成功
{
return begin;
}
}
begin++;
}
//前面没有返回就意味着找不到子串
return NULL;
}
切割字符串函数strtok
这个函数两个参数,str是要切割的字符,delimiters是个字符串,定义了用作分隔符的字符集合。这个函数的作用就是str中遇到delimiters中的字符就标记,把这个标记改成'\0',并返回这一子字符串的地址,同时strtok会保存这个标记的下一个位置,如果下一次使用strtok函数时第一个参数传的是NULL,strtok就会从这个位置开始继续查找下一个标记。当我们传的字符串中有多个分隔符,我们只需要第一次调用strtok时传字符串,之后调用就传NULL来找第二个标记。如果字符串结束都没找到标记,就返回空指针。
我们在模拟实现的时候要注意用static修饰保留上一次查找到的地址。
如果两个分隔符连在一起,就跳过这些连在一起的分隔符,因为这两个分隔符之间没有元素。
char* my_strtok(char* str, const char* sep)
{
assert(sep);
char* s2 = sep;
static char* begin ;
if (str != NULL)
{
begin = str;
}
if (*begin == '\0')//遍历完了str,返回空指针
{
return NULL;
}
char* s1 = begin;
while (*s1 != '\0')//跳过字符串开始的分隔符
{
int flag = 0;
s2 = sep;
while (*s2 != '\0')
{
if (*s1 == *s2)
{
flag = 1;
*s1 = '\0';
begin++;
break;
}
s2++;
}
if (flag == 0)
{
break;
}
else
{
s1++;
}
}
//找分隔符
while (*s1 != '\0')
{
s2 = sep;
int flag = 0;
while (*s2 != '\0')
{
if (*s2 == *s1)
{
flag = 1;
break;
}
s2++;
}
if (flag == 1)
{
*s1 = '\0';
char* ret = begin;
begin = s1+1;
return ret;
}
else
{
s1++;
}
}
//str遍历结束返回后面的字符
char* ret = begin;
begin = s1;
return ret;
}
这段代码可能有点难理解,主要是begin指针的操作有点复杂。