【C语言】字符函数和内存操作函数

大家好,我是苏貝,本篇博客带大家了解字符函数和内存操作函数,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️
在这里插入图片描述


目录

  • 一.字符函数
    • 1.1 字符分类函数
    • 1.2 字符转换函数
  • 二.内存操作函数
    • 2.1 memcpy
    • 2.2 memmove
    • 2.3 memset
    • 2.4 memcmp

一.字符函数

1.1 字符分类函数

下面函数的头文件都是<ctype.h>

函数如果他的参数符合下列条件就返回真即非0,不符合则返回0
iscntrl任何控制字符
isspace空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’
isdigit十进制数字 0~9
isxdigit十六进制数字,包括所有十进制数字,小写字母a~ f,大写字母A~ F
islower小写字母a~z
isupper大写字母A~Z
isalpha字母a~ z 或 A~Z
isalnum字母或者数字,a~ z,A~ Z,0~9
ispunct标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph任何图形字符
isprint任何可打印字符,包括图形字符和空白字符

其实字符分类函数很简单,下面就挑两个作为范例
范例1:

int main()
{int ret1 = islower('X');//X不是小写字母,返回0int ret2 = islower('f');//f是小写字母,返回非0的数printf("%d %d", ret1, ret2);return 0;
}

在这里插入图片描述

范例2:

int main()
{int ret1 = isxdigit('a');//a是16进制数字int ret2 = isxdigit('z');//z不是16进制数字,返回0int ret3 = isxdigit('4');//4是16进制数字printf("%d %d %d", ret1, ret2, ret3);return 0;
}

在这里插入图片描述

1.2 字符转换函数

字符转换函数只有两个,分别是tolower和toupper,它们的函数原型为:

int tolower ( int c ); //将大写字母转化为小写字母
int toupper ( int c );//将小写字母转化为大写字母

其中,参数的类型为int,是将字符的ASCII码值传过去,返回的也是字符的ASCII码值,所有返回类型也是int

范例1:

int main()
{int x = toupper('a');printf("%c\n", x);x = tolower(x);printf("%c\n", x);return 0;
}

在这里插入图片描述

范例2:
将数组arr中的字符全都变成小写字母

int main()
{char arr[] = "ABcdeFGhIjk";char* p = arr;while (*p){if (isupper(*p)){*p = tolower(*p);}p++;}printf("%s", arr);return 0;
}

在这里插入图片描述


二.内存操作函数

2.1 memcpy

①函数介绍

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

memcpy函数的功能:从source的位置开始向后复制num个字节的数据到destination的内存位置。返回值:目标空间的起始位置。这与strcpy函数的功能很相似,那为什么我们已经有了strcpy函数还要设计memcpy函数呢?因为strcmp函数只能拷贝字符串,而内存空间可不是只有字符的,所有我们需要能拷贝非字符类型的函数,memcpy函数应运而生

细节:destination和source的类型都为void * :因为设计该函数的程序员不知道用户想要拷贝的类型,所有用void * 来接收所有类型的指针。source被const修饰:源内存块的内容不会被修改。num是指num个字节

注意:
1.这个函数在遇到 ‘\0’ 的时候并不会停下来。
2.如果source和destination有任何的重叠,复制的结果都是未定义的

范例:
把arr2中的前5个整型的数据拷贝放在arr1中

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

在这里插入图片描述

②模拟实现

模拟函数的三个参数没有改变,依旧是只需要源内存块的起始地址和目标空间的起始地址以及要拷贝的字节数。先对dest和src断言,避免它们为空指针。用while循环num次,当num==0时退出循环。接下来就是将src的内容拷贝到dest中。因为src和dest指针都是void * 类型的,所有需要先强制类型转化为char * ,拷贝一次后,两指针都要指向下一个字节,因为强制类型转化是暂时的,所有完成赋值语句后,两指针还是void * 类型,所有为了指向下一个字节,我们需要再对它们强制类型转化为char * 类型,写成dest=(char*)dest + 1;那可以写成(char*)dest++;吗?不能,因为++的优先级高于强制类型转化。那可以写成++(char*)dest;吗?最好不要,有些编译器会报错

void* my_memcpy(void* dest, const void* src, size_t num)
{assert(dest && src);void* ret = dest;while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}return ret;
}

③我们是否可以用所写的模拟函数让下面代码的数组arr从第5个元素开始往后的5个字符作为源内存块,将第3个元素开始往后的5个字符作为目标内存块,从source的位置开始向后复制num个字节的数据到destination的内存位置呢?即将数组arr变为{ 1,2,3,4,3,4,5,6,7,10}

void* my_memcpy(void* dest, const void* src, size_t num)
{void* ret = dest;assert(dest && src);while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}return ret;
}int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };my_memcpy(arr + 4, arr + 2, 20);int i = 0;for (i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}

答案是不能的,因为当程序想将5赋值给元素7时,5已经被3覆盖。同理,6已经被4覆盖,7已经被3覆盖
在这里插入图片描述
我们注意到这次的拷贝的源内存块和目标内存块是重叠的,而我们上面写的模拟函数的源内存块和目标内存块要求是不重叠的,所以使用失败。那我们如何解决源内存块和目标内存块重叠时成功拷贝呢?别急,有一个函数可以解决,那就是memmove函数

补充:其实有一些编译器的memcpy函数可以处理源内存块和目标内存块重叠的情况,但是并非所有,因此遇见两内存块重叠的情况时最好还是选择memmove函数


2.2 memmove

①函数介绍

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

1.和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的,其余都相同
2.如果源空间和目标空间出现重叠,就得使用memmove函数处理

你瞧,memcpy解决不了的问题memmove轻松搞定

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

在这里插入图片描述

②模拟实现
以上面代码的arr数组举例,如果源内存块是从元素3开始的5个元素,目标内存块是从元素5开始的5个元素,此时如果要拷贝,为避免有些重要的值事先被覆盖,我们就要从后往前拷贝,即从源内存块的最后一个字节开始拷贝
在这里插入图片描述

如果源内存块是从元素3开始的5个元素,目标内存块是从元素1开始的5个元素,此时如果要拷贝我们就要从前往后拷贝
在这里插入图片描述

如果源内存块和目标内存块无重叠,那么拷贝既可以从前往后也可以从后往前
在这里插入图片描述

所以我们可以这样想:
1.dest<src时,从前往后拷贝
2.dest>src时,从后往前拷贝

dest<src时,与上面的memcpy模拟实现的思路相同,不再赘述。现在我们来思考如何从后往前拷贝。我们依旧用while循环,循环num次,当num==0时退出循环,while(num)。从后往前拷贝,就是将源内存块的最后一个字节拷贝到目标内存块的最后一个字节,再将源内存块的倒数第二个字节拷贝到目标内存块的倒数第二个字节……本题中,num=20,所以源内存块的最后一个字节的地址是(char*)src+19,目标内存块的最后一个字节的地址是(char*)dest+19。源内存块的倒数第二个字节的地址是(char*)src+18,目标内存块的倒数第二个字节的地址是(char*)dest+18。我们发现,第一次循环的时候19=num(20)-1,经历一次循环,num–=19;第二次循环的时候18=num(19)-1,经历一次循环,num–=18;那我们是不是在while循环的条件中写成while(num–)呢?这样的话每次循环源内存块字节的地址=(char*)src+num,所以写成如下写法

void* my_memmove(char* dest, char* src, size_t num)
{assert(dest && src);void* ret=dest;if (dest < src){while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}else{while (num--){*((char*)dest + num) = *((char*)src + num);}}return ret;
}

2.3 memset

memset是以字节为单位设置内存的。比如我可以让字符数组arr=“hello world"中从第3个元素开始的5个字符都变成字符’x’,即变成"hexxxxxorld”。

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

范例1:

int main()
{char arr[] = "hello world";memset(arr + 2, 'x', 5);printf("%s", arr);return 0;
}

范例2:
下面代码的目的是用memset函数将arr数组中的元素全部改成1,可以做到吗?

int main()
{int arr[5] = { 0 };memset(arr, 1, 20);int i = 0;for (i = 0; i < 5; i++){printf("%d ", arr[i]);}return 0;
}

如果你觉得可以的话,请看看最后的结果吧!为什么不是我们所想的全是1呢?

在这里插入图片描述
经过调试我们可以看到,调用完成memset后,数组arr的每个字节都由0变1,导致每个整型元素都变为0x01010101,所以不能用memset函数将arr数组中的元素全部改成1。所以memset更适用于字符数组
在这里插入图片描述

但是可以将数组中的每个元素都变为0,因为将每个字节都变为0即每个整形元素都为0

int main()
{int arr[5] = { 1,2,3,4,5 };memset(arr, 0, 20);int i = 0;for (i = 0; i < 5; i++){printf("%d ", arr[i]);}return 0;
}

在这里插入图片描述


2.4 memcmp

int memcmp ( const void * ptr1,const void * ptr2,size_t num );

memcmp函数是比较从ptr1和ptr2指针开始的num个字节的大小,如果 * ptr1> * ptr2,返回一个正数;如果 * ptr1== * ptr2,返回0;如果 * ptr1< * ptr2,返回一个负数。依旧是比较以字节为单位进行比较

int main()
{int arr1[] = { 1,2,3,4,5 };//假如是小端存储//01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00int arr2[] = { 1,2,0x11223304 };//01 00 00 00 02 00 00 00 04 33 22 11int ret = memcmp(arr1, arr2, 9);printf("%d", ret);return 0;
}

用memcmp函数时,切记不要拿数组中的元素直接比较,我们要看它们各自的存储情况。本题两个数组在前8个字节都相等,看第9个字节,03<04,所以前面<后面,返回一个负数
在这里插入图片描述


好了,那么本篇博客就到此结束了,如果你觉得本篇博客对你有些帮助,可以给个大大的赞👍吗,感谢看到这里,我们下篇博客见❤️

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

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

相关文章

鸿蒙手表开发之使用adb命令安装线上包

#国庆发生的那些事儿# 鸿蒙手表开发之使用adb命令安装线上包 前言&#xff1a; 由于之前的哥们匆忙离职了&#xff0c;所以鸿蒙手表项目的新版本我临时接过来打包发布&#xff0c;基本上之前没有啥鸿蒙经验&#xff0c;但是一直是做Android开发的&#xff0c;在工作人员的指…

JAVA在线电子病历编辑器源码 B/S架构

电子病历在线制作、管理和使用的一体化电子病历解决方案&#xff0c;通过一体化的设计&#xff0c;提供对住院病人的电子病历书写、保存、修改、打印等功能。电子病历系统将临床医护需要的诊疗资料以符合临床思维的方法展示。建立以病人为中心&#xff0c;以临床诊疗信息为主线…

微信小程序 rpx 转 px

前言 略 rpx 转 px let query wx.createSelectorQuery(); query.selectViewport().boundingClientRect(function(res){let rpx2Px 1 * (res.width/750);console.log("1rpx " rpx2Px "px"); }); query.exec();参考 https://blog.csdn.net/qq_39702…

状态模式:对象状态的变化

欢迎来到设计模式系列的第十七篇文章。在本文中&#xff0c;我们将深入探讨状态模式&#xff0c;这是一种行为型设计模式&#xff0c;用于管理对象的状态以及状态之间的变化。 什么是状态模式&#xff1f; 状态模式是一种允许对象在内部状态发生变化时改变其行为的设计模式。…

数据挖掘实验(二)数据预处理【等深分箱与等宽分箱】

一、分箱平滑的原理 &#xff08;1&#xff09;分箱方法 在分箱前&#xff0c;一定要先排序数据&#xff0c;再将它们分到等深&#xff08;等宽&#xff09;的箱中。 常见的有两种分箱方法&#xff1a;等深分箱和等宽分箱。 等深分箱&#xff1a;按记录数进行分箱&#xff0…

Altium Designer实用系列(二)----PCB绘图小技巧

一、技巧总结 1.1 丝印大小 在导入PCB之后&#xff0c;元器件的丝印一般都是strock font&#xff0c;个人感觉比较大&#xff0c;也不美观&#xff0c;但是一个个修改成true type又比较麻烦。简便方法是使用相似查找全部修改:   此时会选中所有stroke 类型的丝印&#xff…

【Java】微服务——RabbitMQ消息队列(SpringAMQP实现五种消息模型)

目录 1.初识MQ1.1.同步和异步通讯1.1.1.同步通讯1.1.2.异步通讯 1.2.技术对比&#xff1a; 2.快速入门2.1.RabbitMQ消息模型2.4.1.publisher实现2.4.2.consumer实现 2.5.总结 3.SpringAMQP3.1.Basic Queue 简单队列模型3.1.1.消息发送3.1.2.消息接收3.1.3.测试 3.2.WorkQueue3.…

GB/T 7134-2008 浇筑型工业有机玻璃板材检测

非改性浇筑PMMA板材是指甲基丙烯酸甲酯均聚物板材&#xff0c;或者甲基丙烯酸甲酯与丙烯酸酯类或甲基丙烯酸酯类单体的共聚物板材&#xff0c;通过适当的引发剂本体聚合生产。 GB/T 7134-2008浇筑型工业有机玻璃板材测试项目&#xff1a; 测试项目 测试方法 拉伸强度 GB/T …

电脑技巧:推荐一款桌面增强工具AquaSnap(附下载)

下载&#xff1a;飞猫盘&#xff5c;文件加速传输工具&#xff5c;云盘&#xff5c;橘猫旗下新概念云平台&#xff0c;取件码&#xff1a;ZdRW 一、软件介绍 AquaSnap(界面增强软件)是一款功能强大的界面增强软件。这款软件支持屏幕边缘吸附与屏幕分屏即多显示器控制、摇晃窗口…

【golang】go 返回参数 以及go中 裸返

一、Go 返回参数命名 在Golang中&#xff0c;命名返回参数通常称为命名参数。 Golang允许在函数签名或定义中为函数的返回或结果参数指定名称。或者可以说这是函数定义中返回变量的显式命名。基本上&#xff0c;它解决了在return语句中提及变量名称的要求。 通过使用命名返回参…

Linux 守护进程

一 何为守护进程 守护进程&#xff08; Daemon &#xff09;也称为精灵进程&#xff0c;是运行在后台的一种特殊进程&#xff0c;它独立于控制终端并且周期性 地执行某种任务或等待处理某些事情的发生&#xff0c;主要表现为以下两个特点&#xff1a; 长期运行。守护进程是一…

视频号直播弹幕采集

系列文章目录 websocket逆向http拦截websocket拦截视频号直播弹幕采集 系列文章目录前言技术分析分析技术选择前提准备事件分析消息去重用户进房用户发言用户送礼用户点赞用户唯一id前言 很多小伙伴倒在了礼物事件,还有用户唯一标识下。 本篇文章将讲解视频号直播弹幕的获取的…

gitlab登录出现的Invalid login or password问题

前提 我是在一个项目里创建的gitlab账号&#xff0c;想在别的项目里登录或者官网登录发现怎么都登陆不上 原因 在GitLab中&#xff0c;有两种不同的账号类型&#xff1a;项目账号和个人账号&#xff08;官网账号&#xff09;。 项目账号&#xff1a;项目账号是在特定GitLab…

某果的一个小参数分析

分析链接:aHR0cHM6Ly9hcHBsZWlkLmFwcGxlLmNvbS9hY2NvdW50 分析目标&#xff1a;X-Apple-I-Fd-Client-Info 1.在浏览器搜索关键词&#xff0c;打下断点 我们再里面进行搜索&#xff0c;定位到这个位置&#xff0c;可以看到X-Apple-I-FD-Client-Info这个参数等于e&#xff0c;…

【C++设计模式之解释器模式:行为型】分析及示例

简介 解释器模式&#xff08;Interpreter Pattern&#xff09;是一种行为型设计模式&#xff0c;它提供了一种解决问题的方法&#xff0c;通过定义语言的文法规则&#xff0c;解释并执行特定的语言表达式。 解释器模式通过使用表达式和解释器&#xff0c;将文法规则中的句子逐…

python内置模块winreg,以及使用winreg模块获取所有当前windows电脑已安装应用的安装信息

winreg模块 进入系统注册表的方法多种多样,最常见的就是运行窗口输入命令“regedit”,即可进入注册表,而Python的winreg模块可以对注册表进行一系列操作 "winreg"中的各个常量 注册表地址(HKEY_ )常量 winreg.HKEY_CLASSES_ROOT #存储应用和shell的信息 winreg…

用 HTTP 提交数据,基本就这 5 种方式

网页开发中&#xff0c;向服务端提交数据是一个基本功能&#xff0c;工作中会大量用 xhr/fetch 的 api 或者 axios 这种封装了一层的库来做。 可能大家都写过很多 http/https 相关的代码&#xff0c;但是又没有梳理下它们有哪几种呢&#xff1f; 其实通过 http/https 向服务端…

VBox启动失败、Genymotion启动失败、Vagrant迁移

VBox启动失败、Genymotion启动失败、Vagrant迁移 2023.10.9 最新版本vbox7.0.10、Genymotion3.5.0 Vbox启动失败 1、查看日志 Error -610 in supR3HardenedMainInitRuntime! (enmWhat4) Failed to locate ‘vcruntime140.dll’ 日志信息查看方法->找到虚拟机所在位置->…

对于L1正则化和L2正则化的理解

在DL中&#xff0c;L1和L2正则化经常被使用到&#xff0c;因为大于1L的正则化都是凸优化的问题&#xff0c;是个简单问题&#xff0c;可以被解决。 首先说正则的意义&#xff1a; 一切可以缓解过拟合的方法&#xff0c;都可以被叫做正则化 我最开始理解正则化的时候就是看lh…

在 Azure 中开发云原生应用程序:工具和技巧

Azure 中的云原生开发工具 Azure 包含一系列用于云原生应用程序开发的内置工具和服务。这里介绍的服务和工具是很好的入门选择。 发展 Azure 包括两个用于开发和构建云原生应用程序的主要工具&#xff1a;Visual Studio (VS) 和Azure应用服务。 VS 是一个集成开发环境&#…