深入了解字符(串)函数 -- -- 字符(串)函数的实现(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;
}

四、总结

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

相关文章
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
33 3
|
27天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
22天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
19 0
|
1月前
|
C语言 C++
c语言回顾-内存操作函数
c语言回顾-内存操作函数
39 0
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
366 0
|
21天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
43 1
|
26天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
30天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
1月前
|
存储 编译器
数据在内存中的存储
数据在内存中的存储
41 4
|
1月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
53 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配