C标准库 string.h (一)复制函数与剪切函数

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库吧。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s