在编程的过程中,我们经常要处理字符和字符串,为了方便操作字符和字符串,C语言标准库中提供了一系列库函数,接下来我们就学习一下这些函数。
一、字符函数
1、字符分类函数
C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。这些函数的使用都需要包含一个头文件是ctype.h
函数 | 如果他的参数符合下列条件就返回真 |
iscntrl | 任何控制字符 |
isspace | 空白字符:空格、换页、换行、回车、制表符 |
isdigit | 十进制数字0-9 |
isxdigit | 十六进制数字,包含所有十进制数组,小写字母a-f,大写字母A-F |
islower | 小写字母a-z |
isupper | 大写字母A-Z |
isalpha | 字母a-z或A-Z |
isalnum | 字母或数字,a-z,A-Z,0-9 |
ispunct | 标点符号,任何不属于数字或者字母的圆形字符(可打印) |
isgraph | 任何图形字符 |
isprint | 任何可打印字符,包括图形字符和空白字符 |
给一段代码让大家清楚的看到字符分类函数的用法和返回值,以islower为例:
#include <ctype.h>
int main()
{
int ret = islower('a');
printf("%d\n", ret);
ret = islower('A');
printf("%d\n", ret);
return 0;
}
打印结果:
islower是分类小写字母的函数,当我们给它传一个小写字母时返回非0的值,传一个大写字母判断不是小写字母则返回0。
其他的函数和此函数的使用形式是一模一样的,可以根据以上例子进行使用。
代码练习:将字符串中的小写字母转大写,其他字符不变
#include <ctype.h>
#include <string.h>
#include <stdio.h>
int main()
{
char str[] = "i Like Beijing!";
size_t len = strlen(str);
size_t i = 0;
for (i = 0; i < len; i++)
{
if (islower(str[i]))//遍历判断是否小写
{
str[i] -= 32;//转换大写
}
}
printf("%s\n", str);
return 0;
}
2、字符转换函数
C语言提供了2个字符转换函数:
int tolower(int c);//将参数传进去的大写字母转小写
int toupper(int c);//将参数传进去的小写字母转大写
有了这个函数我们就可以将上面的代码更新一下,不需要+、-32来改变大小写字母,直接使用转换字符函数即可:
#include <ctype.h>
#include <string.h>
#include <stdio.h>
int main()
{
char str[] = "i Like Beijing!";
size_t len = strlen(str);
size_t i = 0;
for (i = 0; i < len; i++)
{
//不用再额外判断字母是不是小写,tolower判断该字符是小写就转换大写,不是就不改
str[i] = toupper(str[i]);//如果是小写字母就转换为大写
//前提是转换结果需要用一块整型空间来接收,因为传值调用,并不能在函数直接内部修改
}
printf("%s\n", str);
return 0;
}
二、字符串函数
3、strlen的使用和模拟实现
strlen库函数功能:求字符串长度,统计的是结束标志\0之前出现的字符个数
strlen库函数的声明:
size_t strlen(const char* str);
strlen函数的调用:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "abcdef";
size_t len = strlen(str);//调用strlen函数,返回值用size_t的变量来接收
printf("%zd\n", len);//打印size_t类型的返回值(字符串长度)
return 0;
}
strlen注意事项:
1、strlen所计算的字符串结尾必须有结束标志 ' \0 '。
2、必须给strlen传递字符串地址,strlen需要通过地址向后访问找到 ' \0 ' 为止,只传递字符会报错。
strlen函数的模拟实现:
仿照strlen的函数参数,返回类型,功能写一个类似的函数
方法1:遍历判断
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
assert(str != NULL);
size_t count = 0;//计数器,统计字符串的长度
while (*str++ != '\0')//后置++,先使用,当解引用以后str++指向下一个元素
{
count++;//不是'\0'就让计数器++一次
}
return count;
}
int main()
{
char str[] = "hello world";
size_t len = my_strlen(str);
printf("%zd\n", len);
return 0;
}
方法2:指针 - 指针
#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
assert(str != NULL);
char* start = str;
while (*start != '\0')
{
start++;
}
return start - str;//\0的地址 - 首字符的地址得到地址直接的元素个数
}
int main()
{
char str[] = "hello world";
size_t len = my_strlen(str);
printf("%zd\n", len);
return 0;
}
方法3:函数递归
#include <stdio.h>
#incldue <assert.h>
size_t my_strlen(const char* str)
{
assert(str != NULL);
if (*str != '\0')
return 1 + my_strlen(str + 1);
else
return 0;
}
int main()
{
char str[] = "hello world";
size_t len = my_strlen(str);
printf("%zd\n", len);
return 0;
}
4、strcpy的使用和模拟实现
strcpy库函数功能:将一个字符串拷贝另一个数组
strcpy函数的声明:
char* strcpy(char* destination, const char* source);
strcpy函数的调用:
#include <string.h>
#include <stdio.h>
int main()
{
char arr1[] = "hello world";
char arr2[20] = { 0 };
strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
strcpy注意事项:
1、strcpy里源字符串必须包含 ' \0 ',因为 ' \0 ' 也会被拷贝到目标空间。
2、strcpy里目标空间必须要有足够大的空间来存储这个拷贝过来的数据。
strcpy函数的模拟实现:
仿照strcpy的函数参数,功能写一个类似的函数
#include <stdio.h>
char* my_strcpy(char* dest, const char* src)
{
char* ret = dest;
assert(dest && src);
while (*dest++ = *src++)//边判断,边赋值
{
;
}
return ret;
}
int main()
{
char arr1[] = "hello world";
char arr2[20] = { 0 };
my_strcpy(arr2, arr1);
printf("%s\n", arr2);
return 0;
}
5、strcat的使用和模拟实现
strcat库函数功能:字符串追加,就是在目标空间的末尾追加上一串源字符串
strcat函数的声明:
char* strcat(char* destination, const char* source);
strcat函数的调用:
#include <string.h>
#include <stdio.h>
int main()
{
char arr1[20] = "hello";
char arr2[] = "world";
strcat(arr1, arr2);
printf("%s\n", arr2);
return 0;
}
从arr1末尾的 ' \0 ' 开始,拷贝源字符串arr2,将arr1的末尾追加上arr2
strcat注意事项:
1、目标空间必须有足够大的空间进行追加。
2、目标空间结尾和源字符串结尾都必须有 ' \0 '。
strcat函数的模拟实现:
仿照strcat的函数参数,功能写一个类似的函数
#include <stdio.h>
char* my_strcat(char* dest, const char* src)
{
assert(dest && src);
char* ret = dest;
while (*dest != '\0')//先找到目标空间的'\0',方便追加
{
dest++;
}
while (*dest++ = *src++)//从dest的'\0'位置开始追加
{
;
}
return ret;
}
int main()
{
char arr1[20] = "hello ";
char arr2[] = "world";
my_strcat(arr1, arr2);
printf("%s\n", arr1);
return 0;
}
6、strcmp的使用和模拟实现
strcmp库函数功能:用来比较两个字符串的大小关系
strcmp的函数声明:
int strcmp(const char* str1, const char* str2);
注意strcmp比较的不是两个字符串的长度的,而是比较两个字符串中对应位置上的字符,按照字典序比较。
标准规定:
- 第一个字符串大于第二个字符串,则返回大于0的数字
- 第一个字符串等于第二个字符串,则返回0
- 第一个字符串小于第二个字符串,则返回小于0的数字
- 那么如何判断两个字符串?比较两个字符串中对应位置字符的ASCII码值的大小
strcmp函数的调用:
#include <stdio.h>
#include <string.h>
int main()
{
int ret1 = strcmp("abcdef", "abq");
int ret2 = strcmp("abcdef", "abcdef");
int ret3 = strcmp("abq", "abcdef");
printf("%d %d %d", ret1, ret2, ret3);//打印-1 0 1
return 0;
}
strcmp函数的模拟实现:
#include <stdio.h>
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;
}
int main()
{
int ret = my_strcmp("abcdef", "abc");
printf("%d", ret);
return 0;
}
7、桃园三结义:长度受限制函数strncpy、strncat、strncmp
前面的三个函数strcpy、strcat、strcmp是长度不受限制的字符串函数,他们仨还有长度受限制的函数,分别是:strncpy、strncat、strncmp,和前面的strcpy、strcat、strcmp的功能是相同的,参数上多了一个值,这个值就是限制字符串函数的执行功能长度限制,表面上不同的是str后面多了一个n,干了这碗wine ( 酒 ) 后我们仨就正式结拜为兄弟。
比如我要拷贝"hello world"到一个空间,但是只想拷贝 "hello" 这6个字符,就可以考虑用长度受限制的字符串拷贝函数strncpy。
strncpy函数的声明:
char* strncpy(char* destination, const char* source, size_t num);
strncat函数的声明:
char* strncat(char* destination, const char* source, size_t num);
strncmp函数的声明:
char* strncmp(char* destination, const char* source, size_t num);
它们的功能大概就是:
strncpy:限制拷贝字符个数
strncat:限制字符追加个数
strncmp:限制字符串比较字符个数
所以具体函数调用就不再一一介绍了,知道是什么功能限制什么就可以了
8、strstr的使用和模拟实现
strstr库函数功能:在一个字符串中查找另一个字符串,简单概述就是判断第二个字符串是不是第一个字符串的子字符串
strstr的函数声明:
char* strstr(const char* str1, const char* str2);
strstr函数返回str2在str1中第一次出现的位置
如果str2在str1中没有出现,就返回NULL
strstr函数的调用:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "abbcde";
char str2[] = "bc";
char* s = strstr(str1, str2);
if (s == NULL)
{
printf("str2不是str1的子串\n");
}
else
{
printf("%s\n", s);
}
return 0;
}
strstr函数的模拟实现:
仿照strlen的函数参数,返回类型,功能写一个类似的函数
方法1:暴力求解
#include <stdio.h>
#include <string.h>
#include <assert.h>
const char* my_strstr(const char* str1, const char* str2)
{
assert(str1 && str2);
const char* s1 = NULL;
const char* s2 = NULL;
const char* start = s1;
if(*str2=='\0')
return str1;
while (*start)
{
s1 = start;//重置位置
s2 = str2;//重置位置
while (*s1==*s2 && *s1!='\0')//如果相等就++继续判断下一个
{
s1++;
s2++;
}
if (*s2 == '\0')//结束循环后判断是不是因为*s2为\0结束的
{
return start;//返回刚开始的判断位置
}
start++;//下一个位置继续对比判断
}
return NULL;
}
int main()
{
char str1[] = "abbbcde";
char str2[] = "bbc";
const char* s = my_strstr(str1, str2);
if (s == NULL)
{
printf("str2不是str1的子串\n");
}
else
{
printf("%s\n", s);
}
return 0;
}
方法2:KMP算法-效率高,但是难度大,难以理解
有兴趣可以自己去了解一下。
9、strtok的使用
以后学习计算机网络时,会学到点分十进制表示的IP地址,例如:192.168.101.25,由点分开的十进制就叫点分十进制,IP地址本质是一个整数,不好记,所以才有了点分十进制表示方法
既然IP地址是用 ' . ' 隔开的,那可以将每个隔开的段拿出来吗?
比如:192,168,101,25这四个由 ' . ' 隔开的段。
当然可以。这里就要是用到strtok函数。该函数可以通过分隔符将一个字符串的每个分割段拿出来。
strtok函数的声明:
char* strtok(char* str, const char* sep);
strtok函数功能:
- sep参数指向一个字符串,定义了用作分隔符的字符集合
- 第一个参数指定一个字符串,它包含了0个或者多个有sep字符串中一个或多个分隔符分割的标记
- strtok函数找到str中的下一个标记,并将其用 ' \0 ' 结尾,返回指向这个标记的指针。(注:strtok分割字符串时是会改变传参过来的字符串的,如果不想改变就拷贝一个传参)
- strtok函数的第一个参数不为NULL,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置
- strtok函数的第一个参数为NULL,函数将在同一个字符串中被保存的位置开始,查找下一个标记
- 如果字符串中不存在更多的标记,则返回NULL指针
简单来说就是第一次调用strtok函数时需要传参传一个需要分割的字符串,他会分隔符位置置为 '\0' 返回已经分割的第一段。但是它一直停留在 ' \0 '的位置,所以下一次调用直接传递NULL就可以继续沿着' \0 '的位置继续向后找分隔符分割成段并返回,直到没有可以分割的段时返回NULL。
注:如果第一次分割字符串后,想继续分割该字符串,调用时可以直接传NULL,因为出了函数不会销毁这个分割后的字符串,一直保存着这个字符串,下一次调用时可以直接传NULL便可继续使用该字符串,是因为该字符串可能被static修饰过,出了作用域不会被销毁。
如果想分割其他字符串,不想分割该字符串。就传其他字符串,不再传递NULL。然后strtok会以刚传的其他字符串为开头,下一次调用传NULL便可继续分割其他的字符串。
strtok函数的调用
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "linlu1024@yeah.net";
char buf[30] = { 0 };
strcpy(buf, arr);
char* p = "@.";
char* s = NULL;
for (s = strtok(buf, p); s != NULL; s = strtok(NULL, p))//根据strtok的特性去遍历
{
printf("%s\n", s);//遍历打印字符串的分段
}
return 0;
}
10、strerror的使用
strerror库函数功能:返回一个错误信息字符串的起始地址,简单概述就是返回一个错误码所对应的错误信息字符串的起始地址,这个错误码就是我们调用时传递的实参。
strerror函数的声明:
char* strerror(int errnum);
strerror函数的调用:
#incldue <stdio.h>
#include <string.h>
int main()
{
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%s\n", strerror(i));
}
return 0;
}
那我们一般什么情况下使用该函数呢?
errno的介绍
首先要知道当库函数调用失败的时候,会将错误码记录到errno这个变量中,errno是记录最后一次错误码的。代码是一个int类型的值,在errno.h中定义,所以使用时需要包含errno.h头文件
errno是C语言规定的一个全局变量,用来存储错误码
然后先暂时了解一下文件操作函数,fopen是用来开辟文件信息区将文件信息放入信息区并返回该文件信息区的函数,然后我们就可以通过FILE*类型的指针接收这个fopen返回的文件信息区的地址。
所以当我们调用fopen函数失败时,此时打开文件失败的错误信息的错误码会记录在errno变量中,我们就可以通过这个strerror将这个错误码对应的错误信息打印出来:
#include <stdio.h>
#include <string.h>
#include <errno.h>
int main()
{
FILE* pf = fopen("add.txt", "r");//文件不存在,打开失败返回NULL
if (pf == NULL)
{
printf("打开文件失败,失败原因: %s\n", strerror(errno));//打印错误信息
return 1;
}
else
{
printf("打开文件成功\n");
}
return 0;
}
perror库函数的介绍:
还有一个库函数叫perror,和上面的strerror一样,也是打印错误信息的,唯一不同的区别就是返回类型。
perror和strerror的区别:
- strerror接收到错误码是找到对应的错误信息的地址并返回,我们想打印就打印,不想打印就暂时存起来。
- perror做的就有点绝,它不返回错误信息的起始地址,而是接收到错误码后直接在函数内部打印错误信息。
- 注:perror不需要穿参,是可以直接获取errno的错误码打印出错误信息。
这就是strerror和perror的区别。
到这里,本篇C语言的字符函数和字符串函数也就结束了,如果有什么问题就请在评论区留言,下一篇C语言笔记再见