字符串函数,字符函数和内存函数

字符串函数,字符函数和内存函数

一、字符串函数

1.strlen

1.1strlen库函数文档

  • 它的参数类型是constchar*类型的,这是考虑到求字符串长度不会改变原字符串的大小。

  • 返回类型是size_t类型的,这是因为计算的长度不可能为负数。

  • 这个函数返回的是字符串的长度,传入一个地址,计算的是\0字符之前的长度。

在这里插入图片描述

1.2strlen的使用及注意事项

  1. 字符串以\0作为结束标志,strlen函数返回的是\0前面出现的字符个数
  2. 参数指向的字符串必须以\0结束
  3. 注意函数的返回类型是size_t,是无符号的
#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcd\0efg";
	char arr3[] = { 'a','b','c'};
	printf("%d\n", strlen(arr1));
	printf("%d\n", strlen(arr2));
	printf("%d\n", strlen(arr3));
	return 0;
}

在这里插入图片描述
注意函数的返回类型是size_t,是无符号的

int main()
{
	if (strlen("abcd") - strlen("abcdefgh") > 0)
	{
		printf(">\n");
	}
	else
	{
		printf("<\n");
	}
	return 0;
}

在这里插入图片描述

1.3strlen的模拟实现

在这里我们用三种方法来实现:计数器、递归、指针-指针

//模拟实现strlen
//计数器
#include<assert.h>
int my_strlen1(const char* str)
{
	int count = 0;
	assert(str);
	while (*str != '\0')
	{
		count++;
		str++;
	}
	return count;
}
//递归
// 不允许创建临时变量
int my_strlen2(const char* str)
{
	assert(str);
	if (*str != '\0')
	{
		return 1 + my_strlen2(str + 1);
	}
	else
	{
		return 0;
	}
}
//指针-指针
int my_strlen3(const char* str)
{
	assert(str);
	const char* start = str;
	while (*str != '\0')
	{
		str++;
	}
	return str - start;
}
int main()
{
	char arr[] = "hello";
	printf("%d\n", my_strlen1(arr));
	printf("%d\n", my_strlen2(arr));
	printf("%d\n", my_strlen3(arr));
	return 0;
}

2.strcpy

2.1strcpy的库函数文件

  • 这个函数有两个参数chardestination ,const char source源头空间不必被修改,所以加上const修饰。
  • 它的功能是将source处的字符拷贝到destination处
  • 返回类型是char*,返回destination处的地址。

在这里插入图片描述

2.2strcpy的使用及注意事项

  • 源字符串必须以 ‘\0’ 结束。
  • 会将源字符串中的 ‘\0’ 拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变。

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

在这里插入图片描述
会将源字符串中的 ‘\0’ 拷贝到目标空间。
在这里插入图片描述

在这里插入图片描述
目标空间必须足够大,以确保能存放源字符串。
在这里插入图片描述
在这里插入图片描述
目标空间必须可变。
在这里插入图片描述

2.3strcpy的模拟实现


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

3.strcat

3.1strcat的库函数文件

  • 这个函数有两个参数chardestination ,const char source源头空间不必被修改,所以加上const修饰。
  • 它的功能是将source处的字符追加到destination的后面。
  • 返回类型是char*,返回destination处的地址。

3.2strcat的使用以及注意事项

  • 源字符串必须以 ‘\0’ 结束。
  • 目标空间必须有足够的大,能容纳下源字符串的内容。
  • 目标空间必须可修改。
  • 字符串自己给自己追加,会陷入死循环。

3.3strcat的模拟实现

#include<assert.h>
#include<stdio.h>
#include<string.h>
char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* ret = dest;
	//找到\0的位置
	while (*dest != '\0')
	{
		dest++;
	}
	//追加
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[] = "world";
	char arr2[20] = "hello ";
	printf("%s\n", my_strcat(arr2, arr1));
	return 0;
}

3.4strcat自己给自己追加

不能实现自己对自己追加,会陷入死循环。

假设下面的字符串对自己追加的话
在这里插入图片描述

\0会被修改为a,后面的都会覆盖过去,这样我们就永远找不到\0的位置,最终导致分配的空间被使用完,使用了未分配的空间而崩溃
在这里插入图片描述

下面是我们模拟用strcat自己对自己追加后的效果

我们可以看到似乎没有出现这个问题,这个是vs对这个问题进行了优化,但是在其他的ide上可能就会出现这个死循环问题,导致程序出现bug

4.strcmp

4.1strcmp的库函数文件

  • 这个函数的两个参数都是const char*的,因为我们只会查看,并不会修改。
  • 这个函数的作用是比较两个字符串的大小,比较规则是从第一个字符开始依次比较,一个字符一个字符的比较,谁的ASCII值大谁就大,如果相等就比较后一个字符。
  • 这个函数的返回类型是int类型的,第一个字符串大于第二个字符串,则返回大于0的数字 第一个字符串等于第二个字符串,则返回0 第一个字符串小于第二个字符串,则返回小于0的数字,
  • 在vs中,大于返回1,等于返回0,小于返回-1。但在其他编译器上不一定成立,

在这里插入图片描述

4.2strcmp的使用

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abc";
	printf("%d\n", strcmp(arr1, arr2));
	return 0;
}

在这里插入图片描述

4.3 strcmp的模拟实现

#include<assert.h>
#include<stdio.h>
#include<string.h>
int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
		{
			return 0;
		}
		str1++;
		str2++;
	}
	if (*str1 > *str2)
	{
		return 1;
	}
	else
	{
		return -1;
	}
	//在一些编译器上
	//return *str1 - *str2;
}
int main()
{
	char arr1[] = "abcd";
	char arr2[] = "abc";
	printf("%d\n", my_strcmp(arr1, arr2));
	return 0;
}

4.4注意事项(函数不安全的原因)

strcpy,strcat,ctrcmp这些函数都是不安全的函数,因为它们时长度不受限制的函数,如果目标空间不够大,就会出现问题。

所以我们使用时,会在开头使用一个预处理指令

#define _CRT_SECURE_NO_WARNINGS

而为了让这些函数相对安全些,我们有了长度受限制的函数strncpy,strncat,strncmp

5.strncpy

5.1strncpy的库函数文件

  • 这个函数有三个参数chardestination, const charsource, size_t num。
  • char*destination是目标空间的地址。
  • const char*source是源头的地址,这个地址不可修改。
  • size_t num是要拷贝几个字节,类型是size_t,因为拷贝的字节个数不是负数。
  • 这个函数的意思是拷贝source的前num个字节到destination中,返回类型是char*返回的是destination的地址。

在这里插入图片描述

5.2strncpy的使用以及注意事项

  • 拷贝num个字符从源字符串到目标空间。
  • 如第一个图所示,如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
  • 如图2所示,不会将最后的\0拷过来,而是需要几个拷贝几个

在这里插入图片描述

5.3 strncpy的模拟实现

char* my_strncpy(char* dest, const char* src, size_t num)
{
	assert(dest && src);
	char* ret = dest;
	//num为0导致的结束,不用加\0
	while (num && (*dest++ = *src++))
	{
		num--;
	}
	//源头字符串已经拷贝完了,num还没为0导致的结束,我们需要添加\0
	if (num)
	{
		while (--num)
		{
			*dest++ = '\0';
		}
	}
	return ret;
}
int main()
{
	char arr1[] = "hello";
	char arr2[] = "xxxxxxxxxx";
	printf("%s\n", my_strncpy(arr2, arr1, 10) );
	return 0;
}

6.strncat

6.1strncat的库函数文件

  • 这个函数的参数和返回类型和strncpy的是一样的,
  • 不同的是函数的功能是追加source的前num个字符到destination中
  • 追加num个字符后还需在后面补充一个\0

在这里插入图片描述

6.2strncat的使用以及注意事项

  • 对于这个函数,我们需注意的是追加之后后面会补充一个\0,如第一个图所示。
  • 即使num超出了source的范围,也只会补充一个\0,而不是多个,如第二个图所示

在这里插入图片描述

6.3strcat的模拟实现

#include<assert.h>
#include<stdio.h>
#include<string.h>
char* my_strncat(char* dest, const char* src, size_t num)
{
	assert(dest && src);
	char* ret = dest;
	//找到\0的位置
	while (*dest != '\0')
	{
		dest++;
	}
	//追加
	while (num--)
	{
		*dest++ = *src++;
		//追加到\0停止
		if ((*dest++ = *src++) == '\0')
		{
			return ret;
		}
	}
	//num超出了source的范围,也只会补充一个\0
	*dest = '\0';
	return ret;
}
int main()
{
	char arr1[] = "xxx\0xxxxxx";
	char arr2[] = "hello";
	printf("%s\n", my_strncat(arr1, arr2, 10));
	return 0;
}

7.strncmp

7.1strncmp的库函数文件

函数的功能是比较前n个字符的大小,返回一个值,因为不会修改字符串的内容,所以参数加上const修饰

如果str1大,返回大于0的数

如果str1小,返回小于0的数

如果相等,返回0

在这里插入图片描述

7.2strncmp的使用以及注意事项

比较到两个字符不一样或一个字符串结束或者num个字符比完

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcfda";
	int ret = strncmp(arr1, arr2, 3);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcfda";
	int ret = strncmp(arr1, arr2, 4);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1[] = "abcde";
	char arr2[] = "abc";
	int ret = strncmp(arr1, arr2, 4);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述

8.strstr

8.1strstr的库函数文件

  • 这个函数有两个参数,都是const char* 类型的
  • 功能是子str1中查找是否存在str2字符串 返回一个const char*型的地址,
  • 如果存在,则返回在str1中第一次出现str2的地址,
  • 如果不存在,则返回NULL

在这里插入图片描述

8.2strstr的使用以及注意事项

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1 []= "abcdefg";
	char arr2 []= "bcd";
	char* ret = strstr(arr1, arr2);
	if (ret != NULL)
	{
		printf("%s\n", ret);
	}
	else
	{
		printf("找不到\n");
	}
	return 0;
}

在这里插入图片描述

#include<stdio.h>
#include<string.h>
int main()
{
	char arr1 []= "abcdefg";
	char arr2 []= "acd";
	char* ret = strstr(arr1, arr2);
	if (ret != NULL)
	{
		printf("%s\n", ret);
	}
	else
	{
		printf("找不到\n");
	}
	return 0;
}

在这里插入图片描述

8.3 strstr的模拟实现

#include<stdio.h>
#include<string.h>
#include<assert.h>
char* my_strstr(const char* str1, const char* str2)
{
	assert(str1 && str2);
	if (*str2 == '\0')
	{
		return (char*)str1;
	}
	 char* s1 = NULL;
	 char* s2 = NULL;
     char* cp = str1;
	while (*cp)
	{
		s1 = cp;
		s2 = str2;
		while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return cp;
		}
		cp++;
	}
	return NULL;
}
int main()
{
	char arr1[] = "abcdefg";
	char arr2[] = "bcd";
	char* ret = my_strstr(arr1, arr2);
	if (ret != NULL)
	{
		printf("%s\n", ret);
	}
	else
	{
		printf("找不到\n");
	}
	return 0;
}

9.strtok

9.1strtok的库函数文件

  • 这个函数有两个参数,str是目标字符串,需要被修改
  • delimiters是一个字符串,用作分隔符的集合。
  • 函数的功能是在str字符串中找到delimiters这个集合中的任意一个字符,将最先出现的这个字符修改为\0,并且返回这个分割好的字符串的地址,
  • 并且内部有一个静态变量记录之前切割好的地址。
  • 这个str可以是null,如果传的是空指针,函数将在同一个字符串中被保存的位置开始,查找下一个标记。

​​​​在这里插入图片描述

9.2strtok的使用以及注意事项

  • sep参数是个字符串,定义了用作分隔符的字符集合 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
  • strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
    并且可修改。)
  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。
  • strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。
  • 如果字符串中不存在更多的标记,则返回 NULL 指针。
//strtok
int main()
{
	char arr[] = "joshua@say.yes/net";
	char buf[30] = { 0 };
	strcpy(buf, arr);//strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)

	const char* p = "@./";//sep参数是个字符串,定义了用作分隔符的字符集合
	char* str = strtok(buf, p);
	printf("%s\n", str);//joshua
	//strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
	//中的位置。
	str = strtok(NULL, p);//say
	printf("%s\n", str);
	//strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标
	//记
	str = strtok(NULL, p);//yes
	printf("%s\n", str);
	str = strtok(NULL, p);//net
	printf("%s\n", str);
	//如果字符串中不存在更多的标记,则返回 NULL 指针。


	return 0;
}

这样重复利用一个函数可能有点繁琐,我们可以用一个for循环来实现上面的多次调用

int main()
{
	char arr[] = "joshua@say.yes/net";
	char buf[30] = { 0 };
	strcpy(buf, arr);
	const char* p = "@./";
	char* str = NULL;
   for (str = strtok(buf, p); str != NULL; str = strtok(NULL, p))
	   {
		   printf("%s\n",str);
	   }
}

在这里插入图片描述

10.strerror

10.1strerror的库函数文件

  • 这个函数会接受一个整形的数字,这个数字是一个错误码,然后返回一个字符串的地址,我们通过打印这个字符串来显示我们的错误信息。
  • c语言运行时如果发生错误,就会将错误码放在errno这个变量中,这个函数可以将错误码翻译成字符串。

在这里插入图片描述

10.2strerror的使用以及注意事项

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>
int main()
{
	//打开文件
	//	//打开文件的时候,如果文件的打开方式是"r"
	//	//文件存在则打开成功,文件不存在打开失败
	//	//打开文件失败的话,会返回NULL
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		printf("%s\n", strerror(errno));
		return 0;
	}
	//读写文件
	//关闭文件
	fclose(pf);
	return 0;
}

在这里插入图片描述

10.3 perror的使用

  • perror和strerror很相似,但是perror可以自己将错误信息打印出来。
  • perrror=printf+strerror

在这里插入图片描述

#include<errno.h>
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 0;
	}
	fclose(pf);
	return 0;
}

在这里插入图片描述

二、字符函数

1.字符分类函数

在这里插入图片描述

2.字符转换函数

这两个函数不会改变字符本身的值,而是返回转换后的值

在这里插入图片描述

3.利用字符函数,将字符串中的大写字母改为小写打印出来

#include<stdio.h>
#include<string.h>
#include<ctype.h>
int main()
{
	char arr[] = "Our Dawn Is Hotter Than Day";
	int i = 0;
	int len = strlen(arr);
	for (i = 0; i < len; i++)
	{
		if (isupper(arr[i]))
		{
			arr[i] = tolower(arr[i]);
		}
		printf("%c", arr[i]);
	}
	return 0;
}

在这里插入图片描述

三、内存函数

我们已经有了字符串的操作函数,但是这些函数只能用于字符串,但我们有时需对整形数组等进行类似字符串的操作,这时内存函数就起作用了。

1.memcpy

1.1 memcpy的库函数文档
  • 这个函数有三个参数,第一个参数voiddestination ,是目标空间的起始地址,第二个参数const voiddestination ,是源头空间的起始地址,size_t num是拷贝num个字节。
  • 这个函数的功能是将source的前num个字节拷贝到destination中。
  • 返回一个void类型的指针,指针指向destiantion。
  • 由于我们不知道我们要用这个函数比较什么类型的数据,所以指针的类型都是void*。
  • 在这里插入图片描述

1.2 memcpy的使用及注意事项

  1. memcpy拷贝是以字节为单位的。
  2. 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
  3. 这个函数在遇到 '\0’的时候并不会停下来。
  4. 如果source和destination有任何的重叠,复制的结果都是未定义的。
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[8] = { 0 };
	memcpy(arr2, arr1, 20);
	return 0;
}

在这里插入图片描述
在这里我们可以清晰地看到拷贝是是以字节为单位的,而拷贝17个字节就可以将前5个数拷贝过去,是小端存储导致的。
在这里插入图片描述

1.3模拟memcpy函数

void* my_memcpy(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	assert(dest && src);
	while (num--)
	{
		*(char*)dest = *(char*)src;//这里将void*类型的指针全部强转为char*类型,是因为拷贝是是以字节为单位
		dest = (char*)dest+1;//而char类型刚好只占一个字节,解引用时刚好访问一个字节,一次拷贝一个字节
		src = (char*)src + 1;//+1时刚好跳过一个字节
	}
	return ret;
}

int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[8] = { 0 };
	my_memcpy(arr2, arr1, 20);
	return 0;
}

1.4memcpy的缺陷

想要将1 2 3 4 5拷贝到3 4 5 6 7 的地方,想要达到的效果是1 2 1 2 3 4 5 8 9 10

在这里插入图片描述
但是实际打印出的效果是1 2 1 2 1 2 1 8 9 10,这是因为拷贝1 2 到3 4处时3 4 的数据已经被修改为1 2了,这是再拷贝3 4 处的数据到5 6内存处,也就是将1 2拷贝到5 6处
在这里插入图片描述
为了避免这样的场景,我们可以从从后面开始拷贝,
在这里插入图片描述

可是如果想要将3 4 5 6 7 拷贝到 1 2 3 4 5处,从后面开始拷贝也会失败
在这里插入图片描述
在这里插入图片描述
这时又需要从前向后面拷贝
在这里插入图片描述

这个似乎需要分情况,经过我们的分析,我们可以得出以下结论
在这里插入图片描述
当然对于以上几种情况,我们可以进行一次简化。
在这里插入图片描述
如果是这样拷贝,那么我们的目标就实现了。
而这个功能其实在memmove函数中就已经实现了

2.memmove

2.1 memmove的库函数文档

这个函数的参数和功能与memcpy一样,但是它可以实现自己拷贝自己

2.2 memmove的使用及注意事项

  • 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
  • 如果源空间和目标空间出现重叠,就得使用memmove函数处理。
  • 在vs上memcpy和memmove没有任何区别。但是其他编译器上不一定

2.3模拟memmove函数

void* my_memmove(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	assert(dest && src);
	if (src > dest)
	{   //从前向后拷贝
		while (num--)
		{
			*(char*)dest = *(char*)src;//这里将void*类型的指针全部强转为char*类型,是因为拷贝是是以字节为单位
			dest = (char*)dest + 1;//而char类型刚好只占一个字节,解引用时刚好访问一个字节,一次拷贝一个字节
			src = (char*)src + 1;//+1时刚好跳过一个字节
		}
	}
	else//从后向前拷贝
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
			//这里从后向前拷贝需要找到被拷贝的最后一个字符,这时首字符的地址加上num就可以得到最后一个字符的地址了
		}
	}
	return ret;
}

int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
	int arr2[8] = { 0 };
	my_memmove(arr2, arr1, 20);
	return 0;
}

3.memcmp

3.1 memcmp的库函数文档

这个函数的功能是比较ptr1和ptr2两个指针指向空间的前num个字节
ptr1>ptr2 返回大于0的数
ptr1=ptr2 返回0
ptr1<ptr2 返回小于0的数

3.2 memcmp的使用及注意事项

int main()
{
	int arr1[] = { 1,2,3 };
	int arr2[] = { 1,2,5 };
	int ret = memcmp(arr1, arr2, 9);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述

#include<stdio.h>
#include<string.h>
int main()
{
	int arr1[] = { 1,2,3 };
	int arr2[] = { 1,2,5 };
	int ret = memcmp(arr1, arr2, 8);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述

#include<stdio.h>
#include<string.h>
int main()
{
	int arr1[] = { 1,2,7 };
	int arr2[] = { 1,2,5 };
	int ret = memcmp(arr1, arr2, 12);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述

4.memset

4.1 memset的库函数文档

这是一个内存设置函数

将ptr空间的前num个字节,以字节为单位 设置为value

在这里插入图片描述

4.2 memset的使用及注意事项

#include<stdio.h>
#include<string.h>
int main()
{
	char arr[] = "hello world";
	memset(arr, 'm', 5);
	printf("%s\n", arr);
	memset(arr + 6, 'n', 4);
	printf("%s\n", arr);
	return 0;
}

在这里插入图片描述
注意·:单位是字节

#include<stdio.h>
#include<string.h>
int main()
{
	int arr[10] = { 0 };
	memset(arr, 1, 40);//这样不可以将数组的每个元素改为0
	return 0;
}

在这里插入图片描述

总结

本节讲解了常用的字符串函数、字符函数和内存函数的使用,以及一些重要函数的模拟实现。
希望对你有所帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值