快速上手字符串函数

文章目录

  • 前言
  • 一、求字符串的长度
    • strlen函数
      • strlen函数学习使用
      • strlen函数模拟实现
        • strlen函数模拟实现方法1:计数器法
        • strlen函数模拟实现方法2:指针减指针法
        • strlen函数模拟实现方法3:递归方法
  • 二、字符串的拷贝,拼接和比较
    • strcpy函数
      • strcpy函数学习使用
      • strcpy函数模拟实现
        • strcpy函数模拟实现方法1:
        • strcpy函数模拟实现方法2:对方法1的改进
    • strncpy函数
      • strncpy函数学习使用
      • strncpy函数模拟实现
    • strcat函数
      • strcat函数学习使用
      • strcat函数模拟实现
    • strncat函数
      • strncat函数学习使用
      • strncat函数模拟实现
    • strcmp函数
      • strcmp函数学习使用
      • strcmp函数模拟实现
    • strncmp函数
      • strncmp函数学习使用
      • strncmp函数模拟实现
  • 三、字符串的查找
    • strstr函数
      • strstr函数学习使用
      • strstr函数模拟实现
    • strtok函数
      • strtok函数学习使用
  • 四、错误信息报告
    • strerror函数
      • strerror函数学习使用
  • 五、内存操作的函数
    • memcpy函数
      • memcpy函数学习使用
      • memcpy函数模拟实现
    • memmove函数
      • memmove函数学习使用
      • memmove函数模拟实现
  • 六、比较实用的字符操作函数
  • 总结

前言

在C语言中,我们对字符串的操作大部分都是通过字符串函数来进行的,下面就让我们来深入的了解一下关于字符串操作的函数。

一、求字符串的长度

strlen函数

函数原型:

size_t strlen( const char *string );
//string:起始位置的地址

strlen是用来求字符串长度的函数,它求的长度不包含结尾的’\0’。
官方文档解释:
在这里插入图片描述
注意,返回字符串中的字符数中不包括终止的null字符!!!

strlen函数学习使用

int main()
{char str1[] = "abcdef";char str2[] = { 'a','b','c','d','e','f'};int test1 = strlen(str1);int test2 = strlen(str2);printf("%d\n%d\n", test1, test2);return 0;
}

在这里插入图片描述
下面我们根据内存来看一下原因:
在这里插入图片描述
我们看到str1和str2相比,结尾有一个’\0’,而strlen函数找到结束字符才停止,str2后面的元素是未知的,str2后’\0’的位置也是未知,所以得到值和我们str1不同。
如果我们传空指针或是空字符串会出现什么情况累?

int main()
{char* str3 = "";char* str4 = NULL;int test3 = strlen(str3);printf("%d", test3);int test4 = strlen(str4);printf("%d", test4);return 0;
}

在这里插入图片描述

我们发现程序会挂掉,但是原因是什么呢?
在这里插入图片描述

空指针不可以进行解引用操作!!!

strlen函数模拟实现

根据上面的案例,我们来模拟实现strlen函数的主要思路是找到结束字符,对传入的地址进行判断(防止为空)。,下面就让我们来模拟实现一下吧。
测试用例:

int main()
{char str1[] = "abcdef";char str2[] = { 'a','b','c','d','e','f'};char* str3 = "";int test1 = my_strlen(str1);int test2 = my_strlen(str2);int test3 = my_strlen(str3);printf("%d\n%d\n%d\n", test1, test2,test3);return 0;
}

strlen函数模拟实现方法1:计数器法

size_t my_strlen(const char* string)
{assert(string);//判断传入的地址是否有效int count = 0;//创建一个计数器,用来存储字符个数while (*string++)//string会先和++结合,但是是前置++,进行解引用时使用的是未++前的值//当解引用未结束字符时,即'\0',这时循环条件为假,结束循环{count++;}return count;
}

在这里插入图片描述

strlen函数模拟实现方法2:指针减指针法

size_t my_strlen(const char* string)
{assert(string);//判断传入的地址是否有效char *start = string;//记录起始位置char *end = string;//记录结束位置while (*end)//判断是否为结束字符{end++;//结束位置后移}return end - start;//指针减指针,得到的是两个指针之间的元素个数
}

在这里插入图片描述

strlen函数模拟实现方法3:递归方法

size_t my_strlen(const char* string)
{if (*string)//如果字符串这个位置不为结束字符{return 1 + my_strlen(string + 1);//返回一个字符的数量,并且进行递归,传入这个元素的下一个位置,直到递归到结束字符为止}return 0;//如果是结束字符,则返回0,因为结束字符是不计算的
}

在这里插入图片描述

二、字符串的拷贝,拼接和比较

strcpy函数

函数原型:

char* strcpy(char* strDestination, const char* strSource);
//strDestination目标字符串的位置
//strSource 源头字符串的位置
//返回值,传入的目标字符串起始的位置

strcpy函数将源头字符串(包括终止的null字符)复制到目标字符串中。
官方文档解释:
在这里插入图片描述
注意:strcpy是不执行溢出检查的,所以我们使用时要注意数组的越界,防止拷贝时出现数组的越界访问!!!

strcpy函数学习使用

int main()
{char strDestination1[15] = "abcdefggggggg";char strDestination2[] = "abcde";char strSource[] = "hijklm";printf("拷贝前strDestination1:%s\n", strDestination1);strcpy(strDestination1, strSource);printf("拷贝后strDestination1:%s\n",strDestination1);printf("拷贝前strDestination2:%s\n", strDestination2);strcpy(strDestination2, strSource);printf("拷贝后strDestination2:%s\n", strDestination2);return 0;
}

在这里插入图片描述
我们发现程序崩溃,并且提示出来错误。观察发现是我们strDestination2拷贝时发生了越界,由原来的5个元素变成了6个,产生了越界访问。所以产生了越界访问错误。让我们通过内存再来详细的看一下吧。
在这里插入图片描述
上面是strDestination1中内存经过strcpy后的变化。在这里插入图片描述
打印结果可以看出strcpy确实对我们的strDestination1进行了拷贝,由于我们strDestination1的内存远大于我们源头的字符串,所以并未出现错误信息,并且程序可以正常运行。
接下来让我们看strDestination2的内存信息吧:
在这里插入图片描述
我们来看打印信息:
在这里插入图片描述
我们可以发现strDestination2内存已经不足了,但还是把源头的字符串全部放入到了strDestination2中。
在这里插入图片描述
通过错误信息来看是我们越界访问并且修改了strDestination2后面的元素造成的。

strcpy函数模拟实现

通过上面用例学习,我们已经了解了strcpy函数的使用方法和注意事项,那么我们在模拟实现就解决这些问题,并且改进一下吧。
测试用例:

int main()
{char strDestination1[15] = "abcdefggggggg";char strDestination2[] = "abcde";char strSource[] = "hijklm";printf("拷贝前strDestination1:%s\n", strDestination1);printf("拷贝后strDestination1:%s\n", my_strcpy(strDestination1, strSource));printf("拷贝前strDestination2:%s\n", strDestination2);printf("拷贝后strDestination2:%s\n", my_strcpy(strDestination2, strSource));return 0;
}

我们在刚才的测试中小小的改动了一下,为了观察函数的返回值,目的是为了实现链式访问。

strcpy函数模拟实现方法1:

char* my_strcpy(char* strDestination, const char* strSource)
{assert(strDestination && strSource);//判断strDestination和strSource地址是否合法char* str = strDestination;//用来记录strDestination的起始位置,方便对目标字符串的返回while ((*strDestination != '\0') && (*strSource != '\0'))//只有双方都不为‘\0’才进入循环{*strDestination = *strSource;//进行拷贝strDestination++;//对目标位置进行加1strSource++;//对源头置进行加1}if (*strSource == '\0')//如果源头为‘\0’,我们需要把源头的‘\0’拷贝到目标字符串中//如果是目标字符串中‘\0’,则证明我们在进行拷贝会产生越界,所以不需要在拷贝了{*strDestination = *strSource;}return str;//返回strDestination的起始位置
}

在这里插入图片描述
这种我们不仅实现拷贝’\0’,并且防止了数组越界的产生。

strcpy函数模拟实现方法2:对方法1的改进

char* my_strcpy(char* strDestination, const char* strSource)
{assert(strDestination && strSource);//判断strDestination和strSource地址是否合法char* str = strDestination;//用来记录strDestination的起始位置,方便对目标字符串的返回while ((*strDestination != '\0') && (*strDestination++ = *strSource++))//strDestination不为空//strDestination先和++结合,和*结合是++前的结果解引用等于strSource++前的结果//一个等号是进行赋值,当*strSource为‘\0’时,把这个结果赋给*strSource时,这个表达式的值也为0{;}return str;//返回strDestination的起始位置
}

在这里插入图片描述

strncpy函数

函数原型:

char *strncpy( char *strDest, const char *strSource, size_t count );
//strDest目标字符串
//strSource 源字符串
//count要复制的字符数
//返回值,传入的目标字符串起始的位置

strncpy函数拷贝count个字符从源字符串到目标空间。如果源字符串的长度小于count,则拷贝完源字符串之后,在目标的后边追加0,直到count个。
官方文档解释:
在这里插入图片描述
注意:目标重叠将产生未定义行为!!!

strncpy函数学习使用

int main()
{char strDest1[] = "abcdefggggggg";char strDest2[] = "abcdefggggggg";char strDest3[] = "abcde";char strSource[] = "hijklm";printf("拷贝前strDestination1:%s\n", strDest1);printf("拷贝后strDestination1:%s\n", strncpy(strDest1, strSource, 4));printf("拷贝前strDestination2:%s\n", strDest2);printf("拷贝后strDestination2:%s\n", strncpy(strDest2, strSource, 8));printf("拷贝前strDestination3:%s\n", strDest3);printf("拷贝后strDestination3:%s\n", strncpy(strDest3, strSource, 8));return 0;
}

在这里插入图片描述
我们发现strncpy函数也没有实现数组是否越界的检查,这需要我们模拟实现时需要思考解决的。
让我们看内存情况了解一下strncpy函数的工作情况吧:
在这里插入图片描述
当我们的源字符串大于我们要拷贝的数量时,只是对目标字符串的替换,并会加入‘\0’。
在这里插入图片描述
当我们的源字符串小于我们要拷贝的数量时,会补‘\0’,直到count个。
在这里插入图片描述
我们可以发现strncpy对超过限制的没有检查作用。

strncpy函数模拟实现

我们的测试用例用刚才的用例来测试我们自己的函数。 我们strncpy函数模拟实现要解决超过限制的没有检查的问题。

char* my_strncpy(char* strDest, const char* strSource, size_t count)
{assert(strDest && strSource);//判断strDest和strSource地址是否合法char* str = strDest;//用来记录strDest的起始位置,方便对目标字符串的返回while (count--)//对传入字符个数进行循环{if (*strDest == '\0')//当目标字符串到达结尾时,就返回我们记录的起始位置{return str;}else if (*strSource == '\0')//当我们源字符串到达结尾时{*strDest = *strSource;//我们把目标字符替换为'\0',并且只对目标字符串进行移动strDest++;}else//当我们都没有到达结尾时,对两个字符串进行移动{*strDest = *strSource;strDest++;strSource++;}}return str;
}

在这里插入图片描述
注意:我们模拟实现要考虑是否补’\0’和数组是否会产生越界的情况。当我们源字符串到达结尾时不可以在对我们的源字符串进行移动。

strcat函数

函数原型:

char *strcat( char *strDestination, const char *strSource );
//strDestination目标字符串的位置
//strSource 源头字符串的位置
//返回值,传入的目标字符串起始的位置

strcat函数追加一个字符串。
官方文档解释:
在这里插入图片描述
注意这里的未定义行为,还是数组的越界访问。

strcat函数学习使用

int main()
{char strDestination[15] = "abcdefg";char strSource[] = "hijklm";printf("追加前strDestination:%s\n", strDestination);printf("追加后strDestination:%s\n", strcat(strDestination, strSource));return 0;
}

在这里插入图片描述
在这里插入图片描述
我们需要把源字符串全部追加到目标字符串中。

strcat函数模拟实现

char* my_strcat(char* strDestination, const char* strSource)
{assert(strDestination && strSource);//判断strDest和strSource地址是否合法char* str = strDestination;//用来记录strDest的起始位置,方便对目标字符串的返回while (*strDestination)//把目标字符串移植末尾结束字符位置{strDestination++;}while (*strDestination++ = *strSource++)//进行字符串追加{;}return str;
}

在这里插入图片描述
但我们实现的函数由一个致命的缺陷,就是对自己的追加。问题的解决我们放在memmove函数中解决。

strncat函数

函数原型:

char *strncat( char *strDest, const char *strSource, size_t count );
//strDest目标字符串的位置
//strSource 源头字符串的位置
//count要追加的字符数
//返回值,传入的目标字符串起始的位置

strncat函数将源字符串的前count个字符追加到目标,再加上一个终止的null字符。如果源字符串的长度小于count,则只复制终止null字符之前的内容。
官方文档解释:
在这里插入图片描述
前面的返回值等我们已经很熟悉了,这里我们只看注意事项。

strncat函数学习使用

int main()
{char strDestination1[15] = "abcdefg";char strDestination2[15] = "abcdefg";char strSource[] = "hijklm";printf("追加前strDestination1:%s\n", strDestination1);printf("追加后strDestination1:%s\n", strncat(strDestination1, strSource,4));printf("追加前strDestination2:%s\n", strDestination2);printf("追加后strDestination2:%s\n", strncat(strDestination2, strSource,8));return 0;
}

在这里插入图片描述
在这里我们分别追加方式分别使用小于源字符串追加和大于源字符的追加。

strncat函数模拟实现

有了前面的基础,我们来实现一下我们自己的函数吧。

char* my_strncat(char* strDest, const char* strSource, size_t count)
{assert(strDest && strSource);//判断strDest和strSource地址是否合法char* str = strDest;//用来记录strDest的起始位置,方便对目标字符串的返回while (*strDest)//把目标字符串移植末尾结束字符位置{strDest++;}while (count--)//对传入字符个数进行循环{if (*strSource == '\0')//当我们源字符串到达结尾时,{*strDest = *strSource;//把结束字符追加后结束本次追加return str;}else//对两个字符串进行移动{*strDest = *strSource;strDest++;strSource++;}}*strDest = '\0';//当循环结束时,代表我们源字符串的不完全追加,需要我们额外的补结束字符return str;
}

在这里插入图片描述
我们自己的实现可以发现思路就是我们strncpy和我们strcat的结合。当让,我们这个依然没有解决追加自己的问题。

strcmp函数

函数原型:

int strcmp( const char *string1, const char *string2 );
//string1要比较的以Null结尾的字符串
//string2 要比较的以Null结尾的字符串
//返回值,这些函数中的每个函数的返回值指示字符串1到字符串2的字典关系。

strcpy函数用于两个字符串比较
官方文档解释:
在这里插入图片描述

strcmp函数学习使用

int main()
{char string1[] = "abcde";char string2[] = "abcde";char string3[] = "abcd";char string4[] = "abcdf";int cmp1 = strcmp(string1, string2);//相等情况int cmp2 = strcmp(string1, string3);//string1>string2int cmp3 = strcmp(string1, string4);//string1<string2printf("%d %d %d", cmp1, cmp2, cmp3);return 0;
}

在这里插入图片描述
这里我们直接用返回值来判断了。
注意:strcpy函数用于两个字符串比较是一个字符一个字符进行比较。

strcmp函数模拟实现

int my_strcmp(const char* string1, const char* string2)
{assert(string1 && string2);//判断string1和string2地址是否合法while (*string1 != '\0' && *string2 != '\0')//只有当两个都不为空字符是才进行比较{if (*string1 > *string2){return 1;}else if (*string1 < *string2){return -1;}else//当两个字符相等时比较下一个字符{string1++;string2++;}}//循环结束,要判断是单个结束还是两个一起结束if (*string1 != '\0'){return 1;}else if (*string2 != '\0'){return -1;}else{return 0;}
}

在这里插入图片描述

strncmp函数

函数原型:

int strncmp( const char *string1, const char *string2, size_t count );
//string1要比较的以Null结尾的字符串
//string2 要比较的以Null结尾的字符串
//count,要比较的字符数
//返回值,这些函数中的每个函数的返回值指示字符串1到字符串2的字典关系。

strncpy函数用于两个字符串前count个字符的比较。
官方文档解释:
在这里插入图片描述

strncmp函数学习使用

int main()
{char string1[] = "abcde";char string2[] = "abcd";int cmp1 = strncmp(string1, string2,4);//相等情况int cmp2 = strncmp(string1, string2,5);//string1>string2printf("%d %d", cmp1, cmp2);return 0;
}

在这里插入图片描述
类比strcmp,我们很容易理解我们的strncmp函数。

strncmp函数模拟实现

int my_strncmp(const char* string1, const char* string2, size_t count)
{assert(string1 && string2);//判断string1和string2地址是否合法while (count--)//进行要比较的字符次数的循环{if (*string1 > *string2){return 1;}else if (*string1 < *string2){return -1;}else//当两个字符相等时比较下一个字符{string1++;string2++;}}//循环结束,证明两个相等return 0;
}

在这里插入图片描述
我们strncmp函数的思想就是循环count次,当count的元素都相同时,证明这两个字符串相等。

三、字符串的查找

strstr函数

函数原型:

char *strstr( const char *string, const char *strCharSet );
//string要搜索的以Null结尾的字符串
//strCharSet 要搜索的以Null结尾的子字符串
//返回值,返回一个指针,指向字符串中第一个出现的strCharSet,如果字符串中没有出现strCharSet则返回NULL

strstr函数在一个字符串中查找子串。
官方文档解释:
在这里插入图片描述
这个函数我们需要注意返回值为NULL还是返回第一次出现的位置,或者是被查找的字符串。

strstr函数学习使用

int main()
{char str[] = "abcccdefgh";char *str1 = strstr(str, "ccdef");char *str2 = strstr(str, "");char *str3 = strstr(str, "acbd");puts(str1);puts(str2);if (str3 == NULL){printf("str3 is NULL\n");}return 0;
}

在这里插入图片描述
我们分别查询在字符串中,不在字符串中和空字符串的情况,充分的了解了各种情况下的返回值。

strstr函数模拟实现

char* my_strstr(const char* string, const char* strCharSet)
{assert(string && strCharSet);//判断string1和string2地址是否合法if (*strCharSet == '\0')//判断传入的要查找的子字符是否为空字符串。{return string;}char* retu = string;//一个节点用来存储要返回的位置char* hand = strCharSet;//用来记录子串的起始位置while (*retu)//当要返回的位置不为空时{strCharSet = hand;//把字串的位置置为起始位置string = retu;//把被查找的位置改为现在存储返回的位置while (*string == *strCharSet)//如果两个字符相等,则比较下一个字符{if (*strCharSet == '\0')//判断是否两个都到达结束位置{return retu;}string++;strCharSet++;}if (*strCharSet == '\0')//当strCharSet到达结束标志时,证明改返回位置是查找的起始位置{return retu;}retu++;}return NULL;
}

在这里插入图片描述
在这里插入图片描述
在这里retu指针的意义是为了防止连续出现多的重复和子串一样元素,但最后不同时,便于找到开始查找的位置。hand指针的意义是每次对比结果不同时,把字串的指针位置回到开始。

strtok函数

函数原型:

char *strtok( char *strToken, const char *strDelimit );
//strToken包含令牌的字符串
//strDelimit字符串,分隔符字符集

strtok函数用于查找字符串中的下一个标记。
官方文档解释:
在这里插入图片描述
注意:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。(strtok函数找到strToken中的下一个标记,并将其用 ‘\0’ 结尾,返回一个指向这个标记的指针。)
strtok函数函数返回值讨论:
1.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
2.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
3.如果字符串中不存在更多的标记,则返回 NULL 指针。

strtok函数学习使用

看了上面的解释,我们看的雨里雾里的。让我们通过实践来学习一下吧。

int main()
{char* strToken = "xiao/hei.123@com.xiaohei";char* strDelimit = "/.@";//strDelimit放置的分隔字符为'/','.','@'char arr[30] = { 0 };strcpy(arr, strToken);//将数据拷贝一份,处理arr数组的内容char* str = NULL;for (str = strtok(arr, strDelimit); str != NULL; str = strtok(NULL, strDelimit))//函数第一次需要把要分隔的字符串传入//后续改函数会记住上次分隔符的位置{printf("%s\n", str);}return 0;
}

在这里插入图片描述
这里注意对同一个字符串进行查找时第二次要传NULL,因为该函数会记录上次查找的位置。

四、错误信息报告

strerror函数

函数原型:

char *strerror( int errnum );
//errnum错误编号
//返回值,指向错误消息字符串的指针

strerror函数用于返回错误码所对应的错误信息。
官方文档解释:
在这里插入图片描述

strerror函数学习使用

下面我们演示一下用法吧

#include <errno.h>//必须包含的头文件
int main()
{FILE* pf;pf = fopen("test.txt", "r");if (pf == NULL){printf("Error: %s\n", strerror(errno));}int* p = (int*)malloc(sizeof(int) * 0xFFFFFFFFF);if (p == NULL){printf("Error: %s\n", strerror(errno));}return 0;
}

在这里插入图片描述
注意:返回的错误信息如果不即时打印就会被下一个错误信息覆盖。
和这个函数相似的还有一个perror函数。

int main()
{FILE* pf;pf = fopen("test.txt", "r");if (pf == NULL){printf("Error: %s\n", strerror(errno));perror("Error");}int* p = (int*)malloc(sizeof(int) * 0xFFFFFFFFF);if (p == NULL){printf("Error: %s\n", strerror(errno));perror("Error");}return 0;
}

在这里插入图片描述

五、内存操作的函数

我们对内存的操作都是针对所有类型的,所以我们的形参都是用void指针接收,返回值也是void类型的指针。

memcpy函数

函数原型:

void *memcpy( void *dest, const void *src, size_t count );
//dest目标字符串的位置
//src 源头字符串的位置
//返回值,传入的目标字符串起始的位置

memcpy函数作用是从src的位置开始向后复制count个字节的数据到dest的内存位置。
官方文档解释:
在这里插入图片描述
memcpy函数在遇到 ‘\0’ 的时候并不会停下来。如果src和dest有任何的重叠,复制的结果都是未定义的。

memcpy函数学习使用

struct stu
{char name[10];int age;
};
int main()
{struct stu stu1 = { {"zhangsan"},20 };struct stu stu2;memcpy(&stu2, &stu1, sizeof(struct stu));printf("%s %d", stu2.name, stu2.age);return 0;
}

在这里插入图片描述

memcpy函数模拟实现

在实现前我们要先思考为什么库函数中用的void指针来接收的。好处是什么?
从上面的例子可以看出,我们这个实现的是可以拷贝所有的类型,所以类型我们把它限定为void,因为void指针可以接收任何指针。

void* my_memcpy(void* dest, const void* src, size_t count)
{assert(dest && src);void* begin = dest;while (count--){*((char*)dest) = *((char*)src);//把dest和src转化为字符指针,对字符指针进行移动(char*)dest += 1;(char*)src += 1;}return dest;
}

在这里插入图片描述
这里面我们用字符指针进行移动,一个字符一个字符的进行拷贝,这样可以做到拷贝任何类型的数据。

memmove函数

函数原型:

void *memmove( void *dest, const void *src, size_t count );
//dest目标字符串的位置
//src 源头字符串的位置
//返回值,传入的目标字符串起始的位置

memmove函数作用是从src的位置开始向后复制count个字节的数据到dest的内存位置。
memmove函数和memcpy函数的区别:
1.memmove函数处理的源内存块和目标内存块是可以重叠的(有的编译器memmove函数和memcpy功能已经一模一样了)。
2. 如果源空间和目标空间出现重叠,最好使用memmove函数处理,memmove函数就是为了拷贝相同区域而生的
官方文档解释:
在这里插入图片描述

memmove函数学习使用

int main()
{char arr1[15] = "abcdefghigkl";char arr2[15] = "abcdefghigkl";char arr3[15] = "abcdefghigkl";memmove(arr1 + 5, arr1, 5);memmove(arr2 + 5, arr2 + 3, 5);memmove(arr3 + 5, arr3 + 7, 5);printf("%s\n", arr1);printf("%s\n", arr2);printf("%s\n", arr3);return 0;
}

在这里插入图片描述
我们看到正对三种情况都可以正确的把字符串进行复制,接下来我们在函数模拟实现中着重的讨论一下如何复制重叠的区域吧,解决我们上面的strcpy等函数的缺陷吧。

memmove函数模拟实现

先让我们看看复制自己会有什么情况吧:
在这里插入图片描述

void* my_memmove(void* dest, const void* src, size_t count)
{assert(dest && src);void* begin = dest;if (*((char*)dest) - *((char*)src) > 0){while (count--){*((char*)dest + count) = *((char*)src + count);//把dest和src转化为字符指针,对字符指针进行移动}}else{while (count--){*((char*)dest) = *((char*)src);//把dest和src转化为字符指针,对字符指针进行移动(char*)dest += 1;(char*)src += 1;}}return begin;
}

思路:
当源头右边重叠时,我们从源头的左边进行拷贝时会把我们的值进行覆盖,所以我们要从源头右边进行拷贝
情况如下图所示:
在这里插入图片描述
当源头左边重叠时,我们从源头的右边进行拷贝时会把我们的值进行覆盖,所以我们要从源头左边进行拷贝
情况如下图所示:
在这里插入图片描述我们把两个元素转化为字符指针,通过指针相减可以得到两个指针的前后关系。然后选择合适的循环来进行拷贝。当两个不交叉重叠时,我们使用哪种方法都相同。
在这里插入图片描述

六、比较实用的字符操作函数

下面是一些实用的字符操作函数:

函数如果参数符合下列条件就返回真
iscntr任何控制字符
isspace空白字符
isdigit十进制数字
isxdigit十六进制数字
islower小写字母
isupper大写字母
isalpha字母
isalnum字母或者数字
ispunct标点符号
isgraph任何图形字符
isprint任何可打印字符
函数字符转换
tolower大写字母转换为小写字母
toupper小写字母转换为大写字母

下面演示几个吧:

int main()
{char arr[] = "aB1cD0eFg2HiGk";int sz = strlen(arr);int i = 0;for (i = 0; i < sz; i++){if (isupper(arr[i]))//如果是大写,就进行字符转化{arr[i] = tolower(arr[i]);}else if (islower(arr[i]))//如果是小写,就进行字符转化{arr[i] = toupper(arr[i]);}else if (isdigit(arr[i]))//如果是十进制数字,就打印{printf("%c ", arr[i]);//我们存的数字字符,如果用%d进行打印,需要让该元素减去一个字符0}}printf("\n%s\n", arr);return 0;
}

在这里插入图片描述

总结

相信你对字符串相关函数已经有了深刻的了解,在实战中多多使用吧。欢迎点赞留言呀。💕

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/78489.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C高级 作业 day3 8/4

1.整理思维导图 2.判断家目录下&#xff0c;普通文件的个数和目录文件的个数 1 #!/bin/bash2 arr(ls -l ~ | cut -d r -f 1 | grep -w d )3 arr1(ls -l ~ | cut -d r -f 1 | grep -w -)4 echo "目录文件个数为 ${#arr[*]}"5 echo "普通文件个数为 ${#arr1[*]}&q…

数据可视化(5)热力图及箱型图

1.热力图 #基本热力图 #imshow&#xff08;x&#xff09; #x&#xff0c;数据 x[[1,2],[3,4],[5,6],[7,8],[9,10]] plt.imshow(x) plt.show() #使用热力图分析学生的成绩 dfpd.read_excel(学生成绩表.xlsx) #:表示行号 截取数学到英语的列数 xdf.loc[:,"数学":英语].…

CS 144 Lab Five -- the network interface

CS 144 Lab Five -- the network interface TCP报文的数据传输方式地址解析协议 ARPARP攻击科普 Network Interface 具体实现测试tcp_ip_ethernet.ccTCPOverIPv4OverEthernetAdapterTCPOverIPv4OverEthernetSpongeSocket通信过程 对应课程视频: 【计算机网络】 斯坦福大学CS144…

OA办公自动化系统设计与实现(论文+源码)_kaic

摘要 随着信息化建设的日益深入&#xff0c;无论是政府还是企事业单位&#xff0c;部门之间的信息沟通与协调工作越来越重要。人们迫切需要一个能充分利用网络优势&#xff0c;并可以管理企业的各种重要信息的软件平台&#xff0c;利用该平台快速建立自己的信息网络和办公管理系…

云运维工具

企业通常寻找具有成本效益的方法来优化创收&#xff0c;维护物理基础架构以托管服务器和应用程序以提供服务交付需要巨大的空间和前期资金&#xff0c;最重要的是&#xff0c;物理基础设施会产生额外的运营支出以进行定期维护&#xff0c;这对收入造成了沉重的损失。 云使企业…

docker安装neo4j

参考文章&#xff1a; 1、Mac 本地以 docker 方式配置 neo4j_neo4j mac docker_Abandon_first的博客-CSDN博客 2、https://www.cnblogs.com/caoyusang/p/13610408.html 安装的时候&#xff0c;参考了以上文章。遇到了一些问题&#xff0c;记录下自己的安装过程&#xff1a; …

全志F1C200S嵌入式驱动开发(从DDR中截取内存)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 linux内核起来的时候,不一定所有的内存都是分配给linux使用的。有的时候,我们是希望能够截留一部分内存的。为什么保留这部分内存呢?这里面可以有很多的用途。比如说,第一,如果…

CSS 高频按钮样式

CSS 高频按钮样式 矩形与圆角按钮 正常而言&#xff0c;我们遇到的按钮就这两种 -- 矩形和圆角&#xff1a; 它们非常的简单&#xff0c;宽高和圆角和背景色。 <div classbtn rect>rect</div><div classbtn circle>circle</div>.btn {margin: 8px aut…

设计模式行为型——中介者模式

目录 什么是中介者模式 中介者模式的实现 中介者模式角色 中介者模式类图 中介者模式代码实现 中介者模式的特点 优点 缺点 使用场景 注意事项 实际应用 什么是中介者模式 中介者模式&#xff08;Mediator Pattern&#xff09;属于行为型模式&#xff0c;是用来降低…

js-7:javascript原型、原型链及其特点

1、原型 JavaScript常被描述为一种基于原型的语言-每个对象拥有一个原型对象。 当试图访问一个对象的属性时&#xff0c;它不仅仅在该对象上搜寻&#xff0c;还会搜寻该对象的原型&#xff0c;以及该对象的原型的原型&#xff0c;依次层层向上搜索&#xff0c;直到找到一个名字…

Android 实现账号诊断动画效果,逐条检测对应的项目

Dialog中的项目 逐条检测效果&#xff1a; 依赖库&#xff1a; implementation com.github.li-xiaojun:XPopup:2.9.19 implementation com.blankj:utilcodex:1.31.1 implementation com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.101、item_account_check.xml <…

Vue [Day3]

Vue生命周期 生命周期四个阶段 生命周期函数&#xff08;钩子函数&#xff09; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale…

Three.js入门

Three.js 介绍 Three.js 是一个开源的应用级 3D JavaScript 库&#xff0c;可以让开发者在网页上创建 3D 体验。Three.js 屏蔽了 WebGL的底层调用细节&#xff0c;让开发者能更快速的进行3D场景效果的开发。 Three.js的开发环境搭建 创建目录并使用npm init -y初始化package…

Containerd容器镜像管理

1. 轻量级容器管理工具 Containerd 2. Containerd的两种安装方式 3. Containerd容器镜像管理 4. Containerd数据持久化和网络管理 1、Containerd镜像管理 1.1 Containerd容器镜像管理命令 docker使用docker images命令管理镜像单机containerd使用ctr images命令管理镜像,con…

无涯教程-Lua - 文件I/O

I/O库用于在Lua中读取和处理文件。 Lua中有两种文件操作&#xff0c;即隐式(Implicit)和显式(Explicit)操作。 对于以下示例&#xff0c;无涯教程将使用例文件test.lua&#xff0c;如下所示。 -- sample test.lua -- sample2 test.lua 一个简单的文件打开操作使用以下语句。…

【C++】STL——list的模拟实现、构造函数、迭代器类的实现、运算符重载、增删查改

文章目录 1.模拟实现list1.1构造函数1.2迭代器类的实现1.3运算符重载1.4增删查改 1.模拟实现list list使用文章 1.1构造函数 析构函数 在定义了一个类模板list时。我们让该类模板包含了一个内部结构体_list_node&#xff0c;用于表示链表的节点。该结构体包含了指向前一个节点…

git之reflog分析

写在前面 本文一起看下reflog命令。 1&#xff1a;场景描述 在开发的过程中&#xff0c;因为修改错误&#xff0c;想要通过git reset命令恢复到之前的某个版本&#xff0c;但是选择提交ID错误&#xff0c;导致多恢复了一个版本&#xff0c;假定&#xff0c;该版本对应的内容…

Springboot部署ELK实战

Springboot部署ELK实战 1、部署docker、docker-compose环境安装docker安装docker-compose 2、搭建elk1、构建目录&&配置文件1、docker-compose.yml 文档2、Kibana.yml3、log-config.conf 2、添加es分词器插件3、启动 3、Springboot项目引入es、logStash配置1、引入依赖…

【新人指南】给新人软件开发工程师的干货建议

在我是新人时&#xff0c;如果有前辈能够指导方向一下&#xff0c;分享一些踩坑经历&#xff0c;或许会让我少走很多弯路&#xff0c;节省更多的学习的成本。 这篇文章根据我多年的工作经验&#xff0c;给新人总结了一些建议&#xff0c;希望对你会有所帮助。 写好注释 没有注…

解决Map修改key的问题

需求 现在返回json数据带有分页的数据&#xff0c;将返回data属性数据变更为content&#xff0c;数据不变&#xff0c;key发生变化 实现1&#xff0c;源数据比较复杂&#xff0c;组装数据比较麻烦 说明&#xff1a;如果使用这种方式完成需求&#xff0c;需要创建对象&#xff0…