【C语言】《C 语言库函数源码级别的理解:模拟实现字符串与内存操作全系列(含最常用12个模拟实现)》
字符函数和字符串函数引言C语言开发过程中最频繁的操作之一就是处理字符串。不管是用户输入、文件读取、还是网络数据解析都绕不开各种字符串操作。C语言中对字符和字符串的处理非常频繁但C语言本身没有字符串类型字符串通常放在字符数组中或者放在常量字符串中。常量字符串是只读的适用于那些不需要修改字符串内容的函数。使用字符串函数时要注意几点目标空间必须足够大、还得确保字符串有\0结尾等。一.字符操作1.字符分类函数C语⾔中有⼀系列的函数是专⻔做字符分类的也就是⼀个字符是属于什么类型的字符的。这些函数的使⽤都需要包含⼀个头⽂件是 ctype.h这些函数的使⽤⽅法⾮常类似我们就讲解⼀个函数的事情其他的⾮常类似int islower ( int c );islower 是能够判断参数部分的 c 是否是⼩写字⺟的。通过返回值来说明是否是⼩写字⺟如果是⼩写字⺟就返回⾮0的整数如果不是⼩写字⺟则返回0。练习写⼀个代码将字符串中的⼩写字⺟转⼤写其他字符不变。#include stdio.h #include ctype.h int main() { int i 0; char str[] Test String.\n; char c; while (str[i]) { c str[i]; if (islower(c)) c - 32; putchar(c); // 输出当前字符 i; // 移到下一个字符 } return 0; }运行如下isupper是 C 语言标准库ctype.h里的函数用来判断一个字符是否是大写字母A到Z。如果是大写字母返回非零值真如果不是返回 0假。2. 字符转换函数C语⾔提供了2个字符转换函数int tolower ( int c ); //将参数传进去的⼤写字⺟转⼩写 int toupper ( int c ); //将参数传进去的⼩写字⺟转⼤写上⾯的代码我们将⼩写转⼤写是-32完成的效果有了转换函数就可以直接使⽤ tolower 函数。#include stdio.h #include ctype.h int main () { int i 0; char str[] Test String.\n; char c; while (str[i]) { c str[i]; if (islower(c)) c toupper(c); putchar(c); i; } return 0; }运行结果3. strlen 的使⽤和模拟实现strlen - C Reference字符串以 \0 作为结束标志strlen函数返回的是在字符串中 \0 前⾯出现的字符个数不包含 \0 )。参数指向的字符串必须要以 \0 结束。注意函数的返回值为 size_t是⽆符号的 易错 strlen的使⽤需要包含头⽂件代码如下#include stdio.h #include string.h int main() { const char* str1 abcdef; const char* str2 bbb; if (strlen(str2) - strlen(str1) 0) { printf(str2str1\n); } else { printf(srt1str2\n); } return 0; }这段代码想比较两个字符串的长度但写的时候有个问题。strlen返回的是size_t也就是无符号整数两个无符号数相减的结果还是无符号的永远不可能是负数。所以strlen(str2) - strlen(str1) 0这个条件实际上一直为真因为 3 减 6 会变成一个大正数不是 -3。比较长度直接用比较就行了。三种模拟实现方法//方式1计数器方式 int my_strlen(const char* str) { int count 0; assert(str); while (*str) { count; str; } return count; } //方式2递归方式不创建临时变量 int my_strlen(const char* str) { assert(str); if (*str \0) return 0; else return 1 my_strlen(str 1); } //方式3指针-指针方式 int my_strlen(const char* str) { assert(str); const char* p str; while (*p ! \0) p; return (int)(p - str); } int main() { char arr[] hello zifuhanshu; size_t count my_strlen(arr); printf(%zu\n, count); return 0; }方式1计数器就是从头到尾数一遍遇到\0停下数了几个就是几个。方式2递归判断当前字符是不是\0是就返回0不是就返回1 自己调用自己往后移一位。代码看起来挺简洁的但实际用的话字符串太长容易把栈搞爆平时写代码不推荐。方式3指针-指针把头指针记下来再弄一个指针p从头走到尾走到\0停下然后p - 头指针就是中间的字符个数。二.长度不受限制的字符串函数1.strcpy 的使⽤和模拟实现strcpy - C Referencechar* strcpy(char * destination, const char * source );把源字符串包含结尾的\0整个复制到目标空间里。源字符串必须以 \0 结束。会将源字符串中的 \0 拷⻉到⽬标空间。⽬标空间必须⾜够⼤以确保能存放源字符串。⽬标空间必须可修改。即目标空间 dest 不可以被 const 声明。strcpy的模拟实现:#include stdio.h #include assert.h // 提供 assert 断言 // 自己实现字符串拷贝函数 char* my_strcpy(char* dest, const char* src) { assert(dest src); // 检查指针是否为空防止传入空指针导致崩溃 char* ret dest; // 保存目标地址后面 dest 会移动用于最后返回 while (*dest *src) { // 先执行 *dest *src赋值 ; // 然后 dest、src 同时往后移一位 } // 判断赋值后的字符是不是 \0 // 是 \0 就停止不是就继续 return ret; // 返回目标字符串的起始地址 } int main() { char arr1[] hello world; // 源字符串 char arr2[20] xxxxxxxxxxxxxx; // 目标空间初始全为 x my_strcpy(arr2, arr1); // 调用拷贝函数 printf(%s\n, arr2); // 打印拷贝后的结果 return 0; }运行结果解析while (*dest *src) { ; }先赋值*dest *src然后dest、src同时后移判断赋值后的字符是不是\0是就停。\0也会被拷贝过去。等价while (*src ! \0) { *dest *src; dest; src; } *dest \0;2.strcat 的使⽤和模拟实现strcat - C Referencechar* strcat(char* destination, const char* source);strcat是字符串拼接函数把源字符串追加到目标字符串的末尾返回目标空间的起始地址。源字符串必须以 \0 结束。⽬标字符串中也得有 \0 否则没办法知道追加从哪⾥开始。⽬标空间必须有⾜够的⼤能容纳下源字符串的内容。⽬标空间必须可修改。即目标空间 dest 不可以被 const 声明。模拟实现strcat函数代码如下#include stdio.h #include assert.h #inlcudestdio.h char* my_strcat(char* dest, const char* src) { char* ret dest; assert(dest ! NULL); assert(src ! NULL); while (*dest) { dest; } while ((*dest *src)) { ; } return ret; } int main() { char arr1[20] hello ; char arr2[] world; my_strcat(arr1, arr2); printf(%s\n, arr1); return 0; }运行结果过程如下先把dest的起始地址保存到ret因为后面dest会移动最后要返回起始地址assert断言检查dest和src是不是空指针防止程序崩溃我用第一个while循环用来找目标字符串的\0找到之后dest就指向\0的位置我用第二个while循环从\0位置开始把源字符串拷过去\0也一起拷遇到\0就停最后返回ret就是目标字符串的起始地址方便链式调用。自己给自己追加会发生什么结果不要这样干结果是未定义的。strcat的工作流程是strcat的工作流程是先找到目标字符串的\0然后从那个位置开始把源字符串拷贝过去。如果dest和src指向同一个字符串找\0的时候dest会一直走到末尾然后开始拷贝。问题是拷贝过程中源数据被自己覆盖掉了原来的\0没了循环停不下来一直越界访问直到程序崩溃。不同的编译器表现可能不一样但不管怎样都不建议这么写。运行结果3. strcmp 的使⽤和模拟实现strcmp - C Referencestrcmp是 C 语言里用来比较两个字符串的函数strcmp(const char* str1, const char* str2)它的作用就是逐个字符比较两个字符串从第一个字符开始比相等就往后继续直到比出大小或者遇到\0。标准规定第⼀个字符串⼤于第⼆个字符串则返回⼤于0的数字第⼀个字符串等于第⼆个字符串则返回0第⼀个字符串⼩于第⼆个字符串则返回⼩于0的数字strcmp函数的模拟实现#include stdio.h #include assert.h int my_strcmp(const char* str1, const char* str2) { assert(str1 str2); // 检查空指针 while (*str1 *str2) // 字符相等就继续往后比 { if (*str1 \0) // 两个都走到末尾了说明完全相等 return 0; str1; str2; } return *str1 - *str2; // 不相等就返回差值0说明str1大0说明str2大 } int main() { char arr1[] abq; char arr2[] abcdef; int ret my_strcmp(arr1, arr2); printf(%d\n, ret); return 0; }运行结果那么如何判断两个字符串答案是:⽐较两个字符串中对应位置上字符ASCII码值的⼤⼩。char* p1 abcdef; char* p2 aqwer; int ret strcmp(p1, p2); printf(%d\n, ret);strcmp比较两个字符串的时候从第一个字符开始逐个往后比。第一对a和a相等继续比第二对第二对b和q不相等b的 ASCII 是98q是113b - q是 -15所以返回负数表示p1 p2。返回值不一定是 -1 或 1只要正负号对就行具体数值不同编译器可能不一样。另外strcmp是区分大小写的A和a不一样补充不同编译器的返回值差异strcmp的返回值在不同编译器下表现不一样VS2019大于返回 1等于返回 0小于返回 -1Linux-gcc大于返回正数比如 14等于返回 0小于返回负数比如 -14C 标准只规定了正数、0、负数三种情况具体数值不固定所以不要依赖具体的返回值只判断正负和是否为零就行。三.长度受限制的字符串函数1.strncpy 函数的使⽤char * strncpy ( char * destination, const char * source, size_t num );strncpy - C Referencestrncpy的使用和注意事项strncpy和strcpy的区别就是多了一个 num数可以控制拷贝多少个字符。拷贝 num 个字符从源字符串里拷 num 个到目标空间。源字符串长度小于 num拷完源字符串后目标后面会补\0一直补到count个。源字符串长度大于等于 num只拷前 num个不会自动补\0用完后记得自己补。目标空间必须足够大且可修改,源和目标不能重叠strncyp函数的模拟实现#include stdio.h #include assert.h char* my_strncpy(char* dest, const char* src, size_t num) { assert(dest src); char* ret dest; size_t i 0; // 1. 拷贝 count 个字符遇到 src 的 \0 就提前停 while (i num src[i] ! \0) { dest[i] src[i]; i; } // 2. 如果 src 长度小于 count后面补 \0 while (i num) { dest[i] \0; i; } return ret; } int main() { char arr1[] abcdef; char arr2[20] xxxxxxxxxx; my_strncpy(arr2, arr1, 3); printf(%s\n, arr2); return 0; }运行结果过程如下my_strncpy先拷数据再补\0。先用while (i num src[i] ! \0)把源字符串的前 num 个字符拷过去但如果中途遇到\0就直接停不继续拷了第二步如果拷完还没到 num个说明源字符串比 num 短剩下的位置全部补\0直到补满num 个。2.strncat 函数的使⽤char * strncat ( char * destination, const char * source, size_t num );strncat是长度受限的字符串拼接函数把源字符串的前num个字符追加到目标字符串的末尾然后自动补一个\0返回目标空间的起始地址。strncat - C Reference如果源字符串的长度小于 count则只复制 \0 之前的内容。strncat的模拟实现:#include stdio.h #include assert.h char* my_strncat(char* dest, const char* src, size_t num) { assert(dest src); char* ret dest; // 1. 找到目标字符串的末尾\0的位置 while (*dest ! \0) { dest; } // 2. 拷贝 num 个字符遇到 src 的 \0 就提前停 while (num-- (*dest *src)) { ; } // 3. 手动补上 \0 *dest \0; return ret; } int main() { char arr1[] abcdef; char arr2[20] xx\0xxxxxxxxxx; int len strlen(arr1); my_strncat(arr2, arr1, len); printf(%s\n, arr2); return 0; }运行结果过程如下arr2初始是xx\0xxxxxxxxxxstrlen碰到第一个\0就停了所以len 6但arr2的有效长度只到第2个字符。my_strncat先找到arr2的\0位置也就是第2个字符后面然后从那里开始把arr1的6个字符abcdef拷贝过去最后补上\0。所以结果是xxabcdef后面的xxxxxxxxxx就被覆盖掉了。3. strncmp函数的使⽤int strncmp ( const char * str1, const char * str2, size_t num );是 C 语言里用来比较两个字符串前num个字符的函数strncmp - C Reference⽐较str1和str2的前num个字符如果相等就继续往后⽐较最多⽐较num个字⺟如果提前发现不⼀样就提前结束⼤的字符所在的字符串⼤于另外⼀个。如果num个字符都相等就是相等返回0.strncmp的模拟实现:#include stdio.h #include assert.h int my_strncmp(const char* str1, const char* str2, size_t num) { assert(str1 str2); // 如果 num 为 0直接返回 0 if (num 0) return 0; // 逐个比较前 num 个字符 while (num-- *str1 *str2 *str1 *str2) { str1; str2; } // 如果 num 减到了 0说明前 num 个字符都相等返回 0 if (num 0) return 0; // 否则返回 ASCII 差值 return *str1 - *str2; } int main() { char arr1[] abcdef; char arr2[] abqdefghi; int ret my_strncmp(arr1, arr2, 2); printf(%d\n, ret); return 0; }运行结果过程如下strncmp比较的是字符的 ASCII 码值。当比较到第三对字符时arr1是carr2是q两个字符不相等函数返回*str1 - *str2也就是c - q。查一下 ASCII 表c是 99q是 11399 减 113 等于-14。负数说明str1小于str2。四.字符串查找1.strstr 的使⽤和模拟实现char * strstr ( const char * str1, const char * str2);在长字符串str1里找子字符串str2返回第一次出现的位置指针strstr - C ReferenceReturns a pointer to the first occurrence of str2 in str1, or a null pointer if str2 is not part of str1.函数返回字符串str2在字符串str1中第⼀次出现的位置。The matching process does not include the terminating null-characters, but it stops there.字符串的⽐较匹配不包含 \0 字符以 \0 作为结束标志。strstr的模拟实现#include stdio.h #include assert.h char* my_strstr(const char* str1, const char* str2) { assert(str1 str2); // 防止传入空指针 // 如果 str2 是空字符串直接返回 str1 if (*str2 \0) return (char*)str1; const char* cur str1; // cur 记录当前从 str1 的哪个位置开始尝试匹配 while (*cur ! \0) { const char* s1 cur; // 每次都从当前位置开始 const char* s2 str2; // 每次重新从头匹配子串 while (*s1 ! \0 *s2 ! \0 *s1 *s2) { s1; s2; } // 如果 s2 走到了末尾说明完全匹配 if (*s2 \0) return (char*)cur; // 否则从 str1 的下一个位置重新尝试 cur; } return NULL; } int main() { char arr1[] abbbcdef; char arr2[] bbc; char* ret my_strstr(arr1, arr2); if (ret NULL) printf(找不到\n); else printf(找到%s\n, ret); return 0; }运行结果:过程如下用cur从str1的每个字符位置开始每次让s1cur、s2str2然后逐字符比较如果str2全比完了都相等就返回cur否则cur换下一个位置继续试直到str1走完还没找到就返回NULL。2. strtok 函数的使⽤char * strtok ( char * str, const char * sep);是按你给的符号比如或,把字符串“切开”一刀一刀地切出每一块。strtok - C Referencesep参数指向⼀个字符串定义了⽤作分隔符的字符集合第⼀个参数指定⼀个字符串它包含了0个或者多个由sep字符串中⼀个或者多个分隔符分割的标记。strtok函数找到str中的下⼀个标记并将其⽤ \0 结尾返回⼀个指向这个标记的指针。注strtok函数会改变被操作的字符串所以被strtok函数切分的字符串⼀般都是临时拷⻉的内容并且可修改。strtok函数的第⼀个参数不为 NULL 函数将找到str中第⼀个标记strtok函数将保存它在字符串中的位置。strtok函数的第⼀个参数为 NULL 函数将在同⼀个字符串中被保存的位置开始查找下⼀个标记。如果字符串中不存在更多的标记则返回 NULL 指针。strtok的模拟实现#define _CRT_SECURE_NO_WARNINGS #include stdio.h #include string.h int main() { char arr[] hejiaqiyeah.nethehe; char arr2[30] { 0 }; strcpy(arr2, arr); const char* sep .; char* ret NULL; for (ret my_strtok(arr2, sep); ret ! NULL; ret my_strtok(NULL, sep)) { printf(%s\n, ret); } // ret my_strtok(arr2, sep); // printf(%s\n, ret); // ret my_strtok(NULL, sep); // printf(%s\n, ret); // ret my_strtok(NULL, sep); // printf(%s\n, ret); return 0; }运行结果ret strtok(arr2, sep); // 第一次切出 hejiaqi printf(%s\n, ret); // 打印 hejiaqi ret strtok(NULL, sep); // 第二次切出 yeah printf(%s\n, ret); // 打印 yeah ret strtok(NULL, sep); // 第三次切出 net printf(%s\n, ret); // 打印 net五.错误信息报告strerror 函数的使⽤char* strerror ( int errnum );strerror - C Referencestrerror 函数可以把参数部分错误码对应的错误信息的字符串地址返回来。在不同的系统和C语⾔标准库的实现中都规定了⼀些错误码⼀般是放在 errno.h 这个头⽂件中说明的C语⾔程序启动的时候就会使⽤⼀个全局的变量errno来记录程序的当前错误码只不过程序启动的时候errno是0表⽰没有错误当我们在使⽤标准库中的函数的时候发⽣了某种错误就会将对应的错误码存放在errno中⽽⼀个错误码的数字是整数很难理解是什么意思所以每⼀个错误码都是有对应的错误信息的。strerror函数就可以将错误对应的错误信息字符串的地址返回strerror的模拟实现#define _CRT_SECURE_NO_WARNINGS #include stdio.h #include string.h #include errno.h // 自己写的strerror char* my_strerror(int errnum) { // 把常见的错误码和错误信息存起来 static char* err[] { No error, // 0 Operation not permitted, // 1 No such file or directory, // 2 No such process, // 3 Interrupted system call, // 4 Input/output error, // 5 No such device or address, // 6 Argument list too long, // 7 Exec format error, // 8 Bad file descriptor, // 9 No child processes // 10 }; if (errnum 0 || errnum 10) return 未知错误; return err[errnum]; } // 自己写的perror void my_perror(const char* str) { if (str ! NULL) { printf(%s: %s\n, str, my_strerror(errno)); } else { printf(%s\n, my_strerror(errno)); } } int main() { // 先打印一下错误码对应的信息 int i 0; printf(错误码对照表\n); for (i 0; i 10; i) { printf(%d: %s\n, i, my_strerror(i)); } printf(\n); // 测试一下打开不存在的文件 FILE* pf fopen(test.txt, r); if (pf NULL) { printf(用strerror打印: %s\n, my_strerror(errno)); } printf(\n); // 再用perror打印一次 pf fopen(test.txt, r); if (pf NULL) { my_perror(打开失败); } return 0; }过程如下my_strerror整了个数组把错误码和错误信息对应存起来传数字进去就返回对应的字符串。my_perror就是包了一层传个前缀进去它自己拿errno去查错误信息帮你打印成 前缀: 错误信息 的格式省得每次写printf。main里先打一遍 0 到 10 的错误码看看都有啥然后打开一个不存在的文件errno自动变成 2分别用俩函数打印一下 No such file or directory看看自己写的好不好使。内存函数1.memcpy 使⽤和模拟实现void * memcpy ( void * destination, const void * source, size_t num );memcpy就是按字节把一块内存的数据原封不动拷到另一块内存去。函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置。这个函数在遇到 \0 的时候并不会停下来。如果source和destination有任何的重叠复制的结果都是未定义的。memcpy代码模拟实现#define _CRT_SECURE_NO_WARNINGS #include stdio.h #include string.h #include assert.h void* my_memcpy(void* dest, const void* src, size_t num) { assert(dest src); void* ret dest; char* d (char*)dest; const char* s (const char*)src; while (num--) { *d *s; } return ret; } int main() { int arr1[] { 1,2,3,4,5,6,7,8,9,10 }; int arr2[20] { 0 }; my_memcpy(arr2, arr1, 5 * sizeof(int)); for (int i 0; i 5; i) { printf(%d , arr2[i]); } printf(\n); return 0; }运行结果过程如下main里定义俩数组arr1有1~10arr2开20个空间全0。调my_memcpy(arr2, arr1, 5*sizeof(int))拷20个字节过去。进函数先assert检查指针不是空把arr2起始地址存ret里留着返回。把dest和src强转成char*方便一个字节一个字节搬。while循环20次每次把s指向的字节赋给d指向的位置然后俩指针都往后挪。20个字节拷完刚好5个int。返回ret回到main打印arr2前5个数出来就是1 2 3 4 5代码演示过程#define _CRT_SECURE_NO_WARNINGS #include stdio.h #include string.h struct S { char name[20]; int age; }; int main() { struct S arr3[] { {张三, 20}, {李四, 30} }; struct S arr4[3] { 0 }; // 把arr3的内容拷到arr4里去 memcpy(arr4, arr3, sizeof(arr3)); // 打印看看拷过去没有 for (int i 0; i 2; i) { printf(名字%s年龄%d\n, arr4[i].name, arr4[i].age); } return 0; }2. memmove 使⽤和模拟实现void * memmove ( void * destination, const void * source, size_t num );memmove - C Reference和memcpy的差别就是memmove函数处理的源内存块和⽬标内存块是可以重叠的。如果源空间和⽬标空间出现重叠就得使⽤memmove函数处理。memmove和memcpy都是拷贝内存的区别就一个拷贝的两块内存如果有重叠用memmove才安全用memcpy会出错。比如数组[1,2,3,4,5,6,7,8,9,10]想把前5个数[1,2,3,4,5]挪到从第3个位置开始放就成了[1,2,1,2,3,4,5,8,9,10]这时候源头1到5和目标3到7有交叉用memcpy拷着拷着就把后面还没拷的数给覆盖了结果就不对。memmove能处理这种情况它会判断一下是从前往后拷还是从后往前拷保证数据不错。memmove模拟实现#define _CRT_SECURE_NO_WARNINGS #include stdio.h #include assert.h void* my_memmove(void* dest, const void* src, size_t num) { assert(dest src); void* ret dest; char* d (char*)dest; const char* s (const char*)src; // 判断有没有重叠 // 如果 dest 在 src 后面并且 dest 和 src 有重叠就从后往前拷 if (d s d s num) { // 从后往前拷 d (char*)dest num - 1; s (const char*)src num - 1; while (num--) { *d-- *s--; } } else { // 没有重叠或者 dest 在 src 前面从前往后拷 while (num--) { *d *s; } } return ret; } int main() { int arr[] { 1,2,3,4,5,6,7,8,9,10 }; // 把前5个数拷到从第3个位置开始下标2 my_memmove(arr 2, arr, 5 * sizeof(int)); for (int i 0; i 10; i) { printf(%d , arr[i]); } printf(\n); return 0; }运行结果过程如下进函数先检查两个指针不是空然后把目标起始地址存到ret里最后要返回它判断从前往后拷还是从后往前拷。判断条件是看目标地址dest是不是在源头src的后面并且两块内存有重叠。如果是就走从后往前拷如果不是就走从前往后拷从后往前拷的时候把dest和src都移到这一块内存的最后一位然后while循环从后往前一个一个字节搬每搬完一次两个指针都往前挪一位从前往后拷的时候直接从起始位置一个一个字节往后搬就行了循环结束返回存好的目标起始地址ret回到main打印数组数组变成1 2 1 2 3 4 5 8 9 10。3.memset 函数的使⽤void * memset ( void * ptr, int value, size_t num );memset - C Referencememset是⽤来设置内存的将内存中的值以字节为单位设置成想要的内容。memset 是以字节为单位设置内存的。memset模拟实现#define _CRT_SECURE_NO_WARNINGS #include stdio.h #include string.h int main() { int arr[10] { 0 }; // 整一个数组10个位置都初始成0 // 把前20个字节全部改成1 memset(arr, 1, 20); // 打印看看变成啥样了 for (int i 0; i 10; i) { printf(%d , arr[i]); } printf(\n); return 0; }运行结果4. memcmp 函数的使⽤int memcmp ( const void * ptr1, const void * ptr2, size_t num );memcmp - C Referencememcmp就是按字节比较两块内存的前num个字节和strcmp的区别是它遇到\0不会停一定要把num个字节全比完。memcmp 不同于 strcmpmemcmp 遇到 \0 不会停止比较。⽐较从ptr1和ptr2指针指向的位置开始向后的num个字节返回值如下memcmp模拟实现#define _CRT_SECURE_NO_WARNINGS #include stdio.h #include string.h int main() { float a[] { 1.0, 2.0, 3.0, 4.0 }; float b[] { 1.0, 3.0 }; // 比8个字节看a和b谁大 int ret memcmp(a, b, 8); printf(%d\n, ret); return 0; }运行结果这个代码就是拿两个数组比一下前8个字节看谁大谁小。a里面放了4个float1.0, 2.0, 3.0, 4.0b里面放了2个float1.0, 3.0。memcmp(a, b, 8)就是从头开始比8个字节一个float占4个字节8个字节刚好比两个float。先比第一个1.0和1.0一样再比第二个2.0和3.02.0比3.0小所以memcmp返回一个负数ret就是负数打印出来也肯定是负数。函数对比函数功能比较/拷贝单位遇到\0停止处理内存重叠主要特点strstr找子串第一次出现的位置字符停止不涉及专门处理字符串strtok按分隔符切字符串字符停止不涉及会修改原字符串用静态变量记住位置strerror错误码转错误信息-不涉及不涉及传入数字返回对应的字符串perror打印错误信息-不涉及不涉及自动拿 errno省得自己写 printfmemcpy拷贝内存字节不停止不处理不管类型按字节拷不重叠时用memmove拷贝内存字节不停止能处理和 memcpy 一样但能处理重叠memset设置内存字节不停止不涉及把每个字节设成同一个值memcmp比较内存字节不停止不涉及按字节比\0也照比不误字符串专用遇到\0就停strstr、strtok、strerror、perror都是处理字符串的遇到\0就认为结束了。内存操作不管\0memcpy、memmove、memset、memcmp都是按字节操作内存的不关心\0说拷多少字节就拷多少字节说比多少字节就比多少字节。易混容易混的区别strstrvsstrtokstrstr找子串位置strtok切分字符串strerrorvsperrorstrerror返回字符串perror直接打印memcpyvsmemmovememmove能处理重叠memcpy不行

相关新闻

Crow框架终极指南:构建高性能C++微服务架构的完整解决方案

Crow框架终极指南:构建高性能C++微服务架构的完整解决方案

Crow框架终极指南:构建高性能C微服务架构的完整解决方案 【免费下载链接】crow Crow is very fast and easy to use C micro web framework (inspired by Python Flask) 项目地址: https://gitcode.com/gh_mirrors/cr/crow 在当今高并发、低延迟的应用场景中…

2026/7/29 22:38:36阅读更多 →
问题是结果的“价值坐标系”

问题是结果的“价值坐标系”

一个结果有没有价值,不取决于它本身多复杂、多漂亮,而取决于它解决了什么问题、满足了什么需求、改变了什么现实。问题,决定了结果应该被如何衡量。第一层:什么叫“价值坐标系”? 坐标系的作用: 是确定一个…

2026/7/29 22:38:36阅读更多 →
没有问题,结果就失去意义

没有问题,结果就失去意义

结果本身不是价值,结果只是解决某个问题之后留下的证明。脱离问题的结果,往往只是形式,而不是价值。这句话揭示了一个现实规律:价值不是来自“做出了什么”,而是来自“为什么做、解决了什么、改变了什么”。第一层&…

2026/7/29 22:38:36阅读更多 →
终极无线电渗透测试工具:EvilCrowRF-V2全面解析与实战指南

终极无线电渗透测试工具:EvilCrowRF-V2全面解析与实战指南

终极无线电渗透测试工具:EvilCrowRF-V2全面解析与实战指南 【免费下载链接】EvilCrowRF-V2 项目地址: https://gitcode.com/gh_mirrors/ev/EvilCrowRF-V2 EvilCrowRF-V2是一款功能强大的开源无线电渗透测试工具,专为无线电安全评估和研究设计。它…

2026/7/29 23:54:58阅读更多 →
中山市工程技术研究中心全科普:认定标准、政策价值与合规规划

中山市工程技术研究中心全科普:认定标准、政策价值与合规规划

依据《中山市工程技术研究中心认定管理办法》及市科技局最新通知,市级工程中心作为中山市核心企业研发平台资质,是企业创新实力官方背书,可叠加各类产业、科创项目加分。当前评审同步强化研发真实性、知识产权质量、成果转化落地核查&#xf…

2026/7/29 23:54:58阅读更多 →
从Modbus到MQTT,飞凌嵌入式FCU1501只用了一个“拖拽“的距离

从Modbus到MQTT,飞凌嵌入式FCU1501只用了一个“拖拽“的距离

工厂里那些"沉默"的老设备 在工业数字化落地的现场,大量PLC、仪表、传感器等老牌工业设备仍在以Modbus协议运行——设备端数据真实存在,却与云平台、MQTT总线、上位机系统之间隔着一道「协议壁垒」,数据难以顺畅流转。 传统的协议…

2026/7/29 23:54:58阅读更多 →
从立案到宣判仅需11.3秒:某省高院AI判决辅助系统上线72小时后暴露出的6个隐性伦理风险

从立案到宣判仅需11.3秒:某省高院AI判决辅助系统上线72小时后暴露出的6个隐性伦理风险

更多请点击: https://intelliparadigm.com 第一章:AI 判决预测分析 AI 判决预测分析是指利用机器学习与自然语言处理技术,对历史司法裁判文书进行建模,以辅助评估案件可能的判决结果、量刑区间或法律适用倾向。该技术并非替代法官…

2026/7/29 23:54:58阅读更多 →
AI康复训练指导≠智能操课!神经康复专家拆解:脑卒中患者前6周黄金干预的8个数据阈值

AI康复训练指导≠智能操课!神经康复专家拆解:脑卒中患者前6周黄金干预的8个数据阈值

更多请点击: https://kaifayun.com 第一章:AI康复训练指导≠智能操课!神经康复专家拆解:脑卒中患者前6周黄金干预的8个数据阈值 AI康复训练指导绝非将传统操课简单数字化——它是一套基于神经可塑性机制、实时生理反馈与个体化运…

2026/7/29 23:54:58阅读更多 →
终极文件清理神器:dupeGuru如何快速帮你找回宝贵磁盘空间

终极文件清理神器:dupeGuru如何快速帮你找回宝贵磁盘空间

终极文件清理神器:dupeGuru如何快速帮你找回宝贵磁盘空间 【免费下载链接】dupeguru Find duplicate files 项目地址: https://gitcode.com/gh_mirrors/du/dupeguru dupeGuru是一款功能强大的跨平台重复文件查找工具,采用Python 3开发&#xff0c…

2026/7/29 23:52:58阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/29 9:47:45阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/29 7:00:19阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/29 7:58:51阅读更多 →
28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“! 在构建复杂的 Agent 系统时,我们经常会遇到这样的场景:Agent 正在执行一个多步骤的任务,比如“下单购买商品”,但执行到一半时,我们…

2026/7/29 0:01:46阅读更多 →
自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

近日,国际专注开放式技术研发的声学品牌Nank南卡,正式官宣实力艺人曾舜晞担任品牌代言人。消息一经发出便轰动全网。为什么耳机品牌不选择流量明星、老牌歌手?而且是选择曾舜晞?让我们一起来探索一下!比起短期的流量&a…

2026/7/29 0:01:46阅读更多 →
【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:01:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/28 20:22:24阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/29 4:31:51阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/29 14:26:42阅读更多 →