恍然大悟!原来C语言中的字符函数和内存函数是这么用的!!

一、字符函数的介绍及其模拟实现

  我们知道C语言是一门使用非常广泛的编程语言,,它不仅方便程序员进行使用而且对初学者十分友好,但是为什么C语言有这样的特点呢?其主要的原因是C语言标准定义了一系列的库函数,有了这些库函数,程序员可以通过少量的代码完成一个或多个步骤,当我们想要调用相应的库函数时我们只需要包含这个函数所需要的头文件,而今天我们所要介绍的字符操作函数就是一类包含在<string.h>的头文件中,有了这些字符操作函数我们可以对字符串进行诸如:计算长度、字符串拷贝、字符串追加、字符串比较、查找字符串等等操作,这样大大减少了工作量,提高了程序的运行效率。下面我将依次介绍C语言中的字符操作函数。

1.strlen函数

  strlen函数是用来统计字符串长度的,即:\0之前的字符串长度,参数所指向的字符串必须以\0结束,同时,它的返回值是size_t的无符号整型,即:返回值是大于零的,知道了库函数中的strlen的实现原理后我们能不能模拟实现strlen函数呢?答案是肯定的,下面我将通过三种方法模拟实现strlen函数。注:strlen的返回值是是大于零的!!!!!

方法一:遍历字符串,代码如下:

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

分析上述代码,我们在函数中引入了count作为计算字符串长度的变量,当*str指向的内容不为\0时count的值加一,同时str指针向后移动一位,指向字符串的下一个字符,通过这种方法,我们可以很轻松的将字符串的长度求出来。

方法二:指针-指针,代码如下:

int my_strlen(const char* str)
{
	int* p = str;
	assert(str);
	while (*str!='\0')
	{
		str++;
	}
	return str - p;
}

分析上述代码,我们在函数内部创建了一个char*类型的指针p,用于接收字符串首元素的地址,在while循环中,我们让str指针向后走,直到遇到\0为止,此时str指向字符串最后一个元素的地址,通过将最后一个元素的地址与首元素的地址相减我们就可以知道字符串的长度。

方法三:递归的思想,代码如下:

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

分析上述代码,如果字符串首元素是\0那么就返回0,如果字符串首元素不是\0那么我们开始递归,直到找到\0通过这种方法我们可以很轻松的求解字符串的长度。

2.strcpy函数

  strcpy是字符串拷贝函数,通过这个函数我们可以将一个字符串的内容拷贝到另一个可修改的空间内,源字符串必须以\0结束,目标空间必须足够大,能够完整的储存源字符串的内容,返回目标空间的起始位置。那么如何使用strcpy函数呢?请参考以下代码:

int main()
{
	char arr1[10] = { 0 };
	char arr2[] = { "abcdef" };
	strcpy(arr1, arr2);
	printf("%s ", arr1);
	return 0;
}

了解strcpy函数的原理和使用方法后,我们可以不可以模拟实现strcpy函数呢?请看以下代码:

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

分析上述代码,我们通过while循环依次访问源字符串的内容,并将其赋给目标空间,直到将源字符串的内容全部拷贝到目标空间为止,最后返回目标空间的起始位置。

 3.strcat函数

strcat是字符串追加函数,它可以将一个字符串的内容追加给另一个字符串,同时不改变追加字符串的内容,其中,源字符串必须以\0结束,目标字符串中也应该有\0,否则没办法知道追加从哪里开始,目标空间必须足够大,能够容纳源字符串的内容,目标空间必须可修改,但不能自己给自己追加,否则就会导致程序死循环。其用法如以下代码:

int main()
{
	char arr1[20] = "hello ";
	char arr2[] = "world";
	printf("%s\n", strcat(arr1, arr2));

	return 0;
}

知道strcat函数的用法后,我们可以模拟实现一个strcat函数,代码如下:

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

分析上述代码,我们首先通过while循环找到了目标空间最后一个字符的地址,然后再通过一次while循环将源字符串的内容追加在目标空间后面,不难发现,源字符串的内容覆盖了目标空间的\0如果用strcat函数自己给自己追加,程序会因为找不到字符串后面的\0导致程序陷入死循环而崩溃,这也就解释了为什么strcat函数不能自己给自己追加。

4.strcmp函数

  strcmp是字符串比较函数,但比较的不是字符串长度,而是字符串中每个字符对应ASCII码值的大小,C语言标准规定,如果第一个字符串大于第二个字符串,则返回大于0的数字;如果第一个字符串小于第二个字符串,则返回小于0的数字;如果第一个字符串等于第二个字符串,则返回等于0的数字。因此,strcmp函数的返回值是int类型。其用法如以下代码:

int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcd";
	int ret =strcmp(arr1, arr2);
	if (ret < 0)
	{
		printf("小于\n");
	}
	else
		printf("大于等于\n");
	return 0;
}

了解其用法后我们可以模拟实现strcmp函数,其代码如下:

int my_strcmp(const char* str1, const char* str2)
{
	assert(str1 && str2);
	while (*str1 == *str2)
	{
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

分析上述代码,我们通过while循环让字符串中对应位置的元素两两比较,然后依次遍历两个字符串,直到找到不相等的元素,返回他们ASCII值的差值。

5.strncpy,strncat,strncmp函数

  观察上面的三个函数,我们发现,他们只能操作长度不受限制的字符串,即:源字符串中有多少字符它们就操作多少字符,如果目标空间不够大,那么就存在越界访问问题,那么C语言是怎么解决这一问题的呢?C语言对上述三个函数做了改动,并提供了strncpy,strncat,strncmp三个函数。这三个函数使操作的字符串长度受了限制,从而让程序更加安全。

  那么他们是如何工作的呢?翻看C语言标准我们发现,这三个函数多了一个参数size_num参数,通过这个参数我们可以限制他们操作字符的个数,从而让程序更加灵活。

6.strstr函数

  strstr函数是字符串查找函数,其作用是在目标空间中查找源字符串的内容,返回源字符串在目标空间第一次出现的位置,字符串的比较匹配不包含\0字符,但以\0作为结束标志。其用法如以下代码:

 int main()
{
	char arr1[] = "abcdefabcdef";
	char arr2[] = "abcde";
	char* ret = strstr(arr1, arr2);
	if (ret != NULL)
		printf("%s\n", ret);
	else
		printf("找不到\n");

	return 0;
}

  下面我们将模拟实现strstr函数,请看以下代码:

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

	assert(str1 && str2);
	if (*str2 == '\0')
	{
		return (char*)str1;
	}

	while (*cur)
	{
		s1 = cur;
		s2 = str2;
		while (*s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return (char*)cur;
		}
		cur++;
	}
	return NULL;
}

分析上述代码,我们创建了三个指针cur,s1,s2,我们首先判断源字符串是否为\0,如果是,则直接返回源字符串;如果不是则进入while循环,让cur,s1指向目标空间的起始位置,s2指向源字符串的起始位置,如果s1,s2所指向的内容相同则进入while循环遍历目标空间和源字符串的内容直到找到不相同的内容,然后跳出循环,判断s2指向的内容是否为\0如果是则返回cur指针指向的内容并重新进入循环,此时让cur指向的内容向后走一个的单位,通过这样循环往复我们就得到了所需要的字符串,由此我们就模拟实现了strstr函数。

7.strtok函数

  strtok函数是一个字符串分割函数,例如:我们现在有一个字符串zhangsan@163.com,我们想要得到zhangsan,163,com这三个部分,该怎么办呢?于是C语言提供了字符串分割函数:strtok可以很轻松地完成上述内容。它包含两个参数,char*str以及const char*sep;前者是我们想要操作的字符串,后者指向一个字符串,定义了用作分割符的字符集合。

  strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。(注:strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且何以修改)。strtok函数的第一个参数不为NULL,函数将找到str中的第一个标记,strtok函数将保存它在字符串中的位置。strtok函数的第一个参数为空,函数将在同一个字符串中被保存的就开始,查找下一个标记。如果字符串中不存在更多的标记,则返回NULL指针。

其用法如下代码:

int main()
{
	char arr[] = "zhangsan@163.com";
	char* sep = "@,.";
	char* str = NULL;
	for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep))
	{
		printf("%s\n", str);
	}

	return 0;
}

8.strerror函数

  strerror函数可以把参数部分错误码对应的错误信息的字符串地址返回来。在不同的系统和C语言标准库的实现中都规定了一些错误码,一般是放在errno.h这个头文件中说明的,C语言的程序在启动的时候会使用一个全局变量errno来记录当前的错误码,只不过程序启动的时候errno为0,表示没有错误,当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误码,存放在errno中,而一个错误码的数字是整数很难理解是什么意思,所以每一个错误码都是有错误信息的。strerro函数就可以将错误对应的错误信息字符串的地址返回。

二、内存函数的介绍及其模拟实现

  在完成之前的介绍后我们知道通过字符串操作函数,我们可以对字符串进行拷贝,追加,比较等操作,那么对于不是字符串的数据呢,我们还能不能通过库函数实现上述的操作呢?答案依然是肯定的,C语言为我们提供了内存函数让我们能够针对任意类型的数据进行复制、追加、比较等操作,而这些函数和字符串操作函数一样,都包含在string.h这个头文件中。下面我们将依次介绍他们的用法及其模拟实现。

1.memcpy函数

  函数memcpy的参数是void*memcpy(void*destination,const void*source,size_ num),其作用是:从source的位置开始向后复制num个字节的数据到destination指向的内存位置,在遇到\0时并不会停下来,如果source和destination有任何重叠,复制的结果都是未定义的。(注:这里的未定义只是C语言的标准未定义,在x64 vs2022编译环境下memcpy是可以完成复制的)。其用法如以下代码:

int main()
{
	int arr1[10] = { 0 };
	int arr2[] = { 1,2,3,4,5,6,7,8,9,10 };
	memcpy(arr1, arr2, 10 * sizeof(int));
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(arr1 + i));
	}
	
	return 0;
}

 其中memcpy(arr1,arr2,10*sizeof(int))的意思是:将arr2中从首元素向后四十个字节的空间的内容复制放到arr1的空间中。

注:size_num的单位是字节。

了解mempy函数的用法后,我们来模拟实现memcpy函数,代码如下:

void* my_memcpy(void* destination, const void* source, size_t num)
{
	assert(destination && source);
	void* str = destination;
	while (num--)
	{
		*(char*)destination = *(char*)source;
		destination = (char*)destination + 1;
		source = (char*)source + 1;
	}
	return str;
}

分析上述代码,由于我们要操作的数据的个数是num,因此我们循环的次数是num次,所以while循环的条件是num--;由于memcpy的类型是void,为了方便操作,我们将destination与source的类型强制转换成char类型,并将source中的数据依次赋值给destination,然后指针依次向后遍历数组,由此我们就模拟实现了memcpy函数。

2.memmove函数

  memmove函数与memcpy函数十分相似,其主要区别在于memmove处理的原内存块和目标内存块是可以重叠的,如果内存块出现重叠就要使用memmove函数。例如以下代码:

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

	return 0;
}

 其中memmove(arr+2,arr,20)的意思是:将arr中从首元素向后的20个字节的空间复制放到arr中第三个元素向后的五个空间中。

模拟实现memmove函数,其代码如下:

void* my_memmove(void* disnation, const void* source, size_t num)
{
	void* ret = disnation;
	if (disnation <= source || (char*)disnation >= ((char*)source + num))
	{
		while (num--)
		{
			*(char*)disnation = *(char*)source;
			disnation = (char*)disnation + 1;
			source = (char*)source + 1;
		}
	}
	else
	{
		disnation = (char*)disnation +num-1;
		source = (char*)source +num-1;
		while (num--)
		{
			*(char*)disnation = *(char*)source;
			disnation = (char*)disnation - 1;
			source = (char*)source - 1;
		}
	}
	return ret;
}

 分析上述代码,memmove函数的实现分为两种情况,其分界点在于源空间与目标空间起始位置关系,如果目标空间的起始位置大于源空间的起始位置那么就从后往前依次插入;其他情况都为从前往后依次插入。

3.memset函数

  memset函数是用来设置内存的,将内存中的值以字节为单位设置成想要的内容。其用法如下:

int main()
{
	char arr[] = "hello world";
	memset(arr, '*', 6);
	printf(arr);
	
	return 0;
}

 以上就是本文的全部内容,你的点赞支持就是小编坚持下去的动力,希望我的文章能给初学C语言的你提供一定的帮助,谢谢大家的观看!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

爱编码的傅同学

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值