写点什么

【C 语言】字符函数 & 字符串函数 & 内存函数(下)[进阶篇 _ 复习专用]

作者:Dream-Y.ocean
  • 2022 年 9 月 11 日
    广东
  • 本文字数:5099 字

    阅读完需:约 17 分钟

【C语言】字符函数&字符串函数&内存函数(下)[进阶篇_复习专用]

💛 前情提要💛


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


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


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


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


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


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




作者介绍:


:mortar_board: 作者: 热爱编程不起眼的小人物:goat::mag_right:作者的 Gitee:代码仓库:pushpin:系列文章推荐:

  1. 实现Strcpy函数 - 通过函数发现 “程序之美” | 不断优化、优化、再优化~

  2. 【第一章】 C语言之牛客网刷题笔记【点进来保证让知识充实你一整天】

  3. 【第二章】 C语言之牛客网刷题笔记 【点进来保证让知识充实你一整天】

  4. 【第三章】 C语言之牛客网刷题笔记 【点进来保证让知识充实你一整天】

  5. 【C语言】字符函数&字符串函数&内存函数(上)[进阶篇_复习专用]

:ledger:我和大家一样都是初次踏入这个美妙的“元”宇宙:earth_asia: 希望在输出知识的同时,也能与大家共同进步、无限进步:star2:




@TOC



💡本章重点

  • 字符串查找系列函数

  • 错误信息报告函数

  • 字符操作函数

  • 内存操作函数



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

🥐Ⅰ.strstr 函数


char * strstr ( const char *str1, const char * str2);
复制代码


我们需要注意:


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


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

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


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


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

  • 2️⃣函数的返回值:

  • 当在str1所指向的字符串中查找到str2所指向的字符串时,返回一个在 str2 在 str1 中第一次出现的地址

  • 如果在 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函数的作用:简单来说就是通过在字符串中查找分隔符从而达到字符串分割的效果


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

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

  • 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 先将程序员输入的字符串打印出来,然后程序自动加上并打印:,再把错误信息打印出来


👉示例:


🥯Ⅲ.总结

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


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

🍞三.字符操作函数

🥐Ⅰ.字符分类函数

🥐Ⅱ.字符转换函数

🥯Ⅲ.总结

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


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

🍞四.内存操作函数

🥐Ⅰ.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:



发布于: 刚刚阅读数: 4
用户头像

Dream-Y.ocean

关注

还未添加个人签名 2022.06.17 加入

还未添加个人简介

评论

发布
暂无评论
【C语言】字符函数&字符串函数&内存函数(下)[进阶篇_复习专用]_c_Dream-Y.ocean_InfoQ写作社区