C语言字符函数与字符串函数超详解

文章目录

  • 前言
  • 1. 字符分类函数
  • 2. 字符转换函数
  • 3. strlen
    • 3. 1 strlen 的使用
    • 3. 2 strlen 的模拟实现
  • 4. strcpy
    • 4. 1 strcpy 的使用
    • 4. 2 strcpy 的模拟实现
  • 5. strcat
    • 5. 1 strcat 的使用
    • 5. 2 strcat 的模拟实现
  • 6. strcmp
    • 6. 1 strcmp 的使用
    • 6. 2 strcmp 的模拟实现
  • 7. strncpy 函数的使用
  • 8. strncat 函数的使用
  • 9. strncmp 函数的使用
  • 10. strstr
    • 10. 1 strstr 的使用
    • 10. 2 strstr 的模拟实现
  • 11. strtok 函数的使用
  • 12. strerror函数的使用


前言

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

1. 字符分类函数

C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。
这些函数的使用都需要包含一个头文件是 ctype.h
cplusplus上的 ctype.h 。
下面是常用的字符分类函数

字符分类函数
可以看出来,这些函数的用法及其相似,因此我们借助其中一个进行讲解。

int islower (int c);

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

来做一个测试:
写一个代码,将字符串中的小写字母转大写,其他字符不变。

#include<stdio.h>
#include<ctype.h>
#include<string.h>
void Upper(char* c)
{(*c) -= 32;//大写字母和小写字母的差是32
}int main()
{char str[] = "Hello World";for (int i = 0; i < strlen(str); i++)//strlen 函数返回的是 str 的长度,后面会介绍{if (islower(str[i]))Upper(&str[i]);}printf("%s", str);
}

输出结果为:
输出结果

2. 字符转换函数

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

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

这两个函数同样也在 ctype.h中。
上面的代码,我们将小写转大写,是 -32 完成的效果,有了转换函数,就可以直接使用 tolower 函数。
修改后的代码:

int main()
{char str[] = "Hello World";for (int i = 0; i < strlen(str); i++){if (islower(str[i]))str[i] = toupper(str[i]);}printf("%s", str);
}

注:以下函数均包括于string.h中。

3. strlen

3. 1 strlen 的使用

size_t strlen ( const char * str );

注意:

  1. 字符串以 '\0' 作为结束标志,strlen 函数返回的是在字符串中 '\0' 前面出现的字符个数
    (不包含 '\0' )。
  2. 参数指向的字符串必须要以 '\0'结束。
  3. 注意函数的返回值为 size_t,是无符号的!
  4. strlen 的使用需要包含头文件(string.h

关于它的使用,也就是将一个字符串的首元素地址传递过去,然后就可以接收它的返回值了。
示例:

#include<stdio.h>
#include<string.h>
int main()
{char* a = "bbb";char b[] = "abcd";printf("%zd %zd", strlen(a), strlen(b));//strlen 的返回值是 size_t 类型的,打印的时候用%zdreturn 0;
}

运行结果:
输出结果

3. 2 strlen 的模拟实现

模拟实现 strlen 的核心思路其实就是:找到字符串的 ‘\0’ ,然后找到其与字符串首元素中间的字符个数

方法一:计数器

#include<stdio.h>
#include<string.h>size_t my_strlen(char* str)
{//计数器size_t count = 0;char* cur = str;while (*cur != '\0')//这里也可以直接写成 while(*cur),因为'\0'的码值为 0cur++,count++;return count;
}int main()
{char* a = "abcdefg";printf("%zd\n", strlen(a));//对照printf("%zd\n", my_strlen(a));return 0;
}

方法二:指针-指针

#include<stdio.h>
#include<string.h>size_t my_strlen(char* str)
{//指针-指针char* cur = str;while (*cur)cur++;return cur - str;//指针-指针得到的是指针之间的元素个数
}int main()
{char* a = "abcdefg";printf("%zd\n", strlen(a));printf("%zd\n", my_strlen(a));return 0;
}

方法三:不创建临时变量

#include<stdio.h>
#include<string.h>size_t my_strlen(char* str)
{//不创建临时变量if (!*str)//递归的终止条件return 0;elsereturn 1 + my_strlen(str + 1);
}int main()
{char* a = "abcdefg";printf("%zd\n", strlen(a));printf("%zd\n", my_strlen(a));return 0;
}

不创建临时变量模拟实现 strlen 使用到了函数递归,如果你对上面的代码有疑惑,可以看函数递归与迭代这篇博客,这里不再过多赘述。

4. strcpy

4. 1 strcpy 的使用

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

它的作用就是将 source 中的字符串拷贝到 destination 中。

  1. 源字符串必须以'\0'结束。
  2. 会将源字符串中的 '\0'拷贝到目标空间。
  3. 目标空间必须足够大,以确保能存放源字符串。
  4. 目标空间必须可修改

使用范例:

#include<stdio.h>
#include<string.h>int main()
{char str1[] = "abcd";char str2[] = "efghxxxxx";printf("str1 = %s\nstr2 = %s\n\n", str1, str2);strcpy(str2, str1);printf("str1 = %s\nstr2 = %s\n\n", str1, str2);return 0;
}

结果:
结果

4. 2 strcpy 的模拟实现

要想实现这个函数,我们要先搞清楚 strcpy 是怎么工作的,在拷贝结束之后,str2 里究竟放的是什么, 我们可以通过调试上面的代码来得到答案:
strcpy函数执行前
strcpy函数执行后
可以看到,在执行 strcpy 函数后, str2 里实际存放的不只是 str1 的数据,只是在拷贝的时候将 str1'\0' 拷贝过来了,所以 str2 的内容变得和 str1 一样了。

这样,我们就可以开始尝试模拟实现 strcpy 函数了。

#include<stdio.h>char* my_strcpy(char* destination, const char* source)
{//注意返回值,返回的是复制结束后 destination 的首元素地址char* ret = destination;while (*destination++ = *source++);*destination = '\0';//可不要忘了'\0'!return ret;
}int main()
{char str1[] = "abcd";char str2[] = "efghxxxxx";printf("str1 = %s\nstr2 = %s\n\n", str1, str2);my_strcpy(str2, str1);printf("str1 = %s\nstr2 = %s\n\n", str1, str2);return 0;
}

5. strcat

5. 1 strcat 的使用

char *strcat(char *dest, const char*src)

strcat 用于连接两个字符串,将 src 中的字符串连接到 dest 字符串的后面,连接时会删除掉 src 后面的'\0'

  1. 源字符串必须以'\0'结束。
  2. 目标字符串中也得有'\0',否则没办法知道追加从哪里开始。
  3. 目标空间必须可修改
  4. 目标空间必须足够大,能容纳下源字符串的内容。

使用范例:

#include<stdio.h>
#include<string.h>int main()
{char str1[50] = "abcd";//注意目标字符串要足够大char str2[] = "efghxxxxx";strcat(str1, str2);printf("%s", str1);return 0;
}

运行结果:
运行结果

5. 2 strcat 的模拟实现

在这里我们不妨多思考一下:在这个函数的模拟实现中,我们必然会涉及到指针的解引用,那么为了防止空指针的解引用,我们可以使用 assert 进行断言,这样在排查问题时也更方便。

#include<stdio.h>
#include<string.h>
#include<assert.h>char* my_strcat(char* dest, const char* src)
{assert(dest && src);//断言 dest 和 src 这两个指针都不为空//注意返回值,也是返回目标字符串的首元素地址char* ret = dest;while (*dest)//找 dest 字符串的'\0'dest++;while (*dest++ = *src++);*dest = '\0';//这两步和 strcpy 是一样的
}int main()
{char str1[50] = "abcd";//注意目标字符串要足够大char str2[] = "efghxxxxx";my_strcat(str1, str2);printf("%s", str1);return 0;
}

不仅是 strcat 函数,实际上 strlenstrcpy 的模拟实现中都可以加入 assert 断言来增强代码的健壮性

6. strcmp

6. 1 strcmp 的使用

int strcmp (const char * str1, const char * str2)

这个函数用来比较两个字符串是否相同,关于它的返回值,我们不妨来看看cplusplus上对返回值的描述。
返回值
也就是说:

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

strcpm是如何比较两个字符串?
比较两个字符串中对应位置上字符ASCII码值的大小。

使用范例:

#include<stdio.h>
#include<string.h>int main()
{char str1[] = "abcd";char str2[] = "abcd";char str3[] = "abce";printf("%d %d", strcmp(str1, str2), strcmp(str1, str3));return 0;
}

输出结果:
输出结果

6. 2 strcmp 的模拟实现

#include<stdio.h>int my_strcmp(const char* str1, const char* str2)
{while (*str1 || *str2)//当 str1 和 str2 有一个不为 '\0' 时进行循环{if (*str1 != *str2)return *str1 - *str2;//返回的就是两个字符串中第一个不相同字符的码值的差str1++, str2++;}return 0;
}int main()
{char str1[] = "abcd";char str2[] = "abcd";char str3[] = "abce";printf("%d %d", my_strcmp(str1, str2), my_strcmp(str1, str3));return 0;
}

7. strncpy 函数的使用

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

用法和 strcpy 基本一致,只是多了一个参数来控制复制多少元素过去。

  1. 拷贝num个字符从源字符串到目标空间。
  2. 如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加\0,直到num个。
  3. 拷贝完成后,如果没有到源字符串的末尾,不会追加'\0'

测试:

#include<stdio.h>
#include<string.h>int main()
{char str1[50] = "abcdxxxxxxxxxxxxxxxxxxxxxxxx";char str2[] = "efghxxxxx";strncpy(str1, str2, 13);printf("%s", str1);return 0;
}

我们通过调试观察 str1 的内容变化。
执行前
执行后

8. strncat 函数的使用

char * strncat ( char * destination, const char * source, size_t num );
  1. source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加一个'\0'字符。
  2. 如果source 指向的字符串的长度小于num的时候,只会将字符串中到'\0' 的内容追加到destination指向的字符串末尾

测试:

#include <stdio.h>
#include <string.h>
int main()
{char str1[20];char str2[20];strcpy(str1, "To be ");//将"To be"放进 str1 中strcpy(str2, "or not to be");strncat(str1, str2, 6);printf("%s\n", str1);return 0;
}

结果:
结果

9. strncmp 函数的使用

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

比较str1str2的前num个字符,如果相等就继续往后比较,最多比较num个字母,如果提前发现不一样,就提前结束。如果num个字符都相等,就是相等返回0.
返回值与 strcmp 一致。
返回值

10. strstr

10. 1 strstr 的使用

char * strstr ( const char * str1, const char * str2);
  1. 函数返回指向字符串str2在字符串str1第一次出现的位置的指针
  2. 字符串的比较匹配不包含'\0'字符,以'\0'作为结束标志)。

示例:

#include <stdio.h>
#include <string.h>
int main()
{char str[] = "This is a simple string";char* pch;pch = strstr(str, "simple");//找到 simple 第一次在 str 中出现的位置,pch指向 s.strncpy(pch, "sample", 6);//使用 strcpy 会有 '\0'printf("%s\n", str);return 0;
}

结果:
结果

10. 2 strstr 的模拟实现

大致思路:先在 str1 中遍历找和 str2 首元素相同的元素,找到了就将此时的 str1 复制出来,开始同时遍历两个字符串,判断两个字符串是否相同,如果相同,返回 str1 ,不相同就继续遍历。

#include <stdio.h>char* my_strstr(const char* str1, const char* str2)
{while (*str1){if (*str1 == *str2){char* tmp1 = str1;char* tmp2 = str2;while (*tmp1 && *tmp2){if (*tmp1 != *tmp2)break;tmp1++, tmp2++;}if (!*tmp2)//如果循环结束的时候 tmp2 是'\0',说明上面的循环走到底了,也就是找到了return str1;}str1++;}return NULL;
}

当然,上面那里在找到与 str2 首元素相同的元素之后,也可以使用 strcmp 函数进行比较。

11. strtok 函数的使用

strtok 函数用于分割字符串

char * strtok ( char * str, const char * sep);
  1. sep参数指向一个字符串,定义了用作分隔符的字符集合(标记)
  2. 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。
  3. strtok函数找到str中的下一个标记,并将其用\0结尾,返回一个指向这个标记的指针。
  4. strtok函数会改变被操作的字符串,所以被strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。
  5. strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置。
  6. strtok函数的第一个参数为 NULL,函数将在上一次传递的字符串中被保存的位置开始,查找下一个标记。(也就是说, strtok 函数可以连续调用,连续调用时第一个参数传 NULL
  7. 如果字符串中不存在标记,则返回 NULL

示例:

#include <stdio.h>
#include <string.h>
int main()
{char arr[] = "192.168.6.111";char* sep = ".";char* str = NULL;for (str = strtok(arr, sep); str != NULL; str = strtok(NULL, sep)){printf("%s\n", str);}return 0;
}

结果:
结果

12. strerror函数的使用

char* strerror ( int errnum );

strerror 函数可以把参数部分错误码对应的错误信息的字符串地址返回来。

在不同的系统和C语言标准库的实现中都规定了一些错误码,一般是放在 errno.h 这个头文件中说明的,C语言程序启动的时候就会使用一个全局的变量errno来记录程序的当前错误码,只不过程序启动的时候errno是 0 ,表示没有错误,当我们在使用标准库中的函数的时候发生了某种错误,就会将对应的错误码存放在errno中,而一个错误码的数字是整数很难理解是什么意思,所以每一个错误码都是有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回

举例:

#include <errno.h>
#include <string.h>
#include <stdio.h>
//我们打印一下0-10这些错误码对应的信息
int main()
{int i = 0;for (i = 0; i <= 10; i++) {printf("%s\n", strerror(i));}return 0;
}

在我使用的环境 win11+VS2022 下输出结果为:

No error
Operation not permitted
No such file or directory
No such process
Interrupted function call
Input/output error
No such device or address
Arg list too long
Exec format error
Bad file descriptor
No child processes

使用举例:

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{FILE* pFile;pFile = fopen("unexist.ent", "r");//尝试打开名为 unexist.ent 的文件,但是并没有找到,也就是打开失败了,那么 pfile 就是 NULLif (pFile == NULL)printf("Error opening file unexist.ent: %s\n", strerror(errno));return 0;
}

输出:
输出
也可以了解一下 perror 函数,perror函数相当于一次将上述代码中的第9行完成了,直接将错误信息
打印出来。perror 函数打印完参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。

#include <stdio.h>
#include <string.h>
#include <errno.h>
int main ()
{FILE * pFile;pFile = fopen ("unexist.ent","r");if (pFile == NULL)perror("Error opening file unexist.ent");return 0;
}

输出结果同上。

如果喜欢的话不妨顺手点个赞,收藏,评论,关注!
我会持续更新更多优质文章!!

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

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

相关文章

VI/VIM编辑器及三种模式

目录 1. 三种模式 2. 使用 VIM 3. i/ a/ o 进入输入模式 VI/VIM是 visual interface 的缩写是 Linux 中最经典的文本编辑器&#xff1b; VIM是 VI 的增强版本&#xff0c;兼容 VI 的所有指令&#xff0c;不仅能够编辑文本&#xff0c;还具有 shell 程序编辑的功能&#xff…

maven引入了jar包但在class文件里找不到jar包里的类

在工作当中遇到的这个问题&#xff0c;别人引入的jar包&#xff0c;我代码里报错 maven clean 和 maven install 都不管用 检查过了pom文件 检查了maven仓库路径下是否有这个cn.hutool的jar包 都没有找到问题 最终解决办法是手动引入 步骤一&#xff1a;点击左上角file->…

3.4-GRU

1网络结构 1.1与LSTM相比 LSTM里面有三个门&#xff0c;还有一个增加信息的tanh单元&#xff0c;参数量相较于RNN显著增加&#xff1b; 因此GRU在参数上比LSTM要少&#xff1b; 另外&#xff0c;LSTM 将必要信息记录在记忆单元中&#xff0c;并基于记忆单元的信息计算隐藏状…

MySQL数据库(基础篇)

&#x1f30f;个人博客主页&#xff1a;心.c 前言&#xff1a;今天讲解的是MySQL的详细知识点的&#xff0c;希望大家可以收货满满&#xff0c;话不多说&#xff0c;直接开始搞&#xff01; &#x1f525;&#x1f525;&#x1f525;文章专题&#xff1a;MySQL &#x1f63d;感…

1.c#(winform)编程环境安装

目录 安装vs创建应用帮助查看器安装与使用&#xff08; msdn&#xff09; 安装vs 安装什么版本看个人心情&#xff0c;或者公司开发需求需要 而本栏全程使用vs2022进行开发c#&#xff0c;着重讲解winform桌面应用开发 使用***.net framework***开发 那先去官网安装企业版的vs…

AI绘画入门实践 | Midjourney:使用 --chaos 给图像风格来点惊喜

在 Midjourney 中&#xff0c;--chaos 影响初始图像网格的多样性&#xff0c;指 MJ 每次出的4张图之间的差异性。 默认值为0&#xff0c;值越高&#xff0c;差异性越大。 使用格式&#xff1a;--chaos 0-100的整数值 使用演示 a lot of flowers --chaos 0 --v 6.0a lot of fl…

项目打包与运行

前端运行时必须有与后端相同的数据库版本&#xff0c;数据库账号密码 右侧maven -> 展开要打包的项目 -> 生命周期 -> 双击package 打包好之后在target目录下 右键打开 在资源目录下输入cmd&#xff0c;执行以下命令即可运行&#xff08;端口号为yml文件…

Redis实战篇(黑马点评)笔记总结

一、配置前后端项目的初始环境 前端&#xff1a; 对前端项目在cmd中进行start nginx.exe&#xff0c;端口号为8080 后端&#xff1a; 配置mysql数据库的url 和 redis 的url 和 导入数据库数据 二、登录校验 基于Session的实现登录&#xff08;不推荐&#xff09; &#xf…

【iOS】—— retain\release实现原理和属性关键字

【iOS】—— retain\release实现原理和属性关键字 1. retain\reelase实现原理1.1 retain实现原理1.2 release实现原理 2. 属性关键字2.1 属性关键字的分类2.2 内存管理关键字2.2.1 weak2.2.2 assgin2.3.3 strong和copy 2.4 线程安全的关键字2.5 修饰变量的关键字2.5.1常量const…

文件上传总结

一、原理 通过界面上的上传功能上传了一个可执行的脚本文件&#xff0c;而WEB端的系统并未对其进行检测或者检测的逻辑做的不够好&#xff0c;使得恶意用户可以通过文件中上传的一句话木马获得操控权 二、绕过方法 1>前端绕过 1.删除前端校验函数 checkFile() 2.禁用js…

大数据平台之HBase

HBase是一个高可靠性、高性能、面向列、可伸缩的分布式存储系统&#xff0c;是Apache Hadoop生态系统的重要组成部分。它特别适合大规模结构化和半结构化数据的存储和检索&#xff0c;能够处理实时读写和批处理工作负载。以下是对HBase的详细介绍。 1. 核心概念 1.1 表&#x…

打造一篇完美的【数学建模竞赛论文】:从准备到撰写的全面指南

目录 一、赛前准备 1.1 报名与纪律要求 1.2 MD5码上传 1.3 竞赛准备 1.4 时间分配 二、论文格式规范 2.1 摘要 2.2 参考文献 2.3 排版要求 三、建模过程与方法 3.1 问题分析与模型假设 3.2 模型构建与求解 3.3 结果分析与检验 四、论文撰写技巧 4.1 论文结构 4…

Godot入门 07 世界构建2.0

添加基础节点Node&#xff0c;重命名为Coins&#xff0c;整理场景树&#xff0c;拖动Coin到Coins节点下。 添加基础节点Node&#xff0c;重命名为Platforms&#xff0c;整理场景树&#xff0c;拖动Platform到Platforms节点下。 添加游戏背景 设置当前图层名称为Mid 添加图层元…

飞牛爬虫FlyBullSpider 一款简单方便强大的爬虫,限时免费 特别适合小白!用它爬下Boss的2024年7月底Java岗位,分析一下程序员就业市场行情

一、下载安装FlyBullSpider 暂时支持Window,现在只在Win11上做过测试 1 百度 点击百度网盘 下载 链接&#xff1a;https://pan.baidu.com/s/1gSLKYuezaZgd8iqrXhk8Kg 提取码&#xff1a;Fly6 2 csdn https://download.csdn.net/download/fencer911/89584687 二、体验初…

vue3 vxe-table 点击行,不显示选中状态,加上设置isCurrent: true就可以设置选中行的状态。

1、上个图&#xff0c;要实现这样的&#xff1a; Vxe Table v4.6 官方文档 2、使用 row-config.isCurrent 显示高亮行&#xff0c;当前行是唯一的&#xff1b;用户操作点击选项时会触发事件 current-change <template><div><p><vxe-button click"sel…

C++入门基础(超详细) 需:C语言基础

1.C的发展史 大致了解一下 C的起源可以追溯到1979年&#xff0c;当时BjarneStroustrup(本贾尼斯特劳斯特卢普&#xff0c;这个翻译的名字不 同的地方可能有差异)在贝尔实验室从事计算机科学和软件工程的研究工作。面对项目中复杂的软件开 发任务&#xff0c;特别是模拟和操作系…

Linux权限维持篇

目录 SSH后门 &#xff08;1&#xff09;软链接sshd &#xff08;2&#xff09;SSH Key 生成公私钥 创建个authorized_keys文件来保存公钥 通过修改文件时间来隐藏authorized_keys &#xff08;3&#xff09;SSH Keylogger&#xff08;记录日志&#xff09; Linux的PA…

【Go系列】Go的UI框架Fyne

前言 总有人说Go语言是一门后端编程语言。 Go虽然能够很好地处理后端开发&#xff0c;但是者不代表它没有UI库&#xff0c;不能做GUI&#xff0c;我们一起来看看Go怎么来画UI吧。 正文 Go语言由于其简洁的语法、高效的性能和跨平台的编译能力&#xff0c;非常适合用于开发GUI…

MICA:面向复杂嵌入式系统的混合关键性部署框架

背景 在嵌入式场景中&#xff0c;虽然 Linux 已经得到了广泛应用&#xff0c;但并不能覆盖所有需求&#xff0c;例如高实时、高可靠、高安全的场合。这些场合往往是实时操作系统的用武之地。有些应用场景既需要 Linux 的管理能力、丰富的生态&#xff0c;又需要实时操作系统的高…

vue中scoped详解以及样式穿透>>>、/deep/、::v-deep

1、scoped scoped属性用于限制样式仅应用于当前组件。当一个style标签拥有scoped属性时&#xff0c;它的CSS样式就只能作用于当前的组件&#xff0c;通过该属性&#xff0c;可以使得组件之间的样式不互相污染。 原理&#xff1a;当样式中加了scoped属性时候&#xff0c;编译的…