字符串相关的函数和内存块相关函数

news2024/12/26 23:24:45

𝙉𝙞𝙘𝙚!!👏🏻‧✧̣̥̇‧✦👏🏻‧✧̣̥̇‧✦ 👏🏻‧✧̣̥̇:Solitary-walk

      ⸝⋆   ━━━┓
     - 个性标签 - :来于“云”的“羽球人”。 Talk is cheap. Show me the code
┗━━━━━━━  ➴ ⷯ

本人座右铭 :   欲达高峰,必忍其痛;欲戴王冠,必承其重。

👑💎💎👑💎💎👑 
💎💎💎自💎💎💎
💎💎💎信💎💎💎
👑💎💎 💎💎👑    希望在看完我的此篇博客后可以对你有帮助哟

👑👑💎💎💎👑👑   此外,希望各位大佬们在看完后,可以互赞互关一下,看到必回
👑👑👑💎👑👑👑

 字符串相关的函数和内存块相关函数思维导图:


目录

一:strlen( )

二:长度不受限制的字符串函数

        strcpy strcat strcmp

三:长度受限制的字符串函数

       strncpy strncat strncmp

四:字符串查找

       strstr strtok

五:错误信息报告

       strerror

        perror

六:字符分类函数


七:内存函数

memcpy

memmove

memcmp

memset


 一:strlen( )

函数原型:

正确的应用中:strlen()的参数部分必须要有 \0

注意:

1)strlen只统计\0 之前的字符个数

2)strlen的函数返回类型是size_t (对应的占位符是  %u)

 strlen模拟实现

方法1:借助计数的方式

size_t my_strlen(const char* start)
{
	//采用计数器来模拟实现strlen
	int count = 0;
	while (*start )
	{
		count++;
		start++;
	}
	return  count;
}
int main()
{

printf("%u\n",my_strlen("abc"));


}

方法2:借助指针 - 指针 

size_t my_strlen( char* str)
{
	//采用指针 - 指针实现strlen 模拟
	char* p = str;//遍历字符串
	while (*p)
	{
		p++;
	}
	return p - str;
}
int main()
{

printf("%u\n",my_strlen("abcd"));
}
二: 长度不受限制的字符串函数
strcpy

函数原型:

使用注意要点:

1)目标空间足够大并且可以修改

2)源字符串必须有 \0

3)  在拷贝的时候 源字符串的\0也拷贝

 strcpy模拟实现

有了以上的注意点,相信对strcpy的模拟实现咱也是易如反掌

思路分析:

1:定义2个指向目标空间和源字符串的指针

 char* dst   //指向目标空间

char*   s     //指向源字符串

2:一个字符一个字符的拷贝

3:注意最后源字符串的\0也要拷贝过去

char* my_strcpy(char* dst, const char* s)
{
	char* ret = dst;//拷贝完之后返回目标空间起始地址,所以需要先保存一下
	assert(dst);
	assert(s);
	while (*s)
	{
		*dst++ = *s++;//对源字符串非\0之前的拷贝
	}
	//别忘了还有\0没有拷贝
	*dst = *s;
	
	return ret;//返回目标空间起始地址
	

}
int main()
{
	/*
	strcpy(char* dst,const char* sor);使用注意事项
	1:目标空间足够大,源字符串有 \0
	2:目标空间可以修改       const char*dst //err
	3:源字符串的\0也会拷贝过去
	*/
	char a1[20] = {0};
	char a2[11] = {"hello bit"};
	my_strcpy(a1, a2);
	printf("%s\n", a1);
	return 0;
}

 对于上面这个核心代码还可以进行再次优化

简化版之后的
    while (*dst++ = *s++)  
    {
        ;
         
    }

代码分析:

先把*s赋给 *dst并进行后置加加 注意此时是对加加之前的条件进行判断真假

直到 *s == \0 赋给*dst 结束拷贝同时源字符串的\0也已经拷贝了

strcat

函数原型:

 函数模拟实现

1)首先找到目标空间的\0

2)其次是对源字符串的拷贝 (和strcpy模拟实现一样的)

char* my_strcat(char* dst, const char* s)
{
	/*
	先找到目标空间的开始追加的地址(一般是\0)
	开始拷贝
	*/
	char* ret = dst;
	while (*dst++)  //找 \0
	{
		;
	}
	while (*dst++ = *s++)//数据拷贝
	{
		;
	}
	return ret;
}

 是否可以对字符串自己进行追加?

答案:no no no

 分析如下:

假设对字符串 "hello"来进行追加

指针s,dst初始位置

经过依次拷贝之后

通过画图我们知道,此时\0已经被覆盖,这就会导致指针s一直找不到从而造成死循环

strcmp

 strcmp函数介绍:

1)比较对应位置上字符的ASCII码值(a-z的ASCII码值依次增加)

2)函数的返回类型:int 

 strcmp模拟实现

分析:假设比较字符串str1 ,str2

int my_strcmp(const char* s1,const char* s2)
{
	while (*s1 && *s2)
	{
		if (*s1 != *s2)
			break;
		s1++, s2++;//继续比较下一对字符
	}
	return *s1 - *s2;
		
}
三:长度受限制的字符串函数(就是在原来功能的基础上加上了字数的限制)
strncpy

注意当指定拷贝的个数大于源字符串的内容时,会自动补加\0

strncat

 strncat可不像strncpyhan函数一样当指定追加的字符个数大于源字符串的内容时,会自动补加\0

strncmp

注意:对num的传参决定了这个函数返回值

str1: abcd

str2 : abd

当num == 3;返回小于0

当num == 2;返回等于0

当num == 4;返回小于0

四:字符串查找
       strstr

strstr  功能:在一个字符串中查找一个子字符串并返回这个子字符串第一次出现的位置

当 *cp = a时,显然是不匹配的,所以cp指向下一个字符 b,

此时是有一个字符可以匹配上,此时s2++,若继续让cp++,当匹配成功时,cp的初始位置是无法找到的,所以设置一个变量s1,s1++,来到下一个字符b的位置 s1,s2指向的字符相等,此时s1++,s2++二者指向的字符不相等,所以此时cp++指向 

同时s1也指向这里,s1 == s2 ,继续 s1++,s2 ++,

s1 == s2,继续 s1++,s2 ++,

这时候内嵌的while循环结束,执行

    if (*s2 == '\0') 
            return cp;

char* my_strstr( char* str1,  char* str2)
{
	char* cp = str1;
	while (*cp)
	{
		char* s1 = cp;
		char* s2 = str2;

		while (*s2 && *s1 && *s1 == *s2)  // 处理对应位置相等
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0') 
			return cp;
		cp++;
	}
	//来到这:没有找到
	return NULL;


}
 strtok

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

1)sep参数是个字符串,定义了用作分隔符的字符集合

  第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标 记。

2)strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容 并且可修改。)

3)strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置(会把这个标记换成\0)

4)strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标 记。 

5)如果字符串中不存在更多的标记,则返回 NULL 指针。 

 strtok函数的使用

solitary@walk.at.123#99 对这个字符串进行提取

int main()
{
	char a1[] = "solitary@walk.at.123#999";
	char copy[50];//对a1临时拷贝
	strcpy(copy, a1);
	char sep[] = "#@.";//分隔符集合
	char* ret = strtok(copy, sep);
	printf("%s\n", ret);

 ret = strtok(NULL, sep);
	printf("%s\n", ret);

 ret = strtok(NULL, sep);
	printf("%s\n", ret);

ret = strtok(NULL, sep);
	printf("%s\n", ret);

	ret = strtok(NULL, sep);
	printf("%s\n", ret);

	return 0;
}

 不知道各位有没有这样的问题:对于这个字符串的具体内容我是知道的,我直接进行这种暴力的打印就可以,那要是这个字符串很长很长,你是否又知道需要具体打印多少次呢?

这里我们借助循环就可以实现,只不过需要我们对strtok这个函数非常的了解

可能有的老铁就会说:我们不是一般借助循环来进行次数的判断嘛。这就说明了我们见的少了吧

当字符串不在存在标记时,这个函数返回NULL  :依据这个点我们作为循环的判断条件

int main()
{
	char a1[] = "solitary@walk.at.123#999";
	char copy[50];//对a1临时拷贝
	strcpy(copy, a1);
	char sep[] = "#@.";//分隔符集合
	char* ret = strtok(copy, sep);
	
	//借助循环来实现字段的打印
	for (ret; ret != NULL; ret = strtok(NULL, sep))
	{
		printf("%s\n", ret);
	}

	return 0;
}
五:错误信息报告
       strerror

strerror函数通常用于处理发生在系统调用库函数(记住不是自己编写 的函数)出现的错误,该函数会返回一个指向错误消息字符串的指针

当库函数调用失败的时候,strerror函数会把错误码记录到errno这个变量中

errno是C语言的一个全局变量

 

 

      perror

perror这个函数功能和strerror函数功能是一样的:都是打印错误码对应的错误信息

perror这个错误码也是从errno这个全局变量里面拿到的

 

六:字符分类函数

七:内存函数
     memcpy

memcpy的前2个参数都是 vod*类型:因为内存块里存放的数据你不知道是什么类型

第三个参数:拷贝源数据总的字节数

  memcp的模拟实现分析:

1)对数据进行一个字节一个字节的拷贝

2)注意:对void* 类型指针不能直接解引用或者是加1操作

3)对指针强转具有临时性

4)对于内存块重叠的拷贝是不可以的(严格意义来说),一般涉及内存块重叠交给memmove函数

#include<stdio.h>
#include<string.h>
void* my_memcpy(void* dst, const void* sor, size_t num)
{
	char* ret = dst;//对目标空间起始地址保留
	while (num--)
	{
		//一个字节的拷贝
		*(char*) dst = *(char*)sor; 
		//dst++, sor++;  // err 强转具有临时性
		//(char*)dst++;  // err
		dst = (char*)dst + 1;
		sor = (char*)sor + 1;
	}
	return ret;
}
int main()
{
	char a1[20] = "xxxxxxxxxxxxxxxx";
	char a2[] = "yyyyy";
	my_memcpy(a1, a2, 4);
	printf("%s\n",a1);
	return 0;
}
          memmove

memmove模拟实现的分析

1)拷贝:从前往后拷贝还是从后往前拷贝

   若是从后往前拷贝  7拷贝到5的位置, 6拷贝到4的位置  ,原来的5已经被覆盖掉

   从前往后拷贝:5拷贝到3 的位置, 6拷贝到4的位置,7拷贝到5 的位置,刚好解决了数据覆盖的问题

注意看,此时  dst 的起始位置 要 > sor的起始位置 ,若果要是从前往后拷贝会不会出现问题

从前往后拷贝:3拷贝到5 的位置,4拷贝到6 的位置,原来5 的已经被覆盖掉了

从后往前拷贝:5拷贝到7 的位置,4拷贝到6 的位置,3拷贝到5的位置刚刚好解决了数据覆盖的问题

此时无论从前往后还是从后往前拷贝都会出现数据覆盖问题

不知道聪明的你,是否已经注意到一个问题就是到底从前往后还是从后往前拷贝其实是取决于dst 与sor的起始相对位置

对于这个函数的模拟其实是有2种方法实现

方法1:

dst < sor  从前往后拷贝

dst >= sor && dst <= sor+num  从后往前拷贝

dst > sor+num  从前往后拷贝

方法2:

dst < sor  从前往后拷贝

dst >= sor   从后往前拷贝

2) 从前往后拷贝的逻辑(这个就和memcpy模拟实现一样的)

    while (num--) 
        {
            //从前往后拷贝  ,和memcpy模拟实现一样
            //一个字节一个字节拷贝
            *(char*)dst = *(char*)sor;
            dst = (char*)dst + 1;
            sor = (char*)sor + 1;
        }

3)从后往前拷贝的逻辑

这里我们需要考虑如何拿到5这个数据对应在最后一个字节

 

 注意:num代表要拷贝字节的总数目

我知道sor+num 是从sor起始位置 跳过num个字节,对于上面的图而言就是指向6 的起始位置,sor+num-1不就是指向原内存块的最后一个字节嘛

代码逻辑: 
//先找到源内存块的最后一个字节  跳过num-1个字节指向最后一个字节
		while (num -- )  //先用后减减 (num不为0 ; 进入下面的同时-1)
		{

			*((char*)dst +num)= *((char*) sor+num); 
			
		}

这里借助方法2来实现模拟


void* my_memmove(void* dst, const void* sor, size_t num)
{
	char* ret = dst;

	assert(dst && sor);
	// 从前往后还是从后往前取余 dst 与 sor 起始位置谁比较在前
	if (dst > sor) 
	{
		//从后往前拷贝,避免数据覆盖
		//先找到源内存块的最后一个字节  跳过num-1个字节指向最后一个字节
		while (num -- )  //先用后减减 (num不为0 ; 进入下面的同时-1)
		{

			*((char*)dst +num)= *((char*) sor+num); 
			
		}
	}
	else
	{
		while (num--) 
		{
			//从前往后拷贝  ,和memcpy模拟实现一样
			//一个字节一个字节拷贝
			*(char*)dst = *(char*)sor;
			dst = (char*)dst + 1;
			sor = (char*)sor + 1;
		}
	}
	return ret;
}
 科普一下:对于内存块数据重叠拷贝按理说memmove就可以实现,但是当我们在VS的编译器来测试的时候,也能实现对 内存块数据重叠拷贝  

注意并不是所有 的编译器在调用标准库的memcpy都能实现内存块数据重叠拷贝,具体情况还是取决于编译器

memcmp
memmcmp函数原型:
int memcmp ( const void * ptr1, const void * ptr2, size_t num );

参数介绍:

1)ptr1 和ptr2  分别指向各自对应内存块的起始地址

2) num : ptr1 和ptr2 指向内存块 的前个num字节进行比较

注意啦,友友们:这里的参数num(要比较的字节数) 和函数strncmp参数的num含义可不同,后者代表:2个字符串要比较的字符个数

还是惯例,接下来我们进行memcmp的模拟实现

 memcmp函数模拟实现
int my_memcmp(const void* str1, const void* str2, size_t num)
{
	assert(str1 && str2);
	//注意memcmp比较的过程是:;一个字节一个字节的比较(是对内存里的数据)
	while (num--)
	{
		/*if (*(char*)str1 > *(char*)str2)
			return 1;
		else if (*(char*)str1 < *(char*)str2)
			return -1;*/
		if ((*(char*)str1 == *(char*)str2))
		{
			
			str1 = (char*)str1 + 1;
			str2 = (char*)str2 + 1;
		}
		else
			return *(char*)str1 - *(char*)str2;
	}
	return *(char*)str1 - *(char*)str2;
}
int main()
{
	/*int a1[] = { 1,8,3,4,5,6,7,8,9 };
	int a2[] = { 1,2,12 };*/
	char a1[] = "abcd";
	char a2[] = "abmnj";


	int ret = memcmp(a1, a2, 2); //注意这里memcmp的第三个参数num表示要比较的前num个字节数而不是个数
	printf("%d", ret);
	return 0;
}

memset

    memset函数原型:  void * memset ( void * ptr, int value, size_t num );

    ptr:起始地址

    value:要设置的值  

    num:要设置的字节数

1)这个函数对内存块的设置,所以说value我们多数传参为字符(在C语言里,字符的本质也是数值)

2)对memset 模拟实现的关键也是在于第二参数

 我们需要考虑如何将int 类型数据转换成char 类型数据???

对于这个问题我暂时有2 个解决方案(目前理论以及实践上都可以说的过去)

方法1:对第二个参数进行改变:char val

方法2:第二个参数依然是int 类型

把int 类型数据转换成char 类型数据:对val % 256

        *((char*)p) = val % 256;

 对应完整代码

 方法2代码:

方法1代码:

 


结语:

以上就是关于字符串以及内存块相关函数的share,相信到这个,各位老铁们的知识量已经大增了吧,这些函数在我们日常的模拟题中也会涉及到,若是到时候用上,那岂不是很香嘛。在此我也衷心希望各位铁子们能够收获满满。

那话不多,你懂的,咱接下来走起!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1415367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

概念抽取:构建认知基础的关键步骤

目录 前言1 概念抽取任务定义1.1 概念知识图谱的关系定义1.2 实体与概念的紧密关联1.3 多样的概念关系 2 概念在认知中的重要角色2.1 语言理解的基础2.2 上下位关系的深化理解 3 概念抽取方法3.1 基于模板的抽取3.2 基于百科的抽取3.3 基于机器学习的方法 4 应用4.1 自然语言理…

ENVI下基于知识决策树提取地表覆盖信息

基于知识的决策树分类是基于遥感影像数据及其他空间数据,通过专家经验总结、简单的数学统计和归纳方法等,获得分类规则并进行遥感分类。分类规则易于理解,分类过程也符合人的认知过程,最大的特点是利用的多源数据。 决策树分类主要的工作是获取规则,本文介绍使用CART算法…

C++力扣题目62--不同路径 63--不同路径II 343--整数拆分 96--不同的二叉搜索树

62.不同路径 力扣题目链接(opens new window) 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。…

leetcode hot100岛屿数量

本题中要求统计岛屿数量&#xff08;数字1的上下左右均为1&#xff0c;则是连续的1&#xff0c;称为一块岛屿&#xff09;。那么这种类型题都是需要依靠深度优先搜索&#xff08;DFS&#xff09;或者广度优先搜索&#xff08;BFS&#xff09;来做的。这两种搜索&#xff0c;实际…

DS:带头双向循环链表的实现(超详细!!)

创作不易&#xff0c;友友们给个三连吧&#xff01;&#xff01;&#xff01; 博主的上篇文章介绍了链表&#xff0c;以及单链表的实现。 单链表的实现&#xff08;超详细&#xff01;&#xff01;&#xff09; 其实单链表的全称叫做不带头单向不循环链表&#xff0c;本文…

uni-app 接口封装,token过期,自动获取最新的token

一、文件路径截图 2、新建一个文件app.js let hosthttp://172.16.192.40:8083/jeecg-boot/ //本地接口 let myApi {login: ${host}wx/wxUser/login, //登录 } module.exports myApi 3、新建一个文件request.js import myApi from /utils/app.js; export const r…

Linux ---- Shell编程之函数与数组

目录 一、函数 1、函数的基本格式 2、查看函数列表 3、删除函数 4、函数的传参数 5、函数返回值 实验&#xff1a; 1.判断输入的ip地址正确与否 2. 判断是否为管理员用户登录 6、函数变量的作用范围 7、函数递归&#xff08;重要、难点&#xff09; 实验&#xff1…

P1024 [NOIP2001 提高组] 一元三次方程求解————C++

目录 [NOIP2001 提高组] 一元三次方程求解题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示 解题思路Code运行结果 [NOIP2001 提高组] 一元三次方程求解 题目描述 有形如&#xff1a; a x 3 b x 2 c x d 0 a x^3 b x^2 c x d 0 ax3bx2cxd0 这样的一个一元…

【2024-01-27可用】NVM安装太慢,镜像地址失效

安装nvm时&#xff0c; Could not retrieve https://registry.npm.taobao.org/latest/SHASUMS256.txt. 解决如下 ### 具体配置 安装路径 root: D:\Program Files\nvm path: D:\Program Files\nodejs镜像地址 node_mirror: https://npmmirror.com/mirrors/node/ npm_mirror:…

STL容器大总结区分(上)

如图所示 ,按大小说明其重要性 那就先说两个最重要的: vector---数组 list-----链表 vector 基本概念 功能&#xff1a; vector 数据结构和 数组非常 相似 &#xff0c;也称为 单端数组 vector 与普通数组区别&#xff1a; 不同之处在于数组是静态空间&…

vue3添加pinia

概述&#xff1a;Pinia 是一个专为 Vue.js 开发的状态管理库。Vue.js 是一个流行的 JavaScript 框架&#xff0c;用于构建用户界面。Pinia 旨在提供一个简单、灵活且性能高效的状态管理方案&#xff0c;使开发者能够更容易地管理应用的状态。 以下是 Pinia 的一些特点和概念&a…

在 React 组件中使用 JSON 数据文件,怎么去读取请求数据呢?

要在 React 组件中使用 JSON 数据&#xff0c;有多种方法。 常用的有以下几种方法&#xff1a; 1、直接将 JSON 数据作为一个变量或常量引入组件中。 import jsonData from ./data.json;function MyComponent() {return (<div><h1>{jsonData.title}</h1>&…

Vue3中ElementPlus组件二次封装,实现原组件属性、插槽、事件监听、方法的透传

本文以el-input组件为例&#xff0c;其它组件类似用法。 一、解决数据绑定问题 封装组件的第一步&#xff0c;要解决的就是数据绑定的问题&#xff0c;由于prop数据流是单向传递的&#xff0c;数据只能从父流向子&#xff0c;子想改父只能通过提交emit事件通知父修改。 父&a…

第十八讲_HarmonyOS应用开发实战(实现电商首页)

HarmonyOS应用开发实战&#xff08;实现电商首页&#xff09; 1. 项目涉及知识点罗列2. 项目目录结构介绍3. 最终的效果图4. 部分源码展示 1. 项目涉及知识点罗列 掌握HUAWEI DevEco Studio开发工具掌握创建HarmonyOS应用工程掌握ArkUI自定义组件掌握Entry、Component、Builde…

Leetcode—2942. 查找包含给定字符的单词【简单】

2023每日刷题&#xff08;一零一&#xff09; Leetcode—2942. 查找包含给定字符的单词 实现代码 class Solution { public:vector<int> findWordsContaining(vector<string>& words, char x) {vector<int> ans;for(int i 0; i < words.size(); i)…

JDK8新特性:Stream

Stream 认识Stream 也叫Stream流&#xff0c;是jdk8开始新增的一套API&#xff08;java.util.stream.*&#xff09;&#xff0c;可以用于操作集合或者数组的数据。优势&#xff1a;Stream流大量的结合了Lambda的语法风格来编程&#xff0c;提供了一种更强大&#xff0c;更加简…

TCS34725使用记录

TCS34725使用记录 1、IIC通信 1、tcs34725硬件通信采用标准的IIC协议&#xff1b; 2、在寄存器读写上需要注意一下&#xff0c;在读写寄存时&#xff0c;需要将地址最高位置1&#xff1b; I2C_SendByte(reg|0x80);//一般的iic操作寄存器都是直接传入reg 2、配置与数据读取 …

简单介绍----微服务和Spring Cloud

微服务和SpringCloud 1.什么是微服务&#xff1f; 微服务是将一个大型的、单一的应用程序拆分成多个小型服务&#xff0c;每个服务负责实现特定的业务功能&#xff0c;并且可以通过网络通信与其他服务通信。微服务的优点是开发更灵活&#xff08;不同的微服务可以使用不同的开…

HTML 曲线图表特效

下面是代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>基于 ApexCharts 的 HTML5 曲线图表DEMO演示</title><style> body {background: #000524; }#wrapper {padding-top: 20px;background: #000524;b…

基于InceptionV2/InceptionV3/Xception不同参数量级模型开发构建中草药图像识别分析系统,实验量化对比不同模型性能

最近正好项目中在做一些识别相关的内容&#xff0c;我也陆陆续续写了一些实验性质的博文用于对自己使用过的模型进行真实数据的评测对比分析&#xff0c;感兴趣的话可以自行移步阅读即可&#xff1a; 《移动端轻量级模型开发谁更胜一筹&#xff0c;efficientnet、mobilenetv2、…