目录
一.字符函数
字符串就是一串零个或多个字符,并且以一个位模式为全0的NULL字节结尾。因此,字符串所包含的字符内部不能出现NULL字节。NULL字节是字符串的终止符,但它本身并不是字符串的一部分,所以字符串的长度并不包含NULL字节。
字符函数分类:
- 求字符串长度:strlen;
- 长度不受限制的字符串函数:strcpy,strcat,strcmp;
- 长度受限制的字符串函数:strncpy,strncat,strncmp;
1.strlen函数
strlen(求字符串长度)函数原型:
size_t strlen(const char* str);
strlen所作的是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符'\0'为止,然后返回计数器值(长度不包含'\0')。
案例一:
int main()
{
char arr1[] = "abcdef";
char arr2[] = {'a','b','c','d','e','f'};
char arr3[] = { 'a','b','c','d','e','f','\0'};
//char arr4[5] = { 'a','b','c','d','e','f' };//err,初始值设定项太多
char arr5[10] = { 'a','b','c','d','e','f' };//因为没有完全初始化,后面会默认初始化为0,而0等同于'\0',对应的ASCII值相同
//不要混淆空字符'\0'和零字符'0'。空字符的码值为0,而零字符则有不同的码值(ASCII中为48)
int len1 = strlen(arr1);
int len2 = strlen(arr2);
int len3 = strlen(arr3);
//int len4 = strlen(arr4);
int len5 = strlen(arr5);
printf("%d\n", len1);//6
printf("%d\n", len2);//随机值22,arr2中不含\0,strlen函数则会一直向后读取,直到遇到\0
printf("%d\n", len3);//6
//printf("%d\n", len4);
printf("%d\n", len5);//6
return 0;
}
strlen返回一个类型为size_t的值(typedef unsigned
int size_t
),它是一个无符号整数类型,在表达式中使用无符号数可能导致不能预料的结果。例如,下面两个表达式看上去相等的:
if (strlen(x) >= strlen(y)){}
if (strlen(x) - strlen(y) >= 0){}
但事实上它们并不相等。第一条语句将按照你预想的那样工作,但第二条语句的结果将永远是真。strlen的结果是个无符号数,所以操作符>=左边的表达式也将是无符号数,而无符号数绝不可能是负的。
案例二:
int main()
{
//函数的返回值size_t,是无符号数
if (strlen("abc") - strlen("qwerty") > 0)//无符号数3-无符号数6=非常大的一个正数
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
运行结果:
案例二改进:
方案一:
int main()
{
if (strlen("abc") > strlen("qwerty"))
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
方案二:
int main()
{
if ((int)strlen("abc") - (int)strlen("qwerty") > 0)
{
printf(">\n");
}
else
{
printf("<=\n");
}
return 0;
}
警告:表达式中如果同时包含了有符号数和无符号数,可能会产生奇怪的结果,和前一对语句一样,下面两条语句并不相等,其原因相同。
if (strlen(x) >= 10){}
if (strlen(x) - 10 >= 0){}
如果把strlen的返回值强制转换为int,就可以消除这个问题。
模拟实现strlen
方式一:计数器
#include<assert.h>
size_t my_strlen(const char* str)
{
assert(str!=NULL);
int count = 0;
while (*str != '\0')
{
count++;
str++;
}
return count;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
方式二:递归
#include<assert.h>
size_t my_strlen(const char* str)
{
assert(str != NULL);
if (*str == '\0')
return 0;
else
return 1 + my_strlen(str + 1);
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
方式三:指针-指针
#include<assert.h>
size_t my_strlen(const char* str)
{
assert(str != NULL);
char* p = s;
while (*p != '\0')
p++;
return p - s;
}
int main()
{
char arr[] = "abcdef";
int len = my_strlen(arr);
printf("%d\n",len);
return 0;
}
2.strcpy函数
strcpy(字符串复制)函数原型:
char* strcpy(char* dest, const char* src);
把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间。src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串,如果参数src和dest在内存中出现重叠,其结果是未定义的。由于dest参数将进行修改,所以它必须是个字符数组或者是一个指向动态分配内存的数组的指针,不能使用字符串常量。
目标参数以前的内容将被覆盖并丢失。即使新的字符串比dest原先的内存更短,由于新字符串是以NULL字节结尾,所以老字符串最后剩余的几个字符也会被有效地删除。
案例一:源字符串必须以'\0'结束
int main()
{
char arr1[20] = {0};
//char arr2[] = "abcdef";//以'\0'结束
char arr2[] = { 'a','b','c' };//不以'\0'结束
strcpy(arr1,arr2);//可能没有为字符串"arr2"添加字符串零终止符
printf("%s\n",arr1);
return 0;
}
源字符串不以'\0'结束,编译时会发出警到:可能没有为字符串“arr2”添加字符串零终止符。
案例二:会将源字符串中的'\0'拷贝到目标空间
int main()
{
char arr1[20] = "XXXXXXXX";
char arr2[] = "abcd";
strcpy(arr1,arr2);
printf("%s\n",arr1);
return 0;
}
调试发现:
案例三:目标空间必须足够大,能容下源字符串的内容
int main()
{
char arr1[4] = {0};
char arr2[] = "abcdef";
strcpy(arr1,arr2);//写入arr1时,缓冲区溢出
printf("%s\n",arr1);
return 0;
}
运行结果:
必须保证目标字符数组的空间足以容纳需要复制的字符串。如果字符串比数组长,多余的字符仍被复制,它们将覆盖原先存储于数组后面的内存空间的值。strcpy无法解决这个问题,因为它无法判断目标空间字符数组的长度。
案例四:目标空间必须可变
int main()
{
char* arr1 = "qwertyuiop";//arr1指向的是常量字符串,其内容是不可修改的
char arr2[] = "abcdef";
strcpy(arr1,arr2);
printf("%s\n",arr1);
return 0;
}
模拟实现strcpy
strcpy函数返回的是目标空间的起始地址
strcpy函数返回类型的设置是为了实现链式访问
链式访问:把一个函数的返回值作为另一个函数的参数
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
assert(dest != NULL);
assert(src != NULL);
char* ret = dest;
//while (*src!='\0')
//{
// *dest = *src;
// src++;
// dest++;
//}
//*dest = *src;//拷贝'\0'
//while (*src != '\0')
//{
// *dest++ = *src++;
//}
//*dest = *src;//拷贝'\0'
while (*dest++ = *src++)
{
;
}
return ret;//返回字符串起始地址
}
int main()
{
char arr1[20] = { 0 };
char* arr2 = "hello csdn";
printf("%s\n", my_strcpy(arr1, arr2));
return 0;
}
3.strcat函数
strcat(连接字符串)函数原型:
char* strcat(char* dest, const char* src);
把src所指向的字符串(包括“\0”)复制到dest所指向的字符串后面(删除dest原来末尾的'\0')。要保证dest足够长,以容纳被复制进来的src。src中原有的字符不变,并且返回指向dest的指针。如果src和dest的位置发生重叠,其结果是未定义的。
案例一:源字符串必须以'\0'结束
int main()
{
char arr1[20] = "hello ";
char arr2[] = "CSDN";
strcat(arr1,arr2);
printf("%s\n",arr1);
return 0;
}
调试分析:
案例二:目标空间必须足够大,能容纳下源字符串的内容
int main()
{
//char arr1[10] = {'h','e','l','l','o'};//默认包含'\0'
char arr1[] = { 'h','e','l','l','o' };//不包含'\0',且如果不指定大小的话,只能放5个字符,没法继续追加
char arr2[] = "CSDN";
strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
运行结果:
调试分析:
案例三:在源字符串追加给目标字符串时,源字符串的 ‘\0’ 也会追加
int main()
{
char arr1[20] = "hello\0xxxxxxxxxx";
char arr2[] = "CSDN";
printf("%s\n", strcat(arr1, arr2));
return 0;
}
调试分析:
模拟实现strcat
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
//找目标字符串中的'\0'
while (*dest)
{
dest++;
}
//再把源字符串拷贝到目标空间
while (*dest++ = *src++)
{
;
}
return ret;//返回目标字符串首地址
}
int main()
{
char arr1[20] = "hello";
char arr2[] = " CSDN";
printf("%s\n",my_strcat(arr1, arr2));
return 0;
}
strcat能否用于自己追加自己?
int main()
{
char arr[20] = "bit";
//my_strcat(arr,arr);
strcat(arr,arr);//'\0'被首字节'b'覆盖了,则无法确认'\0'的位置
printf("%s\n", strcat(arr, arr));
return 0;
}
调试分析:
通过调试可以发现,strcat在自己追加自己时,会陷入死循环。刚开始src和dest均指向‘b’,然后dest经过循环指向字符串“bit”后面‘\0’的位置。此时,src将dest之前的'b','i','t'三个字符逐一追加到从dest开始往后的位置。本来src再拷贝一个‘\0'时就已结束,但是此时字符串"bit"末尾的'\0'已被首字节'b'覆盖,因而无法确认'\0'的位置。所以src则会一直向后拷贝,直至陷入死循环。
4.strcmp函数
strcmp(字符串比较)函数原型:
int strcmp(const char* str1, const char* str2);
strcmp用于比较两个字符串,涉及对两个字符串对应的字符逐个进行比较,直到发现不匹配为止。那个最先不匹配的字符中较“小”(也就是说,在字符集中的序数较小)的那个字符所在的字符串被认为“小于”另外一个字符串。如果其中一个字符串是另外一个字符串的前面部分,那么它也被认为“小于”另外一个字符串,因为它的NULL结尾字节出现得更早。这种比较被称为“词典比较”,对于只包含大写字母或只包含小写字母的字符比较,这种比较过程所给出的结果总是和我们日常所用的字母顺序的比较相同。
标准规定:
- 第一个字符串大于第二个字符串,则返回大于0的数字;
- 第一个字符串等于第二个字符串,则返回0;
- 第一个字符串小于第二个字符串,则返回小于0的数字;
案例一:
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abq";
int ret = strcmp(arr1, arr2);
//strcmp函数比较的不是字符串的长度
//而是比较字符串中对应位置上的字符的大小,如果相同,就比较下一对,直到不同或都遇到\0
//比较对应字符的ASCII码值
printf("%d\n", ret);//-1
return 0;
}
注意:标准并没有规定用于提示不相等的具体值,一个常见的错误是以为返回值是1和-1,分别代表大于和小于。但这个假设并不总是正确的。
案例二:
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abq";
int ret = strcmp(arr1, arr2);
//错误写法
//在vs编译器下,对应的值是-1 0 1
/*
if (ret == 1)
{
printf(">\n");
}
else if (ret == 0)
{
printf("==\n");
}
else if (ret == -1)
{
printf("<\n");
}
*/
//正确写法
if (ret>0)
{
printf(">\n");
}
else if (ret == 0)
{
printf("==\n");
}
else if (ret<0)
{
printf("<\n");
}
return 0;
}
模拟实现strcmp
#include<assert.h>
int my_strcmp(const char* s1, const char* s2)
{
assert(s1 && s2);
while (*s1 == *s2)
{
if (*s1 == '\0')
{
return 0;//相等的情况
}
s1++;
s2++;
}
//不相等的情况
if (*s1 > *s2)
return 1;
else
return -1;
//不相等
//return *s1 - *s2;
}
int main()
{
char arr1[] = "abcq";
char arr2[] = "abcdef";
int ret = my_strcmp(arr1,arr2);
if (ret > 0)
{
printf(">\n");
}
else if (ret == 0)
{
printf("==\n");
}
else if (ret < 0)
{
printf("<\n");
}
return 0;
}
5.strncpy函数
strncpy函数原型:
char* strncpy(char* dest, const char* src, size_t num);
strncpy函数用于将指定长度的字符串复制到字符数组中,即把src所指向的字符串中以src地址开始的前num个字节复制到dest所指的数组中,并返回被复制后的dest。
如果strlen(src)的值小于num,dest数组就用额外的NULL字节填充到num长度。如果strlen(src)的值大于或等于num,那么只有num个字符被复制到dest中。注意:它的结果将不会以NULL字节结尾。也就是结果dest不包括'\0',需要再手动添加一个'\0'。
简而言之,就是把src指向的字符串的前num个字符(不包括'\0','\0'得自己手动加在dest被复制之后)复制到dest指向的字符串中。如果要复制的src的部分有'\0',就把'\0'复制进去,之后就提前结束,即使没复制到第num个字符也是。返回指向dest的指针。
案例一:strlen(src)的值小于num,dest数组就用额外的NULL字节填充到num长度
int main()
{
char arr1[] = "abcdef";
char arr2[] = "qw";
strncpy(arr1, arr2, 4);
printf("%s\n", arr1);
return 0;
}
调试分析:
案例二:strlen(src)的值大于或等于num,那么只有num个字符被复制到dest中
int main()
{
char arr1[] = "abcdef";
char arr2[] = "qwrty";
strncpy(arr1, arr2, 3);
printf("%s\n", arr1);
return 0;
}
调试分析:
案例三:当dest没有足够的空间来容纳strlen(str)长度的字符串
int main()
{
char arr1[] = "abcdef";
char arr2[] = "qwertyuiop";
strncpy(arr1, arr2, 8);
printf("%s\n", arr1);
return 0;
}
运行结果:
6.strncat函数
strncat函数原型:
char* strncat(char* dest, const char* src, size_t num);
把src所指字符串的前num个字符添加到dest所指字符串的结尾处,并覆盖dest所指字符串结尾的'\0',从而实现字符串的连接。如果num大于字符串src的长度,那么仅将src指向的字符串内容追加到dest的尾部。 注意:src和dest所指内存区域不可以重叠,并且dest必须有足够的空间来容纳src的字符串。
案例一:strlen(src)的值小于num,则追加到源字符串的'\0'处时就停止追加
int main()
{
char arr1[20] = "abcdef\0XXXXXXXX";
char arr2[] = "qwe";
strncat(arr1, arr2, 5);
printf("%s\n", arr1);
return 0;
}
调试分析:
案例二:strlen(src)的值大于或等于num,追加完后主动补'\0'
int main()
{
char arr1[20] = "abcdef\0XXXXXXXX";
char arr2[] = "qwertyuiop";
strncat(arr1, arr2, 5);
printf("%s\n", arr1);
return 0;
}
调试分析:
7.strncmp函数
strncmp函数原型:
int strncmp(const char* str1, const char* str2, size_t num);
strncmp函数为字符串比较函数,字符串大小的比较是以ASCII 码表上的顺序来决定,此顺序亦为字符的值。功能是把 str1 和 str2 进行比较,最多比较前 num 个字节,若str1与str2的前num个字符相同,则返回0;若str1大于str2,则返回大于0的值;若str1 小于str2,则返回小于0的值。
标准规定:
- 如果返回值 < 0,则表示 str1 小于 str2;
- 如果返回值 > 0,则表示 str2 小于 str1;
- 如果返回值 = 0,则表示 str1 等于 str2。
案例一:
int main()
{
char arr1[] = "abcdef";
char arr2[] = "qwertyuiop";
int ret = strncmp(arr1,arr2,4);
printf("%d\n",ret);//-1
return 0;
}
案例二:
int main()
{
char arr1[] = "abcdef";
char arr2[] = "abc";
if (arr1 < arr2)//俩个地址在比较:因为数组名表示首元素的地址
{
}
if ("abc" < "abcdef")//俩地址在比较:常量字符串有各自的起始地址,而首元素通常代表首元素的起始地址
{
}
return 0;
}
模拟实现strncmp
int my_strncmp(const char* str1, const char* str2, size_t num)
{
assert(str1&&str2);
while (num && *str1 && *str2)
{
if (*str1 > *str2)
{
return 1;
}
if (*str1 < *str2)
{
return -1;
}
num--;
str1++;
str2++;
}
return 0;
}
int main()
{
char* str1 = "abcdkfg";
char* str2 = "abcdejk";
int num = 0;
scanf("%d", &num);
int ret = my_strncmp(str1, str2, num);
printf("%d\n", ret);
return 0;
}
8.strstr函数
strstr(查找一个子串)函数原型:
char* strstr(const char* str1, const char* str2);
这个函数在str1中查找整个str2第一次出现的起始位置,并返回一个指向该位置的指针。如果str2并没有完整地出现在str1的任何位置,函数将返回一个NULL指针。如果2个参数是一个空字符串,函数就返回str1。
案例一:
int main()
{
char arr1[] = "abcdefabcdef";
char arr2[] = "cdef";
char* ret = strstr(arr1, arr2);
if (NULL == ret)
{
printf("找不到子串\n");
}
else
{
printf("%s\n",ret);
}
return 0;
}
模拟实现strstr
char* my_strstr(const char* str1, const char* str2)
{
assert(str1&&str2);
const char* s1 = str1;
const char* s2 = str2;
const char* cur = str1;//记录当前开始匹配的位置
while (*cur)
{
s1 = cur;//s1指向cur的位置,从cur开始往后比较
s2 = str2;//s2指向str2的位置,回到str2起始位置
while (*s1 && *s2 && (*s1== *s2))//*s1和*s2均不能为'\0',当*s1为'\0'说明str1已被比较完,当*s2为'\0'说明子串已比较完,已匹配到
{
s1++;
s2++;
}
//当*s2为\0说明子串已比较完,已匹配到
if (*s2 == '\0')
{
return (char*)cur;//cur返回这一次开始匹配的起始位置
}
//*s1与*s2不相等,则cur++,来到下一个位置
cur++;
}
//当*cur的值为'\0',则表明查完
return NULL;
}
int main()
{
char arr1[] = "abbbcdef";
char arr2[] = "bbc";
char* ret = my_strstr(arr1, arr2);
if (NULL == ret)
{
printf("找不到子串\n");
}
else
{
printf("%s\n", ret);
}
return 0;
}
9.strtok函数
strtok(查找标记)函数原型:
char* strtok(char* str, const char* sep);
sep参数是个字符集,定义了用作分隔符的字符集合。第1参数指定一个字符串,它包含零个或多个由sep字符串中一个或多个分隔符分隔的标记。strtok找到str的下一个标记,并将其用NULL结尾,然后返回一个指向这个标记的指针。
如果strtok函数的第1个参数不是NULL,函数将找到字符串的第1个标记。strtok同时将保存它在字符串中的位置。如果strtok函数的第1个参数是NULL,函数就在同一个字符串中从这个被保存的位置开始像前面一样查找下一个标记。如果字符串内不存在更多的标记,strtok函数就返回一个NULL指针。在典型情况下,在第1次调用strtok时,向它传递一个指向字符串的指针。然后,这个函数被重复调用(第1个参数为NULL),直到它返回NULL为止。
简而言之:
- sep参数是个字符串,定义了用作分隔符的字符集合;
- 第一个参数指定一个字符串,它包含了0个或多个由sep字符串中一个或多个分隔符分割的标记;
- strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。);
- strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置;
- strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记;
- 如果字符串中不存在更多的标记,则返回NULL指针。
案例一:
int main()
{
char arr[] = "zpengwei@yeah.net";
char buf[20] = {0};
strcpy(buf,arr);
const char* sep = "@.";
char* str = NULL;
//法一:
for (str = strtok(buf, sep); str != NULL;str=strtok(NULL,sep))
{
printf("%s\n",str);
}
//法二:
//printf("%s\n", strtok(buf, sep));//只找第一个标记
//printf("%s\n", strtok(NULL, sep));//从保存好的第一个标记的位置开始继续往后找
//printf("%s\n", strtok(NULL, sep));//从保存好的第二个标记的位置开始继续往后找
return 0;
}
10.strerror函数
strerror(错误信息)函数原型:
char* strerror(int errnum);
当你调用一些函数,请求操作系统执行一些功能如打开文件时,如果出现错误,操作系统是通过设置一个外部的整型变量errno进行错误代码报告的。strerror函数把其中一个错误代码作为参数并返回一个指针用于描述错误的字符串的指针。
通常strerror会和errno搭配使用。同时使用errno时,需要头文件errno.h
案例一:
int main()
{
printf("%s\n", strerror(0));
printf("%s\n", strerror(1));
printf("%s\n", strerror(2));
printf("%s\n", strerror(3));
return 0;
}
运行结果:
案例二:
#include<errno.h>
#include<limits.h>
int main()
{
int* p = (int*)malloc(INT_MAX);//向堆区申请内存
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
return 0;
}
运行结果:
扩展:
perror(s):用来将上一个函数发生错误的原因输出到标准设备(stderr);
参数s所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用某些函数出错时,该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和errno所对应的错误一起输出。
不可以丢了#include<stdio.h>这个头文件,perror是包含在这个文件里的。
11.字符分类函数
11.1.isdigit函数
int isdigit(int c):用于检查所传的字符是否是十进制数字字符。若参数c为阿拉伯数字0~9,则返回非0值,否则返回0
对应的头文件为#include<ctype.h>
int main()
{
int ret = isdigit('5');
printf("%d\n",ret);
return 0;
}
11.2.islower函数
int islower(int c):用于检查所传的字符是否是小写字母。如果参数是一个小写字母,则该函数返回非零值(true),否则返回0(false)。
对应的头文件为#include<ctype.h>
int main()
{
char ch = 'c';
int ret = islower(ch);
printf("%d\n",ret);
return 0;
}
11.3.toupper函数
int toupper(int c):用于把小写字母转换为大写字母。如果参数有相对应的大写字母,则该函数返回参数的大写字母,否则参数保持不变。返回值是一个可被隐式转换为 char 类型的 int 值。
对应的头文件为#include<ctype.h>
int main()
{
char ch = 'a';
putchar(toupper(ch));
return 0;
}
11.4.tolower函数
int tolower(int c):用于把给定的字母转换为小写字母。如果参数有相对应的小写字母,则该函数返回参数的小写字母,否则参数保持不变。返回值是一个可被隐式转换为 char 类型的 int 值。
对应的头文件为#include<ctype.h>
int main()
{
char ch = 'a';
putchar(tolower(ch));
return 0;
}
二.内存函数
字符串由一个NULL字节结尾,所以字符串内部不能包含任何NULL字符。但是,非字符串数据内部包含零值的情况并不罕见。你无法使用字符串函数来处理这种类型的数据,因为当它们遇到第一个NULL字节时将停止工作。不过,我们可以使用另外一组相关的函数,它们的操作与字符串函数类似,但这些函数能够处理任意的字节序列。它们分别为:memcpy,memmove,memcmp,memset。
每个原型都包含一个显式的参数说明需要处理的字节数。但和strn带头的函数不同,它们在遇到NULL字节时并不会停止操作。
1.memcpy函数
memcpy函数原型:
void* memcpy(void* dest, const void* src, size_t num);
memcpy从src的起始位置复制num个字节到dest的内存起始位置。可以用这种方式复制任何类型的值。第3个参数指定复制值的长度(以字节计)。该函数在遇到'\0'的时候并不会停止拷贝。如果src和dest以任何形式出现了重叠,它的结果是未定义的。
案例一:
int main()
{
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
int arr2[5] = {0};
memcpy(arr2,arr1,20);//20字节,可存放5个整数
return 0;
}
调试分析:
案例二:
int main()
{
char arr1[] = "abcd\0efgh";
char arr2[20] = { 0 };
memcpy(arr2, arr1, 6);
return 0;
}
调试分析:该函数在遇到'\0'的时候并不会停止拷贝。
案例三:在同一块内存内进行拷贝,此时src和dest有重叠
int main()
{
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
memcpy(arr1 + 2, arr1, 20);
int i = 0;
int sz = sizeof(arr1) / sizeof(arr1[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);//1 2 1 2 1 2 1 8 9 10
}
return 0;
}
调试分析:虽然标准说明:如果src和dest以任何形式出现了重叠,它的结果是未定义的。但是在VS编译器下,memcpy函数还是可以达到预期效果的。
模拟实现memcpy
void* my_memcpy(void* dest, const void* src, size_t count)
{
assert(dest&&src);
void* ret = dest;
while (count--)
{
//逐字节拷贝
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
//++(char*)dest;
//++(char*)src;
}
return ret;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[5] = { 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;
}
2.memmove函数
memmove函数原型:
void* memmove(void* dest, const void* src, size_t num);
和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。如果源空间和目标空间出现重叠,就得使用memmove函数处理。memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,但复制后源内容会被更改。num表示要被复制的字节数。如果目标区域与源区域没有重叠,则和 memcpy函数功能相同。
虽然它并不需要以下面这种方式实现,不过memmove的结果和这种方法的结果相同:把源操作数复制到一个临时位置,这个临时位置不会与源或目标操作数重叠,然后再把它从这个临时位置复制到目标操作数。
案例一:
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20);
int i = 0;
int sz = sizeof(arr1) / sizeof(arr1[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);//1 2 1 2 3 4 5 8 9 10
}
return 0;
}
调试分析:
模拟实现memmove
基本步骤:
- 当dest<src且有重叠:从前向后拷贝,即从src的起始位置开始向dest的起始位置逐一拷贝;
- 当dest>=src且有重叠:从后向前拷贝,即从src的末尾位置开始从后向前向dest的末尾位置开始逐一拷贝;
- 当dest>src且无重叠:则从前向后拷贝,或从后向前拷贝均可。
void* my_memmove(void* dest, const void* src, size_t count)
{
//格式一:最简单
//if (dest < src)
//{
// //从前向后
//}
//else
//{
// //从后向前
//}
//格式二
//if (dest > src && dest < ((char*)src + count))
//{
// //从后向前,即中间部分
//}
//else
//{
// //从前向后,即两端部分
//}
assert(dest && src);
void* ret = dest;
if (dest < src)
{
//从前向后
while (count--)
{
//逐字节拷贝
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
//从后向前
while (count--)//从20变为19
{
*((char*)dest + count) = *((char*)src + count);
}
}
return ret;
}
int main()
{
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20);
int i = 0;
int sz = sizeof(arr1) / sizeof(arr1[0]);
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);//1 2 1 2 3 4 5 8 9 10
}
printf("\n");
return 0;
}
3.memcmp函数
memcmp函数原型:
int memecmp(const void* ptr1, const void* ptr2, size_t num);
memcmp对两段内存的内容进行比较,这两段内容分别起始于ptr1和ptr2,共比较num个字节。这些值按照无符号字符逐字节进行比较,函数的返回类型和strcmp函数一样:负值表示a小于b,正值表示a大于b,零表示a等于b。
int main()
{
int arr1[] = {1,2,3,4,5};
int arr2[] = { 1,2,3,4,0x11223305 };
int ret = memcmp(arr1,arr2,16);
printf("%d\n",ret);
return 0;
}
4.memset函数
memset函数原型:
void *memset(void *str, int c, size_t n);
复制字符c(一个无符号字符)到参数str所指向的字符串的前n个字节。注意,该函数是以字节为单位来初始化内存单元的。
案例一:
int main()
{
int arr[] = {0x11111111,0x22222222,3,4,5};
memset(arr,0,20);
return 0;
}
调试分析:
案例二:将一个字符数组的元素设置为1
int main()
{
char arr[5] = {0};
memset(arr,'1', 5);
for (int i = 0; i < 5; i++)
{
printf("%c ",arr[i]);
}
return 0;
}
调试分析:
案例三:将一个整型数组的元素设置为1
int main()
{
int arr[10] = { 0 };
int i = 0;
for (i = 0; i < 10; i++)
{
memset(&arr[i], 1, 1);//将最低位设置为1,其他位保持0不变
}
return 0;
}
调试分析:
以十进制数显示:
以十六进制数显示: