【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'。

总结

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

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

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

相关文章

springboot3多模块实践

先帖下目录结构&#xff0c;直接在idea里面新建就行&#xff0c;删掉多余的文件 子模块的新建 根目录pom文件&#xff0c;注意modules、packaging&#xff0c;dependencyManagement统一管理依赖&#xff0c;子模块添加依赖的时候就不用加版本号 <?xml version"1.0…

SAP_FICO模块-获利能力段新增特征字段

业务背景&#xff1a; 公司有启用获利能力分析功能&#xff0c;有一个销售订单接口&#xff0c;是通过第三方销售订单管理平台推送数据到SAP的&#xff0c;用户希望对接新增一个编号ID到销售订单上&#xff0c;并且可以用KE24/KE30报表查看显示&#xff1b; 对于我这么一个后勤…

python3获取显示器信息并计算出各个显示器是多少寸

1、将宽度和高度从毫米转换为英寸(1英寸 = 25.4毫米)。 2、使用勾股定理计算对角线长度。 运行这个脚本后,将会得到每个显示器的对角线尺寸(以英寸为单位),从而确定显示器的尺寸。 pip install screeninfofrom screeninfo import get_monitors import mathdef get_displ…

高通Android13 WIFI配置国家码

不同国家&#xff0c;WIFI使用的信道是不同的&#xff0c;2.4G一共有14个信道&#xff0c;中国使用1-13信道&#xff0c;美国则使用1-11信道。因此&#xff0c;我们需要指定WIFI的国家码&#xff0c;来确定WIFI在扫描和连接过程中&#xff0c;可以在哪些信道上进行。 设置国家…

Java面试八股之简述JVM内存结构

简述JVM内存结构 Java虚拟机&#xff08;JVM&#xff09;内存结构主要分为线程私有区域和线程共享区域两大部分&#xff0c;具体组成部分如下&#xff1a; 线程私有区域 程序计数器&#xff08;Program Counter Register&#xff09;&#xff1a; 记录当前线程执行的字节码行…

逻辑整理(光伏发电预测算法部署用的)

def main():while True:if is_true_month(): # 检查是否为每年的1月份、4月份、7月份、10月份的凌晨1&#xff1a;00&#xff0c;判断到minget_new_history_data # 获取最新的3个月左右的历史数据train model # 进行模型训练save model # 保存模型 在之前模型保存的位置直接覆…

三角形法恢复空间点深度

三角形法恢复空间点深度 如下图&#xff0c;以图 I 1 I_1 I1​为参考&#xff0c;图 I 2 I_2 I2​的变换矩阵为 T T T。相机光心为 O 1 O_1 O1​和 O 2 O_2 O2​。在图 I 1 I_1 I1​中有特征点 p 1 p_1 p1​&#xff0c;对应图 I 2 I_2 I2​中有特征点 p 2 p_2 p2​。理论上直…

战略网络优化:网络可观测性的综合方法

在网络成为运营支柱的时代&#xff0c;了解和优化网络性能至关重要。网络可观测性是了解网络性能的关键&#xff0c;它以一种全面、主动的方式超越了传统监控。本文说明了网络可观测性的变革力量&#xff0c;详细介绍了其优势、差异化因素及其在现代网络管理中的关键作用。 什…

vue学习(三)

14.监视属性watch 当被监视的属性发生变化时&#xff0c;回调函数立即调用&#xff0c;进行操作 监视的两种写法&#xff1a;直接配置或者通过vm添加 watch:{isHot:{immediate:true, //首次用到执行handler(newValue,oldValue){console.log("isHot 被修改了",newV…

python如何做报表系统

首先我们安装的python和PyQt5要保持一致&#xff0c;要么都是32位或者都是64位。 下载安装&#xff0c;安装完成之后我们记得要设置环境变量。 一路选择“下一步”就可以了。 安装完成之后我们需要验证是否成功。 pyqt5的安装直接安装就可以的&#xff0c;主要更改环境变量~~\p…

日语 11 12

11. 若者の意識 わかもの  いしき 新作 新作 新作 新作 新作 しんさく 公開 公開 公開 公開 公開 こうかい 映像 映像 映像 映像 映像 えいぞう 人気 人気 人気 人気 人気 にんき 来週 来週 来週 来週 来週 らいしゅう 外国 外国 外国 外国 外…

数据结构之B数

目录 1.概述 2.特点 3.诞生 4.优缺点 4.1.优点 4.2.缺点 5.应用场景 6.C语言中的B树实现例子 7.总结 1.概述 B树&#xff08;B-tree&#xff09;是一种自平衡的树数据结构&#xff0c;广泛应用于数据库和文件系统中&#xff0c;以便高效地进行顺序读取、写入以及查找…

桥式起重机司机精选试题(附答案)

1、【多选题】凡能引起可燃物质燃烧的热能称为着火源&#xff0c;着火源类型有:( )。( ABCD ) A、明火 B、电气火 C、雷电产生的火花 D、化学反应热 2、【多选题】制动器失效的主要原因是:( )。(BCD) A、制动带间隙过小 B、制动带磨损 C、弹簧失效 D、带上有油 3、【多…

vue3.0(十四)内置组件KeepAlive

文章目录 一、KeepAlive是什么1.KeepAlive的props属性2.KeepAlive的生命周期 二、使用场景三、源码四、缓存后如何获取数据 一、KeepAlive是什么 keep-alive是vue中的内置组件&#xff0c;能在组件切换过程中将状态保留在内存中&#xff0c;防止重复渲染DOM keep-alive 包裹动…

短剧app对接广告联盟流量变现开发 搭建

短剧APP对接广告联盟以实现流量变现的开发和搭建是一个综合性的过程&#xff0c;涉及多个关键步骤和要素。以下是一个大致的指南&#xff1a; 确定目标与定位&#xff1a; 明确短剧APP的目标受众是谁&#xff0c;以及其主要定位是什么&#xff0c;例如是提供原创短剧内容&#…

使用 C# 进行面向对象编程:第 9 部分

使用 OOP 的用户活动日志 应用程序背后的关键概念 在这一部分中&#xff0c;我们将使用之前学到的一些 OOP 概念。我们将创建一个小型应用程序。在继续之前&#xff0c;请阅读我的文章user-activity-log-using-C-Sharp-with-sql-server/。在本课程中&#xff0c;我们将再次使…

国内公开数据

以下是一些关于国内政府部门公布的数据或互联网上开放数据的资源&#xff0c;包括CSV、JSON和Parquet格式&#xff1a; 国内政府部门公开数据 中国政府数据开放平台 链接: 数据开放平台概要: 提供来自中国各级政府的公开数据集&#xff0c;数据格式包括CSV、JSON等。 上海市公…

2024年燃气企业负责人和安全管理人员考试题库。

31.使用&#xff08; &#xff09;进行液化天然气(LNG)的输送&#xff0c;对于卸、装车可以缩短卸、装车时间&#xff0c;提高输送效率。 A.低温泵 B.增压器 C.减压器 答案:A 32.液化天然气(LNG)用作调峰气源时&#xff0c;应注意与原燃气的&#xff08; &#xff09;&…

测试人员遇到需求变更 4大处理技巧

测试人员有效的需求变更管理&#xff0c;可以确保即使在需求频繁变化的情况下&#xff0c;测试工作仍然能够覆盖所有必要的功能点&#xff0c;从而保障最终产品的质量。如果没有合理的需求变更处理技巧&#xff0c;可能会造成不必要的返工和重复测试&#xff0c;无法维持项目的…

平安养老险浙江分公司开展防范非法集资宣传,守护群众“钱袋子”

为进一步提高群众对非法集资的防范意识的鉴别能力&#xff0c;近期&#xff0c;平安养老保险股份有限&#xff08;以下简称“平安养老险”&#xff09;浙江分公司以“守住钱袋子、护好幸福家”为宣传主题&#xff0c;深入居民社区、办公职场等公共场所开展的宣传活动。 平安养老…