深入了解字符(串)函数 -- -- 字符(串)函数的实现(strlen、strcpy、strcmp、strcat、strstr、)内存函数的实现(memcpy、memmove)

简介: 深入了解字符(串)函数 -- -- 字符(串)函数的实现(strlen、strcpy、strcmp、strcat、strstr、)内存函数的实现(memcpy、memmove)

一、前言

C语言不像其它编程语言有专门的字符串类型,像Java语言就有专门对应字符串的引用类型String类型,C语言的字符串一般是存在于字符数组或由字符指针表示,字符串是指一串0个或多个字符,并且以一个位模式为全0的NULL字节结尾,因为NUL为非0打印字符,并且NUL的ASSIC码值为0,所以选它为终止符,字符串的这个特性非常重要,字符串的长度指的是NUL之前字符的长度,不包括NUL。

二、字符(串)函数的理解与实现

1.strlen函数

(1)功能介绍

1.计算字符串长度,不包含\0

2.参数为char*的字符指针,返回size_t的无符号整型。

(2)注意事项

1.传给strlen的参数是字符串的首元素地址,也就是接收一个地址,返回的是从该地址处到\0地址处之间的字符个数

2.如果字符串中没有\0,strlen会返回一个随机值,不同编译器下的返回值也会有所差异(当找不到\0时,strlen会越界查找\0,直到找到为止)

3.当strlen接收一个字符时,strlen会将该字符的ASSIC码值作为地址强行访问,程序最终崩溃,原因是strlen非法访问,导致程序运行异常。



举个栗子:

1.strlen使用:

#include <stdio.h>
#include <string.h>
int main()
{
  char a[] = "abcdef";//字符数组存储字符串
  char* p = "ljj";//字符指针存储字符串首元素地址
  printf("%d\n", strlen(a));//a[]中字符串长度为6
  printf("%d\n", strlen(p));//p指向的字符串长度为3
  return 0;
}

看结果:



2.strlen注意:

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[] = {'a','b','c','d','e','f'};//字符数组存储字符,没有'\0'
  char arr2[] = "abcdef";//字符指针存储字符串首元素地址
  printf("%d\n", strlen(arr1));//arr1数组中没有'\0',会返回一个随机值
  printf("%d\n", strlen(arr2[0]));//arr2[0]就是字符'a',它的ASSIC码值为97,
                                  //strlen将97作为地址非法访问,程序崩溃
  return 0;
}

看结果:



3.返回size_t类型的分析:

#include <stdio.h>
#include <string.h>
int main()
{
  if (strlen("abc") - strlen("abcdef") > 0)//常规来算的话,字符串"abc"长度是3,
                       //"abcdef"长度是6,相减应为-3,小于0
  {
    printf(">\n");//但输出的结果确是 >
  }
  else
  {
    printf("<\n");
  }
  return 0;
}


这是因为strlen函数返回的是size_t的类型,两个size_t类型的数相减得到的还是size_t的类型,也就是无符号整型,自然就是大于等于0了。

(3)模拟实现

strlen函数的实现大致有3种方法,都列举如下,具体的思路都以注释的方式结合在如下源码中。

1.一般思路:

#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)//const修饰保证指针不被修改
{
  assert(str);//保证指针的有效性
  size_t count = 0;//记录字符的个数,由于字符串的长度为大于等于0的数,
           //所以直接将它定义为无符号整数最后返回
  while (*str++)//判断字符是否为\0,不是\0就自增,count也自增记录个数
  {
    count++;
  }
  return count;//返回的就是字符串的长度
}
int main()
{
  char arr[] = "abcdef";
  printf("%d\n", my_strlen(arr));
  return 0;
}

2.递归:

#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
  assert(str);
  if (*str != '\0')
    //当第一个字符不是'\0'时,字符串的长度至少为1
    return 1 + my_strlen(str + 1);//递归逐增
  else
    return 0;//是'\0'就返回0
}
int main()
{
  char arr[] = "abcdef";
  printf("%d\n", my_strlen(arr));
  return 0;
}

3.指针 - 指针:

#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
  assert(str);
  char* ret = str;//记录字符串开始的地址
  while (*str++)//循环终止时str指向\0后面的位置
  {
    ;
  }
  return str - ret - 1;//指针 - 指针返回的是两个指针间的元素个数,
             //只str-ret返回会包含\0,所以要再减1才是字符串的长度
}
int main()
{
  char arr[] = "abcdef";
  printf("%d\n", my_strlen(arr));
  return 0;
}

2.strcpy函数

(1)功能介绍

1.字符串拷贝,将一个字符串拷贝到另一个字符串中,包括’\0’

2.接收的参数是两个char*的字符指针,返回一个类型为char *的目标数组的首地址。



(2)注意事项

1.源字符串必须以’\0’结束

2.目标空间必须足够大,确保足够存放字符串

3.目标空间必须可变,以改变原字符串存放新传过来的字符串。

举个栗子:

1.拷贝包含’\0’:

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[10] = "xxxxxxxxxx";
  char arr2[] = "abcdef";
  strcpy(arr1, arr2);
  printf("%s\n", arr1);//以字符串形式打印,结果为abcdef
  return 0;
}


2.错误示范:

#include <stdio.h>
#include <string.h>
int main()
{
  char* p1 = "abcdef";//p1指向的常量字符串的首地址
  char arr2[] = "xxxxxxx";
  strcpy(p1, arr2);//p1是常量指针,不能被修改
  printf("%s\n", p1);//程序无法正常运行
  return 0;
}

(3)模拟实现

strcpy的模拟实现也很简单,思路作为注释结合在代码中了,希望能供各位参考!!!

代码如下:

#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, const char* src)
{
  assert(dest && src);//保证两个指针的有效性
  char* ret = dest;//保存字符串的首地址最后返回
  while (*dest++ = *src++)//拷贝的思路就是将源字符串的地址解引用赋值给目标字符串的解引用,
              //当赋的值不是'\0'的时候,循环继续,两个地址自增
  {
    ;
  }
  return ret;//返回的是目标字符串的地址
}
int main()
{
  char arr1[10] = "xxxxxxxxxx";
  char arr2[] = "abcdef";
  my_strcpy(arr1, arr2);
  printf("%s\n", arr1);//以字符串形式打印
  return 0;
}

3.strcat函数

(1)功能介绍

1.将源字符串追加到目标字符串之后

2.追加从目标字符串的\0处开始,追加包括源字符串的\0。



(2)注意事项

1.源字符串必须以’\0’结束

2.目标空间必须足够大,以完整存放追加的字符串和目标空间内原本的字符串之和

3.目标空间必须可修改。

举个栗子:

strcat使用:

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[20] = "hello ";
  char arr2[] = "world!";
  strcat(arr1, arr2);
  printf("%s\n", arr1);//打印结果为hello world!
  return 0;
}


(3)模拟实现

strcat的模拟实现如下,包含注释思路,仅供大家参考。

#include <stdio.h>
#include <assert.h>
char* my_strcat(char* dest, const char* src)
{
  assert(dest && src);
  char* ret = dest;//先记录dest所指向的指针
  while (*dest)//找目标字符串中的\0,当dest解引用不为\0时,进入循环体,dest++,继续判断是否为\0
  {
    dest++;
  }
  while (*dest++ = *src++)//找到dest的\0后,从该处开始拷贝src所指向的字符串
  {
    ;
  }
  return ret;
}
int main()
{
  char arr1[20] = "hello ";
  char arr2[] = "world!";
  my_strcat(arr1, arr2);
  printf("%s\n", arr1);//打印结果为hello world!
  return 0;
}

4.strcmp函数

(1)功能介绍

比较两个字符串的大小:

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

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

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



(2)注意事项

1.这个函数值得注意就是它的返回值,第一个字串大就是返回大于0的数,小就是返回小于0的数,相等返回0。

2.实际比较的是字符串每个字符的ASSIC码值

举个栗子:

strcmp使用:

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[] = "abc";
  char arr2[] = "abq";
  int ret = strcmp(arr1, arr2);//比较的是每个字符所对应的ASSIC码值
  if (ret > 0)
  {
    printf("arr1>arr2\n");
  }
  else if (ret == 0)
  {
    printf("arr1=arr2\n");
  }
  else
  {
    printf("arr1<arr2\n");//输出为arr1<arr2,'q'比'c'大
  }
  return 0;
}

(3)模拟实现

strcmp的模拟实现如下,包含注释思路,仅供大家参考。

#include <stdio.h>
#include <assert.h>
int my_strcmp(const char* str1, const char* str2)
{
  assert(str1 && str2);
  while (*str1 == *str2)//当两个字符串的字符相等时,进入循环,不相等无法进入循环
  {
    if (*str1 == '\0')//任何一个字符为\0,
    {
      return 0;
    }
    str1++;
    str2++;//两个指针自增继续判断
  }
  return *str1 - *str2;//不相等直接返回字符之差,第一个字符大返回就是大于0的数
}
int main()
{
  char arr1[] = "abc";
  char arr2[] = "abq";
  int ret = my_strcmp(arr1, arr2);//比较的是每个字符所对应的ASSIC码值
  if (ret > 0)
  {
    printf("arr1>arr2\n");
  }
  else if (ret == 0)
  {
    printf("arr1=arr2\n");
  }
  else
  {
    printf("arr1<arr2\n");//输出为arr1<arr2,'q'比'c'大
  }
  return 0;
}

5.strstr函数

(1)功能介绍

找子串:

在目标字符串中找是否存在与源字符串相同的子字符串,

若找到该子字符串,就返回目标字符串中子字符串往后的一段字符串,

若没有找到,则返回一个空指针。



(2)注意事项

返回值是指向目标字符串中子字符串的首字符的地址。

举个栗子:

strstr使用:

#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[] = "abbcde";
  char arr2[] = "bbc";
  char* sp = strstr(arr1, arr2);//将返回值给到sp的字符指针
  printf("%s\n", sp);//打印结果是bbcde
  return 0;
}


(3)模拟实现

strcmp的模拟实现如下,要想实现,必须满足如下两种情况,可以根据如下的图来理解,同时附有代码及注释参考。

情况1:



情况二:



代码如下:

#include <stdio.h>
#include <assert.h>
char* my_strstr(const char* str1, const char* str2)
{
  assert(str1 && str2);
  const char* s1 = str1;
  const char* s2 = str2;
  const char* ret = s1;
  while (*ret)//当str1中记录的子字符串地址的参数不为0时,才能继续查找字符串
  {
    s1 = ret;
    s2 = str2;
    while (*s1!='\0' && *s2!='\0' &&  * s1 == *s2)//str1和str2都还没有结束并且相同时,
                            //循环进行查找
    {
      s1++;
      s2++;
    }
    if (*s2 == '\0')
    {
      return ret;//如果str2找完了,说明子字符串找到了,返回ret记录的地址
    }
    ret++;
  }
  return NULL;//循环结束还没有找到就返回空指针
}
int main()
{
  char arr1[] = "abbcde";
  char arr2[] = "bbc";
  char* sp = my_strstr(arr1, arr2);//将返回值给到sp的字符指针
  printf("%s\n", sp);//打印结果是bbcde
  return 0;
}

三、内存函数的理解与实现

1.memcpy函数

(1)功能介绍

内存拷贝,可以拷贝任意类型的数据。



(2)注意事项

函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。

举个栗子:

memcpy使用:

#include <stdio.h>
#include <string.h>
int main()
{
  int arr1[10] = { 0 };
  int arr2[] = { 1,2,3,4,5,6,7,8,9 };
  memcpy(arr1, arr2, 5 * sizeof(int));//向arr1中拷贝arr2中的5个字节大小的数据
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", arr1[i]);
  }
  printf("\n");
  return 0;
}


(3)模拟实现

memcpy的模拟实现如下,包含注释思路,仅供大家参考。

#include <stdio.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
  assert(dest && src);
  void* ret = dest;//先记录dest的地址
  while (num--)
  {
    *((char*)dest) = *((char*)src);//将dest和src强制类型转换为char*类型,进行赋值
    ++(char*)dest;
    ++(char*)src;//复制后再以char*类型自增一个字节,每次比较的都是一个字节
  }
  return ret;//返回dest原本的地址
}
int main()
{
  int arr1[10] = { 0 };
  int arr2[] = { 1,2,3,4,5,6,7,8,9 };
  my_memcpy(arr1, arr2, 5 * sizeof(int));//向arr1中拷贝arr2中的5个整型大小的数据
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", arr1[i]);//打印结果为1234500000
  }
  printf("\n");
  return 0;
}

2.memmove函数

(1)功能介绍

内存拷贝,可以说是对memcpy的补充,memcpy在有些编译器中对内存块重叠的数据拷贝时,复制的结果是未定义的,而memmove可以很好的弥补这一缺陷。



(2)注意事项

同memcpy函数,函数memmove从source的位置开始向后复制num个字节的数据到destination的内存位置,对于内存重叠的数据,也能很好的拷贝出我们想要的结果。

举个栗子:

memmove使用:

#include <stdio.h>
#include <string.h>
int main()
{
  int arr1[10] = { 1,2,3,4,5,6,7,8,9 };
  memmove(arr1 + 2, arr1, 5 * sizeof(int));//向arr1+2的位置中拷贝arr1中的前5个整型大小的数据
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", arr1[i]);//打印结果为1212345890
  }
  printf("\n");
  return 0;
}


(3)模拟实现

memmove的模拟实现如下,包含注释思路,供大家参考。

#include <stdio.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
  assert(dest && src);
  void* ret = dest;//先记录dest的地址
  if (dest < src)
  {
    //前-->后
    while (num--)
    {
      *((char*)dest) = *((char*)src);//将dest和src强制类型转换为char*类型,进行赋值
      ++(char*)dest;
      ++(char*)src;//复制后再以char*类型自增一个字节,每次比较的都是一个字节
    }
  }
  else//后-->前
  {
    while (num--)
    {
      *((char*)dest + num) = *((char*)src + num);
    }
  }
  return ret;
}
int main()
{
  int arr1[10] = { 1,2,3,4,5,6,7,8,9 };
  my_memmove(arr1 + 2, arr1, 5 * sizeof(int));//向arr1+2的位置中拷贝arr1中的
                        //前5个整型大小的数据
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", arr1[i]);
  }
  printf("\n");
  return 0;
}

四、总结

以上就是一些我对字符(串)函数的理解与认识,希望对大家有所帮助,喜欢的可以一键三连哦,蟹蟹大家的支持!!!

相关文章
|
11天前
|
存储 C语言
C语言学习记录——动态内存函数介绍(malloc、free、calloc、realloc)
C语言学习记录——动态内存函数介绍(malloc、free、calloc、realloc)
17 1
|
2天前
|
安全 C语言
【C语言基础】:内存操作函数
【C语言基础】:内存操作函数
|
6天前
|
运维 Serverless Nacos
Serverless 应用引擎产品使用合集之在访问量过大的情况下,函数配置的cpu和内存会自动扩容吗
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
11天前
|
C语言 C++
C语言学习记录——内存函数(memcpy、memmove、memcmp、memset、模拟实现memcpy、模拟实现memmove)
C语言学习记录——内存函数(memcpy、memmove、memcmp、memset、模拟实现memcpy、模拟实现memmove)
16 3
|
1天前
|
消息中间件 存储 Kafka
实时计算 Flink版产品使用问题之 从Kafka读取数据,并与两个仅在任务启动时读取一次的维度表进行内连接(inner join)时,如果没有匹配到的数据会被直接丢弃还是会被存储在内存中
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
2天前
|
存储 小程序 编译器
【C语言基础】:数据在内存中的存储
【C语言基础】:数据在内存中的存储
|
3天前
|
存储 C++
C primer plus 学习笔记 第12章 存储类别、链接和内存管理
C primer plus 学习笔记 第12章 存储类别、链接和内存管理
|
11天前
|
存储 编译器 C语言
C语言学习记录——数据的存储(数据类型、类型的基本归类、整型在内存中的存储、大小端介绍、浮点型在内存中的存储)二
C语言学习记录——数据的存储(数据类型、类型的基本归类、整型在内存中的存储、大小端介绍、浮点型在内存中的存储)二
11 0
|
11天前
|
存储 编译器 C语言
C语言学习记录——数据的存储(数据类型、类型的基本归类、整型在内存中的存储、大小端介绍、浮点型在内存中的存储)一
C语言学习记录——数据的存储(数据类型、类型的基本归类、整型在内存中的存储、大小端介绍、浮点型在内存中的存储)一
19 2
|
11天前
|
存储 缓存 NoSQL
了解Redis,第一弹,什么是RedisRedis主要适用于分布式系统,用来用缓存,存储数据,在内存中存储那么为什么说是分布式呢?什么叫分布式什么是单机架构微服务架构微服务的本质
了解Redis,第一弹,什么是RedisRedis主要适用于分布式系统,用来用缓存,存储数据,在内存中存储那么为什么说是分布式呢?什么叫分布式什么是单机架构微服务架构微服务的本质