【C语言】字符函数&字符串函数&内存函数(下)

💛 前情提要💛
本章节就进入C语言的核心:深度剖析C语言库函数中的字符串函数内存操作函数

C语言中对字符和字符串的处理很是频繁,但是C语言不像python,本身是没有字符串类型的,所以字符串通常放在常量字符串中或者字符数组中。

字符串常量适用于那些对它不做修改的字符串函数.

接下来我们即将进入一个全新的空间,对代码有一个全新的视角~

以下的内容一定会让你对C语言有一个颠覆性的认识哦!!!

以下内容干货满满,跟上步伐吧~


🍞一.字符串查找系列函数

🥐Ⅰ.strstr函数

在这里插入图片描述

char * strstr ( const char *str1, const char * str2);

我们需要注意:

💡strstr函数的工作原理:在一个字符串中查找另外一个字符串【即判断一个字符串是否为另外一个字符串的子串】

  • 函数的形参为:主串的字符串的起始地址、需要被查找的字符串的起始地址(子串)

  • 函数的返回类型为:char*

    ❗有了以上了解,从中得出两个点:

  • 1️⃣在str1所指向的字符串中判断str2所指向的字符串是否为str1的子串【即在主串中判断是否有这个子串】

  • 2️⃣函数的返回值:

    1. 当在str1所指向的字符串中查找到str2所指向的字符串时,返回一个在str2在str1中第一次出现的地址
    2. 如果在str2在str1中查找不到,则返回一个NULL

👉strstr函数的模拟实现:

思路:

💫为了在函数的查找过程中找到子串时,能返回一个子串在主串中第一次出现的地址,我们需要设置两个指针:

  • ❗一个指针负责【当在主串中找到与子串第一个字符相等的时候,继续往后判断剩余的字符】
  • ❗一个负责【存储子串第一次在主串出现的地址,这样才能返回子串在主串中第一次出现的地址(即子串在主串中的起始地址)】
char* my_strstr(const char* str1,const char* str2)
//因为仅仅是在 str1里找str2是否存在【并不修改数据】
//所以可以加个const修饰
{
    assert(str1 && str2);

    const char* s1 = str1;
    const char* s2 = str2;

    const char* cp = str1;

    while (*cp)
    {
        s1 = cp;
        s2 = str2;

        //当子串为空字符串的时候:
        if (*str2 == '\0') 
        {
            return (char*)str1; //const修饰的指针 和 没被修饰的 相当于两种类型,所以还需要 强制类型转换
        }

        while (*s1 && *s2 && (*s1 == *s2))  
        //即 s1与s2也不能等于\0,不然\0=\0就成立
        //继续往后走就 非法访问内存了
        {
            s1++;
            s2++;
        }
        //不相等 or 判断完 的时候就跳出来

        //因为只有 *s2 = \0 的情况才代表 找完了
        //不然其他情况都不算 找完
        if (*s2 == '\0')
        {
            return (char*)cp;
        }
        //这样就很严谨了

        cp++;
    }

    return NULL;

}

🥐Ⅱ.strtok函数

这里是引用

char * strtok ( char * str, const char * sep )

💡strtok函数的作用:简单来说就是通过在字符串中查找分隔符从而达到字符串分割的效果

  • 函数的形参分别为:要被分割的字符串的起始地址、分隔符的字符集合的起始地址

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

      1. str(第一个参数)指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记
  • 函数的返回类型为:char*【被分割出来的字符串的起始地址】

➡️strtok函数的工作原理:

  • strtok函数会在str指向的字符串中找到sep集合中的标记(分隔符),并用\0替代这个标记,返回一个被分割出的这段字符串的起始地址

❗有了以上了解,可以从中得出三个点:

  • 1️⃣strtok函数的第一个参数不为 NULL 时,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置【即保存了在这个标记符时的位置,就不往后找了】

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

  • 3️⃣如果字符串中不存在更多的标记,则返回 NULL 指针【即已经全部分割完的时候,就返回NULL

💥特别注意:

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

👉示例:

我们现在需要分割一个字符串:“My email address is:Dream_Y-ocean@CSDN.com

我们想将每个单词都分割出来

Eg:“My”、“email”、“address”……

我们便需要:

  • 分割字符集合:、:@.

  • 拷贝被分割的字符串:strcpy函数

这里是引用
但上面的代码会不会显得冗余呢🤔


我们能不能再优化一下呢~


答案是可以的!!!

  • 我们可以用for循环
    在这里插入图片描述

所以我们要好好利用好for循环哦~~

int main()
{

    char arr[] ="My email address is:Dream_Y-ocean@CSDN.com";

    char sep[] = " :@.";  
    //空格、:、. 、@

    char tmp[50] = { 0 };

    strcpy(tmp, arr);

    //优化前:
    /*printf("%s\n", strtok(tmp, sep));

    printf("%s\n", strtok(NULL, sep));

    printf("%s\n", strtok(NULL, sep));

    printf("%s\n", strtok(NULL, sep));

    printf("%s\n", strtok(NULL, sep));

    printf("%s\n", strtok(NULL, sep));

    printf("%s\n", strtok(NULL, sep));*/

    //优化后:
    for (char* ret = strtok(tmp, sep); ret != NULL; ret = strtok(NULL, sep))
    {
        printf("%s\n", ret);
    }

    return 0;
}

🥯Ⅲ.总结

✨综上:就是字符串查找系列函数

➡️简单来说:就是以分割、查找…对字符串进行操作的函数

🍞二.错误信息报告函数

🥐Ⅰ.strerror函数

这里是引用

char * strerror ( int errnum );

💡strterror函数的作用:将错误码这个整型里的错误码翻译成错误信息

  • 函数的参数为:int【即参数一般为errnno

    • errno相当于C语言设置的一个全局变量,用来专门存储全局的错误码
    • 即只要程序调用失败,程序就自动把错误吧存储到errno这个变量中
      • 函数的返回类型为:char*

    👉示例:
    在这里插入图片描述

🥐Ⅱ.perror函数

在这里插入图片描述

void perror ( const char * str );

💡perror函数的作用:与strerror函数一样,将错误信息翻译出来,并打印出来

  • 函数的参数为:填入想打印的字符串

  • 函数的返回类型:void

➡️perror函数的工作原理:可以理解为perror将strerror函数内置在里面,并一并将其打印出来

  • 即程序调用失败的时候,perror先将程序员输入的字符串打印出来,然后程序自动加上并打印:,再把错误信息打印出来

👉示例:
在这里插入图片描述

🥯Ⅲ.总结

✨综上:就是错误信息报告函数

➡️简单来说:就是可以把错误信息打印出来的函数

🍞三.字符操作函数

🥐Ⅰ.字符分类函数

函数 下列函数如果条件满足都返回[>0]
iscntrl 判断字符是否为:任何控制字符
isspace 判断字符是否为:空白字符【空格‘ ’,换页‘\f’,换行'\n',回车‘\r’,制表符'\t'或者垂直制表符'\v'】
isdigit 判断字符是否为:十进制数字 0~9
isxdigit 判断字符是否为:十六进制数字【包括所有十进制数字,小写字母a~f,大写字母A ~ F】
islower 判断字符是否为:小写字母a~z
isupper 判断字符是否为: 大写字母A~Z
isalpha 判断字符是否为:字母az或AZ
isalnum 判断字符是否为:字母或者数字,a~ z,A~ Z,0~9
ispunct 判断字符是否为:标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph 判断字符是否为:任何图形字符
isprint 判断字符是否为:任何可打印字符,包括图形字符和空白字符

🥐Ⅱ.字符转换函数

函数 下列函数会将字符进行转换
tolower 将字符转换为小写
toupper 将字符转换为大写

🥯Ⅲ.总结

✨综上:就是字符操作函数

➡️简单来说:就是对字符进行操作判断的函数

🍞四.内存操作函数

🥐Ⅰ.memcpy函数

在这里插入图片描述

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

💡memcpy函数的作用:通过一个一个字节的方式去实现内存拷贝

  • 函数参数类型为:拷贝目标的起始地址、拷贝源的起始地址、拷贝的数据大小【单位:字节】

  • 函数返回类型为:void*

➡️memcpy函数的工作原理:对字节实现类似strcpy函数的操作,一个一个字节的拷贝

❗有了以上了解,可以从中得出一个点:

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

💥特别注意:

  • 1️⃣这个函数在遇到 \0 的时候并不会停下来【不像strcpy函数】

  • 2️⃣如果source和destination空间上有任何的重叠,复制的结果都是未定义的【即如果空间重叠的时候会影响拷贝】

👉示例:

初始化数组,想将数组内的1,2,3,4,5替换到3,4,5,6,7的位置上
在这里插入图片描述
拷贝后:
在这里插入图片描述
却发现想拷贝的数据被修改了❗

💫综上:memcpy的拷贝应避免重叠的内存空间

👉函数的模拟实现:

思路:

  • 因为函数是用void*指针去接收地址的,所以传什么类型都可实现memcpy函数的通用

  • 函数的实现大致与strcpy函数的实现一致,仅仅细微不同

    void* my_memcpy(void* dest, const void*src, size_t num)   //size_t == unsigned int == 无符号
    //设置成 void*的返回值,即 可以快速返回 dest的初始地址
    //更快的访问到 内容改变的空间
    {
     assert(dest && src);
    
     void* ret = dest;
    
     while (num--)
     {
         *(char*)dest = *(char*)src;
    
         dest = (char*)dest + 1;  
         //不难直接 dest++,因为 dest的类型为 void*,加1电脑不知道应该加多少
         src = *(char*)src + 1;
    
         //*(char*)dest++ = *(char*)src++;  
         //不能这样些,因为 (char*)这种 强制类型转换 只是一种临时的状态
         //在 ++的时候就不起作用了,状态效果已经过去了
         //所以对于 void*来说++不知道加多少
         //且 ++ 的优先级 > 强制类型转换,所以 这样写不行
     }
    
     return ret;
    }

🥐Ⅱ.memmove函数

在这里插入图片描述

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

💡memmove函数的作用:与memcpy函数的功能一样,但此函数可以实现拷贝重叠的空间

  • 函数参数类型为:拷贝目标的起始地址、拷贝源的起始地址、拷贝的数据大小【单位:字节】

  • 函数返回类型为:void*

❗有了以上了解,可以从中得出两个点:

  • 1️⃣与memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠

  • 2️⃣如果源空间和目标空间出现重叠,就得使用memmove函数处理

👉函数的模拟实现:

思路:

  • void*强制转换类型成char*可以将函数实现更多操作更多类型、普适性更强

【Eg:只强制类型转换成int*那一次性就要访问4个字节的内容,如果想拷贝1个字符(即1字节),就会把本来不想拷贝的也拷贝了】

这里是引用

void* my_memmove(void*dest, const void*src, size_t num)
{
    assert(dest && src);

    void*ret = dest;

    if (dest < src)
    {
        //src从前向后拷贝[前->后]
        //从前到后的方法 就是 memcpy的方法
        while (num--)
        {
            *(char*)dest = *(char*)src;
            src = (char*)src + 1;
            dest = (char*)dest + 1;
        }
    }
    else
    {
        //后->前
        while (num--)
        {
            *((char*)dest + num) = *((char*)src + num);
        }
    }
    return ret;
}

🥐Ⅲ.memcmp函数

这里是引用

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

💡memcmp函数的作用:与strcmp函数的功能一样,只不过是对所有类型都进行一个字节一个字节的比较

➡️ memcmp函数的工作原理:与strcmp的原理一样~只不过是对内存进行比较

❗了解以上知识后,函数的返回值之所以为整型,是因为:
在这里插入图片描述

  • 1️⃣若 ptr1<ptr2,则返回 <0的数字【在VS编译器下,返回值为-1

  • 2️⃣若 ptr1=ptr2, 则返回0【在VS编译器下,返回值为0

  • 3️⃣若 ptr1>ptr2, 则返回 >0的数字【在VS编译器,返回值为1

🥐Ⅳ.memset函数

这里是引用

void * memset ( void * ptr, int value, size_t num );

💡memset函数的作用:在ptr指向空间的前num个字节内容改 我们指定value的内容

  • 函数参数类型为:一个指向想被修改内存空间的地址、想修改成的指定内容(整型)、修改的内存空间大小

  • 函数的返回类型:void*

➡️ memset函数的工作原理:类似于memcpy的工作原理,将前num个字节的内容修改成value的内容

👉示例:

这里是引用
对想要修改的空间进行初始化
在这里插入图片描述
这样就成功将前20个字节修改为1

int main()
{
    int arr[10] = { 0 }; //40个字节

    memset(arr, 1, 20); //20个字节
    //01 01 01 01

    //一般用于 字符数组里 修改数据
    //而 对于 整型数组 一般不推荐 == 因为 是修改每个字节的,而不是4个字节去访问

    return 0;
}

🥯Ⅴ.总结

✨综上:就是内存操作函数

➡️简单来说:就是内存函数都可以理解为微观视角【对内存空间(字节)进行操作】


🫓总结

综上,我们基本了解了C语言中的 “字符串函数和内存操作函数” :lollipop: 的知识啦~~

恭喜你的内功又双叒叕得到了提高!!!

感谢你们的阅读:satisfied:

后续还会继续更新:heartbeat:,欢迎持续关注:pushpin:哟~

:dizzy:如果有错误❌,欢迎指正呀:dizzy:

:sparkles:如果觉得收获满满,可以点点赞👍支持一下哟~:sparkles:
在这里插入图片描述

全部评论

相关推荐

投递美团等公司6个岗位
点赞 评论 收藏
分享
评论
1
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务