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

四、总结

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

相关文章
|
2月前
|
C语言
【C语言篇】字符和字符串以及内存函数详细介绍与模拟实现(下篇)
perror函数打印完参数部分的字符串后,再打印⼀个冒号和⼀个空格,再打印错误信息。
|
2月前
|
存储 安全 编译器
【C语言篇】字符和字符串以及内存函数的详细介绍与模拟实现(上篇)
当然可以用scanf和printf输入输出,这里在之前【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)已经讲过了,这里就不再赘述,主要介绍只针对字符的函数.
|
2月前
【C初阶】内存函数:memcpy+memmove+memset+memcmp
【C初阶】内存函数:memcpy+memmove+memset+memcmp
|
3月前
|
存储 缓存 C语言
【C语言】字符函数,字符串函数,内存函数
C语言中的字符串函数和内存函数
35 0
【C语言】字符函数,字符串函数,内存函数
|
2月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
|
3月前
|
存储 分布式计算 Hadoop
HadoopCPU、内存、存储限制
【7月更文挑战第13天】
215 14
|
2月前
|
存储 监控 Docker
如何限制docker使用的cpu,内存,存储
如何限制docker使用的cpu,内存,存储
|
3月前
|
存储 固态存储 芯片
计算机中内存与存储
【7月更文挑战第28天】
44 1
|
3月前
|
存储 弹性计算 程序员
新手程序员如何阿里云服务器配置?新人开发者CPU内存带宽存储怎么选?
对于新手开发者、个人或学生选择阿里云服务器,推荐ECS经济型e实例(ecs.e-c1m1.large),适用于小型网站或轻量应用。配置2核2G内存、3M固定带宽、40G ESSD系统盘,仅99元/年且续费同价。
|
2月前
|
存储 编译器 C语言
数据在内存中的存储
数据在内存中的存储

热门文章

最新文章

下一篇
无影云桌面