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

1. 字符分类函数

C语言中有一系列的函数是专门做字符分类的,也就是一个字符是属于什么类型的字符的。
这些函数的使用都需要包含一个头文件是 ctype.h
这些函数的使用方法非常类似,我们就讲解一个函数的事情,其他的非常类似:
int islower ( int c );
islower 是能够判断参数部分的 c 是否是小写字母的。
通过返回值来说明是否是小写字母,如果是小写字母就返回非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;
}

putchar(c) 将转换后的字符输出到控制台。

2. 字符转换函数

C语言提供了2个字符转换函数:
int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写
int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写
上面的代码,我们将小写转大写,是-32完成的效果,有了转换函数,就可以直接使用 tolower
数。
#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 = toupper(c);
putchar(c);
i++;
}
return 0;
}

3. strlen 的使用和模拟实现

size_t strlen ( const char * str );
字符串以 '\0' 作为结束标志,strlen函数返回的是在字符串中 '\0' 前面出现的字符个数(不包
'\0' )。
参数指向的字符串必须要以 '\0' 结束。
注意函数的返回值为 size_t,是无符号的( 易错 )
strlen的使用需要包含头文件
学会strlen函数的模拟实现
#include <stdio.h>
#include <string.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的模拟实现:
方式1:
//计数器⽅式
int my_strlen(const char * str)
{
int count = 0;
assert(str);
while(*str)
{
count++;
str++;
}
return count;
}

方式2:

//不能创建临时变量计数器
int my_strlen(const char * str)
{
assert(str);
if(*str == '\0')
return 0;
else
return 1+my_strlen(str+1);
}
方式3:
//指针-指针的⽅式
int my_strlen(char *s)
{
assert(str);
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}

4. strcpy 的使用和模拟实现

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

将源字符串指向的C字符串复制到目标数组中,包括终止的空字符(并在该点停止)。

源字符串必须以 '\0' 结束。

会将源字符串中的 '\0' 拷贝到目标空间。

目标空间必须足够大,以确保能存放源字符串。

目标空间必须可修改。

学会模拟实现。

strcpy的模拟实现:

//1.参数顺序
//2.函数的功能,停⽌条件
//3.assert
//4.const修饰指针
//5.函数返回值
//6.题⽬出⾃《⾼质量C/C++编程》书籍最后的试题部分
char* my_strcpy(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while((*dest++ = *src++))
{
;
}
return ret;
}

5. strcat 的使用和模拟实现

将源字符串的副本追加到目标字符串后。目标字符串的终止空字符将被源字符串的首字符覆盖,且在目标字符串中由两者连接形成的新字符串末尾会包含一个空字符。
源字符串必须以 '\0' 结束。
目标字符串中也得有 \0 ,否则没办法知道追加从哪里开始。
目标空间必须有足够的大,能容纳下源字符串的内容。
目标空间必须可修改。
字符串自己给自己追加,如何?

模拟实现strcat函数:

char *my_strcat(char *dest, const char*src)
{
char *ret = dest;
assert(dest != NULL);
assert(src != NULL);
while(*dest)
{
dest++;
}
while((*dest++ = *src++))
{
;
}
return ret;
}

    my_strcat 找到的是字符串的末尾(\0),不是缓冲区的末尾,只要缓冲区足够大,复制就不会越界;

    越界只发生在 “缓冲区空闲空间 < 源字符串长度” 时;

    实际开发中,一定要确保目标缓冲区足够大,或使用带长度限制的 strncat

    核心逻辑:字符串的末尾是 “逻辑结束”,缓冲区的末尾是 “物理结束”,两者不是一回事。

    6. strcmp 的使用和模拟实现

    该函数开始比较每个字符串的第一个字符。若两者相等,则继续比较后续字符对,直至字符不一致或遇到终止空字符为止。

    标准规定:

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

    第一个字符串等于第二个字符串,则返回0

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

    那么如何判断两个字符串? 比较两个字符串中对应位置上字符ASCII码值的大小。

    strcmp函数的模拟实现:

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

    7. strncpy 函数的使用

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

    将源字符串的前num个字符复制到目标字符串。若在完成num个字符复制前检测到源字符串C字符串的结束(以空字符为标志),则在目标字符串末尾补零,直至总字符数达到num个。

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

    8. strncat 函数的使用

    char * strncat ( char * destination, const char * source, size_t num );
    将source指向字符串的前num个字符追加到destination指向的字符串末尾,再追加一个 \0
    符。
    如果source 指向的字符串的长度小于num的时候,只会将字符串中到\0 的内容追加到destination指向的字符串末尾。
    /* strncat example */
    #include <stdio.h>
    #include <string.h>
    int main ()
    {
    char str1[20];
    char str2[20];
    strcpy (str1,"To be ");
    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 );
    比较str1和str2的前num个字符,如果相等就继续往后比较,最多比较num个字母,如果提前发现不一样,就提前结束,大的字符所在的字符串大于另外⼀个。如果num个字符都相等,就是相等返回0。

    10. strstr 的使用和模拟实现

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

    函数返回字符串str2在字符串str1中第一次出现的位置。

    字符串的比较匹配不包含 \0 字符,以 \0 作为结束标志。

    /* strstr example */
    #include <stdio.h>
    #include <string.h>
    
    int main ()
    {
    char str[] ="This is a simple string";
    char * pch;
    pch = strstr (str,"simple");
    strncpy (pch,"sample",6);
    printf("%s\n", str);
    return 0;
    }
    strstr的模拟实现:
    char * strstr (const char * str1, const char * str2)
    {
    char *cp = (char *) str1;
    char *s1, *s2;
    if ( !*str2 )
    return((char *)str1);
    while (*cp)
    {
    s1 = cp;
    s2 = (char *) str2;
    while ( *s1 && *s2 && !(*s1-*s2) )
    s1++, s2++;
    if (!*s2)
    return(cp);
    cp++;
    }
    return(NULL);
    }

    函数定义
    const char *str1:被查找的主字符串,只读。
    const char *str2:要查找的子串,只读。
    返回值 char *:找到子串时返回其在 str1 中的起始地址;未找到返回 NULL。
    初始化与特殊情况处理
    cp 是遍历主字符串的指针,初始化为 str1 的起始地址。
    如果子串 str2 是空字符串(!*str2 为真),直接返回 str1 的起始地址(按 C 标准,空串在任何字符串的开头都存在)。
    遍历主字符串
    外层循环:用 cp 遍历主字符串 str1,直到遇到 \0。
    每次循环时,s1 指向当前 cp 的位置(作为当前匹配的起点),s2 指向子串 str2 的起始位置。
    逐字符匹配子串
    内层循环:逐字符比较 s1 和 s2 指向的字符。
    条件 *s1 && *s2 && !(*s1 - *s2) 表示:两个字符都不为 \0,且 ASCII 码相等(*s1 - *s2 == 0)。
    匹配成功时,两个指针同时向后移动,继续比较下一个字符。
    判断匹配是否完成
    如果 !*s2 为真,说明子串 str2 的所有字符都已匹配完成(s2 指向了 str2 的结束符 \0),此时返回当前 cp 的位置(即子串在 str1 中的起始地址)。
    如果匹配失败,cp 向后移动一个字符,继续下一轮匹配尝试。
    未找到子串
    外层循环结束后仍未找到子串,返回 NULL。

    11. strtok 函数的使⽤

    char * strtok ( char * str, const char * sep);
    sep参数指向一个字符串,定义了用作分隔符的字符集合。
    第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标
    记。
    strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:
    strtok函数会改变被操作的字符串,所以被strtok函数切分的字符串一般都是临时拷贝的内容并且
    可修改。)
    strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
    中的位置。
    strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标
    记。
    如果字符串中不存在更多的标记,则返回 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;
    }

    在Windows11+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");
    if (pFile == NULL)
    printf ("Error opening file unexist.ent: %s\n", strerror(errno));
    return 0;
    }

    输出:

    Error opening file unexist.ent: No such file or directory
    也可以了解一下 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;
    }
    输出:
    Error opening file unexist.ent: No such file or directory
    Logo

    开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

    更多推荐