一.strlen實現

str的原理:它的作用是計算字符串長度,根據這個知識我們來寫代碼吧。

int MyStrlen(const char* s)
{
    int count = 0;//用於計算長度
    while(*s != '\0')
    {
        count++;
        s++;
    }
    return count;
}

其實strlen的模仿是比較簡單的,只要死抓strlen是以'\0'結尾的即可,下面我用圖帶着大家理解一下吧。

C語言常用庫函數實現(一)_內存拷貝_c語言寫一個函數,完成內存之間的拷貝_bc

其實就是這樣的,s每次++其實就是往上移動一格,直到碰到'/0'才會停止,我們就輕鬆搞定了strlen的模擬實現。

二.strcpy

  strcpy的原理:將源字符串 src(包括結束符 '\0')複製到目標地址 dest 指向的內存空間中。

strcpy(char* dest,char* src)

根據strcpy的定義其實也能清楚它到底要幹嘛,將src拷到dest你可以理成把src的字符一個一個拷到dest即可。

        下面我們就來實現它吧!

char* strcpy(char* des, const char* src)
{
	assert(des != NULL);//看是不是NULL
	assert(src != NULL);
	char* str = des;//保留des用於返回
	while (*src != '\0')
	{
		*str = *src;
		str++;
		src++;
	}
	*str = '\0';
	return des;
}

        但是這個代碼其實是有槽點的很且很不安全,下面我們用圖來分析這個槽點是什麼

C語言常用庫函數實現(一)_內存拷貝_c語言寫一個函數,完成內存之間的拷貝_bc_02

    你發現了沒,如果真的要把src內容全部塞給dest那麼就會導致dest訪問越界,這也C語言中strcpy的一個安全隱患

三.strcmp

strcmp的原理:按字典順序依次比較兩個字符串的字符大小(即:從第一個字符開始依次往下比)前者大返回一個任意整數(我將用1代替)相等返回0,後者大返回任意一個負數(我用-1代替)

int strcmp(const char* str1, const char* str2)
{
	assert(str1);
	assert(str2);
	const char* s1 = str1;
	const char* s2 = str2;
	while (*s1 != '\0' && *s2 != '\0')
	{
		if (*s1 > *s2)
		{
			return 1;
		}
		else if (*s1 < *s2)
		{
			return -1;
		}
		else
		{
			s1++;
			s2++;
		}
	}
	if (*s1 == '\0' && *s2 =='\0')
	{
		return 0;
	}
	else if (*s1 != '\0')
	{
		return 1;
	}
	else
	{
		return -1;
	}
}

循環終止時,結果只能是1或-1,因為此時字符比較已出現差異。若循環未終止,則存在三種可能情況:前者字符串到達終止符'\0'、後者字符串到達'\0',或雙方同時到達'\0'。

四.strstr

strstr的原理:在一個字符串(主串)中找另一個字符串(子串)首次出現的位置

char* strstr(const char* str1, const char* str2)
{
	assert(str1 != NULL);
	assert(str2 != NULL);
    if (!*str2)
		return((char*)str1);//這裏強轉是因為C的一個特殊的情況,就是參數是const char*返回的確實char*
	const char* cp = str1;
	const char* s1, * s2;
	while (*cp)
	{
		s1 = cp;
		s2 = str2;
		while (*s1 == *s2 && *s2 != '\0')
		{
			s1++;
			s2++;
		}
		if(*s2 == '\0' )
			return (char*)cp;
		cp++;
	}
	return(NULL);
}

防止野指針與空字符串處理
assert用於確保str1str2非空指針,避免野指針問題。若str2為空字符串,直接返回str1,因為空串是任何字符串的子串。

循環核心邏輯
外層循環通過cp遍歷str1的每個字符作為匹配起點。內層循環通過s1s2同步移動比較字符,若完全匹配(*s2 == '\0'),則返回當前cp的位置。若中途不匹配,則外層循環跳到下一個字符重新開始匹配。

可優化點説明

  1. 循環邊界優化
    外層循環可限制為strlen(str1) >= strlen(str2)的情況,避免無效比較。例如str1="ab"str2="abc"無需比較。
  2. 兩層循環的必要性
    str1="bbac"str2="bac"為例:
  • 第一次從str1的第一個'b'開始匹配,內層比較到第二個字符'b' != 'a'失敗。
  • 外層循環跳到第二個'b'重新匹配,此時內層完整匹配"bac",返回正確結果。
  1. 終止條件*s2 != '\0'的作用
    防止比較越界。例如str1="abc\0xxx"str2="abc",當s2指向'\0'時匹配成功,若未檢查可能導致s1s2繼續向後訪問,引發越界。

五.memcpy

memcpy的原理:其核心是按字節,逐個複製內存數據,不關心數據的類型(因此是 “內存拷貝”)

//des:目的地,src:來源,count:拷貝幾個字節
void* memcpy1(void* dst, const void* src, size_t count)
{
	assert(dst);
	assert(src);
	char* s1 = (char*)dst;
	char* s2 = (const char*)src;
	while (count--)
	{
		*s1 = *s2;
		s1++;
		s2++;
	}
	return dst;
}

其實memcpy和strcpy原理都是差不多的,不過memcpy要實現的是任意類型,而strcpy只能實現字符類型,其實要理解這個代碼只需要認識內存即可,下面我通過一張圖來展示

C語言常用庫函數實現(一)_內存拷貝_c語言寫一個函數,完成內存之間的拷貝_字符串_03

        這裏我們以整數類型為例(int n = 4)。整數類型在內存中佔4個字節空間,其地址取最低位的那個字節地址。只要保證這4個字節空間連續存儲即可。接下來我們重點討論內存移動的問題。

        為什麼使用void指針?可以將其理解為無類型屬性(就像五行中的"無"),而其他類型都有特定屬性(金、木、水、火、土)。要實現所有類型的拷貝,就需要一個能承載各種類型的通用載體,void*(無類型)顯然是最合適的選擇。

        但為什麼又要轉為char類型呢?因為void*不能直接解引用操作,只能作為載體使用。選擇char類型是因為:char類型大小為1字節,因此char指針每次移動1字節這樣就能確保精確控制內存移動。如果使用short(每次移動2字節),就無法正確處理char類型數據(1字節),會導致產生垃圾數據。        

        理解了這些,memcpy的工作原理就清晰了。相信聰明的你已經明白了其中的原理。

        但是這個代碼是可能有問題的,至於什麼問題下面我用圖來展示。

C語言常用庫函數實現(一)_內存拷貝_c語言寫一個函數,完成內存之間的拷貝_bc_04

你看第一張圖是符合memcpy(不會出現問題)但是你看第二張圖很明顯出了問題,為什麼呢?

其實我們根據代碼可以知道,我們是用src取賦值給dst,但是如果你dst在src前面就會導致一個問題,我src把第一格的內容給了dst(src下一格)那麼我在取下一格不就是src上一格的內容嗎,因此就會導致問題,而這個問題就叫內存重疊。

        我們知道了memcpy當dst在src前面的時候會出現內存重疊,但是在前面多少不會出現內存重疊呢?

        其實就是保證dst不會觸碰到src的地址,即dst起點>src終點。

六.memmove

memmove的出現就是為了解決內存重疊的問題的

void* memmove(void* dst, const void* src, size_t count)
{
	void* ret = dst;
	char* dst1 = (char*)dst;
	const char* src1 = (const char*)src;
	if (dst1 < src1 || dst1 > src1 + count)//確保元素修改後不在使用
	{
		while (count--)
		{
			*dst1 = *src1;
			 dst1 ++;
			 src1 ++;
		}
	}
	else
	{
		dst1 = dst1 + count - 1;
		src1 = src1 + count - 1;
		while (count--)
		{
			*dst1 = *src1;
			 dst1 --;
			 src1 --;
		}
	}
	return ret;
}

 從代碼中可以看出,memmove 的解決方案體現在 else 語句塊中。讓我們分析這段代碼的具體作用吧。

        memcpy 之所以會出現內存重疊問題,是因為 src 可能將自身資源複製到 src+n 的位置。這樣後續操作時,src 可能會再次訪問這個位置。要解決這個問題,關鍵在於避免 src 將資源複製到自己將要訪問的位置。

        解決方案很簡單:讓 src 把已複製的內容全部放在目標位置之後。這樣就能確保 src 不會重複處理同一個內容。

這次我講的是面試高頻率考的函數實現,希望能給大家帶來幫助,如果有誤可以評論我,我們抓緊修改的,希望能大家一起進步,我的一些解釋採用了AI潤色,希望和它的幫助下能給大家帶來簡潔明瞭的解釋