1. 程式人生 > >如何寫出一個好的程式

如何寫出一個好的程式

我們以memmove函式作為例子,來看我們如何一步步精簡和優化你的程式碼。

寫之前我們應該知道memmove這個函式有什麼用?
他就是一個按位元組的拷貝函式,把目標的內容按位元組拷貝到你指定的地址,他和strcpy

不同的地方就是他是按位元組拷貝,他可以拷貝任意的型別的。

現在我們考慮一下,開始第一種方案,只完成拷貝功能

NO'1

void MyMemMove(char *dst,char *src,int count)
{
while(count--)
{
*dst++=*src++;
}
}

如果可以寫出這個說明,你的水平對於大學的C語言來說就是可以及格的,對於那些白卷的 和函式亂宣告的你已經算是很厲害的,但是你距離精英還是有距離的。因為這個程式是可
以執行的,但是可用性不夠高,因為你要可以拷貝各種型別的。

NO'2

void MyMemMove(void *dst, void *src , int count)
{
           while (count --)
          {
                   *( char *)dst = *(char *) src;
                    dst = (char *)dst + 1;
                    src = (char *)src + 1;
          }
}

我們可以發現它使用了void*現在這個函式可以接受這種各樣的型別,然後經過強制
指標轉換確實是從使用者的程式碼轉移到了庫的程式碼裡,但我們可以將MyMemMove理解為庫,而將 Test理解為使用者,事實上通過調整之後的效果卻有天壤之別, NO.1是一逸永勞,而N0.2是一 勞永逸!

NO'3

void * MyMemMove(void *dst, const void *src, int count )
{
           assert(dst );
           assert(src );
           void *ret = dst ;
           while (count --)
          {
                   *( char *)dst = *(char *) src;
                    dst = (char *)dst + 1;
                    src = (char *)src + 1;
          }
           return ret;
}
我們給src加上了一個const,其實這個const說到底是一個約束程式設計師的關鍵詞,他可以防止你在後 面更改src的值現在再來考慮這樣一種情況,有使用者這樣呼叫庫:MyMemMove(NULL,src, count) 這是完全可能的,因為一般來說這些地址都是程式計算出來的,那就難免會算錯,出現零地址或者 其它的非法地址也不足為奇。可以預料的是,如果出現這種情況的話,則程式馬上就會down掉。 實上在標準庫裡已經存在解決這些功能的巨集:assert(關於assert我還是會有部落格介紹的),而且 更加好用,它還可以在定義DEBUG時指出程式碼在那一行檢查失敗,而在沒有定義DEBUG時完全可以把 它當作不存在。

NO'4

void * MyMemMove(void *dst, const void *src, int count )
{
           assert(dst );
           assert(src );
           void * ret = dst ;
           if (dst <= src || ( char *)dst >= ((char *) src + count )) {

                    while (count --) {
                             *( char *)dst = *(char *) src;
                              dst = (char *)dst + 1;
                              src = (char *)src + 1;
                   }
          }
           else {
                    dst = (char *)dst + count - 1;
                    src = (char *)src + count - 1;

                    //逆序拷貝
                    while (count --) {
                             *( char *)dst = *(char *) src; 
                              dst = (char *)dst - 1;
                              src = (char *)src - 1;
                   }
          }
           return(ret);
}
我原來看到這裡也是很吃驚,後面那麼長的東西是幹什麼的,其實寫到NO'3的時候你已經很優秀了 ,但是寫程式最重要的是注意程式的嚴密性,曾經看到過一句話,其實寫一個程式主幹道用的程式碼 一般少於個個分枝用來完善的程式碼。寫出一個程式碼你要保證它的使用範圍,保證它不會出BUG。所以 得顧及個個方面,而記憶體這東西是最容易出問題的。 請看下面的程式碼
# define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<Windows.h>
#include<assert.h>
void * MyMemMove(void *dst, const void *src, int count)
{
	assert(dst);
	assert(src);
	void *ret = dst;
	while (count--)
	{
		*(char *)dst = *(char *)src;
		dst = (char *)dst + 1;
		src = (char *)src + 1;
	}
	return ret;
}

int main()
{
	char p[256] = "hello,world!";
	MyMemMove(p + 1, p, strlen(p) + 1);
	printf("%s\n", p);
	system("pause");
	return 0;
}
我們一起來看看他的執行結果 這是為什麼呢? 呼叫這個函式的記憶體分佈有4種情況,分別如下

現在我們例子中呼叫方法圖中是第三個,這個hhhhhhhhhh是如何發生的呢? 我們開始
分析一下 首先讓指標分別指向src指標和dst指標指向p和p+1; 然後開始拷貝,現在src第一個值為h,既p的第一個值為h,然後dst經過拷貝也為h,既p+1為h。 接下來的一步就是把p+1(src++)的值拷貝給p+2(dst++),結果p+2的值還是h,然後就這樣一直 下去,知道src結束,所有拷貝過去的值都是h,也就成了我們的hhhhhhhhh. 但是我們應該怎麼解決呢? 看著第三張圖,如何拷貝才能保證給dst賦值,沒錯是倒序拷貝,當拷貝src後面的內容時,dst開 闢的那些空間剛剛不重疊。所以我們就有了最後一個版本。完美的解決掉所有問題。 寫程式碼的人很多,寫出好程式碼的就沒有多少了,一個好的程式碼需要你面面俱到,有人說過一個好的 程式設計師一定是是一個好的偵探,所以寫程式碼一定要細心,從我做起,寫這些東西也在警示我自己。