【C字符串函数】字符串函数和内存操作函数模拟实现(进阶版)(二)

简介: 【C字符串函数】字符串函数和内存操作函数模拟实现(进阶版)

3.长度受限的字符串函数


下列带n字符串函数和不带n的字符串使用规则和性质基本相同,主要区别在于,带n的字符串有长度限制(当n>strlen(src)时,dest的剩余部分将用空字节来填充),当然对于带限制的n的字符串兄弟函数在代码上也就是在while判断的时候多了一个n–的(n!=0)的判断条件.


3-1strncpy拷贝(限)

  • n为要从源中复制的字符数

函数原型:char* strncpy(char* dest,const char* src,size_t count)

基本使用:

int main()
{
  char arr1[20] = "xxxxxxxx";
  char arr2[] = "hello";
  printf("%s\n", strncpy(arr1, arr2, 4));
  return 0;
}

运行结果:

81b01e92591d41c2a4e2ab2ce0e3b672.png

模拟实现:

char* my_strncpy(char* dest, const char* src,size_t count)
{
    assert(dest&&src);
  const char* ret = dest;
    while(count--&&*dest++=*src);
  return ret;
}

3-2strncat连接(限)

函数原型:char* strncat(char* dest,const char* src,size_t count)

基本使用:

模拟实现:

char* my_strcat(char* dest, const char* src,int n)
{
   assert(dest&&src);
  char* ret = dest;
  while (*dest)//找到dest的'\0'位置
  {
    dest++;
  }
  while (n--&&*dest++ = *src++);//从src拷贝n个字符/src指向的那个字符串的字符个数到dest
  //*dest = '\0';前面并没有从src拷贝'\0'过dest,但dest本身后面都是'\0'所以可以不写这一步
  return ret;
}

3-3strncmp比较(限)

函数原型:int strcmp(const char* str1,const char* str2,size_t count)

基本使用:

int main()
{
  char arr1[20] = "hello";
  char arr2[20] = "hello";
  int ret=strncmp(arr1, arr2,5);
  printf("%d\n", ret);
  return 0;
}

模拟实现:

//
int my_strncmp(const char* str1, const char* str2,int n)
{
    assert(str1&&str2);
  while (n--&&*str1==*str2&&*dest)//拷贝结束或*str1!=*str2或*dest=='\0'结束
  {
    str1++;
    str2++;
  }
  return *str1 - *str2;//内容相减得到的是字符的ASCII值的差值;>0代表*str1>*str2
}

4.字符串查找

4-1strstr找子串

  • 在arr1种查找子串arr2
  • 找到则返回第一个子串的首地址,没找到则返回NULL
  • 类似的算法:KMP算法-KMP优点:更高效
  • 函数原型:char* strstr(const char* str1,const char* str2)

基本使用:

int main()
{
  char* arr1 = "abbbcde";
  char* arr2 = "bbcde";
  char* ret=strstr(arr1,arr2);
  if(ret==NULL)
  {
  printf("找不到\n";
    }
  else
  {
  printf("找到了,%s\n",ret);
  }
  return 0;
}

模拟实现:

char* my_strstr(const char* str1, const char* str2)
{
    assert(str1&&str2);
  char *s1 = str1;
  char *s2 = str2;
    char* p = str1;//p用于记录s1每次是从哪里开始的,方便后续s1!=s2时,s1从哪里开始
  if (*s2 == '\0')//如果查找的子串为空,则自定义返回为str1,也可返回空
  {
    return str1;
  }
  while(*p)//原本传过来的str1就为空,或s1已经是最后一个有效字符;
  {
    s1 = p;//s1每次从上一次保存位置的下一个开始
    s2 = (char*)str2;//s2每次从str2开始
    while (*s1 && *s2 && ( * s1 == *s2))
    //结束循环的三种路径:
    //1.s1为'\0',即被查找完毕都没找到子串
    //2.s2为'\0',即遍历s2,说明在s1中找到了子串
    //3.*s1!=*s2,即不相等
    {
      s1++;
      s2++;
    }
    if (*s2 == '\0')//s2此时为0,说明s2中的字符已被查找完->子串查找成功
    {
      return (char*)p;//p被const修饰,避免类型差异报错,故强制转换为char*类型
    }
    p++;//如果s1!=s2,标记处后移一位
  }
  return NULL;//没找到,返回NULL
}

4-2strtok切割

函数原型:char* strtok(char* str,const char* sep);


sep参数是个字符串,定义了用作分隔符的字符集合


第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记。


strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。


(注: strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容并且可修改。)


strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串 中的位置。


strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。


如果字符串中不存在更多的标记,则返回NULL 指针


基本使用:

int main()
{
  char arr1[] = "syh_it@outlook.com";
  //因为切割过程会改变arr1,所以对arr1拷贝,仅是对拷贝的字符数组进行操作
  char temp[30] = { 0 };//
  strcpy(temp,arr1);
  char arr2[] = "@.";//分隔符
  /*char* p = strtok(temp, arr2);
  第一次调用
  temp!=NULL,向后找@的位置,找到将@改为'\0',并做好标记,下次直接从'\0'处开始找
  printf("%s\n", p);
  p = strtok(NULL, arr2);
第二次调用
从上次记录的位置开始,找到下一个标记符
  printf("%s\n", p);
  p = strtok(NULL, arr2);
  printf("%s\n", p);*//函数内部有记忆,出了函数并没有销毁,猜测有static修饰
  for (char* p = strtok(temp, arr2); p!=NULL;p=strtok(NULL,arr2))
  {
    printf("%s\n", p);
  }
}

运行结果:

62f6afa179ba48feb387f02509510338.png

5.错误信息报告

5-1strerror打印错误信息

  • 返回错误码所对应的错误信息
  • 当库函数调用有问题时,就会产生错误码,如文件打开失败
  • 类似网页错误码404,而strerror的作用就是将错误码转换为人可识别的错误信息打印出来
  • 额外引用头文件:#include<errno.h>

函数原型:char* strerror(int errnum)

int main()
{   
  FILE* fp = fopen("text1.txt", "r");
  if (fp == NULL)
  {
      // errno  没有错误默认为0
        //No error 表示没有错误
    printf("%s\n", strerror(errno));
    //perror较strerror的优点:当要打印的错误信息比较多的时候,perrror可以附加错误信息的备注 的优点可以完美展现
    perror("每天都要记得刷题的个人信息");
  }
  else
  {
    printf("%s\n", "打开成功");
    fclose(fp);
    fp = NULL;
  }
  return 0;
}

6.字符操作

6-1字符分类函数(判断) & 6-2字符转换(转换)


image.png


使用举例:

int main()
{
  char ch = 'A';//'A':65
  //printf("%c\n", ch + 32);//'a':97
  printf("%c\n", tolower(ch));
  //注意 : tolower(ch)返回值是小写字母的ASCII码值,而不是把ch从大写改变成了小写
  //需要头文件 #include <ctype.h>
  //把字符串中的大写改为小写
  char str[] = "Test String.\n";
  char c;
  int i = 0;
  while (str[i]!='\0') 
  {
    c = str[i];
    if (isupper(c))//大写?
    {
      c = tolower(c);//转换为小写
    }
    putchar(c);
    i++;
  }
  return 0;
}

7.内存操作函数

9f5a9044611f4077be1eaa31e4349337.png

7-1memcpy(内存拷贝)

函数原型:void* memcpy(void* dest,const void* src,size_t num)

  • 这个函数不止能用于字符数组的拷贝,扩大至整型数组
  • 这个函数在遇到’\0的时候并不会停下来,也不一定需要’\0’
  • 如果dest目标空间和src源空间有任何的重叠,复制的结果会因为位置的不同产生两种结果(内存不可重叠) ,只能用memmove函数

基本使用:

int main(){
  int arr1[5] = { 1,2,3,4,5 };
  int arr2[10] = { 0 };
  //将arr1拷贝到arr2中
  memcpy(arr2, arr1, 20); 
  for (int i = 0; i < 10; i++)
  {
    printf("%d\t", arr2[i]);
  }
  return 0;
}

模拟实现:

void* my_memcpy(void* dest, void* src)
{
  assert(dest && src);
  void* ret = dest;
  while (num--)
  {
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1;
    src = (char*)src + 1;
  }
  return ret;
}
int main()
{
    assert(dest&&src);
  void* ret = dest;
  while (count--)//按字节数复制,因为可能要复制的count不是int字节的整数倍
  {
    *(char*)dest = *(char*)src;//char*强转告诉dest和sec+1加多少个步长
    dest = (char*)dest + 1;
    src = (char*)src + 1;
  }
  return ret;
}

7-2memmove(内存移动)

  • 解决当dest和src有重叠部分,引发的问题(内存可重叠)

函数原型:void* memcpy(void* dest,const void* src,size_t num)

基本使用:

int main()
{
  int arr1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
  //将1 2 3 4 拷贝到3 4 5 6的位置
  memmove(arr1 + 2, arr1, 16);
  return 0;
}


8c11a6c287ed4fff8fb452abfc27104f.png

模拟实现:

void* my_memmove(void* dest, const void* src, size_t count)
{ 
    assert(dest&&src);
  void* ret = dest;
  //当dest和src有重叠的时候,要分情况
  if (dest < src)//当dest在src左边,src应该从前向后拷贝
  {
    while (count--)
    {
      *(char*)dest = *(char*)src;
      dest = (char*)dest + 1;
      src = (char*)src + 1;
    }
  }
  else//当dest在src右边src从后向前拷贝
  {
    while (count--)
    {
      *((char*)dest + count) = *((char*)src + count);
    }
  }
  return ret;
}
目录
相关文章
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
35 3
|
1月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
1月前
|
存储 程序员 编译器
C语言——动态内存管理与内存操作函数
C语言——动态内存管理与内存操作函数
|
1月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
195 1
|
1月前
|
程序员 C语言
C语言内存函数精讲
C语言内存函数精讲
|
30天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
25 0
|
1月前
|
C语言 C++
c语言回顾-内存操作函数
c语言回顾-内存操作函数
41 0
|
1月前
|
存储 C语言 C++
来不及哀悼了,接下来上场的是C语言内存函数memcpy,memmove,memset,memcmp
本文详细介绍了C语言中的四个内存操作函数:memcpy用于无重叠复制,memmove处理重叠内存,memset用于填充特定值,memcmp用于内存区域比较。通过实例展示了它们的用法和注意事项。
69 0
|
1月前
一刻也没有为它哀悼~接下来登场的是动态内存分配的malloc与realloc以及free函数
一刻也没有为它哀悼~接下来登场的是动态内存分配的malloc与realloc以及free函数
70 0
|
1月前
|
编译器 C语言 C++
【C语言】精妙运用内存函数:深入底层逻辑的探索
【C语言】精妙运用内存函数:深入底层逻辑的探索