对字符串和内存函数的认识(2)

        这次我们就紧接着上次,再更多的认识,使用一些函数,并对部分函数进行模拟实现。

目录

目录

 函数介绍

strstr 

对strstr的简单认识 

模拟实现strstr 

strtok 

对strtok的简单认识 

strerror 

对strerror的简单认识 

字符函数 

字符分类函数 

 字符转换函数

应用 

memcpy 

对memcpy的简单认识 

模拟实现memcpy 

内部拷贝问题 

memmove 

对memmove的简单认识 

模拟实现mommove 

memset 

对memset的简单认识 

memcmp 

对memcmp的简单认识 

小结 


 函数介绍

strstr 

对strstr的简单认识 

         如何来理解strstr呢,我们可以通过命名来推理:
        strcpy -- string copy 是字符串拷贝。
        strcmp -- string compare 是字符串比较。
        strstr -- string string 两个字符串,难道是在字符串里找到字符串?我们往下看:

        我们观察它的参数const char* strstr(const char* str1, const char* str2),我们可以看到,strstr确实是在字符串中寻找字符串,它会返回str1str2首次出现的位置,若没有,则返回空指针,那么我们接下来就来使用它:

#include<string.h>
int main()
{
	char str1[] = "abcdefghidefg";
	char str2[] = "def";
	char* ret = strstr(str1, str2);
	if (NULL == ret)
	{
		printf("没找到\n");
	}
	else
	{
		printf("%s\n", ret);
	}
	return 0;
}

运行结果如下:

        我们可以发现,strstr在同时存在两个需要寻找的字符串时返回首次该字符串出现的地址。

        紧接着我们对其进行模拟实现。

模拟实现strstr 

         那么这个函数应该如何进行模拟实现呢?

        我们先对简单的情况进行分析:我们发现,若是只创建两个参数的指针变量,那么在两个指针移动完之后,我们就失去了一开始进行匹配的位置:

        那么,我们就需要再创建一个指针cp记录str1开始匹配的位置,但如果一次匹配不成功的话,str2的起始位置也会失去,那么我们就干脆不对原位置进行处理,再创建两个指针s1,s2来进行移动操作:

        那我们进行编写:

#include<stdio.h>
#include<assert.h>
我们就先来对参数进行创建,我们接收的两个字符串都是不会进行修改的,那么我们就在前面加上const进行修饰。
const char* my_strstr(const char* str1, const char* str2)
{
	const char* cp;//让cp记录开始匹配的位置
	const char* s1;//让s1遍历str1指向的字符串
	const char* s2;//让s2遍历str2指向的字符串
	assert(str1 && str2);//断言
	if (str2 == '\0')//如果传的是空字符串,那么直接返回str1
		return str1;
	cp = str1;
	while (*cp)//判断str1是否为NULL
	{
		s1 = cp;
		s2 = str2;
		while (*s1 && *s2 && *s1 == *s2)//如果相等开始匹配,如果遇到'\0'就退出
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
			return cp;
		cp++;//不相等,找下一个
	}
	return NULL;
}

int main()
{
	char str1[] = "abbbcdef";
	char str2[] = "bbc";
	const char* ret = my_strstr(str1, str2);
	if (NULL == ret)
	{
		printf("没找到\n");
	}
	else
	{
		printf("%s\n", ret);
	}
	return 0;
}

 运行结果如下:

        我们虽然对strstr进行了模拟实现,但是它的算法不够高效,有兴趣的读者可以去研究一下KMP算法,也是解决在字符串中找字符串的问题。

strtok 

对strtok的简单认识 

        strtok是来干什么的呢?我们在中进行查看:strtok - C++ Reference (cplusplus.com)

,我们可以发现,strtok是来切割字符串的,比如:
        xmluoli@163.com

        假设这是一个邮箱,那么@和.就是分隔符,我们可以通过strtok来进行切割得到分隔符分割后的字符串。那么如何来对strtok进行使用呢,我们先对其参数进行分析: 

        它的参数char * strtok ( char * str, const char * delimiters ),从下面的描述来看,delimisters是分隔符字符串的集合,是将分隔符,比如"@.",放入其中,在使用strtok后,它会将第一个分割符改成'\0',并且记录下该位置。听起来有点难以理解,我们直接来进行实验:

#include<string.h>
int main()
{
	char arr[] = "xmluoli@163.com";
	char* p = "@.";
	char* s = strtok(arr, p);
	printf("%s\n", s);
	return 0;
}

  运行结果如下:

        我们在对其参数的认识当中得知,strtok是会将分割符改成'\0'的,那么会还原吗?我们进行调试:

        我们看到strtok是不会对修改的字符串进行还原的,所以我们在使用它的时候,还是先进行拷贝,对拷贝的字符串进行切割,我们接着对它一大段话进行解读:
        如果strtok函数第一个参数不为NULL,那么函数将找到strtok的第一个标记,strtok将会保存它在字符串当中的位置。如果strtok函数为NULL,那么就会重保存的位置开始,寻找下一个分隔符,如果找不到其他分隔符,返回NULL。既然如此,我们就来进行实验:

#include<string.h>
int main()
{
	char arr[] = "xmluoli@163.com";
	char buf[200] = { 0 };
	strcpy(buf, arr);
	char* p = "@.";
	char* s = strtok(buf, p);
	s = strtok(NULL, p);
	printf("%s\n", s);
	return 0;
}

运行结果如下:

        我们可以看到,确实如此。
        那么接下来就会出现一个问题,如果需要切割很长的字符串,难道需要重复写调用函数的代码吗?我们就对代码进行改造:

#include<string.h>
int main()
{
	char arr[] = "xmluoli@163.com";
	char buf[200] = { 0 };
	strcpy(buf, arr);
	char* p = "@.";
	char* s = NULL;
	for (s = strtok(buf, p); s != NULL; s = strtok(NULL, p))//for循环的初始化只进行一次
	{
		printf("%s\n", s);
	}
	return 0;
}

 运行结果如下:

        这样,字符串再长,也不需要对代码进行修改了。

strerror 

对strerror的简单认识 

         strerror是将错误码翻译成错误信息,返回的是错误信息字符串的起始地址。那么什么是错误码呢?这个大家应该比较熟悉就比如说404,就是找不到该网站。那么,在C语言中使用库函数的时候,会将错误码放在errno这个变量里面,errno是全局变量,可以直接使用:

        那么我们接下来就来看一些错误码所对应的信息:

#include<string.h>
int main()
{
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d: %s\n", i, strerror(i));
	}
	return 0;
}

运行结果如下:

        不得不说,这个函数在今后的使用一定会有很大的用处,我们可以在编写代码的时候加上这一句,对于寻找和修改bug就会有极大的帮助。

字符函数 

        在认识了一些字符串函数之后,我们来认识一些字符函数。 

字符分类函数 

         

        对于字符分类函数小编就不一一详细介绍了,我们就通过islower来举个例子。

        通过上面我们可以知道,islower判断如果是小写字母返回一个非零的值,如果不是小写字母返回非零的值,我们编写代码:

#include<ctype.h>
int main()
{
	int ret = islower('a');
	printf("%d\n", ret);
	ret = islower('X');
	printf("%d\n", ret);
	return 0;
}

  运行结果如下:

        我们刚入门的时候判断字母大小写需要写很多的if...else语句来进行判断,而现在我们就可以利用这些函数来简便我们的工作量了。

 字符转换函数

        字符转换函数其实也就只有两个函数:

        tolower


        toupper 

        这两个函数也是比较好理解的,一个是把大写字母转化为小写字母,一个是把小写字母转化成大写字母,我们直接进行应用:

#include<ctype.h>
int main()
{
	int ret = tolower('A');
	printf("%c\n", ret);
	ret = toupper(ret);
	printf("%c\n", ret);
	return 0;
}

运行结果如下:

        这样我们平时需要使用转换大小写也方便了很多。

应用 

        下面我们就对认识的函数进行应用,我们把一个字符串"Hello World!"当中的所以大写字母全部转化成小写字母,那么应该如何进行实现呢?我们只需要创建一个指针来遍历这个字符串,然后遇到大写字母进行转换就可以了: 

#include<ctype.h>
int main()
{
	char arr[] = "Hello World!";
	char* p = arr;
	while (*p)
	{
		if (isupper(*p))
		{
			*p = tolower(*p);
		}
		p++;
	}
	printf("%s\n", arr);
	return 0;
}

运行结果如下:

        到这里认识的函数都与字符相关,但是C语言中又不仅仅是字符,所以我们接下来就来认识与内存相关的函数 。

memcpy 

对memcpy的简单认识 

        在认识了strcpy后,这个函数也很好理解,就是拷贝,不过这个函数是对内存进行拷贝:

        我们可以看到它的参数:void * memcpy ( void * destination, const void * source, size_t num ),其中void*可以接收任意类型的数据。3个参数一个void*的目的地,一个void*的源头,还有一个需要拷贝的字节大小,那么我们就直接来对其进行应用:

#include<string.h>
int main()
{
	int arr1[10] = { 0 };
	int arr2[] = { 1,2,3,4,5 };
	memcpy(arr1, arr2, 20);
	return 0;
}

我们进行调试:

        我们可以看到,memcpy确实将arr2中的数据拷贝过去了。那么我们紧接着进对其进行模拟实现。

模拟实现memcpy 

        如何来模拟实现memcpy呢?模拟实现memcpy我们首先从参数上下手:

        我们知道,memcpy是可以拷贝任意类型的数据的,所以我们自然要将源头和目的地都设置成void*来方便接收,紧接着就是字节的处理了,既然它是拷贝字节,那我们就一个字节一个字节进行拷贝,那么,在进行拷贝的时候,我们就需要强转成char* 来进行处理了,需要注意的是,我们在指针移动时,前面将destsrc的强转赋值是临时的,我们之后在移动指针往后的时候是不能dest++,也就是对void*进行++处理的,我们需要再次进行强制类型转换 :

#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src);//进行断言
 	void* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
 return ret;
}

int main()
{
	int arr1[10] = { 0 };
	int arr2[] = { 1,2,3,4,5 };
	my_memcpy(arr1, arr2, 20);
	return 0;
}

我们进行调试:

内部拷贝问题 

        这时就有读者想问了,那么我可以把一个数组里面的内容在该数组内进行拷贝呢?就比如说我创建一个数组arr[1,2,3,4,5,6]。我可以把1,2,3拷贝到3,4,5上吗?那我们就通过实验进行验证: 

#include<stdio.h>
#include<assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
	assert(dest && src);//进行断言
	while (num--)
	{
		*(char*)dest = *(char*)src;
		dest = (char*)dest + 1;
		src = (char*)src + 1;
	}
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	my_memcpy(arr + 2, arr, 20);
	return 0;
}

我们进行调试:

        调试后我们发现,得到的并不是我们想要的,它在重复的拷贝1,2,这是为什么呢?

        所以我们得出结论,对于重叠的内存的拷贝我们是不能使用my_memcpy的,这里我们如果使用库函数memcpy,会发现还是将1,2,3,4,5拷贝过去了。在C语言规定中,memcpy是不能对重叠的内存进行拷贝的。这说明编译器对memcpy进行了优化,我们模拟实现的memcpy是办不到的,那么我们应该使用什么呢?就是接下来的memmove。

memmove 

对memmove的简单认识 

        memmovememcpy的参数都是相同的:

        我们直接使用: 

#include<string.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	memmove(arr + 2, arr, 20);
	return 0;
}

进行调试:

        在完成使用之后,我们就对其进行模拟实现。

模拟实现mommove 

        那么memmove该如何进行模拟实现呢?我们只需要解决掉覆盖的问题就可以了,我们会发现,之前我们想把前面的内容拷贝到后面从前往后拷会重叠。那我们不妨从后往前拷,那么想把后面的内容拷贝到前面的思想也是一样的,我们就从前往后拷就行了:

        当源头和目的地不重叠的时候,两种方式都是可以的,总而言之,源头是不能被修改,这样,我们就分为了4处:前不重叠,前重叠,后重叠,后不重叠,为了方便,我们就让前半部分采用从后往前拷贝,后半部分采用从前往后拷贝,那我们就开始编写函数 :

#include<assert.h>
void* my_memmove(void* dest,const void* src, size_t num)
{
	assert(dest && src);//断言
	void* ret = dest;
	if (dest < src)//void*可以比较大小
	{
		//前->后
		for (size_t i = 0; i < num; i++)
		{
			*(char*)dest = *(char*)src;
			dest = (char*)dest + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		//后->前(我们需要让源头指针指向最后一个字节)
		while (num--)
		{
			//num已经变成19
			*((char*)dest + num) = *((char*)src + num);
			//每次num变化,不需要过多操作
		}
	}
	return ret;
}

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

运行结果如下:

memset 

对memset的简单认识 

        memset,如同其名,内存设置,它是将所指定的内存设置成你想设置的,我们往下看:

        我们可以看到,它的第一个参数是想要设置的起始位置,第二个参数是设置的内容,第三个是以字节为单位设置个数,那么我们直接运用它:

#include<string.h>
int main()
{
	char arr[] = "hello world.";
	memset(arr + 6, 'x', 3);
	printf("%s\n", arr);
	return 0;
}

运行结果如下:

        这里需要强调的是第三个参数是以字节为单位设置个数,我们来举一个例子:

#include<string.h>
int main()
{
	int arr[10] = { 0 };
	memset(arr, 1, 40);
	printf("%s\n", arr);
	return 0;
}

进行调试:

在内存中查看:

        这时我们就会发现,想把arr当中的元素全部改成1是无法实现的,我们在内存里可以看到,它是把所有的字节都改成01了。

memcmp 

对memcmp的简单认识 

        memcmpstrcmp类似,memcmp是进行内存块比较,我们可以看到:

        memcmp是以一个字节一个字节进行比较的,参数也是类似的,这里我们还是需要注意一下返回值:

        那么,我们直接使用:

#include<string.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6 };
	int arr2[] = { 1,2,4 };
	int ret = memcmp(arr1, arr2, 12);
	printf("%d ", ret);
	return 0;
}

 运行结果如下:

        memcmp想要模拟实现的话与strncmp是及其相似的,这里小编就不过多演示了。

小结 

        今天就先到这里啦,对于字符串与内存函数的认识到这里就结束了呢,下一次就是结构体啦!不断不说,一些函数的使用,对于我们编写代码的效率的提升大大提高 。但小编的技术还不够,还需要不断地努力,终有一天,会抵达我幻想的那片天空,好啦,我们下次见!

  • 8
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值