2012年4月4日 星期三

[筆記] 全文轉錄 - 好的遊戲程式系統簡介

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] 全文轉錄 - 好的遊戲程式系統簡介
 時間  2012/04/04 Wed 17:53:15
───────────────────────────────────────

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

http://forum.dci.org.tw/viewtopic.php?t=519

文章主題: 自問自答: 怎樣才是好的遊戲系統? (一個技術與非技術的討論)
1. 符合玩家需求(非技術性,歡迎參與討論...)

文章主題: 2. 應用物件導向設計系統(技術篇)

文章主題: 3. 最終章 封閉系統但是保持系統擴充性


內容太多了…懶得貼了…有興趣的自己去看吧…0rz

--

     身高不是距離
                   技巧不是問題
                                 只要有"心"
                                             人人都可 定‧三‧米


--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表

[筆記] 全文轉錄 - 類別的設計

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] 全文轉錄 - 類別的設計
 時間  2012/04/04 Wed 17:50:12
───────────────────────────────────────

http://imandrewliao.blogspot.com/2009/01/blog-post_16.html

類別是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,然後把所有不能用的功能重寫。

    你做了什麼事?把一個功能齊全的人類別,降級成什麼都不會的假人?

    只因為它長得像人?

    或者應該由一個物品,開始去繼承,增加功能比較適合?


    這裡提一個繼承基本上的判斷標準: 假人是不是一種人?

    意思是,人類會什麼,應該假人也要會什麼,少把類別寫成,假人是人的一種,

    但是他太多不會這不會那時...,大概就是錯誤的繼承關係了。



--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表

[筆記] 全文轉錄 - 少用繼承

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] 全文轉錄 - 少用繼承
 時間  2012/04/04 Wed 17:46:41
───────────────────────────────────────

http://imandrewliao.blogspot.com/2009/01/blog-post_07.html

物件導向程式設計中,為了提升程式碼的再利用性,提供了繼承這樣的架構給我們使用,

只要看不順眼的類別,就可以用繼承改寫舊功能,達到擴展的目的,範例如下:

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,卻連鎖反應要把某個肉粽樹的函式都要改過,想到就累人啊,

所以,有得選的情形下,請少用繼承多用組成。


再舉個生活上的例子,老闆要設計個簡報在客戶面前推銷商品,為了達到目標,

他需要繼承文書小姐的能力+產品部門對商品規格特色了解的能力+簡報能力+....,

或者,

他可以把公司內文書小姐、產品部經理、行銷推廣部經理叫來,說出需求,

接下來大家就自動會把事情搞定,而老闆則可專心挑毛病去,

愛用繼承的,通常工作累得要死,

愛用組成的,通常是老闆,你選哪個?



--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表

[筆記] 全文轉錄 - 類別裡面的const"變數"

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] 全文轉錄 - 類別裡面的const"變數"
 時間  2012/04/04 Wed 17:44:20
───────────────────────────────────────

http://imandrewliao.blogspot.com/2009/02/const.html

最近被問到,類別裡面的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)通常用於類別內,獨一無二的整數的常數值


--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表

[筆記] i++ 好,還是 ++i 好?

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] i++ 好,還是 ++i 好?
 時間  2012/04/04 Wed 17:41:35
───────────────────────────────────────

不負責任聲明:以下憑印象+google寫成,有錯誤很正常 0rz

              如果有人願意指出錯誤,我會很感謝您的 <(_ _)>

--------------------本文開始分隔線--------------------

下面是我們常用到的迴圈
for(i=0 ; i<? ; i++)
{
    (something)
}

大家常用 i++ 還是 ++i 呢?

其背後的運作模式為何?

T& opertor++ ()
{
    a = i++;
    a = ++i;
}

取址運算子 &
取值運算子 *

i++
    temp = (*this); // temp = i
    ++*this;        // i = i + 1
    return *temp;   // return = temp

++i
    ++*this;        // i = i + 1
    return *this;   // return i

簡單來說,i++ 背後會比 ++i 多複製了一次(我印象中聽到是兩次…0rz)

如果 i++ or ++i 的 i 只是單純的資料型態,如 int

那麼先加或後加就沒什麼太大的差別了

但是如果 i 是代表一個很大的物件

那麼多做的那些動作對於程式執行的效率就會有所影響

因此建議盡可能養成使用 ++i 的寫作習慣 (不過我已經寫了快七年的 i++ 了…0rz)

(對於ints 和 pointer,編譯器最佳化可將這額外的動作去除。但對於較複雜的
 iterator 型別,這個額外動作的潛在代價可能很高。)

--

     身高不是距離
                   技巧不是問題
                                 只要有"心"
                                             人人都可 定‧三‧米


--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表

[筆記] 變數宣告在什麼位置比較好?

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] 變數宣告在什麼位置比較好?
 時間  2012/04/04 Wed 17:38:43
───────────────────────────────────────

不負責任聲明:以下憑印象+google寫成,有錯誤很正常 0rz

              如果有人願意指出錯誤,我會很感謝您的 <(_ _)>

--------------------本文開始分隔線--------------------

以前的 C-style 寫法,希望變數都宣告在函式最一開始的地方

這樣的話,有宣告什麼變數都一目了然,非常的方便

如下所示

void main()
{
    int a, b, c

    a = xxx;

    b = xxx;

    c = xxx;
}

而且不論早宣告還是晚宣告,程式在 compile 的時候都一樣會將全部空間都撥出來給你

例如你整個程式宣告了三個 int 的變數

那麼不論這三個變數在最一開始就宣告了

或是在程式中段或尾段才宣告

compile 所撥出來的空間都是一樣的

那麼宣告在什麼時間點或位置有差別嗎?

經主管說明後,真的有差…

最好是在你要使用這個變數前再宣告,而且盡可能的給予初值

如下所示

void main()
{
    …
    …
    …

    int a = XXX;

    int b = XXX;

    int c = XXX;
}

不過我也不清楚給予初值最主要的目的是什麼,在網路上查了一下,
int x = 10;


int x;
x = 10;

效果是相同的
但後者彈性太大,而且在 code 海中,x=10 很容易被忽略掉。

另一個說法是:

最好在建構式中,給予所有變數初值,避免執行期間產生錯誤,

因為一般而言,C++在除錯版的時候,會自動給定變數初值(0, NULL),而Release版不會

變數值是看當時配置的記憶體資料內容而定

所以典型的程式描述是我Debug版跑起來都沒錯,但是Release版就是會當機...。

回到正題,這樣做有什麼好處呢?

1. 當你在維護程式時,你想要知道變數影響的地方,

    你只要從宣告的地方開始往下找就好,而不需要從函數最一開始的地方開始看。

2. 若宣告在函數開頭的地方,有時會無法給予初值,

    因為有些變數需要程式先執行某段 code 後才能給予初值。

3. 當程式中途有 return 結束的狀況時,如下所示

    void main()
    {
        int a, b, c

        a = xxx;

        return ???;

        b = xxx;

        c = xxx;
    }

    我印象中是說宣告變數時,背後都會去執行某一段 code

    因此如果一開始就把變數都宣告好,也執行了背後那段 code

    但是程式因為 return 結束或其他原因而沒有使用到這個變數

    那麼執行宣告 b, c 的 code 的時間就浪費掉了

    int 這種資料型態可能還好

    但如果是那種大物件的資料型態,花費的時間有時就會很可觀了

--

     接球會"噴"

我             舉球會"歪"              但是!   我算分超強 o(一︿一+)o

                         攻擊會"OUT"

--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表

[筆記] const & const_cast

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] const & const_cast
 時間  2012/04/04 Wed 17:35:48
───────────────────────────────────────

不負責任聲明:以下憑印象+google寫成,有錯誤很正常 0rz

              如果有人願意指出錯誤,我會很感謝您的 <(_ _)>

--------------------本文開始分隔線--------------------

在先前的文章中有提到

指定運算元:A::operator =( const A& a ){}

     盡量加上 const     b:因為有加 const 的關係,可以保證在 function 內,
               ↓       ↓                                      B 值不會被更改
T& operator = const (T& b)
                     ↑
                      &:reference 有加的話效率較高?

當我們希望函數中不要更改任何的物件時

可以使用 const 來限制


    void mem_function() const
or
    get_XXX() const



但是有時候我們在函數中不得不去修改某物件的值時該怎麼辦呢?


    get_XXX() const
    {
        if(temp > 0)
        {
            temp = ???;
        }

        return temp
    }

我們可以使用 const_cast

來解除 const 不得更改物件狀態或值的限制


可是,這樣就造成矛盾了不是?

原先我們是為了不要修改到函數裡面的物件狀態而限制為 const

但是現在卻又把它給解除了

這樣不是很容易就會出現 bug 嗎?


所以 const_cast 盡可能不要用,或是完全不要用

可以將要修正的值,宣告成

mutable temp

即可

http://caterpillar.onlyfun.net/Gossip/CppGossip/constAndmuttable.html

而且盡可能的使用 const ,可以讓程式更少出錯且易於維護


--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表
▋※ Modify: 2012/04/04 Wed 17:39:05

[筆記] 複製建構式 copy-constructor

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] 複製建構式 copy-constructor
 時間  2012/04/04 Wed 17:31:47
───────────────────────────────────────

不負責任聲明:以下憑印象+google寫成,有錯誤很正常 0rz

              如果有人願意指出錯誤,我會很感謝您的 <(_ _)>

--------------------本文開始分隔線--------------------

複製建構式:
A::A(const A &a )
{

}

前一篇文章有提到,C++標準中有幾個函式你不需要寫,C++都會幫你生出來,

而你寫了,就不會幫你產生,複製建構式是其中一個


那什麼時候會需要自己寫一個?

Ans:當有使用指標的時候,不然 copy 的會是 pointer,而不是 data

1. http://www.cnblogs.com/oomusou/archive/2007/01/14/620374.html

當類別資料copy涉及記憶體時,永遠要寫複製建構式與operator =();

複製建構式: A::A(const A &a ){}

指定運算元: A::operator =( const A& a ){}


不然在解構時,同一個運算元會被解構兩次而造成 code 掛掉

2. http://imandrewliao.blogspot.com/2009/01/blog-post_22.html

(第三點的內容)


另外印象中好像有提到在使用 copy-constructor 時

會配合使用 swap function 來做資料的交換

(不過我不知道原因為何…0rz)


如交換 a, b 資料 → a.swap(b)

temp(b)
this → swap(b)
return *this;


另外
    (a = b).function;

    a = b;
    a.function

是相同的

--------------------------------------------------

以下是第一個連結的全文轉貼,避免連結失效

(原創) 哪些地方會用到Copy Constructor和Assignment Operator? (C/C++)

C#、Java都沒有copy constructor,所以這對大部分programmer都很陌生

簡單地說,凡需要copy的地方,就需要copy constructor:


1.由copy-initialization方式建立物件。

    Ex.

    Foo foo1;
    Foo foo2(foo1);

    以上直接使用copy constructor。

    string s = "C++ Primer";
    Foo foo = Foo();

    此時是先由default constructor建立一個temporary object後,

    再由copy constructor將temporary object 『copy』給物件。

2.以by value的方式傳進function和由function return值。

    Ex.
    int Foo(int n);

    只要不是使用by reference的方式,就得使用copy constructor。

3.建立STL container中的element。

    Ex.vector<string> svec(5);

    先由string的default constructor建立一個temporary string object,

    再由string的copy constructor『copy』到vector中每個element。

4.由initialization list建立array。

    Ex.int ia[] = {1, 2, 3};

    先由int default contructor先建立temporary int object,

    再由int的copy contructor『copy』到array中。

    所以copy constructor其實是無所不在的,只是我們從來沒有發現,

    雖然C++ compiler會自己synthesize copy constructor,

    但也允許我們自己定義自己的Copy Constructor,這與C#、Java不同。


而assignment operator呢?

Foo foo1;
Foo foo2;
foo2 = foo1;

以上會執行foo2的assignment operator。


所以簡單的說,copy constructor和assignment operator都在做『copy』的動作,

資料是pointer,也就是動態資料時,就必須重新改寫,

否則只會copy pointer,而不是copy data。

--------------------------------------------------

以下是第二個連結的全文轉貼,避免連結失效


建構式與解構式

預設建構式與解構式有許多的地雷,首先,如先前所說,

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,讓繼承解構的順序正確,

還有,同建構式般,不可以在解構式中,呼叫虛擬函式,

因為解構是繼承物件先解構,然後才基底物件,

衍生物件已經不見了,基底物件去哪呼叫正確的虛擬函式呢?

--

     身高不是距離
                   技巧不是問題
                                 只要有"心"
                                             人人都可 定‧三‧米


--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表
▋※ Modify: 2012/04/04 Wed 17:39:15

[筆記] 如何宣告初始值?

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] 如何宣告初始值?
 時間  2012/04/04 Wed 17:09:57
───────────────────────────────────────

不負責任聲明:以下憑印象+google寫成,有錯誤很正常 0rz

              如果有人願意指出錯誤,我會很感謝您的 <(_ _)>

--------------------本文開始分隔線--------------------


一般人在宣告初始值時,大部分都採用下面的方式

T::T()
{
    a = XXX;
    b = XXX;
    c = XXX;
}

也有另一種做法如下

T::T()
: a(120), b(0.3), c(5)
{

}

這兩者有差嗎?以結果來看沒差。
但在執行效率上,後者會較快

因為前者的做法,等於後者做兩次
原因我也沒有辦法說得很清楚
大家可以去看這篇文章
http://imandrewliao.blogspot.com/2009/01/c-1.html
或是去參考 Effective C++ 這本書

重點是,後者在使用上有個限制,就是給初始值的順序要與變數宣告的順序一致
不然給值時會發生錯誤
原因同樣在上述 blog 文章中可以看到

--------------------------------------------------
以下為全文轉貼,避免連結失效

變數初值

程式寫這麼久,以前經常會遇到,"啊? 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(){}
複製建構式: A::A(const 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)
    ....
};


--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表

[筆記] 實務分享:當在 free(p) 時,程式出錯,問題何在?

 作者  ie945167 (龍蝦)                                      站內  Lobster
 標題  [筆記] 實務分享:當在 free(p) 時,程式出錯,問題何在?
 時間  2012/04/04 Wed 17:04:25
───────────────────────────────────────

不負責任聲明:以下憑印象+google寫成,有錯誤很正常 0rz

              如果有人願意指出錯誤,我會很感謝您的 <(_ _)>

--------------------本文開始分隔線--------------------

主管分享的經驗,我自己連 malloc, free 都沒寫過…悲劇 0rz

主管:「最近遇到一個問題,有個程式在 free(p)的時候掛掉,請問大家,原因為何?」

malloc():動態配置記憶體
free():釋放記憶體

p = malloc(100); //配置100個 byte 的記憶體位置給p,但不做初始化的動作


...
free(p); //掛掉 囧 why?

常見可能的原因:
1. p = NULL
2. p指標飛了,分配時的指向不一樣
3. free(p),p的值不是通過malloc()等函數得到的
4. p已經free過了
還有什麼原因?

我印象中,最後好像是 free 的空間大小與原本 malloc 所配置的大小不一樣
至於是怎麼查出來的呢?
可以看下面的解說

下面是一個記憶體空間

memory cookie   memory
 ______________________
|4 ~ 8 byte  | p      |
 ___________

              ↑
            pointer

(圖畫得不好請見諒…)

在指標(pointer)所指的位置前 4~8 個byte,有所謂的 memory cookie

好像是用來記錄後面的 memory 分配的大小

因此可以用 memory cookie 來判斷 free 的空間大小是否與原本配置的大小相同。

(主管用意在於讓我們知道有所謂的 memory cookie 這種東西的存在可以利用)


題外話,google得來:

malloc 和free 一定要成對出現,有malloc 的,一定要有free,

且注意malloc 分配的大小,防止破壞管理分配記憶體的資訊。

當陣列ar作為參數時,在函數體內,ar只是一個指標,

所以 sizeof(array)=4,而非陣列所含元素個數+1.

--

     接球會"噴"

我             舉球會"歪"              但是!   我算分超強 o(一︿一+)o

                         攻擊會"OUT"

--
▅◣ Origin:  謠 言 報  bbs.csie.fju.edu.tw
▋◤ Author: ie945167 從 219-85-0-189-adsl-nei3.dynamic.so-net.net.tw 發表