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