前言:经历了漫长的指针学习,终于能够开启新的内容了,本篇文章要讲的是字符函数与字符串函数。平时刷题的过程中我们总会遇到一些字符串相关的问题需要字符串函数来解决,但却缺乏对那些字符函数的讲解别担心本篇我致力于搞定字符函数与字符串函数的问题,接下来我们就学习⼀下这些函数。
本篇文章内容超级多请各位读者按需阅读噢!
有一些代码上的优化由于篇幅有限我已经上传了资源在资源处查找噢!
文章目录
一,字符分类函数
C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。 这些函数的使⽤都需要包含⼀个头文件是 ctype.h。
这些函数的使用方法都非常类似下面以一个函数为例:
以 int islower ( int c );
这个函数为例:
- 参数为int类型 ,通过ASCAII码来判断是否是小写。
- 返回值也为int,通过返回值来说明是否是小写字母,如果是小写字母就返回非0的整数,如果不是小写字母,则返回 0。
#include <stdio.h>
#include <ctype.h>
int main ()
{
int i = 0;
char str[] = "Test String.\n";
char c;
while (str[i])
{
c = str[i];
if (islower(c))
{
c -= 32;
}
putchar(c);
i++;
}
return 0;
}
}
剩余的一些字符分类函数的使用放在资源里了,想要了解的资源里下载噢。
二,字符转换函数
1. int tolower ( int c ); //将参数传进去的大写字母转小写
- 参数为int类型 ,通过ASCAII码来判断是否是大写。
- 返回值也为int,通过返回值来说明是否是大写字母,如果是大写字母就返回非0的整数,如果不是大写字母,则返回 0。
2.int toupper ( int c ); //将参数传进去的小写字母转大写
- 参数为int类型 ,通过ASCAII码来判断是否是小写。
- 返回值也为int,通过返回值来说明是否是小写字母,如果是小写字母就返回非0的整数,如果不是小写字母,则返回 0。
下面展示使用方法:
#include<string.h>
#include<ctype.h>
//使用字符转换函数来转换大小写
//转大写函数
void trance_upper(char p[])
{
int i = 0;
while (p[i])
{
if (islower(p[i]))
{
p[i] = toupper(p[i]);//小写转大写
}
printf("%c", p[i]);
i++;
}
}
//转小写函数
void trance_lower(char p[])
{
int i = 0;
while (p[i])
{
if (isupper(p[i]))
{
p[i] = tolower(p[i]);//大写转小写
}
printf("%c", p[i]);
i++;
}
}
int main()
{
char p[] = "aaaaDDFaaaRTVGB\n";
trance_upper(p);//打印函数 打印小写转大写后的p字符串
trance_lower(p);//打印大写转小写后的p字符串
return 0;
}
上面的字符函数相对来说比较简单,因为参数只有一个且不涉及指针返回类型也是普通整型。所以对于字符函数的介绍就到这里,下面是重头戏我们要着重介绍字符串函数的使用以及该函数的模拟实现。
三,字符串函数
1,strlen的模拟实现
在前面指针的章节中我们曾经介绍过strlen这个函数的模拟实现,下面我们再来具体了解一下这个函数:
size_t strlen(const char* str);
1. 参数str是一个指针 接收的是地址 返回类型为size_t无符号整数类型。
2. 它的功能是求字符串长度统计\0之前(不包含\0)的字符个数;参数指向的字符串必须要以 ‘\0’ 结束。
但有一点需要特别注意就是他的返回类型是 size_t
无符号类型。为什么说特别容易错呢?举个例子
#include<string.h>
#include<stdio.h>
int main()
{
const char* str1 = "abcdef";
const char* str2 = "bbb";
if(strlen(str2)-strlen(str1)>0)
{
printf("str2>str1\n");
}
else
{
printf("srt1>str2\n");
}
return 0;
}
这段代码输出的结果是上面呢?按照正常思路分析strlen(str2)==3
而strlen(str1)==6
让他们相减得到-3;if语句
为假走到else
打印str1>str2。我们运行起来看一看结果:
结果是str2>str1也就是打印了if语句下的内容?这是为什么呢?别忘了strlen返回值是size_t
类型所以我们将-3看成是一个无符号数,如果将-3看成是一个无符号数的话将是一个非常大的正数。
下面我们就来看一下strlen的模拟实现:
/******** strlen的模拟实现 *********/
#include<assert.h>
size_t my_strlen1(char p[])
{
int count = 0;//计数器
assert(p != NULL);
while (*p != '\0')
{
count++;
p++;
}
//printf("%d", count);
return count;
}
//模拟实现2 递归法
size_t my_strlen2(const char* p)
{
assert(p != NULL);
if (*p == '\0')
{
return 0;
}
else
{
return 1 + my_strlen2(p + 1);//
}
}
//模拟实现2 使用指针运算法
size_t my_strlen3(const char* p)
{
assert(p != NULL);
const char* init = p;//创建一个init变量接收p的初始值方便返回给
while (*p != '\0')
{
p++;
}
return p - init;
}
int main()
{
char p[] = "abcdefg";
size_t ret1 = my_strlen1(p);
size_t ret2 = my_strlen2(p);
size_t ret3 = my_strlen3(p);
printf("%zd\n", ret1);
printf("%zd\n", ret2);
printf("%zd\n", ret3);
}
1. 第一种方法是使用p变量遍历整个p数组直到找到\0停下期间p每往后走一次就让计数器count++一下最终得到的就是字符的个数。
2. 第二种使用递归的方法,递归结束的条件就是当p遇到\0的时候停下,当p1=\0的时候就将该数组分分1个字符去加上后面的字符 以此类推寻找未知。对于递归不理解的话可以去看我之前讲递归的文章递归
3. 第三种就是指针运算通过地址的加减得到字符个数这里我们在讲指针的时候也讲过。
2,strcpy的模拟实现
首先先来认识一下这个函数:
char* strcpy(char* destination, const char* source);
- 第一个参数是要复制的目标地址。
- 第二个参数是要复制的起始位的地址 源头的地址。
- 返回值 返回的是dest即目标的首地址。
/*** strcpy的模拟实现 ****/
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcdef";
//char arr2[20] = {0};
char arr2[20] = "xxxxxxxxxx";
char *ret=strcpy(arr2, arr1);
printf("%s", ret);//打印"abcdef"
}
在模拟strcpy的时候还要注意几个点:
- 源字符串必须以 ‘\0’ 结束。
- 会将源字符串中的 ‘\0’ 拷贝到目标空间。
- 目标空间必须足够大,以确保能存放源字符串。
- 目标空间必须可修改。
下面我们来模拟,在模拟前我们需要知道整个逻辑是什么样的我们画图展示:
char* my_strcpy(char* dest,const char* src)
{
assert((dest && src) != NULL);
char* ret = dest;
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;//将\0给dest
return ret;
}
还有一些代码上的优化由于篇幅有限我已经上传了资源在资源处查找噢!
3,strcat的模拟实现
先来认识一下这个函数:
/char* strcat(char* dest, const char* src);
1. 第一个参数是要拼接的目标地址。
2. 第二个参数是要拼接的起始位的地址 源头的地址。
3. 返回值 返回的是dest即目标的首地址。
int main()
{
char arr1[20] = "hello";//目标空间要足够大 如果不给arr1初始化大小 将arr2拼接到arr1后就会报错
// 因为arr1会根据内容的大小分配大小为6的空间 在将bit.这4个字符放到arr1中就放不下了
char arr1[20] = "hello\0xxxxxxxxx";//通过调试我们知道拼接会覆盖目标数字的\0 即从\0处开始向后拼接arr2数字的所有内容包括\0
char arr2[] = "bite.";
char*ret=strcat(arr1, arr2);
printf("%s", ret);
return 0;
}
在模拟strcat的时候还要注意几个点:
源字符串必须以 ‘\0’ 结束。
- 目标字符串中也得有 \0 ,否则没办法知道追加从哪里开始。
- 目标空间必须有足够的大,能容纳下源字符串的内容。
- 目标空间必须可修改。
char* my_strcat(char* dest, char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest != '\0')
{
dest++;
}
//走到这说明dest已经到达\0了
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
//到这src==\0
*dest = *src;//处理\0
return ret;
}
还有一些代码上的优化由于篇幅有限我已经上传了资源在资源处查找噢!
4,strcmp的模拟实现
我们先来认识一下这个函数:
int strcmp(const char* string1, const char* string2);
1. 第一个参数是第一个需要比较的字符串的地址。
2. 第二个参数是第二个需要比较的字符串的地址。
3. 返回值是int 返回大于等于小于零的数。
int main()
{
char arr1[] = "abcdef";//比较的本质是 比较两个字符串中对应位置上字符ASCII码值的大小
char arr2[] = "abcdq";
int ret =strcmp(arr1, arr2);
//如果arr1>arr2返回大于0的数
//如果arr1=arr2返回0
//如果arr1<arr2返回小于0的数
if (ret > 0)
{
printf("arr1>arr2");
}
else if (ret == 0)
{
printf("arr1==arr2");
}
else
{
printf("arr1<arr2");
}
return 0;
}
在模拟strcmp函数时需要注意字符串比较,比较的是两个字符串中对应位置上字符ASCII码值的大小。这个函数的模拟就相对来说比较简单了,我们只需使用循环拿到每个字符进行比较即可,我们给出代码:
int my_strcmp(char* str1, char* str2)
{
assert(str1 && str2);//断言一下避免传入的是空指针
while (*str1 == *str2)//如果一样进入循环++比较下一个元素
{
if (*str1 == *str2 && *str1 == '\0')//从头到尾都相等则整个字符串都相等
{
return 0;
}
str1++;
str2++;
}
if (*str1 > *str2)//不相等就判断
{
return 1;//大于
}
else
{
return -1;//小于
}
}
5,strncpy的模拟实现
char* strncpy(char* destination, const char* source, size_t num);
1. 第一个参数接受目标的首地址(要拷贝到的数组) 。
2. 第二个参数接受源头的首地址 。
3. 第三个参数接受需要复制的个数 cahr*+1
跳过一个字节可以写成循环。
函数功能:
拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个。
int main()
{
char arr1[20] = "xxxxxxxxxxxxxxxxxx";
char arr2[] = "youkonwbite";
char*ret=strncpy(arr1, arr2, 12);//通过调试我们发现 strncpy就是纯粹的将需要拷贝的字符给拷贝到目的地
//除非是将arr2的\0拷贝过去 不然单纯拷贝过去是不会自动加\0的
printf("%s", ret);
return 0;
}
在模拟之前我们需要明白strncpy与strcpy的区别就是有了字符限制,本质上就是多加了一个条件,内部的模拟逻辑跟模拟strcpy时是一样的。我们给出代码:
#include<assert.h>
char* my_strncpy(char* dest, const char* src, size_t num)
{
int i = 0;
assert(dest && src);
char* ret = dest;//将dest给ret方便记录初始地址 因为返回值返回的是目标数组的首地址
for (i = 0;i < num;i++)//需要复制几个就循环几次
{
*dest = *src;//内部逻辑与上面模拟strcpy时一样
dest++;
src++;
}
return ret;
}
6,strncat的模拟实现
先来认识函数:
char* strncat(char* destination, const char* source, size_t num);
1. 第一个参数接受目标的首地址(要拼接到的数组) 。
2. 第二个参数接受源头的首地址 。
3. 第三个参数接受需要复制的个数 cahr*+1
跳过一个字节可以写成循环
int main()
{
char arr1[] = "bite";
char arr2[20] = "hellonnn\0nnnn";//拼接是碰到\0后才开始拼接到后面
//拼接的本质是先找到目标字符串的\0从该位置开始拼接 拼接完后再加上\0
char *ret=strncat(arr2, arr1, 2);
printf("%s", ret);
return 0;
}
要注意:
1.将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加⼀个\0字符。
2.如果source 指向的字符串的长度小于num的时候,只会将字符串中到 \0 的内容追加到destination指向的字符串末尾。
接下来来模拟实现:
#include<assert.h>
char* my_strncat(char* dest, char* src, size_t num)
{
assert(dest && src);
char* ret = dest;//将dest给ret方便记录初始地址 因为返回值返回的是目标数组的首地址
while (*dest != '\0')
{
dest++;
}
//到这dest就已经到达目标字符串 \0的位置了 开始拼接
int i = 0;
for (i = 0;i < num;i++)//这个for循环与上面的类似 需要拼接几个就循环几次
{
*dest = *src;//内部逻辑依然一样
dest++;
src++;
}
//到这就已经拼接完成了 但是还要给目标数组加上\0
*dest = '\0';
return ret;
}
7,strncmp的模拟实现
先来认识函数:
int strncmp(const char* str1, const char* str2, size_t num);
1. 第一个参数是参与比较的第一个字符串
2. 第二个参数是参与比较的第二个字符串
3. 第三个参数接受需要比较的个数 cahr*+1
跳过一个字节
比较原理:
⽐较str1和str2的前num个字符,如果相等就继续往后⽐较,最多⽐较num个字⺟,如果提前发现不⼀ 样,就提前结束,⼤的字符所在的字符串⼤于另外⼀个。如果num个字符都相等,就是相等返回0.
int main()
{
char str1[] = "abcdef";
char str2[] = "abcdq";
int ret = strncmp(str1, str2, 5);
if (ret > 0)
{
printf("str1>str2");
}
else if (ret == 0)
{
printf("str1==str2");
}
else
{
printf("str1<str2");
}
return 0;
}
下面我们来模拟实现我们给出代码:
#include<assert.h>
int my_strncmp(char* str1, char* str2, size_t num)
{
while (num-- && (*str1 == *str2))//num控制比较的字符个数 比较几个就循环几次
{
assert(str1 && str2);
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
if (*str1 > *str2)
{
return 1;
}
else
{
return -1;
}
}
7,strstr的模拟实现
char* strstr(const char* str1, const char* str2);
//strstr函数找子字符串
1. 第一个参数 str1接受第一个字符串的地址。
2. 第二个参数 str2接受第二个字符串的地址。
3. 从str1 中找str2 如果能找到则返回 str1中与str2第一个字符一样的字符的地址。
下面我们来模拟:
注意!函数返回字符串str2在字符串str1中第⼀次出现的位置。
字符串的⽐较匹配不包含 \0 字符,以 \0 作为结束标志。
由于篇幅实在有限我们这里给出分析,代码已经上传到资源了!
四,strtok函数的使用
char * strtok ( char * str, const char * sep);
- 第一个参数接受需要切割数组的首地址
- 第二个参数 为存放分隔符的指针
- strtok函数的第⼀个参数不为NULL ,函数将找到str中第⼀个标记,strtok函数将保存它在字符串中的位置并返回。
- strtok函数的第⼀个参数为NULL ,函数将在同⼀个字符串中被保存的位置开始,查找下⼀个标记找到后再保存再返回。
举个例子:
int main()
{
char arr1[] = "[email protected]";
char tmp[20] = {
0 };
strcpy(tmp, arr1);
//切割tmp
const char* sep = "@.";//存放分割符
char* p = strtok(tmp, sep);//第一个参数不为NULL找到@ 改成\0记住该位置 并返回'h'的地址
printf("%s\n", p);
p = strtok(NULL, sep);//第二个参数为NULL则从\0处起找到. 改成\0记住该位置 并返回'b'的地址
printf("%s\n", p);
p = strtok(NULL, sep);//第三个参数为NULL则从第二个\0起找到下一个. 改成\0记住该位置 并返回'n'的地址
printf("%s\n", p);
p = strtok(NULL, sep);//第四个参数为NULL则从第三个\0开始往后找下一个分隔符 发现没有了就返回空指针
printf("%s\n", p);
return 0;
}
优化版本放在资源里了!
五,strerror的使用
char* strerror(int errnum);
功能是记录错误码 错误码记录在errno中
1.参数是一个错误码(一个整数)
2.返回值 把参数部分错误码对应的错误信息的字符串地址返回来。
举个例子:
#include<errno.h>
int main()
{
int i = 0;
for (i = 0;i < 10;i++)
{
char* p = strerror(i);
printf("%d:%s\n", i, p);
}
return 0;
}
还有一个返回错误信息的函数叫perror,它可以直接将错误信息 打印出来我们举一个打开文件失败的例子:
int main()
{
//打开文件
//打印文件失败的时候,会返回NULL指针
//如果打开成功,返回的是非NULL指针
//以r的形式打开文件,文件如果不存在,则打开失败
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
//printf("%s\n", strerror(errno));
perror("error");//perror就相当于printf("%s\n", strerror(errno));这一句
return 1;
}
//...
return 0;
}
以上就是本章的全部内容啦!本篇文章内容超级多请各位读者按需阅读噢!
最后感谢能够看到这里的读者,如果我的文章能够帮到你那我甚是荣幸,文章有任何问题都欢迎指出!制作不易还望给一个免费的三连,你们的支持就是我最大的动力!