C语言进阶—— 字符操作函数+内存操作函数详解 (吐血爆肝 !!!)

Posted Perceus

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言进阶—— 字符操作函数+内存操作函数详解 (吐血爆肝 !!!)相关的知识,希望对你有一定的参考价值。

@toc


⏳ 前言 ⏳

本篇文章将会详细介绍和学习字符串操作函数,重点介绍处理字符和字符串的库函数和字符操作分类改写函数,内存操作函数的使用和注意事项,还有自己模拟实现该函数的代码练习。


⚽本章重点

求字符串长度

  • strlen

长度不受限制的字符串函数

  • strcpy
  • strcat
  • strcmp

长度受限制的字符串函数介绍

  • strncpy
  • strncat
  • strncmp

字符串查找

  • strstr
  • strtok

误信息报告

  • strerror

字符操作

内存操作函数

  • memcpy
  • memmove
  • memset
  • memcmp

⛳一、求字符串长度函数

1.strlen

在之前学习中,我们会经常使用一个字符串函数求字符串的长度-- strlen。

图解:

重点内容:

strlen函数的模拟实现三种方法:

#include<stdio.h>
#include<assert.h>
//1.计数器实现求字符串长度函数
    int my_strlen1(const char* str)//整个过程不改变指针指向内容,加上const
{
    assert(str != NULL);//加上断言,防止接收空指针
    int count = 0;
    while (*str != \'\\0\')//也可以直接用while(*str)
    {
        count++;
        str++;
    }
    return count;
}

//2.递归实现求字符串长度,不用创建临时变量
int my_strlen2(const char* str)
{
    assert(str != NULL);
    if (*str != \'\\0\')//也可以直接用if(*str)
    {
        return 1 + strlen(str + 1);//不能直接使用str++,可以使用++str,建议直接用str+1
    }
    else
        return 0;
}

//3.指针-指针得到中间元素的个数,实现求字符串长度
int my_strlen3(const char* str)
{
    assert(str != NULL);
    const char* tmp = str;//创建临时指针变量保存str起始值
    while (*str != \'\\0\')
    {
        str++;
    }
    return str - tmp;
}

int main()
{
    char arr[] = "abcdefgh";
    int ret1 = my_strlen1(arr);//1.计数器方法
    int ret2 = my_strlen2(arr);//2.递归方法
    int ret3 = my_strlen3(arr);//3.指针 - 指针方法
    printf("%d\\n", ret1);
    printf("%d\\n", ret2);
    printf("%d\\n", ret3);
    return 0;
}


✊二、长度不受限制的字符串函数

1.strcpy

如果我们要将一个字符串的内容拷贝到另外一个字符串空间中时,需要使用字符串拷贝- -strcpy函数,在正式介绍strcpy函数之前,我们先看一段代码:

#include<stdio.h>
#include<string.h>
int main()
{
    char arr1[] = "abcdefghik";
    char arr2[] = "hello";
    //将字符串arr2拷贝给arr1
    //不能些arr1 = arr2 因为数组名表示首元素地址
    strcpy(arr1, arr2);
    //用调试窗口观察arr1变化
    return 0;
}

strcpy函数进行字符串拷贝的算法分析

重点内容:

模拟实现strcpy

#include<stdio.h>
#include<assert.h>
char* my_strcpy(char* dest, const char* src)
{
    assert(dest != NULL);
    assert(src != NULL);
    char* ret = dest;//储存原字符串首字符地址

//拷贝src指向字符串的内容到dest指向的空间,包括\'\\0\'
    //方法一:常规思路版
    while (*src != \'\\0\')
    {
        *dest = *src;
        dest++;
        src++;
    }
    *dest = *src;

//方法二:代码精简版
    while (*dest++ = *src++)//遇到‘/0’为0,循坏停止
    {
        ;
    }
    //返回目的空间起始位置
    return ret;
}
int main()
{
    char arr1[] = "abcdefghik";
    char arr2[] = "hello";
    my_strcpy(arr1, arr2);//模拟实现strcpy函数
    printf("%s", arr1);
    return 0;
}


2.strcat

如果我们要将一个字符串的内容追加到另外一个字符串的末尾空间中时,需要使用字符串拷贝-- - strcat函数,在正式介绍strcat函数之前,我们先看一段代码:

#include<stdio.h>
#include<string.h>
int main()
{
    char arr1[30] = "hello";
    //arr1空间需要足够大来接收追加过来的内容
    //否则会造成越界访问
    char arr2[] = "world";
    //将字符串arr2内容追加给arr1
    strcat(arr1, arr2);
    printf("%s", arr1);
    return 0;
}

strcat在进行追加的时候是否会将’\\0’追加过去?

strcat函数进行字符串追加的算法分析:

重点内容:

的模拟实现strcat

#include<stdio.h>
#include<assert.h>
char* my_strcat(char* dest, const char* src)
{
    assert(dest != NULL);
    assert(src != NULL);
    char* dest_start = dest;

    //1.找到目的空间中的\'\\0\'
    while (*dest != \'\\0\')//跳过不是\'\\0\'的字符
    {
        dest++;
    }
    //2.追加
    while (*dest++ = *src++)
    {
        ;
    }
    return dest_start;
}

int main()
{
    char arr1[30] = "hello";
    char arr2[] = "world";
    my_strcat(arr1, arr2);//模拟实现strcat
    printf("%s", arr1);
    return 0;
}

思考:strcat为什么不能自己追加自己?


3.strcmp

如果我们要比较两个字符串的是否相等,或者比较字符串大小,不能用操作符 == 来直接进行判断,而是需要用到字符串比较函数strcmp

strcmp函数进行字符串追加的算法分析:

重点内容:

模拟实现strcmp

#include<stdio.h>
#include<assert.h>
int my_strcmp(const char* p1, const char* p2)
{
    assert(p1 && p2);
    while (*p1 == *p2)
    {
        if (*p1 == \'\\0\')
        {
            return 0;
        }
        p1++;
        p2++;
    }

//方法一:vs实现的方式
if (*p1 > *p2)
{
    return 1;
}
else
{
    return -1;
}

//方法二:linux下gcc实现方式
  int my_strcmp(const char* p1, const char* p2)
{
    return *p1 - *p2;
}

int main()
{
    char* p1 = "abc";
    char* p2 = "ab";
    int ret = my_strcmp(p1, p2);
    printf("ret = %d", ret);
    return 0;
}


✋三、长度不限制的字符串函数

1.strncpy

strncpy与strcpy相比较多了一个字母n,这个n代表的是需要拷贝字符的个数,也就是说strncpy需要关注拷贝字符的个数,而不是像strcpy那样关注’\\0’。

举例:

#include<stdio.h>
#include<string.h>
int main()
{
    char arr1[10] = "abcdefg";
    char arr2[] = "1234";
    int len = 0;//len是拷贝字节的个数
    scanf("%d", &len);
    strncpy(arr1, arr2, len);
    printf("%s", arr1);
    return 0;
}

算法图解分析:

重点内容

模拟实现strncpy

#include<stdio.h>
#include<assert.h>
//模拟实现strncpy
//方法一
//char* my_strncpy(char* dest, const char* src, size_t n)
//{
//  char* dest_start = dest;
//  while ((n > 0) && (*src != \'\\0\'))
//  {
//      *dest = *src;
//      dest++;
//      src++;
//      n--;
//  }
//  while (n > 0)
//  {
//      *dest = \'\\0\';
//      dest++;
//      n--;
//  }
//  return dest_start;
//}

//方法二
char* my_strncpy(char* dest, const char* src, size_t count)
//count比n更有实际意义
{
    assert(dest != NULL);//引用断言
    assert(src != NULL);
    char* start = dest;
    while (count && (*dest++ = *src++) != \'\\0\')
    {
        count--;
    }
    if (count)
    {
        while (count--)
        {
            *dest++ = \'\\0\';
        }
    }
    return start;
}

int main()
{
    char arr1[10] = "abcdefg";
    char arr2[] = "1234";
    size_t len = 0;
    scanf("%d", &len);
    my_strncpy(arr1, arr2, len);
    printf("%s", arr1);
    return 0;
}


2,strncat

strcat函数是字符串追加,在使用的时候以src的’\\0’作为追加结束标志,因此在使用strcat来追加一个字符串数组本身的时候,会因\\0被提前覆盖而无法追加成功。

strncat与strcat相比多了一个字母n,这个n代表的是需要追加字符的个数,也就是说strncat需要关注追加字符的个数,而不是像strcat那样关注’\\0’。

strncat在追加字符串的时候,会自动在末尾处添加字符串结束标志’\\0’。(这也是我们在追加的时候,不用关注原dest, src中’\\0’,仅需关注追加字符的个数的原因)

#include<stdio.h>
#include<string.h>
int main()
{
    char arr1[15] = "12345\\0xxxxxxx";
    char arr2[] = "abcd";
    size_t count = 0;
    scanf("%d", &count);
    strncat(arr1, arr2, count);
    printf("%s", arr1);
    return 0;
}

strncat在追加字符串的时候,会自动在末尾处添加字符串结束标志’\\0’。(这也是我们在追加的时候,不用关注原dest, src中’\\0’,仅需关注追加字符的个数的原因)

#include<stdio.h>
#include<string.h>
int main()
{
    char arr1[15] = "12345\\0xxxxxxx";
    char arr2[] = "abcd";
    size_t count = 0;
    scanf("%d", &count);
    strncat(arr1, arr2, count);
    printf("%s", arr1);
    return 0;
}

算法图解分析:

情况二程序演示效果:

重点内容:

模拟实现strncpy

#include<stdio.h>
#include<assert.h>
//方法一
//char* my_strncat(char* dest, const char* src, size_t count)
//{
//  char* start = dest;
//  //dest找到\\0的位置
//  while (*dest!=\'\\0\')
//  {
//      dest++;
//  }
//  while (count)
//  {
//      //将src中的字符追加给dest
//      *dest = *src;
//      dest++;
//      src++;
//      //如果src以及指向\\0的位置,提前结束循环
//      if (*src == \'\\0\')
//      {
//          break;
//      }
//      count--;
//  }
//  *dest = \'\\0\';//字符个数追加完毕后,再单独追加\'\\0\'
//  return start;
//}

//方法二
char* my_strncat(char* dest, const char* src, size_t count)
{
    assert(dest != NULL && src != NULL);
    char* start = dest;
    while (*dest++)
        ;
    dest--;
    while (count--)
        if ((*dest++ = *src++) == \'\\0\')
            return start;
    *dest = \'\\0\';
    return start;
}
int main()
{
    char arr1[15] = "12345\\0xxxxxxx";
    char arr2[] = "abcd";
    size_t count = 0;
    scanf("%d", &count);
    my_strncat(arr1, arr2, count);
    printf("%s", arr1);
    return 0;
}

3.strncmp

strcmp用来比较两个字符串的大小,其算法思想如下:

而strncmp与strcmp相比多了一个字母n,这个n代表的是需要比较字符的个数,也就是说strncmp需要关注比较字符的个数,而不是像strcmy那样仅关注’\\0’。

strncmp的返回结果与strcmp一样,返回 > 0, == 0, < 0 的整数。

以下面代码为例:

#include<stdio.h>
#include<string.h>
    int main()
{
    char arr1[] = "abcdef";
    char arr2[] = "abcd12";
    int ret = 0;
    size_t count = 0;
    scanf("%d", &count);
    ret = strncmp(arr1, arr2, count);
    if (ret > 0)
    {
        printf("arr1 > arr2\\n");
    }
    else if (ret == 0)
    {
        printf("arr1 = arr2\\n");
    }
    else
    {
        printf("arr1 < arr2\\n");
    }
    return 0;
}

算法图解分析:

重点内容:

库函数参考:

int __cdecl strncmp
(
    const char* first,
    const char* last,
    size_t      count
)
{
    size_t x = 0;
    if (!count)
    {
        return 0;
    }
    /*
     * This explicit guard needed to deal correctly with boundary
     * cases: strings shorter than 4 bytes and strings longer than
     * UINT_MAX-4 bytes .
     */
    if (count >= 4)
    {
        /* unroll by four */
        for (; x < count - 4; x += 4)
        {
            first += 4;
            last += 4;
            if (*(first - 4) == 0 || *(first - 4) != *(last - 4))
            {
                return(*(unsigned char*)(first - 4) - *(unsigned char*)(last - 4));
            }
            if (*(first - 3) == 0 || *(first - 3) != *(last - 3))
            {
                return(*(unsigned char*)(first - 3) - *(unsigned char*)(last - 3));
            }
            if (*(first - 2) == 0 || *(first - 2) != *(last - 2))
            {
                return(*(unsigned char*)(first - 2) - *(unsigned char*)(last - 2));
            }
            if (*(first - 1) == 0 || *(first - 1) != *(last - 1))
            {
                return(*(unsigned char*)(first - 1) - *(unsigned char*)(last - 1));
            }
        }
    }
    /* residual loop */
    for (; x < count; x++)
    {
        if (*first == 0 || *first != *last)
        {
            return(*(unsigned char*)first - *(unsigned char*)last);
        }
        first += 1;
        last += 1;
    }
    return 0;
}

⚾四、字符串查找

1.strstr

查找字符串,找子字符串

找到字符串返回的是子字符串的地址,找不到返回空指针。

#include<stdio.h>
#include<string.h>
int main()
{
    char arr1[] = "abbbcdefbcd";
    char arr2[] = "bcd";
    char* ret = strstr(arr1, arr2);
    if (ret == NULL)
    {
        printf("Can not find!\\n");
    }
    else
    {
        printf("%s\\n", ret);
    }
    return 0;
}

算法图解分析:

重点内容:

模拟实现strstr:( KMP算法 )

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>
#include <cassert>

char* my_strstr(const char* p1, const char* p2)
{
    assert(p1 != NULL);   //断言
    assert(p2 != NULL);

    char* s1 = NULL;
    char* s2 = NULL;
    char* cur = p1;

    if (*p2 == \'\\0\')  //如果p2中只放了\'/0\',返回一个字符串
    {
        return (char*)p1;
    }

    while (*cur != \'\\0\')
    {
        s1 = cur;  
        s2 = p2;

        while ((*s2 != \'\\0\') && (*s1 != \'\\0\') && (*s1 == *s2))
        {
            s1++;
            s2++;
        }
        if (*s2 == \'\\0\')
        {
            return cur;  //找到字串的情况
        }
        cur++;
    }
    return NULL;   //找不到字符的情况
}

int main()
{
    char* str1 = "abbbchjdfdj";
    char* str2 = "bbc";
    char* ret = my_strstr(str1, str2);

    if (ret == NULL)
    {
        printf("找不到\\n");
    }
    else
    {
        printf("%s", ret);
    }

    return 0;
}

其实库函数的实现逻辑跟我们实现的方式是一样的,两者有所区别的是代码风格,库函数的代码风格更

加简练,需要有一定的编程功底!

库函数源代码参考:

char* _cdecl strstr
(
    const char* str1,
    const char* str2
)
{
    char* cp = (char*)str1;
    char* s1, * s2;
    if (!*str2)
        return((char*)str1);
    while (*cp)
    {
        s1 = cp;
        s2 = (char*)str2;
        while (*s1 && *s2 && !(*s1 - *s2))
            s1++, s2++;
        if (!*s2)
            return(cp);
        cp++;
    }
    return(NULL);
}

2.strtok

应用举例:

int main()
{
    //192.168.31.121  网络ip地址 --点分十进制
    //192 168 31 121  ---strtok  .
    //student_zhang@whu.edu 邮箱地址
    //student_zhang whu edu --- @ .
    char arr[] = "student_zhang@whu.edu";
    char* p = "@."; //分割的字符集合

    char buf[100] = { 0 };
    char* ret = 0;//用于接收分割后标记的位置
    strcpy(buf, arr);//分割字符串之前先进行临时拷贝

    //分割buf中的字符串

    //一次只能分割一次,三段分割一共要调用三次,太挫了这种写
    ret = strtok(buf, p);
    printf("%s\\n", ret);

    ret = strtok(NULL, p);
    printf("%s\\n", ret);

    ret = strtok(NULL, p);
    printf("%s\\n", ret);

    //一次性打印
    for (ret = strtok(buf, p); ret != NULL; ret = strtok(NULL, p))
    {
        printf("%s\\n", ret);
    }

    return 0;
}


⛳五、误信息报告函数

1.strerror

错误码-- - 所对应的错误信息:

实际在使用的时候,错误码并非由我们来控制的,而是接收系统返回的错误信息

printf("%s\\n", strerror(errno));
//errno是一个全局的错误码的变量
//当C语言的库函数在执行的过程中发生了错误,就会把对应的错误码,赋值到errno中
//errno需要引用头文件 errno.h

实际使用举例:
打开文件

#include<stdio.h>
#include<errno.h>
int main()
{
    FILE* pf = fopen("test.txt", "r");
    //当前路径下是没有test.txt文件的,所以应该会打开文件失败

    if (pf == NULL)  //fopen函数返回指针,打开文件失败返回空指针
    {
        printf("%s\\n", strerror(errno));
    }
    else
    {
        printf("Open file success!\\n");
    }
    return 0;
}

示例:找不到文件


⌚六、字符操作

1.字符分类函数


2.字符转换函数:

举例:
大写字母转小写

#include <stdio.h>
#include <ctype.h>

int main()
{
    int i = 0;
    char str[] = "I AM A STUDENT ";

    while (str[i])
    {

        if (isupper(str[i])) //判断是否是大写
        {
            str[i] = tolower(str[i]);//转为小写
        }
        i++;
    }
    printf("%s\\n", str);
    return 0;
}


✨七、内存操作函数

1.memcpy

在之前的学习中,我们知道字符串拷贝可以使用strcpy函数,但是,当我们拷贝的数据不是字符串的时候,比如说int类型、double类型,还能使用strcpy函数吗?strcpy函数在拷贝的时候是以\\0为字符串拷贝的结束标志,那么在拷贝其它类型数据的时候,拷贝该结束的时候不一定存在\\0。所以使用strcpy函数肯定是行不通的。

那怎么办呢?

实际上我们可以使用memcpy函数-- - 内存拷贝函数,用来拷贝任意类型数据。

举例:

#include<stdio.h>
#include<string.h>
int main()
{
    int arr1[] = { 0,1,2,3,4 };
    int arr2[5] = { 0 };
    memcpy(arr2, arr1, sizeof(arr1));
    return 0;
}

拷贝前:

拷贝后:

算法分析 + 图解:

重点内容:

模拟实现函数:

#include<stdio.h>
#include<assert.h>
#include<string.h>

void* my_memcpy(void* dest, const char* src, size_t num)
{   //void* 可接受所有类型指针,但不能解引用操作和运算,那肯定要使用强制类型转换
    void* dest_start = dest; //存储地址

    assert(dest && src);

    while (num--)
    {
        //*(char*)dest = *(char*)src;
        //++(char*)dest
        //++(char*)src

        *((char*)dest)++ = *((char*)src)++;
    }
    return dest_start;
}
int main()
{
    int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[10] = { 0 };

    my_memcpy(arr2, arr1, 20);//20是字节,强制类型转换为char*步长为1
    return 0;
}


2.memmove

假设我们有一个整型数组 1 2 3 4 5 6 7 8 9 10 ,如果我们想要将前5个数字拷贝到第3 - 8个位置上,也就是:

如果我们通过my_memcpy可以做到吗?试验以下就知道了:

执行前:

执行后:

得到的结果是:1 2 1 2 1 2 1 8 9 10,并不是我们想要的 1 2 1 2 3 4 5 8 9 10

为什么呢?

分析后,可以发现从后向前的拷贝方式是可以的,不会出现后面需要被拷贝的数据提前被覆盖的情况。

但是,如果我们拷贝 3 4 5 6 7 到 1 2 3 4 5的位置上,那么从前往后的拷贝方式还行得通吗?
是不是发现从后向前拷贝会出现数据提前被覆盖的情况。所以这时候我们需要进行分情况讨论:

为了方便我们实际编程,我们可以将情况二、情况三合并起来,这样就得到:

重点内容

模拟实现memmove函数

#include<stdio.h>
#include<assert.h>
#include<string.h>

void* my_memmove(void* dest, const void* src, size_t num)
{
    void* dest_start = dest;
    assert(dest && src);
    if (dest < src)
    {
        //从前向后拷贝
        while (num--)
        {
            *(char*)dest = *(char*)src;
            ++(char*)dest; //dest = (char*)dest + 1;
            ++(char*)src;  //src = (char*)src + 1;
        }
    }
    else
    {
        //从后向前拷贝
        while (num--)
        {
            *((char*)dest + num) = *((char*)src + num);//+num字节找到最后的字节就实现从后往前拷贝
        }
    }
    return dest_start;
}
int main()
{
    int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[10] = { 0 };

    my_memmove(arr1 + 2, arr1, 20);
    return 0;
}


3.memcmp(简单了解)

举例:

#include<stdio.h>
#include<string.h>
int main()
{
    int arr1[4] = { 1,2,3,5 };
    int arr2[4] = { 1,2,3,4 };
    int ret = memcmp(arr1, arr2, sizeof(arr1));
    if (ret > 0)
    {
        printf("arr1 > arr2");
    }
    else if (ret == 0)
    {
        printf("arr1 == arr2");
    }
    else
    {
        printf("arr1 < arr2");
    }
    return 0;
}


4.memset(简单了解)

相关信息:

举例:

#include<stdio.h>
#include<string.h>
int main()
{
    char arr[] = "abcdefg";
    memset(arr, \'*\', 4);
    printf("%s", arr);
    return 0;
}



以上是关于C语言进阶—— 字符操作函数+内存操作函数详解 (吐血爆肝 !!!)的主要内容,如果未能解决你的问题,请参考以下文章

C语言进阶学习笔记三字符串函数详解(爆肝吐血整理,建议收藏!!!)

C语言学习内存操作函数之------->memcpy memmove 详解与手动实现

C语言学习内存操作函数之------->memcpy memmove 详解与手动实现

C语言进阶:字符串和内存函数

C语言进阶之旅(留下的足迹)

C语言进阶之旅(留下的足迹)