C语言-字符函数与字符串函数

在C语言中,我们经常要对字符或是字符串进行各种操作。那C语言究竟给了我们哪些方法呢,本篇文章就是让大家了解对字符和字符串处理相关的知识。

目录

1.字符函数

1.1字符分类函数

1.2字符转换函数

2.字符串函数

2.1strlen函数的使用和模拟实现

2.2strcpy函数的使用和模拟实现 

2.3strcat函数的使用和模拟实现

2.4strcmp函数的使用和模拟实现

2.5strncpy函数的使用和模拟实现

2.6strncat函数的使用和模拟实现

2.7strncmp函数的使用和模拟实现

2.8strstr函数的使用和模拟实现

2.9strtok函数的使用

2.10strerror函数与perror函数的使用


1.字符函数

字符函数分为字符分类函数字符转换函数。在使用字符函数前,我们要包含头文件ctype.h

1.1字符分类函数

C语言中有一系列函数是专门给字符进行分类的,称为字符分类函数,具体函数如下:

函数参数符合就返回真,反之为假
isalpha字母字符
islower小写字母
isupper大写字母
isdigit数字字符
isalnum字母、数字字符
isspace空白字符:空格" ",换页"\f",换行"\n",回车"\r",制表符"\t",垂直制表符"\v"。
iscntrl控制字符
isprint可打印字符
ispunct标点字符
isgraph图形字符

这些函数的使用方法都类似,我们这里以islower和isupper举例:

1 int islower(char c);2 int isupper(char c);

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

#include <stdio.h>
#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;//大小写字母ASCII码相差32,如'A'为97,'a'为65}i++;}return 0;
}

这里我们就用到了islower函数,只要其中的参数是小写字符,就会返回真。其余字符分类函数都是类似的,大家自行尝试练习。

1.2字符转换函数

C语言中一共有两个字符转换函数:

tolower(char c)将大写字母c转换为小写字母
toupper(char c)将小写字母c转换为大写字母

使用也是非常的简单,我们将上面1.1中代码的转换大小写部分用toupper进行改写:

#include <stdio.h>
#include <ctype.h>int main()
{char arr[] = "I am a student.";int i = 0;while (arr[i] != '\0'){if (islower(arr[i]));{arr[i] = toupper(arr[i]);//将字符串中的每个字符都转换为大写}i++;}return 0;
}

2.字符串函数

我们不仅仅指对字符进行操作,有时也需要对字符串进行操作。和字符串操作相关的函数称为字符串(操作)函数

2.1strlen函数的使用和模拟实现

函数原型:

 size_t strlen(const char* str);

1.strlen函数的参数是即字符串(或字符数组),即首元素地址,统计的是字符串中 '\0' 前面出现的字符个数。

2.函数的返回值是size_t,是无符号的。

3.字符串必须以'\0'结束,否则结果为随机值

4.不能传字符,函数会将其ASCII码值当做地址,由于地址偏小,可能被操作系统所使用,返回值为error

#include <stdio.h>
#include <string.h>int main()
{if (strlen("abc") - strlen("abcdef") > 0)//if ((int)strlen("abc") - (int)strlen("abcdef") > 0){printf("str1>str2\n");}else{printf("str1<=str2\n");}return 0;
}

上述例子中,我们能得到strlen("abc")的值是3,strlen("abcdef")的值是6,3-6的-3,所以结果为str1<=str2。

然而正确结果为str1>str2。这是因为二者的返回值是size_t类型,是无符号的一种整型,所以两个无符号的数相加减得到的数也是无符号的,结果恒为正,应为str1>str2。

5.strlen函数的模拟实现

方法一:count计数器

size_t my_strlen(const char* str)
{assert(str);int count = 0;while (*str != '\0'){str++;count++;}return count;
}

方法二:指针-指针 

size_t my_strlen(const char* str)
{assert(str);char* start = str;while (*str != '\0'){str++;}return str-start;
}

方法三:函数递归

不能创建临时变量计数器的方式

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

2.2strcpy函数的使用和模拟实现 

函数原型:

char* strcpy(char* destination, const char* source);
1.ctrcpy函数是将源字符串source的内容拷贝到目标字符串destination中,因此我们不希望源字符串被改变,类型为const char*。
2.源字符串必须以 '\0' 结束,并且 会将源字符串中的 '\0' 拷拷贝到目标空间。
3.目标字符串的空间 必须足够大,以确保能存放源字符串。
#include <stdio.h>
#include <string.h>int main()
{char arr1[20] = "Wow, ";char arr2[] = "fantastic!";strcpy(arr1, arr2);printf("%s\n", arr2);return 0;
}

显然上面代码的结果为fantastic!

4.strcpy函数的模拟实现

char* my_strcpy(char* dest, const char* src)
{assert(dest&&src);char* ret = dest;while (*dest++ = *src++){;}return ret;
}

我们让src的每一位都赋值给dest,然后++,直到*src为'\0'时,赋值结束,同时也会将'\0'赋值过去。但是我们返回的是dest原来那个位置的地址,所以我们再最开始要定义一个指针ret等于dest,和dest指向相同位置,最后返回这个位置即ret。

2.3strcat函数的使用和模拟实现

函数原型:

char* strcat(char* dest, const char* src);
1.strcat函数是将源字符串source追加到目标字符串后面,因此我们不希望源字符串被改变,类型为const char*。
2.源字符串和目标字符串中必须含有'\0',否则无法知道从哪里开始追加。
3. 目标字符串的空间 必须足够大,以确保能容纳下源字符串的内容。
#include <stdio.h>
#include <string.h>int main()
{char arr1[20] = "Wow, ";char arr2[] = "fantastic!";strcat(arr1, arr2);printf("%s\n", arr1);return 0;
}

上面代码将arr2连接到arr1后面,结果显然为Wow, fantastic!

4.strcat函数的模拟实现

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

其实和strcpy函数模拟实现相差不大,只是要将dest位置移到'\0'的位置,再将src拷贝过来就行了。

5.自己给自己追加

当一个字符串arr自己追加自己的时候,也就是my_strcat(arr,arr),这样代码是有问题的。我们可以仔细看看我们模拟实现的代码再结合当两个字符串都相同进行分析,就会发现当dest移动到'\0'位置将*src++赋值给*dest++时,字符串中的'\0'被不断覆盖,而while循环结束的条件是*src=='\0',也就是程序会不断在while循环中不停止,造成程序崩溃。下图是第一个'\0'被覆盖的情形:

但是我们库函数中的strcat是可以实现字符串的自身追加的,我们的模拟函数在这一点其实是有缺陷的,也许是因为VS中strcat的实现方法和我们不同。但是这也给我们提了个醒,当涉及到字符串自身的追加时,我们还是尽量不要使用strcat,万一不行呢?那具体用什么方法我在之后篇章会写到。

2.4strcmp函数的使用和模拟实现

函数原型:

int strcmp(const char* str1, const char* str2);
1.strcmp函数比较的是两个字符串的内容,因此我们不希望两个字符串的内容发生改变,二者都为
const char*类型。 标准规定stcmp函数:
第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
字符串比较大小比较的是两个字符串中对应位置上字符ASCII码值的大小。
#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "apple";char arr2[] = "applf";char arr3[] = "Apple";printf("%d\n", strcmp(arr1, arr2));//-1printf("%d\n", strcmp(arr2, arr3));//1printf("%d\n", strcmp(arr1, arr3));//1printf("%d\n", strcmp(arr1, arr1));//0return 0;
}

2.strcmp函数的模拟实现

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

虽然VS上的结果分别为-1,0,1,但是我们只需要做到标准规定的要求就行了,即保证是负数、0、正数即可。如果两者开始字母都相等,那就往后一个一个比较,直到二者都为'\0'即两字符串相同,返回0。

2.5strncpy函数的使用和模拟实现

函数原型:

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

1.strncpy函数是将源字符串的num个字符拷贝到目标字符串中,因此我们不希望源字符串被改变,类型为const char*。

2.如果源字符串的长度小于num,则拷贝完源字符串之后,在目标空间的后边追加0,直到num个。

#include <stdio.h>
#include <string.h>int main(){char arr1[20] = "anvelope";char arr2[] = "happy";strncpy(arr1, arr2, 5);printf("%s\n", arr1);//happyopereturn 0;}

3.strncpy函数的模拟实现

char* my_strncpy(char* dest, const char* src, size_t num)
{assert(dest && src);char* str = dest;for (size_t i = 0; i < num; i++){*dest++ = *src++;}return str;
}

我们只要在strcpy的基础上再外加一个循环就行了,循环num次即可。(暂不考虑加0的情况)。

2.6strncat函数的使用和模拟实现

函数原型:

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

1.将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加⼀个 '\0' 。因此我们不希望源字符串被改变,类型为const char*。

2.如果source指向的字符串的长度小于num的时候,只会将字符串中到'\0'的内容追加到destination指向的字符串末尾。

#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "anvelope ";char arr2[20] = "happy";strncat(arr2, arr1, 8);printf("%s\n", arr2);//happy anvelopereturn 0;
}

3.strncat函数的模拟实现

char* my_strncat(char* dest, const char* src, size_t num)
{assert(dest && src);char* str = dest;while (*dest != '\0')dest++;for (size_t i = 0; i < num; i++){*dest++ = *src++;}*dest='\0';return str;
}

仍然在strncat的基础上外加一层循环,最后再追加一个'\0'即可。 

2.7strncmp函数的使用和模拟实现

函数原型:

int strncmp(const char* str1, const char* str2, size_t num);
1.strncmp比较的是str1和str2两个字符串的前num个字符,如果相等就继续往后比较,最多比较到第num个字符。

2.同strcpy标准,但是只比较前num个字符:

第一个字符串大于第二个字符串,则返回大于0的数字
第一个字符串等于第二个字符串,则返回0
第一个字符串小于第二个字符串,则返回小于0的数字
#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "anvelope ";char arr2[] = "happy";int ret = strncmp(arr1, arr2, 3);//-1printf("%d\n", ret);return 0;
}

3.strncpy函数的模拟实现

int my_strncmp(const char* str1, const char* str2, size_t num)
{assert(str1 && str2);while ((*str1 == *str2) && --num){if (*str1 == '\0')return 0;str1++;str2++;}return *str1 - *str2;
}
依然在原strcmp函数的基础上外加一层循环就行了。注意--num不要写成num--,不然会多一次循环。 

2.8strstr函数的使用和模拟实现

函数原型:

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

1.strstr函数返回字符串str2在字符串str1中第⼀次出现的位置,因此并不希望字符串被改变,顾指针类型为const char*。

2.如果str2的第一个字符为'\0',则返回str1。

#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "An apple a day keeps a doctor away.";const char* p = "keep";char* ret = strstr(arr1, p);if (ret != NULL){printf("%s\n", ret);}else{printf("没找到\n");}return 0;
}

上述代码要现在arr1中找到p第一次出现的位置,即第一个字符串中k的位置,返回这个位置,得到的就是从k开始,'\0'结束的字符串,即keeps a doctor away。

3.strstr函数的模拟实现

这个函数的模拟实现比之前的字符串函数难度要高一些,我们要考虑多种情况,也需要涉及多个指针。我们先看下面一种比较简单的情况:

在这种情况中,我们需要几个指针:cur用来遍历str1,当满足相等条件时,用s1和s2来判断后面的几个字符是否都和str2相等,这是最基本的情况,我们接下来看复杂一点的情况:

这种情况和第一种有什么区别呢?主要是在于当cur遍历到第一个字符e的时候,s1和s2会判断cur后面4个字符是否和str2相等,而且这是不相等的情况,因为从第一个e开始是eees,和str2不同。此时我们应该让cur++,从第二个e的位置开始重新利用s1和s2进行比较,发现和s2相等,即返回此时的cur。我们再看一种情况:

这种情况就是,str2并没有再str1中出现,那么我们直接返回NULL就好了。

出了上面三种情况,同时我们还需要注意一些特殊的情况,就是我们是认为*s1和*s2如果相等的话就继续比下一对,只要相等就一直往下比,那就有可能出现下面这种情况:

也就是如果*s1和*s2都为'\0'的时候,s1和s2就会造成越界访问,我们应该避免这种情况。

了解了以上的情况之后,我们就可以开始写代码了

char* my_strstr(const char* str1, const char* str2)
{const char* s1 = NULL;const char* s2 = NULL;const char* cur = str1;if (*str2 == '\0')return (char*)str1;while (*cur){s1 = cur;s2 = str2;while (*s1 == *s2 && *s1 != '\0' && *s2 != '\0'){s1++;s2++;}if (*s2 == '\0')return (char*)cur;cur++;}return NULL;
}

需要注意内层while循环的条件是在*s1等于*s2的同时*s1和*s2都不能为'\0',在这种情况下如果跳出了循环是因为*s2为'\0',那就说明str2在str1中出现了,此时我们返回cur就可以了。特殊地,当s2本身里面就是空的,也就是*s2就已经为'\0',我们直接返回str1就好了,这是极其特殊的情况,我们单独处理即可。

2.9strtok函数的使用

函数原型:

char * strtok ( char * str, const char * sep);
1. sep参数指向一个字符串,定义了用作分隔符的字符集合
2.第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标
记。
3.strtok函数找到str中的下一个标记,并将其用'\0' 结尾,返回一个指向这个标记的指针。(strtok函
数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修
改。)
4.strtok函数的第一个参数不为NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
中的位置。
5.strtok函数的第一个参数为NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标
记。
6.如果字符串中不存在更多的标记,则返回NULL 指针。
#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "has@been.slained";char arr2[30] = { 0 };strcpy(arr2, arr1);//保护arr1const char* sep = "@.";char* ret = strtok(arr2, sep);printf("%s\n", ret);char* ret = strtok(NULL, sep);//strtok会记住其位置,当第二次传参为NULL,继续寻找下一个标记printf("%s\n", ret);char* ret = strtok(NULL, sep);printf("%s\n", ret);return 0;
}

这样写的话当分隔符较多时比较复杂,此时我们可以用for循环来简化代码:

#include <stdio.h>
#include <string.h>int main()
{char arr1[] = "has@been.slained";char arr2[30] = { 0 };strcpy(arr2, arr1);const char* sep = "@.";char* ret = NULL;for (ret = strtok(arr2, sep); ret != NULL; ret = strtok(NULL, sep)){printf("%s\n", ret);}return 0;
}

2.10strerror函数与perror函数的使用

函数原型:

char * strerror ( int errnum );
1.s trerror函数可以把参数部分错误码对应的错误信息的字符串地址返回来。
2.在不用的系统与C语言标准库的实现中都规定了一些错误码,一般是放在errno.h这个头文件当中说明的,C语言成语启动的时候就会使用一个全局的变量errno来记录程序当前的错误码,只不过程序启动时的错误码是0,即没有错误。当我们再使用标准库中的函数发生了某种错误,就会将对应的错误码存放在errno中,而一个错误码的数字都有对应的错误信息,strerror的作用就是将错误对应的错误信息字符串的地址返回。

我们可以多试试几个错误码,看看它所对应的错误信息分别是什么: 

#include <stdio.h>
#include <string.h>
#include <errno.h>int main()
{for (int i = 0; i <= 10; i++){printf("%d:		%s\n", i, strerror(i));}return 0;
}

大家可以自己在编译器上试试,在Windows11+VS2022环境下输出的结果如下:

  1  No error2  Operation not permitted3  No such file or directory4  No such process5  Interrupted function call6  Input/output error7  No such device or address8  Arg list too long9  Exec format error10  Bad file descriptor11  No child processes

我们再来看一个常见的例子:

#include <stdio.h>
#include <string.h>
#include <errno.h>int main()
{//fopen以读的形式打开文件,如果文件不存在,就打开失败FILE* pf = fopen("test.txt", "r");if (pf == NULL){printf("%s\n",strerror(errno));return 1;}//读文件//......//关闭文件fclose(pf);return 0;
}

输出:

No such file or directory

当我们打开文件test.txt时打开失败,即pf为空指针的时候,我们就让它返回错误的信息。那么接下来,我们来看看perror函数。

函数原型:

void perror(const char* str);

perror函数相当于是对strerror的使用进行了封装。我们上面的代码要用printf函数来打印出错误信息,而perror函数直接将错误信息打印出来。perror函数打印完参数部分的字符串后,再打印一个冒号和一个空格,再打印错误信息。所以上述代码我们也可以写成:

#include <stdio.h>
#include <string.h>
#include <errno.h>int main()
{//fopen以读的形式打开文件,如果文件不存在,就打开失败FILE* pf = fopen("test.txt", "r");if (pf == NULL){perror("fopen fail!");return 1;}//读文件//......//关闭文件fclose(pf);return 0;
}

输出:

fopen fail!: No such file or directory

那以上就是字符函数与字符串函数相关的重要知识。当然函数并不止这几个,上述只是我们经常用到或比较重要的。大家在熟练运用本章的函数后可以自行拓展学习新的函数。

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

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

相关文章

数电期末复习(二)逻辑代数基础

这里写目录标题 2.1 二值逻辑变量与基本逻辑运算2.1.1 与运算2.1.2 或运算2.1.3 非运算2.1.4 常用复合逻辑运算 2.2 逻辑函数的建立及其表示方法2.2.1 真值表表示2.2.2 逻辑函数表达式表示2.2.3 逻辑图表示方法2.2.4 波形图表示方法 2.3 逻辑代数2.3.1 逻辑代数的基本定律和恒等…

磁盘的管理

会在linux中使用硬盘 分区 格式化&#xff08;重新安装文件系统&#xff09; 挂载 硬盘的分类 1.机械硬盘 2.固态硬盘 硬盘的数据结构 扇区&#xff1a;盘片被分为多个扇形区域&#xff0c;每个扇区存放512字节的 数据 &#xff08;扇区越多容量越大&#xff09; 存放数据的…

开启智慧之旅,AI与机器学习驱动的微服务设计模式探索

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 &#x1f680; 转载自热榜文章&#x1f525;&#xff1a;探索设计模式的魅力&#xff1a;开启智慧…

排序 “贰” 之选择排序

目录 ​编辑 1. 选择排序基本思想 2. 直接选择排序 2.1 实现步骤 2.2 代码示例 2.3 直接选择排序的特性总结 3. 堆排序 3.1 实现步骤 3.2 代码示例 3.3 堆排序的特性总结 1. 选择排序基本思想 每一次从待排序的数据元素中选出最小&#xff08;或最大&#xff09;的一个…

前后端交互概念

前后端交互概念 1前后端分离开发概念2搭建后端环境2.1配置文件commomcommon-utilservice-utilmodelservice gitee使用 1前后端分离开发概念 前段&#xff1a;运用html、css、js和现成库&#xff0c;对数据作展示。 后端&#xff1a;运用Java和Java框架&#xff0c;提供数据或操…

Redis 逻辑过期策略设计思路

引言&#xff1a; 当我们平常使用Redis缓存的时候&#xff0c;会出现一种场景&#xff0c; redis的key到过期时间了&#xff0c;总是需要到数据库里面去查一遍数据再set回redis&#xff0c;这个时候如果数据库响应比较慢&#xff0c;那么就会造成用户等待&#xff0c;如果刚好…

vue-Router 路由(常量路由)

1、安装 pnpm i vue-router 2、新建文件&#xff1a;src/routes.ts import { RouteRecordRaw } from vue-routerexport const constantRoute: RouteRecordRaw[] [{//path: /,redirect: /login,},{//path: /login,component: () > import(/views/Login/index.vue),name…

Jenkins构建实用场景指南

1 总体说明 本文主要介绍在研发实战时,通过Jenkins解决企业级软件构建打包一些实用场景。通常是在打包构建前,通过命令和工具进行预处理,避免修改源码,可按需配置构建任务,自动持续集成。 2 Jenkins简介 2.1 复制任务 研发实战创建构建任务,推荐从已有的构建任务进行…

解释一下“暂存区”的概念,在Git中它扮演什么角色?

文章目录 暂存区在Git中的概念与作用什么是暂存区&#xff08;Staging Area&#xff09;暂存区的位置和结构 暂存区在Git工作流程中的角色1. 分离工作区与版本库的交互示例代码与操作步骤示例1&#xff1a;将工作区的修改添加至暂存区 2. 控制提交内容的粒度示例2&#xff1a;分…

llama2 与 llama3比较

Llama 3 刚刚在4月18号推出&#xff0c;距 Llama 2 发布正好 9 个月。它已经可以在 Meta 网站上进行聊天&#xff0c;可以从 Huggingface 以 safetensors 或 GGUF 格式下载。 llama 2 与 llama3 比较 1. 模型输出&#xff08;model output&#xff09; llama 2 输出只能是文本…

MySQL下载与安装

文章目录 1&#xff1a;MySQL下载与安装2&#xff1a;配置环境变量3&#xff1a;验证是否安装成功 1&#xff1a;MySQL下载与安装 打开MySQL官网&#xff0c;MySQL 下载链接选择合适的版本和操作系统&#xff0c;页面跳转之后选择No thanks, just start my download.等待下载即…

JAVAEE——IP协议

文章目录 IP协议IP协议报头格式IP协议报头的各个区段四位版本四位首部长度八位服务类型16位总长度16位标识&#xff0c;3位标志&#xff0c;13位片偏移八位生存时间八位协议 地址管理IP地址解决提议1&#xff1a;动态分配Ip地址解决提议2&#xff1a;NAT机制 IP协议 IP协议报头…