字符串函数总结

字符串函数总结

  1. 求字符串长度:strlen()函数

    size_t my_strlen(const char* str) //因为这个函数不会修改str,const增加鲁棒性
    {
       assert(str != NULL) //防范野指针
       int count;
       while (*str++ != '\0')
          {
             count++;
          }
       return count;
    }
    
    • 字符串已经以’\0’作为结束标志,strlen函数返回的是在字符串中’\0’前面出现的字符个数(不包含’\0’)
    • 注意函数的返回值为size_t,是无符号的,要用%zu格式打印
       if (strlen("abc")) - strlen("qwerty") > 0)
          printf(">\n);
       else 
          printf("<=\n");
    

    结果将会打印>,因为strlen返回无符号整型size_t,结果虽然是负数,但会被强制转换为大于0的无符号整型。如果要使用的话可以进行强制类型转换if (int)(strlen("abc")) - (int)strlen("qwerty") > 0)

  2. 长度不受限制的字符串函数

    1. strcpy()函数

      char* my_strcpy(char* dest, const char* src)
      {
         assert(dest && src)
         char* ret = dest //保存起始位置
         while(*dest++ = *src++)
         {
            ;
         }
         return ret
      }
      
      • src必须以’\0’结束,strcpy()中的’\0’拷贝到dest
      • src只用于读取不修改,因此设为const src以增强其鲁棒性
      • dest必须足够大,以确保能存放字符串
    2. strcat()函数 将src追加到dest后面

      char* my_strcat(char* dest, const char* src)
      {
         assert(dest && src);
         char* ret = dest;
         //1.用strlen找到dest中开始拷贝的位置,即第一个'\0'的位置
         while (*dest)
         {
            dest++; //如果判断条件里写*dest++,则当dest已经为'\0'时,dest还会再++一次
            //此时dest就会将'\0'包括进去,那么即使之后的代码有效,在打印时由于打印到'\0'就不再打印了,所以打印无效
         }
         //2.用strcpy将src从头开始将dest拷贝到目标位置,src的'\0'被覆盖
         while (*dest++ = *src++)
         {
            ;
         }
         return ret;
      }
      
      //第一步的另一种写法
      while (*dest++)
      {
            ;
      }
      dest--;
      
      int main()
      {
         char arr1[20] = "hello ";
         char arr2[] = "bit";
         my_strcat(arr1, arr2);
         printf("%s\n", arr1);
         return 0;
      }
      
      • dest从’\0’开始被src首字符开始覆盖,src的’\0’也被一同拷贝
      • 设计思路:先用strlen找到dest’\0’的位置(即开始拷贝的位置),然后用strcpy将src拷贝到dest之前找到的位置
      • 用my_strcat函数,字符串自己给自己追加的时候会造成死循环,某些C语言库中的strcat函数解决了这个问题
    3. strcmp()函数

      int my_strcmp(const char* s1, const char* s2)
      {
         assert(s1 && s2);
         while (*s1 == *s2)
         {
            if (*s1 == '\0')
            {
               return 0; //相等
            }
            s1++;
            s2++;
         }
         //if (*s1 > *s2) //不相等
         //return 1;
         //else
         //return -1;
         return *s1 - *s2;
      }
      
      int main()
      {
         char str1[] = "abcq";
         char str2[] = "abc";
      
         int ret = my_strcmp(str1, str2);
      
         if (ret > 0)
         {
            printf(">\n");
         }
         else if (ret == 0)
         {
            printf("=\n");
         }
         else
         {
            printf("<\n");
         }
         return 0;
      }
      
      • "abc" < "abcdef"或者arr1 < arr2这么写是在比较首元素地址的大小
      • C语言标准规定,若str1>str2,则返回大于0的数字,<则返回小于0的数字,=返回0
      • strcmp函数比较的不是字符串的长度,而是比较字符串中对应位置上的字符的ASCII码大小,如果相同,就比较下一对,直到不同或者都遇到’\0’
  3. 长度受限制的字符串函数

    1. strncpy

      char *strncpy( char *strDest, const char *strSource, size_t count ); 
      
      • 限制了操作的字符个数
      • str2长度小于count时不够的时候其余位置会拿’\0’填充
      • src也会将自己末尾的’\0’一同拷贝到dest
    2. strncat

      • src也会将自己末尾的’\0’一同拷贝到dest
      • 可以自己给自己追加了,不会像strcat一样造成死循环
    3. strncmp

  4. 字符串查找

    1. strstr:找子串,返回str2在str1中第一次出现的位置,若没有找到则返回NULL

      char* my_strstr(const char* str1, const char* str2)
      {
         assert(str1 && str2);
         //滚动匹配
         const char* s1 = str1; //加上const和str1保持一致,否则权限被放大了(指向同一个地址,一个可以被修改,一个不能被修改)
         const char* s2 = str2;
      
         const char* cur = str1; //记录开始匹配的位置
         while (*cur)
         {
            //匹配失败重置
            s1 = cur;  //匹配失败s1重置到cur当前位置
            s2 = str2; //匹配失败s2重置到str2开始位置
      
            while (*s1 && *s2 && (*s1 == *s2)) //前提条件是*s1,*s2不为零且两者相等
            {
               s1++;
               s2++;
            }
            if (*s2 == '\0') //s2被找完了,也就是说s2匹配成功
            {
               return (char*)cur; //返回这一次匹配开始的起始位置,强转为返回类型char*
            }
            cur++; //匹配失败cur前进一位
         }
         return NULL; //找不到
      }
      
      int main()
      {
         char str1[] = "abcdefjlkjjl\0XXXXX";
         char str2[] = "cdef";
         char* ret = my_strstr(str1, str2);
         if (NULL == ret)
         {
            printf("Cannot find the string.\n");
         }
         else
         {
            printf("%s\n", ret);
         }
         return 0;
      }
      
      • 还可以用KMP算法实现
    2. strtok:查找自定义分隔符(token)

      char *strtok( char *strToken, const char *sep);
      
      • sep参数是个字符串,定义了用作分隔符的字符集合
      • strToken为一个字符串,里面包含了0个或者多个被sep分割的字符串段
      • strtok函数的第一个参数
      • strtok()找到str中的下一个标记,并将其用’\0’结尾,返回一个指向这个标记的指针。strtok()会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改
      • strtok函数的第一个参数不为NULL时,函数将找到str中第一标记,strtok函数将保存它在字符串中的位置;strtok函数的第一个参数为NULL时,函数将在同一个字符串中被保存的位置开始,查找下一个标记
      printf("%s\n", strtok(arr, sep)); //只找第一个标记
      printf("%s\n", strtok(NULL, sep)); //是从上一次保存好的位置开始继续往后找
      printf("%s\n", strtok(NULL, sep)); //函数内部有一个静态指针变量保存字符串位置,
      //函数调用结束后不会被销毁,可以下一次调用时被用到
      printf("%s\n", strtok(NULL, sep));
      
      • 不区分分隔符的出现顺序,相同的分隔符只要写一个
      • 实际使用不可能手动写n次printf("%s\n", strtok(NULL, sep)),要写成循环的形式,具体使用方式如下代码所示
      int main()
      {
         char arr[] = "wj.feng@tum.de";
         char buf[30] = { 0 };
      
         strcpy(buf, arr); //strok会修改原数据,因此使用buf拷贝
         const char* sep = "@."; //不区分分隔符的出现顺序,相同的分隔符只要写一个
         char* str = NULL;
         for (str = strtok(buf, sep); str != NULL; str = strtok(NULL, sep))
         {
            printf("%s\n", str); 
         }
      
         return 0;
      }
      
  5. 错误信息报告: strerror

    // strerror 头文件:#include <errno.h>
    // 全局变量:errno(错误码)比如说404就是一种错误码
    int main()
    {
       printf("%s\n", strerror(0));
       printf("%s\n", strerror(1));
       printf("%s\n", strerror(2));
       printf("%s\n", strerror(3));
    
       int* p = (int*)malloc(INT_MAX);
       if (p == NULL) 
       {
          printf("%s\n", strerror(errno)); //库函数malloc出错时会把错误码放到errno里
          //errno是全局变量,会被更新的
          perror("malloc"); //与strerror(不打印)使用场景不同
          //perror是直接打印错误码对应的字符串,可以加上自定信息(如"malloc")
          return 1;
       }
       return 0;
    }
    
  6. 内存操作函数:str类函数只能用于字符型类型,其他数据如int类数组就不能用

    1. memcpy

      //void * memcpy ( void * destination, const void * source, size_t num );
      void* my_memcpy(void* dest, const void* src, size_t count)
      //void* 可以用来接收任意类型的指针,但时候时必须要进行强制转换
      {
         assert(dest && src);
         void* ret = dest;
         while (count--)
         {
            *(char*)dest = *(char*)src;
            dest = (char*)dest + 1;
            src = (char*)src + 1;
            //或者这么写,但这可能有编译器计算路径不确定的问题,还是不要用了
            //((char*)dest)++; //++优先级高于强制类型转换
            //((char*)src)++; 
         }
         
         return ret;
      }
      
      • 函数从src位置开始往后复制count个字节的数据到dest
      • 这个函数在遇到’\0’的时候不会停下来
      • 不能用于src和dest有重叠的情况,复制情况未定义,要用memmove
    2. memmove

      //第一种写法,前->后/后->前/后->前
      #include <stdio.h>
      #include <string.h>
      #include <limits.h>
      #include <errno.h>
      #include <stdlib.h>
      #include <assert.h>
      //void * memmove ( void * destination, const void * source, size_t num );
      void* my_memmove(void* dest, const void* src, size_t count)
      {
         assert(dest && src);
         void* ret = dest;
         //1
         if (dest < src)
         {//前->后
            while (count--)
            {
               *(char*)dest = *(char*)src;
               dest = (char*)dest + 1;
               src = (char*)src + 1;
      
               //++(char*)dest; //在某些编译器下可能会有问题
               //++(char*)src;
            }
         }
         else
         {//后->前
            while (count--)
            {
               *((char*)dest + count) = *((char*)src + count);//以count=20为例,则第一个之间相差19个字节
            }
         }
         return ret;
      }
      
      int main()
      {
         int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
         int sz = sizeof(arr1) / sizeof(arr1[0]);
         //int arr2[10] = { 0 }; //创建一个临时空间不是一个好的写法,因为很难给定到底给多少大小,因此还是用自己的空间
         my_memcpy(arr2, arr1, 20);
         //my_memcpy(arr1 + 2, arr1, 20); //从3开始复制 期望结果: 1 2 1 2 3 4 5 8 9 10
         实际结果:1 2 1 2 1 2 1 8 9 10
         memcopy不适合重叠内存拷贝(自己拷贝到自己),要用memmove
      //my_memmove(arr1 + 2, arr1, 20);
         my_memmove(arr1, arr1 + 2, 20);
      
         int i = 0;
         for (i = 0; i < sz; i++)
         {
            printf("%d ", arr1[i]);
         }
      
         return 0;
      }
      
      //第二种写法,前->后/后->前/前->后
      void* my_memmove(void* dest, const void* src, size_t count)
      {
         assert(dest && src);
         void* ret = dest;
         if (dest > src && dest < ((char*)src + count))
         {//后->前
            while (count--)
            {
               *(char*)dest = *(char*)src;
               dest = (char*)dest + 1;
               src = (char*)src + 1;
            }
         }
         else
         {//前->后
            while (count--)
            {
               *((char*)dest + count) = *((char*)src + count);
            }
         }
         return ret;
      }
      
      • 既然memmove的功能比memcpy的功能强大,为什么还不废除memcpy?因为memcpy早于memmove出现,出于兼容早期版本C语言等目的是不能随便废除memcpy函数的
      • 同时相比于my_memcpy,为了方便使用,VS编译器库函数中的memcpy也实现了重叠拷贝
    3. memset:内存设置

      //memset 初始化内存单位
      //void* memset(void* dest, int c, size_t count);
      int main()
      {
         int arr[] = { 1,2,3,4,5 };
         memset(arr, 0xFF, 20); //以字节为单位来初始化内存单元的
         return 0;
      }
      
    4. memcmp

      //memcmp
      //int memcmp(const void* ptr1, const void* ptr2, size_t num); //因为要兼容所有数据类型,所以用了void*,因此这里是一个字节一个字节进行比较的
      //形参与实参数据类型不一致时,强制转换为形参的数据类型void*
      int main()
      {
         int arr1[5] = { 1,2,3,4,5 };
         int arr2[5] = { 1,2,3,4,0x11223305 };
         int ret = memcmp(arr1, arr2, 18); //1,逐字节比较
         int ret = memcmp(arr1, arr2, 17); //0
         printf("%d\n", ret);
         return 0;
      }
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Weijian Feng

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值