string.h 复制函数与剪切函数
一些问题提前汇总
size_t
size_t 的作用,龙崎发现了cstdde.h中定义者 typedef _W64 unsigned int size_t; 也即 size_t 就是无符号的int类型。实际上 size_t 是为了兼容平台而设计的,比如到某些平台会变成普通的 int 之类的。
*s1++ = *s2++;
这样的代码其实是符合C的逻辑的,++运算就是在运算结束后在进行自增运算,也就是说自增在生效之前这个代码的情况其实就是*s1 = *s2; 而到了“;”分号这里运算结束然后s1 与 s2 自增。
const 修饰
int a; int * const p = &a; // 指针常量,*p可以修改 *p = 8;(允许) // p不可以修改 p++(错误) const int *p = &a; // 常量指针 *p不可修改 *p = 8;(错误) // p 可以修改 p = &b (允许) const int * const p = &a; // *p 和 p均不可改变了 // 注 int const与const int的写法是一样的
数组重叠
memcpy
memcpy的函数原型为:
void *memcpy( void *s1, const void *s2, size_t n );
功能:函数memcpy从s2指向的对象中复制n个字符到s1指定的对象中。
如果发生在两个重叠的对象中,则这种行为未定义。
如果确定目标字符串 s1 和 源串 s2 不重叠, memcpy(s1,s2,n)将快速安全地执行复制。如果它们两个可能重叠,可以树勇memmove(s1,s2,n)来代替它。不要认为这两个函数以任何一种特殊的顺序访问存储空间。特别地,如果想在一个字符数组的相邻元素构成的序列中存储相同的值,可以使用memset。
#include <stdio.h> #include <string.h> void *memcpy(void *s1, const void *s2, size_t n); /* 自实现 */ void *_memcpy(void *s1, const void *s2, size_t n) { char *start, *src; start = s1; src = s2; // 警告:赋值丢弃了指针目标类型的限定 ? while(n > 0 && (*start++ = *src++) != '\0') n--; while(n-- > 0) *start++ = '\0'; *start = '\0'; return s1; } /* 不知道是不是编译器的问题 while循环中 等式复杂时自增自减可能出现莫名其妙的段错 如18行: while(n-- > 0 && (*start++ = *src++) != '\0') ; 这样写时,会出现段错 */ /* 标准库 */ void *memcpy(void *s1, const void *s2, size_t n) { char *su1; const char *su2; for(su1 = s1, su2 = s2; 0 < n; ++su1, ++su2, --n) { *su1 = *su2; } return s1; } int main(int argc, char const *argv[]) { char text[20]; printf("%sn", memcpy(text, "hello world", 7)); return 0; }
首先是 void *
string.h 字符串处理,按理说直接处理 char * 就可以了,为什么要设置成 void * ?
其实也很好理解,这是为什么方便处理其他数据的时候也可以用 memcpy 这样的函数来 copy
把 const void * 原本就意味着可以修改指针,倒是不能通过指针修改所指向的内存数据,这也是一种对源数据的保护行为,而在我的实现中将其赋值到 char * 再去处理就没有原来保护原本空间的意思了。弹出“赋值丢弃了指针目标类型的限定”这样的警告也是可以理解的
根据标准库的实现
可以发现memcpy只是单纯的从 s2 出拷贝n个字节到 s1 处
与字符串是否结尾无关
所以在使用这个函数的时候要注意越界的问题
memmove
memmove的函数原型为:
void *memmove( void *s1, const void *s2, size_t n );
功能:函数memmove从s2指向的对象中复制n个字符串到s1指向的对象中,
复制看上去就像先把n个字符从s2指向的对象复制到一个n个字符的临时数组中,
这个临时数组和s1、s2指向的对象都不重叠,然后再把这n个字符从临时数组中复
#include <stdio.h> #include <string.h> void *memmove(void *s1, const void *s2, size_t n); /* 自实现 */ void *_memmove(void *s1, const void *s2, size_t n) { char *su1; const char *su2; int i; if ( s1 < s2 && s2 - s1 < n ) { for ( su1=s1+n, su2=s2+n ; n > 0; --n, --su1, --su2) // 加了 n 忘记减一了 { *su1 = *su2; } }else { for( i=0, su1=s1, su2=s2; i < n; ++i, ++su1, ++su2 ) // i 多余了 { *su1 = *su2; } } return s1; } /* 标准库 */ void *memmove(void *s1, const void *s2, size_t n) { char *sc1; const char *sc2; sc1 = s1; sc2 = s2; // 如果 sc1 的地址比 sc2 要低,并且两者相处不足 n 字节 if (sc2 < sc1 && sc1 < sc2 + n) { for(sc1 += n, sc2 += n; 0 < n; --n) // 逆向复制 { *--sc1 = *--sc2; } }else { for( ; 0 < n; --n) // 正向复制 { *sc1++ = *sr2++; } } return s1; } int main() { char text[20]; printf("%sn", memmove(text, "hello world", 5)); return 0; } /* 1. 没用while用for循环之后,第一问题发现,都要初始化的话, 可以把初始化放在外面 2. 逆向赋值的时候,加了 n 忘记减一了 3. for循环定义的那个 i 多余了 关于 memmove 的使用 这个也只是简单的复制 n 个字节,只不过有区分特殊情况 在相同的情况下使用 memmove 比 memcpy 要安全 */
memmove有判断内存重叠的情况,这确实是以前从未想到过的问题。正如标准库中所提醒的“不要认为这两个函数以任何一种特殊的顺序访问存储空间”,我们可以从标准库的实现看出来这一点。
strcpy
strcpy的函数原型为:
char *strcpy( char *s1, const char *s2 )
功能:把s2指向的串(包括终止的空字符)复制到s1指向的数组中,如果
复制发生在两个重叠的对象中,这行为为定义。
#include <stdio.h> #include <string.h> char *strcpy(char *s1, const char *s2); /* 自实现 */ char *_strcpy(char *s1, const char *s2) { char *start = s1; while(*s1++ = *s2++) ; return start; } /* 标准库 */ char *strcpy(char *s1, const char *s2) { char *s = s1; for(s = s1; (*s++ = *s2++) != '\0'; ) ; return s1; } int main() { char text[20]; printf("%sn", _strcpy(text, "hello world")); return 0; } /* 这个貌似没什么好说的,个人还是偏好while */
如果可以保证目标串 s1 和源串 s2 不重叠,那么 strcpy(s1,s2)将安全并快速地执行复制操作。如果它们可能重叠,使用 memmove(s1, s2, strlen(s2)+1) 来代替它。依旧还是老样子没有判断重叠,貌似如果越界也不会检查,所以还是memmove最好了,考虑以后都用memmove了。
strncpy
strncpy的函数原型为:
char *strcpy( char *s1, const char *s2 )
功能:把s2指向的串(包括终止的空字符)复制到s1指向的数组中,如果
复制发生在两个重叠的对象中,这行为未定义。
#include <stdio.h> #include <string.h> char *_strncpy(char *s1, const char *s2, size_t n); /* 自实现 */ char *_strncpy(char *s1, const char *s2, size_t n) { char *start = s1, count=0; while((count < n) && (*s1 = *s2)) { count++, s1++, s2++; // printf("第%d个 已拷贝n", count); } while (count++ < n) { *s1 ++ = '\0'; // printf("第%d个 已补零n", count); } return start; } /* 标准库 */ char *(strncpy) (char *s1, const char *s2, size_t n) { char *s; // 当n不为0 且s2为拷贝完时,复制字符过去 for(s = s1; 0 < n && *s2 != '\0'; ++s) *s++ = *s2++; // 若n有多出,则补零 for( ; 0 < n; --n) *s++ = '\0'; return s1; } int main() { char text[20]=""; printf("%sn", _strncpy(text, "hello world", 20)); return 0; } /* size_t 是一种兼容平台的大小类型 比较标准库发现,在计数上可以让计数的n反过来从n开始递减直到0为止 这样可以少定义一个变量,节省了1字节的内存,看起来也干净些 */
如果可以保证目标串 s1 和源串 s2 不重叠,strncpy(s1,s2,n2)将安全的执行复制操作。然而,函数在s1处精确地存储n2个字符,它可能丢弃尾部的字符,包括终止的空字符。函数会更具需要存储一些额外的空字符来弥补一个短的技术。如果两个区域可能重叠,使用 memmove(s1,s2,n2) 来代替它。(必须在结尾存储适当数量的空字符,如果这很重要的话。)不要认为这两个函数以任意特定的顺序访问存储空间。
strcat
strcat的函数原型为:
char *strcat( char *s1, const char *s2 );
功能:函数strcat把s2指向的串(包括终止的空字符)的副本添加到s1指向
的串的末尾,s2得第一个字符覆盖s1末尾的空支付。如果复制发生在两个重
叠的对象中,则行为未定义。
#include <stdio.h> #include <string.h> char *_strcat(char *s1, const char *s2); /* 自实现 */ char *_strcat(char *s1, const char *s2) { char *start = s1; while( *s1 ) s1++; while(*s1++ = *s2++) ; return start; } /* 标准库 */ char *strcat(char *s1, const char *s2) { char *s; // 指针移动到s1的结尾 for(s = s1; *s != '\0'; ++s) ; // 如果s2未结尾则拷贝 for( ; (*s = *s2) != '\0'; ++s, ++s2) ; return (s1); } int main() { char text[20]="hello"; printf("%sn", strcat(text, " world")); return 0; } /* 这个还比较简单,但是感觉for循环确实挺万能的 */
strncat
strncat的函数原型为:
char *strcat( char *s1, const char *s2, size_t n );
功能:函数strncat匆匆s2指向的数组中将最多n个字符(空字符及其后面的
字符不添加)添加到s1指向的串的末尾,s2的第一个字符覆盖s1末尾的空字
符。通常在最后的结果后面加上一个空字符。如果复制发生在两个重叠的对
象中,则行为未定义。
#include <stdio.h> #include <string.h> char *_strncat(char *s1, const char *s2, size_t n); /* 自实现 */ char *_strncat(char *s1, const char *s2, size_t n) { char *start = s1; while( *s1 ) s1++; while((0 < n--) && (*s1++ = *s2++)) { ; // printf("第%d个 已赋值n", n); } while (0 < n--) { *s1 ++ = '\0'; // printf("第%d个 已补零n", n); } return start; } /* 标准库 */ char *strncat(char *s1, const char *s2, size_t n) { char *s; // 指针移动到s1的结尾 for(s = s1; *s != '\0'; ++s) ; // 如果s2未结尾则拷贝 for( ; 0 < n && *s2 != '\0'; --n) *s++ = *s2++; // 字符串s1结尾补'\0' *s = '\0'; return (s1); } int main() { char text[20]="hello"; printf("%sn", _strncat(text, " world", 10)); return 0; } /* 这次换n自减,中间好像很奇怪的溢出了,没发现什么情况,或者是我单词写错了? 不过确实还挺神奇的发现 这个strncat 并没有像 strncpy 那样不足的补零 */
感慨:
我们以后还是多用 memmove 来编写程序把,反正这些库函数都没有检查数组越界,大家自己定好范围然后传个n进去让他move吧。感觉平常很有爱的strcpy和strncpy都其实不太完善。回头工作室还是自己重写一个string库吧。