字符串函数详解及模拟实现
- 前言
- 求字符串长度
- strlen
- 介绍
- 作用
- 注意事项
- 模拟实现
- 长度不受限制的字符串函数
- strcpy
- 介绍
- 作用
- 注意事项
- 模拟实现
- strcat
- 介绍
- 作用
- 注意事项
- 模拟实现
- strcmp
- 介绍
- 作用
- 模拟实现
- 长度受限制的字符串函数
- 说明
- strncpy
- 介绍
- 注意事项
- 模拟实现
- strncat
- 介绍
- 注意事项
- 模拟实现
- strncmp
- 介绍
- 模拟实现
- 字符串查找函数
- strstr
- 介绍
- 模拟实现
- strtok
- 介绍
- 错误信息报告
- strerror
- 介绍
- 内存操作函数
- memcpy
- 介绍
- 模拟实现
- memmove
- 介绍
- 模拟实现
- memset
- 介绍
- 模拟实现
- memcmp
- 介绍
- 模拟实现
前言
C语言中有许多库函数,有我们常见的IO函数(printf,scanf),时间函数(time),数学函数(fabs),字符串函数等等。
C语言中对字符和字符串的处理很是频繁,但是C语言本身没有字符串类型,字符串通常放在常量字符串中或者字符数组中。
字符串常量适用于那些对它不做修改的字符串函数
这篇文章会为你介绍字符串函数,来帮助你更好的理解和学习字符串函数。
求字符串长度
strlen
介绍
strlen的参数:size_t strlen( const char *string );
strlen函数位于<string.h>头文件下。其返回值是求得字符串的长度。
PS:长度一定是正数,size_t是通过宏定义实现的,其类型是unsigned int无符号整型。本次说明之后本文将不会再介绍
作用
计算字符串长度
strlen对于接受到的地址以1个字节为单位向前计算,当遇到终止符’\0’时停止计算。
注意事项
如果输入的字符串没有确切的大小,比如未初始化等,那么返回的会是随机值,因为只有遇到’\0’,函数才会停止。如图:
并且,由于strlen返回值是无符号整数,其相减结果也一定会是正整数,所以不能随意的相减,乘负数也不可以。如图:
当然,可以通过强制类型转换来得到想要的结果
模拟实现
知道了原理,很容易实现strlen
有三种实现方法,分别是
- 计数
- 指针减去指针(指针减去指针为两个地址之间的元素个数)
- 递归
代码如下
size_t my_strlen1(const char* str) {//计数法,遇到不是\0的数便+1
char *s = (char*)str;
size_t count = 0;
while (*s++ != '\0')
count++;
return count;
}
size_t my_strlen2(const char* str) {//地址减去地址是两个地址之间的元素个数
char *s = (char*)str;
while (*s++ != '\0');
return (size_t)(s - str)-1;//这里-1是因为此时s在\0处
}
size_t my_strlen3(const char* str) {//递归方法
if (*str == '\0')
return 0;
else
return 1 + my_strlen3(str + 1);
}
执行结果如下:
长度不受限制的字符串函数
strcpy
介绍
strcpy的参数:char *strcpy( char *str1, const char *str2 );
strcpy函数位于<string.h>头文件下。其返回值是char*类型指针。
作用
将字符串str2的内容复制到字符串str1里。
注意事项
-
strcpy会将\0也放进去
-
strcpy不会在意空间大小,将8字节的字符串放进4字节大小中,代码仍然能够执行且会强制放进去,结果会报错,因为“放不下”;所以目标空间必须足够大
-
(字符串必须以\0结束)字符串str2应该有具体大小,类似strcpy,否则会将随机值放入str1中并报错:因为和strcpy一样,只会str2遇到了\0才会停止复制
模拟实现
代码:
char* my_strcpy(char* s1, const char*s2) {
assert(s1&&s2);//断言函数,判断空指针
char* ret = s1;
while (*s1++ = *s2++)
;
return ret;
}
结果:
strcat
介绍
strcat的参数:char *strcat( char *str1, const char *str2 );
strcat函数位于<string.h>头文件下。其返回值是char*类型指针。
作用
将字符串str2追加到str1后面,并返回str1的指针。
注意事项
- str1要预留一定空间给str2的位置,否则会越界。
- str2 需要有固定的大小,否则会将随机值录入,且会有越界的风险。
程序崩溃
解决方案:放入\0即可
模拟实现
代码:
char* my_strcat(char* str1, const char* str2) {
assert(str1 && str2);//断言
char* ret = str1;//保留str1地址
while (*str1)
str1++;
//找到了str1的\0的位置
while (*str1++ = *str2++)
;
//追加
return ret;
}
结果:
strcmp
介绍
strcmp的参数:int strcmp( const char *str1, const char *str2 );
strcmp函数位于<string.h>头文件下。
作用
比较字符串大小内容(不是长度)(参考字典序)(ASCII值),若str1>str2,返回一个大于0的数,若str1=str2,返回0,若str1《str2,返回小于0的数。
通常会返回1,0,-1。
模拟实现
代码:
int my_strcmp (const char* str1,const char* str2) {
assert(str1 && str2);
while (*str1 && *str2){//遍历
if (*str1 > *str2)//大于返回1
return 1;
else if (*str1 < *str2)//小于返回-1
return -1;
*str1++;//等于则继续
*str2++;
}
return 0;//遍历结束,相等,返回0
}
结果:
长度受限制的字符串函数
说明
因为strcpy,strcat,ctrcmp长度不受限制,在上面的三个函数的注意事项里面也已经提到了,所以这些函数并不安全,会出现一些问题,比如越界访问。
所以,为了更好的使用函数,C语言又规定了一些长度受限制的相对安全的函数使用,接下来将会介绍这些函数。
注意,并不是绝对安全
strncpy
介绍
strncpy的参数:
char *strncpy( char *str1, const char *str2, size_t count );
和strcpy一样,位于<string.h>头文件下。
相对于strcpy,多了一个count参数,其作用是规定复制count个字节,用于解决strcpy不看大小直接复制容易出错的问题
注意事项
- 若count大于str2的长度,即要复制进去的内容不够,那么会补’\0’
当然,如果str2并未规定大小,那么这种情况下不会录入\0,而是会存入随机值。
模拟实现
代码:
char * my_strncpy(char * str1, const char *str2, size_t n)
{
assert(str1 && str2);
char *ret = str1;
for (size_t i = 0; i < n; i++) {
if (n>strlen(str1)){
*(str1 + i) = '\0';//对超出部分进行处理
}
else{
*(str1 + i) = *str2++;
}
}
return ret;
}
结果:
strncat
介绍
strncat的参数:
char *strncat( char *str1, const char *str2, size_t count );
位于<string.h>头文件下。
和strncpy相对于strcpy一样,strncat多了一个count参数,其作用是规定追加count个字节。
注意事项
- strncat并不是真的追加count个字节,而是会帮你多放了个’\0’进去,用来结束字符串。
- strncat最多只会追加count+1个字符,如果count大于要追加的字符串长度,则无事发生
模拟实现
代码:
char* my_strncat(char* str1, const char* str2,size_t count) {
assert(str1 && str2);//断言
char* ret = str1;//保留str1地址
while (*str1)
str1++;
//找到了str1的\0的位置
while(count--){
if (*str2 == '\0'){//处理count过大的情况
*str1 = '\0';
break;
}
else{
*str1++ = *str2++;
}
}
//追加
return ret;
}
结果:
strncmp
介绍
strncmp的参数:
char *strncat( const char *str1, const char *str2, size_t count );
位于<string.h>头文件下。
多了一个count参数,其作用是比较count个字节。
模拟实现
代码:
int my_strncmp(const char* str1, const char* str2,size_t count) {
assert(str1 && str2);
while (count--){//遍历
if (*str1 > *str2)//大于返回1
return 1;
else if (*str1 < *str2)//小于返回-1
return -1;
*str1++;//等于则继续
*str2++;
}
return 0;//遍历结束,相等,返回0
}
结果:
字符串查找函数
顾名思义,用于查找字符串的函数
strstr
介绍
参数:char *strstr( const char *str1, const char *str2 );
在str1中查找str2是否存在,返回str1中str2第一次出现的位置的起始地址,否则返回空指针。
模拟实现
代码:
char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
char* start = (char*)str1;//在这里需要强制类型转换成char*
char* find = (char*)str2;//寻找的字符串
char* p = (char*)str1;//cp就是用来保存首地址的
while (*p)
{
start = p;//吧p赋给start,用start来寻找,防止找不到已经找到str2的起始点
while (*start != '\0' && *find != '\0' && *start == *find)
{
//循环,寻找字符串
start++;
find++;
}
if (*find == '\0')//find遇到了\0,意味着找到了
{
return p;//返回此时的p
}
find = (char*)str2;//没找到,但是find可能已经改变过,所以重新赋值
p++;//p++可以得到原起始位置的下一个位置
}
return NULL;
}
结果:
strtok
介绍
strtok的参数:char *strtok( char *str1, const char *str2 );
在str1中寻找和str2其中相等的字符然后置为\0
- 第一个参数str1指定一个字符串,它包含了0个或者多个由str2字符串中一个或者多个分隔符分割的标记。
- strtok函数找到str1中的下一个标记,井将其用\0结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
- strtok函数的第一个参数不为NULL,函数将找到str1中第一个标记,strtok函数将保存它在字符串中的位置。
- strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
- 如果字符串中不存在更多的标记,则返回NULL指针。
- 这是用static静态变量实现的。
错误信息报告
strerror
介绍
参数:char *strerror( int errnum );
strerror函数从内部数组中搜索错误号 errnum,并返回一个指向错误消息字符串的指针。strerror 生成的错误字符串取决于开发平台和编译器。
内存操作函数
memcpy
介绍
参数:void *memcpy( void *str1, const void *str2, size_t count );
- 函数memcpy从str2的位置开始向后复制count个字节的数据到str1的内存位置。
- 这个函数在遇到’\0’的时候并不会停下来。
- 如果str2和str1有任何的重叠,复制的结果都是未定义的。
- memcpy能够拷贝的数据不会受限制,可以拷贝多种类型的数据
模拟实现
代码:
void* my_memcpy(void* arr1, const void* arr2, size_t count) {
assert(arr1 && arr2);
void* ret = arr1;
while(count--) {
*(char*)arr1 = *(char*)arr2;
arr1 = (char*)arr1 + 1;
arr2 = (char*)arr2 + 1;
}
return ret;
}
结果:
memmove
介绍
参数:void *memmove( void *str1, const void *str2, size_t count );
其作用和memcpy类似,都是复制拷贝。
但是memmove比memcpy要更强,因为在部分情况下,memcpy无法完成覆盖拷贝。如图。
在vs下,memcpy可以完成覆盖拷贝,但是在某些编译器下无法完成,所以在这种情况下尽量使用memmove。
模拟实现
对于数据重复区间的情况进行特殊处理
代码:
void* my_memmove(void* dest, const void* src, size_t count) {
void *ret = dest;
if (dest < src){//从前向后拷贝
while (count--) {
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else{//从后向前拷贝
while (count--) {
*((char*)dest + count) = *((char*)src+count);
}
}
return ret;
}
结果:
memset
介绍
参数:void *memset( void *dest, int c, size_t count );
该函数可以将dest开始的count个字节全部置为c
模拟实现
代码:
void* my_memset(void *arr, const int c, size_t count){
void*ret = arr;
for (size_t i = 0; i < count; i++){
*((char*)arr + i) = c;
}
return ret;
}
结果:
memcmp
介绍
参数:int memcmp(const void *str1, const void *str2, size_t n);
C 库函数 memcmp 把存储区 str1 和存储区 str2 的前 n 个字节进行比较。
模拟实现
代码:
void* my_memcpy(void* dest, const void* src, size_t n)
{
assert(dest);
assert(src);
char* pdest = (char*)dest;
const char* psrc = (const char*)src;
while (n--)
{
*pdest++ = *psrc++;
}
return dest;`
🐂🐸完结撒花
彩蛋就是没有彩蛋