C语言进阶--字符函数与内存函数

news2024/7/4 15:49:27

目录

一.字符函数

1.strlen函数

模拟实现strlen

2.strcpy函数

模拟实现strcpy

3.strcat函数

模拟实现strcat

strcat能否用于自己追加自己?

4.strcmp函数

模拟实现strcmp

5.strncpy函数

6.strncat函数

7.strncmp函数

模拟实现strncmp

8.strstr函数

模拟实现strstr

9.strtok函数

10.strerror函数

11.字符分类函数

11.1.isdigit函数

11.2.islower函数

11.3.toupper函数

11.4.tolower函数

二.内存函数

1.memcpy函数

模拟实现memcpy

2.memmove函数

模拟实现memmove

3.memcmp函数

4.memset函数


一.字符函数

字符串就是一串零个或多个字符,并且以一个位模式为全0的NULL字节结尾。因此,字符串所包含的字符内部不能出现NULL字节。NULL字节是字符串的终止符,但它本身并不是字符串的一部分,所以字符串的长度并不包含NULL字节。

字符函数分类:

  1. 求字符串长度:strlen;
  2. 长度不受限制的字符串函数:strcpy,strcat,strcmp;
  3. 长度受限制的字符串函数: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结尾字节出现得更早。这种比较被称为“词典比较”,对于只包含大写字母或只包含小写字母的字符比较,这种比较过程所给出的结果总是和我们日常所用的字母顺序的比较相同。

标准规定:

  1. 第一个字符串大于第二个字符串,则返回大于0的数字;
  2. 第一个字符串等于第二个字符串,则返回0;
  3. 第一个字符串小于第二个字符串,则返回小于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的值。

标准规定:

  1. 如果返回值 < 0,则表示 str1 小于 str2;
  2. 如果返回值 > 0,则表示 str2 小于 str1;
  3. 如果返回值 = 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为止。

简而言之:

  1. sep参数是个字符串,定义了用作分隔符的字符集合;
  2. 第一个参数指定一个字符串,它包含了0个或多个由sep字符串中一个或多个分隔符分割的标记;
  3. strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。);
  4. strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置;
  5. strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记;
  6. 如果字符串中不存在更多的标记,则返回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

基本步骤:

  1. 当dest<src且有重叠:从前向后拷贝,即从src的起始位置开始向dest的起始位置逐一拷贝;
  2. 当dest>=src且有重叠:从后向前拷贝,即从src的末尾位置开始从后向前向dest的末尾位置开始逐一拷贝;
  3. 当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;
}

调试分析:

以十进制数显示:

以十六进制数显示:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

哨兵2号数据下载与利用Python处理(波段融合、降采样、可视化、裁剪等)

简单介绍 网址:https://scihub.copernicus.eu/dhus/#/home 哨兵2号(Sentinel-2)是欧洲空间局(European Space Agency,简称ESA)推出的一组遥感卫星,旨在为地球观测和环境监测提供高质量的光学图像数据。 S2MSI2A是哨兵2号卫星的一种传感器。 S2MSI2A是哨兵2号卫星搭载…

huggingface - PEFT.参数效率微调

GitHub - huggingface/peft: &#x1f917; PEFT: State-of-the-art Parameter-Efficient Fine-Tuning. 最先进的参数高效微调 (PEFT) 方法 Parameter-Efficient Fine-Tuning (PEFT) 方法可以使预训练语言模型 (PLM) 高效适应各种下游应用程序&#xff0c;而无需微调模型的所有…

记录--Vue3 封装 ECharts 通用组件

这里给大家分享我在网上总结出来的一些知识&#xff0c;希望对大家有所帮助 按需导入的配置文件 配置文件这里就不再赘述&#xff0c;内容都是一样的&#xff0c;主打一个随用随取&#xff0c;按需导入。 import * as echarts from "echarts/core"; // 引入用到的图表…

ctfshow web入门 php特性 web93-97

1.web93 intval($num,0),0代表根据变量类型进行使用哪一种进制进行取整 可以使用8进制&#xff0c;正负数&#xff0c;小数点 payload: 010574 4476.0 4476.0 2.web94 过滤了0&#xff0c;不能使用8进制了&#xff0c;还可以使用小数点&#xff0c;正负数等 payload&#xff1…

【Java算法题】剑指offer_算法之02动态规划

对于动态规划问题&#xff0c;我将拆解为如下五步曲&#xff0c;这五步都搞清楚了&#xff0c;才能说把动态规划真的掌握了&#xff01; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 JZ42 连续子数组的…

CTFshow-pwn入门-栈溢出pwn35-pwn36

pwn35 首先还是先下载pwn文件拖进虚拟机加上可执行权限&#xff0c;使用checksec命令查看文件的信息。 chmod x pwn checksec pwn32位的我们直接拖进ida中反编译&#xff1a; // main int __cdecl main(int argc, const char **argv, const char **envp) {FILE *stream; // […

阿里云 OSS介绍

1、什么是阿里云 OSS&#xff1f; OSS 为 Object Storage Service&#xff0c;即对象存储服务。是阿里云提供的海量、安全、低成本、高可靠的云存储服务。 OSS 具有与平台无关的 RESTful API 接口&#xff0c;可以在任意应用、任意时间、任意地点 存储与访问 任何类型的数据。…

软考:软件工程:软件可行性分析,需求分析,ER实体图,数据流图,状态转换图,数据字典

软考&#xff1a;软件工程: 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准备的 &#xff08;1&#…

微信小程序——分页组件的创建与使用

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

使用Aspose.Words将word转PDF并且去水印。

&#x1f61c;作 者&#xff1a;是江迪呀✒️本文关键词&#xff1a;Java、工具类、转换、word转pdf、Aspose.Words、后端☀️每日 一言&#xff1a;只要思想不滑坡&#xff0c;办法总比困难多。 前言 在我们日常开发中经常会有将word文档转为PDF的场景&#xff0…

深入理解循环队列----循环数组实现ArrayDeque

我们知道队列这种数据结构的物理实现方式主要还是两种&#xff0c;一种是链队列&#xff08;自定义节点类&#xff09;&#xff0c;另一种则是使用数组实现&#xff0c;两者各有优势。此处我们将要介绍的循环队列其实是队列的一种具体实现&#xff0c;由于一般的数组实现的队列…

IP地址分类

IP地址是TCP/IP协议中非常关键的要素之一&#xff0c;它用于标识网络中的设备和主机。根据使用版本的不同&#xff0c;IP地址分为IPv4和IPv6两种类型。 IPv4&#xff08;Internet Protocol version 4&#xff09;是IP地址的第四个版本&#xff0c;采用32位二进制数来表示IP地址…

JavaWeb之EL表达式和JSTL标签库

文章目录 EL表达式基本介绍EL表达式搜索域数据的顺序EL表达式输出Bean的普通属性&#xff0c;数组属性&#xff0c;List集合属性&#xff0c;Map集合属性EL表达式 - 运算关系运算逻辑运算算数运算empty运算三元运算. 点运算 和 [] 中括号运算符 EL表达式的11个隐含对象EL获取四…

Java中Thread 类的五种基本用法(简介)

目录 一.线程创建 Lambda创建一个线程 基础格式 举例 运行结果 二.线程中断 第一种:设置变量方法 举例 运行结果 第二种:interrupted&#xff08;&#xff09;方法 举例 运行结果 三.线程等待 举例 运行结果 四.线程休眠 举例 五.获取线程实例 举例 运行结果 …

如何用梯度下降法求解数学建模的拟合问题——以logistics增长问题为例

引言 众所周知的是&#xff0c;在大学课程中一般只会教授一种拟合方法(也即参数估计方法)——最小二乘法。这是一种直接求解的方法&#xff0c;非常的有效&#xff0c;不仅是损失最小解&#xff0c;而且是最大似然解。只不过&#xff0c;有一个缺点&#xff0c;它只能解决线性…

Jenkins配置仅合并代码后触发流水线

使用GitLabJenkins集成&#xff0c; 使用Jenkins的Generic WebHook插件&#xff1b;此插件可以作为各个工具间集成使用的通用方式&#xff0c;但是遇到些场景需要写些代码。关于 “合并代码后触发Pipeline”的配置方式&#xff0c; 其实思路简单&#xff0c;实现和让我描述起来…

电脑怎样连接打印机?分享4个简单操作!

为了更方便学习&#xff0c;我买了一个打印机来打印需要用的资料&#xff0c;但是操作了半天还是没连接上&#xff0c;想请问一下有经验的朋友是怎么将打印机与电脑进行连接的呢&#xff1f; 在现代人的工作和生活中&#xff0c;打印机是一个重要的设备。我们可以利用打印机进行…

一文搞懂String、StringBuffer、StringBuilder三者的对比以及扩容机制

String:不可变的字符序列&#xff1b;底层使用char[]存储StringBuffer:可变的字符序列&#xff1b;线程安全的&#xff0c;效率低&#xff1b;底层使用char[]存储StringBuilder:可变的字符序列&#xff1b;jdk5.0新增的&#xff0c;线程不安全的&#xff0c;效率高&#xff1b;…

行为型设计模式09-中介者模式

&#x1f9d1;‍&#x1f4bb;作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 中介者模式 1、中介者模式介绍 中介者模式&#xff08;Mediator Pattern&#xff09;是一种行为设计模…

【MySQL数据库】MySQL 高级SQL 语句一

[TOC](MySQL 高级SQL 语句 一、MySQL 高级SQL 语句1.1select -显示表格中一个或数个字段的所有数据记录1.2distinct不显示重复的数据记录1.3where有条件查询1.4and、or且 或1.5in 显示已知的值的数据记录1.6between 显示两个值范围内的数据记录1.7通配符&#xff0c;通常通配符…