C语言常见字符串函数解析(上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: C语言常见字符串函数解析(上)

前言


  • 常见的字符串函数在一定程度上可以让我们在写代码,或者是在刷某些有关字符串的题目时事半功倍,并且常见字符串函数的功能非常常用,因此我们应该熟练使用这些字符串函数,以及部分函数要能自我实现。
  • 字符串函数都要引入一个库函数:string(#include <string.h>)


1.长度不受限制的常见字符串函数


strlen


strlen函数是求字符串长度的,遇到 \0 停止(计算\0之前有多少个字符),如果有多个\0,则只计算第一个\0前面的字符个数。


a0cc65abf82146f793825c7a577559fa.png


strlen的使用:

#include <stdio.h>
#include <string.h>
int main()
{
  char arr[] = "abcdef";
  printf("%d\n", strlen(arr));
  return 0;
}



运行结果:6


arr有6个字符,所以所得为6,值得注意的是,strlen函数的返回值是 size_t(unsigned int),size_t表示无符号整型,但是这里我们用%d形式打印也是没有问题的。


为什么strlen函数的返回值要弄size_t 呢?因为长度是没有负数之说的,所以size_t符合实际,但是size_t又难免会出现一些问题,例如:

#include <stdio.h>
#include <string.h>
int main()
{
  char a1[] = "abc"; // 3
  char a2[] = "abcdef";  // 6
  if (strlen(a1) - strlen(a2) < 0)
  {
    printf("a1 < a2\n");
  }
  else
  {
    printf("a1 > a2\n");
  }
  return 0;
}


猜这里输出的结果是什么呢?


正常来说应该输出a1 < a2才对,可是这里的输出是a1 > a2,那么就说明strlen(a1) - strlen(a2) > 0,这是为什么呢?


strlen(a1)返回一个size_t的数3,strlen(a2)返回一个size_t的数6,3 - 6 = -3,此时-3也是一个size_t类型,所以当-3作为一个无符号数来看待的话,那将是一个很大的整数,自然也就大于零输出第一个printf了。


所以库函数中strlen返回值为size_t可以说有利也有弊,需细心使用,接下来我对字符串函数的实现,如果是返回整型的话,我都会采用返回int的。


strlen的自我实现


这里我的strlen实现有三种方式:计数,指针减指针,递归,他们分别对应my_strlen1, my_strlen2, my_strlen3

#include <stdio.h>
#include <assert.h>
int my_strlen1(const char* s)
{
  assert(s);
  int count = 0;
  while (*s)
  {
    ++count;   // *s 不是 \0 就加一
    ++s;
  }
  return count;
}
int my_strlen2(const char* s)
{
  assert(s);
  const char* cur = s;
  while (*cur)
  {
    cur++;
  }
  return (int)(cur - s); // 用 cur 指针找到 \0 ,再用 cur 减去 s 得到之间字符的个数 6
}
int my_strlen3(const char* s)
{
  assert(s);
  if (*s != '\0')
    return 1 + my_strlen3(s + 1);
  else
    return 0;
}
int main()
{
  char a[] = "abcdef";
  printf("%d\n", my_strlen1(a));  // 6
  printf("%d\n", my_strlen2(a));  // 6
  printf("%d\n", my_strlen3(a));  // 6
  return 0;
}


strcpy


strcpy的功能是字符串拷贝,将源头(src)字符串拷贝到目的地(dest)字符串当中,并且是从头开始拷贝,src中的\0也要拷贝过去。

注意:dest 的字符串长度要大于等于 src ,不然 src 拷贝过去会出现非法访问的错误。


image.png


strcpy函数返回目的地字符串(被拷贝后)首元素地址。

strcpy的使用

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[] = "xxxxxxxxxx";
  char arr2[] = "abcdef";
  printf("%s\n", strcpy(arr1, arr2));
  return 0;
}



运行结果为:abcdef


41b189aa1395415ea2d9bb16db1f08d7.png

strcpy的自我实现


上图实际上就是整个拷贝的过程,*dest++ = *src++是整个代码实现核心。

实现代码如下:


#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
  assert(dest && src);
  char* ret = dest;  // 先要记住dest的起始位置
  while (*dest++ = *src++)  // 先运算*dest = *src,再判断*dest,再分别++
  {
    ;
  }
  return ret; // 返回dest起始位置
}
int main()
{
  char arr1[] = "xxxxxxxxxx";
  char arr2[] = "abcdef";
  printf("%s\n", my_strcpy(arr1, arr2));
  return 0;
}


strcat


该函数的功能是在目的地字符串末尾追加源字符串(连接),目的地字符串的末尾不包括\0,也就是说\0将会被追加的字符串覆盖。

  • 注意:
    源字符串必须以 \0 结束。
    目标空间必须足够的大,能容纳下追加后目的地字符串的所有内容。
    目标空间必须可修改。


dca2c8d60d964f28a78be5dcdeeabf67.png


strcat的使用

1.正常的追加

42cf21965c16468aac933644766282ad.png

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[20] = "xxxxx";
  char arr2[] = "abc";
  printf("%s\n", strcat(arr1, arr2));
  return 0;
}

运行结果为:xxxxxabc

2.目的地字符串中存在\0


b88641f38d244492a89ff7127fd79dca.png


#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[20] = "xxx\0xxxxxxxx";
  char arr2[] = "abcdef";
  printf("%s\n", strcat(arr1, arr2));
  return 0;
}

运行结果为:xxxabcdef

要注意:


strcat不能追加自己,因为再追加自己的同时,末尾的\0在追加的时候被修改了,这时就会死循环,因为要追加的字符串也找不到\0了,此时程序会崩溃。如果要追加自己,可以用下面要讲解的strnpy函数。


strcat的自我实现

通过上面的解析可以知道,我们首先要让一个指针找到dest目的地字符串)的\0,再进行追加(连接),而追加的功能类似于拷贝(*dest++ = *src++)。


代码实现:

#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
  assert(dest && src);
  char* ret = dest; 
  // 先找到 dest 的第一个 \0
  while (*dest)
  {
    dest++;
  }
  while (*dest++ = *src++) // 追加
  {
    ;
  }
  return ret;
}
int main()
{
  char arr1[20] = "xxxxx";  // 大小为 20 ,为了能够承受住 arr2 的追加
  char arr2[] = "abcdef";
  printf("%s\n", my_strcat(arr1, arr2));
  return 0;
}


运行结果为:xxxxxabcdef

strcmp

该函数的功能是比较两个字符串,看相等,小于,还是大于,是小于还是大于是根据字符的ASCLL码值来比较的。而字符的比较是两个字符串一对一对字符的比。


55ddedca99f84ae2b332ea2f61d9d69a.png

ce77c77560aa46318c398335d05a9790.png


  • 如果str1 < str2 返回一个小于0的数,如果str1 == str2返回0,如果str1 > str2返回一个大于0的数。
  • 例如”abc““ac”比较,a == ab != c,又bASCLL码值``小于``c的ASCLL码值,所以返回一个小于零的数。


strcmp的使用

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "abcdq";
  char arr3[] = "abcd";
  printf("%d\n", strcmp(arr1, arr2));
  printf("%d\n", strcmp(arr1, arr3));
  return 0;
}


运行结果为:-1 1

这是因为vs的strcmp如果小返回-1,大返回1,相等返回0,而标准就是上面所说。


7c6a42ce40ba4b7397bd88fcf4e5bffd.png


strcmp的自我实现

这里按标准的返回值来实现

#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
  assert(str1 && str2);
  while (*str1 != '\0' || *str2 != '\0')
  {
    if (*str1 - *str2)
    {
      return *str1 - *str2;
    }
    str1++;
    str2++;
  }
  return 0;
}
int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "abcdq";
  printf("%d\n", my_strcmp(arr1, arr2));
  return 0;
}


运行结果为:-12

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

strncpy


该函数的功能是指定拷贝几个字符,与strcpy不同的是,strncpy多了一个确定拷贝字符个数的参数,这也就限制了长度,让使用者更能精确的拷贝自己想要的字符。


同样要注意的是:

1.源字符串拷贝到目的地字符串时不能超出目的地字符串的空间大小;

2.如果拷贝个数小于源字符串的长度,这时不会拷贝\0,也就是“abcdef”,如果拷贝4个,则只拷贝“abcd”过去;

3.如果拷贝个数大于源字符串的长度 + 1(因为后面还有一个\0),则多出来的拷贝放\0。


该函数的函数参数:


9237c4425b554e7d9b0f7580266261aa.png

strncpy的使用

1.正常拷贝:

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[] = "xxxxxxxxxxxxxxxx";
  char arr2[] = "abcdef";
  printf("%s\n", strncpy(arr1, arr2, 5));  // 拷贝5个
  return 0;
}


运行结果为:abcdexxxxxxxxxxx

2.拷贝个数等于源字符串的长度 + 1

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[] = "xxxxxxxxxxxxxxxx";
  char arr2[] = "abcdef";
  printf("%s\n", strncpy(arr1, arr2, 7)); // 拷贝7个,该字符串的长度为6
  return 0;
}


运行结果为:abcdef

3.拷贝个数大于源字符串的长度 + 1:

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[] = "xxxxxxxxxxxxxxxx";
  char arr2[] = "abcdef";
  printf("%s\n", strncpy(arr1, arr2, 10)); // 拷贝10个,arr2不够,后面拷贝\0
  return 0;
}


76898c38b3094fa2b2da74d9bf2ab6ed.png


运行结果为:abcdef


相关文章
|
1月前
|
存储 网络协议 编译器
【C语言】深入解析C语言结构体:定义、声明与高级应用实践
通过根据需求合理选择结构体定义和声明的放置位置,并灵活结合动态内存分配、内存优化和数据结构设计,可以显著提高代码的可维护性和运行效率。在实际开发中,建议遵循以下原则: - **模块化设计**:尽可能封装实现细节,减少模块间的耦合。 - **内存管理**:明确动态分配与释放的责任,防止资源泄漏。 - **优化顺序**:合理排列结构体成员以减少内存占用。
151 14
|
1月前
|
存储 编译器 C语言
【C语言】数据类型全解析:编程效率提升的秘诀
在C语言中,合理选择和使用数据类型是编程的关键。通过深入理解基本数据类型和派生数据类型,掌握类型限定符和扩展技巧,可以编写出高效、稳定、可维护的代码。无论是在普通应用还是嵌入式系统中,数据类型的合理使用都能显著提升程序的性能和可靠性。
53 8
|
1月前
|
存储 算法 C语言
【C语言】深入浅出:C语言链表的全面解析
链表是一种重要的基础数据结构,适用于频繁的插入和删除操作。通过本篇详细讲解了单链表、双向链表和循环链表的概念和实现,以及各类常用操作的示例代码。掌握链表的使用对于理解更复杂的数据结构和算法具有重要意义。
536 6
|
1月前
|
存储 网络协议 算法
【C语言】进制转换无难事:二进制、十进制、八进制与十六进制的全解析与实例
进制转换是计算机编程中常见的操作。在C语言中,了解如何在不同进制之间转换数据对于处理和显示数据非常重要。本文将详细介绍如何在二进制、十进制、八进制和十六进制之间进行转换。
43 5
|
1月前
|
C语言 开发者
【C语言】断言函数 -《深入解析C语言调试利器 !》
断言(assert)是一种调试工具,用于在程序运行时检查某些条件是否成立。如果条件不成立,断言会触发错误,并通常会终止程序的执行。断言有助于在开发和测试阶段捕捉逻辑错误。
48 5
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
94 2
|
3月前
|
缓存 Java 程序员
Map - LinkedHashSet&Map源码解析
Map - LinkedHashSet&Map源码解析
89 0
|
3月前
|
算法 Java 容器
Map - HashSet & HashMap 源码解析
Map - HashSet & HashMap 源码解析
69 0
|
16天前
|
存储 设计模式 算法
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。 行为型模式分为: • 模板方法模式 • 策略模式 • 命令模式 • 职责链模式 • 状态模式 • 观察者模式 • 中介者模式 • 迭代器模式 • 访问者模式 • 备忘录模式 • 解释器模式
【23种设计模式·全精解析 | 行为型模式篇】11种行为型模式的结构概述、案例实现、优缺点、扩展对比、使用场景、源码解析
|
16天前
|
设计模式 存储 安全
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析
结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式,前者采用继承机制来组织接口和类,后者釆用组合或聚合来组合对象。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象结构型模式比类结构型模式具有更大的灵活性。 结构型模式分为以下 7 种: • 代理模式 • 适配器模式 • 装饰者模式 • 桥接模式 • 外观模式 • 组合模式 • 享元模式
【23种设计模式·全精解析 | 创建型模式篇】5种创建型模式的结构概述、实现、优缺点、扩展、使用场景、源码解析

推荐镜像

更多
下一篇
开通oss服务