模拟实现C语言中经典库函数,字符相关的函数与内存相关的函数

简介: 模拟实现C语言中经典库函数,字符相关的函数与内存相关的函数
  • 前言:C语言中拥有非常多的库函数,仅仅知道它们是不够的,在知道它们的原理后,去模拟实现它能够帮助我们更好的掌握这些库函数。
    PS(在面试时,部分企业会让你来模拟实现一些库函数)

模拟实现strcmp

在模拟实现一个库函数之前,我们要先了解这个库函数的作用

这里我们可以通过这个查看C语言库函数的网站去查找:查找库函数

由此我们可以知道,strcmp的作用是将俩个字符串相比较,如果前者比后者长则返回大于0的数,反正则返回小于0的数,相等则返回等于0的数

代码实现思路:

分为三种情况:

①俩者所有字符全部相等 --> 返回0

②前者比后者小 --> 返回 -1

③前者比后者大 --> 返回1

代码实现:

#include <stdio.h>
#include <assert.h>
//模拟实现strcmp
int my_strcmp(const char* str1, const char* str2)//用const修饰防止传过来的指针值被修改
{
  assert(str1 && str2);//放在传过来的是空指针
  while (*str1++ == *str2++)//如果字符相等则进入循环看它们是否一一对应相等
  //如果不相等则跳出循环
  {
    if (*str1 == '\0')//如果*str1等于'\0'时,说明俩个字符串全部遍历完并且一一对应相等
      return 0;
  }
  if (*str1 > *str2)//如果不相等判断字符的大小,前置大则返回1
    return 1;
  else
    return -1;
}
int main()
{
  char arr1[100] = {0};
  char arr2[100] = {0};
  gets(arr1);
  gets(arr2);
  int ret = my_strcmp(arr1, arr2);//保存返回值
  printf("%d\n", ret);
  return 0;
}

运行结果:

模拟实现strcat

还是一样的,在实现一个库函数之前,先了解该库函数的作用

注:将一个字符串,拼接到另一个字符串后面,该字符串不能是同一个字符串,并返回目标字符串的地址。

代码实现思路:

  • 找到目标空间地址的尾部
  • 将另一个字符串衔接上去
    代码实现:
//模拟实现strcat
char* my_strcat(char* str1, char* str2)
{
  //1.找到目标地址的尾部
  //2.衔接上去
  assert(str1 && str2);
  char* tmp = str1;//保留目标空间的起始地址
  while (*str1++)//找到目标地址的尾部'\0'
  {
    if (*str1 == '\0')//找到'\0'
    {
      while ( *str1++ = *str2++)//将地址衔接上去
      {
        ;
      }
    }
  }
  return tmp;
}
int main()
{
  char arr1[100] = {0};
  char arr2[100] = {0};
  gets(arr1);
  gets(arr2);
  my_strcat(arr1, arr2);
  printf("%s\n", arr1);
  return 0;
}

运行结果:

模拟实现strstr

工欲善其事,必先利其器。如果我们想做好一件事,首先就要做好充分的准备工作。所以我们先了解其作用再来实现!

其作用是:在一个字符串中,找另一个字符串,如果没有找到则返回NULL,找到了就返回该字符串,并将原函数中该字符串后面的结果一并输出。

代码实现思路:

s1,s2(都是俩个字符串的指针)代表在s1中找s2

如果我们让s1,s2直接向匹配我们会发现,我们哪怕找到了想对应的字符,但是由于s1是不断的在往后走,我们并不能直接找到字符在相等的时候的位置,此时我们想要在通过原本的指针去找到相等的起始位置是十分困难的。因此,我们需要在引入一个指针去帮住我们记录下当俩个字符串完全相等的时候的起始地址。

我们让指针cp帮我们早到原本的地址,让s1去判断是否相等,让字符完全匹配时,返回cp的地址即可

代码实现:

//模拟实现strstr
const char* my_strstr(const char* str1, const char* str2)
{
  assert(str1 && str2);
  const char* cp;//记录开始匹配的位置
  const char* s1;//记录str1的位置
  const char* s2;//记录str2的位置
  if (*str2 == '\0')
  {
    return str1;
  }
  cp = str1;//让cp去记录开始匹配的位置
  while (*cp)
  {
    s1 = cp;
    s2 = str2;//让s2代替原本的str2去动
    while (*s1 && *s2 && *s1 == *s2)//s1,s2 !='\0' 并且s1 == s2
    {
      //找到它们的相同值,看他是否对应
      s1++;
      s2++;
    }
    if(*s2 == '\0')//如果全部找完,发现s2是'\0'说明找到了
    {
      return cp;//返回之前所留的cp记录的就是一开始的位置
    }
    cp++;//如果没有找到,就让cp往后加一继续重复刚刚的过程
  }
  return NULL;//如果全部运行了都没找到说明没有找到,返回空指针;
}
int main()
{
  char arr1[] = "mnabbbefghij";
  char arr2[] = "bbb";
  char* ret = my_strstr(arr1, arr2);
  if (ret == NULL)
  {
    printf("找不到\n");
  }
  else
  {
    printf("%s\n", ret);
  }
  return 0;
}

运行结果:

模拟实现memcpy

依然来先看该库函数的作用:

可以将任意同类型的数据,拷贝到另一个同类型的数据中去,但不能拷贝重叠内存

代码实现思路:

首先在实现的过程中,我们要知道,我们并不知道使用者会传过来什么类型的数据,因此我们在实现的过程中用俩个 void* 类型的指针来接受,并且我们要让使用者传过来他所需要拷贝的数据有多少字节。为什么需要知道字节?因为我们并不知道它的类型,我们将传过来的地址转换成char*类型,让它一次加1走一个字节俩俩交换,就能实现任意数据的交互了。

代码实现:

//模拟实现memcpy -- 不重叠的内存拷贝可以使用memcpy 
void* my_memcpy(void* arr1, const void* arr2, size_t num)
{
  char* tmp = arr1;
  assert(arr1 && arr2);
  while (num--)
  {
    *(char*)arr1 = *(char*)arr2;//强制类型转换是一种临时变量
    arr1 = (char*)arr1 + 1;//让它们一次走一个字节,走一次交换换一次
    arr2 = (char*)arr2 + 1;
  }//全部字节交换完成后它们的地址也就全部交换完成了
  return tmp;
}
int main()
{
  int arr1[] = { 1,2,3,4,5,6 };
  int arr2[] = { 0,9,2,2,9 };
  my_memcpy(arr1, arr2, 16);
  int i = 0;
  for (i = 0; i < 6; i++)
  {
    printf("%d ", arr1[i]);
  }
  return 0;
}

运行结果:

模拟实现memmove

还是老规矩,运行代码之前先来看它的作用

前面我们说讲的memcpy可以拷贝同类型的数据,但不能拷贝重叠内存,而这个库函数就可以完美实现拷贝重叠内存

代码实现思路:

我们要知道在同一个数据中拷贝数据的时候会出现俩种情况:

①把低地址的值覆盖到高低址

②把高地址的值覆盖到低地址

因此我们要对俩种情况进行分类讨论

代码实现:

//模拟实现memmove -- 可以拷贝重叠内存
void* my_memove(void* dest, void* src, size_t sz)
{
  char* tmp = dest;
  assert(dest && src);
  if (dest < src)//分俩种情况
  {
    //从前向后
    while (sz--)
    {
      *(char*)dest = *(char*)src;
      dest = (char*)dest + 1;
      src = (char*)src + 1;
    }
  }
  else
  {
    //从后往前
    while (sz--)
    {
    *((char*)dest+sz) = *((char*)src + sz);// 让src与dest找到最后一个数,从后往前赋值
    }
  }
  return tmp;
}
int main()
{
  int arr[] = { 1,3,2,7,8 };
  my_memove(arr+2, arr , 12);
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

运行结果:

序言

成大事不在于力量的大小,而在于能坚持多久。希望各位也能每天坚持学习,能够坚持也就是一种最大的天赋!如若写的不好的地方也希望各位指出。


相关文章
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
635 15
|
存储 C语言
`scanf`是C语言中用于按格式读取标准输入的函数
`scanf`是C语言中用于按格式读取标准输入的函数,通过格式字符串解析输入并存入指定变量。需注意输入格式严格匹配,并建议检查返回值以确保读取成功,提升程序健壮性。
1683 0
|
监控 网络协议 安全
基于标准C语言实现的跨平台Modbus协议库
基于标准C语言实现的跨平台Modbus协议库
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
792 1
一文彻底搞清楚C语言的函数
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
883 3
|
存储 算法 安全
【C语言程序设计——函数】分数数列求和1(头歌实践教学平台习题)【合集】
if 语句是最基础的形式,当条件为真时执行其内部的语句块;switch 语句则适用于针对一个表达式的多个固定值进行判断,根据表达式的值与各个 case 后的常量值匹配情况,执行相应 case 分支下的语句,直到遇到 break 语句跳出 switch 结构,若没有匹配值则执行 default 分支(可选)。例如,在判断一个数是否大于 10 的场景中,条件表达式为 “num> 10”,这里的 “num” 是程序中的变量,通过比较其值与 10 的大小关系来确定条件的真假。常量的值必须是唯一的,且在同一个。
1131 2
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
3862 0
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
1280 1
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
1258 0