【c语言】字符串函数和内存函数

🌟🌟作者主页ephemerals__

🌟🌟所属专栏C语言

目录

前言

一、字符串函数

1.strlen的使用和模拟实现

2.strcpy的使用和模拟实现

3.strcat的使用和模拟实现

4.strcmp的使用和模拟实现

5.strstr的使用和模拟实现

6.strncmp、strncpy、strncat的使用

7.strtok的使用

二、内存函数

1.memcpy的使用和模拟实现

2.memmove的使用和模拟实现

3.memset的使用

总结


前言

        在编程的过程中,我们经常要对字符串和内存进行各种各样的处理,c语言提供了一系列字符串函数和内存函数,便于我们对字符串或者内存空间进行操作。本篇文章我们就来学习其中的一些函数。

一、字符串函数

1.strlen的使用和模拟实现

        c语言中,strlen函数用于计算一个字符串的长度。它的原型如下:

size_t  strlen ( const char * str ) ;

这里需要注意的是:

1.字符串以\0为结束标志,这个函数返回值是字符串的长度,不包括\0。

2.传参时要传入字符、0串的首元素地址,指向的字符串要以\0为结尾。

3.函数返回值类型是size_t,是一个无符号整数

4.strlen函数使用需要引头文件string.h

接下来我们试着使用一下strlen函数:

#include <stdio.h>
#include<string.h>

int main()
{
	char str[] = "hello";
	int ret = strlen(str);
	printf("%d\n", ret);
	return 0;
}

运行结果:

可以看到,"hello"一共有五个字符,所以字符串的长度就是5。也就是说,这个函数的原理就是通过统计出\0之前的字符个数。这样,我们尝试模拟实现一下它:

方法1:计数器

size_t my_strlen(const char* str)
{
	size_t count = 0;//定义变量统计字符个数
	while (*str != '\0')
	{
		str++;
		count++;
	}
	return count;
}

这里我们定义了一个变量count,负责统计\0之前的字符个数。当指针走到\0处时,循环停止。

方法2:递归

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

方法3:指针减指针

size_t my_strlen(const char* str)
{
	char* p = str;
	while (*str++);//指针持续往后走,知道遇到\0为止
	return str - p - 1;//后置++使得指针多走了一步,多减一个1
}

这里先记录一下首元素地址,然后让str跑到字符串末尾,再减去首元素地址就得到两地址之间的元素个数,也就是字符串长度。

2.strcpy的使用和模拟实现

        strcpy这个函数的作用是将源字符串中的内容拷贝到目标字符串中。它的原型如下:

char* strcpy ( char* dest, const char* src ) ;

这里需要注意:

1.第一个参数是目标字符串的首地址,第二个参数是源字符串的首地址。

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

3.目标字符串的空间必须足够大,能够包含整个源字符串;目标字符串不能是常量字符串。

4.函数返回值是目标字符串的首元素地址。

5.源字符串中的\0也会一同拷贝。

我们尝试使用一下它:

#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "xxxxxxxxxxxx";
	char str2[] = "hello";
	printf("%s\n", strcpy(str1, str2));
	return 0;
}

运行结果:

模拟实现

char* my_strcpy(char* dest,const char* src)
{
	char* ret = dest;//记录首元素地址
	while (*dest++ = *src++);
	return ret;
}

这里我们首先记录了原字符串的首地址,然后循环赋值完成拷贝,最后返回首地址。

3.strcat的使用和模拟实现

        strcat函数的作用是将源字符串内容追加到目标字符串上。原型如下:

char * strcat ( char * dest, const char * src ) ;

要注意的是:

1.第一个参数是目标字符串的首地址,第二个参数是源字符串的首地址。

2.源字符串和目标字符串都必须以\0结尾。

3.目标字符串的空间必须足够大;目标字符串不能是常量字符串。

4.函数返回值是目标字符串的首地址。

接着我们使用一下这个函数:

#include <stdio.h>
#include <string.h>

int main()
{
	char str1[20] = "hello ";
	char str2[] = "world";
	printf("%s\n", strcat(str1, str2));
	return 0;
}

运行结果:

模拟实现

char* my_strcat(char* dest, const char* src)
{
	char* ret = dest;//记录目标字符串首地址
	while (*dest)
	{
		dest++;
	}//使指针指向目标字符串末尾
	while (*dest++ = *src++);//循环追加,直到源字符串末尾
	return ret;
}

4.strcmp的使用和模拟实现

        strcmp函数是一个很重要的函数,它用于比较两个字符串的大小。字符串的比较在很多实例中会使用到,它的比较规则如下:

在了解了比较规则之后,我们来看一下函数原型:

int strcmp ( const char* str1, const char* str2 ) ;

1.参数传入要比较的两个字符串的首地址。

2.两个字符串都要以\0结尾。

3.如果str1大于str2,函数返回正数,否则返回负数,相等则返回0。

我们写一个程序使用该函数,实现两个字符串比较:

#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "abcd";
	char str2[] = "abdc";
	int flag = strcmp(str1, str2);
	if (flag > 0)
	{
		printf("str1大于str2\n");
	}
	else if (flag < 0)
	{
		printf("str1小于str2\n");
	}
	else
	{
		printf("两者相等\n");
	}
	return 0;
}

运行结果:

接着,我们尝试模拟实现strcmp:

int my_strcmp(const char* str1, const char* str2)
{
	while (*str1 == *str2)
	{
		if (*str1 == '\0')//处理出现空字符串或者字符比较到末尾的情况
		{
			return 0;
		}
		str1++;
		str2++;
	}
	return *str1 - *str2;
}

5.strstr的使用和模拟实现

        接下来,我们学习一下strstr函数。strstr函数的作用是判断一个字符串是否是另一个字符串的子字符串(是否为包含关系)

它的原型如下:

char * strstr ( const char * str1, const char * str2) ;

1.两个字符串必须要以\0结尾。

2.函数的返回值是str2在str1中第一次出现的位置,如果没找到,返回空指针。

我们尝试使用这个函数:

#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "123456789";
	char str2[] = "3456";
	printf("%s\n", strstr(str1, str2));
	return 0;
}

运行结果:

可以看到,函数确实找到了第二个字符串在第一个字符串中的位置。接下来我们模拟实现一下它:

char* my_strstr(const char* str1, const char* str2)
{
	char* cur = str1;
	if (*str2 == '\0')//空字符串的情况
	{
		return str1;
	}
	while (*cur != '\0')
	{
		char* s1 = cur;
		char *s2 = str2;
		while (*s1 && *s2 && (*s1 - *s2) == '\0')//遍历相等的部分
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')//遍历结束后如果str2已结束,说明是子串
		{
			return cur;
		}
		cur++;
	}
	return NULL;
}

6.strncmp、strncpy、strncat的使用

        这三个函数是在原来功能的基础之上,限定了比较/拷贝/追加的字符个数。函数的参数部分多了一个num,表示限定的字符个数。举个例子:

#include <stdio.h>
#include <string.h>

int main()
{
	char str1[] = "xxxxxxxxx";
	char str2[] = "hehe";
	strncpy(str1, str2, 2);
	printf("%s\n", str1);
	char str3[20] = "hello ";
	char str4[] = "world";
	strncat(str3, str4, 3);
	printf("%s\n", str3);
	return 0;
}

运行结果:

可以看到,程序根据我们限定的字符个数完成了拷贝和追加。

7.strtok的使用

        strtok函数也叫做字符串分割函数。顾名思义,它的作用就是根据给出的特定字符来分割字符串。下面是它的原型:

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

这里需要注意以下几点:

1.第一个参数是要被分割的字符串,第二个参数是由分隔符组成的字符串。

2.strtok函数会在str中寻找限定的分隔符,遇到分隔符就会在字符串中将其变为\0,完成分割。函数返回上一次分割后\0的下一个字符的地址;如果是第一次分割,则返回字符串首地址。

3.对一个字符串进行多次分割时,需要多次调用该函数:第一次调用时str传入要被分割的字符串,函数会使用静态变量保存被分割的位置,之后调用时str要传入NULL,函数就会从保存的位置开始进行分割。

4.当字符串按照分隔符完成了所有分割之后,就会返回NULL。

我们写代码体现一下它的使用效果:

#include <stdio.h>
#include <string.h>

int main()
{
	char str[] = "123.12.14.1.50";
	char* p = NULL;
	printf("%s\n", str);
	for (p = strtok(str, "."); p != NULL; p = strtok(NULL, "."))
	{
		printf("%s\n", p);
	}
	return 0;
}

运行结果:

可以看到字符串成功被分割成几部分。不难发现,strtok函数可以和for循环结合使用,达到分割字符串的效果

二、内存函数

        在学习了这些字符串函数之后,我们可以发现,它们虽然实用,但是却只能对字符串进行操作。那么是否有一些函数可以让我们对任何类型也进行相似操作呢?它就是内存函数。这些内存函数的使用也需要引头文件string.h

1.memcpy的使用和模拟实现

        memcpy函数也叫做内存拷贝函数,它能够将特定字节的内存值拷贝到其他内存位置中。它的原型:

void * memcpy ( void * destination, const void * source, size_t num );

其中的前两个参数分别表示目标空间和源空间的首地址,第三个参数表示要拷贝的字节数。函数的返回值是目标空间的首地址。我们来使用一下它:

#include <stdio.h>
#include <string.h>

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

运行结果:

可以看到,有五个元素的内容被拷贝到arr2当中。

接下来我们尝试模拟实现

void* my_memcpy(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	while (num--)
	{
		*(char*)dest = *(char*)src;
		(char*)dest += 1;//这里由于强制类型转换是临时的,不能直接写++
		(char*)src += 1;
	}
	return ret;
}

可以看出,我们这里使用了泛型编程的思想,将void*指针强转为char*类型,然后根据给定的字节数一个字节一个字节赋值。

2.memmove的使用和模拟实现

        对于刚才的memcpy函数,如果想要拷贝的两个内存空间之间出现重叠情况,那么函数就无法完成正常的拷贝。而memmove函数就弥补了这个缺陷。原型如下:

void * memmove ( void * destination, const void * source, size_t num );

它的参数含义和返回值和memcpy是完全相同的。我们尝试使用它来拷贝重叠的内存:

#include <stdio.h>
#include <string.h>

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

运行结果:

可以看到,它成功地完成了重叠内存的拷贝。那我们是否可以模拟实现这个函数呢?

我们首先来思考一下,现在有一个数组arr,它的内容如下:

现在我们想要将1,2,3,4,5这五个元素拷贝到3,4,5,6,7的位置上,能否完成呢?

将1赋值到3的位置上,将2赋值到4的位置上,到赋值3的时候,就会发现,此时的3已经被覆盖成1了,这样下去,最终结果就会变成1,2,1,2,1,2,1,8,9,10。

聪明的你肯定会想到,既然从前往后不行,那么我们就从后往前进行拷贝:先将5赋值到7的位置,再将4赋值到6的位置,以此类推...你就会发现结果就是1,2,1,2,3,4,5,8,9,10,说明成功了。

难道所有的情况都可以通过从后往前拷贝的方法赋值成功吗?当我们反过来将3,4,5,6,7拷贝到1,2,3,4,5的位置上,我们就发现从后往前的拷贝就不行了,就要用从前往后拷贝的方式

所以我们就可以得出以下结论在有重叠空间的情况下,如果dest的地址小于src,那么就从前向后拷贝;如果dest的地址大于src,就从后向前拷贝。没有重叠空间时怎样都行。图示:

这里由于右边非重叠空间的部分和中间部分是连续的,所以我们使用从后向前拷贝的方法会更加方便编写代码

代码如下:

void* my_memmove(void* dest, const void* src, size_t num)
{
	void* ret = dest;
	if (dest < src)//从前向后
	{
		while (num--)
		{
			*(char*)dest = *(char*)src;
			(char*)dest += 1;
			(char*)src += 1;
		}
	}
	else//从后向前
	{
		while (num--)
		{
			*((char*)dest + num) = *((char*)src + num);
		}
	}
	return ret;
}

3.memset的使用

        memset函数也叫做内存设置函数,它可以对特定区域的内存全部赋成你想要的值。它的原型如下:

void * memset ( void * ptr, int value, size_t num ) ;

它的第一个参数是要设置的内存空间的首元素地址,第二个参数是要赋的值,第三个参数是内存空间的大小(字节)。

它的返回值是内存空间的首元素地址。

我们来使用一下这个函数:

#include <stdio.h>
#include <string.h>

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

运行结果:

可以看到,前六个字符都被赋值成了'x'。

总结

        本篇文章我们学习了字符串函数和内存函数的相关知识,它们在我们的编程当中十分常见和实用。同时,我们也学会了如何思考问题,解决问题。之后博主会更新数据存储方式相关的内容。如果你觉得博主讲的还不错,就请留下一个小小的赞在走哦,感谢大家的支持❤❤❤

  • 38
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值