2009年7月21日 星期二

我要畢業了~~

7/21是我碩士論文口試的日子,在眾老師的用心提點,總算完成碩士成就,冠上碩士稱號,智力+3,可惜我偏愛肌肉系職業,IQ可能沒什麼用,哈哈哈...,不管如何感謝這些年來所有給予鼓勵的老師、同學們、讓我三年來有飯吃的DCI同仁們、還有感謝老婆孩子,當然,要感謝的人太多了,只好謝天吧[1]

7/21也是老婆生日的日子,所以今天算來是雙喜臨門,考完經過樂透站,順手買了幾張,來個三喜臨門吧~~

[1]..............

2009年7月7日 星期二

迫在眉梢

剩下兩周就要碩士論文口試了,現在最擔心的竟然不是論文還沒寫完+還沒校稿,而是: 口試當天要買什麼孝敬委員們啊? 咖啡? 冷的熱的? 要加蛋糕甜點嗎? 我會不會太奇怪啊?

2009年6月8日 星期一

外掛之後

碩士論文做的是關於外掛的研究,為了抓外掛的資料,忽然很感佩有"再生"功能設計的遊戲,例如"Quake系列",有很多玩家會把自己的遊戲過程PO上網,省了我很多找logs的時間,可是,對於沒有的絕大部分遊戲怎麼辦?起源於老闆的一句話"你有沒有辦法取得XX遊戲的按鍵紀錄?",當時想說,取按鍵紀錄?好像只要偷偷利用SetWindowLong函式,然後把處理訊息的WndProc換成自己的,接下來不是只要等在這個函式中就好了?

得意算盤打得精,誰知道這是一個很長的路,走了大概兩個月吧,要讓SetWindowLong可以執行,需要把自己的程式注入對方程式中,寫到最後,變成在寫外掛了,換掉WndProc? 簡單,五分鐘搞定,第一個犧牲的對象是某跳舞遊戲,但是為了精確對時,讓我延畢了一學期,本來以為不會再碰外掛的事了,忽然老師又給了個想法"你可不可以利用LUA抓取山口山的部分截圖?",想想,LUA?我會,抓圖?簡單,只要取得山口山的Direct3D繪圖裝置就搞定了,好吧,就接下來了。

這次,更累了,攔不到山口山的Device!!!

Device不是從IDirect3D9生出來的嗎?怎麼山口山這麼奇怪,硬是另闢蹊徑,問遍了GOOGLE,有文章提到可以從COM物件創建方面攔截,但是...失敗。老師說可以去掃描記憶體,我試了,在山口山程式記憶體中翻山越嶺,守著電腦執行4個多小時後,放棄了,就算成功,誰會願意每次執行山口山都要等個幾個小時? 好幾個禮拜,對著山口山這個大怪物,執行又跳出,跳出又不死心繼續執行,最後發現,我快到了極限,想不出任何的方法,只好準備向老師自首。有一句話是對的,山窮水盡疑無路,柳暗花明又一村,正當要放棄的時候,一個很偶然的錯誤,正當我的外掛開始跑,而不小心把山口山切成錯誤的視窗模式,把它改回全螢幕獨占模式時,莫名奇妙忽然抓到了繪圖裝置,一切都搞定了,好想哭!!後來修修改改,用了一個很醜的方法,但是總是可以透過LUA來自動執行抓圖,沒圖沒真相...


掛人程式,感覺沒那麼難,真的難的是,開了一扇門後面還有無數扇門,要跟原設計者鬥智鬥力鬥時間,我覺得,最後應該還是原設計者落下風,為什麼? 雖然我很累,花了好幾個月,可是他們更慘,花了好幾年來完成遊戲,被人破解,也不能怪他們了,那要怪誰? 微軟的濫OS吧,可是我得感謝微軟,沒有這個爛OS,我恐怕要等頭髮花白才有辦法畢業吧~~

2009年5月25日 星期一

疊疊樂~

乙澤最近超愛講話,抓著你的手就是一連串地劈哩啪啦地說著,東一句"阿爸...",西一句"阿爸...",有時說不出所以然還是繼續叫著"阿爸",某天,他叫我之後又卡住了,我故意也叫他名字,兩人就這麼"阿爸"來"乙澤"去,忽然他改變我的稱謂變成"阿爸爸",然後開始開始點名,阿媽媽、媽咪咪、阿公公...,舅媽本來就被他稱為舅媽媽,多一個字,變成舅媽媽媽,連自己也慘遭毒口,變成"乙澤澤",點完名,換鴨子子、手機機...,最後看見一整個興奮的小孩笑得超high。

2009年5月16日 星期六

3:8

小澤澤不喜歡吃東西(目前只剩多多是他不拒絕的)...,現在已經快要2足歲了,體重從出生到現在,一直沒破10KG,説他3:8,他反而開心地大笑,有點不知道該怎麼辦,有人說是因為他正在長牙,不舒服,所以不吃,有人說給他餓久一點再餵,不論如何,心裡還是很擔心...。

2009年4月24日 星期五

澤澤閉嘴...


可愛的澤澤,玩著玩著,嘴巴有時候會張大大,忘了閉起來,這有失帥哥的風範,因此發現的時候,就會叫他嘴巴閉閉,這天發現他嘴巴又張開,照下了這張超可愛的照片。
(ps 他是故意這樣閉嘴巴的)

本末倒置

我經常遇到以下的情境

1. 系統測試不如預期
2. 老闆不爽,質疑沒有監督好品質
3. 檢討
4. 改版
5. goto 1

無窮的迴圈,老闆失去耐心,員工失去鬥志,我在一邊看,只有一個想法,大多數人都應該有這樣的經驗,
去餐廳點菜的時候沒意見,等到菜端上桌,東挑西挑,說要甜一點、鹹一點、淡一點,可是沒人肯承認,最初的那道菜根本不是你想要的,同一道菜,廚師怎麼改,永遠不能滿足你。

這點就像1~5的迴圈,大家都只在事後檢討這裏打轉,沒有人想去事前分析設計一下如何進行,問到系統想要給玩家的核心概念是什麼? 畫面風格概念是什麼? 某個地圖的特色是什麼? 介面的操作的特色是什麼? 答不出來,這難怪介面一改再改沒人愛,場景東加西加越加越顯雜亂。

也許你會說,因為東西做完了,在測試,這種情況難免,但是我要說的是,為什麼事前不先把最後的成品可能長相先溝通討論,不要在最後的時候,才一句"你怎麼做成這樣?",把責任怪到別人頭上?

以自我本位而言,沒人知道你內心想什麼,知道你想什麼,沒人知道你的標準何在,因此每次交付工作給別人,你總是感嘆天兵到處都是,所以,我覺得,以後做事之前,是不是先應該找方法,確認彼此之間,對工作的內容認知是一致的,這樣絕對可以省卻很多1-5迴圈發生。

2009年3月11日 星期三

澤澤的對話

澤澤越來越可愛,一張小嘴咭哩呱啦地,已經可以容易的組成6個字以內的短句,受到人家的恩惠,不止會說"謝謝",連對方稱謂都加上了,又甜又討人喜歡,有時候對阿姨啊姊姊啊,分不太出來亂叫一通,被叫姊姊的如果是阿姨等級,嘴裡忙更正澤澤,但是看得出來,心裡頗樂的,但是阿姨跟姨嬤被叫錯,就尷尬了,還要跟對方說不好意思。

最近大家都發現了一個令人擔心的現象,就是他說話不知怎地,開始會大舌頭,一句"阿爸",叫成"阿阿阿阿阿爸",剛好六個字,害得我每次都得正經地糾正他,老婆這地方倒是看得很開,解釋說,是因為小乙澤一次想說太多字,咬在嘴吧裡面,才變成這樣,希望如此啦....。

經濟愈差 病毒愈多 -每10部電腦就有1.1部成為非自願的殭屍電腦

轉載: http://marktzen.blogspot.com/2009/03/1011.html#links
這是我研究所同學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"變數"

最近被問到,類別裡面的const變數撰寫方式,乾脆整理const的一些規則,首先,const應該是常數不是變數,所有的常數只能被初值化,不能被賦值,所以只能寫在建構式的initial-list中,一般常數的設計方式有以下幾種:

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日 星期一

澤澤的歸納法

小澤澤會開始認東西了,牛牛啊、狗狗啊、球球啊,還有,他會說英文的Apple,這實在是令人神奇與感動,只是他現在對事件的分辨還一知半解,經常讓人又覺得好笑,譬如,除了牛牛之外,四隻腳的都是狗狗,圓圓的東西都是Apple而且具有球球的屬性,所以拿水果給他玩要小心,他拿在手裡,第一件事就是用稚嫩的童音,可愛地大喊一聲"丟!",然後水果就摔在地上滾來滾去,看他拍手大笑,大人可是哭笑不得,只是我們要吃摔爛的水果,這時後還真羨慕不吃水果的老婆。

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日 星期五

類別的設計

類別是C++實踐物件導向程式的基礎,一般人對類別設計應該都有了些基本的概念,這篇想要以系統開發的角度,來強調類別設計與使用的注意事項。

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日 星期日

變數初值

程式寫這麼久,以前經常會遇到,"啊? C++不可以這樣嗎?"或者是"這樣寫有差別嗎?"的驚訝,想說這可能不只是只有我有的感覺,因此把這些東西分享出來,給大家做參考,你也可以參考Effective C++這本書籍:

初值化(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 buf;
int size;
A() : buf(size), size(20)
....
};

你希望配置大小為size的vector的陣列,但是在 呼叫buf(size)建構時,size記憶體還沒配置出來,根本不知道size的數值,而,size(20)初值後,buf(size)已經執行過了,所以會發生錯誤,所以正確的順序:
class A
{
int size;
vector buf;
A() : size(20), buf(size)
....
};

澤澤的異想世界



澤澤是我的兒子,才一歲半,帶出去,每個稍有審美觀念的路人,都會卯起來說好可愛,這對身為父母的我們而言,除了不擔心未來找不到老婆之外,也滿足了我(不含老婆)無聊的虛榮心。



但是,這小子經常不知哪裡學來一些怪把戲,遇到尷尬的時刻,嗯的一聲,用一根指頭胡亂的指著一個方向,圖片裡那兩個小妹妹,很認真地看著我兒子指的方向許久,後來聽說,這個所謂的怪把戲是我教的...。

最近他迷戀上有毛的質料,恰巧冬天來了,看到有阿姨身上穿有毛絨絨的衣裳時,都會舉起他的小指頭,"毛毛、毛毛"地大叫,因為模樣可愛,所以大人都會友善地把毛絨絨的部份,大方地給他玩,可是他不喜歡用手摸,最後都會把小臉湊到上面,小心翼翼輕輕地摩擦著臉...,真擔心剛剛他哭過,眼淚鼻涕會殘留在上面....,只是不知情的阿姨們還是很高興地稱讚著可愛。

可愛歸可愛,他發起脾氣來,也是讓人又愛又恨,不管三七二十一地右手猛搖,飆著眼淚,"不要、不要",地哀叫著,一般這時候,所有人都會投降的,只能順著他的意,可是有時候像是喝牛奶、洗澡、睡覺等,大人們可是不能退讓的,有一次晚上,喝完牛奶該睡了,不知怎麼就是不肯睡,發起脾氣來,又是哭又是鬧,猛搖著右手,使出他拿手的"不要不要~",正當無計可施的時候,我老婆靈機一動,把他抱在一張毛絨絨的毯子上,只見他心滿意足地把小臉靠在上面,安安靜靜地睡著了,又恢復成人見人愛的小天使模樣。

2009年1月8日 星期四

0.01秒的代價

我的畢業論文,跟跳舞機遊戲相關,就是那種只要聽音樂,然後按上下左右的遊戲,論文論什麼呢? 就是眼睛閉起來,只看上下左右按下的時間,跟正確時間的相關關係,來推測現在是BOT在玩還是人類在玩? 聽起來很玄,但是經過兩年學校的洗禮,發現方法還頗為容易理解,就是線性代數與資料探勘的應用,此篇不是技術文章,所以不用太擔心我會介紹這兩者。

首要工作呢,就是要蒐集遊戲中的資料,來推估一些準確度上的特性,以作為分類的依據,因為不方便找開發公司要玩家資料(一方面開發公司也會以千百種理由拒絕你),因此腦筋動到駭別人程式的方面。

從九月到現在,駭客程式寫了一個多月,難歸難,總是學到一些東西,推算了一些可能,給老師打包票,誤差應該在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日 星期四

好的遊戲程式系統簡介

日前發於DCI的文章...
關於遊戲系統架構的一點淺見

STL5.2.1版編譯與VC安裝

STL安裝:

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