分分钟带你了解字符串、内存函数【C语言】

本篇文章介绍一些字符串函数和内存函数,灵活使用这些函数可以提高你的的编程效率,并且可以让代码变得精练,接下来我们来了解一下这些函数,再模拟实现一些常用的函数。

目录

字符串函数

strlen

strcpy / strncpy

strcat /strncat

strcmp /strncmp 

strstr

strtok

strerror

内存操作函数

memcpy

memmove

memset


字符串函数

strlen

strlen函数的使用相信大家已经不陌生了,是用来求字符串长度的一个字符串函数,这里我们主要来模拟实现一下他的功能。

1.计数器法

size_t my_strlen_count(const char* arr)
{
	assert(arr);
	int count = 0;
	const char * p = arr;
	while (*p++ != '\0')
	{
		count++;
	}
	return count;
}

2.指针减指针法

size_t my_strlen_point(const char* arr)
{
	assert(arr);
	const char* start = arr;
	const char* end = arr;
	while (*end != '\0')
	{
		end++;
	}
	return end - start;
}

3.递归法

size_t my_strlen_recursion(const char* arr)
{
	assert(arr);
	const char * p = arr;
	if (*p != '\0')
	{
		return 1 + my_strlen_recursion(p+1); //将p的位置后移一个单位。
	}
	else return 0;
}

使用strlen的注意事项:

1.strlen的返回值是再字符串'\0'前面出现的字符个数(不包含'\0')

2.strlen参数指向的字符串必须以'\0'结束,要不然strlen无法停止或计算有误

3.strlen的函数返回类型的size_t类型(无符号整形)

这里我介绍几种不同定义方式的字符数组情况:

1.char arr[]=“abcdefg”

这种情况下,arr数组中不仅存放了abcdefg这几个字符,而且在g的后面默认放了一个'\0',strlen计算的值正常。

2.char arr[]={'a','b','c','d','e','f','g'};

这种情况,如果使用strlen函数测量字符串会使strlen计算出随机值,因为这个数组中没有具体设定'\0'作为结束标志,所以会出现计算错误的情况。

 如图,这里我们将abcdefg(a的ascii码值为97)存放到0x008FF954的内存中,可是strlen却测出19的长度,因为strlen并没有在源字符串中测到'\0'('\0'的ascii码为0),strlen从0x008FF967的地址处测到了'\0',所以返回值是19。当我们在使用这种初始化的时候,一定不要忘记在字符串的末尾放上一个'\0'!!!

3.char arr123[10] = { 'a','b','c','d','e','f','g'};

我们知道,限定了大小的数组,如果没有初始化,数组会自动给我们初始化为0,所以在我们指定了大小之后,arr123中剩下未被我们初始化的元素会被自动初始化为'\0'。


strcpy / strncpy

strcpy是一个将源头的字符串拷贝到目标字符串的函数,会是实现覆盖的作用,这里我们来看看他的使用。

这个函数的功能还是比较简单的,接下来我们就来模拟实现它吧。

char* my_strcpy(char* destination, const char* source)
{
	assert(destination && source);
	char* start = destination;
	while (*destination++ = *source++)
		;
	return start;
}

这里我们用start存放目的地的起始地址,拷贝完之后将起始地址返回就可以了,这样返回值可以实现函数的链式访问。

这里的我们仍然要注意:

1.源字符串必须以'\0'结尾。

2.strcpy会将源字符串中的'\0'也拷贝到目标空间中。

3.我们要保证目标空间必须足够大,以确保能存放源字符串。

4.目标空间必须可变,所以源字符串不能被const修饰。


strncpy

strncpy的功能和strcpy的功能相差无几,只是多了一个参数count,这个参数表示strncpy拷贝几个字节。

 我们来看一下如何使用:

 这里有一个注意事项:

如果源字符串的长度小于count,则拷贝完源字符串之后,会在目标的后面追加'\0',直到count个。

第10-14个字符全被拷贝为了'\0'


 strcat /strncat

strcat函数就是将一个字符串追加到另一个字符串的后面,strcpy是拷贝一个字符串,而这个函数可以理解为追加一个字符串。我们来看看具体使用。

这个函数也比较简单,接下来我们来看看这个函数是如何实现的吧。

这个函数的实现与strcpy其实差不多,这里给出一点思路。

1.找到目标字符串的中\0'。

2.将源字符串的内容拷贝到以'\0'为起始位置的目标空间.。(这部分就跟strcpy相似了)

char* my_strcat(char* dest, const char* source)
{
	assert(dest && source);
	char *p= dest;
	while (*dest)dest++;
	while (*dest++ = *source++);
	return p;
}

注意事项:

1.源字符串必须以'\0'结尾,这是必须的。

2.目标空间必须要足够大,能容纳下追加的字符串。

3.它会覆盖掉源字符串的'\0',并且将直接的'\0'拷贝过去。


strncat

strncat函数也只是加上了拷贝的数目count,即从源字符串到目标字符串追加多少个字符。

接下来我们来看看如何使用这个函数。


strcmp /strncmp 

strcmp是字符串比较函数,用来比较到出现另一个字符不一样或者一个字符串结束或者num字符全部比较完(注:不是比较字符串长度的)。这个函数的使用还是比较多的,用于我们字符串的匹配等等。

如果字符串1>字符串2  返回大于0的数;

如果字符串1=字符串2  返回0;

如果字符串1<字符串2  返回小于0的数;

接下来看看如何使用strcmp的

(strcmp)字符串比较函数的实现
1.比较*p1是否等于*p2,如果相等则两个指针向后移动,直到都为'\0'则return 0;

2.如果碰到*p1>*p2或*p1<*p2则判断return大于0或小于0的数。

int my_strcmp(const char* p1, const char* p2)
{
	assert(p1 && p2);
	while (*p1 == *p2)
	{
		if (*p1 == '\0')
			return 0;    //两个相等,如果p1或p2为'\0',表示检测到最后一个字符,返回0;
		p1++;
		p2++;
	}
	if (*p1 > *p2)
		return 1;
	else 
		return -1;

strnrcmp的使用与strcmp相似的,可以控制你想比较的数目,与上面几个函数的使用方法一样,这里就不再赘述了。


strstr

strstr函数的功能是查找str2是否为str1的子串,如果找到了就会返回第一次找到地址的地址。如果没有找到,则会返回空指针。

现在我们来使用一下strstr函数

strstr函数的实现思路:

1.使用r1、r2指针进行查找,如果发现*r1和*r2相等,即首元素相同,使用temp指针保存当前r1位置,这时则r1和r2同时向后进行对比,如果发现不同或r1为'\0'则退出,发现*r2为'\0'了,则返回temp的位置。

2.如果发现不同的情况,我们要使r1回退到temp+1的位置再进行查找。

3.如果发现*r1为’\0'还未查找出结果则返回一个NULL,表示r2不为r1的字串。

char* my_strstr(const char* arr1, const char* arr2)
{
	assert(arr1 && arr2);
	const char* r1 = arr1;
	const char* r2 = arr2;
	const char* temp = arr1;
	while (*temp)        
	{
		r1 = temp;
		r2 = arr2;
		while (*r1 && *r2 &&(*r1 == *r2))  //*r1、*r2不等于‘\0',并且*r1和*r2的值相同。
		{
			r1++;
			r2++;
		}
		if (*r2 == '\0')                  //只有当检查到*r2 == '\0'了返回地址
			return (char*)temp;           //temp是const char*类型;
		temp++;
	}
	return NULL;
}

这个函数的实现推荐大家去模拟实现一下,难度也不是太大,还是比较考验对指针的使用的。


strtok

 函数介绍:

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

简单描述它的作用,strtok就是一个可以按照你定义的一些分隔符实现对一串字符串的分隔。

这里sep参数可以是个字符串或是一个分隔符数组。我们可以使用sep中的分隔符来分割str数组中的数据。

这里分三种情况:

①str为非空指针,则strtok会保存第一次出现分隔符的下一个地址,然后将保存的地址作为函数参数返回。

②如果传入的是NULL,则strtok会从上次保存的地址向后查找,查到到分隔符,则会记录下这个分隔符的下一个地址,并将这个地址保存,再将分隔符地址改为'\0',返回保存数据的地址。

③如果没有查找到分隔符了,strtok会返回一个空指针。

这里我们直接来看看strtok函数是如何使用的。

这里除了第一次出现的分隔符没有被置'\0',其余的分隔符全部被置换为了'\0',因为第一次传入的不是空指针,后面的循环传入的全是空指针 ,则strtok会从上次保存的地址开始向后查找,这里我们就可以推测strtock中是存放了一个static修饰的指针来存放地址。

这里我们更不能难发现,strtok会修改掉源字符串中的内容,所以我们通常会将源字符串拷贝一份然后再传入到strtok函数中进行分隔。


strerror

streeor是一个打印错误码的函数。

如果程序发生错误,则会产生一个错误码,每一个错误码会对应一个信息,而strerror的功能就是将错误码转化为错误信息,可以让我们知道出错的原因。

现在我们使用strerror来看看不同的错误码表示的意思分别是什么,比如错误码0、1、2、3。

errno

但是,错误码是动态的,我们一般不会知道发生了错误或是发生了什么类型的错误。这时我们需要知道一个全局变量errno。

errno是错误码的意思,程序发生错误时会产生一个错误码,错误码会被存放到全局变量errno中,这时我们只需要使用strerror函数打印errno就可以知道此时程序发生了什么错误。

这里我们来使用strerror打印errno中的错误信息(errno需要引头文件<errno.h>)。

 这里我们申请一个最大整形的内存,这时内存肯定不会帮我们开辟这么大的空间,所以我们使用strerror打印错误,则会打印出没有足够空间的错误信息。


内存操作函数

在有了上面一些字符串操作函数的了解下,下面的一些内存操作函数其实也就大同小异了,这里就不做太多介绍了,大家知道如何使用和可以模拟实现就问题不大了。 

memcpy

memcpy的使用与strcmp的使用相似,strcpy的操作单位是一串字符,strncpy中拷贝的是我们传入的是字符串中字符的个数,而memcpy操作的是我们传入的字节数,无论是短整型(2)个字节还是整型(4)个字节,memcpy都会按照我们传入的字节数来进行拷贝。

我们来看看如何使用memcpy。

使用起来还是很简单的,下来我们来看看如何实现memcpy这个函数。

void* memcpy(void* r1, const void* r2, size_t count)
{
	assert(r1 && r2);
	void* temp = r1;
	while (count--)
	{
		*(char*)r1 = *(char*)r2;
		r2 = (char*)r2 + 1;
		r1 = (char*)r1 + 1;
	}
	return temp;
}

memmove

如果我们想要将一个字符串中的内容自己拷贝自己,接下来就会发生这样的错误。我们想要的结果无法出现。

 我们发现,如果将arr1中的内容拷贝到arr+2中去,我们想要的是将arr1中20个字节的数据整体拷贝arr1+2的地方,实现数据的移动。

我们预测输出  1 2 1 2 3 4 5 6 7 8 9 10,

而使用memcpy后我们的结果是 1 2 1 2 1 2 1 8 9 10

很明显,这不是我们想要的,我们可以知道,是在memcpy的拷贝中,前段数据将后端数据进行了覆盖,导致拷贝出现了错误,这时我们如果想要实现自身对自身的拷贝,我们可以使用memmove函数。

接下来我们来看看memmove能不能达到我们想要的效果。

结果如上所示,memmove成功地将数据进行了移动,所以,如果有字符串内部的移动拷贝,我们可以交给memmove函数。

接下来,我们了解了memmove函数的作用,我们可以来试着模拟实习一下memmove函数的功能。

首先我们要分析为什么memmove不会覆盖掉我们要拷贝的数据。

有了上面的拷贝分析,结合memcpy的过程,实现memmove应该不会太难。

 下面我们来看如何具体实现

void* my_memmove(void* dest, const void* sou, size_t count)
{
    assert(dest && sou);
	void* temp = dest;
    if (dest == sou)
		return temp;
    if (sou > dest)   
	{
		while (count--)
		{
			*(char*)dest = *(char*)sou;
			sou = (char*)sou + 1;
			dest = (char*)dest + 1;
		}
	}
    else
	{
		while (count--)
		{              //sou+19刚好指向最后一个字节。
			*((char*)dest + count) = *((char*)sou + count);
		}
	}
	return temp;
}


memset

  • s指向要填充的内存块
  • c是要被设置的值。
  • n是要被设置该值的字符数。
  • 返回类型是一个指向存储区s的指针。

接下来我们来看看如何使用:

这个函数还是比较好使用的,通常我们使用只要我们不把要置换的字节数搞错基本没什么错误会出现。

本片博客的介绍就此结束了,本篇介绍的函数有点多,其实我们只用知道是如何使用的就行了,所以这里我也只是地将使用和实现描述了一下,这样有利于我们以后熟练地使用这些函数,提高我们的编程效率,有更多方便快捷、功能强大的函数在以后我会一一介绍给大家。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Brant_zero2022

素材免费分享不求打赏,只求关注

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

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

打赏作者

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

抵扣说明:

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

余额充值