【字符串+内存函数的介绍】(二)

简介: 【字符串+内存函数的介绍】(二)

2.11 memcpy


void* memcpy(void* destination,const void* source,size_t num);


注:

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

2)这个函数遇到’\0’的时候并不会停下来。

3)如果source和destination有任何的重叠,复制的结果都是未定义的。


struct {
 char name[40];
 int age;
} person, person_copy;
int main ()
{
 char myname[] = "Pierre de Fermat";
 memcpy ( person.name, myname, strlen(myname)+1 );
 person.age = 46;
 memcpy ( &person_copy, &person, sizeof(person) );
 printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );//Pierre de Fermat 46
 return 0;
}

memcpy 负责拷贝两块独立空间中的数据,那么重叠内存的拷贝,是怎么做的呢?–>利用memmove


2.12 memmove


void* memmove(void* destination, const void* source, size_t num);

和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的,如果源空间和目标空间出现重叠,就得使用memmove函数处理。


int main()
{
  char str[] = "memmove can be very useful......";
  memmove(str + 20, str + 15, 11);
  puts(str);
  return 0;
}


微信图片_20230221145822.png

2.13 memcmp


int memcmp(const void* ptr1, const void* ptr2, size_t num);

比较从ptr1和ptr2指针开始的num个字节,返回值与strncpy相同,区别:strncpy比较的是指针指向的字符的大小,memcmp比较的是所有类型的大小。


3. 库函数的模拟实现


3.1 模拟实现strlen(三种方式)


方式1:计数器


int my_strlen(const char * str)
{
  int count = 0;
  while(*str)
  {
     count++;
     str++;
  }
  return count;
}

方式2:递归

int my_strlen(const char * str)
{
  if(*str == '\0')
  return 0;
  else
  return 1+my_strlen(str+1);
}


方式3:指针减指针(得到的为指针指向类型的个数)

int my_strlen(char *s)
{
   char *p = s;
   while(*p != ‘\0’ )
       p++;
   return p-s;
}


3.2 模拟实现strcpy

char* my_strcpy(char* dest, const char* src)
{
  char* ret = dest;
  assert(dest && src);
  while(*dest++ = *src++)
  {
    ;
  }
  return ret;
}

3.3 模拟实现strcat

char* my_strcat(char* dest, const char* src)
{
  char* ret = dest;
  assert(dest && src);
  while (*dest)//不能在里面直接++,这样会跳过一个'\0'的位置
  {
    dest++;
  }
  while (*dest++ = *src++)
  {
    ;
  }
  return ret;
}
int main()
{
  char arr1[20] = "abcdef";
  //char arr1[20];
  char arr2[5] = "ABCD";
  char* p = my_strcat(arr1, arr2);
  printf("%s\n", p);
  printf("%s\n", arr1);
  return 0;
}


strcat不能自己给自己追加,会无限循环下去:


微信图片_20230221150257.png

3.4 模拟实现strstr(下一篇扩展KMP算法)


三指针迭代,一指针记录位置,两个指针向后++查找


char* my_strstr(const char* str1, const char* str2)
{
  assert(str1 && str2);
  const char* s1 = str1;
  const char* s2 = str2;
  const char* p = str1;
  while (*p)
  {
    s1 = p;
    s2 = str2;
    while ( *s2 != '\0' && *s1 == *s2)
    {
      s1++;
      s2++;
    }
    if (*s2 == '\0')
    {
      return (char*)p;
    }
    p++;
  }
  return NULL;
}
int main()
{
  char arr1[] = "abcdef";
  char arr2[] = "def";
  char* ret = my_strstr(arr1, arr2);
  if (ret == NULL)
  {
    printf("子串不存在\n");
  }
  else
  {
    printf("%s\n", ret);
  }
  return 0;
}

3.5 模拟实现strcmp


int my_strcmp(const char* s1, const char* s2)
{
  assert(s1 && s2);
  while (*s1 == *s2)
  {
    if (*s1 == '\0')
    {
      return 0;//相等
    }
    s1++;
    s2++;
  }
  return (*s1 - *s2);
}
int main()
{
  char arr1[20] = "zhangsan";
  char arr2[] =   "zhangsanfeng";
  int ret = my_strcmp(arr1, arr2);
  if (ret < 0)
    printf("arr1<arr2\n");
  else if (ret == 0)
    printf("arr1=arr2\n");
  else
    printf("arr1=arr2\n");
  return 0;
}


3.6 模拟实现memcpy


void* my_memcpy(void* dest, const void* src, size_t num)
{
  assert(dest && src);
  void* ret = dest;
  while (num--)
  {
    *(char*)dest = *(char*)src;
    dest = (char*)dest + 1;
    src = (char*)src + 1;//强转的目的是让其+1迈过的长度为char,即一个字节
  }
  return ret;
}
int main()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[10] = { 0 };
  my_memcpy(arr2, arr1, 20);//20个字节大小,即5个整形
  return 0;
}

微信图片_20230221150611.png

为什么memcpy不能拷贝重叠的内存呢?通过模拟实现我们知道:当我们拷贝到重叠的部分时,那里已经被新的值所覆盖,故再次拷贝将会把新值拷贝过去。

微信图片_20230221150621.png

微信图片_20230221150628.png

当然,这是模拟实现的memcpy,当我们真正运用memcpy时,会发现出来的结果跟memmove一样:

微信图片_20230221150803.png

这是因为由于VS本身的功能会将这个错误避免,在不同编译器下,函数处理的结果可能会不一样,而模拟实现的my_memcpy才是memcpy真正的逻辑原理,因此,memmove还是非常必要的


3.7 模拟实现memmove


还以上面arr1数组为例,由于dest>src,故我们可以从右往左拷贝,即从高地址拷贝到低地址:

微信图片_20230221150914.png

即对模拟实现的my_memcpy稍作拷贝上的修改即可:

void* my_memmove(void* dest, void* src, size_t num)
{
  assert(dest && src);
  void* ret = dest;
  if (dest < src)
  {
    //与my_memcpy拷贝方向一致,即从前到后
    while (num--)
    {
      *(char*)dest = *(char*)src;
      dest = (char*)dest + 1;
      src = (char*)src + 1;
    }
  }
  else
  {
    //后->前
    while (num--)
    {
      *((char*)dest + num) = *((char*)src + num);
    }
  }
  return ret;
}
void test3()
{
  int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
  my_memmove(arr1 + 2, arr1, 20);
  //1 2 1 2 3 4 5 8 9 10
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", arr1[i]);
  }
}
int main()
{
  test3();
  return 0;
}

微信图片_20230221151123.png

4. 实现一道典型题目


微信图片_20230221151237.png

这个分支是我后续加上的(2022.7.25),因为我突然想到了一个新的方法来实现这道题目。在此之前,可以用两种方法实现,今天之后,就变成三种了:

  1. 指针数组
  2. 两步翻转
  3. strtok辅助实现


  • 1.指针数组


那先来介绍第一种,这也是我刚接触到这道题第一反应想到的方法:

思路是通过记住每一个单词的首地址,通过数组(数组的每一个元素为指针变量,即指针数组)封装起来,再将其地址逆序按%s输出:


#include<stdio.h>
#include<string.h>
int main()
{
    char* at[500];
    char arr[100];
    gets(arr);
    int len = strlen(arr);
    int i = 0;
    int j = 0;
    at[j++] = &arr[0];
    for(i=0;i<len;i++)
    {
        if(arr[i]==' ')
        {
            at[j++] = &arr[i+1];
            arr[i] = '\0';
        }
    }
    //arr[i] = '\0';
    for(i=j-1;i>=0;i--)
    {
        printf("%s ",at[i]);
    }
    return 0;
}

微信图片_20230221151458.png

当然结果是对的,但实际上,我们只是记录其位置将其打印而已,字符串本身并没有发生变化。因此,我们参考一下第二种方法:


  • 2.两部翻转


两步翻转,即先翻整体,再翻局部


微信图片_20230221151650.png


#include<stdio.h>
#include<string.h>
#include<assert.h>
void reverse(char* left, char* right)
{
    assert(left);
    assert(right);
    while (left < right)
    {
        char tmp = *left;
        *left = *right;
        *right = tmp;
        left++;
        right--;
    }
}
int main()
{
    char arr[101] = { 0 };
    //输入
    gets(arr);//I like beijing.
    //逆置
    int len = strlen(arr);
    //1. 逆序整个字符串
    reverse(arr, arr + len - 1);
    //2. 逆序每个单词
    char* start = arr;
    while (*start)
    {
        char* end = start;
        while (*end != ' ' && *end != '\0')
        {
            end++;
        }
        reverse(start, end - 1);
        if (*end != '\0')
            end++;
        start = end;
    }
    //输出
    printf("%s\n", arr);
    return 0;
}

微信图片_20230221151809.png

即这种方法在原数组的基础之上逆序了单词。


接下来,也就是增加这个分支的目的,即第三个方法,因为上文已经提到过strtok的用处,这里就不具体描述了。


  1. 3.strtok 辅助实现


#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
  char str[] = "I like beijing.";
  char* pch;
  char flag = *(str + strlen(str) - 1);
  pch = strtok(str, " ");
  char** arr = (char**)malloc(sizeof(char*) * 5);
  int j = 0;
  while (pch != NULL)
  {
    arr[j++] = pch;
    printf("%s ", pch);
    pch = strtok(NULL, " ");
  }
  printf("\n");
  for (int i = j-1; i>=0; i--)
  {
    printf("%s ", arr[i]);
  }
  free(arr);
  return 0;
}

微信图片_20230221152006.png

这种方式其实也是类似于第一种的思想,存储位置,逆序打印,但很明显,这种方法在strtok的辅助之下使其更为简洁和灵活,值得关注的是,这里的malloc大小为20个字节,如果单词数比较多的话,可以多开辟空间,是没有影响的,因为j记录了实际有效单词的数量。 当然即便面对单词之间有多个空格,strtok也会自动将其忽略,这也是strtok的强大之处。


5.总结:


通过对以上函数的了解,对于字符数组的操作以及内存类的函数会变得得心应手,要用其功能必先了解其原理。那么,这篇文章就到这里,码字不易,你们的支持将是我前进的不竭动力!

相关文章
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
35 3
|
15天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
36 6
|
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)
187 1
|
1月前
|
程序员 C语言
C语言内存函数精讲
C语言内存函数精讲
|
29天前
|
存储 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用于内存区域比较。通过实例展示了它们的用法和注意事项。
66 0
|
1月前
一刻也没有为它哀悼~接下来登场的是动态内存分配的malloc与realloc以及free函数
一刻也没有为它哀悼~接下来登场的是动态内存分配的malloc与realloc以及free函数
70 0