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;
}



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