当前位置: 首页 > news >正文

字符函数和字符串函数(上)

目录

  • 前言
  • 一、字符分类函数
  • 二、字符转换函数
  • 三、strlen的使用和模拟实现
    • 1.strlen函数易错点
    • 2.strlen函数的模拟
  • 四、strcpy函数的使用和模拟实现
    • 1.strcpy函数的使用
    • 2.strcpy函数的模拟
    • 3.strcpy函数的返回值
  • 五、strcat 的使用和模拟实现
    • 1.strcat函数的使用
    • 2.strcat函数的模拟
    • 3.strcat函数的返回值
    • 4.字符串自己给自己追加
  • 六、strcmp函数的使用和模拟
    • 1.strcmp函数的使用
    • 2.strcmp函数的模拟实现:
  • 总结


前言

在编程的过程中,我们经常要处理字符和字符串,为了⽅便操作字符和字符串,C语⾔标准库中提供了⼀系列库函数,接下来我们就学习⼀下这些函数。


一、字符分类函数

C语⾔中有⼀系列的函数是专⻔做字符分类的,也就是⼀个字符是属于什么类型的字符的。
这些函数的使⽤都需要包含⼀个头⽂件是ctype.h
在这里插入图片描述
这些函数使用方法非常类似,我们来讲解一个函数:

int islower ( int c );

判断是否为小写字母,如果为小写就返回一个大于0的数:
在这里插入图片描述

islower 是能够判断参数部分的 c 是否是⼩写字⺟的。
通过返回值来说明是否是⼩写字⺟,如果是⼩写字⺟就返回⾮0的整数,如果不是⼩写字⺟,则返回0。

练习:
写⼀个代码,将字符串中的⼩写字⺟转⼤写,其他字符不变
我们可以利用大小写字母的ASCII码的关系来写:

//'a'->97   'A'->65 a - A = 32int main()
{char arr[] = "I Am a Student";int i = 0;while (arr[i] != '\0'){if (arr[i] >= 'a' && arr[i] <= 'z'){arr[i] -= 32;}i++;}printf("%s\n", arr);return 0;
}

我们还可以用islower()来判断是否小写字母;

#include <ctype.h>
int main()
{char arr[] = "I Am a Student";int i = 0;while (arr[i] != '\0'){if (islower(arr[i])){arr[i] -= 32;}i++;}printf("%s\n", arr);return 0;
}

运行结果:
在这里插入图片描述

二、字符转换函数

C语⾔提供了2个字符转换函数:

int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写 
int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写

上⾯的代码,我们将⼩写转⼤写,是-32完成的效果,有了转换函数,就可以直接使⽤tolower 函数

int main()
{int i = 0;char str[] = "Test String.\n";char c;while (str[i])  //\0的ASCII码值是0{c = str[i];if (islower(c))c = toupper(c);putchar(c);i++;}return 0;
}

运行结果:
在这里插入图片描述

三、strlen的使用和模拟实现

1.strlen函数易错点

 size_t strlen ( const char * str );

• 字符串以 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前⾯出现的字符个数(不包
含 ‘\0’ )。
• 参数指向的字符串必须要以 ‘\0’ 结束。
• 注意函数的返回值为size_t,是⽆符号的( 易错 )
• strlen的使⽤需要包含头⽂件
• 学会strlen函数的模拟实现

这里要特别注意的是strlen函数返回值是size_t类型的,我们们来看看下面的程序:

int main()
{const char* str1 = "abc";const char* str2 = "abcdef";if (strlen(str1) - strlen(str2) < 0){printf("str1<str2\n");}elseprintf("str1>=str2\n");return 0;
}

由于strlen函数返回类型是size_t类型:所以strlen(str1)-strlen(str2)的结果也是size_t类型:所以导致结果:
在这里插入图片描述

2.strlen函数的模拟

根据所学的知识,我们有三种方法:
方法1:

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

方法2:

//不能创建临时变量计数器
int my_strlen(const char * str)
{assert(str);if(*str == '\0')return 0;elsereturn 1+my_strlen(str+1);
}

方法3:

//指针-指针的⽅式
int my_strlen(char *s)
{assert(str);char *p = s;while(*p != ‘\0)p++;return p-s;
}

四、strcpy函数的使用和模拟实现

1.strcpy函数的使用

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

• Copies the C string pointed by source into the array pointed by destination, including the
terminating null character (and stopping at that point).
• 源字符串必须以 ‘\0’ 结束。
• 会将源字符串中的 ‘\0’ 拷⻉到⽬标空间。
• ⽬标空间必须⾜够⼤,以确保能存放源字符串。
• ⽬标空间必须可修改。
• 学会模拟实现。

我们来看看下面的例子:

int main()
{char arr1[] = "hello bit";char arr2[20] = { 0 };strcpy(arr2, arr1);printf("%s\n", arr2);return 0;
}

在这里插入图片描述
运行结果:
在这里插入图片描述
其实我们继续深入看这个函数:这个函数不仅仅把前面的字符串拷贝下来了,一同拷贝下来的还有‘\0’
如何验证,我们看看下面的代码:

int main()
{char arr1[] = "hello bit";char arr2[20] = "xxxxxxxxxxxxxxxxxx";strcpy(arr2, arr1);printf("%s\n", arr2);return 0;
}

我们来进行调试:
在这里插入图片描述
我们清楚的看到,‘\0’也一同拷贝过来了

那如果我们这样创建arr2呢?

int main()
{char arr1[] = "hello bit";char* arr2 = "xxxxxxxxxxxxxxx";strcpy(arr2, arr1);printf("%s\n", arr2);return 0;
}

不知道大家还是否记得一个名词叫常量字符串,常量字符串是不能被修改的;
所以此程序报错;
但是有人说可以不用strpyt函数也可以用来复制:

int main()
{char arr1[] = "abcdef";char arr2[20] = { 0 };//strcpy(arr2, arr1);arr2 = arr1;printf("%s", arr2);return 0;
}

这样可以付给arr2吗?
学到这里了,我们不应该犯这个错误了,
这里的arr1,arr2表示的不是他们里面的值了,他们表示的是字符串首元素的地址,表示的是一个常量,所以不可以修改,如果你输入这串代码,你的编译器会报错的;

2.strcpy函数的模拟

按照正常的思维我们来思考这个问题:
在这里插入图片描述
我们想要讲arr1中的字符付给arr2,再通过循环,来实现逐步付给arr2,但是有个问题:怎么停下来,如果我们用src是否等于\0来判断,那最后的\0将无法付给arr2;那我们如果在末尾单独加上\0呢:

#include <assert.h>
void my_strcpy(char* dest, const char* src)
{assert(src != NULL);assert(dest != NULL);while (*src){*dest = *src;dest++;src++;}*dest = *src;
}int main()
{char arr1[] = "hello bit";char arr2[20] = "xxxxxxxxxxxxxxx";my_strcpy(arr2, arr1);printf("%s\n", arr2);return 0;
}

这样我们也可以实现:
在这里插入图片描述

我们是不是发现这个while的循环体比较冗余:
在这里插入图片描述
我们有没有办法将其化简一下:
我们首先想到的是:

void my_strcpy(char* dest, const char* src)
{assert(src != NULL);assert(dest != NULL);while (*src){*dest++ = *src++;}*dest = *src;
}

但是这里还是要在结尾加上’\0’,我们有没有办法将其加入到循环体中一起解决,我们来分析一下:
在这里插入图片描述
我们知道如果以赋值来确定循环条件,那就要看被赋值的对象是0还是非0:

在这里插入图片描述
这里我们可以巧妙的利用这点:

//最终优化char* my_strcpy(char* dest, const char* src)
{char* ret = dest;assert(dest != NULL);assert(src != NULL);while ((*dest++ = *src++)){;   //空语句}return ret;
}

这样我们就可以极大的简化这个代码;

3.strcpy函数的返回值

我们注意到strcpy函数时有返回值的,返回值为char*,我们通过查资料得知:

在这里插入图片描述
我们清楚的知道:返回值是目的地,也就是修改的那个参数,即第一个参数,所以我们的模拟函数最后可以这么写:
在这里插入图片描述

五、strcat 的使用和模拟实现

1.strcat函数的使用

语法要求:

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

• Appends a copy of the source string to the destination string. The terminating null character
in destination is overwritten by the first character of source, and a null-character is included
at the end of the new string formed by the concatenation of both in destination.
• 源字符串必须以 ‘\0’ 结束。
• ⽬标字符串中也得有 \0 ,否则没办法知道追加从哪⾥开始。
• ⽬标空间必须有⾜够的⼤,能容纳下源字符串的内容。
• ⽬标空间必须可修改。
• 字符串⾃⼰给⾃⼰追加,如何?

int main()
{char arr1[20] = "bit on ";char arr2[] = "the road";strcat(arr1, arr2);printf("%s\n", arr1);return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述

2.strcat函数的模拟

在这里插入图片描述
那这样实现就不难了:

char *my_strcat(char *dest, const char*src)
{char *ret = dest;assert(dest != NULL);assert(src != NULL);while(*dest){dest++;}while((*dest++ = *src++)){;}return ret;
}

3.strcat函数的返回值

有人或许对上面的模拟函数中的ret不理解,其实,我们对比一下原函数就理解了;
strcat函数与strcpy函数一样,都是有返回值的,返回值也是目标字符串的首元素地址,但是我们在寻找目标字符串的\0的时候,会将dest指针指向其他地方,所以我们在使用dest前,我们用ret将其存下,方便返回;
最后我们来看看完整的代码:

char* my_strcat(char* dest, const char* src)
{char* ret = dest;assert(dest != NULL);assert(src != NULL);while (*dest){dest++;}while ((*dest++ = *src++)){;}return ret;
}int main()
{char arr1[20] = "bit on ";char arr2[] = "the road";printf("%s\n", my_strcat(arr1, arr2));return 0;
}

运行结果:
在这里插入图片描述

4.字符串自己给自己追加

那现在我们写的函数就是完全体了?可以媲美strcpy函数的本尊了?
其实远远达不到;
我们来看看如果我们在一个字符串后加上它自己,我们的函数可不可以实现?
我们来分析一下:
<1>初始状态:
dest与src都指向第一个元素:
在这里插入图片描述
<2>利用循环指向\0:
在这里插入图片描述
<3>开始逐步交换:
这我们首先将a与\0交换:
在这里插入图片描述
这时候我们就会发现:\0被换成了第一个元素,那src指针就无法通过\0停下,所以,这将是一个死循环;

但是我们来看看strcat本尊能不能做到:

int main()
{char arr1[20] = "abcdef";//my_strcat(arr1, arr1);printf("%s\n",strcat(arr1, arr1));return 0;
}

运行结果:
在这里插入图片描述
我们可以看到,strcat函数完全可以做到;所以,我们写的模拟函数只是实现我们所想的功能,与真正的函数还是有些距离的。

六、strcmp函数的使用和模拟

1.strcmp函数的使用

This function starts comparing the first character of each string. If they are equal to each other, it continues with the following pairs until the characters differ or until a terminating null-character is reached;
此函数开始比较每个字符串的第一个字符。如果它们的ASCII码相等,它继续使用向下配对,直到字符不同或终止已达到空字符;

• 标准规定:
◦ 第⼀个字符串⼤于第⼆个字符串,则返回⼤于0的数字
◦ 第⼀个字符串等于第⼆个字符串,则返回0
◦ 第⼀个字符串⼩于第⼆个字符串,则返回⼩于0的数字
◦ 那么如何判断两个字符串? ⽐较两个字符串中对应位置上字符ASCII码值的⼤⼩。

这里我们可以举几个例子:
在这里插入图片描述
我们可以用代码带测试一下:
在这里插入图片描述
在这里插入图片描述

结果符合;
但是这里我们多运行几次我们就知道VS中的输出比较固定,-1或1,所以我们在书写程序的时候要考虑到其他的编译器,不能将其特殊考虑;

2.strcmp函数的模拟实现:

这个函数实现·不难:

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

我们还可以进行缩减:

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

我们实验一下:
在这里插入图片描述

到这我们就基本讲完了字符串函数的一半,为什么说是一半呢,因为我们上面讲的

  • strcpy
  • strcat
  • strcmp

都是长度不受限制字符串函数,这些函数不管输入的字符串长度有多长,全部处理完,那聪明的你们肯定会想到还有长度受限制字符串函数

  • strncpy
  • strncat
  • strncmp

总结

由于篇幅太长,长度受限制字符串函数我们放到下期来讲,我们下期见!


相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Ubuntu 20.04 内核升级后网络丢失问题的解决过程
  • 经典sql题(九)SQL 查询详细指南总结二
  • 面试金典题9
  • Linux基础---13三剑客及正则表达式
  • 【Web】PolarCTF2024秋季个人挑战赛wp
  • 并查集LRU cache
  • 上海市高等学校信息技术水平考试 C程序设计(2021A场)全解
  • 希尔排序(C语言实现)
  • CMake中的PUBLIC、PRIVATE 和 INTERFACE用法
  • 2024/9/21黑马头条跟学笔记(十)
  • Ubuntu 安装和使用 Fcitx 中文输入法;截图软件flameshot
  • 动态住宅IP的多元化应用
  • 网址匹配正则表达式(python实现)
  • 欺诈文本分类检测(十五)——数据校正与增强
  • 分布式消息中间件kafka
  • [数据结构]链表的实现在PHP中
  • 【Amaple教程】5. 插件
  • hadoop集群管理系统搭建规划说明
  • iOS 颜色设置看我就够了
  • Linux编程学习笔记 | Linux多线程学习[2] - 线程的同步
  • nodejs:开发并发布一个nodejs包
  • PHP变量
  • spring cloud gateway 源码解析(4)跨域问题处理
  • Storybook 5.0正式发布:有史以来变化最大的版本\n
  • vue.js框架原理浅析
  • 笨办法学C 练习34:动态数组
  • 闭包--闭包之tab栏切换(四)
  • 讲清楚之javascript作用域
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 聊聊redis的数据结构的应用
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 排序(1):冒泡排序
  • 数据结构java版之冒泡排序及优化
  • 想使用 MongoDB ,你应该了解这8个方面!
  • 一道面试题引发的“血案”
  • 译自由幺半群
  • 由插件封装引出的一丢丢思考
  • 在Unity中实现一个简单的消息管理器
  • SAP CRM里Lead通过工作流自动创建Opportunity的原理讲解 ...
  • ​LeetCode解法汇总1276. 不浪费原料的汉堡制作方案
  • # include “ “ 和 # include < >两者的区别
  • # 数仓建模:如何构建主题宽表模型?
  • ###C语言程序设计-----C语言学习(3)#
  • #NOIP 2014# day.1 生活大爆炸版 石头剪刀布
  • (1)安装hadoop之虚拟机准备(配置IP与主机名)
  • (20050108)又读《平凡的世界》
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (顶刊)一个基于分类代理模型的超多目标优化算法
  • (附源码)计算机毕业设计ssm-Java网名推荐系统
  • (四)activit5.23.0修复跟踪高亮显示BUG
  • (详细版)Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models
  • (转)socket Aio demo
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • ****Linux下Mysql的安装和配置
  • *上位机的定义