一篇博客学会系列(1) —— C语言中所有字符串函数以及内存函数的使用和注意事项

目录

1、求字符串长度函数

1.1、strlen

2、字符串拷贝(cpy)、拼接(cat)、比较(cmp)函数

2.1、长度不受限制的字符串函数

2.1.1、strcpy

2.1.2、strcat

2.1.3、strcmp

2.2、长度受限制的字符串函数

2.2.1、strncpy

2.2.2、strncat

2.2.3、strncmp

3、字符串查找函数

3.1、strstr

3.2、strtok

4、错误信息报告函数

4.1、strerror

4.2、perror

5、字符函数

5.1、字符分类函数

5.2、字符转换函数

5.2.1、tolower

5.2.2、toupper

6、内存操作函数

6.1、memcpy

6.2、memmove

6.3、memset

6.4、memcmp

1、求字符串长度函数

1.1、strlen

  • strlen用于求字符串长度。
  • 包含头文件<string.h>。
  • 字符串已经 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包含 '\0' )。
  • 参数指向的字符串必须要以 '\0' 结束

注意:

1、函数的返回值为size_t,是无符号( 易错 )

2、因为strlen返回的是 '\0' 前面的字符个数,如果字符串中间本身就一个'\0',那么返回的值就会返回字符串中的'\0'之前的字符个数。

例如:"abc\0def" 这个字符串,使用strlen函数会返回3。

【使用方式】 

int main()
{
	char arr[] = "Hello hacynn";
	int ret = strlen(arr);
	printf("%d\n", ret);
	return 0;
}

【运行结果】

【易错提醒】

请问ret的值是多少?
int ret = strlen("abc") - strlen("abcdef");

答案是3,因为函数的返回值为size_t,是无符号的整型。

【模拟实现strlen】

int my_strlen(char* arr)
{
	int count = 0;
	while (*arr != '\0')
	{
		count++;
		arr++;
	}
	return count;
}

int main()
{
	char arr[] = "Hello hacynn";
	int ret = my_strlen(arr);
	printf("%d\n", ret);
	return 0;
}

2、字符串拷贝(cpy)、拼接(cat)、比较(cmp)函数

2.1、长度不受限制的字符串函数

2.1.1、strcpy

  • strcpy用于拷贝字符串,将字符串2拷贝到字符串1当中。
  • 包含头文件<string.h>。
  • 源字符串必须以 '\0' 结束。
  • 会将源字符串中的 '\0' 拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变。

【使用方法】 

int main()
{
	char arr1[] = "Hello hacynn";
	char arr2[20] = { 0 };
	strcpy(arr2,arr1);
	printf("%s\n", arr2);
	return 0;
}

【运行结果】 

【模拟实现strcpy】

char* my_strcpy(char* dest,const char* src)
{
	char* ret = dest;
	while (*dest = *src)
	{
		dest++;
		src++;
	}
	return ret;
}

int main()
{
	char arr1[] = "Hello hacynn";
	char arr2[20] = { 0 };
	my_strcpy(arr2,arr1);
	printf("%s\n", arr2);
	return 0;
}

2.1.2、strcat

  •  strcat用于拼接两个字符串,将字符串2拼接到字符串1末尾。
  • 包含头文件<string.h>。
  • 源字符串必须以 '\0' 结束(保证找得到目标空间的末尾),在拷贝时会把源字符串的 '\0 '也拷贝过去。
  • 目标空间必须有足够的大,能容纳下源字符串的内容,并且还可以被修改。

注意:

        不能字符串自己追加自己,因为当自己追加自己的时候,追加的过程中会将目标字符串的 '\0' 覆盖掉,而有因为此时目标字符串就是源字符串,就会导致源字符没有 '\0' ,将会一直拼接下去导致死循环。

        虽然有些环境中该函数可以完成自己拼接自己,但是C语言的标准中并未规定strcat可以自己拼接自己,所以这个函数最好不要使用在自己拼接自己的情况下。如果真有自己追加自己的场景,建议使用strncat函数,这个函数将在下文进行讲解。

【使用方式】

int main()
{
	char arr1[20] = "Hello ";
	char arr2[] = "hacynn" ;
	strcat(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

 【运行结果】

【模拟实现strcat】

char* my_strcat(char* dest, const char* src)
{
	char* ret = dest;
    //找到目标空间的末尾
	while (*dest != '\0')
	{
		dest++;
	}
    //数据追加
	while (*dest = *src)
	{
		dest++;
		src++;
	}
	return ret;
}

int main()
{
	char arr1[20] = "Hello ";
	char arr2[] = "hacynn" ;
	my_strcat(arr1, arr2);
	printf("%s\n", arr1);
	return 0;
}

2.1.3、strcmp

  • strcmp用于比较两个字符串。
  • 包含头文件<string.h>。
  • 误区:该函数不是比较字符串长度的,而是比较对应位置上字符的大小(ASCII)。
  • 标准规定:
    第一个字符串大于第二个字符串,则返回大于0的数字
    第一个字符串等于第二个字符串,则返回0
    第一个字符串小于第二个字符串,则返回小于0的数字

【使用方式】

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abz";
	if (strcmp(arr1, arr2) > 0)
		printf(">\n");
	else if (strcmp(arr1,arr2) < 0)
		printf("<\n");	
    else
        printf("=\n");
	return 0;
}

【运行结果】 

【模拟实现strcmp】

int my_strcmp(const char* str1, const char* str2)
{
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	if (*str1 > *str2)
		return 1;
	else
		return -1;
}

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abz";
	if (my_strcmp(arr1, arr2) > 0)
		printf(">\n");
	else
		printf("<=\n");	
	return 0;
}

2.2、长度受限制的字符串函数

  • 就是可以限制操作个数的字符串函数。
  • 包含头文件<string.h>。

2.2.1、strncpy

  • 区别仅与strcpy差一个参数,记录要操作的个数。 
  • 拷贝num个字符从源字符串到目标空间。
  • 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
  • 因为拷贝个数由用户自己决定,因此\0没有被拷贝过来的可能性也是有的。

【使用方式】 

int main()
{
	char arr1[] = "Hello hacynn";
	char arr2[20] = { 0 };
	strncpy(arr2, arr1, 5); //拷贝前五个字符 ,此时拷贝\0后arr2中并不会有\0
	printf("%s\n", arr2);
	return 0;
}

【运行结果】

【特殊情况】

如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。如下:

int main()
{
	char arr1[] = "Hello";
	char arr2[20] = "xxxxxxxxxxxxxxxxx";
	strncpy(arr2, arr1, 10);   //此时10大于arr1的元素个数,就会在后添加0直至够10个
	printf("%s\n", arr2);
	return 0;
}

2.2.2、strncat

  • 区别也仅与strcat差一个参数,记录要操作的个数。
  • 使用strncat追加,当结束追加时,就算没到\0,也会在末尾追加一个\0。
  • 如果源字符串的长度小于num,则追加完源字符串之后,会自动停止追加。注意此处与strncpy的区别。
  • 包含头文件<string.h>。

【使用方式】

int main()
{
	char arr1[20] = "Hello ";
	char arr2[] = "hacynn" ;
	strncat(arr1, arr2, 3);
	printf("%s\n", arr1);
	return 0;
}

【运行结果】 

2.2.3、strncmp

  • 区别也仅与strcmp差一个参数,记录要操作的个数。
  • 包含头文件<string.h>。

【使用方式】

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcz";
	if (strncmp(arr1, arr2, 3) > 0)   //只比较前三个字符
		printf(">\n");
	else if (strncmp(arr1, arr2, 3) == 0)
		printf("=\n");
	else
		printf("<\n");
	return 0;
}

【运行结果】

 

3、字符串查找函数

3.1、strstr

  • 查找一个字符串中是否存在与另一个字符串当中,即找子串
  • 返回一个指向str1中第一个出现str2的指针,如果str2不是str1的一部分,则返回一个空指针NULL。
  • 包含头文件<string.h>。

【使用方式】

可以看到,即使是有两个字串 ,也只会返回第一次出现的地址。

int main()
{
	char arr1[] = "abcdefghidef";   //def出现了两次
	char arr2[] = "def";
	char* ret = strstr(arr1, arr2);
	if (ret == NULL)
		printf("找不到\n");
	else
		printf("%s\n", ret);
	return 0;
}

【运行结果】 

【模拟实现strstr】

const char* my_strstr(const char* str1, const char* str2)
{
    if (*str2 == '\0')
		return str1;
	char* pc = str1;  //pc用于记录开始匹配的位置
	while (*pc)
	{
		char* s1 = pc;   //遍历str1指向的字符串
		char* s2 = str2; //遍历str2指向的字符串
		while (*s1 && *s2 && (*s1 == *s2))
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
			return pc;
		pc++;
	}
	return NULL;
}

int main()
{
	char arr1[] = "abcdefghidef";
	char arr2[] = "def";
	char* ret = my_strstr(arr1, arr2);
	if (ret == NULL)
		printf("找不到\n");
	else
		printf("%s\n", ret);
	return 0;
}

【图解】 

3.2、strtok

 

比较奇葩的一个函数
char * strtok ( char * str, const char * delimiters );
  • 切割字符串函数,例如[email protected],当切割标记是@和 . 时,通过三次合理的使用可以切割出三个字符串:hacynn  nash  com
  • 包含头文件<string.h>。
  • delimiters参数是个字符串,定义了用作分隔符的字符集合
  • 第一个参数指定一个字符串,它包含了0个或者多个由delimiters字符串中一个或者多个分隔符分割的标记。
  • strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  • strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  • 如果字符串中不存在更多的标记,则返回 NULL 指针

【使用方式】

int main()
{
	char arr[] = "[email protected]";
	char buf[200] = { 0 }; //因为strtok会改变被操作字符串,
	                        //所以拷贝一个临时变量来操作
	strcpy(buf, arr);
	char* p = "@.";
	char* s = strtok(buf, p); //参数不为NULL,找到第一个标记
	printf("%s\n", s);

	s = strtok(NULL, p); //参数为NULL,找到下一个标记
	printf("%s\n", s);

	s = strtok(NULL, p); 参数为NULL,找到下一个标记
	printf("%s\n", s);

	return 0;

【运行结果】

【使用方式优化 】

在实际开发中,我们不一定知道这个字符串是怎样的,这个字符串需要切割几次的,因此手动设置切割几次将代码写死的方式是不可取,而应该使用以下的方式进行自动切割。

int main()
{
	char arr[] = "[email protected]@abcd";
	char buf[200] = { 0 };
	strcpy(buf, arr);
	char* p = "@.";

	char* s = NULL;
	for (s = strtok(buf, p); s != NULL; s = strtok(NULL, p))
	{
		printf("%s\n", s);
	}
	

	return 0;
}

这里巧妙的运用了for函数的初始化部分只执行一次的特点,而strtok也只需要第一次传地址,其他时候都只需要传NULL就行。

【优化后的运行结果】 

4、错误信息报告函数

4.1、strerror

  • strerror函数是将错误码翻译成错误信息,返回错误信息的字符串起始地址。
  • 包含头文件<string.h>。
  • C语言中使用库函数的时候,如果发生错误,就会将错误码放在errno的变量中,errno是一个 全局变量,可以直接使用。

 【错误码举例】

int main()
{
	int i = 0;
	for ( i = 0; i < 10; i++)
	{
		printf("%d: %s\n", i, strerror(i));
	}
	return 0;
}

每一个错误码都对应一个错误信息 

【使用方式】

以打开文件为例子,fopen以读的形式打开文件,当文件存在时打开成功,文件不存在时打开失败,并返回空指针。可以利用这个来设置一个打开失败时的错误信息告知。

int main()
{
	FILE* pf = fopen("add.txt", "r");  //当前文件路径中并没有add.txt文件,打开失败
	if (pf == NULL)
	{
		printf("打开文件失败,原因是:%s\n", strerror(errno));
		return 1;
	}
	else
	{
		printf("打开文件成功\n");
	}
	return 0;
}

【运行结果】

4.2、perror

  • perror也是用于翻译错误信息 ,但与strerror不同的是,perror会直接打印错误码所对应的错误信息。而perror中传递的字符串参数就是自定义显示信息的部分,打印的结果就是 自定义显示信息:错误信息
  • 包含头文件<stdlib.h>
  • 可以简单理解为:perror = printf + strerror 即翻译又打印

【使用方式】

int main()
{
	FILE* pf = fopen("add.txt", "r");
	if (pf == NULL)
	{
		perror("打开文件失败");   //注意:此处是perror,不是printf。
		return 1;
	}
	else
	{
		printf("打开文件成功\n");
	}
	return 0;
}

【运行结果】 

5、字符函数

5.1、字符分类函数

字符分类函数使用非常简单,由于篇幅受限,在这里不就一一列举了 ,只需要把下面的图看懂就行。

5.2、字符转换函数

5.2.1、tolower

这个函数听名字就知道是用于将大写字母转换成小写字母,而这类函数唯一需要注意的就是函数有返回值,返回类型为int,因此在使用的时候最好使用一个int ret接收返回值。

int main()
{
	int ret = tolower('A');
	printf("%c\n", ret);
}

5.2.2、toupper

小写字母转大写字母,其他注意点与tolower一致。

6、内存操作函数

上文讲到的字符串函数只适用于字符串,但是内存中的数据不仅仅只有字符,这就导致这些函数有很大的局限性。因此需要有一个能够对所有类型的数据都适用的函数,这就是内存操作函数的出现的原因。下面我们来学习一下内存操作函数。

6.1、memcpy

  • 函数memcpy从source的位置开始向后拷贝num个字节的数据到destination的内存位置。
  • 包含头文件<string.h>
  • 这个函数在遇到 '\0' 的时候并不会停下来。
  • 如果source和destination有任何的重叠,复制的结果都是未定义的。
  • 因为C语言标准中并未规定memcpy能适用于重叠内存的拷贝,因此不重叠内存的拷贝才使用memcpy,而重叠内存的拷贝使用接下来讲解的memmove函数。

【使用方式】 

使用memcpy拷贝整型数据。

int main()
{
	int arr1[10] = { 0 };
	int arr2[] = { 1,2,3,4,5 };
	memcpy(arr1, arr2, sizeof(int) * 5);
	int i = 0;
	for ( i = 0; i < 5; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

【运行结果】 

 

【模拟实现memcpy】

void* my_memcpy(void* dest, const void* src, size_t sz)
{
	void* ret = dest;
	while (sz)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
		sz--;
	}
	return ret;
}

int main()
{
	int arr1[10] = { 0 };
	int arr2[] = { 1,2,3,4,5 };
	my_memcpy(arr1, arr2, sizeof(int) * 5);
	int i = 0;
	for ( i = 0; i < 5; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

6.2、memmove

  •  memmove的参数和功能与memcpy完全一致。
  • 包含头文件<string.h>
  • 唯一有区别的就是memmove函数处理的源内存块和目标内存块是可以重叠的。
  • 因此当出现重叠内存的拷贝时,就使用memmove函数处理。

 【模拟实现memmove】

void* my_memmove(void* dest, const void* src, size_t sz)
{
	void* ret = dest;
	if (dest < src)
	{
		while (sz)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
			sz--;
		}
	}
	else
	{
		while (sz--)
		{
			*((char*)dest + sz) = *((char*)dest + sz);
		}
	}
	return ret;
}

int main()
{
	int arr1[] = { 1,2,3,4,5 ,6,7,8,9,10 };
	my_memmove(arr1, arr1+2, sizeof(int) * 5);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr1[i]);
	}
	return 0;
}

6.3、memset

  • 将ptr所指向空间的前num个字节设置为指定值value。
  • 包含头文件<string.h>

【使用方式】 

int main()
{
	char arr[] = "hello world";
	memset(arr + 6, 'x', 3);
	printf("%s\n", arr);
	return 0;
}

 【运行结果】

6.4、memcmp

  • 比较ptr1和ptr2前num个字节的内容。
  • 包含头文件<string.h>
  • 标准规定:
    ptr1大于ptr2,则返回大于0的数字。
    ptr1等于ptr2,则返回0。
    ptr1小于ptr2,则返回小于0的数字。

 【使用方式】

int main()
{
	int arr1[] = { 1,2,3,4,5,6,7 };
	int arr2[] = { 1,2,3,7 };
	int ret = memcmp(arr1, arr2, sizeof(int) * 3);
	printf("%d\n", ret);
}

【运行结果】 


如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

如果觉得作者写的不错,求给博主一个大大的点赞支持一下,你们的支持是我更新的最大动力!

猜你喜欢

转载自blog.csdn.net/zzzzzhxxx/article/details/133243791