超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现

作者的码云地址:https://gitee.com/dongtiao-xiewei
后续作者会更新力扣的每日一题系列,原代码会全部上传码云,推荐关注哦,笔芯~

还像更深入地了解c语言?快来订阅作者的c语言进阶专栏!作者承诺本系列不会TJ!预计更新:指针,字符串处理,内存管理,结构体,预处理等等

模拟实现函数的规范性问题我已经在以前的文章讲过了,本文章不再做详细阐述,原文章地址https://blog.csdn.net/weixin_57402822/article/details/119394214?spm=1001.2014.3001.5501

今天为大家带来字符串函数的使用,为了能够帮助大家更加深入的了解C语言字符串函数,会将大部分函数模拟实现一遍,加深大家的理解

字符串以及内存函数

  • strlen函数,求字符串长度的函数
    • 函数模拟实现
  • 字符串函数:长度不受限
    • strcpy
    • strcat
      • 模拟实现
    • strcmp
      • 模拟实现
  • 长度受限的字符串函数
    • strncpy
      • 模拟实现
    • strncmp
    • strncat
  • 字符串查找
    • strstr函数
      • 模拟实现
    • strtok
  • 内存操作函数
    • memcpy
      • 模拟实现
    • memmove
      • 模拟实现

若要使用字符串函数,请引入头文件!

strlen函数,求字符串长度的函数

定义:这是一个库函数,作用是可以求出指定字符串的长度

这是其在cplusplus上的定义

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第1张图片

注意!strlen函数只能识别到字符’\0’才会停止!

错误使用示例

int main()
{
     
	char arr[] = {
      'a','b' };//这里没有添加‘\0’
	printf("%d\n", strlen(arr));
	return 0;
}

所以输出的结果并没有达到我们的预期,函数直到在随机内存中找到’\0’才能停下,所以会输出一个随机值
超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第2张图片
正确使用用例

int main()
{
     
	char arr[] = "ab";//字符串会默认在末尾加上'\0'
	printf("%d\n", strlen(arr));
	return 0;
}

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第3张图片

函数模拟实现

这里介绍三种模拟实现的方式

  1. 引入计数器
  2. 指针-指针
  3. 递归实现

计数器实现

毫无疑问,这是最简单的一种逻辑了,直接数,直到遇到’\0’停止计数

unsigned my_strlen(const char* str)
{
     
	//my_strlen_count
	assert(str);
	unsigned count=0;
	while(*str)
	{
     
		str++;
		count++;
	}
	return count;
}

指针-指针实现

我们可以再定义一个指针变量来往后,寻找,直到找到’\0’为止,利用指针-指针=中间元素的个数来计算出字符串长度

	//my_strlen_ptr
	unsigned my_strlen(const char* str)
{
     
	assert(str);
	char* cur = str;
	while (*cur)
		cur++;
	return cur - str;
}

递归实现

假如有一个字符串"abcd"
我们可以这么想:
1. "abcd"的长度等于"abc"长度+1
2. "abc"长度等于"ab"长度+1
3. "ab"长度等于"a"长度+1

这么一直循环下去,可以轻松的写出我们的递归实现逻辑

unsigned my_strlen(const char* str)
{
     
	//my_strlen_recursion
	if (!(*str))
		return 0;
	else
		return my_strlen(str + 1) + 1;

}

字符串函数:长度不受限

这里介绍一些常用的字符串操作函数

strcpy

我在这篇函数编写规范中已经讲到了这个函数,这里不再阐述
原文地址:https://blog.csdn.net/weixin_57402822/article/details/119394214?spm=1001.2014.3001.5501

strcat

这是cplusplus上的定义

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第4张图片
这个函数可以实现在目标字符后追加源字符串的功能

注意!必须确保源空间足够大,追加的字符串直到’\0’为止

使用示范

int main()
{
     
	//strcat_test
	char arr1[30] = "hello";
	char arr2[] = "world!\0hahahaha";//注意这一条语句
	printf("%s\n", strcat(arr1, arr2));
	return 0;
}

输出结果以’\0’终止

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第5张图片

模拟实现

根据函数的定义,我们需要先找到目标字符串的末尾,再进行追加,其中目标字符串的’\0’将会替代,新函数的’\0’将是源字符串的’\0’

//my_strcat
char* my_strcat(char* dst, const char* src)
{
     
	
	assert(dst && src);
	char* ret = dst;//保存目标字符串的起始地址,方便返回
	while (*dst)
		dst++;//找到目标字符串的末尾
	while (*dst++ = *src++)
		;//开始追加
	return ret;

}

strcmp

这是cplusplus上的定义
超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第6张图片
此函数的比较方式:
会一个字符一个字符的往后比较,直到遇到不相等的字符为止

关于返回值的一点说明

  1. 如果1字符串比2字符串大,则返回一个大于0的值,VS默认返回1,linux上返回两个字符串ASC码差值
  2. 如果1字符串比2字符串小,则返回一个小于0的值,VS默认返回-1,linux上返回两个字符串ASC码差值
  3. 如果1字符串比2字符串相等,则返回一个等于0的值,VS默认返回0

使用示范

int main()
{
     
	//strcmp_test
	char arr1[] = "abcdef";
	char arr2[] = "abq";
	char arr3[] = "ab";
	int ret = strcmp(arr1, arr2);
	int ret1 = strcmp(arr1, arr3);
	printf("%d,%d\n", ret, ret1);
	return 0;
}

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第7张图片

模拟实现

要模拟实现,首先要搞清楚函数的工作原理

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第8张图片
我们也需要一个字符一个字符的进行比较

//my_strcmp
int my_strcmp(const char* s1, const char* s2)
{
     
	assert(s1 && s2);
	while (*s1 == *s2 && *s1 && *s2)//如果字符串相等,应该终止
	{
     
		s1++;
		s2++;//相等就比较下一个
	}
	return *s1 - *s2;//返回asc码差值
}

以上的函数,由于数量不受控制,所以函数不够安全,为了保证函数的安全性,以下引入长度受限的字符串函数

长度受限的字符串函数

以下函数跟上面函数唯一的区别都是加了一个控制操作数量的参数

strncpy

cplusplus上的定义
超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第9张图片
与字符串拷贝函数唯一的区别是规定了拷贝的个数

使用示例

//strncpy_test
int main()
{
     
	char arr1[100] = "hello world!";
	char arr2[] = "xxxxx";
	strncpy(arr1, arr2, 2);//只拷贝两个字符到源字符串中
	printf("%s\n", arr1);
	return 0;
}

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第10张图片

模拟实现

首先需要了解一些细节
需要记录还剩多少字符等待拷贝
若源字符串长度小于num,则自动补’\0’直到num为止

//my_strncpy
char* my_strncpy(char* dst, const char* src, size_t n)
{
     

	assert(dst && src);
	char* ret = dst;
	while (*src && n)
	{
     
		*dst = *src;
		dst++;
		src++;
		n--;//记录已经拷贝了多少字符
	}
	if (n)
	{
     
		*dst = '\0';
		dst++;
		n--;
		//若n还没到0,拷贝'\0'
	}
	return ret;
}

strncmp

与上述函数差不多,这里不再做详细阐述

功能:比较指定长度的字符串

strncat

功能:将指定源字符串长度追加至目标字符串

字符串查找

strstr函数

cplusplus上的定义
超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第11张图片

介绍:此函数会查找将源字符串做为子串后,第一次出现在目标字符串的起始地址,若不存在,返回NULL

使用示例

int main()
{
     
//strstr_test
	char arr1[] = "i am a good student, bit student!";
	char arr2[] = "student";
	char* ret = strstr(arr1, arr2);
	if (ret == NULL)
		printf("none");
	else
		printf("%s\n", ret);
	return 0;
}

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第12张图片

模拟实现

这个函数的实现有点意思,大家听好了!

测试字符

char arr1[]="abbbcdef";
char arr2[]="bbc";

需要在arr1中找到是否有arr2的子串

大家可能会想到这一种办法,就是指定一个指针arr1,若与arr2相等,就开始遍历两个字符比较,不过这种方法在这个用例中不适用

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第13张图片
所以,我们希望做出以下的改进

  1. 需要一个指针来记录开始匹配arr1的位置
  2. 需要一个指针一直指向arr2的起始位置,方便每次的遍历
  3. 需要两个指针在找到第一个相等的字符后,分别开始遍历
  4. 返回可以直接返回第一大点说明超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第14张图片
    的指针
  5. 退出条件,arr1或arr2其中一个到达’\0’

以上思路我们也可以画图解释

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第15张图片
代码实现

//my_strstr
char* my_strstr(const char* s1, const char* s2)
{
     
	assert(s1 && s2);
	if (!(*s2))
	{
     
		return s1;//如果s2是空字符串,直接返回s1
	}
	char* st1;
	char* st2;
	//起始位置
	char* cur = s1;
	//每一次尝试匹配的位置
	//abbbcdef
	//bbc
	while (*cur)
	{
     
		st1 = cur;
		st2 = s2;
		//遍历寻找,cur是否与s2相等
		while (*st1 && *st2 && *st1 == *st2)
		{
     
			st1++;
			st2++;//遍历中
		}
		if (!(*st2))
		{
     
			return cur;//跳出情况一:s2到达末尾
		}
		cur++;
	}
	return NULL;
	//跳出情况二:s2到达末尾
}

strtok

cplusplus定义

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第16张图片
此函数的使用比较复杂

功能:在某一字符串中寻找是否有分隔字符串中的元素,有,则将其分割,并将其置为’\0’

若第一个参数不为NULL,将找到str中第一个标记,并记住相关位置

若第一个参数为NULL,则从保存的标记开始继续往下寻找

若不存在更多的标记,返回NULL

使用示例

//strtok_test

int main()
{
     
	char arr1[] = "[email protected]";
	char set[] = "@.";
	char* ret = NULL;
	for (ret = strtok(arr1, set); ret != NULL; ret = strtok(NULL, set))
	{
     
		//for第一句,初始化ret,strtok会记住分割的位置
		//for第二句,判断ret是否为NULL,不存在可分割的元素才会返回空
		//for第三句,利用strtok的记忆特性,传空指针使用即可
		printf("%s\n", ret);
	}
	return 0;
}

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第17张图片


以上是关于字符串操作的,关于非字符串操作又该怎么进行呢?

就需要用到内存函数了

内存操作函数

memcpy

cplusplus上的定义
超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第18张图片
功能拷贝指定字节数的数据

使用示例

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

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第19张图片

模拟实现

先看看定义,需要用void*为啥?

void*是万能接受指针,可以接受任意类型的数据

但不能进行指针加减和解引用等操作,需要强转,最好转为char方便操作

void* my_memcpy(void* dst, const void* src, size_t count)
{
     
	assert(dst && src);

	void* ret = dst;

	int i = 0;
	for (i = count; i > 0; i--)
	{
     
		*(char*)dst = *(char*)src;
		//dst = (char*)dst + 1;
		//src = (char*)src + 1;
		(char*)dst += 1;
		(char*)src += 1;
	}
	return ret;
}

但是,这个函数不能实现这一种拷贝

int arr[]={1,2,3,4,5,6,7,8,9,0};
arr与arr+2的拷贝

这种重叠的情况,memcpy是未定义的,需要使用memmove函数

memmove

这个函数就是为了改进上面情况设计的功能,这里就不再做详细阐述
可能需要改变一下上述的复制方式
重点讲讲其模拟实现

模拟实现

第一种情形:dst地址小于src的地址

这里从前往后复制没有什么问题

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第20张图片
第二种情形,dst地址大于src的地址

**注意!**这里我们为了避免数据被覆盖,需要从后往前复制了

若任然从前往后复制

超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第21张图片

只能从后往前复制超详细的C进阶教程!(三)字符串及内存函数的使用及其模拟实现_第22张图片
核心思想:防止数据被覆盖!!!!

void* my_memmove(void* dst, const void* src, size_t count)
{
     
	void* ret = dst;
	if (*(char*)dst < *(char*)src)
	{
     
		while (count--)
		{
     
			*(char*)dst = *(char*)src;
			(char*)dst += 1;
			(char*)src += 1;
		}
	}
	else
	{
     
		(char*)dst += count - 1;
		(char*)src += count - 1;

		while (count--)
		{
     
			*(char*)dst = *(char*)src;
			(char*)dst -= 1;
			(char*)src -= 1;
		}
	}
	return ret;
}

你可能感兴趣的:(C语言进阶系列,c语言,c++,函数式编程,字符串,内存管理)