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

本文介绍了C语言中的几个重要字符串和内存函数,如strstr用于查找子串,strtok切割字符串,strerror处理错误码,以及memcpy,memmove,memset和memcmp的用法和实现。这些函数对提高编程效率至关重要。
摘要由CSDN通过智能技术生成

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

目录

目录

 函数介绍

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是及其相似的,这里小编就不过多演示了。

小结 

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

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值