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张图片

前言:

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

函数介绍:

1.1 strlen

格式strlen( const char* str )

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

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

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

使用:

#include 
#include 
int main()
{
	char arr1[] = "abcdefg";
	printf("%d\n", strlen(arr1));
	return 0;
}

 实现的三种方式:

1、局部变量统计

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

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

2、递归调用

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

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

3、指针法

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

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

缺点:

C语言: 详解常用的字符串函数(使用+模拟实现)_第2张图片

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

1.2 strcpy

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

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

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

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

目标空间必须可修改。 

如果是 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,然后++。使用断言需要包含头文件   

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

1.3 strcat 

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

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

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

目标空间必须可以修改。

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

使用:

#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循环能让指针找到结尾 ‘\0’ 处,然后第二个循环把每一个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("%d\n", ret);
	return 0;
}

实现:

int my_strcmp(const char* s1, const char* s2)
{
	assert(s1);
	assert(s2);
	while (*s1 == *s2)
	{
		//让s1和s2比完了就停下来
		if (*s1 == '\0')
		{
			return 0;
		}
		s1++;
		s2++;
	}
	//直接返回他们差值
	return *s1 - *s2;
}

第一个while循环让两个指针指针指向的字符串逐个字符比较。如果其中*s1= ‘\0’,由于进入循环是要两个比较的字符相等,说明两个都是结尾了,两个字符串比较到结尾说明他们相等,所以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("%s\n", 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 == '\0')
	{
		return (char*)str1;
	}
	while (*cp)
	{
		//每次回到有效起始位置
		s1 = cp;
		s2 = str2;
		//两个都不是末尾,并且相等的时候就都++
		while (*s1 && *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		//如果s2到底了,说明查完了
		if (*s2 == '\0')
		{
			return (char*)cp;
		}
		cp++;
	}
	//如果都不符合,说明没有查到,返回空指针
	return NULL;
}

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

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

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

1.6 strtok

格式:char * strtok ( char* str, const char* sep );
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
如果字符串中不存在更多的标记,则返回 NULL 指针。
光看定义比较难理解,通俗来说,这是一个查找函数,如果在第一个字符串找都第二个字符串中含有的字符,那么就会将其改成 ‘\0’ ,并返回首元素地址。
第二使用的时候,第一个参数传 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("%s\n", 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. 字符分类函数C语言: 详解常用的字符串函数(使用+模拟实现)_第3张图片

使用需要包含头文件    

用法: 

小写转换大写

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

	}
	return 0;
}

打印结果:A

2.1 memcpy

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

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

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

如果 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。

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

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

五一快乐!

C语言: 详解常用的字符串函数(使用+模拟实现)_第4张图片

你可能感兴趣的:(C语言,c语言,开发语言,后端,字符串)