聊聊字符串函数那点事

本文详细介绍了C语言中常用的字符串处理函数,包括strlen用于计算字符串长度,strcpy用于复制字符串,strcat用于连接字符串,strcmp用于比较字符串。还讨论了这些函数的模拟实现,以及涉及到的内存管理和指针操作。此外,提到了其他相关函数如strncpy,strncat,strncmp,strstr,strtok,strerror,memcpy,memcmp和memset的功能和使用注意事项。

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析2

在这里插入图片描述


👉🏻strlen

size_t strlen ( const char * str );
其参数类型是一个指针(地址),返回值是size_t,即无符号整型

strlen函数的功能主要是计算得出字符串的长度,它的具体实现过程是从某处地址开始往后读取,直到读取到\0停止,\0到起始地址的长度就是字符串长度。
所以,在这里,\0的存在是很重要的,如果没有\0,我们就不知道要读取到哪里停止。
在这里插入图片描述
如上图,为什么字符串长度明明是6,我们数组的长度却要设置为7呢?
其实其中我们再多出一部分空间用来存放\0,用来保证我们读取完字符串长度后可以正常读取结束。
在这里插入图片描述
如上图,我们可以知道,arr1的字符串长度为4,arr2字符串长度为5,所以strlen(arr1) - strlen(arr2)的结果按理来说肯定要<0,最后的输出结果应该为hello才对,为什么是hi呢?
我们注意,在上述中,strlen的返回类型为无符号整型,4-5=-1,-1其实是个无符号整型,作为无符号整型又怎么可能<0呢?所以最后打印hi是正确的。

模拟实现strlen函数

一、计数法

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

二、递归法

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

三、指针-指针

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

👉🏻strcpy

char* strcpy(char * destination, const char * source );

strcmp的功能是将某个字符串的内容拷贝覆盖到某个字符串中。
这里需要注意几点:

  • 源字符串必须以\0结尾
    那不然strcmp根本不知道拷贝多少字符结束
  • strcpy会将源字符串中的\0也拷贝到目标空间
  • 目标空间必须足够大,能够容放的下源字符串
  • 目标空间必须可变

在这里插入图片描述

由上图我们看到,arr作为数组名,其意义是一个地址,即是一个常量,常量是不可以被修改的。
在这里插入图片描述
如上图,strcpy只读取到了arr中的\0处,只将as\0拷贝到arr2中
目标空间不可修改情况
在这里插入图片描述
上图中,我们的目标空间中的值是常量,常量不可被修改,所以这边出现了访问冲突。

strcpy模拟实现

char*  my_strcpy(char* str2, const char* str1)
{
	char* p = str2;
	while (*str2++ = *str1++)//*优先级比后置++高,先使用后++
	{
		;
	}
	return p;//返回起始地址
}
int main()
{
	char* arr1 = "dasdaszx";
	char arr2[20] = { 0 };
	my_strcpy(arr2, arr1);
	printf("%s\n", arr2);
	return 0;
}

👉🏻strcat

char * strcat ( char * destination, const char * source );

strcat的功能是在目标字符串的后面追加字符串。
strcat将源字符串追加到目标字符串的过程中,会将目标字符串后的\0也取代了。
注意事项:

  • 源字符串必须以\0结束
  • 目标空间必须足够大
  • 目标空间必须可以修改
  • 字符串不可给自己追加

模拟实现strcat

char* my_strcat(char* str1, const char* str2)
{
	char* p = str1;
	while (*str1)
	{
		str1++;
	}
	strcpy(str1, str2);
	return p;

}
int main()
{
	char arr1[20] = "dasd ";
	char* arr2 = "cxzcxz";
	printf("%s\n", my_strcat(arr1, arr2));
	return 0;
}

👉🏻strcmp

int strcmp ( const char * str1, const char * str2 );

strcmp的功能就是对两个字符串进行大小对比
返回结果为<0,=0,>0.
对比的过程主要是对相对应字符的比较,如abc与bbc对比,a比b大,此时已经得出>0的结果,就不用再往后进行比较了
再比如abc和abc比较,a与a相等,往后继续b与b比较,又相等,再往后c与c比较,全部比试完之后,发现全部相等,则返回0.
而如果我们平常在判断语句进行字符串比较时,比较的其实就是字符串首字符的地址。

模拟实现strcmp

int my_strcmp(const char* str1, const char* str2)
{
	while (*str1 == *str2)
	{
		str1++;
		str2++;
	}
	return *str1 - *str2;
}
int main()
{
	char arr1[] = "abcde";
	char arr2[] = "abcdf";
	if (my_strcmp(arr1, arr2) > 0)
		printf(">");
	else
		printf("<=");
	return 0;
}

👉🏻strncpy、strncat、strncmp

strncpy

char * strncpy ( char * destination, const char * source, size_t num );

  • 拷贝num个字符从源字符串到目标空间,你让我拷贝几个我就拷贝几个。
  • 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加\0,直到num个。

strncat

char * strncat ( char * destination, const char * source, size_t num );

  • 将源文件的第一个num字符附加到目标文件,加上一个终止空字符(\0)。
  • 如果source中C字符串的长度小于num,则只包含终止符之前的内容
    复制空字符。也就是说如果我们本身长度为7(包括\0),但是要我们追加长度8,我们只追加\0(包含\0本身)之前的字符数,不凑满8个。

strncat相比于strcat它能够自己给自己追加。

strncmp

int strncmp ( const char * str1, const char * str2, size_t num );

比较到出现另个字符不一样或者一个字符串结束或者num个字符全部比较完。
如果给的比较字符数太大,函数再比较得出结果后就不会再进行无用的比较。

👉🏻strstr

char * strstr ( const char *str1, const char * str2);

  • 返回str2在str1中第一次出现的指针,如果str2不是str1的一部分,则返回空指针str1。
    功能即是寻找一个字符串里面能不能找到自己想要的字符串。

相似函数:
1.strchar: char *strchr(const char *str, int c)
strchr() 用于查找字符串中的一个字符,并返回该字符在字符串中第一次出现的位置。
如果在字符串 str 中找到字符 c,则函数返回指向该字符的指针,如果未找到该字符则返回 NULL。
str – 要查找的字符串。
c – 要查找的字符。
2.strrchr: char * strrchr ( char * str, int character );
返回指向 C 字符串 str 中最后一个出现的字符的指针。
终止空字符被视为 C 字符串的一部分。因此,也可以定位它以检索指向字符串末尾的指针

模拟实现strstr

char* my_strstr(const char* str1, const char* str2)
{
	char* p1 = str1;
	char* s1 = NULL;
	char* s2 = NULL;

	while (*p1)//str1在查找到\0时还没发现则停止循环
	{
		//我们需要设置一个回家的地址
		s1 = p1;
		s2= str2;
		while (*s1&&*s2&&*s1 == *s2)//*s1&&* s2即当s1指向\0说明没找到,s2指向\0说明找到了,二者达到任意一个都说明出现结果了
		{
			s1++;
			s2++;
		}
		if (s2 == '\0')
		{
			return p1;//即返回找到相同字符的首地址
		}
		if (s1 == '\0')
			return NULL;
		p1++;//此时若*s1 != *p2,p1再向前走一步,若为\0,说明到\0还没发现
	}
}
int main()
{
	char arr1[] = "dascxzf";
	char arr2[] = "scx";
	char* p = my_strstr(arr1, arr2);
	if (p != NULL)
		printf("找到了\n");
	else
		printf("没有\n");
	return 0;
}

👉🏻strtok

char * strtok ( char * str, const char * sep );

  • sep参数是个字符串,定义了用作分隔符的字符集合

  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标
    记。

  • strtok函数找到str中的下一个标记,并将其用 \0 结尾(这是一个修改动作),返回一个指向这个标记的指针(起始地址)。(注:
    strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
    并且可修改。)

  • strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
    中的位置。
    在这里插入图片描述
    如上图,arr2中存放的是分隔符的字符集合,有.和!。我们第一次调用strtok函数时,参数是不为NULL的,因为我们要先确定接下来要进行分隔的字符串,第一次非常重要,我们在找到目标字符串第一个标记点的同时,将strtok绑定了该函数,strtok会存储第一次标记点中分隔符所在的地址。
    那么这么strtok这么做的意义在于什么呢?
    我们一看上图,我们知道我们的分隔符一共有两个,我如果想要把后面!分隔符所分隔的内容打印出来怎么办呢?,请看下👇🏻

  • strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标
    记。
    在这里插入图片描述
    strtok是一个具有“记忆性”的函数,它会存储我们上一次分隔符所在的地址,而后下一次分隔,会从该地址往后寻找到下一个分隔符,除了第一次的参数为目标字符串的地址,而后的参数只要NULL即可,我们可以理解strtok既然帮我们记忆了我们下一次需要开始出发寻找的地址,所以我们干脆设个空指针继承它记忆库中的地址就行了。

  • 如果字符串中不存在更多的标记,则返回 NULL 指针。

我们其实可以看到,strtok这个函数用起来实在非常冗杂,那么我们能不能用代码优化一下呢?让它不用每次都重复复制参数NULL来更新分隔。👇🏻
在这里插入图片描述

👉🏻strerror

char * strerror ( int errnum );
头文件:#include <errno.h>

在这里插入图片描述

一般,strerror是将错误码(如01234)翻译成对应的错误信息,而返回的就是参数就是错误信息的首地址,然后打印的时候才可以根据这个首地址将错误信息打印出来。
但实际上,在C语言中,错误码一般都是存放在errno这个变量当中,我们若想知道某块代码出了什么问题,我们就可以将strerror(errno)放在代码下方打印出来,看看是什么情况。
在这里插入图片描述
但是像这样每次还要printf打印出来感觉很麻烦,所以有一个函数perrno它在strerror的基础上,可以直接实现打印错误信息,不用再借printf之手。
在这里插入图片描述
所以,对于strerror和perrno,若只想得到错误信息的地址返回值,使用前者,若想打印错误信息,则使用后者。

✌🏻memcpy

void * memcpy ( void * destination, const void * source, size_t num );

它的功能其实和strcpy差不多,但是memcpy的功能更为全面,它能拷贝任意类型的内容,而这里多了一个新的参数size_t num,为所要拷贝的内容字节量,类型为无符号整型。
在这里插入图片描述
如上图,我们从arr中拷贝了前5个元素,大小即为20个字节到arr2中

模拟实现memcpy

void* my_memcpy(void* dest, const void* src, size_t num)
{
	char* p = dest;//待会要返回的起始地址
	while (num--)
	{
		*((char*)dest)++ = *((char*)src)++;//强制类型转换,1字节1字节的修改,无论什么类型最后都能修改的到
		//我们一定要先用()将(char*)dest括起来,先强制类型转换再++,此时所走的地址长度才为1
	}
	return p;
}

模拟实现memcpy(内存空间重叠情况)

int main()
{
	int arr[]={ 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	my_memcpy(arr + 2, arr, 20);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

根据上述代码的要求,我们会出现内存重叠情况
在这里插入图片描述
我们看到,目标空间和原空间存在重叠部分,这个重叠部分会导致什么情况呢,我们稍微分析下就会发现问题所在,当我们把1,2复制给目标空间中的第一个和第二个元素时,同时我们原空间的第三,第四个元素的值就被覆盖了,发生了变化,此时,我们再调用原空间第三、第四个元素去给目标空间赋值就得不到我们想要的结果了,所以,我们如何改良这个模拟memcpy呢?
其实我们可以分成两种情况

  • 1.当src<dest的时候,我们采取从后往前赋值
  • 2.当src>=dest时,我们采取从前往后赋值
    代码如下
void* my_memcpy(void* dest, const void* src, size_t num)
{
	char* p = dest;
	if (src < dest)
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	 }
	else
	{
		while (num--)
				{
					*((char*)dest)++ = *((char*)src)++;
				}
	}
	return p;
}

实现效果👇🏻👇🏻👇🏻
在这里插入图片描述

其实,在c语言的发展过程中,memcpy其实是一个落后的函数,因为它在较落后的编译器中遇到内存空间重叠的情况下是会出现错误的(VS目前不会)
它的升级版函数是memmove,它就解决了该问题。所以,一般现在都用memmove.

✌🏻memcmp

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

其功能与strcmp一样,也是对应字节的比较,而这里多出了size_t num ,与memcpy中的size_t num同一个意思。

✌🏻memset

void * memset ( void * ptr, int value, size_t num );

将 ptr 指向的内存块的num个字节数设置为指定值
在这里插入图片描述

可以看到memset是一字节一字节设置的

😺字符分类函数

函数如果为真返回非零值
iscntrl任何控制字符
isspace空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’
isdigit十进制数字 0~9
isxdigit十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A ~ F
islower小写字母a~z
isupper大写字母A~Z
isalpha字母a ~ z或A~Z
isalnum字母或者数字,a ~ z,A ~ Z,0~9
spunct标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph任何图形字符
isprint任何可打印字符,包括图形字符和空白字符

⛄️字符转换函数

tolower(大写字母转小写)

int tolower ( int c );

toupper(小写字母转大写)

int toupper ( int c );


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长
在这里插入图片描述
在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值