【C语言】字符串函数详解

文章目录

  • Ⅰ. strcpy -- 字符串拷贝
    • 1、函数介绍
    • 2、模拟实现
  • Ⅱ. strcat -- 字符串追加
    • 1、函数介绍
    • 2、模拟实现
  • Ⅲ. strcmp -- 字符串比较
    • 1、函数介绍
    • 2、模拟实现
  • Ⅳ. strncpy、strncat、strncmp -- 可限制操作长度
  • Ⅴ. strlen -- 求字符串长度
    • 1、函数介绍
    • 2、模拟实现(三种方式)
      • ① 计数器的方式
      • ② 递归的方式
      • ③ 指针-指针的方式
  • Ⅵ. strstr -- 字符串查找
    • 1、函数介绍
    • 2、模拟实现
  • Ⅶ. strtok -- 字符串分割
  • Ⅷ. strerror、perror -- 错误报告函数

在这里插入图片描述

Ⅰ. strcpy – 字符串拷贝

1、函数介绍

char *strcpy(char *Destination, const char *Source);

strcpy 函数是一个用于拷贝字符串的函数,即将一个字符串中的内容拷贝到另一个字符串中(会覆盖原字符串内容)。它的参数是两个指针,第一个指向的是拷贝字符串的目的地的起始位置,即要将字符串拷贝到什么地方;第二个指向的是要拷贝字符串的内容的起始位置,即需要拷贝的字符串。它的返回值是目标空间的起始位置。

💥注意:

  • 源字符串(需要被拷贝的字符串)必须以 \0 结束
  • 会将源字符串中的 \0 一同拷贝到目标空间
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变

​ 举个例子,比如我们要将 arr2 数组中的 "def" 拷贝到 arr1 数组中。

#include<stdio.h>
#include<string.h>
int main()
{char arr1[10] = "abc";char arr2[] = "def";strcpy(arr1, arr2);printf("%s", arr1);return 0;
}// 运行结果
def

注意: 拷贝结束后 arr1 数组中只有 "def",因为 "abc" 被覆盖了。

2、模拟实现

char* my_strcpy(char* dest, const char* src) 
{if (dest == NULL || src == NULL)return NULL;char* p = dest;while (*p++ = *src++) // 当遇到\0赋值之后判断为0则退出循环,达到了我们的目的{}return dest;
}

​ 在这个代码中,我们首先添加了一个错误处理机制,以防止传递给 strcpy 函数的参数是 NULL 指针。如果参数是 NULL 指针,则函数将返回 NULLmain 函数将输出一条错误消息并退出程序。

​ 此外,我们还使用了 const 关键字来指示源字符串 src 是只读的,这可以帮助我们防止无意中修改源字符串。

Ⅱ. strcat – 字符串追加

1、函数介绍

char *strcat(char *Destination, const char *Source);

strcat 函数是一个用于追加字符串的函数,即将一个字符串中的内容追加到另一个字符串后面(不会覆盖原字符串内容)。它的参数是两个指针,第一个指向的是追加字符串的目的地的起始位置,即要将字符串追加到什么地方;第二个指向的是要追加字符串的内容的起始位置,即需要追加的字符串。它的返回值是目标空间的起始位置。

💥注意:

  • 源字符串必须以 \0 结束。
  • 目标空间必须足够大,能容纳下源字符串的内容。
  • 目标空间必须可修改。
  • 字符串不能给自己追加( \0 被覆盖,无终止条件)。

​ 举个例子,比如我们要将 arr2 数组中的 “liren!” 追加到 arr1 数组的后面。

#include<stdio.h>
#include<string.h>
int main()
{char arr1[20] = "hello ";char arr2[] = "liren!";strcat(arr1, arr2);printf("%s", arr1);return 0;
}// 运行结果:
hello liren!

2、模拟实现

char* my_strcat(char* dest, const char* src) 
{char* p = dest;while (*p != '\0') // 找到目标空间的'\0'{p++;}while (*p++ = *src++) // 当遇到\0赋值之后判断为0则退出循环,达到了我们的目的{}return dest;
}

​ 这个实现中,首先定义了两个指针,一个指向目标字符串的末尾,一个指向源字符串的开头。然后,使用 while 循环将源字符串中的每个字符逐个复制到目标字符串的末尾,直到源字符串末尾为止。最后,在目标字符串的末尾添加一个 NULL 字符,表示字符串的结束。

​ 需要注意的是,在本函数的实现中,目标字符串必须具有足够的空间来容纳源字符串,否则会导致缓冲区溢出和未定义行为。此外,源字符串必须是一个 const char* 类型的指针,以防止在函数内部修改源字符串的内容。

Ⅲ. strcmp – 字符串比较

1、函数介绍

int strcmp(const char *string1, const char *string2);

strcmp 函数是一个用于比较两个字符串内容的函数。它的参数是两个指针,指针分别指向两个待比较字符串的起始位置。它的返回值是一个整型数字。当 string1 大于 string2 的时候返回一个大于 0 的数;当 string1 等于 string2 的时候返回 0;当 string1 小于 string2 的时候返回一个小于 0 的数。

💥注意:

  • 字符串比较的不是字符串长度的大小,而是两个字符串中对应位置字符的 ASCII 值。

​ 举个例子,比如比较字符串 "hello world!" 和字符串 "hello liren!" 的大小。

#include<stdio.h>
#include<string.h>
int main()
{char arr1[20] = "hello world!";char arr2[20] = "hello csdn!";printf("%d\n", strcmp(arr1, arr2));printf("%d\n", strcmp(arr2, arr1));return 0;
}// 运行结果:
1
-1

2、模拟实现

int my_strcmp(const char *s1, const char *s2) 
{while (*s1 == *s2) {if(*s1 == '\0')return 0;s1++;s2++;}return *(const unsigned char*)s1 - *(const unsigned char*)s2;
}

​ 这个实现中,使用 while 循环比较两个字符串中的每个字符。如果两个字符相等,则继续比较下一个字符;如果不相等,则返回它们的 ASCII 码之差。需要注意的是,由于有符号和无符号字符之间的比较会产生未定义行为,因此我们将两个指针都转换为 const unsigned char * 类型。

​ 如果两个字符串完全相等,则返回值为 0。如果 s1 小于 s2,则返回一个负整数,反之则返回一个正整数。

​ 需要注意的是,在使用 strcmp 函数比较字符串时,一定要确保两个字符串都以 NULL 字符结尾。如果没有以 NULL 字符结尾,会导致函数在比较过程中访问到未知的内存区域,从而导致未定义行为。

Ⅳ. strncpy、strncat、strncmp – 可限制操作长度

​ 我们发现 strcpy 是将一个字符串全部拷贝到另一个字符串,strcat 是将一个字符串全部追加到另一个字符串后面,strcmp 也是比较两个字符串的全部内容,这类操作函数称为长度不受限制的字符串操作函数。

​ 那么我们如果操作字符串时并不想操作整个字符串,而只想操作字符串的一部分怎么办呢❓❓❓

​ 库函数中的 strncpy、strncat、strncmp 便解决了这个问题。

char *strncpy(char *Dest, const char *Source, size_t count);
char *strncat(char *Dest, const char *Source, size_t count);
int strncmp(const char *string1, const char *string2, size_t count);

​ 这里就不细讲用法了,比较简单,自行尝试!

Ⅴ. strlen – 求字符串长度

1、函数介绍

size_t strlen( const char *string );

strlen 函数是一个用于求字符串长度的库函数。它的参数是被求长度的字符串的起始地址,返回值是一个无符号整型。

💥注意:

  • 参数指向的字符串要以 \0 结束。
  • strlen 返回的是在字符串中 \0 之前出现的字符个数(不包含 \0 )。
  • 注意函数的返回值为 size_t,是无符号的(易错)。

​ 举个例子,比如我们要求字符串 "abcdef" 的长度。

#include<stdio.h>
#include<string.h>
int main()
{char arr[] = "abcdef";size_t ret = strlen(arr);printf("%d\n", ret);return 0;
}// 运行结果:
6

2、模拟实现(三种方式)

① 计数器的方式

​ 我们定义一个变量为 count,如果传入的指针指向的内容不是 \0,那么 count++,同时指针后移一位,循环往复,直到找到 \0 时返回 count 即可。

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

② 递归的方式

​ 我们一进入函数体就判断传入指针指向的内容是否为 \0,如果是就返回 0,不是就返回 1 + my_strlen(str+1),如此进行下去,直到递归到内层时找到 \0,这时再一步步将值返回回来即可。

size_t my_strlen(const char* str)
{if (*str == '\0')return 0;elsereturn 1 + my_strlen(str + 1);
}

③ 指针-指针的方式

​ 进入函数体时,我们事先定义一个指针变量将传入的指针保存下来,然后将传入的指针向后移,直到遇到 \0 时,我们返回当前指针与保存的指针的差值即可。(指针与指针的差的绝对值是两个指针之间的元素个数)

size_t my_strlen(const char* str)
{const char* p = str; // 保存起始位置while (*str != '\0')str++;return str - p;
}

Ⅵ. strstr – 字符串查找

1、函数介绍

char *strstr(const char *string, const char *strCharSet);

strstr 函数可以在一个字符串(字符串1)中查找另一个字符串(字符串2),如果字符串2存在于该字符串1中,那么就返回被字符串2在字符串1中第一次出现的起始位置,如果在字符串1中找不到字符串2,那么就返回空指针(NULL)。它的第一个参数是字符串1的起始位置,第二个参数是字符串2的起始位置。

💥注意:

  • 若字符串2为空字符串,则返回字符串1的起始位置。

​ 举个例子,比如我们在字符串 “abcdefbcd” 中查找字符串 “bcd”。

#include<stdio.h>
#include<string.h>
int main()
{char arr1[] = "abcdefbcd";char arr2[] = "bcd";char* ret = strstr(arr1, arr2); // 在arr1中查找arr2字符串第一次出现的位置if (ret != NULL)printf("%s\n", ret);elseprintf("找不到\n");return 0;
}// 运行结果:
bcdefbcd

​ 注意:strstr 函数的返回值是字符串 "bcd" 在字符串 "abcdefbcd"第一次出现的位置的起始位置,而不是出现几次就返回几个起始位置。

2、模拟实现

strstr 函数的模拟实现相对复杂,在实现过程中我们需要设置3个指针变量来辅助实现函数功能。

  • cp指针: 记录每次开始匹配时的起始位置,当从该位置开始匹配时就找到了目标字符串,便于返回目标字符串出现的起始位置;当从该位置开始没有匹配成功时,则从cp++处开始下一次的匹配。
  • p1p2指针: 通过判断p1和p2指针解引用后是否相等来判断每个字符是否匹配成功,若成功,则指针后移比较下一对字符;若失败,p1指针返回cp指针处,p2指针返回待查找字符串的起始位置。

​ 例如,在字符串"abbbcdef"中查找字符串"bbc":
​ 刚刚开始时3个指针的指向如图所示:

在这里插入图片描述

​ 若p1与p2匹配不成功,则cp指针后移,接着将cp指针赋值给p1指针:

在这里插入图片描述

​ 此时,p1与p2匹配成功,那么cp指针不动,p1和p2指针后移继续比较:

在这里插入图片描述

​ 当p1与p2匹配不成功时,cp指针后移一位,p1返回cp位置,p2返回待查找字符串起始位置:

在这里插入图片描述

​ 从此位置开始下一轮的比较:

在这里插入图片描述

​ 直到当p2指向的内容为\0时,便说明待查找字符串中的字符已经被找完,也说明了从当前cp位置开始匹配能够找到目标字符串,所以此时返回指针cp即可。

char* my_strstr(const char* str1, const char* str2)
{assert(str1 != NULL); // 断言,当str1为空指针报错assert(str2 != NULL); // 断言,当str2为空指针报错const char* cp = str1; // 记录开始匹配时的起始位置if (*str2 == '\0') // 要查找的字符串为空字符串return (char*)str1;while (*cp){const char* p1 = cp;const char* p2 = str2;while ((*p1!='\0') && (*p2!='\0') && (*p1 == *p2)){p1++;p2++;}if (*p2 == '\0') // 目标字符串已被查找完return (char*)cp;cp++;}return NULL; // 找不到目标字符串
}

​ 但是这种算法的时间复杂度比较高,优化方法就是 KMP 算法,具体可以看 KMP 算法的笔记!

Ⅶ. strtok – 字符串分割

char *strtok(char *strToken, const char *strDelimit);

strtok 函数能通过给定的一系列字符将一个字符串分割成许多子字符串的函数。它的第一个参数是需要被分割的字符串的首地址;第二个参数是一个字符串的首地址,该字符串是用作分隔符的字符集合。返回值是查找到的标记的首地址。

💥注意:

  • 找到 strToken 中的一个标记时,会将其用 \0 结尾并返回这个标记的首地址。
  • strtok 函数是会改变 strToken 的,所以在使用 strtok 函数切分的字符串都是临时拷贝的内容并且可修改。
  • 第一个参数不为 NULL 时,函数将找到 strToken 中的第一个标记,并保存它在字符串中的位置。
  • 第一个参数为 NULL 时,函数将从同一个字符串中被保存的位置开始查找它的下一个标记。
  • 若字符串中不存在更多的标记,则返回 NULL 指针。

​ 举个例子,比如我们要将字符串"2916776007@qq.com"以"@“字符和”."字符分割开。

#include<stdio.h>
#include<string.h>
int main()
{char arr1[] = "2916776007@qq.com"; // 待分割字符串char arr2[] = "@."; // 分隔符的字符集合char arr3[20] = { 0 };strcpy(arr3, arr1); // 将数据拷贝一份使用,防止原数据被修改char* token = strtok(arr3, arr2); // 第一次传参需传入待分割字符串首地址while (token != NULL) // 说明还未分割完{printf("%s\n", token);token = strtok(NULL, arr2); // 对同一个字符串进行分割,第二次及以后的第一个参数为NULL}return 0;
}// 运行结果:
2916776007
qq
com

注意:strtok 函数找到第一个标记时,将其后的 ’@‘ 字符改为 ’\0’ 并返回第一个标记的首地址,所以我们以返回的地址为首地址开始打印字符串的时候就只会打印出 2916776007,第二次再对该字符串调用 strtok 函数时将从 ’@’ 字符后面开始寻找下一个标记。

Ⅷ. strerror、perror – 错误报告函数

char *strerror(int errnum);

strerror 函数可以把错误码转换为对应的错误信息,返回错误信息对应字符串的起始地址。

void perror(const char *string);

perror 函数可以打印一个错误信息,无返回值。

​ 我们需要知道,库函数在使用的时候如果发生错误,都会有对应的错误码,而这些错误码都会被存放在 errno 这个全局变量中,如果要使用这个全局变量,我们需要引其对应的头文件:#include<errno.h>

​ 举个例子:(注:fopen函数的功能是打开一个文件,当其执行成功时会返回打开文件的首地址,执行失败时会返回一个空指针)

#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{FILE* pf = fopen("test.txt", "r");//打开test.txt文件阅读if (pf == NULL){printf("%s\n", strerror(errno));perror("fopen");}return 0;
}// 运行结果:
No such file or directory
fopen: No such file or directory

当我们要打开一个不存在的文件(test.txt)来阅读的时候,显然fopen函数会执行失败,于是pf指针接收的便是空指针(NULL)。

  • strerror: 只负责将错误码转换为对应的错误信息,不打印。
  • perror: 直接打印错误信息,并且我们可以自己加上注释来明确错误来源于哪个库函数。

在这里插入图片描述

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

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

相关文章

Windows部署NVM并下载多版本Node.js的方法(含删除原有Node的方法)

本文介绍在Windows电脑中&#xff0c;下载、部署NVM&#xff08;node.js version management&#xff09;环境&#xff0c;并基于其安装不同版本的Node.js的方法。 在之前的文章Windows系统下载、部署Node.js与npm环境的方法&#xff08;https://blog.csdn.net/zhebushibiaoshi…

centos 8 中安装Docker

注&#xff1a;本次样式安装使用的是centos8 操作系统。 1、镜像下载 具体的镜像下载地址各位可以去官网下载&#xff0c;选择适合你们的下载即可&#xff01; 1、CentOS官方下载地址&#xff1a;https://vault.centos.org/ 2、阿里云开源镜像站下载&#xff1a;centos安装包…

STM32-笔记40-BKP(备份寄存器)

一、什么是BKP&#xff08;备份寄存器&#xff09;&#xff1f; 备份寄存器是42个16位的寄存器&#xff0c;可用来存储84个字节的用户应用程序数据。他们处在备份域里&#xff0c;当VDD电源被切断&#xff0c;他们仍然由VBAT维持供电。当系统在待机模式下被唤醒&#xff0c;或…

vue-cli项目配置使用unocss

在了解使用了Unocss后&#xff0c;就完全被它迷住了。接手过的所有项目都配置使用了它&#xff0c;包括一些旧项目&#xff0c;也跟同事分享了使用Unocss的便捷性。 这里分享一下旧项目如何配置和使用Unocss的&#xff0c;项目是vue2vue-cli构建的&#xff0c;node<20平常开…

新增文章分类功能

总说 过程参考黑马程序员SpringBoot3Vue3全套视频教程&#xff0c;springbootvue企业级全栈开发从基础、实战到面试一套通关_哔哩哔哩_bilibili 目录 总说 一、功能实现 1.1 Controller层 1.2 Service层 1.3 Impl层 1.4 Mapper层 1.5 测试接口 二、优化 2.1 2.2 一、…

知识图谱常见的主流图数据库

在知识图谱中&#xff0c;主流使用的图数据库包括以下几种&#xff1a; Neo4j&#xff1a;这是目前全球部署最广泛的图数据库之一&#xff0c;具有强大的查询性能和灵活的数据模型&#xff0c;适用于复杂关系数据的存储和查询。 JanusGraph&#xff1a;JanusGraph是一个开源的…

JavaSE学习心得(多线程与网络编程篇)

多线程-网络编程 前言 多线程&JUC 多线程三种实现方式 第一种实现方式 第二种实现方式 第三种实现方式 常见成员方法 买票引发的安全问题 同步代码块 同步方法 Lock锁 生产者和消费者 常见方法 等待唤醒机制 练习 抢红包 抽奖 多线程统计并求最…

Pytorch基础教程:从零实现手写数字分类

文章目录 1.Pytorch简介2.理解tensor2.1 一维矩阵2.2 二维矩阵2.3 三维矩阵 3.创建tensor3.1 你可以直接从一个Python列表或NumPy数组创建一个tensor&#xff1a;3.2 创建特定形状的tensor3.3 创建三维tensor3.4 使用随机数填充tensor3.5 指定tensor的数据类型 4.tensor基本运算…

candb++ windows11运行报错,找不到mfc140.dll

解决问题记录 mfc140.dll下载 注意&#xff1a;放置位置别搞错了

​公专网一体5G工业路由器,智慧电网全链路加密监控管理

随着可再生能源的集成 电网调度策略复杂性增加 需更精细的并网管理以平衡供需 传统电力网络的通信基础落后 难以适应电力设施的广泛分布 和日益增长的管理维护需求 计讯物联5G公专网一体路由器 通过融合公网和专网的优势 有效解决了现代电网对于 高效、灵活和安全通信的需求 ↓…

【Linux】--- 进程的等待与替换

进程的等待与替换 一、进程等待1、进程等待的必要性2、获取子进程status3、进程等待的方法&#xff08;1&#xff09;wait&#xff08;&#xff09;函数&#xff08;2&#xff09;waitpid函数 4、多进程创建以及等待的代码模型5、非阻塞接口 轮询 二、进程替换1、替换原理2、替…

zerotier搭建虚拟局域网,自建planet

基于该开源项目 自建planet节点&#xff0c;更快速&#xff0c;更安全 本教程依据docker-zerotier-planet 项目文档书写&#xff0c;并以linux(centos 7)和windows作为示例&#xff0c;需要其他系统配置方法&#xff0c;可移步项目文档 一. 前置资源 具有外网ip的服务器 后面…

屏幕轻触间:触摸交互从 “感知” 到 “智算” 的隐秘路径

从用户点击屏幕到前端感知及数据处理全流程剖析 引言 在移动智能设备与触摸交互技术深度融合的当下&#xff0c;当我们的手指轻触手机屏幕&#xff0c;一系列复杂且精妙的技术流程便瞬间启动。这一过程涵盖硬件层、驱动层、操作系统层、应用层&#xff0c;甚至延伸到后端的数…

深入Node.js集群:原理、优势与搭建实战,如何应对高并发

文章目录 一、Node.js 集群简介二、Node.js 集群原理剖析2.1 主从模型2.2 负载均衡机制2.3 进程间通信&#xff08;IPC&#xff09; 三、Node.js 集群优势详解3.1 性能提升3.2 高可用性3.3 资源利用率优化 四、Node.js 集群搭建实战4.1 准备工作4.2 创建主控制节点4.3 工作节点…

数字普惠金融对新质生产力的影响研究(2015-2023年)

基于2015—2023年中国制造业上市公司数据&#xff0c;探讨了数字普惠金融对制造业企业新质生产力的影响及作用机理。研究发现&#xff0c;数字普惠金融有助于促进制造业企业新质生产力的发展&#xff0c;尤其是在数字普惠金融的使用深度较大的情况下&#xff0c;其对新质生产力…

数据仓库基础常见面试题

1.数据仓库是什么 ‌数据仓库&#xff08;Data Warehouse&#xff09;是一个面向主题的、集成的、非易失的、随时间变化的数据集合&#xff0c;用于支持企业的管理决策‌。它不同于传统的操作型数据库&#xff0c;后者主要用于处理日常业务交易和实时查询&#xff0c;而数据仓库…

记一次OpenEuler Linux磁盘分区表损坏的数据恢复

问题复现 原本有一台GIS地图服务器存放大量数据&#xff0c;突然有一天磁盘满了&#xff0c;于是运维人员照常进行磁盘扩容。但由于误操作&#xff0c;导致使用fdisk的时候把分区表损坏了&#xff0c;表现如下&#xff1a; 这里可以看到启动时能看到xvda被分为了xvda1和xvda2…

分布式数据存储基础与HDFS操作实践(副本)

以下为作者本人撰写的报告&#xff0c;步骤略有繁琐&#xff0c;不建议作为参考内容&#xff0c;可以适当浏览&#xff0c;进一步理解。 一、实验目的 1、理解分布式文件系统的基本概念和工作原理。 2、掌握Hadoop分布式文件系统&#xff08;HDFS&#xff09;的基本操作。 …

APP推荐:全新TV端来了,8K原画电视版

▌ 软件介绍 B站都不陌生吧&#xff0c;一个能追番、学习、娱乐的多元平台&#xff0c;之前也分享过几款第三方TV端&#xff0c;其中的BV最近更新了全新版本。 使用了全新的UI界面&#xff0c;由之前的顶部菜单栏改成了侧边布局&#xff0c;已解锁限制&…

【数据结构】基础知识

目录 1.1 什么是数据结构 1.2数据 1.3 逻辑结构 1.4 存储结构 1.4.1 顺序存储 1.4.2 链式存储 1.4.3 索引存储 1.4.4 散列存储 1.5 操作 1.1 什么是数据结构 数据的逻辑结构以及存储操作 数据结构没有那么复杂&#xff0c;它就教会你一件事&#xff1a;如何更有效的…