• 【C进阶】字符串函数


     C语言中对字符和字符串的处理很频繁,但是C语言本身是没有字符串类型的,字符串通常放在常量字符串中或者字符数组

    字符串常量适用于那些对它不做修改的字符串函数

    本章重点介绍处理字符串函数的库函数的使用和注意事项


    一、字符串函数

    这些函数都要引头文件:#include

    1.1 strlen

    size_t  strlen(const char *str)

    (1)库函数,是用来求字符串长度的,本质上统计的是字符串中\0之前的字符的个数(不包括’\0‘)

    (2)参数指向的字符串(传地址)必须要以 '\0' 结束

    (3)注意函数的返回值为size_t,是无符号的(易错)


    下面看看这个代码:思考一下结果是什么?

    1. #include
    2. #include
    3. int main()
    4. {
    5. const char* str1 = "abcdef";
    6. const char* str2 = "bbb";
    7. if (strlen(str2) - strlen(str1) > 0)
    8. {
    9. printf("str2>str1\n");
    10. }
    11. else
    12. {
    13. printf("srt1>str2\n");
    14. }
    15. return 0;
    16. }

    【答案】:srt2>str1

    【解释】:两个无符号数相减得到的还是无符号数,那么一个小的数减去一个大的数还是得不到负数,所以就会打印srt2>str1

    【改进】:将size_t类型强转为int类型

    if ((int)strlen(str2) - (int)strlen(str1) > 0)


    strlen函数的三种模拟实现:

    (1)计数器:

    1. size_t my_strlen(const char* str)
    2. {
    3. int count = 0;
    4. while (*str++)
    5. {
    6. count++;
    7. }
    8. return count;
    9. }

    (2)递归:

    1. size_t my_strlen(const char* str)
    2. {
    3. if (*str)
    4. return my_strlen(str+1) + 1;
    5. else
    6. return 0;
    7. }

    (3)指针-指针

    1. size_t my_strlen(const char* str)
    2. {
    3. char* p = str;
    4. while (*p)
    5. {
    6. p++;
    7. }
    8. return p - str;
    9. }

    1.2 strcpy

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

    把 source所指向的字符串复制到 destination,而且会覆盖destination中的字符(字符串拷贝)

    (1) 源字符串必须以 '\0' 结束

    (2)会将源字符串中的 '\0' 拷贝到目标空间(不然找不到‘\0'没法结束,就可能会导致数组越界)

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

    (4)目标空间必须可变

    (5)返回值是目标空间的地址(参数前面是目标字符串的地址,后面是源头字符串的地址)


    验证strcpy会将源字符串中的'\0'拷贝到目标空间: 

    1. #include
    2. #include
    3. int main()
    4. {
    5. char arr1[20] = "WORLDGOOD";
    6. char arr2[] = "HELLO";
    7. strcpy(arr1, arr2);
    8. printf("%s\n", arr1);
    9. return 0;
    10. }

    未strcpy前的arr1: 

    strcpy后的arr1: 可以看到下标为5的元素就是'\0'


     当目标空间为常量字符串时,不满足条件(4),那么就会出现错误:

    1. #include
    2. #include
    3. int main()
    4. {
    5. char* p = "abcdefghi";//常量字符串
    6. char arr2[] = "HELLO";
    7. strcpy(p, arr2);//err
    8. printf("%s\n", p);
    9. return 0;
    10. }

     strcpy函数的模拟实现: 
    1. #include
    2. char* my_strcpy(char* dest, const char* src)
    3. {
    4. char* ret = dest;
    5. assert(dest && src);//防止dest和src是空指针
    6. while (*dest++ = *src++)
    7. {
    8. ;
    9. }
    10. return ret;
    11. }

    1.3 strcat

    char * strcat(char * destination ,const char * source)

    把 source所指向的字符串追加到 destination所指向的字符串的结尾(字符串追加)

    内部逻辑:

    <1>找到destination的末尾'\0'

    <2>再把source的内容追加到destination的后面,会把前面字符串的’\0'覆盖

    (1)源字符串必须以'\0'结束(’\0'也会追加过去)

    (2)目标空间必须足够大,而且必须可以修改

    (3)目标字符串中也得有’\0',保证可以找到目标空间的末尾进行追加

    (4)字符串不能自己给自己追加:这样会进入死循环,前面的'\0'被覆盖了,没有’\0'无法结束


     strcpy函数的模拟实现: 

    <1>找到目标空间的末尾

    <2>数据的追加(跟strcpy函数一样)

    1. char* my_strcat(char* dest,const char* src)
    2. {
    3. char* ret = dest;
    4. assert(dest && src);
    5. //1.找到目标空间的末尾
    6. while (*dest != '\0')
    7. {
    8. dest++;
    9. }
    10. //2.数据的追加(跟strcpy函数一样)
    11. while (*dest++ = *src++)
    12. {
    13. ;
    14. }
    15. return ret;
    16. }

     1.4 strcmp

    int strcmp (const char * str1,const char *str2)

    (string compare)字符串比较函数

    (1)不是比较长度,而是比较对应位置字符的大小(也就是ACSII值)

    eg: str1=”abcdef“  str2=“abq" 

    在这个两个字符串前面两个字符都一样,但是到第三个字符位置上,str1是c,str2是q,q的ACSII值大于c,那么最后str1

    (2)标准规定:

    str1>str2,则返回大于0的数

    str1

    str1和str2相等,则返回等于0的数

     在VS中返回值就是-1,0,1,但是其他编译器就不一定


      strcmp函数的模拟实现:

    (1)初阶版:

    1. #include
    2. int my_strcmp(const char* str1, const char* str2)
    3. {
    4. assert(str1 && str2);
    5. while (*str1==*str2)
    6. {
    7. if (*str1 == '\0')
    8. return 0;
    9. str1++;
    10. str2++;
    11. }
    12. if (*str1 > *str2)
    13. {
    14. return 1;
    15. }
    16. else
    17. {
    18. return -1;
    19. }
    20. }

    (2) 进阶版:这样简化了很多,而且返回值不只是1或者-1

    1. #include
    2. int my_strcmp(const char* str1, const char* str2)
    3. {
    4. assert(str1 && str2);
    5. while (*str1==*str2)
    6. {
    7. if (*str1 == '\0')
    8. return 0;
    9. str1++;
    10. str2++;
    11. }
    12. return *str1 - *str2;
    13. }

     上面介绍的都是长度不受限制的字符串函数(strcpy,strcat,strcmp)

    下面再来学习一下长度受限制的字符串函数:(strncpy,strncat,strncmp)

    2.1 strncpy

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

    拷贝num个字符从源字符串到目标空间

    (1)如果源字符串的长度小于num,则拷贝完源字符串之后,在目标空间的后面追加’\0',直到num个

    1. #include
    2. #include
    3. int main()
    4. {
    5. char arr1[20] = "XXXXXXXXXXXXX";
    6. char arr2[] = "abc";
    7. strncpy(arr1, arr2, 6);
    8. printf("%s\n", arr1);
    9. return 0;
    10. }

    我们可以看到arr2只有四个元素,但是要传6个字符,那么就自动在后面补上’\0',直到补到num个为止(这里printf打印arr1时,后面的XXXX是不会打印的,因为字符串遇到‘\0'就结束了,就不会再向后走了)


    2.2 strncat 

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

     如果num大于源字符串的长度,只会追加源字符串的长度

    1. #include
    2. #include
    3. int main()
    4. {
    5. char arr1[20] = "abc\0xxxxxxxxxxxxx"; //一定要有\0,不然找不到字符串的末尾
    6. char arr2[] = "defghi";
    7. strncat(arr1, arr2, 10);
    8. printf("%s\n", arr1);
    9. return 0;
    10. }

     2.3 strncmp

    int strncmp(const char * str1, const char *str2)

    比较到出现另一个字符不一样或者一个字符串结束或者num个字符全部比较完、

    1. #include
    2. #include
    3. int main()
    4. {
    5. char arr1[] = "abczef";
    6. char arr2[] = "abcqw";
    7. int ret = strncmp(arr1, arr2, 4);
    8. printf("%d\n", ret);
    9. return 0;
    10. }

    3.1 strstr

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

     string string 在字符串中找字符串

    (1)strstr会返回str1中str2第一次出现的位置(多次出现只会返回第一次出现的地址)

             如果str1中没有str2,就返回NULL

    eg:代码结果:defghi

    1. #include
    2. #include
    3. int main()
    4. {
    5. char arr1[] = "abcdefghi";
    6. char arr2[] = "def";
    7. char* ret = strstr(arr1, arr2);
    8. if (ret == NULL)
    9. {
    10. printf("找不到\n");
    11. }
    12. else
    13. {
    14. printf("%s\n", ret);
    15. }
    16. return 0;
    17. }
       strstr函数的模拟实现:

    (1)得有一个指针(cp)记录从哪开始str1和str2相匹配了

    (2)对于str1="abbbcdef\0"  str2="bbc\0”就没有那么简单了(当str1指向第三个b时,发现str2对应的不是b而是c,那么有得重新开始,所以还得有个指针指向str2的头部得以返回)

    我们就考虑再加两个指针s1和s2分别去遍历str1和str2,str1和str2就指向这两个字符串的头部

    1. #include
    2. #include
    3. const char *my_strstr(const char* str1, const char* str2)
    4. {
    5. const char* cp=str1;//记录开始匹配的位置
    6. const char* s1 ;
    7. const char* s2 ;//分别去遍历字符串
    8. assert(str1 && str2);
    9. if (*str2 == '\0')
    10. return str1;//空字符串是所有字符串的子字符串
    11. while (*cp)//如果str1是空字符就是直接跳出循环
    12. {
    13. s1 = cp;//重新开始s1就得从cp开始
    14. s2 = str2;//s2从str2开始
    15. while (*s1&&*s2&& *s1 == *s2)//s1和s2不相等或者都为0就停下来
    16. {
    17. s1++;
    18. s2++;
    19. }
    20. if (*s2 == '\0')
    21. return cp;
    22. cp++;//s1和s2不相等就cp向后走一个位置
    23. }
    24. return NULL;//找不到返回空指针
    25. }

     这个strstr的模拟实现是一种暴力求解的方式,算法不够高效,有兴趣可以去研究KMP算法


    3.2 strtok

    char * strtok (char *str ,const char *sep)

    这个函数是用来切割字符串

    eg:shenyu@yeah.net(yeah是域名,.net是后缀)//@和.就切割了字符串

    192.168.101.23  //IP地址,点分十进制

    (IP地址本来是一个无符号整数,这个整数不方便记忆,所以将这个整数转化为点分十进制的表示方式)

    (1)sep参数是个字符串,定义了用作分隔符的字符集合

    (2)第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记

    (3)strtok函数找到str中的下一个标记,并将其用 \0 结尾(把这个标记改为‘\0'),返回一个指向这个标记的指针(有记忆功能)(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改)

    (4)strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置(第一次传参:s = strtok(arr, p))

    (5)strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记(后面传参:s = strtok(NULL, p))

    (6)如果字符串中不存在更多的标记,则返回 NULL 指针

    理解怎么使用:

    1. #include
    2. #include
    3. int main()
    4. {
    5. char arr[] = "shenyu@yeah.net";
    6. char buf[200] = { 0 };
    7. strcpy(buf, arr);//拷贝一份
    8. char* p = "@.";
    9. char* s = strtok(buf, p);
    10. printf("%s\n", s);
    11. s = strtok(NULL, p);
    12. printf("%s\n", s);
    13. s = strtok(NULL, p);
    14. printf("%s\n", s);
    15. return 0;
    16. }

    代码结果: 

     

    奇葩的函数:第一次调用和后面的调用传递的参数不一样

    真正使用strtok函数的代码:这个代码的结果和上面一样,for直接打印所有分割的字符

    1. #include
    2. #include
    3. int main()
    4. {
    5. char arr[] = "shenyu@yeah.net";
    6. char buf[200] = { 0 };
    7. strcpy(buf, arr);//拷贝一份
    8. char* p = "@.";
    9. char* s = NULL;
    10. for (s = strtok(arr, p); s != NULL; s = strtok(NULL, p))
    11. {
    12. printf("%s\n", s);
    13. }
    14. return 0;
    15. }

    3.3 strerror

    char * strerror (int errnum)

     错误码翻译错误信息,返回错误信息的字符串的起始地址

    当使用库函数或者进行整个的软件设计的过程中,可能会有错误码 eg:404

    C语言中使用库函数的时候,如果发生错误,就会将错误码放在errno的变量中

    errno是一个全局的变量,可以直接使用的 eg:0,1,2,3,4,5...

    下面我们来看看错误码对应的错误信息:

    1. #include
    2. int main()
    3. {
    4. for (int i = 0; i < 10; i++)
    5. {
    6. printf("%d: %s\n", i, strerror(i));
    7. }
    8. return 0;
    9. }

    结果如下: 

    再来个打开文件的例子:

    fopen以读的形式打开文件
    如果文件存在,打开成功
    如果文件不存在,打开失败 

    用errno变量要引头文件:#include 

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. FILE *pf=fopen("add.txt", "r");
    7. if (pf == NULL)
    8. {
    9. printf("打开文件失败,原因是:%s\n", strerror(errno));
    10. return 1;
    11. }
    12. else
    13. {
    14. printf("打开文件成功\n");
    15. }
    16. return 0;
    17. }

    拓展:perror函数:直接打印错误所对应的错误信息

    相当于printf+strerror(打印规则:自定义信息+xxxxx)

    1. #include
    2. int main()
    3. {
    4. FILE *pf=fopen("add.txt", "r");
    5. if (pf == NULL)
    6. {
    7. perror("打开文件失败");
    8. }
    9. else
    10. {
    11. printf("打开文件成功\n");
    12. }
    13. return 0;
    14. }

     


    二、字符分类函数: 

    这些函数都需要引头文件:#include 

    函数如果他的参数符合以下条件就返回真
    iscntrl任何控制字符
    isspace

    空白字符:空格’ ‘,换页’\f',换行‘\n',回车’\r',制表符‘\t'或者垂直制表符’\v'

    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任何可打印字符,包括图形字符和空白字符

    eg1:判断一个字符是否是小写字母 (    int islower (int c)  ) 

    1. #include
    2. int main()
    3. {
    4. char ch = 'w';
    5. if (islower(ch))
    6. {
    7. printf("小写\n");
    8. }
    9. else
    10. {
    11. printf("非小写\n");
    12. }
    13. return 0;
    14. }

     eg2:判断一个字符是不是十六进制数字

    1. #include
    2. int main()
    3. {
    4. int ret = isxdigit('q');
    5. printf("%d\n", ret);
    6. return 0;
    7. }

     三、字符转换函数

    这些函数都需要引头文件:#include 

    int tolower(int c);

    int  toupper(int c);

     eg1:大写转小写,小写转大写

    1. #include
    2. int main()
    3. {
    4. int ret = tolower('a');
    5. printf("%c\n", ret);//A
    6. ret = tolower(ret);
    7. printf("%c\n", ret);//a
    8. return 0;
    9. }

    eg2:字符串大写字母全部转为小写

    1. #include
    2. int main()
    3. {
    4. char arr[] = "Test String.\n";
    5. char* p = arr;
    6. while (*p)
    7. {
    8. if (isupper(*p))
    9. {
    10. *p = tolower(*p);
    11. }
    12. p++;
    13. }
    14. printf("%s", arr);
    15. return 0;
    16. }

    本次内容就到此啦,欢迎评论区或者私信交流,觉得笔者写的还可以,或者自己有些许收获的,麻烦铁汁们动动小手,给俺来个一键三连,万分感谢 ! 

  • 相关阅读:
    C# JSON转为实体类和List,以及结合使用
    KT148A语音芯片组合播放之间有间隔不连贯的处理方法V1
    谁还说我没表情包用?马上用Python采集上万张个表情包
    MindSpore优秀论文5:[AAAI] CycleCol:基于循环卷积神经网络对真实单色-彩色摄像系统着色
    硬盘文件系统系列专题之二 NTFS
    当年很流行,现在已经淘汰的前端技术有哪些?
    C++字符串类 - std::string - 常用总结
    随机森林----评论情感分析系统
    H5页面在ios上文本框不能输入
    打造千万级流量秒杀第十七课 技术选型:如何选择满足“三高”要求的技术?
  • 原文地址:https://blog.csdn.net/qq_73017178/article/details/133428293