C语言之常用字符/字符串函数(长文)

前言

在计算机编程过程中,友友们都会使用到大量的字符或字符串相关的函数,但大多只晓得相关的用法,却不知道其背后的实现过程,了解的不够深入,时间久了也许会这样…

在这里插入图片描述

在本小节中将会向大家展示一下字符函数和字符串函数的实现,这里将向大家举例一些非常常见的函数,通过对函数的使用以及自我实现来加深对这些知识的了解,话不多说,走起😏

📖汇总:

在这里插入图片描述

1、strlen

关于这个函数,使用的频率可谓是相当的高,相信大家一定不陌生,在之前的博客中也有总结过,在这里回顾一下。

在这里插入图片描述

显而易见,这是一个计算字符串长度的函数

✏️小试牛刀:

(1)关于’\0’

int main()
{
	char arr1[] = "abc";
	char arr2[] = { 'a', 'b', 'c' };
    //例①
	int len1  = strlen(arr1);
    printf("%d\n", len1);
    //例②
    int len2  = strlen(arr2);
    printf("%d\n", len2);
	return 0;
}

👁效果展示:

在这里插入图片描述

在编译器中,例①显示的结果是3,例②显示的结果是随机值15。

因为strlen函数计算的是’\0’之前的字符个数。

在例①中,字符串是以’\0’为结束标志,因此字符的个数为3;

在例②中,不包含’\0’,用strlen求长度时,未见到’\0’时,就会持续的向后数,直到遇见’\0’为止

在这里插入图片描述

(2)关于strlen的返回值类型

可以看见库里面strlen的返回类型是无符号整数,这会有什么影响吗?当然

#include <stdio.h>
int main()
{
 	const char*str1 = "abcdef";
 	const char*str2 = "bbb";
 	if(strlen(str2)-strlen(str1)>0)
 	{
 		printf("str2>str1\n");
 	} 
 	else
 	{
 		printf("str1>str2\n");
 	}
 	return 0;
}

这段代码你认为结果会是多少呢?如果不了解strlen函数返回值类型的友友萌一定认为是str1>str2,事实上!

在这里插入图片描述

搞错了!重来

答案是str2>str1,因为strlen的返回类型是size_t(无符号整型)

在这里插入图片描述

在平时模拟实现strlen时,用int作为返回值就会避免这样的情况。

🔖说明总结:

  1. 字符串以 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包含 ‘\0’ )。
  2. 参数指向的字符串必须要以 ‘\0’ 结束。
  3. 注意函数的返回值为size_t,是无符号的( 易错 )

strlen的模拟实现

#include <stdio.h>
#include <assert.h>
//1.方法一:计数器的版本
int my_strlen(const char* arr)
//arr并不会被修改,所以加上const修饰,指针所指向的内容将不能够通过指针     被修改,函数会变得更加安全
{   
	assert(arr);//断言指针,arr不得是空指针,保证指针的有效性,使用更安全
	int count = 0;
    //计数器count,放字符串的长度
	while (*arr != '\0')
    //取地址arr不等于'\0'说明找到了一个字符
	{
		count++;
        arr++;//使其找到下一个字符
	}
	return count;
}
//2.方法二:递归的版本
//int my_strlen(char* arr)
//{
//	if(*arr == '\0')
//		return 0;
//	else
//		return 1 + my_strlen(arr + 1);
//}
//3.方法三:指针减指针的版本
//int my_strlen(char* arr)
//{
//	char*str = arr;
//	while(*str != '\0')
//		str++;
//	return str - arr;
//}
int main()
{
	char arr[20] = "asdfg";
	int ret = my_strlen(arr);
	printf("%d", ret);
	return 0;
}

2、strcpy

这个函数使用的频率更加的频繁,因此需要大家深刻了解

在这里插入图片描述

这是一个字符串拷贝的函数

✏️小试牛刀:

int main()
{
	char* str = "xxxxxxxxxxxxxxxxxxx";
	char arr[15] = "##########";
    char arrr[5] ="###";
    //例①
	strcpy(arr, "hello");
    printf("%s\n", arr);
    //例②
	char arr2[] = { 'a', 'b', 'c' };
    strcpy(arr,arr2)
    printf("%s\n", arr);
    //例③
    strcpy(arrr,"hello");
    printf("%s\n", arrr);
    //例④
    char* p = "hello";
	strcpy(str, p);
	printf("%s\n", str);
	return 0;
}

👁效果展示:

在例①中:

在这里插入图片描述

hello在传参时。传的时h的地址,把地址传给了strSource,因此strSource 指向hello中h的地址,然后将其指向的内容拷贝到arr中

拷贝hello时’\0’也被拷贝过去了

在例②中,arr2不含有’\0’,因此拷贝时就会出现问题,不知道何时遇见’\0’,此时已经越界很久了

挂了!!!

在这里插入图片描述

在例③中:

hello字符拷贝过去打印出来了,但因为目标空间不足,导致报错,作为合格的程序员应该保证不犯这样的错误

在这里插入图片描述

在例④中:

str指向的是一个字符串常量,不能被修改,因此…程序崩溃

在这里插入图片描述

🔖说明总结:

  1. 源字符串必须以’\0’结束
  2. 会将字符串中的’\0’拷贝到目标空间
  3. 目标空间必须有足够的大,能够容纳下源字符串的内容
  4. 目标空间必须可以修改

strcpy的模拟实现

#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
	assert(dest != NULL);
	assert(src != NULL);
	char* ret = dest;
    //拷贝,连同'\0'一起
	while (*dest++ = *src++)
	{
		;
	}
	return ret;
}
int main()
{
	char arr1[20] = "$$$$$$$$$";
	char arr2[6] = "hello";
	printf("%s", my_strcpy(arr1, arr2));
	return 0;
}

3、strcat

在这里插入图片描述

这是一个字符串追加函数

✏️小试牛刀:

int main()
{
    //例①
	char arr1[20] = "hello ";
    char arr2[] = "world";
	strcat(arr1, arr2);//字符串追加(连接)
	printf("%s\n", arr1);
    //例②
    char str1[20] = "hello \0##########";
	char str2[] = "world";
	strcat(str1, str2);//字符串追加(连接)
	printf("%s\n", str1);
    //例③
    char tmp[15] = "abcd";
    strcat(tmp, tmp);
    printf("%s\n", tmp);
	return 0;
}

👁效果展示:

在例①中:

在这里插入图片描述

我们可以看见,arr[6]处的’\0’被’w’所替代,arr[7]处的’\0’被’o’所替代,以此类推

在例②中:

为了检验字符串追加过程中,‘world’后面的’\0’是一起追加过去的,还是原来本就自带的,我们通过例②来实现。

如果’\0’一起被追加过去了,那么后面有一个’#‘将会被’\0’覆盖,结果中将不会打印出’#‘来。如果是自带的,’#'们将会被打印出来。

在这里插入图片描述

结果表明源字符串中的’\0’会被追加过去。

因此例①的字符串追加过程应当如下:
在这里插入图片描述

在例③中:

用strcat函数能否实现自己给自己追加呢?

在这里插入图片描述

结果显示不可行。

在这里插入图片描述

🔖说明总结:

  1. 源字符串必须以’\0’结束
  2. 目标空间必须有足够的大,能够容纳下源字符串的内容
  3. 目标空间必须可以修改
  4. 无法实现自己追加自己

strcat的模拟实现

#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
	assert(dest && src);
	char* tmp = dest;//记录目标空间的起始位置
    //1.找到目标字符串中的'\0'
	while (*dest)
	{
		dest++;
	}
    //当循环结束时dest指向'\0'
    //2.源字符串追加过去,包括'\0'
	while (*dest++ = *src++)
	{
		;
	}
	return tmp;//返回目标空间的起始位置
}
int main()
{
	char arr1[20] = "hello ";
	char arr2[20] = "world!";
	printf("%s\n", my_strcat(arr1, arr2));
	return 0;
}

4、strcmp

strcmp函数之前有所介绍

在这里插入图片描述

这是字符串比较函数

在还未认识strcmp函数之前,你是否以为比较两个字符串是这样的:

int main()
{
	char* p = "obc";
	char* q = "abcdef";
	if (p > q)
	{
		printf(">\n");
	}
	else
	{
		printf("<=\n");
	}
	return 0;
}

在这里插入图片描述

这样的代码代表着将’obc’的首元素地址放入指针变量p中,'abcdef’的首元素地址放入指针变量q中,把俩指针进行大小的比较,字符串本身并未比较。

✏️小试牛刀:

int main()
{
    int ret1 = strcmp("abbb", "abq");
	int ret2 = strcmp("aaa", "aaa");
    int ret3 = strcmp("acb","abb");
	printf("%d\n", ret1);
    printf("%d\n", ret2);
    printf("%d\n", ret3);
	return 0;
}

👁效果展示:

在这里插入图片描述

🔖说明总结:

标准规定:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字

strcmp的模拟实现

#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* s1, const char* s2)
{
	assert(s1 && s2);
	while (*s1 == *s2)
	{	
		if (*s1 == '\0')
        //当比到\0时,说明字符串相等,返回0
		{
			return 0;		
        }
       	s1++;
		s2++;
     }
	return *s1 - *s2;
    //*s1大于*s2,返回大于0的数;*s1小于*s2,返回小于0的数
}
int main()
{
	char* p = "abcdef";
	char* q = "abcdef";
	int ret = my_strcmp(p, q);
	if (ret > 0)
	{
		printf("p > q\n");
	}
	else if (ret < 0)
	{
		printf("p < q\n");
	}
	else
	{
		printf("p == q\n");
	}
	return 0;
}

5、strncpy strncat strncmp

可以看出前面的几个函数都是长度不受限制的,你源字符串有多少,就给你拷贝多少到目标空间,有多少就给你追加到目标空间里多少,有多少就给你字符串比较多少。

因此也就会有长度受限制的字符串函数,这里介绍一下。

在这里插入图片描述

可以看出和原来的长度不受限制的字符串函数相比,长度受限制的字符串函数多了一个参数count,就是通过该参数来决定拷贝多少,追加多少,比较多少。

✏️小试牛刀:

int main()
{
    //strncpy函数
	char arr1[20] = "abcdef";
    char str1[20] = "abcdefghi";
	char arr2[] = "qwer";
	strncpy(arr1, arr2, 2);
    printf("%s\n", arr1);//qwcdef
    strncpy(str1,arr2, 6);
    printf("%s\n", str1);//qwer
    //arr2不够6个用'\0'补
    
	//strncat函数
	char ar1[20] = "hello ";
    char st1[20] = "hello \0###########";
	char ar2[] = "world";
	strncat(ar1, ar2, 3);
	printf("%s\n", ar1);//hello wor\0
    strncat(st1, ar2, 10);
    printf("%s\n", st1);//hello world\0
    //'\0'都追加过去了,就不必继续,实际上只追加了6个字符
    
	//strncmp函数
	char* p = "aqcdef";
	char* q = "abcqwert";
	int ret = strncmp(p, q, 4);//小于0
	int ret = strncmp(p, q, 3);//等于0
	printf("%d\n", ret);
	return 0;
}

👁效果展示:

strncpy函数:

在这里插入图片描述

在这里插入图片描述

和strcpy相比strncpy更加的安全。因为strcpy不会管目标空间是否放的下,咔咔一顿拷贝,如果目标空间不够放,就会产生问题。

strncat函数:

在这里插入图片描述

在这里插入图片描述

strncmp函数:

在这里插入图片描述

🔖说明总结:

strncpy

  1. 拷贝num个字符从源字符串到目标空间

  2. 若源字符串的长度小于num,则拷贝完源字符串后,在目标的后面追加 ‘\0’ ,直到num个

strncat:

  1. 追加的字符串长度小于源字符串的长度,只需要追加相印的字符串个数后加’\0’
  2. 追加的字符串个数大于源字符串的长度,追加完源字符串就结束

strncmp:

  1. 比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完为止

strncpy的模拟实现

#include <stdio.h>
#include <assert.h>
char* my_strncpy(char* dest, const char* src, size_t count)
{
    assert(dest && src);
	char* tmp = dest;//记录目标空间起始位置
	while (count && (*dest++ = *src++))
    //拷贝字符串,在本例子中,包含末尾的'\0'
	{
		count--;
	}
	if (count)
	{
		while (--count)
		{
			*dest++ = '\0';
		}
	}
    //拷贝个数(7)大于源字符串的字符个数(6),则用'\0'补齐个数,此处补一个'\0'
	return tmp;//返回目标空间起始位置   
}
int main()
{
	char arr1[20] = "############";
	char arr2[] = "hello";
	printf("%s\n", my_strncpy(arr1, arr2, 7));
	return 0;
}

strncat的模拟实现

#include <stdio.h>
#include <assert.h>
char* my_strncat(char* dest, const char* src, size_t count)
{
    assert(dest && src);
	char* tmp = dest;//记录目标空间起始位置
	while (*dest)//找到目标字符串中的'\0'
	{
		dest++;
	}
	while (count--)
	{	
        //追加字符串,'\0'都被追加过去了,就直接返回目标空间起始地址
		if ((*dest++ = *src++) == '\0')
			return tmp;//返回目标空间起始位置 
	}
	*dest = '\0';
    //源字符串中的'\0'未被追加过去时,需要在追加完后补一个'\0'
	return tmp;//返回目标空间起始位置   
}
int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "world";
	printf("%s\n", my_strncat(arr1, arr2, 10));
	return 0;
}

strncmp的模拟实现

#include <stdio.h>
#include <assert.h>
int my_strncmp(const char* arr1, const char* arr2, size_t count)
{
	assert(arr1 && arr2);
	while (count-- && *arr1 == *arr2)
	{	
		if (*arr1 == '\0' || count == 0)
		{
			return 0;
		}
		arr1++;
		arr2++;      
	}
	return *arr1 - *arr2;
    //*arr1大于*arr2,返回大于0的数;*arr1小于*arr2,返回小于0的数
}
int main()
{
	char* p = "aqcdgg";
	char* q = "aqcp";
	int ret = my_strncmp(p, q, 4);
	if (ret > 0)
	{
		printf("p > q\n");
	}
	else if (ret < 0)
	{
		printf("p < q\n");
	}
	else
	{
		printf("p == q\n");
	}
	return 0;
}

6、strstr

strstr函数此前并未介绍过,让我们来一探究竟

在这里插入图片描述

该函数的作用就是判断strCharSet字符串是否是string字符串的子串

✏️小试牛刀:

int main()
{
	char arr1[20] = "abcdcdbcde";
	char arr2[10] = "bcd";
	char* tmp = strstr(arr1, arr2);
	if (tmp == NULL)
	{
		printf("没找到!\n");
	}
	else
	{
		printf("找到了:%s\n",tmp);
	}
	return 0;
}

👁效果展示:

在这里插入图片描述

🔖说明总结:

  1. 如果没找到的话,返回空指针

  2. 找到了的话,返回第一次出现的位置的地址

strstr的模拟实现

#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* arr1, const char* arr2)
{
	assert(arr1 && arr2);
	const char* s1 = NULL;
	const char* s2 = NULL;
	const char* flag = arr1;//记录匹配的位置
	if (*arr2 == '\0')//arr2是一个空字符串
	{
		return (char*)arr1;
	}
	while (*flag)
	{
		s1 = flag;//s1指向记录匹配位置的变量flag处
		s2 = arr2;//s2指向arr2起始位置
		while (*s1 && *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
        //需要保证s1和s2指向的内容不为'\0'
        //如果没有该条件
        //当s1指向的内容为'\0',就会访问到非该字符串的内容
        //当s2指向的内容为'\0',说明匹配成功,无需进入循环
		if (*s2 == '\0')
		{
			return (char*)flag;//匹配成功,返回匹配成功的字符的地址
		}
		flag++;//下一个字符进行匹配
	}
	return NULL;//所有字符都无法匹配,返回空指针
}
int main()
{
	char arr1[20] = "abbbcde";
	char arr2[10] = "bbc";
	char* tmp = my_strstr(arr1, arr2);
	if (tmp == NULL)
	{
		printf("没找到!\n");
	}
	else
	{
		printf("%s\n",tmp);
	}
	return 0;
}

7、strtok

strtok函数也是一个比较奇怪陌生的函数

在这里插入图片描述

该函数的作用是strDelimit(分隔符字符)分解strToken字符串为一组字符串

✏️小试牛刀:

int main()
{
	char arr[] = "abc@258.211";
	char* p = "@. ";//分隔符
	char tmp[30] = { 0 };
	strcpy(tmp, arr);
	char* ret = NULL;
    //只有第一次调用时传tmp,以后调用都传空指针
    //当ret为NULL时,说明找不到分隔符了,结束
	for (ret = strtok(tmp, p); ret != NULL; ret =strtok(NULL, p))
	{
		printf("%s\n", ret);
	}
	return 0;
}

👁效果展示:

在这里插入图片描述

在这里插入图片描述

🔖说明总结:

  1. strDelimit 参数是个字符串,定义了用作分隔符的字符集合,分隔符不可以是’\0’。
  2. 第一个参数指定一个字符串,它包含了0个或者多个由 strDelimit 字符串中一个或者多个分隔符分割的标记。
  3. strtok 函数找到 str 中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:strtok 函数会改变被操作的字符串,所以在使用 strtok 函数切分的字符串一般都是临时拷贝的内容并且可修改。)
  4. strtok 函数的第一个参数不为 NULL ,函数将找到 str 中第一个标记,strtok 函数将保存它在字符串中的位置。
  5. strtok 函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
  6. 如果字符串中不存在更多的标记,则返回 NULL 指针。

8、strerror

这也是一个陌生领域
在这里插入图片描述

返回错误码所对应的错误信息,如果有需要的话,用printf函数进行打印

✏️小试牛刀:

#include <stdio.h>
#include <errno.h>
#include <string.h>
int main()
{
    //例①
	printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	printf("%s\n", strerror(4));
	printf("%s\n", strerror(5));
    //例②
	FILE* pf = fopen("test.txt", "r");//打开文件
	if (pf == NULL)//打开文件失败,就会返回一个空指针
	{
		printf("%s\n", strerror(errno));//打印失败的原因
		return 1;
	}
    //...(读文件)
	fclose(pf);//关闭文件
	pf = NULL;
	return 0;
}

👁效果展示:

在例①中:
在这里插入图片描述

在例②中:

在这里插入图片描述

🔖说明总结:

  1. 使用库函数的时候,调用库函数失败的时候,都会设置错误码,把错误码放入C语言中的一个全局变量errno中,这时使用strerror函数将错误码翻译成错误信息,该函数返回的是错误信息(一个字符串)的首字符地址。

  2. 若要使用全局错误码errno,需要引头文件 #include <errno.h>

perror

perror函数的功能和strerror函数类似,但和其相比更加方便

在这里插入图片描述

perror函数可以直接打印错误信息

✏️小试牛刀:

#include <stdio.h>
#include <string.h>
int main()
{
	//打开文件失败的时候,会返回NULL
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
        //这里是调用fopen之后,如果有错误,就进行错误信息打印
		return 1;
	}
	//...(读文件)
	fclose(pf);//关闭文件
	pf = NULL;
	return 0;
}

👁效果展示:

在这里插入图片描述

🔖说明总结:

  1. perror的参数是一个字符串,是自定义的信息。

  2. 无需传errno,因为该函数可以直接拿取errno的值,直接转化成错误无信息,并打印(打印的格式参考效果展示)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

富春山居_ZYY(已黑化)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值