strcpy(),memcpy(),memmove(),memset(),strcmp(),strstr()的实现

在C语言中strcpy的原型是char *strcpy(char *dest,const char *src),它的的功能是把src所指由'\0'结束的字符串复制到dest所指的数组中,下面是实现

char *strcpy(char *dest,const char *src)
{
	assert((NULL != dest) && (NULL != src));
	char* pDest = dest;
	while(*src != '\0')
	{
		*pDest++ = *src++;
	}
	return dest;
}

内存拷贝:memcpy(),函数memcpy(),与strcpy()不同,需要考虑内存重叠的可能。当(pSrc<pDst) && ((char*)pSrc+size > pDst)时,如果拷贝是从头开始正向拷贝,就会在拷贝过程中污染pSrc中还未拷贝的数据。因此在这种情况下必须反向拷贝。实际上在C语言库中,与内存复制有关的函数有两个:memcpy()和memmove()。前者没有考虑内存重叠,而后者考虑了内存重叠。所以使用时需要注意。下面是实现代码(我的实现代码加入了处理内存重叠的情况):

void memcpy(void *pDest,const void *pSrc, int size)
{
	assert((NULL != pDest) && (NULL != pSrc) && (size>=0));
	//内存拷贝,参数都是void类型的指针,所以要将其转换成char*类型的指针以便按字节进行拷贝
	char* dest = static_cast<char*> (pDest);
	const char* src = static_cast<const char*> (pSrc);
	//拷贝的目标地址段与原字符串的地址段有重复,则进行反向拷贝
	if(src<dest && (src+size)>dest)
	{
		dest = dest+size-1;
		src = src+size-1;
		while(size>0)
		{
			*dest-- = *src--;
			size--;
		}
	}
	else//拷贝的目标地址段与原字符串的地址段无重复,则正向拷贝
	{
		while(size>0)
		{
			*dest++ = *src++;
			size--;
		}
	}
}

既然要考虑源地址段与拷贝的目标地址段重复的问题,那么为了便于记忆,我们可以不管它是否源地址段与目标地址段重复,我们都可以进行反向拷贝(????????这里是有问题的,见下面memmove()函数的分析)。

void memcpy(void *pDest,const void *pSrc, int size)
{
	assert((NULL != pDest) && (NULL != pSrc) && (size>=0));
	//内存拷贝,参数都是void类型的指针,所以要将其转换成char*类型的指针以便按字节进行拷贝
	char* dest = static_cast<char*> (pDest);
	const char* src = static_cast<const char*> (pSrc);

	//为了便于记忆,不管是否源地址段与目标地址段重复,我们从尾端进行拷贝。
	dest = dest+size-1;
	src = src+size-1;
	while(size>0)
	{
		*dest-- = *src--;
		size--;
	}
}

memmove()函数分析:下面是以memmove()的例子来说明边界问题的考虑:memmove()实现的算法是将内存中某块固定大小的区域移动到另外一个区域。最常见的写法
·

void memmove(void* pDst,const void* pSrc, size_t size)
{
	assert(pDst != NULL);
	assert(pSrc != NULL);
	assert(size >= 0);

	char* pstrSrc=(char*)pSrc;
	char* pstrDst=(char*)pDst;
	while(size--)
		*pstrDst++ = *pstrSrc++;
}
仔细观察上面程序,会发现有问题。两块内存区域之间由于位置关系可能会发生重叠。重叠通常有三种情况,一是目标区域的首地址落在源区域内;二是目标区域的尾地址落在源区域内;三是两个区域完全重叠。从结果上来看,只有第一种情况才有可能发生与预期不相同的结果,即复制的数据被后续复制数据覆盖的错误。为了解决这个问题,memmove()通常使用反方向复制的方法,从源区域的尾地址开始复制,这样就能避免出现已复制的数据被后续复制覆盖的错误。代码如下:

void memmove(void* pDst,const void* pStr, size_t size)
{
	assert(NULL != pDst);
	assert(NULL != pStr);
	assert(size >= 0);
	//上面的第一种情况,目标区域的首地址落在源区域内,从尾端开始复制
	if((pSrc<pDst) && ((char*)pSrc+size > pDest))
	{
		char *pstrSrc = (char*)pSrc + size -1;
		char *pstrDst = (char*)pDst + size -1;
		while(size--)
			*pstrDst-- = *pstrSrc--;
	}
	else
	{
		char* pstrSrc = (char*)pStr;
		char* pstrDst = (char*)pDst;
		while(size--)
			*pstrDst++ = *pstrSrc++;
	}
}

由于在内存拷贝时会出现目标区域的尾地址落在源区域内这一情况,所以前面在实现memcpy(),函数时不管怎么样都从尾地址开始拷贝,遇到这种情况时就会出问题,所以完整的分析见实现memmove()函数的分析。

void *memset(void *s, int ch, size_t n);将s所指向的某一块内存中的每个字节的内容全部设置为ch指定的ASCII值, 块的大小由第三个参数指定,这个函数通常为新申请的内存做初始化工作, 其返回值为指向S的指针。

void* Memset(void* buffer, char c, int count)
{
	assert(NULL != buffer && count>=0);
	//按字节赋值,所以将void*类型的指针转换为char*类型
	char* pBuff = static_cast<char*> (buffer);
	while(count--)
	{
		*pBuff++ = c;
	}
	return buffer;
}

strcmp()函数的实现:在C语言中int strcmp(char *str1,char * str2),它的功能是比较str1和str2,
当str1<str2,返回值<0
当str1>str2,返回值>0
当str1=str2,返回值=0

int strcmp(char *str1,char *str2)
{
    while(*str1&& *str2 && *str1==*str2)
    {
        ++str1;
        ++str2;
    }

    return *str1 - *str2;
}
在C语言里strstr()的原型为:char* *strstr(char *s1,char *s2),他的功能是从字符串s1中寻找s2第一次出现的位置(不比较结束符)返回第一次出现位置的指针。下面是代码:

/*未经过优化过的算法*/
const char* strstr1(const char* s1, const char* s2)
{
    assert(s1 && s2);
    const char *r = s2,*p;
    const char *p1 = s1;
    while('\0' != *p1)
    {
        p = p1;
		r = s2;
        while(*p == *r)
        {
            p++;
            r++;
        }
        if('\0' == *r)
        {
            return p1;
        }
        else
        {
            p1++;
        }
    }
    return NULL;
}

/*经过优化过后的算法,加入了很多条件判断*/
const char* strstr2(const char* str1,const char* str2)
{
    assert(NULL != str1 && NULL != str2);
    const char* ps1 = str1;
    const char* ps2 = str2;

    int len1 = strlen(str1);
    int len2 = strlen(str2);
	if((len1 == 0) || (len2 == 0)) //两个字符串中有空串则返回NULL
		return NULL;
    /*如果str2的长度大于str1的长度,那么在str1中不可能存在str2,直接返回NULL*/
    if(len2 > len1)
        return NULL;
    while('\0' != *ps1)
    {
        while((*ps1 == *ps2) && ('\0' != *ps2))
        {
            ps1++;
            ps2++;
        }
        if('\0' == *ps2)//str2是str1的字串,返回str2在str1中首次出现的位置
        {
            return str1;
        }
        else//从str1中的下一个字符开始重新比较,重置ps1和ps2
        {
            ps2 = str2;
            str1++;
            ps1 = str1;
            len1--;
        }
         /*此时str1中剩余的字符串已经小于str2的长度,已无法继续查找的必要*/
        if(len1<len2)
        {
            return NULL;
        }
    }
    return NULL;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章