• 快速上手字符串函数


    前言

    在C语言中,我们对字符串的操作大部分都是通过字符串函数来进行的,下面就让我们来深入的了解一下关于字符串操作的函数。

    一、求字符串的长度

    strlen函数

    函数原型:

    size_t strlen( const char *string );
    //string:起始位置的地址
    
    • 1
    • 2

    strlen是用来求字符串长度的函数,它求的长度不包含结尾的’\0’。
    官方文档解释:
    在这里插入图片描述
    注意,返回字符串中的字符数中不包括终止的null字符!!!

    strlen函数学习使用

    int main()
    {
    	char str1[] = "abcdef";
    	char str2[] = { 'a','b','c','d','e','f'};
    	int test1 = strlen(str1);
    	int test2 = strlen(str2);
    	printf("%d\n%d\n", test1, test2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    下面我们根据内存来看一下原因:
    在这里插入图片描述
    我们看到str1和str2相比,结尾有一个’\0’,而strlen函数找到结束字符才停止,str2后面的元素是未知的,str2后’\0’的位置也是未知,所以得到值和我们str1不同。
    如果我们传空指针或是空字符串会出现什么情况累?

    int main()
    {
    	char* str3 = "";
    	char* str4 = NULL;
    	int test3 = strlen(str3);
    	printf("%d", test3);
    	int test4 = strlen(str4);
    	printf("%d", test4);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    我们发现程序会挂掉,但是原因是什么呢?
    在这里插入图片描述

    空指针不可以进行解引用操作!!!

    strlen函数模拟实现

    根据上面的案例,我们来模拟实现strlen函数的主要思路是找到结束字符,对传入的地址进行判断(防止为空)。,下面就让我们来模拟实现一下吧。
    测试用例:

    int main()
    {
    	char str1[] = "abcdef";
    	char str2[] = { 'a','b','c','d','e','f'};
    	char* str3 = "";
    	int test1 = my_strlen(str1);
    	int test2 = my_strlen(str2);
    	int test3 = my_strlen(str3);
    	printf("%d\n%d\n%d\n", test1, test2,test3);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    strlen函数模拟实现方法1:计数器法
    size_t my_strlen(const char* string)
    {
    	assert(string);//判断传入的地址是否有效
    	int count = 0;//创建一个计数器,用来存储字符个数
    	while (*string++)//string会先和++结合,但是是前置++,进行解引用时使用的是未++前的值
    	//当解引用未结束字符时,即'\0',这时循环条件为假,结束循环
    	{
    		count++;
    	}
    	return count;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    strlen函数模拟实现方法2:指针减指针法
    size_t my_strlen(const char* string)
    {
    	assert(string);//判断传入的地址是否有效
    	char *start = string;//记录起始位置
    	char *end = string;//记录结束位置
    	while (*end)//判断是否为结束字符
    	{
    		end++;//结束位置后移
    	}
    	return end - start;//指针减指针,得到的是两个指针之间的元素个数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述

    strlen函数模拟实现方法3:递归方法
    size_t my_strlen(const char* string)
    {
    	if (*string)//如果字符串这个位置不为结束字符
    	{
    		return 1 + my_strlen(string + 1);
    		//返回一个字符的数量,并且进行递归,传入这个元素的下一个位置,直到递归到结束字符为止
    	}
    	return 0;//如果是结束字符,则返回0,因为结束字符是不计算的
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    二、字符串的拷贝,拼接和比较

    strcpy函数

    函数原型:

    char* strcpy(char* strDestination, const char* strSource);
    //strDestination目标字符串的位置
    //strSource 源头字符串的位置
    //返回值,传入的目标字符串起始的位置
    
    • 1
    • 2
    • 3
    • 4

    strcpy函数将源头字符串(包括终止的null字符)复制到目标字符串中。
    官方文档解释:
    在这里插入图片描述
    注意:strcpy是不执行溢出检查的,所以我们使用时要注意数组的越界,防止拷贝时出现数组的越界访问!!!

    strcpy函数学习使用

    int main()
    {
    	char strDestination1[15] = "abcdefggggggg";
    	char strDestination2[] = "abcde";
    	char strSource[] = "hijklm";
    	printf("拷贝前strDestination1:%s\n", strDestination1);
    	strcpy(strDestination1, strSource);
    	printf("拷贝后strDestination1:%s\n",strDestination1);
    	printf("拷贝前strDestination2:%s\n", strDestination2);
    	strcpy(strDestination2, strSource);
    	printf("拷贝后strDestination2:%s\n", strDestination2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    我们发现程序崩溃,并且提示出来错误。观察发现是我们strDestination2拷贝时发生了越界,由原来的5个元素变成了6个,产生了越界访问。所以产生了越界访问错误。让我们通过内存再来详细的看一下吧。
    在这里插入图片描述
    上面是strDestination1中内存经过strcpy后的变化。在这里插入图片描述
    打印结果可以看出strcpy确实对我们的strDestination1进行了拷贝,由于我们strDestination1的内存远大于我们源头的字符串,所以并未出现错误信息,并且程序可以正常运行。
    接下来让我们看strDestination2的内存信息吧:
    在这里插入图片描述
    我们来看打印信息:
    在这里插入图片描述
    我们可以发现strDestination2内存已经不足了,但还是把源头的字符串全部放入到了strDestination2中。
    在这里插入图片描述
    通过错误信息来看是我们越界访问并且修改了strDestination2后面的元素造成的。

    strcpy函数模拟实现

    通过上面用例学习,我们已经了解了strcpy函数的使用方法和注意事项,那么我们在模拟实现就解决这些问题,并且改进一下吧。
    测试用例:

    int main()
    {
    	char strDestination1[15] = "abcdefggggggg";
    	char strDestination2[] = "abcde";
    	char strSource[] = "hijklm";
    	printf("拷贝前strDestination1:%s\n", strDestination1);
    	printf("拷贝后strDestination1:%s\n", my_strcpy(strDestination1, strSource));
    	printf("拷贝前strDestination2:%s\n", strDestination2);
    	printf("拷贝后strDestination2:%s\n", my_strcpy(strDestination2, strSource));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    我们在刚才的测试中小小的改动了一下,为了观察函数的返回值,目的是为了实现链式访问。

    strcpy函数模拟实现方法1:
    char* my_strcpy(char* strDestination, const char* strSource)
    {
    	assert(strDestination && strSource);//判断strDestination和strSource地址是否合法
    	char* str = strDestination;//用来记录strDestination的起始位置,方便对目标字符串的返回
    	while ((*strDestination != '\0') && (*strSource != '\0'))//只有双方都不为‘\0’才进入循环
    	{
    		*strDestination = *strSource;//进行拷贝
    		strDestination++;//对目标位置进行加1
    		strSource++;//对源头置进行加1
    	}
    	if (*strSource == '\0')
    	//如果源头为‘\0’,我们需要把源头的‘\0’拷贝到目标字符串中
    	//如果是目标字符串中‘\0’,则证明我们在进行拷贝会产生越界,所以不需要在拷贝了
    	{
    		*strDestination = *strSource;
    	}
    	return str;//返回strDestination的起始位置
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在这里插入图片描述
    这种我们不仅实现拷贝’\0’,并且防止了数组越界的产生。

    strcpy函数模拟实现方法2:对方法1的改进
    char* my_strcpy(char* strDestination, const char* strSource)
    {
    	assert(strDestination && strSource);//判断strDestination和strSource地址是否合法
    	char* str = strDestination;//用来记录strDestination的起始位置,方便对目标字符串的返回
    	while ((*strDestination != '\0') && (*strDestination++ = *strSource++))
    	//strDestination不为空
    	//strDestination先和++结合,和*结合是++前的结果解引用等于strSource++前的结果
    	//一个等号是进行赋值,当*strSource为‘\0’时,把这个结果赋给*strSource时,这个表达式的值也为0
    	{
    		;
    	}
    	return str;//返回strDestination的起始位置
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    strncpy函数

    函数原型:

    char *strncpy( char *strDest, const char *strSource, size_t count );
    //strDest目标字符串
    //strSource 源字符串
    //count要复制的字符数
    //返回值,传入的目标字符串起始的位置
    
    • 1
    • 2
    • 3
    • 4
    • 5

    strncpy函数拷贝count个字符从源字符串到目标空间。如果源字符串的长度小于count,则拷贝完源字符串之后,在目标的后边追加0,直到count个。
    官方文档解释:
    在这里插入图片描述
    注意:目标重叠将产生未定义行为!!!

    strncpy函数学习使用

    int main()
    {
    	char strDest1[] = "abcdefggggggg";
    	char strDest2[] = "abcdefggggggg";
    	char strDest3[] = "abcde";
    	char strSource[] = "hijklm";
    	printf("拷贝前strDestination1:%s\n", strDest1);
    	printf("拷贝后strDestination1:%s\n", strncpy(strDest1, strSource, 4));
    	printf("拷贝前strDestination2:%s\n", strDest2);
    	printf("拷贝后strDestination2:%s\n", strncpy(strDest2, strSource, 8));
    	printf("拷贝前strDestination3:%s\n", strDest3);
    	printf("拷贝后strDestination3:%s\n", strncpy(strDest3, strSource, 8));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述
    我们发现strncpy函数也没有实现数组是否越界的检查,这需要我们模拟实现时需要思考解决的。
    让我们看内存情况了解一下strncpy函数的工作情况吧:
    在这里插入图片描述
    当我们的源字符串大于我们要拷贝的数量时,只是对目标字符串的替换,并会加入‘\0’。
    在这里插入图片描述
    当我们的源字符串小于我们要拷贝的数量时,会补‘\0’,直到count个。
    在这里插入图片描述
    我们可以发现strncpy对超过限制的没有检查作用。

    strncpy函数模拟实现

    我们的测试用例用刚才的用例来测试我们自己的函数。 我们strncpy函数模拟实现要解决超过限制的没有检查的问题。

    char* my_strncpy(char* strDest, const char* strSource, size_t count)
    {
    	assert(strDest && strSource);//判断strDest和strSource地址是否合法
    	char* str = strDest;//用来记录strDest的起始位置,方便对目标字符串的返回
    	while (count--)//对传入字符个数进行循环
    	{
    		if (*strDest == '\0')//当目标字符串到达结尾时,就返回我们记录的起始位置
    		{
    			return str;
    		}
    		else if (*strSource == '\0')//当我们源字符串到达结尾时
    		{
    			*strDest = *strSource;//我们把目标字符替换为'\0',并且只对目标字符串进行移动
    			strDest++;
    		}
    		else//当我们都没有到达结尾时,对两个字符串进行移动
    		{
    			*strDest = *strSource;
    			strDest++;
    			strSource++;
    		}
    	}
    	return str;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述
    注意:我们模拟实现要考虑是否补’\0’和数组是否会产生越界的情况。当我们源字符串到达结尾时不可以在对我们的源字符串进行移动。

    strcat函数

    函数原型:

    char *strcat( char *strDestination, const char *strSource );
    //strDestination目标字符串的位置
    //strSource 源头字符串的位置
    //返回值,传入的目标字符串起始的位置
    
    • 1
    • 2
    • 3
    • 4

    strcat函数追加一个字符串。
    官方文档解释:
    在这里插入图片描述
    注意这里的未定义行为,还是数组的越界访问。

    strcat函数学习使用

    int main()
    {
    	char strDestination[15] = "abcdefg";
    	char strSource[] = "hijklm";
    	printf("追加前strDestination:%s\n", strDestination);
    	printf("追加后strDestination:%s\n", strcat(strDestination, strSource));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述
    在这里插入图片描述
    我们需要把源字符串全部追加到目标字符串中。

    strcat函数模拟实现

    char* my_strcat(char* strDestination, const char* strSource)
    {
    	assert(strDestination && strSource);//判断strDest和strSource地址是否合法
    	char* str = strDestination;//用来记录strDest的起始位置,方便对目标字符串的返回
    	while (*strDestination)//把目标字符串移植末尾结束字符位置
    	{
    		strDestination++;
    	}
    	while (*strDestination++ = *strSource++)//进行字符串追加
    	{
    		;
    	}
    	return str;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述
    但我们实现的函数由一个致命的缺陷,就是对自己的追加。问题的解决我们放在memmove函数中解决。

    strncat函数

    函数原型:

    char *strncat( char *strDest, const char *strSource, size_t count );
    //strDest目标字符串的位置
    //strSource 源头字符串的位置
    //count要追加的字符数
    //返回值,传入的目标字符串起始的位置
    
    • 1
    • 2
    • 3
    • 4
    • 5

    strncat函数将源字符串的前count个字符追加到目标,再加上一个终止的null字符。如果源字符串的长度小于count,则只复制终止null字符之前的内容。
    官方文档解释:
    在这里插入图片描述
    前面的返回值等我们已经很熟悉了,这里我们只看注意事项。

    strncat函数学习使用

    int main()
    {
    	char strDestination1[15] = "abcdefg";
    	char strDestination2[15] = "abcdefg";
    	char strSource[] = "hijklm";
    	printf("追加前strDestination1:%s\n", strDestination1);
    	printf("追加后strDestination1:%s\n", strncat(strDestination1, strSource,4));
    	printf("追加前strDestination2:%s\n", strDestination2);
    	printf("追加后strDestination2:%s\n", strncat(strDestination2, strSource,8));
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    在这里我们分别追加方式分别使用小于源字符串追加和大于源字符的追加。

    strncat函数模拟实现

    有了前面的基础,我们来实现一下我们自己的函数吧。

    char* my_strncat(char* strDest, const char* strSource, size_t count)
    {
    	assert(strDest && strSource);//判断strDest和strSource地址是否合法
    	char* str = strDest;//用来记录strDest的起始位置,方便对目标字符串的返回
    	while (*strDest)//把目标字符串移植末尾结束字符位置
    	{
    		strDest++;
    	}
    	while (count--)//对传入字符个数进行循环
    	{
    		if (*strSource == '\0')//当我们源字符串到达结尾时,
    		{
    			*strDest = *strSource;//把结束字符追加后结束本次追加
    			return str;
    		}
    		else//对两个字符串进行移动
    		{
    			*strDest = *strSource;
    			strDest++;
    			strSource++;
    		}
    	}
    	*strDest = '\0';//当循环结束时,代表我们源字符串的不完全追加,需要我们额外的补结束字符
    	return str;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述
    我们自己的实现可以发现思路就是我们strncpy和我们strcat的结合。当让,我们这个依然没有解决追加自己的问题。

    strcmp函数

    函数原型:

    int strcmp( const char *string1, const char *string2 );
    //string1要比较的以Null结尾的字符串
    //string2 要比较的以Null结尾的字符串
    //返回值,这些函数中的每个函数的返回值指示字符串1到字符串2的字典关系。
    
    • 1
    • 2
    • 3
    • 4

    strcpy函数用于两个字符串比较
    官方文档解释:
    在这里插入图片描述

    strcmp函数学习使用

    int main()
    {
    	char string1[] = "abcde";
    	char string2[] = "abcde";
    	char string3[] = "abcd";
    	char string4[] = "abcdf";
    	int cmp1 = strcmp(string1, string2);//相等情况
    	int cmp2 = strcmp(string1, string3);//string1>string2
    	int cmp3 = strcmp(string1, string4);//string1
    	printf("%d %d %d", cmp1, cmp2, cmp3);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    这里我们直接用返回值来判断了。
    注意:strcpy函数用于两个字符串比较是一个字符一个字符进行比较。

    strcmp函数模拟实现

    int my_strcmp(const char* string1, const char* string2)
    {
    	assert(string1 && string2);//判断string1和string2地址是否合法
    	while (*string1 != '\0' && *string2 != '\0')//只有当两个都不为空字符是才进行比较
    	{
    		if (*string1 > *string2)
    		{
    			return 1;
    		}
    		else if (*string1 < *string2)
    		{
    			return -1;
    		}
    		else//当两个字符相等时比较下一个字符
    		{
    			string1++;
    			string2++;
    		}
    	}
    	//循环结束,要判断是单个结束还是两个一起结束
    	if (*string1 != '\0')
    	{
    		return 1;
    	}
    	else if (*string2 != '\0')
    	{
    		return -1;
    	}
    	else
    	{
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    在这里插入图片描述

    strncmp函数

    函数原型:

    int strncmp( const char *string1, const char *string2, size_t count );
    //string1要比较的以Null结尾的字符串
    //string2 要比较的以Null结尾的字符串
    //count,要比较的字符数
    //返回值,这些函数中的每个函数的返回值指示字符串1到字符串2的字典关系。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    strncpy函数用于两个字符串前count个字符的比较。
    官方文档解释:
    在这里插入图片描述

    strncmp函数学习使用

    int main()
    {
    	char string1[] = "abcde";
    	char string2[] = "abcd";
    	int cmp1 = strncmp(string1, string2,4);//相等情况
    	int cmp2 = strncmp(string1, string2,5);//string1>string2
    	printf("%d %d", cmp1, cmp2);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    类比strcmp,我们很容易理解我们的strncmp函数。

    strncmp函数模拟实现

    int my_strncmp(const char* string1, const char* string2, size_t count)
    {
    	assert(string1 && string2);//判断string1和string2地址是否合法
    	while (count--)//进行要比较的字符次数的循环
    	{
    		if (*string1 > *string2)
    		{
    			return 1;
    		}
    		else if (*string1 < *string2)
    		{
    			return -1;
    		}
    		else//当两个字符相等时比较下一个字符
    		{
    			string1++;
    			string2++;
    		}
    	}
    	//循环结束,证明两个相等
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述
    我们strncmp函数的思想就是循环count次,当count的元素都相同时,证明这两个字符串相等。

    三、字符串的查找

    strstr函数

    函数原型:

    char *strstr( const char *string, const char *strCharSet );
    //string要搜索的以Null结尾的字符串
    //strCharSet 要搜索的以Null结尾的子字符串
    //返回值,返回一个指针,指向字符串中第一个出现的strCharSet,如果字符串中没有出现strCharSet则返回NULL
    
    • 1
    • 2
    • 3
    • 4

    strstr函数在一个字符串中查找子串。
    官方文档解释:
    在这里插入图片描述
    这个函数我们需要注意返回值为NULL还是返回第一次出现的位置,或者是被查找的字符串。

    strstr函数学习使用

    int main()
    {
    	char str[] = "abcccdefgh";
    	char *str1 = strstr(str, "ccdef");
    	char *str2 = strstr(str, "");
    	char *str3 = strstr(str, "acbd");
    	puts(str1);
    	puts(str2);
    	if (str3 == NULL)
    	{
    		printf("str3 is NULL\n");
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述
    我们分别查询在字符串中,不在字符串中和空字符串的情况,充分的了解了各种情况下的返回值。

    strstr函数模拟实现

    char* my_strstr(const char* string, const char* strCharSet)
    {
    	assert(string && strCharSet);//判断string1和string2地址是否合法
    	if (*strCharSet == '\0')//判断传入的要查找的子字符是否为空字符串。
    	{
    		return string;
    	}
    	char* retu = string;//一个节点用来存储要返回的位置
    	char* hand = strCharSet;//用来记录子串的起始位置
    	while (*retu)//当要返回的位置不为空时
    	{
    		strCharSet = hand;//把字串的位置置为起始位置
    		string = retu;//把被查找的位置改为现在存储返回的位置
    		while (*string == *strCharSet)//如果两个字符相等,则比较下一个字符
    		{
    			if (*strCharSet == '\0')//判断是否两个都到达结束位置
    			{
    				return retu;
    			}
    			string++;
    			strCharSet++;
    		}
    		if (*strCharSet == '\0')//当strCharSet到达结束标志时,证明改返回位置是查找的起始位置
    		{
    			return retu;
    		}
    		retu++;
    	}
    	return NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    在这里插入图片描述
    在这里插入图片描述
    在这里retu指针的意义是为了防止连续出现多的重复和子串一样元素,但最后不同时,便于找到开始查找的位置。hand指针的意义是每次对比结果不同时,把字串的指针位置回到开始。

    strtok函数

    函数原型:

    char *strtok( char *strToken, const char *strDelimit );
    //strToken包含令牌的字符串
    //strDelimit字符串,分隔符字符集
    
    • 1
    • 2
    • 3

    strtok函数用于查找字符串中的下一个标记。
    官方文档解释:
    在这里插入图片描述
    注意:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。(strtok函数找到strToken中的下一个标记,并将其用 ‘\0’ 结尾,返回一个指向这个标记的指针。)
    strtok函数函数返回值讨论:
    1.strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
    2.strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
    3.如果字符串中不存在更多的标记,则返回 NULL 指针。

    strtok函数学习使用

    看了上面的解释,我们看的雨里雾里的。让我们通过实践来学习一下吧。

    int main()
    {
    	char* strToken = "xiao/hei.123@com.xiaohei";
    	char* strDelimit = "/.@";//strDelimit放置的分隔字符为'/','.','@'
    	char arr[30] = { 0 };
    	strcpy(arr, strToken);//将数据拷贝一份,处理arr数组的内容
    	char* str = NULL;
    	for (str = strtok(arr, strDelimit); str != NULL; str = strtok(NULL, strDelimit))
    	//函数第一次需要把要分隔的字符串传入
    	//后续改函数会记住上次分隔符的位置
    	{
    		printf("%s\n", str);
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在这里插入图片描述
    这里注意对同一个字符串进行查找时第二次要传NULL,因为该函数会记录上次查找的位置。

    四、错误信息报告

    strerror函数

    函数原型:

    char *strerror( int errnum );
    //errnum错误编号
    //返回值,指向错误消息字符串的指针
    
    • 1
    • 2
    • 3

    strerror函数用于返回错误码所对应的错误信息。
    官方文档解释:
    在这里插入图片描述

    strerror函数学习使用

    下面我们演示一下用法吧

    #include //必须包含的头文件
    int main()
    {
    	FILE* pf;
    	pf = fopen("test.txt", "r");
    	if (pf == NULL)
    	{
    		printf("Error: %s\n", strerror(errno));
    	}
    	int* p = (int*)malloc(sizeof(int) * 0xFFFFFFFFF);
    	if (p == NULL)
    	{
    		printf("Error: %s\n", strerror(errno));
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述
    注意:返回的错误信息如果不即时打印就会被下一个错误信息覆盖。
    和这个函数相似的还有一个perror函数。

    int main()
    {
    	FILE* pf;
    	pf = fopen("test.txt", "r");
    	if (pf == NULL)
    	{
    		printf("Error: %s\n", strerror(errno));
    		perror("Error");
    	}
    	int* p = (int*)malloc(sizeof(int) * 0xFFFFFFFFF);
    	if (p == NULL)
    	{
    		printf("Error: %s\n", strerror(errno));
    		perror("Error");
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    五、内存操作的函数

    我们对内存的操作都是针对所有类型的,所以我们的形参都是用void指针接收,返回值也是void类型的指针。

    memcpy函数

    函数原型:

    void *memcpy( void *dest, const void *src, size_t count );
    //dest目标字符串的位置
    //src 源头字符串的位置
    //返回值,传入的目标字符串起始的位置
    
    • 1
    • 2
    • 3
    • 4

    memcpy函数作用是从src的位置开始向后复制count个字节的数据到dest的内存位置。
    官方文档解释:
    在这里插入图片描述
    memcpy函数在遇到 ‘\0’ 的时候并不会停下来。如果src和dest有任何的重叠,复制的结果都是未定义的。

    memcpy函数学习使用

    struct stu
    {
    	char name[10];
    	int age;
    };
    int main()
    {
    	struct stu stu1 = { {"zhangsan"},20 };
    	struct stu stu2;
    	memcpy(&stu2, &stu1, sizeof(struct stu));
    	printf("%s %d", stu2.name, stu2.age);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    memcpy函数模拟实现

    在实现前我们要先思考为什么库函数中用的void指针来接收的。好处是什么?
    从上面的例子可以看出,我们这个实现的是可以拷贝所有的类型,所以类型我们把它限定为void,因为void指针可以接收任何指针。

    void* my_memcpy(void* dest, const void* src, size_t count)
    {
    	assert(dest && src);
    	void* begin = dest;
    	while (count--)
    	{
    		*((char*)dest) = *((char*)src);//把dest和src转化为字符指针,对字符指针进行移动
    		(char*)dest += 1;
    		(char*)src += 1;
    	}
    	return dest;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    这里面我们用字符指针进行移动,一个字符一个字符的进行拷贝,这样可以做到拷贝任何类型的数据。

    memmove函数

    函数原型:

    void *memmove( void *dest, const void *src, size_t count );
    //dest目标字符串的位置
    //src 源头字符串的位置
    //返回值,传入的目标字符串起始的位置
    
    • 1
    • 2
    • 3
    • 4

    memmove函数作用是从src的位置开始向后复制count个字节的数据到dest的内存位置。
    memmove函数和memcpy函数的区别:
    1.memmove函数处理的源内存块和目标内存块是可以重叠的(有的编译器memmove函数和memcpy功能已经一模一样了)。
    2. 如果源空间和目标空间出现重叠,最好使用memmove函数处理,memmove函数就是为了拷贝相同区域而生的
    官方文档解释:
    在这里插入图片描述

    memmove函数学习使用

    int main()
    {
    	char arr1[15] = "abcdefghigkl";
    	char arr2[15] = "abcdefghigkl";
    	char arr3[15] = "abcdefghigkl";
    	memmove(arr1 + 5, arr1, 5);
    	memmove(arr2 + 5, arr2 + 3, 5);
    	memmove(arr3 + 5, arr3 + 7, 5);
    	printf("%s\n", arr1);
    	printf("%s\n", arr2);
    	printf("%s\n", arr3);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述
    我们看到正对三种情况都可以正确的把字符串进行复制,接下来我们在函数模拟实现中着重的讨论一下如何复制重叠的区域吧,解决我们上面的strcpy等函数的缺陷吧。

    memmove函数模拟实现

    先让我们看看复制自己会有什么情况吧:
    在这里插入图片描述

    void* my_memmove(void* dest, const void* src, size_t count)
    {
    	assert(dest && src);
    	void* begin = dest;
    	if (*((char*)dest) - *((char*)src) > 0)
    	{
    		while (count--)
    		{
    			*((char*)dest + count) = *((char*)src + count);//把dest和src转化为字符指针,对字符指针进行移动
    		}
    	}
    	else
    	{
    		while (count--)
    		{
    			*((char*)dest) = *((char*)src);//把dest和src转化为字符指针,对字符指针进行移动
    			(char*)dest += 1;
    			(char*)src += 1;
    		}
    	}
    	return begin;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    思路:
    当源头右边重叠时,我们从源头的左边进行拷贝时会把我们的值进行覆盖,所以我们要从源头右边进行拷贝
    情况如下图所示:
    在这里插入图片描述
    当源头左边重叠时,我们从源头的右边进行拷贝时会把我们的值进行覆盖,所以我们要从源头左边进行拷贝
    情况如下图所示:
    在这里插入图片描述我们把两个元素转化为字符指针,通过指针相减可以得到两个指针的前后关系。然后选择合适的循环来进行拷贝。当两个不交叉重叠时,我们使用哪种方法都相同。
    在这里插入图片描述

    六、比较实用的字符操作函数

    下面是一些实用的字符操作函数:

    函数如果参数符合下列条件就返回真
    iscntr任何控制字符
    isspace空白字符
    isdigit十进制数字
    isxdigit十六进制数字
    islower小写字母
    isupper大写字母
    isalpha字母
    isalnum字母或者数字
    ispunct标点符号
    isgraph任何图形字符
    isprint任何可打印字符
    函数字符转换
    tolower大写字母转换为小写字母
    toupper小写字母转换为大写字母

    下面演示几个吧:

    int main()
    {
    	char arr[] = "aB1cD0eFg2HiGk";
    	int sz = strlen(arr);
    	int i = 0;
    	for (i = 0; i < sz; i++)
    	{
    		if (isupper(arr[i]))//如果是大写,就进行字符转化
    		{
    			arr[i] = tolower(arr[i]);
    		}
    		else if (islower(arr[i]))//如果是小写,就进行字符转化
    		{
    			arr[i] = toupper(arr[i]);
    		}
    		else if (isdigit(arr[i]))//如果是十进制数字,就打印
    		{
    			printf("%c ", arr[i]);//我们存的数字字符,如果用%d进行打印,需要让该元素减去一个字符0
    		}
    	}
    	printf("\n%s\n", arr);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    在这里插入图片描述

    总结

    相信你对字符串相关函数已经有了深刻的了解,在实战中多多使用吧。欢迎点赞留言呀。💕

  • 相关阅读:
    解决wget下载过慢的问题
    百度安全X盈科全球数据合规服务中心:推进数据安全及合规智能化创新领域深化合作
    Mac上安装Java的JDK多版本管理软件jEnv
    TikTok运营干货——养号篇
    想要精通算法和SQL的成长之路 - 课程表III
    inner join 、 left join 和 right join 区别
    Go语言重新开始,Go Modules 的前世今生与基本使用
    Java异常、断言和日志①——Java基础
    XX攻击——反射型 XSS 攻击劫持用户浏览器
    基于JavaWeb的学生住宿管理系统
  • 原文地址:https://blog.csdn.net/2301_76986069/article/details/132050787