栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言: 详解常用的字符串函数(使用+模拟实现)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言: 详解常用的字符串函数(使用+模拟实现)

目录

前言:

函数介绍:

1.1 strlen

1.2 strcpy

1.3 strcat 

1.4 strcmp

1.5 strstr

1.6 strtok

1.7 strerror

1.8 perror

2. 字符分类函数​

2.1 memcpy

2.2 memmove​​​​​​​


前言:

C语言中,字符串函数和字符函数的使用是很频繁的,如果我们能够熟练使用,能够帮助我们解决很多的字符问题。

函数介绍:

1.1 strlen

格式:strlen( const char* str )

字符串以 ‘’ 作为结束标志,返回的是 ‘’ 前面出现的字符个数。(不包括 ‘’)

参数指向的字符串必须要以 ‘’ 结尾,不然结果就是未知的了。

函数的返回值是 size_t 类型,是无符号的。

使用:
#include 
#include 
int main()
{
	char arr1[] = "abcdefg";
	printf("%dn", strlen(arr1));
	return 0;
}

 实现的三种方式:

廒1、局部变量统计

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

每走过一个不是 ‘’ 的字符,局部变量就+1,最后返回这个值。

廒2、递归调用

size_t my_strlen(const char* str)
{
	if (*str != '')
	{
		return 1 + my_strlen(str + 1);
	}
	else
	{
		return 0;
	}
}

如果不是 ‘’ ,那么返回1+my_strlen。否则,就返回0。

廒3、指针法

size_t my_strlen(const char* str)
{
	const char* temp = str;
	while (*str != '')
	{
		str++;
	}
	return (str - temp);
}

用一个局部变量记录指针初始位置,如果不是遇到 ‘’ 指针就一直往后走,最后返回指针-局部变量。(两个指针相减得到之间相差元素个数)

缺点:

 这段代码的结果就是,hehe,因为返回值是无符号整形,而strlen(p1)-strlen(p2)得到的值是负数,自动转为非负的数,最终得到一个正整数。所以还是会打印hehe。所以我们应该避免相减为负数的情况,直接改成   strlen(p2)  >   strlen(p2)  就好了。

1.2 strcpy

格式:strcpy( char* dest,const char* src )

源字符串必须以 ‘’ 结束。

会将源字符串的 ‘’ 拷贝到目标空间。

目标空间必须足够大,确保能够放得下源字符串。

目标空间必须可修改。 

如果是 char* p=“abcdef”;这样是不可修改的,因为指针指向的是常量字符串

使用:

#include 
#include 
int main()
{
	char arr1[20] = "ABCDEFG";
	char arr2[30];
	strcpy(arr2, arr1);
	printf(arr2);
	return 0;
}

实现:
char* my_strcpy(char* dest, const char* src)
{
	assert(dest);
	assert(src);
	char* temp = dest;
	while (*dest++ = *src++)
	{
		;
	}
	return temp;
}

断言assert判断传入指针非空,创建一个临时变量记录木目的空间首地址,然后把src指向的空间中的内容赋值给dest,然后++。使用断言需要包含头文件   

注意:由于最后遇到 ‘’ 了,它的ASCII码值是0,所以会退出循环。

1.3 strcat 

格式:strcat( char* dest,const char* src )

源字符串必须以 ‘’ 结束。

目标空间足够大,能够容纳下源字符串的内容。

目标空间必须可以修改。

字符串是不可以给自己追加的,不然陷入死循环。

使用:
#include 
#include 
int main()
{
	char arr1[20] = "hello ";
	char arr2[20] = "wrold";
	strcat(arr1, arr2);
	printf("%s", arr1);
	return 0;
}

实现:
char* my_strcat(char* dest, const char* src)
{
	assert(dest);
	assert(src);
	char* temp = dest;
	while (*dest)
	{
		dest++;
	}
	while (*dest++ = *src++)
	{
		;
	}
	return temp;
}

由于不需要对src进行修改,所以加上const修饰,增加代码健壮性。第一个while循环能让指针找到结尾 ‘’ 处,然后第二个循环把每一个src处的字符赋值给dest。最后返回dest的首元素地址。

1.4 strcmp

格式:strcmp( const char* str1,const char* str2 )

第一个字符串大于第二个字符串,则返回大于0的数字。

第一个字符串等于第二个字符串,则返回0。

第一个字符串小于第二个字符串,则返回小于0的数字。

判断两个字符是靠他们的ASCII码值来比较的,所以小写字母大于大写字母。

使用:
#include 
#include 
int main()
{
	char arr1[20] = "hello ";
	char arr2[20] = "Hello";
	int ret = strcmp(arr1,arr2);
	printf("%dn", ret);
	return 0;
}
实现:
int my_strcmp(const char* s1, const char* s2)
{
	assert(s1);
	assert(s2);
	while (*s1 == *s2)
	{
		//让s1和s2比完了就停下来
		if (*s1 == '')
		{
			return 0;
		}
		s1++;
		s2++;
	}
	//直接返回他们差值
	return *s1 - *s2;
}

第一个while循环让两个指针指针指向的字符串逐个字符比较。如果其中*s1= ‘’,由于进入循环是要两个比较的字符相等,说明两个都是结尾了,两个字符串比较到结尾说明他们相等,所以return 0。如果退出循环了,就返回 *s1 - *s2,如果小于返回的值就是负数,如果大于返回的值就是正数。

1.5 strstr

格式:strstr( const char* str1,const char* str2 )

用于查找子集。

第二个字符串如果是第一个字符串的子集,那么返回第二个字符串在第一个字符串中的首元素地址。否则,返回空指针。

使用:
#include 
#include 
int main()
{
	char arr1[20] = "helio world";
	char arr2[20] = "world";
	char* p = strstr(arr1, arr2);
	printf("%sn", p);
	return 0;
}
实现:
//s2在s1中查找,cp代表每次要查找的起始位置,避免了部分符合回不去的情况.
char* my_strstr(const char* str1, const char* str2)
{
	//断言
	assert(str1);
	assert(str2);
	const char* s1 = str1;
	const char* s2 = str2;
	const char* cp = str1;
	//如果传过来一个空集,一定是子集.
	if (*str2 == '')
	{
		return (char*)str1;
	}
	while (*cp)
	{
		//每次回到有效起始位置
		s1 = cp;
		s2 = str2;
		//两个都不是末尾,并且相等的时候就都++
		while (*s1 && *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		//如果s2到底了,说明查完了
		if (*s2 == '')
		{
			return (char*)cp;
		}
		cp++;
	}
	//如果都不符合,说明没有查到,返回空指针
	return NULL;
}

创建指针s1、s2、cp,其中s1和s2用来比较是否字符相等,cp来记录从这一个字符开始的对比是否符合,因为其中可能出现部分符合的情况。

先if判断 *str2 是否为空,如果为空,直接返回str1的首元素地址。

s1每次都回到cp的位置,s2回到第二个字符串首元素位置。然后进入比较,如果两个都不为空并且相等的话,那么s1++,s2++。如果*s2 == ‘’ ,那么说明s2比到头了,说明是子集,所以返回cp指向的位置。否则,就是不相等的情况,那么cp++,从下一个元素开始比较。最后如果都找不到,说明不是子集,返回NULL。

1.6 strtok  格式:char * strtok ( char* str, const char* sep );  第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。 strtok函数找到str中的下一个标记,并将其用 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。) strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。 strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。 如果字符串中不存在更多的标记,则返回 NULL 指针。 光看定义比较难理解,通俗来说,这是一个查找函数,如果在第一个字符串找都第二个字符串中含有的字符,那么就会将其改成 ‘’ ,并返回首元素地址。 第二使用的时候,第一个参数传 NULL就行了,函数会从上次找到元素的地址后面一个地方开始查找。如果没找到,返回空指针。 使用:
#include 
#include 
int main()
{
	char arr1[] = "abc.def@ghijk";
	char arr2[] = "@.";
	char temp[30] = { 0 };
	strcpy(temp, arr1);
	char* p = NULL;
	for (p = strtok(temp, arr2); p != NULL; p = strtok(NULL, arr2))
	{
		printf("%sn", p);
	}
	return 0;
}

结果:abc

           def

           hijk

这个函数由于会修改第一个参数,所以一般是在临时变量中进行。for循环进入的时候调用一次,传参拷贝过后的temp和arr2,用一个指针p来接收。判断条件为返回的指针 p!=NULL,最后每次执行完都会继续调用,只不过第一个参数改为NULL。

这样的做法比一次一次调用要效率高。

  

1.7 strerror

格式: char *strerror( int errnum )

返回错误码所对应的信息

#include     必须包含的头文件

缺点:

需要手动打印错误信息

1.8 perror

格式:perror( const char *string )

相比于strerror,这个更为有效,能够自动打印错误信息。

使用:
#include  
int main()
{
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("打印错误信息");
	}
	else
	{
		printf("打印成功n");
		fclose(pf);
		pf = NULL;
	}
	
	return 0;
}

2. 字符分类函数

使用需要包含头文件    

用法: 

小写转换大写

#include 
#include 
int main()
{
	char ch = 'a';
	if (isalpha(ch))
	{
		printf("%cn", toupper(ch));

	}
	return 0;
}

打印结果:A

2.1 memcpy

格式:memcpy ( void * destination, const void * source, size_t  num )

函数memcpy从source的位置开始向后赋值num个字节的数据到destination的内存位置。

这个函数在遇到 ‘’ 的时候不会停下来。

如果 source 和 destination 有任何的重叠,复制的结果都是未定义的。

size_t  num  是总字节大小,如果是用其他类型的算,就要折算成字节大小。

使用:

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

由于计算的是 int 类型的,所以大小要乘上整形的大小4。

结果:0 0 0 0 0 0 0 0 0 0

实现:
void* my_memcpy(void* dest, void* src, size_t count)
{
	assert(dest);
	assert(src);
	void* ret = dest;
	while (count--)
	{
		*(char*)dest = *(char*)src;
		++(char*)dest;
		++(char*)src;
	}
	return ret;
}

先断言判断非空指针。由于dest和src都是无类型的指针,解引用需要先强制类型转换为char*类型的,因为这个函数要做到所有类型都是用,只能采取最小的计量值1个字节,所以就是char型的。循环中每次把一个字节的值赋值,然后++,这样就把每一个字节的内容都拷贝过去了。

2.2 memmove

格式:memmove( void *dest, const void *src, size_t count )

 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。 如果源空间和目标空间出现重叠,就得使用memmove函数处理。 使用:
#include 
int main()
{
	int arr[20] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr, arr+1, 4);
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

结果: 2 2 3 4 5 6 7 8 9 10

实现:
void* my_memmove(void* dest, void* src, size_t count)
{
	assert(dest);
	assert(src);
	if (dest < src)
	{
		//从前向后拷贝
		while (count--)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		//从后向前拷贝
		while (count--)
		{
			*((char*)dest + count) = *((char*)src + count);
		}
	}
}

这其中涉及到了目标空间在前面还是后面的问题:  如果在前面并且和源头空间有重叠,那么就有可能互相影响导致结果不正确。例如abcdefg,把bcd移动到abc的位置,如果从向前移动,那么c移动到b这里的时候b就改变了,本来要把b移到a就变成了c移动到a。

所以我们的思路应该是:如果目标空间在前面,那么从前往后移动。其他的都从后往前移动。(从前往后指的是从头部移还是先从尾部移)

今天的文章就到此结束啦!感谢各位的观看!

五一快乐!

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/849267.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号