2009年7月21日 星期二
我要畢業了~~
7/21也是老婆生日的日子,所以今天算來是雙喜臨門,考完經過樂透站,順手買了幾張,來個三喜臨門吧~~
[1]..............
2009年7月7日 星期二
2009年6月8日 星期一
外掛之後
得意算盤打得精,誰知道這是一個很長的路,走了大概兩個月吧,要讓SetWindowLong可以執行,需要把自己的程式注入對方程式中,寫到最後,變成在寫外掛了,換掉WndProc? 簡單,五分鐘搞定,第一個犧牲的對象是某跳舞遊戲,但是為了精確對時,讓我延畢了一學期,本來以為不會再碰外掛的事了,忽然老師又給了個想法"你可不可以利用LUA抓取山口山的部分截圖?",想想,LUA?我會,抓圖?簡單,只要取得山口山的Direct3D繪圖裝置就搞定了,好吧,就接下來了。
這次,更累了,攔不到山口山的Device!!!
Device不是從IDirect3D9生出來的嗎?怎麼山口山這麼奇怪,硬是另闢蹊徑,問遍了GOOGLE,有文章提到可以從COM物件創建方面攔截,但是...失敗。老師說可以去掃描記憶體,我試了,在山口山程式記憶體中翻山越嶺,守著電腦執行4個多小時後,放棄了,就算成功,誰會願意每次執行山口山都要等個幾個小時? 好幾個禮拜,對著山口山這個大怪物,執行又跳出,跳出又不死心繼續執行,最後發現,我快到了極限,想不出任何的方法,只好準備向老師自首。有一句話是對的,山窮水盡疑無路,柳暗花明又一村,正當要放棄的時候,一個很偶然的錯誤,正當我的外掛開始跑,而不小心把山口山切成錯誤的視窗模式,把它改回全螢幕獨占模式時,莫名奇妙忽然抓到了繪圖裝置,一切都搞定了,好想哭!!後來修修改改,用了一個很醜的方法,但是總是可以透過LUA來自動執行抓圖,沒圖沒真相...
掛人程式,感覺沒那麼難,真的難的是,開了一扇門後面還有無數扇門,要跟原設計者鬥智鬥力鬥時間,我覺得,最後應該還是原設計者落下風,為什麼? 雖然我很累,花了好幾個月,可是他們更慘,花了好幾年來完成遊戲,被人破解,也不能怪他們了,那要怪誰? 微軟的濫OS吧,可是我得感謝微軟,沒有這個爛OS,我恐怕要等頭髮花白才有辦法畢業吧~~
2009年5月25日 星期一
2009年5月16日 星期六
2009年4月24日 星期五
本末倒置
1. 系統測試不如預期
2. 老闆不爽,質疑沒有監督好品質
3. 檢討
4. 改版
5. goto 1
無窮的迴圈,老闆失去耐心,員工失去鬥志,我在一邊看,只有一個想法,大多數人都應該有這樣的經驗,
去餐廳點菜的時候沒意見,等到菜端上桌,東挑西挑,說要甜一點、鹹一點、淡一點,可是沒人肯承認,最初的那道菜根本不是你想要的,同一道菜,廚師怎麼改,永遠不能滿足你。
這點就像1~5的迴圈,大家都只在事後檢討這裏打轉,沒有人想去事前分析設計一下如何進行,問到系統想要給玩家的核心概念是什麼? 畫面風格概念是什麼? 某個地圖的特色是什麼? 介面的操作的特色是什麼? 答不出來,這難怪介面一改再改沒人愛,場景東加西加越加越顯雜亂。
也許你會說,因為東西做完了,在測試,這種情況難免,但是我要說的是,為什麼事前不先把最後的成品可能長相先溝通討論,不要在最後的時候,才一句"你怎麼做成這樣?",把責任怪到別人頭上?
以自我本位而言,沒人知道你內心想什麼,知道你想什麼,沒人知道你的標準何在,因此每次交付工作給別人,你總是感嘆天兵到處都是,所以,我覺得,以後做事之前,是不是先應該找方法,確認彼此之間,對工作的內容認知是一致的,這樣絕對可以省卻很多1-5迴圈發生。
2009年3月11日 星期三
澤澤的對話
最近大家都發現了一個令人擔心的現象,就是他說話不知怎地,開始會大舌頭,一句"阿爸",叫成"阿阿阿阿阿爸",剛好六個字,害得我每次都得正經地糾正他,老婆這地方倒是看得很開,解釋說,是因為小乙澤一次想說太多字,咬在嘴吧裡面,才變成這樣,希望如此啦....。
經濟愈差 病毒愈多 -每10部電腦就有1.1部成為非自願的殭屍電腦
這是我研究所同學Mark的部落格文章,他在趨勢科技工作,參考看看。
經濟愈差 病毒愈多 -每10部電腦就有1.1部成為非自願的殭屍電腦根據報導每10部電腦就有1.1部成為非自願的殭屍/傀儡電腦,但多數人都不自知,甚至到警察找上門,才知道自己的電腦被當作攻擊跳板,幫駭客賺黑心錢。根據近來趨勢科技監測到的多項案例顯示,經濟愈差病毒反而愈多。許多原本兼差的駭客或是企業的 IT 技術人員,可能因為被裁員的關係,轉向將撰寫惡意程式當作主要收入來源。比本部落格先前提到有駭客刻意撰寫假的線上理財工具,使其成為駭客線上詐財工具 藉機斂財,時值不景氣的地下黑金當道, 防毒軟體預算不能省,尤其是個人用戶電腦容易被駭客當成殭屍電腦發動不法攻擊,觸犯法律。趨勢科技表示,在受害用戶中植入殭屍病毒,並加以販售的例子已出現多起案例,比如去年在紐奧良聯邦陪審團所受理的一份起訴書中,一名巴西男子因組織及出租一個由 100,000 台電腦所組成的傀儡網路而遭到起訴。據稱他嘗試將傀儡網路的使用權以及程式碼,以 $37,000 美元的價格出售給第三方 [相關文章:Alleged Brazilian botnet herder faces US extradition • The Register]。此外, FBI 在第二階段的打擊傀儡網路行動 (任務名稱為 “Operation Bot Roast”) 中逮捕了八名嫌犯,這些嫌犯據稱已造成超過 $2 千萬美元的經濟損失,受害電腦超過一百萬台 [相關文章:FBI: Operation 'Bot Roast II' Nets Hackers ]。
<後面的有些商業廣告嫌疑,被我刪了,有興趣者,自行閱讀原文>
2009年2月13日 星期五
類別裡面的const"變數"
1. 用#define 這個方式是最暴力的,被define的常數本身不隸屬任何類別,曾經看過這樣的程式碼,寫在程式的任何地方意義都是一樣的:
// 寫在裡面
class ABC
{
#define PI 3.14159
};
#define PI 3.14159
class ABC
{
};
所以,嚴格說來,這不算"類別裡面的const變數"。
2. 用const修飾的變數
class ABC
{
const float PI;
};
這種方式只能在建構式的initial-list初值化,錯過這個機會,變數值就不知道是什麼了
ABC::ABC()
: PI(3.14159) // 這裡
{
}
3. 類別內共用的常數變數,可以用static修飾,讓所有物件共用這個"常數"
class ABC
{
static const int SONG=123;
};
用這種方法可以直接在類別宣告裡面給初值,但是有一個限制,這種方法僅限於型別是整數的型態(至少VC是這樣的),如果想要用非整數的型態,要用這樣的寫法
class ABC
{
static const float PI;
};
在CPP某處加:
const float ABC::PI=3.14159;
為什麼要加在CPP? 只是因為不能讓"const float ABC::PI=3.14159;"被編譯兩次以上,如果放在.h有可能發生,而造成編譯錯誤。
4. 使用enum
class ABC
{
enum { SONG=134, STOP=1113 };
};
用這種方法,只要enum區段合法,任何時候都可以用 ABC::SONG 這樣的寫法來取得其值,不過限制是,enum只支援整數型別...。
這麼多種常數設計方式,我們要如何挑選正確的呢?
以下提出一般常用的原則(記住,只是原則,沒有規定一定要這樣)
1)#define大致上會被丟到歷史的洪流去了,因為本身有很多隱藏的危機,在系統寫大的時候,經常造成不知所謂的BUG
2)const變數,每個物件還有機會設定各自的常數值
class ABC
{
private:
const float PI;
public:
ABC(float a)
:PI(a)
{
}
};
void main( void )
{
ABC abc(100);
ABC def(200);
}
3)通常用於類別內,獨一無二的非整數的常數值
4)通常用於類別內,獨一無二的整數的常數值
2009年2月4日 星期三
恐怖的巧合?
這天,老婆心血來潮,當著小澤澤的面前問他,"你的小卡車呢?",小澤澤依照往常"嗯!"地胡亂一指,指著辦公桌下和活動櫃中間,一個隱密狹小的隙縫,老婆低頭一看,真的塞在那裡!! 我和老婆面面相覷,不知道小澤澤是真的懂事了還是運氣好? 不過我希望是前者啦,反正問他真的假的,他也答不出來。
2009年2月2日 星期一
2009年1月22日 星期四
建構式與解構式
預設建構式與解構式有許多的地雷,首先,如先前所說,C++在你沒有撰寫此兩個函式的時候,會自動生成,那麼因為建構式支援多載,預設解構式還會生成嗎? 答案是不會的,以下以範例說明:
class ClassHasDefaultConstruct
{
};
class ClassHasNoDefaultConstruct
{
public:
ClassHasNoDefaultConstruct(int i){}
};
我們可以注意到第二個範例,有寫建構式但不是預設建構式(沒有任何參數的),此時C++就放棄幫你建立預設建構式了,可是有時候,我們在使用STL函式庫的時候,強迫要求你設計預設建構式,所以ClassHasNoDefaultConstruct這個類別是不能為STL容器所用。
這裡有個小伎倆,把有參數的建構式當成預設建構式用,利用預設參數的方式:
class ClassHasNoDefaultConstruct2
{
public:
ClassHasNoDefaultConstruct2(int i=0){}
};
使用建構式有一些注意事項,如果你的類別支援繼承,在建構式中,不可呼叫虛擬函式,因為,物件建構的順序是:基底類別建構式完成後,然後才是衍生類別建構,所以在基底類別建構式中呼叫虛擬函式,永遠是基底類別的函式
其次養成習慣,最好在建構式中,給予所有變數初值,避免執行期間產生錯誤,因為一般而言,C++在除錯版的時候,會自動給定變數初值(0, NULL),而Release版不會,變數值是看當時配置的記憶體資料內容而定,所以典型的程式描述是"我Debug版跑起來都沒錯,但是Release版就是會當機..."。
class BadSample
{
private:
char *ptr;
public:
BadSample(){};
~BadSample()
{
if( ptr ) delete ptr; // 當機
};
};
第三是當類別資料copy涉及記憶體時,永遠要寫複製建構式與operator =(); 或者,類別不允許這兩個函式的執行,原因很簡單,以下以程式說明之:
class CopyConstructor
{
private:
char *ptr;
public:
void ANewFunction()
{
ptr=new char[1024];
}
~CopyConstructor()
{
if( ptr ) delete[] ptr; // 假設建構式有把ptr=NULL;
}
};
以下例子解構時都會當機,因為ptr會被釋放多次:
CopyConstructor a;
a.ANewFunction();
CopyConstructor b(a); // b物件刪除的時候,ptr釋放一次,等到a物件刪除時,又一次
CopyConstructor c;
c=a; // 同上
所以要嗎你把類別這麼寫:
class CopyConstructor
{
private:
char *ptr;
public:
void ANewFunction()
{
ptr=new char[1024];
}
CopyConstructor(const CopyConstructor& c )
{
ptr=new char[1024]; // 各自配置各自的記憶體
memcpy( ptr, c.ptr, 1024 );
}
void operator =(const CopyConstructor& c ) // 簡化起見,不討論回傳值
{
ptr=new char[1024]; // 各自配置各自的記憶體
memcpy( ptr, c.ptr, 1024 );
}
~CopyConstructor()
{
if( ptr ) delete[] ptr; // 假設建構式有把ptr=NULL;
}
};
要嗎你把類別這麼寫:
class CopyConstructor
{
private:
char *ptr;
public:
void ANewFunction()
{
ptr=new char[1024];
}
CopyConstructor(const CopyConstructor& c )
{
ASSERT(0); //給他當機
}
void operator =(const CopyConstructor& c ) // 簡化起見,不討論回傳值
{
ASSERT(0); //給他當機
}
~CopyConstructor()
{
if( ptr ) delete[] ptr; // 假設建構式有把ptr=NULL;
}
};
解構式的注意事項是,如果想讓後續類別繼承,一定要用virtual,讓繼承解構的順序正確,還有,同建構式般,不可以在解構式中,呼叫虛擬函式,因為解構是繼承物件先解構,然後才基底物件,衍生物件已經不見了,基底物件去哪呼叫正確的虛擬函式呢?
2009年1月21日 星期三
澤澤的話語
ㄅ字頭的字: 爸、不要、布布
ㄇ字頭的音: 媽、嬤、馬
ㄉ字頭的音: 丟、到
ㄓ字頭的音: 找不到 (超順,一口氣唸完...)
ㄐ字頭的音: 舅舅(尾巴還會可愛的拉高音調),舅媽媽(一定要多個媽)
ㄧ字頭的音: 要、姨、姨嬤、衣服、衣架
ㄨ字頭的音: 我
....
那日心血來潮,發現他會說"我"、ㄧ字頭的音,想說把這些組在一起,多個"是",多個"澤",就可以說出"我是乙澤"這個令人充滿驚奇的句子。
開始了,他很奇怪地跟著我說了,"我"..."素".....,歐買尬,原來我兒子有嚴重的台灣國語,老婆在旁邊大笑,澤澤跟著笑起來,"哈哈哈...",混亂了好一陣子後,他才又專心跟著我念,"我...素...乙...",乙字發的不是很準,有點像一,不管如何,眼看著快要大功告成,我還沒說"澤"的時候,這小子忽然高興的大叫著,"衣服!","我素衣服...",啊? 我兒子是衣服?? 沒錯,到今天為止,只要我說,"我是.....",他一定接"衣服....."。
2009年1月16日 星期五
類別的設計
1. 變數封裝
基本上除了特別特別的需求,一般都建議你,把資料放在private區段,為什麼呢,這樣我才可以設計函式,管控如何存取這些變數值,避免變數被莫名奇妙的變更,造成錯誤時,難以追蹤的困擾,所以一個基本的變數,如果需要被外界存取,那需要一個設定函式,一個讀取函式,如果不需要,內部使用,那一個都不需要提供。
可以放在protected嘛?基本上,類別設計,建議你忘記有這個區間的存在,為什麼?protected區間可以被後續繼承類別存取,除非你有特殊用途,否則你埋下了一個炸彈,你如何知道後續繼成出去的類別,不會胡亂更動其值?不會又寫了一個函式,把資料public出去?
class NPC
{
private:
int m_iHp; // 生命力,除了初值化外,不應該由外界來操作這個變數
int m_iAcc; // 物理攻擊準確度,由企劃公式產生,更不應該由外界來操作
};
這樣的寫法,也斷絕了,系統開發時,有人妄想修改資料的念頭,減少將來系統長大時,潛藏錯誤發生的機會,真的需要處裡內部數值時,再以下列方法處理:
class AStrangeClass
{
private:
NPC *m_pNpc;
public:
void SetNpc( NPC *npc ){ m_pNpc=npc; }
NPC *GetNpc(){ return m_pNpc; }
};
關於全域變數呢,基本上建議,寫程式應該要把他視為罪惡,這意味著有一個完全不能改的機制在那邊,無法控制其變數名稱,只要跟這個變數有瓜葛的類別,形成了一個盤根糾結的系統,如同先前我提到的,少用繼承一樣的結果,將來改其中任何一個地方,潛在需要修改的程式是全部。可是全域變數一定會用到的,這個地方建議去參考"Design Patterns"的Singleton樣式,提供了一個將變數全域化,但卻被類別管控的方法。
2. 成員函式設計
成員函式設計,視目的而定,沒有一定的規範(除非專案本身的規定),這裡建議,請在函式放置位置的時候,多思考一下,要放在哪個區段,一般的建議是這樣,如果函式需要被外界存取,放public(廢話...),不需要的時候,請放private(廢話兩枚)。
是廢話嗎?回去隨便拿一個別人寫的類別看看,是不是有很多函式被外界呼叫後,如果數值亂填,可能造成系統錯誤之類的?你也許會反駁說,說明中有寫,這是內部使用的,錯誤是因為外界亂用啊?可是我要說,設計類別的基本原則,就是讓人容易使用,且不容易出錯,與其你放任函式public,裡頭寫一大堆exception,為什麼不把這些函式private起來,錯誤發生率也小多了不是嗎?
常在程式之間流傳的對答:啊,我知道bug發生原因了,是因為某某某呼叫了我一個函式,而這個函式是內部使用的,不可以這樣呼叫...,謹慎考慮多少可以減少潛藏可能發生問題的機會。
3. 注意別人類別的規範
接下來是使用別人類別的注意事項,不要企圖去改變回傳值的屬性,原因是來自C++轉型的方便,許多人喜歡把參數或是回傳值,亂轉型,變成其他的類型來操作,這本是無可厚非的事情,最明顯的就是const回傳值,但是,系統開發的時候,標明const,就是不希望你去改變這個數值,嚴重的說來,改變這個值,搞不好會發生嚴重的錯誤,而經常有人就愛去碰:
class A
{
public:
const char *GetTypeName(){ return "this is a type"; }
};
void main()
{
A a;
const char *ptr=a.GetTypeName();
strcpy( (char *)ptr, "this is not a type" );
}
編譯都對,執行的時候,就當給你看,所以(a) 當你看見函式回傳值有const時,不要嘗試去改變回傳值內容
(b) 當函式宣告有throw時,請處理exception
因為throw表示,函式內可能發生無法預期的錯誤,catch它,可以明瞭錯誤的原因,方便除錯
(c) 當類別裡面沒有虛擬函式時,不要嘗試去繼承它
如果你寫個類別,會給後續類別繼承,你會怎麼寫?一定是在裡面寫一些虛擬函式,暗示後來的程式師,這幾個函式可能以後會有其他的寫法,如果你寫個類別,沒有任何虛擬函式,那表示什麼?不想被別人繼承?功能已經完備?不論哪個答案,都不該再有類別繼承它了。
除此之外,還有一個原因,虛擬解構式,在繼承情況下,解構的記憶體才會正確的釋放,你的類別什麼虛擬函式都沒有(含解構式),繼承它,未來背負著一個memory leak的風險。
(d) 正確的繼承
假設,CHuman是個基底類別,CHuman人物會動、可裝備、會說話....,假設遊戲中出現了一種東西叫"假人",人類經常習慣用名稱將之歸類為人類的一種,所以他該繼承CHuman,然後把所有不能用的功能重寫。你做了什麼事?把一個功能齊全的人類別,降級成什麼都不會的假人?只因為它長得像人?或者應該由一個物品,開始去繼承,增加功能比較適合?
這裡提一個繼承基本上的判斷標準: 假人是不是一種人? 意思是,人類會什麼,應該假人也要會什麼,少把類別寫成,假人是人的一種,但是他太多不會這不會那時...,大概就是錯誤的繼承關係了。
2009年1月11日 星期日
變數初值
初值化(Initial)和給定數值(Assign)
(a) 什麼是initial?變數宣告的時候,直接給了數值,像是:
int a=10;
(b)什麼是Assign?
變數宣告完後,再另外給定數值,像是:
int a;
a=10;
這兩種寫法有差異嗎? 對build-in類型,像是int, float, ...沒有差別,但是對物件而言,就決定了一點點效能上的差別了,我以一個類別A來解釋:
class A
{
}; // 什麼事都沒有的類別
使用assign你必須先宣告一個物件 A a;
此時,物件已經執行了一次建構式,把內部數值初始一次,你也許會問,類別中沒有建構式啊?何來呼叫建構式? 答案是,類別雖然沒有設計建構式,但是C++標準中有幾個函式你不需要寫,C++都會幫你生出來,而你寫了,就不會幫你產生,分別是:
預設建構式: A::A(){}
複製建構式: A::A(const A &a ){}
解構式: A::~A(){}
指定運算元: A::operator =( const A& a ){}
所以當你下A a;a=b;這樣的程式時,已經執行了一次建構式,之後再一行assign,物件又會執行一次"指定運算元",所以你跑了兩次物件的函式。
而Initial呢? 看起來A a=b; 這樣的程式碼,同樣是執行一次建構式,然後再執行一次指定運算元?答案是,編譯器直接執行複製建構式,所有數值一次搞定,比起Assign快了一倍,也許不多,但是,如果你的物件內部,變數數量超多,一個assign需要執行的指令碼很多,那也許就有點可觀了:)
說到建構式,也許有人會問,他看過以下兩種程式碼,有差別嗎?
A::A()
: a(120), c(0.3)
{
}
A::A()
{
a=120;
c=0.3;
}
答案依照結果論而言,沒有差別,但是同樣潛在一點點效能上的差別,首先第一種方法A()後面接著的":a(...." 那行稱為 "member initializer list",專供給物件內成員變數的初值化,與呼叫基底類別的建構式所用,在這個時候,物件的記憶體還在配置中,C++會一邊配置記憶體,一邊把你指定的數值塞進去,所以,變數給定數值的次數只有一次,而下面那種A::A(){ ... } 的方式呢? 此時記憶體已經配置完成,變數數值已經給定過了一次,而此時又再設定一次,所以跑了兩次數值給定的程序,所以會慢一滴滴。
member initializer list這麼好用,有什麼限制嗎?
有的,指定順序要跟變數宣告的順序一致,原因說過,C++會一邊配置記憶體,一邊給定數值,如果順序不對,以下的程式就可能出錯:
class A
{
vector
int size;
A() : buf(size), size(20)
....
};
你希望配置大小為size的vector
class A
{
int size;
vector
A() : size(20), buf(size)
....
};
澤澤的異想世界
2009年1月8日 星期四
0.01秒的代價
首要工作呢,就是要蒐集遊戲中的資料,來推估一些準確度上的特性,以作為分類的依據,因為不方便找開發公司要玩家資料(一方面開發公司也會以千百種理由拒絕你),因此腦筋動到駭別人程式的方面。
從九月到現在,駭客程式寫了一個多月,難歸難,總是學到一些東西,推算了一些可能,給老師打包票,誤差應該在0.033秒內,老師面有難色,他委婉地希望壓到0.01秒以內。
10月起就開始撰寫在別人家程式裡面,錄製玩家資料,與推算正確時間點,那是瞎子摸象的過程,我怎麼知道對方程式怎麼寫? 怎麼取時間? 標準時間又是什麼? 程式邏輯改了又改,每測一次單位時間是5分鐘,因為每次開始前,都要經過長長的更新時間,不能用除錯工具,當了不知道為什麼,錯了只能從LOG去猜,要怎麼調整,都是為了那0.01秒。
摸索來到了12月,沒錯,一個月內,只在喬公式,猜測這邊可能加個係數,那邊可能多除個4,無聊且痛苦的日子,誤差一直在+-0.033秒左右徘迴,後來問到指導教授一句: "對方遊戲公司有跟我們合作,所以可以去找他們解答...",一時間愈哭無淚,我怎麼不早問,白白浪費了這麼久,花在猜測的時間上。
現在1月了,情況如何? 是的,有改善,所有錄製時間的誤差最大也不超過0.01秒,我過關了嗎?
沒有,原因有兩個
(1) 不要忘了,蒐集資料的程式只是第一步,我的論文書面資料隻字未動
(2) 老師很好心,找人與我共同研究,他做分析,但是0.01秒關係到按鍵分數的評分一個等級,會造成論文結果的誤差
所以需要把這個0.01秒的誤差解決...
學校論文繳交時間是2/2日,必要條件是,通過口試+繳交總圖書館4x頁論文正本,扣除過年,好了,只剩下十來天,我這裡先上演mission impossible4了! 結果這0.01秒的代價,就是再花半年的時間+一學期註冊費60,000元+沒辦法專職工作的損失.....好貴的0.01秒啊
2009年1月7日 星期三
少用繼承
class AI
{
private:
virtual void FSM(); // AI類別實做FSM
};
class Monster : public AI
{
private:
virtual void FSM(); // Monster類別改寫原來的函式
};
但就"程式碼再利用的"目的而言,事實上,我們常用的方法還有一種,"組成",利用物件彼此間的協力,完成特定工作,譬如,老闆要求去做一件A、B君都只會做部分功能的工作,依照繼承的觀念,是否要生成第三個類別C,繼承A、B的能力,然後改寫部分關聯而增加的程式碼? 然而我們是不是可以換個方向思考,我可以設計個類別C,工作交代C,C會先請A君做完他會做的,然後B繼續接手,一樣可以完成工作,與繼承兩者,事實上複雜度一樣,都需要3個類別,但是程式維護的小心程度卻是不可相提並論,程式如下
class AI
{
public:
void FSM();
};
class TimeControl
{
public:
void DoSomething();
};
用繼承:
class Npc:public AI, public TimeControl
{
void DoSomething(); // 這裡遭遇第一個難關
// 多重繼承,要小心AB的成員變數、成員函式設計關係
};
用組成:
class Npc
{
private:
AI ai;
TimeControl tc;
public:
void DoSomething(); // 我可以在這裡,靈活運用ai, tc物件的函式達成老闆目標
};
你可以看見,往後ABC三個類別的修改,相依性是否會降低很多很多,說白話,只要AB類別確定功能正常,發生BUG一定就是C,A類別新增怎樣的功能,不會影響C或B的運作,同理C增減怎樣的函式,AB都不會影響,而使用繼承呢? 就算你確定ABC三類別功能都正確,卻有可能因為繼承的關係,造成邏輯錯誤,像是C發生BUG的原因,是因為C修改了一個變數值,然後是A或B非常重要的變數,不允許由外部變更的(這裡有個小伏筆,避免修改到別人家的變數,變數請用private封裝...)。
加上有許多程式濫用繼承,一個遊戲專案裡頭隨便都成千上百個類別,加上彼此繼承的關係,專案像是N坨大肉粽樹,盤根錯節,某Z類別有BUG,原因卻是在千里之外八竿子打不到關係的A類別造成,改了A,卻連鎖反應要把某個肉粽樹的函式都要改過,想到就累人啊,所以,有得選的情形下,請少用繼承多用組成。
再舉個生活上的例子,老闆要設計個簡報在客戶面前推銷商品,為了達到目標,他需要繼承文書小姐的能力+產品部門對商品規格特色了解的能力+簡報能力+....,或者,他可以把公司內文書小姐、產品部經理、行銷推廣部經理叫來,說出需求,接下來大家就自動會把事情搞定,而老闆則可專心挑毛病去,愛用繼承的,通常工作累得要死,愛用組成的,通常是老闆,你選哪個?
2009年1月1日 星期四
STL5.2.1版編譯與VC安裝
1. 首先,當然需要取得STLport原始檔案
請到http://sourceforge.net/projects/stlport/去下載最新版本,並將之解壓縮到一個你覺得名字夠響亮的資料夾下,就"C:\mighty-STL"好了,為了方便,以後稱這個資料夾為"[STLport]"。
2. 設定編譯變數
用來指定VC DOS指令的相關變數、執行檔路徑等
(2.a)開一個DOS視窗(應該不用教了...)
(2.b)然後執行你VC資料夾下面的一個批次檔
2003版,沒有意外,是這樣"C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\vsvars32.bat"
2008版,沒有意外,是這樣"C:\Program Files\Microsoft Visual Studio .NET 2008\Common7\Tools\vsvars32.bat"
記得喔,在DOS視窗下,頭尾兩個 " 都要打喔,不然會被視為錯誤的指令
3. 編譯STLport
(3.a) 設定STLport編譯環境
DOS視窗路徑移動到[STLport]下,執行"configure 你的vc版本代號",代號清單如下:
msvc6 VC6.0
msvc7 VC.net 2002
msvc71 VC.net 2003
msvc8 VC.net 2005
msvc9 VC.net 2008
例如,我的VC是2008,指令就是: "configure msvc9"
(3.b) 開始編譯
DOS視窗路徑移動到[STLport]\build\lib
執行 "nmake clean install"
正常的話,開始等約數分鐘,可以在[STLport]\lib\發現編譯好的函式庫了
STLport編譯預設為多行緒的函式庫,如果想要產生非多行緒的函式庫,請在configure 時,加入"--without-thread"選項,不過,非SingleThread的專案,就link不過了,因為兩者編譯出來的函式庫名稱相同,所以想要兩者共存,需要自行指定函式庫。
4. VC環境設定
(4.a) 新增include資料夾 [STLPort]\stlport,需要放在第一個,避免VC編譯時仍然採用舊版隨機送的STL
(4.b) 新增lib資料夾 [STLPort]\lib,先後順序無所謂...
(4.c) 執行時預設VC自動連結相關函式庫,除非你太閑,把[STLport]\stlport\stl\config\user_config.h中定義 _STLP_DONT_USE_AUTO_LINK打開,每個專案都要定義_STLP_VERBOSE_AUTO_LINK一次
(4.d) 如果你不想使用STLport了,可以於編譯時定義以下兩值,強迫使用STD版(對某些情況,如怎麼編譯就是不會成功的,我有一個MFC專案就是如此...文件說可以定義_STLP_USE_MFC...)
#define _STLP_DONT_REDEFINE_STD
#define _STLP_WHOLE_NATIVE_STD