真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

C語言——復(fù)刻一個(gè)strcpy()函數(shù),體驗(yàn)代碼編寫和優(yōu)化的過程-創(chuàng)新互聯(lián)

體驗(yàn)代碼的優(yōu)化過程

項(xiàng)目需求:自己實(shí)現(xiàn)一個(gè)strcpy()

網(wǎng)站建設(shè)哪家好,找創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、微信小程序開發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了酒泉免費(fèi)建站歡迎大家使用!

初始代碼實(shí)現(xiàn)如下:

void my_strcpy(char* dest, char* src);	// 函數(shù)聲明

// 測試
void test()
{char str1[] = "################################";
	char str2[] = "我愛華農(nóng)?。?!華農(nóng)加油?。?!";

	my_strcpy(str1, str2);	// 自己實(shí)現(xiàn)strcpy
	printf("%s\n", str1);
 }


////// 自己實(shí)現(xiàn)的strcpy函數(shù)
///////// 字符串復(fù)制的目的地(char*)
///////// 待復(fù)制的字符串(char*)
///void my_strcpy(char* dest, char* src)
{while (*src != '\0')	// src中的'\0'前的字符都要進(jìn)行復(fù)制
	{*dest = *src;		// 將待復(fù)制字符串的字符復(fù)制到目的字符串中
		dest++;				// 指針向后移位,以便進(jìn)行下一個(gè)字符的賦值
		src++;				// 同上
	}
	*dest = *src;			// 最后的'\0'也要復(fù)制過去
}

注意到my_strcpy()函數(shù)的函數(shù)體中實(shí)際上是可以把解引用*和自增++放在同一行的。于是對strcpy()的優(yōu)化如下:

void my_strcpy(char* dest, char* src)
{while (*src != '\0')		// src中的'\0'前的字符都要進(jìn)行復(fù)制
	{*dest++ = *src++;		// 優(yōu)化
	}
	*dest = *src;				// 最后的'\0'也要復(fù)制過去
}

此時(shí)代碼中的*dest++ = *src++執(zhí)行過程就是:先執(zhí)行解引用*,然后進(jìn)行賦值=,然后執(zhí)行自增++。

從表面看這行代碼還是比較好直接理解的,就是我們所看到的*在前面,++又是后置的,所以肯定是先執(zhí)行了解引用*,然后執(zhí)行后置++,而后置++執(zhí)行自增又是在整個(gè)語句結(jié)束后,也就是賦值語句=結(jié)束之后才++。所以整個(gè)語句的執(zhí)行順序就是先解引用*,然后賦值=,然后指針自增。

但實(shí)際的執(zhí)行順序是這樣的,我先查閱了以下運(yùn)算符優(yōu)先級表,表格如下:

在這里插入圖片描述

*++運(yùn)算符的優(yōu)先級是相同的,所以此時(shí)的結(jié)合先后順序要考慮的是結(jié)合性(=運(yùn)算符優(yōu)先級是較低的,肯定是在最后才執(zhí)行\(zhòng)結(jié)合),由于是這兩個(gè)運(yùn)算符是右結(jié)合(右到左),即結(jié)合順序是從右到左,所以是++先結(jié)合,然后才是*,也就是說實(shí)際上

*dest++ = *src++++先執(zhí)行,然后才是*執(zhí)行,但是由于++是后置的,所以盡管++先結(jié)合或者說先執(zhí)行(以上表述中的“結(jié)合”、“執(zhí)行”不嚴(yán)格區(qū)分,認(rèn)為是一個(gè)意思)了,但是它的自增效果實(shí)現(xiàn)是在整個(gè)語句結(jié)束之后,也就是最后的=賦值結(jié)束之后才++,所以最終語句的執(zhí)行順序還是:先解引用*,然后賦值=,然后自增。但我們要知道該語句本質(zhì)的結(jié)合\執(zhí)行順序是:先后置++,后*,然后=。只不過++的自增效果實(shí)現(xiàn)是在最后而已。

以上是第一步優(yōu)化,實(shí)際上還可以繼續(xù)優(yōu)化:

我們注意到此時(shí)my_strcpy()函數(shù)的實(shí)現(xiàn)是把字符串內(nèi)容的拷貝和'\0'的拷貝分為了兩個(gè)部分,其實(shí)沒有必要。我們將函數(shù)進(jìn)一步優(yōu)化后如下:

void my_strcpy(char* dest, char* src)
{while (*dest++ = *src++) {}		// 優(yōu)化
}

此時(shí)的代碼不僅能夠繼續(xù)實(shí)現(xiàn)將字符串中的內(nèi)容進(jìn)行復(fù)制,并且還能將字符串最后的'\0'也進(jìn)行復(fù)制,而且在'\0'復(fù)制之后while循環(huán)就會停下來,函數(shù)執(zhí)行完畢。

這是因?yàn)樵趶?fù)制完字符串的最后一個(gè)字符后,循環(huán)條件處的整個(gè)*dest++ = *src++表達(dá)式的值就是最后一個(gè)字符(非'\0'),此時(shí)相當(dāng)于循環(huán)條件仍是真,會進(jìn)入空循環(huán)體執(zhí)行,空循環(huán)體執(zhí)行完之后再次來到循環(huán)條件處(此時(shí)的src相較于上一次循環(huán)已經(jīng)是++了,其解引用*后指向的是'\0',于是在完成賦值操作后,整個(gè)表達(dá)式的值就是'\0',此時(shí)循環(huán)條件就變?yōu)榧?,循環(huán)結(jié)束。這里我們注意到'\0'是在完成了賦值操作后,循環(huán)才結(jié)束的。所以上面的代碼就很好地將字符串內(nèi)容的拷貝和'\0'的拷貝合成一個(gè)部分\語句,并且能夠保證'\0'也被拷貝)

以上是第二步優(yōu)化,如果要考慮代碼的健壯性,我們可以再繼續(xù)優(yōu)化:

我們考慮這樣的情況,如果我們在調(diào)用這個(gè)函數(shù)的時(shí)候,不小心給destsrc傳了一個(gè)空指針:

void test()
{char str1[] = "################################";
	char str2[] = "我愛華農(nóng)?。?!華農(nóng)加油?。。?;

	my_strcpy(str1, NULL);	// 傳了一個(gè)NULL指針
	printf("%s\n", str1);
 }

此時(shí)代碼運(yùn)行結(jié)果如下:

在這里插入圖片描述

程序掛掉了?。?!因?yàn)槲覀兇藭r(shí)在函數(shù)中會訪問這個(gè)test()中傳過來的空指針NULL,這是非法的!

這就說明我們的代碼在健壯性方面存在欠缺,原因就是我們的my_strcpy()函數(shù)中缺乏對形參變量的合法性判斷:我們對主調(diào)函數(shù)傳過來的參數(shù),不管三七二十一就直接開始解引用了。

正確的做法是我們應(yīng)該對這些形參使用前先進(jìn)行一個(gè)判斷:

void my_strcpy(char* dest, char* src)
{if (dest != NULL && src != NULL)
	{while (*dest++ = *src++) {}
	}
}

此時(shí)就算我們的test()函數(shù)在調(diào)用my_strcpy()的時(shí)候給這個(gè)函數(shù)傳遞了一個(gè)NULL指針,程序運(yùn)行效果如下:

在這里插入圖片描述

可以看到,現(xiàn)在程序至少能夠規(guī)避掉這個(gè)錯(cuò)誤而不會掛掉(這是很重要的!因?yàn)槌绦蛉绻渌糠值墓δ苷?,但是因?yàn)槟氵@個(gè)地方的問題而程序掛掉了,那么其他部分的功能也執(zhí)行不了!所以我們至少至少的!應(yīng)該要保證程序不會掛掉!)

這個(gè)時(shí)候程序就健壯了一些,但是我們還要考慮到,程序更改成這樣之后,其實(shí)我們是不容易去發(fā)現(xiàn)這個(gè)錯(cuò)誤(或者說是BUG)的。

為了讓我們的程序更健壯,并且容易進(jìn)行Debug,我們調(diào)用一個(gè)函數(shù):assert()(我們稱之為:斷言),其使用必須先包含一個(gè)頭文件:

assert()這個(gè)函數(shù)的功能是:我們在這個(gè)函數(shù)()內(nèi)傳入一個(gè)表達(dá)式

  1. 如果這個(gè)表達(dá)式的執(zhí)行結(jié)果為真,那么該語句就相當(dāng)于一個(gè)空語句(效果上"相當(dāng)",不是執(zhí)行的時(shí)間和空間效率上的"相當(dāng)");
  2. 如果這個(gè)表達(dá)式的執(zhí)行結(jié)果為假,那么程序就會在這里報(bào)錯(cuò),并且會顯示程序出錯(cuò)的位置信息

那么我們利用這個(gè)函數(shù),對my_strcpy()進(jìn)行進(jìn)一步的改進(jìn):

void my_strcpy(char* dest, char* src)
{assert( dest != NULL );	// 一旦dest為NULL,該函數(shù)就會報(bào)錯(cuò)
	assert( src != NULL );	// 一旦src為NULL,該函數(shù)就會報(bào)錯(cuò)
	while ( *dest++ = *src++ ) {}
}

此時(shí)我們還是在test()函數(shù)中給src傳一個(gè)NULL,程序運(yùn)行效果如下:

在這里插入圖片描述

可以看到此時(shí)DOS窗口很好地將代碼出錯(cuò)的文件路徑,出錯(cuò)的位置(行數(shù))都顯示了出來

這樣的做法將有利于我們編寫程序時(shí)去發(fā)現(xiàn)BUG。修改后的整體代碼如下:

void my_strcpy(char* dest, char* src);	// 函數(shù)聲明

// 測試
void test()
{char str1[] = "################################";
	char str2[] = "我愛華農(nóng)?。?!華農(nóng)加油?。?!";

//	my_strcpy(str1, NULL);	
//  my_strcpy(NULL, str2);
	my_strcpy(str1, str2);	
    
    printf("%s\n", str1);
 }

void my_strcpy(char* dest, char* src)
{assert( dest != NULL );	// 一旦dest為NULL,該函數(shù)就會報(bào)錯(cuò)
	assert( src != NULL );	// 一旦src為NULL,該函數(shù)就會報(bào)錯(cuò)
	while ( *dest++ = *src++ ) {}		// 優(yōu)化
}

此時(shí)當(dāng)我們把test()在調(diào)用時(shí)傳入正常的參數(shù),代碼也能夠正常運(yùn)行:

在這里插入圖片描述

程序優(yōu)化到這一步就差不多了,我們回想一下:我們寫my_strcpy()這個(gè)函數(shù)的目的是要自己實(shí)現(xiàn)跟庫函數(shù)中strcpy()的一樣的功能。功能現(xiàn)在是實(shí)現(xiàn)得差不多了,我們來看看我們寫的這個(gè)my_strcpy()與庫函數(shù)中的strcpy()又有什么不同呢?

通過MSDN(微軟提供提供給廣大程序員的開發(fā)大全,是一個(gè)幫助文檔)我們查閱一下關(guān)于strcpy()函數(shù)的聲明:

在這里插入圖片描述

再對比我們寫的my_strcpy()函數(shù)的聲明:

void my_strcpy(char* dest, char* src);

可以發(fā)現(xiàn)有兩個(gè)區(qū)別:

strcpy()my_strcpy()的區(qū)別
  1. strcpy()的返回值是char*類型,strcpy()返回值是void類型
  2. strcpy()的第二個(gè)形參(src)有const修飾,strcpy()則沒有

strcpy()比我們多的這兩個(gè)地方,有什么作用呢?接下來我們剖析一下:

第一個(gè)區(qū)別

第一個(gè)區(qū)別:char *strSourcechar前多了一個(gè)const修飾:

有什么用呢?

我們先看我們自己寫的my_strcpy()函數(shù)的代碼:

void my_strcpy(char* dest, char* src)
{assert( dest != NULL );
	assert( src != NULL );
	while ( *dest++ = *src++ ) {}
}

我們寫這個(gè)函數(shù)的目的是將源字符串src中的內(nèi)容賦值到目標(biāo)字符串dest中,實(shí)現(xiàn)這個(gè)過程的語句是上面的第五行代碼

`while ( *dest++ = *src++ ) {}`

設(shè)想如果我們哪天在復(fù)刻這個(gè)代碼的時(shí)候,不小心把destsrc的位置寫反了,寫成了這樣:

`while ( *src++ = *dest++ ) {}`

程序肯定會出問題!

或者說我們在函數(shù)中對src的內(nèi)容*src不小心進(jìn)行了修改(src是我們要復(fù)制的字符串,其內(nèi)容是我們在調(diào)用這個(gè)函數(shù)的時(shí)候不希望會被修改的)

這些都是不允許的,最根本的原因就是我們調(diào)用這個(gè)函數(shù)的初衷是為了復(fù)制,并不希望待復(fù)制的字符串反過來被莫名其妙地修改。但是項(xiàng)目開發(fā)的過程中,犯這樣這樣的錯(cuò)誤在所難免(變量用著用著,不小心就把它改掉了…)

所以為了避免這樣的問題發(fā)生我們可以在my_strcpy()形參src的位置利用const修飾:
這里涉及到指針常量和常量指針的問題的知識,大家可以看我的這篇博客:C/C++中指針常量和常量指針的一些小見解

void my_strcpy(char* dest, const char* src)	// const常量指針,src所指向的內(nèi)容*src不可被修改
{assert( dest != NULL );	
	assert( src != NULL );
	while ( *dest++ = *src++ ) {}
}

這樣讓src變成一個(gè)常量指針之后,在函數(shù)中就使得其指向的內(nèi)容*src不會被錯(cuò)誤修改,即使程序員不小心寫錯(cuò)了,想修改src中的內(nèi)容,編譯器也會及時(shí)地報(bào)錯(cuò):表達(dá)式必須是可修改的左值。

所以代碼到了這里,第一個(gè)區(qū)別存在的原因我們就清楚了,代碼的優(yōu)化就又進(jìn)了一步,代碼產(chǎn)生BUG的可能性就更小了。

第二個(gè)區(qū)別

接下里我們繼續(xù)看第二個(gè)區(qū)別:strcpy()的返回值是char*類型,strcpy()返回值是void類型

那么返回的這char*類型有什么用呢?我們查閱幫助文檔:

在這里插入圖片描述

可以獲知strcpy()的返回值是目標(biāo)字符串strDestination的地址,這樣做有什么用呢?

用途:

? 可以用于直接作為printf對字符串的輸出:printf("%s\n", my_strcpy(str1, str2)),這里也體現(xiàn)了鏈?zhǔn)皆L問的思想

(函數(shù)(的返回值)直接作為另一個(gè)函數(shù)參數(shù))

要使得我們的my_strcpy()函數(shù)返回目標(biāo)字符串strDestination的地址,我們將函數(shù)更改后如下:

char* my_strcpy(char* dest, const char* src)	// 返回值類型修改為char*
{char* tempSave_Addr = dest;					// 保存目標(biāo)字符串strDestination的起始地址

	assert( dest != NULL );
	assert( src != NULL );
	while ( *dest++ = *src++ ) {}
	
	return tempSave_Addr;						// 返回值目標(biāo)字符串strDestination的起始地址

}

此時(shí)我們可以驗(yàn)證剛才提到的第一個(gè)用途,直接用于printf打印輸出復(fù)制后的目標(biāo)字符串strDestination

void test()
{char str1[] = "################################";
	char str2[] = "我愛華農(nóng)!??!華農(nóng)加油?。?!";
    
//	my_strcpy(str1, str2);
//	printf( "%s\n", str1);
// 	上面的兩行代碼合為下面一行:
    printf( "%s\n", my_strcpy(str1, str2));
 }

程序運(yùn)行后輸出了被復(fù)制后的str1,跟我們修改之前的運(yùn)行效果一致。

這個(gè)地方也體現(xiàn)了鏈?zhǔn)皆L問(函數(shù)的返回值作為另一個(gè)函數(shù)的參數(shù)),程序的這一步修改,其多了一個(gè)功能:這個(gè)函數(shù)可以直接作為其他函數(shù)的參數(shù)。功能進(jìn)一步豐富了。

最后我們加上必要的注釋之后,整體代碼如下:

#include#include#include

char* my_strcpy(char* dest, char* src);		// 函數(shù)聲明

// 測試
void test()
{char str1[] = "################################";
	char str2[] = "我愛華農(nóng)?。?!華農(nóng)加油!??!";

	printf("%s\n", my_strcpy(str1, str2));	// 打印被復(fù)制后的目標(biāo)字符串
}


////// 將src指向的字符串拷貝到dest指向的空間,包括'\0'字符
///////// 字符串復(fù)制的目的地(char*)
///////// 要復(fù)制的字符串(char*)
///char* my_strcpy(char* dest, const char* src)
{char* tempSave_Addr = dest;			// 保存目標(biāo)字符串strDestination的起始地址
	
	// 以下兩行代碼是斷言,用于保證指針的有效性
	assert( dest != NULL );				// 一旦dest為NULL,該函數(shù)就會浮窗報(bào)錯(cuò)
	assert( src != NULL );				// 一旦src為NULL,該函數(shù)就會浮窗報(bào)錯(cuò)

	while ( *dest++ = *src++ ) {}		// 將src指向的字符串拷貝到dest指向的空間,包括'\0'字符
	
	return tempSave_Addr;				// 返回值目標(biāo)字符串strDestination的起始地址
}


int main()
{test();

	system("pause");
	return 0;
}

程序運(yùn)行結(jié)果:

在這里插入圖片描述

以上就是我們關(guān)于自己編寫一個(gè)類似strcpy()函數(shù)my_strcpy()的過程,體驗(yàn)代碼優(yōu)化的一個(gè)過程。目的在于提高我們的coding技巧,減少BUG的產(chǎn)生,即使產(chǎn)生BUG不可用避免,也要學(xué)會如何編寫程序使得我們Debug的難度降低。

順便分享比特鵬哥給我們的coding技巧建議:

  1. 盡量使用assert
  2. 盡量使用const
  3. 養(yǎng)成良好的編碼風(fēng)格
  4. 添加必要的注釋
  5. 避免編碼的陷阱(野指針濫用,如上面代碼實(shí)現(xiàn)過程中的NULL)

以上內(nèi)容總結(jié)自鵬哥的C語言教學(xué)視頻25.VS環(huán)境-C語言實(shí)用調(diào)試技巧(2)嗶哩嗶哩_bilibili并加上了一些個(gè)人的思考。
歡迎大家批評指正,交流想法~
最后偏心一下我的學(xué)校:
(以下圖片來自華南農(nóng)業(yè)大學(xué)官方公眾號,非個(gè)人拍攝?。。。?br />(以下圖片來自華南農(nóng)業(yè)大學(xué)官方公眾號,非個(gè)人拍攝?。。。?br />(以下圖片來自華南農(nóng)業(yè)大學(xué)官方公眾號,非個(gè)人拍攝?。。。?/p>

在這里插入圖片描述在這里插入圖片描述
在這里插入圖片描述
我大華農(nóng):抗疫必須勝利?。。?br />我大華農(nóng):抗疫必須勝利?。?!
我大華農(nóng):抗疫必須勝利?。?!

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機(jī)房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧


本文標(biāo)題:C語言——復(fù)刻一個(gè)strcpy()函數(shù),體驗(yàn)代碼編寫和優(yōu)化的過程-創(chuàng)新互聯(lián)
網(wǎng)頁鏈接:http://weahome.cn/article/diopsd.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部