C语言中几个常见的字符串函数和内存函数分析及其模拟实现
目录
一. 字符串函数
1.strcmp 函数
int strcmp ( const char* str1,const char* str2)
比较俩个字符串函数 比较的方式是逐一比较 俩个字符串中字符的ascii码值。
比较字符串内容只能用strcmp函数,不可直接用 > , < 来比较,如果直接用大小与号来比较,事实上比较的是俩个字符串的首元素的地址,而非字符串内容的比较。
模拟实现strcmp函数,我们需要用到指针,利用指针来实现单独访问字符串的每一个字符并进行比较
代码实现:
#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2); //assert函数,判断起始地址的合法性,如果为空指针则报错
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;
}
s1++;
s2++;
}
if (*s1 > *s2)
{
return 1;
}
else
return -1;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcde";
printf("%d", my_strcmp(arr1, arr2));
return 0;
}
2.strncmp 函数
int strncmp( ( const char* str1 ,const char* tr2 , size_t num) ;
与strcmp 一直比较直到字符串结束 不同的是,strncmp 比较到出现不一样的字符或者出现 ' \0' 或者 num 个字符全部比较完。
代码实现
#include<stdio.h>
#include<assert.h>
int my_strncmp(const char* str1, const char* str2, int num)
{
assert(str1 && str2);
if (!num) // num = 0 直接返回0
{
return 0;
}
while(--num && *str1 && *str1 == *str2) // 前置-- ,判断str 是否为 '\0 ',比较str,str2
{
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcdeg";
int ret = my_strncmp(arr1, arr2, 6);
printf("%d", ret);
return 0;
}
3. strcat 函数
char* stract ( char* destination ,const char* source)
Appends (附加) a copy of the source string to the destination string.The terminating (终端) null character in destination is overwritten by the first charaster of source,and a null-character is include at the end of the new string formd by the concatenation of both in destinion.
将源字符串的内容复制到目标字符串。目标字符串的终止字符 即 ' \0' ,将被源字符串的第一个字符串覆盖,并且,新形成的字符串的末尾包含一个空字符。
由函数的定义我们可以得知实现,模拟实现stract 函数需要掌握的几个关键点。
1.源字符必须要以 ' \0 ' 结束。
2.目标空间必须足够大,可以容纳源字符串内容。
3.目标空间必须可修改。
代码实现:
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* s1,const char* s2)
{
assert(s1 && s2);
char* ret = s1;
while (*s1)
{
s1++;
}
while (*s1++ = *s2++)
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello ";
char arr2[] = " world";
char* ret = my_strcat(arr1, arr2);
printf("%s", ret);
return 0;
}
4.strncat 函数
char* strncat (char* destination , const char* source ,size_t num) ;
strncat 函数 与 strcat 函数相比 更为精密。
将源字符串的num个字符的内容复制到目标字符串。目标字符串的终止字符 即 ' \0' ,将被源字符串的第一个字符串覆盖,并且,新形成的字符串的末尾包含一个空字符。
代码实现
#include<stdio.h>
#include<assert.h>
char* my_strncat(char* s1, const char* s2, int num)
{
assert(s1 && s2);
char* ret = s1;
while (*s1)
{
s1++;
}
while (num-- && *s2) // 如果 *s2 判断是否为 ' \0 ',避免越界访问引起错误
{
*s1++ = *s2++;
}
*s1 = '\0'; // 上面s1后置++,直接赋值 \0 即可。
return ret;
}
int main()
{
char arr1[40] = "hello \0xxxxxxxx";
char arr2[] = "worlddddd";
char* ret = my_strncat(arr1, arr2, 5);
printf("%s", ret);
return 0;
}
5.strstr 函数
char* strstr (const char*str1, const char*str2)
Returns a pointer to the first occurrence of str2 in str1,or a null pointer if str2 is not part of str1.
如果字符串2,在字符串1中出现,则返回 字符串1中出现字符串2的字符串2的首地址,如果字符串2没有在字符串1中出现则返回 空指针。
#include<stdio.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
char* s1 = str1;
char* s2 = str2;
char* str = str1;
while (*str)
{
s1 = str;
s2 = str2;
while (*s1 && *s2 && (*s1 == *s2))
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return str;
}
str++;
}
return NULL;
}
int main()
{
char arr1[] = "asdfgabcdfe";
char arr2[] = "abcd";
char* ret = my_strstr(arr1, arr2);
if (ret == NULL)
{
printf("没找到");
}
else
printf("%s", ret);
return 0;
}
6. strcpy 函数
char* strcpy(char* destination,const char* source)
将源字符串复制到目标字符串中。包括源字符串中的 ' \0' 。
模拟实现时需注意
1. 源字符串必须以 ' \0 ' 结束 。
2. 复制时,包括源字符串的 ' \0 '。
3. 目标字符串的空间足够大,可以容纳源字符串的内容。
4. 目标字符串必须可以更改。
代码:
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, char* sru)
{
char* s1 = dest;
assert(dest && sru);
while (*dest++ = *sru++)
{
;
}
return s1;
}
int main()
{
char arr1[20] = " ";
char arr2[] = "hai";
my_strcpy(arr1, arr2);
printf("%s",arr1);
return 0;
}
7.strncpy 函数
char* strcpy(char* destination,const char* source, size_t num)
将源字符串复制 num 个字符的内容 到目标字符串中。包括源字符串中的 ' \0' 。
代码实现
char* my_strncpy(char* dest, char* sru, int num)
{
assert(dest && sru);
char* s1 = dest;
while (num-- && *sru) //防止越界
{
*dest++ = *sru++;
}
*dest = '\0'; // 在复制完后,添加\0
return s1;
}
int main()
{
char arr1[50] = "a \0xxxxxxxxxxxxxxxxx ";
char arr2[] = "hai";
my_strncpy(arr1, arr2, 10);
printf("%s", arr1);
return 0;
}
既要防止越界,也要处理好结尾(\0)
8.strlen 函数
size_t strlen (const char* str)
1. 字符串以 '\0'为结束标志,strlen 函数返回的是字符串中 ' \0’前出现字符的个数,不包括 '\0'
2. size_t 为函数的返回值类型,是无符号的。
代码实现:
#include<stdio.h>
#include<assert.h>
int my_strlen(char* s1)
{
assert(s1);
int count = 0;
while (*s1)
{
s1++;
count++;
}
return count;
}
int main()
{
char arr1[] = "qweraaa";
int ret = my_strlen(arr1);
printf("%d", ret);
return 0;
}
9.strerror 函数
char * strerror (int errum) ;
必须包含的头文件 <errno.h>
返回错误码所对于的错误信息。
例:
#include<limits.h>
#include<errno.h>
int main()
{
int* p = (int*)malloc(INT_MAX);
if (p == NULL)
{
printf("%s ", strerror(errno)); // errno为一个全局变量,是错误码,使用时需引用头文
// <limits.h>
}
}
当我们用malloc函数 向内存申请过于大的空间时 ,程序会报警,我们就可以用 strerror 函数得到错误对应的信息。 malloc 函数 后面的博客会有分析。
10. strtok 函数
char* strtok ( char* str ,const char* sep ) ;
sep 是个自定义参数,定义了 用作分隔符 的字符集合。
第一个参数给定一个字符串,它包含了0个或者多个有sep字符串中一个或多个分隔符分隔的标记。
1. strtok 函数 找到str中下一个标记,将其用 ' \0' 结束,返回一个指向这个标记的指针。
2. strtok函数会改变该字符串的内容,一般使用strtok函数的都是拷贝的内容,并且可修改。
3. strtok 函数的第一个参数 不为NULL ,函数将找到str 中第一个标记,strtok 函数将保存它在字符串中的位置。
4. strtok 函数的第一个参数为 NULL ,函数在同一个字符串中被保存的位置开始,查找下一个标记。
5. 如果字符串中不存在更多的标记,则返回 NULL 指针。
有点复杂,作者不会模拟实现,以后或许。
但是作者可以简单分析如何更好的使用这个函数。
#include<string.h>
int main()
{
char arr1[] = "hello worid /siwen.2022_8_7";
char arr2[40] = { 0 };
strcpy(arr2, arr1);
char sep[] = " /.";
printf("%s\n", strtok(arr2, sep));
printf("%s\n", strtok(NULL, sep));
printf("%s\n", strtok(NULL, sep));
printf("%s\n", strtok(NULL, sep));
return 0;
}
上述的代码显得不太聪明,当字符串和分隔符太多时,代码会显得特别冗余,也容易出错。
我们可以用for 循环 简单高效的实现。
#include<string.h>
int main()
{
char arr1[] = "hello worid /siwen.2022_8_7";
char arr2[40] = { 0 };
strcpy(arr2, arr1);
char sep[] = " /.";
char* str = NULL;
for (str = strtok(arr2, sep); str != NULL; str = strtok(NULL, sep))
{
printf("%s\n", str);
}
return 0;
}
二.内存函数
11.memcpy 函数
void * memcpy(void *destination , const void *source , size_t num) ;
1.memcpy 函数,从source 位置开始复制 num个字符的数据到destination 位置。
2. 这个函数遇到 ' \0 ' ,不会停止。
3. 如果 source 和 destination 有任何的重叠,复制的结果都是未定义的。(即不适用一段数据的自我拷贝,但是我们可以用 memmove函数来实现)
代码实现
void* my_memcpy(void* dest,const void*sur,int num )
{
assert(dest && sur);
void* ret = dest;
while (num--)
{
*((char*)dest) = *((char*)sur);
dest = (char*)dest + 1;
sur = (char*)sur + 1;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8 };
int arr2[10] = {0};
my_memcpy(arr2, arr1, 20);
int i = 0;
int sz = sizeof(arr2) / sizeof(arr2[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
12. memmove 函数
void * memmove(void *destination , const void *source , size_t num) ;
可以理解为 memcpy 的 plus 版本 ,
memmove处理的源内存块 和 目标内存块是可以重叠的
代码实现
void* my_memmove(void* dest, const void* sur, int num)
{
void* ret = dest;
if (dest < sur)
{
while (num--)
{
*((char*)dest) = *((char*)sur);
dest = (char*)dest + 1;
sur = (char*)sur + 1;
}
return ret;
}
else
{
while (num--)
{
*((char*)dest + num) = *((char*)sur + num);
}
return ret;
}
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9 };
my_memmove(arr1, arr1+2, 20);
int i = 0;
int sz = sizeof(arr1) / sizeof(arr1[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
简单分析一下这个代码
如图所示,当dest < source 时 ,数据的复制顺序为 从前到后 ,依次把 3复制到1 的位置上 ,4 复制到2的位置上,依次复制。但是如果倒叙复制,7复制到5 的位置上,而5本该复制到3的位置 ,5却被提前改为了 7,修改了源数据块的内容, 这个函数就达不到我们预期的目标。我们只能采取顺序复制
当 dest > source 同时 ,小于 source + num 个字节 的 地址时 ,只有 倒叙 复制 ,才不会改变源内存块的数据内容。
当 dest < (char*)source +num -1 时 ,采取顺序复制 还是倒叙复制 都不会影响源内存块的数据。
13.memcmp 函数
int memcmp (const void* ptr1, const void* ptr2,size_t num)
比较从 ptr1 和ptr2 指针开始的 num个 字节
1. ptr1 > ptr2 return 一个 大于零的数
2. ptr1 < ptr2 return 一个 小于零的数
3. ptr1 = ptr2, return 0
有点难,作者不会模拟实现,以后或许吧
可以写个简单的代码实现一下这个函数
int main()
{
int arr1[10] = { 1,2,3,4,5,6 };
int arr2[10] = { 1,2,3,4,5,8 };
int ret = memcmp(arr1, arr2, 24);
if (ret > 0)
{
printf("ptr1>ptr2");
}
else if (ret < 0)
{
printf("ptr1>ptr2");
}
else if(ret == 0)
{
printf("ptr1 = ptr2");
}
return 0;
}
本文介绍的所有函数其头文件都可以用 <string.h>
我或许大抵是写完了 ,感谢观看。