字符函数和字符串函数(下)

简介: 字符函数和字符串函数(下)

字符函数和字符串函数(上)+https://developer.aliyun.com/article/1384933

8.strstr

函数介绍:

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

函数功能:

字符串查找函数

注意事项:

  • 在str1指向的字符串中查找str2指向的字符串。
  • 返回一个指向str1中第一次出现的str2的指针。
  • 如果 str2 不是 str1 的一部分,则返回一个空指针。
  • 匹配过程不包括终止空字符,但它到此为止。

strstr模拟实现

char* my_strstr(char* str1, char* str2)
{
  assert(str1 && str2);
  if (*str2 == '\0')
  {
    return str1;
  }
  char* s1 = str1;
  char* s2 = str2;
  char* cp = str1;
  while (*cp)
  {
    s1 = cp;
    s2 = str2;
    while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2)
    {
        s1++;
        s2++;
    }
    if (*s2 == '\0')
    {
      return cp;
    }
    cp++;
  }
  return NULL;
}
int main()
{
  char arr1[] = "abbvcbcbbdbbvbnui";
  char arr2[] = "bbvb";
  char* ret = my_strstr(arr1, arr2);
  if (ret == NULL)
  {
    printf("找不到\n");
  }
  else
  {
    printf("%s\n", ret);
  }
  return 0;
}

9.strtok

函数介绍:

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

函数功能:

字符串拆分函数

注意事项:

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

strtok模拟实现

int main()
{
  char arr[] = "konglong@qq.com";
  char* p = "@.";
  char buf[20] = { 0 };
  strcpy(buf, arr);
  char* ret=NULL;
  for (ret = strtok(buf, p); ret != NULL; ret = strtok(NULL, p))
  {
    printf("%s\n", ret);
  }
  return 0;
}

10.strerror

函数介绍:

char * strerror ( int errnum );

函数功能:

把错误码转换成错误信息的函数

注意事项:

  • C语言的库函数在运行的时候,如果发生错误,就会把错误码存在一个变量中,这个变量是:errno
  • 返回的指针指向静态分配的字符串(错误信息字符串)

一些错误码对应的错误信息:

int main()
{
  printf("%s\n", strerror(0));
  printf("%s\n", strerror(1));
  printf("%s\n", strerror(2));
  printf("%s\n", strerror(3));
  printf("%s\n", strerror(4));
  printf("%s\n", strerror(5));
  return 0;
}
//结果:
No error
Operation not permitted
No such file or directory
No such process
Interrupted function call
Input/output error

二、字符分类函数

函数         如果它的参数复合下列条件就返回真(一个非零数字)

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

三、字符转换函数

tolower   将大写字母转换为小写字母

toupper   将小写字母转换为大写字母

int tolower ( int c );
int toupper ( int c );

注意事项:

  • 如果 c 是小写字母并且具有大写等效字母,则将 c 转换为其大写等效字母。如果无法进行此类转换,则返回的值为 c 不变
  • 这里是值传递,不会改变实参

示例:

int main()
{
  char arr[] = "I Have An Apple";
  int i = 0;
  while (arr[i])
  {
    if (isupper(arr[i]))
    {
      printf("%c", tolower(arr[i]));//把转换的小写字母直接打印出来
    }
    else
    {
      printf("%c", arr[i]);
    }
    i++;
  }
  return 0;
}
//结果:
i have an apple
int main()
{
  char arr[] = "I Have An Apple";
  int i = 0;
  while (arr[i])
  {
    if (isupper(arr[i]))
    {
      arr[i] = tolower(arr[i]);//用转换后的小写字符替换原有的大写字符
    }
    printf("%c", arr[i]);
    i++;
  }
  return 0;
}

以上介绍的这些函数都是针对字符串或者字符的,那如果我们要拷贝其他类型的数据呢?用上面这些函数自然就行不通了,此时就要用到即将介绍的内存函数了。

四、内存函数

1.memcpy

函数介绍:

void * memcpy ( void * destination, const void * source, size_t num );

函数功能:

内存拷贝函数

注意事项:
 这里的destination指向要在其中赋值内容的目标数组,source指向要复制的数据源,num是要复制的字节数,注意这里前两个指针的的类型还有函数返回值都是void*,这是因为,memcpy这个函数是内存拷贝函数,它有可能拷贝整型,浮点型,结构体等等各种类型的数据……虽然返回类型是void*,但他也是必不可少的,void*也表示一个地址,用户可以把它强制转换成自己需要的类型去使用。

 

memcpy模拟实现

void* my_memcpy(void* dest, const void* src, size_t num)
{
  assert(dest && src);
  void* ret = dest;
  while (num)
  {
    *(char*)dest = *(char*)src;
    ((char*)dest)++;
    ((char*)src)++;
    num--;
  }
  return ret;
}
int main()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  int arr2[10] = { 0 };
  my_memcpy(arr2, arr1+2, 16);
  return 0;
}

上面代码中模拟出来的my_memcpy函数已经可以实现把arr1数组中的元素拷贝到arr2数组里面去。但当我们想把arr1数组中的1、2、3、4、5拷贝到arr1数组中的3、4、5、6、7上去时,就会发生错误,如下图:

原因是:当1拷贝到3上时,原来的3已经被1替换,当2拷贝到4上的时候,原来的4已将被2替换。所以当拷贝arr[2]到arr[4]上的时候,原本arr[2]里面存放的3已将被1替换了,同理,所以才得出了不符合我们预期的结果。那如何解决这个问题呢?先来分析这个问题产生的原因,这是因为源空间与目标空间之间有重叠,这里的arr[2]、arr[3]、arr[4]既是源空间也是目标空间,当拷贝1和2的时候把源空间中开没有拷贝的3和4就给覆盖了,此时源空间arr[2]和arr[3]里面存的就不再是3和4了,而是1和2,所以此时拷贝arr[2]和arr[3]里面的数据,其实拷贝的就是1和2。为了解决这个问题,我们可以从后往前拷贝,此时就不会出现这样的问题

解决的思路就是:先拷贝重叠区域的元素,避免其还没拷贝就被覆盖掉。但并不是一遇到源空间和目标空间重叠就从后王前拷贝,比如下面这样,此时要把3、4、5、6、7拷贝到1、2、3、4、5里面

此时就不能用上面的方法从后往前拷贝了,但是可以根据上面提到的解决思路:先拷贝重叠区域的元素,所以此时就应该从前往后拷贝,即按照3、4、5、6、7的顺序去拷贝

 因此在拷贝的时候,是按照从前往后的顺序还是从后往前的顺序得看具体的情况。当目标空间的起始位置在源空间的前面时,就得从前往后进行拷贝。当目标空间的起始位置在源空间的后面的时候,就要从后往前拷贝。当源空间和目标空间没有任何重叠的时候,不管是从前往后拷贝还是从后往前拷贝效果都一样。

 接下来介绍的memmove函数就可以完成重叠空间的元素拷贝。

2.memmove

函数介绍:

void * memmove ( void * destination, const void * source, size_t num );

函数功能:

内存拷贝函数

注意事项

它的参数、返回值与memcpy函数一模一样。这里就不过多介绍。对于这两个函数来说,目标空间必须足够大,不然就会发生越界访问。

memmove模拟实现

void* my_memmove(void* dest, const void* src, int num)
{
  assert(dest && src);
  void* ret = dest;
  if (dest < src)//目标空间的地址小,说们目标空间靠前
  {
    //从前向后
    while (num--)
    {
      *(char*)dest = *(char*)src;
      dest = (char*)dest + 1;
      src = (char*)src + 1;
    }
  }
  else
  {
    //从后往前
    while (num--)//num为1的时候,下面的num就是0
    {
      *((char*)dest + num) = *((char*)src + num);//通过num的减减就可以实现对每一个字节的访问
    }
  }
  return ret;
}
int main()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  my_memmove(arr1+2, arr1, 20);
  return 0;
}

3.memcmp

函数介绍:

int memcmp ( const void * ptr1,const void * ptr2,size_t num )

函数功能:

内存比较函数

注意事项:

  • 比较从ptr1和ptr2指针开始的num个字节
  • 两个内存块中不匹配的第一个字节在 ptr1 中的值低于 ptr2 中的值返回一个小于零的数子,相等返回零,两个内存块中不匹配的第一个字节在 ptr1 中的值大于在 ptr2 中的值返回一个大于零的数子

小结

上述的这些都是我们经常会在平时的代码中使用到的,熟练掌握起来为我们提供很大的帮助。当然,除此之外,C语言的库函数中还有许多有趣的有关字符和字符串的函数,如果你有兴趣想要了解,可以在下面的网站或软件找到它们,里面都有对每一个函数详细的各个部分的说明、作用的解析和示例,博主就不一一赘述了。

相关文章
|
存储 缓存 算法
Streaming System 第一章:Streaming 101
简介 Streaming101起源于在O'really上发表的两篇博客,原文如下:https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-101https://www.oreilly.com/ideas/the-world-beyond-batch-streaming-102其中对流式计算的设计理念做了非常透彻的介绍。
10503 0
|
Java
Java实现微信支付
Java实现微信支付
713 2
|
前端开发
多次请求同一数据接口造成数据混乱问题解决办法
在进行前端开发过程中,经常会遇到需要请求同一个数据接口但不同参数的需求,这种情况下当用户通过页面操作频繁请求该接口,而接口的不同参数响应时间差异较大时,容易引发数据渲染混乱的bug。
2952 0
|
SQL 资源调度 监控
Flink SQL性能优化实践
Apache Flink流处理性能优化指南:探索数据源读取并行度、状态管理、窗口操作的优化策略,包括设置默认并行度、使用RocksDB状态后端、调整窗口大小。调优方法涉及数据源分区、JOIN条件优化、使用Broadcast JOIN。注意SQL复杂度、并发控制与资源调度,如启用动态资源分配。源码层面优化自定义Source和Sink,利用执行计划分析性能瓶颈。异常检测与恢复通过启用检查点,监控任务性能。预处理数据、使用DISTINCT去重,结合UDF提高效率。选择高效序列化框架和启用数据压缩,优化网络传输和系统配置。处理数据倾斜,均衡数据分布,动态调整资源和任务优先级,以提升整体性能。
611 2
|
12月前
|
Android开发
Android面试高频知识点(1) 图解Android事件分发机制
Android面试高频知识点(1) 图解Android事件分发机制
|
11月前
|
分布式计算 Java 持续交付
如何选择合适的微服务框架
如何选择合适的微服务框架
264 0
|
12月前
|
前端开发 JavaScript 安全
前端JS实现密码校验键盘横竖、26字母、相同字母、相同数字、密码包含用户名、数字 字母不能连续 不能相同三个、不能横向 竖向 连续三个 包含字符、不能有中文符号
该 JavaScript 代码实现了一个严格的密码校验功能,确保密码满足多种安全要求,包括长度、字符类型、不包含中文及特殊字符、不与用户名相似等。通过多个辅助函数,如 `validateFormat` 检查密码格式,`isHasChinaCharFun` 检测中文符号,`getCharAll` 生成键盘组合,以及 `checkPasswordFun` 综合验证密码的有效性和安全性。此工具对于提高用户账户的安全性非常有用。
522 0
|
安全 Linux
Linux线程(十一)线程互斥锁-条件变量详解
Linux线程(十一)线程互斥锁-条件变量详解
|
机器学习/深度学习 存储 算法
【博士每天一篇论文-综述】Echo State Network Optimization: A Systematic Literature Review
本文综述了74篇关于Echo State Network(ESN)优化的研究文章,指出生物启发方法尤其是粒子群优化(PSO)是ESN参数优化的常用技术,探讨了ESN在不同参数和拓扑结构下的行为特性,同时识别了超参数优化、评估指标和数据集选择等方面的研究空白。
124 3
|
人工智能 自然语言处理 搜索推荐
人工智能:赋能开发的革命性力量
在科技飞速发展的今天,人工智能(AI)正深刻影响软件开发。AI作为强大的助手,通过自动化需求分析、智能代码补全及测试等手段大幅提升开发效率。同时,AI助力软件创新,如个性化推荐系统改善用户体验,并通过数据分析辅助决策,降低项目风险。随着AI技术普及,掌握相关技能变得至关重要,推动开发者技能升级与人才培养。总之,AI正重塑软件开发,加速流程、激发创新,引领行业发展至新高度。
334 1