C/C++学习之路之字符串函数、字符函数、内存操作函数(下)

前言:
在上篇文章中,博主已经介绍了部分字符串函数(上篇链接:C/C++学习之路之字符串函数、字符函数、内存操作函数(上)_暮影从柯的博客-CSDN博客)。这期博主将介绍剩下的字符串函数,以及字符函数和内存操作函数。

目录

一、strstr()介绍及其模拟实现

二、strtok()介绍

三、字符函数

四、memcpy()介绍及其模拟实现

五、memmove()介绍及其实现

六、memcmp()介绍

七、memset()介绍


一、strstr()介绍及其模拟实现

1.库函数strlen()介绍

//头文件引用:<string.h>
const char* strstr(const char* str1, const char* str2)
{
    //函数功能:在str1指向的字符串中寻找str2指向的字符串第一次出现的地址
    //参数str1 - 主字符串
    //参数str2 - 子字符串
    //返回类型char* - 子串在主串中第一次出现的地址
}
//注意事项:
//主串中找到子串,返回找到的地址;找不到,返回NULL

示例:

2.模拟实现

const char* my_strstr(const char* str1, const char* str2)
{
	const char* p1;//指向主串
	const char* p2;//指向子串
	const char* cp;//指向主串匹配的起点
	assert(str1 && str2);
	if (*str2 == '\0')
		return str1;
	cp = str1;
	while (*cp)//一次匹配
	{
		p1 = cp;
		p2 = str2;
		while (*p1 && *p2 && *p1 == *p2)//匹配过程
		{
			p1++;
			p2++;
		}
		if (*p2 == '\0')//匹配成功
			return cp;
		cp++;
	}
	return NULL;
}

 解释:

1.原理:我们采用的是暴力算法实现strstr。我们先定义一个指针cp,cp表示每次主串子串匹配时主串开始的位置。再定义两个指针p1,p2,分别指向主串和子串匹配的字符。之后开始匹配,若该次匹配失败,则cp++,指向主串下次匹配的位置,再让p1=cp,p2回退到指向第一个字符,再次匹配。直到匹配成功,返回地址;或cp指向'\0',表示结束,此时匹配失败,返回NULL。

2. 代码解释:若str2指向的是'\0',则为空字符串,直接返回str1。先让cp指向主串的第一个字符。外面的循环,也就是第一个while,表示一次匹配,当cp不指向'\0'就进行一次匹配。之后让p1=cp表示配对的起点,p2指向子串的第一个字符。里面的循环,也就是第二个while,表示字符配对。当*p1==*p2,表示指向字符相同,p1、p2都+1,继续配对下一个字符。当至少存在*p1=='\0'、*p2=='\0'、*p2!=*p1其中一种情况是跳出循环。此时再检查*p2是否为'\0',*p2=='\0'表示匹配成功,返回匹配起点cp。若不等于'\0',就说明是其他两种情况,都属于匹配失败。此时cp++,指向下一次匹配的起点。当最后都没有匹配成功时就说明找不到,返回NULL。

对于strstr()的实现,还可以用KMP算法。但本篇篇幅有限,若评论区有较多人想要了解或学习KMP算法,我会尽快写一篇博客讲解。

二、strtok()介绍

//头文件引用:<string.h>
char* strtok(char* str, const char* delimiters)
{
    //函数功能:根据分隔符对字符串进行分隔
    //参数str - 被分隔的字符串
    //参数delimiters - 指向分隔符的集合
    //返回类型char* - 指向分隔后的字符串
}
//函数原理及用法: 

第一次调用时,传入需要分隔的字符串和分隔符集合,函数会从第一个字符开始浏览,当找到第一个非分隔符的字符时,记为起点,之后继续浏览,找到分隔符时,将分隔符改为'\0',返回起点的地址。

之后的调用中,第一个参数只需要传入NULL,而函数会从上次的分隔符后一位开始浏览,找到起点终点,返回起点的地址。

当函数遇到'\0'后,返回起点的地址,并且,在之后的调用中,函数都只会返回NULL。

示例:

三、字符函数

字符函数包括字符分类函数字符转换函数。他们都需要引用头文件<ctype.h>,并且他们的参数返回类型都是int

我们先讲一下字符分类函数。

字符分类函数就是你输入一个整型数字(输入字符也行,因为字符在内存中是以ASCII码值存储的),函数会按照ASCII码值找到该数字对应的字符,如果该字符符合函数要求,返回真(非0数字),不符合,返回假(0)。

由于他们的用法类似,只是对参数字符的需求不一。我们用一张表格表示:

函数如果它的参数满足下列条件就返回真
isalnum任何控制字符
isalpha字母a~z或A~Z
islower小写字母a~z
isupper大写字母A~Z
iscntrl任何控制字符
isdigit十进制数字0~9
isxdigit十六进制数字,包括所有十进制数字,小写字母a~f,大写字母A~F
isblank制表符'\t'、空格' '
isspace空白字符:空格' ',换页'\f',换行'\n',回车'\r',制表符'\t'或者垂直制表符'\v'
isprint任何可打印字符,包括图形字符和空白字符
ispunct标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph任何图形字符

还有字符转换函数:tolower()toupper()

当参数为'A'~'Z'时,tolower会返回参数的小写的ASCII码值。若参数不为'A'~'Z',则返回参数的值。

同理,当参数为'a'~'z'时,toupper会返回参数的大写的ASCII码值。若不满足,则返回参数的值。

示例:

刚才讲的这些都是关于字符和字符串的函数。而对于某些操作,例如复制,有时候也需要在其他类型上面实现。所以下面我会介绍一些内存操作函数,这些函数可以对内存字节进行直接操作。

四、memcpy()介绍及其模拟实现

1.库函数memcpy()介绍

//头文件引用:<string.h>
void* memcpy(void* destination, const void* source, size_t num)
{
    //函数功能 - 将source指向的前num个字节的内容复制到destination指向的空间
    //参数destination - 复制的目标空间
    //参数source - 被复制的内容
    //参数num - 被复制的字节个数
}
//注意事项:
//sourcedestination的空间必须≥num,防止出现溢出
//sourcedestination指向的空间不能有重叠,否则复制的结果都是未定义(等下解释)
//复制的单位是字节,不同类型的数据其所占空间大小不同

示例:

2.模拟实现

void* my_memcpy(void* dest, const void* src, size_t sz)
{
	assert(dest && src);
	char* p = (char*)dest;//用p保存dest并用来遍历复制,dest不变,作为返回值
	while (sz--)//每次循环复制一个字节,循环sz次
	{
		*p = *(char*)src;
		p++;
		src = (char*)src + 1;
	}
	return dest;
}

解释:memcpy有一个非常重要的点是它是以字节为单位复制的,而对于一个字节的内容进行操作,我们自然而然的就想到了char*。在一次复制中,我们需要把src先转化为char*,再进行复制,之后以char*的类型++,表示跳到下一个字节。由于char*类型转换是临时的,所以我们每次复制都需要进行类型转换。当然,你也可以像我一样,创建一个char*指针存储src,这样直接用该指针进行++就行。

根据我们写的函数,我们就可以解释为什么source(src)和destination(dest)指向的空间不能重叠。先看个图:

这是怎么回事呢?

这是因为,复制完两个字节后,src指向的空间,从4变成了之前复制的2,下一个字节的5变成了3,dest指向的6和7之后自然也会被复制成2和3。

那如果,我们就是想像预期那样进行复制呢?这就要说到下一个函数,memmove()了。

对于这种空间重叠的情况,C语言规定了另外一种函数,memmove(),来满足使用。接下去博主就来介绍一下这个函数。

五、memmove()介绍及其模拟实现

1.库函数memmove()介绍

//头文件引用:<string.h>
void* memmove(void* destination, const void* source, size_t num)
{
    //函数功能:与memcpy类似,并且该函数的sourcedestination两个内存块可以重叠
    //参数destination - 目标内存块
    //参数source - 源内存块
    //参数num - 复制的字节数
}
//注意事项:
//源内存块和目标内存块的空间要足够大

示例:

2.模拟实现

这次我们先讲一下memmove能实现上述功能的原理。对于memcpy()所举的例子,我们知道用我们写的my_memcpy()是实现不了的。但是,如果我们将src和dest位置交换一下,你会发现,虽然src和dest的内存块依旧存在重叠,但却能得到我们预期的值。

这是因为,我们的memcpy()是从前往后复制的,即src从源内存块的第一个字节,复制到最后一个字节。当dest在src前面时,dest的改变并不会影响到src要复制的值,因为此时src前面的内容已经复制完毕了。而当dest在src的后面时,我们dest的改变使得原先src的值发生改变。

也就是说,当dest在src后面,我们从源内存块的最后一个字节开始复制,值也从目标内存块的最后一个字节开始存入,是不是就能避免这种情况呢?

理解了这个原理,我们就能写出代码了。

void* my_memmove(void* dest, const void* src, size_t num)
{
	assert(dest && src);
	char* dest2 = (char*)dest;
	char* src2 = (char*)src;
	if (dest > src)//源字符串在前
	{
		dest2 += num - 1;
		src2 += num - 1;
		while (num--)
		{
			*dest2-- = *src2--;
		}
	}
	else//源字符串在后
	{
		while (num--)
			*dest2++ = *src2++;
	}
	return dest;
}

(其实只要没有重叠部分,两种复制方法都可以,像上面的代码这么分有些果断,不过两种方法的所占时间和空间差不多,为了容易理解就采用了这种写法。)

六、memcmp()介绍

//头文件引用:<string.h>
int memcmp(const void* ptr1, const void* ptr2, size_t num)
{
    //函数功能:比较两个内存块前num个字节
    //参数ptr1 - 指向第一个内存块
    //参数ptr2 - 指向第二个内存块
    //参数num - 内存块比较的字节个数
}
//注意事项:
//比较ptr1ptr2指向的内存块的第一个字节内数据大小,如果相等继续比较下一个字节
//当前num个字节都相等时,返回0
//若出现ptr1指向的内存块中某个字节的数据大于ptr2对应的字节的数据,返回>0的数
//若相反,返回<0的数

示例:

在小端存储模式的环境下编译

解释:

虽然a>b,但memcmp()比较的是单个字节内数据的大小。根据数据在内存中的存储知识,我们知道:

又因为我们是在小端存储模式的环境下编译的,则a、b变量在内存中的形式为:

当使用memcmp()时,在第一个字节上a<b,所以返回的是一个<0的数。

七、memset()介绍

//头文件引用:<string.h>
void* memset(void* ptr, int value, size_t num)
{
    //函数功能:将ptr指向的内存块的前num个字节的值改为value
    //参数ptr - 指向要设置内存的内存块
    //参数value - 每个字节要设置的值
    //参数num - 要设值的字节数
}
//注意事项:
//以字节为单位修改内存的值
//每个字节设置的值都相同

示例:

memset()是以字节为单位设置值,则我们用memset()来初始化字符数组会比较方便。

八、结语

本篇介绍的字符串函数、字符函数、内存操作函数与上篇介绍的字符串函数,都是比较实用的函数。熟练掌握这些函数的原理和用法,我们写程序就会事半功倍。

那以上呢就是《C/C++学习之路之字符串函数、字符函数、内存操作函数(下)》的全部内容,如果对你有帮助,请不要吝啬的点赞啦,每一个点赞都是我更新的动力。如有不足,也请各位读者在评论区指出,我会认真听取每一个建议,并作出相应的改变。

谢谢观看!

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值