【字符函数和字符串函数】

目录

  1. 字符分类函数
  2. 字符转换函数
  3. strlen的使用和模拟实现
  4. strcpy的使用和模拟实现
  5. strcat的使用和模拟实现
  6. strcmp的使用和模拟实现
  7. strncpy的使用
  8. strncat的使用
  9. strncmp的使用
  10. strstr的使用和模拟实现
  11. strtok的使用
  12. strerror的使用
  13. 字符串匹配优化-KMP算法

在编程的过程中,经常要处理字符和字符串,为了方便操作字符和字符串,c语言标准库提供了一系列库函数,接下来学习一下这些函数

1. 字符分类函数

c语言有一系列函数专门做字符分类的,也就是一个字符属于什么类型的字符
这些函数都需要一个头文件ctype.h

函数如果它的参数符合下列条件就返回真
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任何可打印字符,包括图形字符和空白字符

控制字符说明,当c在0x00-0x1F之间或等于0x7F(DEL)时,返回非零值,否则返回零

这些函数的使用方法类似,只讲解一个:

int islower (int c);

islower函数能够判断参数部分的c是否是小写字母
通过返回值来说明是否是小写字母,如果是小写字母就返回非0的整数,如果不是小写字母,则返回0

练习

写一个代码,将字符串中的小写字母转大写,其他字符不变

int i = 0;
char str[] = "test string.\n";
char c;while (str[i]) {c = str[i];if (islower(c)) {c -= 32;		}putchar(c);i++;
}

输出

在这里插入图片描述

2. 字符转换函数

c语言提供了两个字符转换函数:

int tolower ( int c ); //将参数的大写字母转小写
int toupper ( int c ); //将参数的小写字母转大写

有了转换函数,就可以用这种方法实现上面的功能

int i = 0;
char str[] = "test string.\n";
char c;while (str[i]) {c = str[i];if (islower(c)) {c = toupper(c);		}putchar(c);i++;
}

3. strlen的使用和模拟实现

函数原型

size_t strlen ( const char * str);

  • 字符串以 \0作为结束标志,strlen函数返回\0之前字符个数,不包含\0
  • 参数指向的字符串必须以\0结束
  • 函数的返回值是size_t,是无符号的
     如 strlen(3)-strlen(6),一个是3的长度,一个是6,减下来本来等于-3,但因为返回值是无符号的,在没有其他变量接收的时候,会转化为一个无符号的数\
  • 使用包含头文件

使用

 const char* str1 = "abcdef";const char* str2 = "bbb";if(strlen(str2)-strlen(str1)>0){printf("str2>str1\n");} else{printf("srt1>str2\n");}

上面由于strlen返回的是无符号,所以短的减长的会是一个无符号,大于0,输出str2>str1

输出:

在这里插入图片描述

strlen的模拟实现
方式1,计数器:

//计数器的方式
int my_strlen(const char* str) {assert(str);int count = 0;while (*str) {count++;str++;}return count;
}

方式2,指针:

//指针-指针的方式
int mystrlen(const char* str) {assert(str);char* p = str;while (*p) {p++;}return p - str;
}

方式3 递归:

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

在这里插入图片描述
指针每次+1,往右挪一位,字符串的长度就会减1

4. strcpy函数的使用和模拟实现

char* strcpy ( char* destination, const char* source );

复制源指针的字符串到目标指针,遇到空字符结束

  • 源字符串必须以\0结束
  • 会将源字符串中的 \0 拷贝到目标空间
  • 目标空间必须足够大,以确保能存放源字符串,不然报错
  • 目标空间必须可修改

模拟实现

char* my_strcpy(char* destination,const char* source) {char* ret = destination;assert(destination);assert(source);while (*source) {*destination = *source;destination++;source++;}*destination = '\0';return ret;
}

strcat的使用和模拟实现

拼接源字符串到目标字符串,目标字符串的结尾空字符被新字符第一个字符覆盖

  • 原字符串必须以\0结束
  • 目标字符串也得有 \0 , 否则不知道追加从哪开始
  • 目标空间必须足够大,能容纳下两个字符串
  • 目标空间可修改
  • 字符串自己给自己追加

模拟实现

char* my_strcat(char* dest,const char* src) {char* ret = dest;assert(dest);assert(src);while (*dest) {dest++;}while (*src) {*dest = *src;dest++;src++;}*dest='\0';return ret;
}

6. strcmp的使用和模拟实现

这个函数开始比较每个字符串的第一个字符,如果相等继续比较下一对,直到不同或者空字符结束

  • 第一个字符串大于第二个字符串,返回大于0的数字
  • 第一个字符串等于第二个字符串,返回0
  • 第一个字符串小于第二个字符串,返回小于0的数组

模拟实现

int my_strcmp(const char* str1,const char* str2) {assert(str1);assert(str2);while (*str1 == *str2) {if (*str1 == '\0') {return 0;}str1++;str2++;}return *str1 - *str2;
}

7. strncpy函数的使用

char* strncpy ( char* destination, const char* source, size_t num );

拷贝原字符串前num个字符到目标字符串,如果在复制num个字符后,原字符串的末尾被找到,目标指针用0填充直到写入num个字符

  • 从原字符串拷贝num个字符到目标空间
  • 如果原字符串长度小于num,则拷贝完原字符串后,在目标后边追加0,直到num个

使用

   char string[32] = "Cats are nice";strncpy( string, "Dog", 3 );printf ( "After:  %s\n", string );

输出:

在这里插入图片描述
这个函数拷贝时不会在末尾自动加\0

8. strncat的使用

char* strncat ( char* destination, const char* source, size_t num );

  • 将source指向字符串的前num个字符追加到destination的末尾,再追加一个\0
  • 如果source的字符串长度小于num,只会将到\0的内容追加到末尾
 char str1[20];char str2[20];strcpy (str1,"To be ");strcpy (str2,"or not to be");strncat (str1, str2, 6);printf("%s\n", str1);

拼接str2前6个字符到str1末尾,也就是or not

输出:

在这里插入图片描述

9. strncmp的使用

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

比较str1和str2的前num个字符,如果相等继续往后比较,最多比较num个,如果提前发现不一样就提前结束,如果num个字符都相等,就返回0

在这里插入图片描述

10. strstr的使用和模拟实现

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

  • 函数返回字符串str2在字符串str1中第一次出现的位置,不是子串返回NULL
  • 字符串的比较匹配不包含\0.以\0作为结束标志
 char str[] ="This is a simple string";char * pch;pch = strstr (str,"simple");printf("%p\n", pch );

输出:

在这里插入图片描述模拟实现

char* my_strstr(const char* str1, const char* str2) {char* start1 = (char*)str1;char* s1, *s2;if (!*str2)return ((char*) str1);while (*start1) {s1 = start1;s2 = (char*) str2;while (!(*s2-*s1) && *s1 && *s2) {s1++;s2++;if (!*s2) {return start1;}}start1++;}return NULL;
}

11. strtok的使用

  • sep参数指向一个字符串,定义了用作分隔符的字符集合
  • 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记
  • strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针 ( strtok函数会改变被操作的字符串,所以在使用的时候切分的字符串一般都是临时拷贝的内容,并且可修改 )
  • strtok函数的第一个参数不为NULL,函数将找到str中的第一个标记,strtok函数保存它在字符串中的位置
  • strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
  • 如果字符串中不存在更多的标记,则返回NULL指针

使用

char arr[] = "192.168@6.111";
char* sep = ".@";
char* s = NULL;//分开输出
s = strtok(arr,sep);
printf("%s ",s);
s = strtok(NULL, sep);
printf("%s ", s);
s = strtok(NULL, sep);
printf("%s ", s);
s = strtok(NULL, sep);
printf("%s ", s);//循环
for (s = strtok(arr, sep); s != NULL; s = strtok(NULL, sep)) {printf("%s ", s);
}

12. strerror的使用

char* strerror ( int errnum ) ;

可以吧参数部分错误码对应的错误字符串返回

在不同的系统和c标准库都规定了一些错误码,一般放在 errno.h 头文件,用一个全局的变量errno来记录错误码,刚开始是0,表示没有错误,数字很难理解错误信息,所以可以通过数字打印错误信息

//打印0-10的错误信息
for (int i = 0; i <= 10; i++) {printf("%d :%s\r\n", i, strerror(i));
}

输出 (win10 vs2022):

在这里插入图片描述

使用

FILE* pfile = fopen("exist.ent","r");
if (pfile == NULL) {printf("error opening file exist.ent:%s",strerror(errno));
}

由于目录不存在此文件,会打印错误信息,不存在该文件

输出

在这里插入图片描述
也可以使用perror函数,可以直接将错误信息打印出来,不需要错误码,还会先输出冒号和空格,再打印错误信息

FILE* pfile = fopen("exist.ent","r");
if (pfile == NULL) {//printf("error opening file exist.ent:%s",strerror(errno));perror("error opening file exist.ent");
}

输出

在这里插入图片描述

13. 字符串匹配优化-KMP算法

上面的strstr函数的使用使用的是一种BF算法,从T字符串和S字符串的第一个开始比较,如果相等继续比较第二个,不相等,比较T字符的第二个和S字符串的第一个,它的时间复杂度是 O (m*n),m和n可看做是两个字符串的长度。这种方法是可以优化的,如果S字符串的长度是5,当T字符串剩下的长度不足5时,肯定是匹配失败的,这时,可以提前返回失败

这里讲解另一种匹配思路,KMP方法

简介
kmp算法是字符匹配算法,核心是利用匹配失败后的信息,尽量减少子串的匹配次数达到快速匹配的目的。具体通过next()的函数实现,本身包含了模式串的局部匹配信息,时间复杂度为O(m+n)

KMP的i值不会回退,只回退j,j也不是一定回退到0位置

  1. 首先说明为什么主串不回退

在这里插入图片描述

上面的一个主串和子串,按照BF匹配,匹配到5下标,一个是d,一个是c,所以需要回退,j回退到0,i回退到刚开始匹配的下标的下一个,也就是1下标,继续开始匹配

但我们再想一想,既然能匹配到最后一个字符,说明两个串前面有一部分是相同的,例如上图的第一个蓝色下划线ab,子串中有两个ab,之所以用第一个ab,是因为中间有可能有其他字母漏匹配,以第一次匹配的为准。
在这种情况下,i没必要回退,继续匹配下一个字符d,j也没必要回退到0下标,因为ab和上面匹配到的ab是一样的,j完全可以回退到ab后的2下标继续匹配,这时同时移动两个下标,两个字符串完全匹配上了,就用没有回退的方式找到了子串的位置

上面可能是一个特定的例子,但可以总结出这种思路,根据不同的结果调整j返回的下标,,这个就是next数组,,它保存了每个结果匹配不成功j返回的下标,不同的j值对应了一个k值,k就是返回的位置

k值的计算规则是这样:

规则: 找到匹配成功部分两个相等的真子串 (不包括本身),一个以下标0开始,另一个以j-1下标结尾。若找到,k值找到的子串长度,没找到填0
不管什么数据,next[0] = -1,next[1] = 0

以下面的练习来说明next数组的填写规则

在这里插入图片描述
首先规则2说,0和1下标,总是填-1和0

在这里插入图片描述

接下来看2下标,按照规则1的说法,需要找两个真子串,一个以下标0开始,一个以j-1,也就是找以a开始以b结尾的两个真子串,只有一个ab,所以填0

在这里插入图片描述

然后看3下标,就是找以0下标开始,2下标结束的两个子串,也就是以a开始,a结尾的子串,有两个a,长度是1,填1

在这里插入图片描述

到下标4的时候,找以a开始b结尾的两个子串,0和1下标的ab和2和3下标的ab符合条件,ab长度是2,填2

按照上述规则继续往下,得出下面结果:

在这里插入图片描述

那么如何将这种方法转换为程序呢?这里我们分为两种情况

第一种情况,要填的下标的前一个字母和匹配的第一个字串的下一个相等,下图所示

在这里插入图片描述
我们要填第9个下标,9下标需要根据8下标填,之前匹配到的字串是abc (绿色下划线),可以发现8下标和k下标的字母都是a,说明9下标肯定可以匹配abca,所以在8下标的基础上加1。
j看做需要填的下标,可以得出规则,k是next[j-1],如果next[j-1] == next[k] ,next[j] = next[j-1] +1

第二种情况,要填的j-1下标的字母和匹配到的第一个字串的下一个字符不一致

在这里插入图片描述

需要填6下标,i下标和k下标一个是a,一个是c,不一致。所以k需要回退,回退到k的位置,k的字符是a还是不一致,继续回到0下标,0下标时a,和i下标一致,所以填1,如果一直不相等,会回到0下标,值是-1的话,加1,填为最小值0就行

规则,如果next[j-1] != next[k] , k = next[k],一直重复,直到找到 next[k] == next [j-1],如果值为-1,填为0

得出以上两种情况,就可以写代码了

代码

//获取next数组
void GetNext(char* sub,int* next) {next[0] = -1;next[1] = 0;int lensub = strlen(sub);int i = 2; //当前下标int k = 0; //前一项k值while (i<lensub) {if (k==-1 || sub[i - 1] == sub[k]) {next[i] = k + 1;i++;k++;}else {k = next[k];}}
}
//kmp
int KMP(const char* str,const char* sub ) {assert(str!=NULL && sub!=NULL);int lenstr = strlen(str);int lensub = strlen(sub);if (lenstr == 0 || lensub == 0) return-1;int* next = (int*)malloc(sizeof(int) * lensub);assert(next != NULL);GetNext(sub,next);int i = 0;   //遍历主串int j = 0;   //遍历子串while (i<lenstr && j<lensub) {if (j==-1 || str[i] == sub[j]) {i++;j++;}else {j = next[j];}if (j >= lensub) {//找到了,返回下标return i - j;}}//没找到,返回-1return -1;
}

测试

printf("%d\r\n", KMP("ababcedfdab", "abab"));    //0
printf("%d\r\n", KMP("ababcedfdab", "ababf"));   //-1
printf("%d\r\n", KMP("ababcedfdab", "edfd"));    //5

输出

在这里插入图片描述

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

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

相关文章

什么是透明加密技术?透明加密有哪些优势?

透明加密技术是一种特殊的加密方法&#xff0c;它在用户毫不知情的情况下对数据进行加密和解密&#xff0c;保障了数据的安全性。用户在使用这种加密技术时&#xff0c;无需改变他们的日常操作习惯&#xff0c;加密和解密过程在后台自动进行&#xff0c;使得用户在享受数据安全…

基于springboot-“有光”摄影分享网站系统(2023年☆全网唯一)【附源码|数据库|表结构|万字文档(LW)|技术文档|说明文档】

主要功能 前台登录&#xff1a; 注册用户&#xff1a;用户账号、密码、姓名、手机号、身份证号、性别、邮箱 用户&#xff1a; ①首页、公告资讯展示、图片素材展示、活动展示、视频素材展示、查看更多 ②论坛、发布帖子、活动、活动标题、活动类型、公告资讯、公告标题、公告…

[原创][3]探究C#多线程开发细节-“用ConcurrentQueue<T>解决多线程的无顺序性的问题“

[简介] 常用网名: 猪头三 出生日期: 1981.XX.XXQQ: 643439947 个人网站: 80x86汇编小站 https://www.x86asm.org 编程生涯: 2001年~至今[共22年] 职业生涯: 20年 开发语言: C/C、80x86ASM、PHP、Perl、Objective-C、Object Pascal、C#、Python 开发工具: Visual Studio、Delphi…

金蝶Apusic应用服务器 任意文件上传漏洞复现

0x01 产品简介 金蝶Apusic应用服务器&#xff08;Apusic Application Server&#xff0c;AAS&#xff09;是一款标准、安全、高效、集成并具丰富功能的企业级应用服务器软件&#xff0c;全面支持JakartaEE8/9的技术规范&#xff0c;提供满足该规范的Web容器、EJB容器以及WebSer…

STM32F407-14.3.7-01PWM输入模式

PWM 输入模式 此模式是输入捕获模式的一个特例。其实现步骤与输入捕获模式基本相同&#xff0c;仅存在以下不同之处&#xff1a; 例如&#xff0c;可通过以下步骤对应用于 TI1① 的 PWM 的周期&#xff08;位于 TIMx_CCR1⑨ 寄存器中&#xff09;和占空 比&#xff08;位于 …

rabbitmq消息队列实验

实验目的&#xff1a;实现异步通信 实验条件&#xff1a; 主机名 IP地址 组件 test1 20.0.0.10 rabbitmq服务 test2 20.0.0.20 rabbitmq服务 test3 20.0.0.30 rabbitmq服务 实验步骤&#xff1a; 1、安装rabbitmq服务 2、erlang进入命令行&#xff0c;查看版本 …

IDEA导入JavaWeb项目(非Maven)

IDEA导入JavaWeb(非Maven)项目教程 运行教程 亲爱的粉丝们&#xff0c;我深知你们对IDEA导入JAVAWeb工程的迫切需求。在这个充满竞争的时代&#xff0c;每一个项目都离不开高效的沟通。过程中需要对应的环境适配和软件…

提升Jmeter测试效率的9种参数化方法!

jmeter工具无论做接口测试还是性能测试&#xff0c;参数化都是一个必须掌握且非常有用的知识点。参数化的使用场景: 1&#xff09;多个请求都是同一个ip地址&#xff0c;若服务器地址更换了&#xff0c;则脚本需要更改每个请求的ip 2&#xff09;注册账号&#xff0c;不允许账…

计算机网络HTTP篇

目录 一、HTTP基本概念 二、GET 与 POST 2.1、GET 与 POST 有什么区别&#xff1f; 2.2、GET 和 POST 方法都是安全和幂等的吗&#xff1f; 三、HTTP 缓存 3.1、强制缓存&#xff1a; 3.2、协商缓存 四、HTTP 特性 4.1、HTTP/1.1 4.1.1、HTTP/1.1 的优点 4.1.2、HTT…

OSError: We couldnt connect to ‘https://huggingface.co‘

最近在做NerF类的数字人口型算法。需要加载一些huggingface上面的模型&#xff0c;但是无法连接上&#xff0c;如下图所示 于是先科学上网&#xff0c;打开https://huggingface.co/models 然后搜索提到的无法加载的模型&#xff0c;比如这里是cpierse/wav2vec2-large-xlsr-53-…

Unity DOTS《群体战斗弹幕游戏》核心技术分析之3D角色动画

最近DOTS发布了正式的版本, 我们来分享现在流行基于群体战斗的弹幕类游戏&#xff0c;实现的核心原理。今天给大家介绍大规模战斗群体3D角色的动画如何来实现。 DOTS 对角色动画支持的局限性 截止到Unity DOTS发布的版本1.0.16,目前还是无法很好的支持3D角色动画。在DOTS 的b…

周报:css相关扩展知识

目录 1. 扩展知识&#xff1a;浮动盒子的排列位置 浮动盒子常见排列特点&#xff1a; 浮动盒子扩展特点&#xff1a; 2.扩展知识:行高的取值 line-height常见取值&#xff1a; 行高的取值的方式&#xff1a; 两个方式的区别&#xff1a; 3.扩展知识&#xff1a;body背景…