目录
一.前言
我们在C语言的从程序代码编写中,对字符和字符串的处理相当频繁,但是我们还知道,C语言本身并没有字符串类型。而字符串通常放在【常量字符串】或者【字符数组】中。其中,字符串常量适用于那些对它不做修改的字符串函数。
二.strlen
1.函数介绍
功能求字符串长度
#include<stdio.h>
#include<string.h>
int main()
{
printf("%d\n", strlen("abcdef"));//6
return 0;
}
字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
参数指向的字符串必须要以 '\0' 结束。
注意函数的返回值为size_t,是无符号的( 易错 )
size_t strlen ( const char * str );
strlen库函数的返回类型是size_t,是无符号数。
sizeof -- 操作符 -- 计算大小的。而size_t本质:unsigned int,size_t是专门为sizeof的返回类型设计的 。既然size_t是无符号整型的,所以值恒>0
2.三种模拟实现
计算器
#include <stdio.h>
#include<assert.h>
int my_strlen(const char* str)
{
assert(str);
int count = 0;
while (*str != '\0')//判断字符串是否结束
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\n", len); // 6
return 0;
}
递归
#include<stdio.h>
int my_strlen(char* s)
{
if (*s == '\0')
return 0;
else
return 1 + my_strlen(s + 1);
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n", len); // 6
return 0;
}
指针减指针
#include<stdio.h>
int my_strlen(char* s)
{
char* p = s;
while (*p != '\0')
p++;
return p - s;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr); //6
printf("%d\n", len);
return 0;
}
三.长度不受限制函数
1.strcpy
strcpy 函数(string copy)的作用是,可以将字符串从源地址复制至目的地址,通俗来讲就是用来实现字符串的复制和拷贝。并且它会将源地址内的结束标识符 ' \0 ' 一并拷贝过去,因此源地址必须以 ' \0 ' 结尾,且目的地址也将以结束标识符结尾。并且,因为其作用为拷贝字符串,因此目标地址内的空间必须足够大,要有足够的空间容纳下源地址内的字符串,同时目的地址的空间必须是可变、可修改的。
注意事项
- 源字符串必须以 '\0' 结束。
- 会将源字符串中的 '\0' 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可变。
#include<stdio.h>
#include<string.h>
int main()
{
const char arr1[] = "Hellow!";
const char arr2[10] = { 0 };
printf("Before copy , the char inside arr1 are %s\n", arr1);
printf("Before copy , the char inside arr2 are %s\n", arr2);
printf("\n");
strcpy(arr2, arr1);
//strcpy函数使用格式为:strcpy(目的地址, 源地址);
printf("After copy , the char inside arr1 are %s\n", arr1);
printf("After copy , the char inside arr2 are %s\n", arr2);
return 0;
}
我们初始化字符数组 arr 中的内容,并使 arr2 中内容为空。首先我们将两个字符串进行打印,验证拷贝前两字符串中的存储内容。接着我们使用 strcpy 函数将字符数组 arr1 中的字符串拷贝至 arr2 中,并在此打印两数组中的内容对拷贝结果进行验证。
模拟实现
基础模拟
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
while (*dest++ = *src++)//拷贝过去,往后慢慢走加一跳地址
{
;
}
return ret;//返回改完的起始地址,所以前面ret保留一下
}
int main()
{
//char* p = "abcdefghiqwer";//保证空间能修改,这样都不能被修改,是错误的
char arr1[20] = "";//保证空间足够
char arr2[] = "hello bit";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
对应上面的用法来写
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* p2, const char* p1)
{
assert(p2 != NULL);
assert(p1 != NULL);
//等价于assert(p2 && p1);
char* ret = p2;
//将目的地址作为返回值
while (*p2++ = *p1++)
{
;
}
//返回目的地址的作用是为了实现链式访问
return ret;
}
int main()
{
const char arr1[] = "Welcome!";
const char arr2[10] = { 0 };
printf("Before copy , the char inside arr1 are %s\n", arr1);
printf("Before copy , the char inside arr2 are %s\n", arr2);
printf("\n");
my_strcpy(arr2, arr1);
printf("After copy , the char inside arr1 are %s\n", arr1);
printf("After copy , the char inside arr2 are %s\n", arr2);
return 0;
}
2.strcat
strcat 函数(string catenate)的作用是,将源地址的字符串追加补充至目的地址处。与字符串拷贝函数相同,它在进行补充追加时是从目的地址的结束标识符处 ' \0 ' 开始追加的,追加至源地址的结束标识符处停止。且它同样要求目标地址内的空间必须足够大,要有足够的空间容纳下源地址内的字符串,同时目的地址的空间必须是可变、可修改的。
注意事项
- 源字符串必须以 '\0' 结束。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
#include<stdio.h>
#include<string.h>
int main()
{
const char arr1[20] = "Hellow!";
const char arr2[20] = "Welcome!";
printf("Before catenate , the char inside arr1 are %s\n", arr1);
printf("Before catenate , the char inside arr2 are %s\n", arr2);
printf("\n");
strcat(arr2, arr1);
printf("After catenate , the char inside arr1 are %s\n", arr1);
printf("After catenate , the char inside arr2 are %s\n", arr2);
return 0;
}
我们同样首先定义并初始化两个字符数组,接着打印他们验证他们各自内部的存储内容。然后通过使用stract 函数,我们将字符数组 arr21中的内容,成功的追加补充到了字符数组 arr1 中,并再次进行了打印,验证追加补充的结果。
注意,strcat 函数无法追加自己。原因很好理解,我们在定义时就已经固定了字符数组的储存空间了,当追加自己时,相当于将自己与和与自己相同大小的字符数组,即两倍自身大小的数据放入自己的储存空间中,可想而知一定是不可行的。
模拟实现
基础实现,切记不能自己给自己追加
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
//1. 找目标空间的\0
while (*dest)
{
dest++;
}
//2. 追加
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr[20] = "hello ";
my_strcat(arr, "word");
char arr[20] = "hello ";
char arr2[] = "world";
my_strcat(arr, arr2);
char arr[20] = "hello ";
char* p = "world";
my_strcat(arr, p);
printf("%s\n", arr);
return 0;
}
对应上面用法的题目来写
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
char* my_strcat(char* p2, char* p1)
{
assert(p2 && p1);
char* ret = p2;
//查找目标空间中的结束标识符,找到后停止
while (*p2)
{
p2++;
}
//从结束标识符开始追加
//追加:
while (*p2++ = *p1++)
{
;
}
return ret;
}
int main()
{
const char arr1[20] = "Hellow!";
const char arr2[20] = "Welcome!";
printf("Before catenate , the char inside arr1 are %s\n", arr1);
printf("Before catenate , the char inside arr2 are %s\n", arr2);
printf("\n");
my_strcat(arr2, arr1);
printf("After catenate , the char inside arr1 are %s\n", arr1);
printf("After catenate , the char inside arr2 are %s\n", arr2);
return 0;
}
3.strcmp
strcmp 函数(string compare)的作用为按照顺序依次比较两字符串对应位置字符的 ASCII 码值(注意不是比较两字符串的长度),直到结束标识符 ' \0 ' 或对应位置的字符不同。若比较至结束标识符都没有不同则两字符串相等,若两字符串对应位置字符有不同,对应位置上字符 ASCII 码值小的字符串小于另一个字符串。
注意
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
#include<stdio.h>
#include<string.h>
int main()
{
const char arr1[] = "abcd";
const char arr2[] = "abz";
int ret = strcmp(arr1, arr2);
//若arr1大于arr2,则返回大于零的数
//若arr1等于arr2,则返回等于零的数
//若arr1小于arr2,则返回小于零的数
if (ret > 0)
{
printf("arr1 > arr2\n");
}
else if (ret = 0)
{
printf("arr1 = arr2\n");
}
else
{
printf("arr1 < arr2\n");
}
return 0;
}
首先我们定义并初始化两个字符数组,接着对两个数组进行比较,根据 strcmp 函数的比较结果得到返回值,再根据返回值反馈我们想要的字符串比较结果。
该函数的作用并不是比较字符串的长短,而是比较对应位置字符 ASCII 码值的大小。
模拟实现
基础实现
int my_strcmp(const char* str1, const char* str2)
{
assert(str1 && str2);
while (*str1 == *str2)
{
if (*str1 == '\0')//相等的时候,看是不是\0
return 0;
str1++;
str2++;
}
if (*str1 > *str2)
return 1;
else
return -1;
return *str1 - *str2;//上面if——else一段可以这样写,很简便
}
int main()
{
char arr1[] = "abq";
char arr2[] = "abcdef";
int ret = strcmp(arr1, arr2);//库函数使用,对应字符位置去比较,比较大小的,对应字符串位置上的内容
int ret = my_strcmp(arr1, arr2);//模拟实现
if (ret>0)
printf("arr1>arr2\n");
return 0;
}
根据上面用法写法
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
//模拟实现strcmp函数:
int my_strcmp(const char* p1, const char* p2)
{
assert(p1 && p2);
//找到字符不同的对应位:
while (*p1 == *p2)
{
if (*p1 == '\0')
{
return 0;
}
p1++;
p2++;
}
if (*p1 > *p2)
{
return 1;
}
else
{
return -1;
}
}
int main()
{
const char arr1[] = "abcd";
const char arr2[] = "abz";
int ret = my_strcmp(arr1, arr2);
//若arr1大于arr2,则返回大于零的数
//若arr1等于arr2,则返回等于零的数
//若arr1小于arr2,则返回小于零的数
if (ret = 1)
{
printf("arr1 > arr2\n");
}
else if (ret = 0)
{
printf("arr1 = arr2\n");
}
else
{
printf("arr1 < arr2\n");
}
return 0;
}
四.长度受限制函数
1.strncpy
strncpy函数(string number copy)的作用为将指定长度的字符串复制到字符数组中,即表示把源地址中字符串开始的前n个字符拷贝到目的地址中。与 strcpy 相同,它同样会将源地址内的结束标识符 ' \0 ' 一并拷贝过去,因此源地址必须以 ' \0 ' 结尾,且目的地址也将以结束标识符结尾。并且,因为其作用为拷贝字符串,因此目标地址内的空间必须足够大,要有足够的空间容纳下源地址内的字符串,同时目的地址的空间必须是可变、可修改的。
注意
- 此函数不受到 '\0' 的影响,拷贝num个字符从源字符串到目标空间。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
#include<stdio.h>
#include<string.h>
//strncpy函数的使用:
int main()
{
const char arr1[] = "Hellow!Welcome!";
const char arr2[10] = { 0 };
printf("Before copy , the char inside arr1 are %s\n", arr1);
printf("Before copy , the char inside arr2 are %s\n", arr2);
strncpy(arr2, arr1, 7);
printf("After catenate , the char inside arr1 are %s\n", arr1);
printf("After catenate , the char inside arr2 are %s\n", arr2);
return 0;
}
我们看到,使用该函数,我们成功的将字符数组 arr1 中的前七个字符拷贝到了 字符数组 arr2 中,并且通过前后两次的反馈打印验证了按指定长度拷贝操作成功完成。
注意:若源字符串的长度小于我们传递过去的参数,则拷贝完源字符串之后,将会在目标后追加字符 ' 0 ',直到拷贝至参数规定个数。
模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strncpy(char* dest, const char* str, size_t n)
{
assert(dest && str);
char* ret = dest;
while (n--)
{
*dest++ = *str++;
}
return ret;
}
int main()
{
char arr1[] = "xxxxxxxxxx";
char arr2[] = "abcde";
printf("%s\n", my_strncpy(arr1, arr2, 4)); // abcdxxxxxx
return 0;
}
2.strncat
strncat 函数(string num catenate)的作用为从源地址处将指定长度的字符串追加补充到目的地址中。与 strcat 函数类似,它在进行补充追加时也是从目的地址的结束标识符处 ' \0 ' 开始追加的,不同的是追加至参数限制的字符数处停止。但它同样要求目标地址内的空间必须足够大,要有足够的空间容纳下出家补充的字符串,同时目的地址的空间必须是可变、可修改的。
#include<stdio.h>
#include<string.h>
int main()
{
const char arr1[] = "Hellow!Welcome!";
const char arr2[20] = "Welcome!";
printf("Before copy , the char inside arr1 are %s\n", arr1);
printf("Before copy , the char inside arr2 are %s\n", arr2);
strncat(arr2, arr1, 7);
printf("After catenate , the char inside arr1 are %s\n", arr1);
printf("After catenate , the char inside arr2 are %s\n", arr2);
return 0;
}
我们可以看到,首先定义并初始化两个字符数组,接着打印追加补充之前它们各自空间内的内容进行确认,然后我们使用了 strncat 函数有限制的从数组 arr1 向 arr2 中追加补充了七个字符,最后进行了打印反馈,验证了我们的有限制追加补充结果。
模拟实现
#include<stdio.h>
#include<assert.h>
char* my_strncat(char* dest, const char* str, size_t n)
{
assert(dest && str);
char* ret = dest;
while (*dest)
{
dest++;
}
while (n--)
{
*dest++ = *str++;
}
*dest = '\0';
return ret;
}
int main()
{
char arr1[20] = "hello\0xxxxx";
char arr2[] = "bitxxxxx";
printf("%s\n", my_strncat(arr1, arr2, 3)); //hellobit
return 0;
}
3.strncmp
strncmp 函数(string number compare)的作用为有限制的按照顺序依次比较两字符串对应位置字符的 ASCII 码值(注意不是比较两字符串的长度),直到参数限制位数位置上全部比较结束或对应位置的字符不同。若参数限制位数位置上的字符都比较结束且都没有不同则两字符串相等,若两字符串对应位置字符有不同,对应位置上字符 ASCII 码值小的字符串小于另一个字符串。
#include<stdio.h>
#include<string.h>
int main()
{
const char arr1[] = "abcd";
const char arr2[] = "abz";
int ret = strncmp(arr1, arr2, 3);
//比较前 3 个字符
//若arr1大于arr2,则返回大于零的数
//若arr1等于arr2,则返回等于零的数
//若arr1小于arr2,则返回小于零的数
if (ret > 0)
{
printf("arr1 > arr2\n");
}
else if (ret = 0)
{
printf("arr1 = arr2\n");
}
else
{
printf("arr1 < arr2\n");
}
return 0;
}
其作用原理与作用过程,与 strncmp 函数十分类似,唯一不同便是限制了字符比较的位数,所以在此不再做过多的阐述,可以参考 strncmp 函数进行理解。
该函数的作用也不是比较字符串的长短,而是比较对应位置字符 ASCII 码值的大小。
模拟实现
#include<stdio.h>
#include<assert.h>
int my_strncmp(char* dest, const char* str, size_t n)
{
int ret = 0;
assert(dest && str);
while (n && !(*dest - *str))
{
n--;
dest++;
str++;
}
if (n && *dest - *str > 0)
return 1;
else if (n && *dest - *str < 0)
return -1;
return ret;
}
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abcqqqqq";
printf("%d\n", my_strncmp(arr1, arr2, 3)); //0
return 0;
}
五.字符串查找
1.strstr
strstr 函数(string string)的作用为从一个字符串中寻找其字串,通俗来讲就是从一个字符串中寻找另一个字符串。若找到目标字串则返回指向目标字串的指针,若没有找到目标字串则返回空指针(不返回)。
#include<stdio.h>
#include<string.h>
int main()
{
const char arr1[] = "abcdefg";
const char arr2[] = "cde";
char* ret = strstr(arr1, arr2);
//从ar1中寻找arr2
if (ret == NULL)
{
printf("找不到该字符串!\n");
}
else
{
printf("成功找到该字符串'%s'!\n", ret);
}
return 0;
}
我们定义并初始化了两个字符数组,并使用 strstr 函数进行字串寻找处理,并使用字符型指针接收汉函数的返回值,最后,对接收了返回值的指针 ret 进行判断,若为空则确定为没有从数组 arr1 中找到字串 arr2,否则便通过返回值,使用指针对字串地址内的数据进行访问。
模拟实现
char* my_strstr(const char* str1, const char* str2)
{
}
assert(str1 && str2);
if (*str2 == '\0')
{
return (char*)str1;
}
const char* s1 = NULL;
const char* s2 = NULL;
const char* cp = str1;
while (*cp)
{
s1 = cp;
s2 = str2;
while (*s1 !='\0' && *s2!='\0' && *s1 == *s2)
{
s1++;
s2++;
}
if (*s2 == '\0')
{
return (char*)cp;
}
cp++;
}
return NULL;
}
int main()
{
char arr1[] = "abbbcdbbcef";
char arr2[] = "bbc";
char* ret = my_strstr(arr1, arr2);//模拟实现
if (ret == NULL)
{
printf("找不到\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
最开始不相等的时候,str1往后走,然后相等然后需要记住这个相等的地址,因为成功了要返回这个地址,不成功了,str1要回来这个地址str1和str2同时往后走,走到不相等的时候,我的str2要回到开始的地址所以我们写s1和s2,去代替str1和str2往后走,如果失败了,可以重新让s2接收str2初始的地址再写一个cp去保存开始相等的那个地址,这样失败了,可以让s1接收cp地址,再往后找,成功了,就返回cp开始相等的地址
2.strtok
strtok 函数(string token)的作用为将字符串分解为一组字符串。听起来似乎很难理解,其实很简单。该函数有两个数组作为参数,它的实际作用便是将其中一个数组组为分割数组,在另一个数组中寻找这些“分割符”,并在分割符处将这个数组内的字符串加上结束标识符 ' \0 ' ,将其分割成一组(多个)字符串。若第一个参数不为 NULL ,将找到字符数组中的第一个标记并保存它在字符串中的位置;若第一个参数为 NULL ,将在同一个字符串中被保存的位置开始,查找下一个标 记。
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "1254594572@QQ.COM";
char arr2[30] = { 0 };
strcpy(arr2, arr1);
const char* arr3 = "@.";
printf("账号:%s\n", strtok(arr2, arr3));
//找到第一个标记停止
printf("服务商:%s\n", strtok(NULL, arr3));
//从保存好的位置开始往后找
printf("网址后缀:%s\n", strtok(NULL, arr3));
//从保存好的位置开始往后找
return 0;
}
首先我们要知道,strtok 函数是会对数组本身进行操作的,所以我们为了保护原始数据,在定义并初始化好字符数组之后,又定义了一个新的数组并将原始数据拷贝过去,作为临时拷贝供我们进行操作。接着我们定义并初始化了分割符数组,函数将根据分割符数组 arr3 对 临时拷贝 arr2 进行分割。第一次执行函数时之前没有标记,于是直接进行操作找到第一个标记并分割打印。此时就已经存在标记了,再连续两次找到前一次执行作下的标记按照分割符将数组分割完毕并打印。
但是上面的代码风格较为简陋、复杂,我们可以将上面段代码优化为:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "1254594572@QQ.COM";
char arr2[30] = { 0 };
strcpy(arr2, arr1);
const char* arr3 = "@.";
char* str = NULL;
for (str = strtok(arr2, arr3); str != NULL; str = strtok(NULL, arr3))
{
printf("%s\n", str);
}
return 0;
}
六.错误信息报告
strerror
strerror函数(string error)的作用为返回错误码对应的信息。即根据接收到的错误码(错误码 errno 为全局变量),返回错误原因的详细信息。
#include<stdio.h>
#include<string.h>
int main()
{
int i = 0;
for (i = 0; i <= 4; i++)
{
printf("错误原因为:%s\n", strerror(i));
}
return 0;
}
当库函数使用的时候,发生错误会把errno这个全局的错误变量设置为本次执行库函数产生的错误码,errno是C语言提供的一个全局变量,可以直接使用,放在errno.h文件中的
int main()
{
//打开文件 ——fopen提供文件没和打开方式,打开成功返回非空指针,打开失败就是NULL
//我们用strerror,看错误信息
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
printf("%s\n", strerror(errno));
perror("fopen");
return 1;
}
//读文件
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
还有个函数是perror,是打印错误信息,相当于printf+strerror
七.字符操作
1.字符分类函数
函数 | 如果他的参数符合下列条件就返回真 |
iscntrl | 任何控制字符 |
isspace | 空白字符:空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v' |
isdigit | 十进制数字 0~9 |
isxdigit | 十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F |
islower | 小写字母a~z |
isupper | 大写字母A~Z |
isalpha | 字母a~z或A~Z |
isalnum | 字母或者数字,a~z,A~Z,0~9 |
ispunct | 标点符号,任何不属于数字或者字母的图形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
例如:isdigit
char ch = '0';
if (ch >= '0' && ch <= '9')
{
//复杂
}
if (isdigit(ch))
{
//方便快捷
}
2.字符转换
int tolower ( int c ); //把大写转为小写
int toupper ( int c ); //把小写转为大写
#include<stdio.h>
#include <ctype.h>
int main()
{
char ch = 0;
while (ch = getchar())
{
if (islower(ch))
{
ch = toupper(ch);
}
else
{
ch = tolower(ch);
}
printf("%c", ch);
}
return 0;
}
大写改为小写,小写改为大写
八.内存操作函数
1.memcpy
memcpy函数(memory copy)的作用为从源内存空间向目的内存空间拷贝限制数量(单位是字节)的数据。它与 strcpy 函数类似,作用均为拷贝数据,不同的是 strcpy 仅仅只操作字符串故会在结束标识符 ' \0 ' 处停止,而 memcpy 函数操作的是内存,内存中的数据是相邻的,故不会在结束标识符处停止。
注意
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
- 这个函数在遇到 '\0' 的时候并不会停下来。
- 如果source和destination有任何的重叠,复制的结果都是未定义的。
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9 };
int arr2[5] = { 0 };
printf("Before copy , the char inside arr2 are :>");
int i = 0;
for (i = 0; i < 5; i++)
{
printf(" %d", arr2[i]);
}
printf("\n");
memcpy(arr2, arr1, 20);
printf("After copy , the char inside arr2 are :>");
for (i = 0; i < 5; i++)
{
printf(" %d", arr2[i]);
}
printf("\n");
return 0;
}
我们首先创建并初始化了两个整形数组,接着打印出数组 arr2 内数据的存放情况。紧接着我们通过内存拷贝函数,将数组 arr1 内的前20个字节的数据拷贝给了 arr2数组。整型数组内每个数据元素所占的内存空间为4个字节,20个字节即将数组 arr1 中的前五个数据元素拷贝给了数组 arr2。最后再次打印数组 arr2 中的数据,验证拷贝结果。
如果源内存空间和目标内存空间有任何的重叠,复制的结果都是未定义的。
模拟实现
//小细节——这里事void指针,我们拷贝的时候转化为什么,要转化为char类型,如果int的话,我们拷贝17个字节,int一次四个,所以用char,一个一个
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;//返回目标的起始地址,所以保持一下
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;//强制类型转换,不能用++,不然类型不符合
src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
int ret = my_memcmp(arr2, arr1+2, 20);
printf("%d\n", ret);
return 0;
}
但是当我们出现内存重叠的时候怎么拷贝,就是下面test2函数
void* my_memcpy(void* dest, const void* src, size_t num)
{
void* ret = dest;//返回目标的起始地址,所以保持一下
assert(dest && src);
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;//强制类型转换,不能用++,不然类型不符合
src = (char*)src + 1;
}
return ret;
}
void test1()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1 + 2, 17);
}
void test2()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memcpy(arr1 + 2, arr1, 20);//12345拷贝的34567,自己给自己拷贝
}
上面自己给自己,我们拷贝到3的时候,1已经把3给覆盖了,内存重叠的时候,出现问题,加一个判断,重叠的时候从后往前,正常的时候就从前往后,当dest在src前面的时候从前向后——当dest在src后面的时候从后向前
但是我们库函数memmove就是提供给我们拷贝重复内存的,我们来看下面memmove的模拟实现,就是对我们模拟实现memcpy改造
2.memmove
当我们拷贝时候出现内存空间重复的时候,就用到我们的memmove,就是拷贝重复空间的函数
memmove函数(memory move)的作用为弥补 memcpy 函数的不足,主要用于处理内存的重叠部分。即如果源空间和目标空间出现重叠,就得使用 memmove 函数来进行处理。
注意:
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
- 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
#include<stdio.h>
#include<string.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9 };
printf("Before copy , the char inside arr are :>");
int i = 0;
for (i = 0; i < 5; i++)
{
printf(" %d", arr[i]);
}
printf("\n");
memmove(arr+2, arr, 20);
printf("After copy , the char inside arr are :>");
for (i = 0; i < 5; i++)
{
printf(" %d", arr[i]);
}
printf("\n");
return 0;
}
这个函数与 memcpy 函数除处理对象不同外,语法结构和使用场景等都极其类似,这里也就不再做过多赘述。
模拟实现
void* my_memmove(void* dest, const void* src, size_t num)
{
void* ret = dest;
assert(dest && src);
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;
}
void test3()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1+2, arr1, 20);
}
void test4()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memcpy(arr1, arr1+2, 20);//也是可以搞定的,但是谁的工作谁做,又不是没员工
}
int main()
{
test1();
test2();
test3();
test4();
return 0;
}
我们看test3和test4,所以库函数memcpy也可以,但是重叠的拷贝不是他干的活,还是交给memmove
3.memsrt
把一块内存空间设置成你想要的值,以字节为单位来修改
#include<stdio.h>
#include<string.h>
int main()
{
//char arr[20] = { 0 };
//memset(arr, 'x', 10);
//printf("%s\n", arr); //xxxxxxxxxx
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memset(arr, '\0', 10);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]); // 0 0 0 4 5 6 7 8 9 10
}
//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 ...将前10个字节改为0
//00 00 00 00 00 00 00 00 00 00 00 00 04 00 00 00
return 0;
}
4.memcmp
比较从ptr1和ptr2指针开始的num个字节,不在乎有无 '\0' ,你让它比较几个字节就比较几个字节。
#include<stdio.h>
#include<string.h>
int main()
{
int arr1[] = { 1,2,7,4,5 };
int arr2[] = { 1,2,3,4,5 };
printf("%d\n", memcmp(arr1, arr2, 9)); //1 // 9表示比较前9个字节
return 0;
}
结束语
这节课讲解很多重要的字符串函数和内存函数,有些模拟实现需要掌握,还有基本用法,参数等