[資料結構] Stack in D.

這是例用 D 語言的 Template 功能以及動態陣列實作出的簡單的 Stack,沒有什麼實際的用途,只是復習資料結構和測試 Code Snippet 這個 plug-in 的功能而已。不過從這個例子,可以看出來 D 語言比起 C++/Java 是一個相當方便和注重實用的語言,放棄了與 C 完全相容的包袱,但又不像 Java 在設計上把自己綁得太死(例如完全放棄指標),讓 D 可以同時定位在 System/Application Programming 兩塊上。

module stack;

private import tango.io.Stdout;
private import tango.text.convert.Sprint;

// 簡化過的 Template 語法,比 C++/Java 順眼的多
class Stack(Type)
{
    Type [] content;  // 存放實際資料

    // 陣列不應該是負的,這裡面的條件並須在除了執行 member function 的時候外,
    // 任何時間點都必須成立。
    invariant () {
        assert (content.length >= 0);  
    }

    // 不是很好的測試方式,我要再去看看 unit test 的書,
    // 軟體工程沒認真上課。orz
    unittest {
        int value = 0;

        // 將 Template 實體化,這個 class 裡的 Type 會全部被代換成 
        // int 這個資料型態。
        //
        // 和 C++/Java 不同,注意宣告和實體化的語法有所不同,所以
        // 可以一看就知道哪個是宣告,哪個是實體化。
        //
        // 另外據說這樣子的語法讓 compiler 比較好 parse,也不會有
        // 語意上的混淆。
        // http://www.digitalmars.com/d/1.0/templates-revisited.html

        // 在變數有初始值的時候可以不用宣告型態,編譯器會自動處理,和
        // 下一行是完全相同的,可以少打一些字。
        auto stack = new Stack!(int);       
        //Stack!(int) stack = Stack!(int);

        stack.push(10);

        // 注意可函數沒有參數時,不用加括號,像是 properties 一樣
        // 注意 isEmpty/size 都是函式,而不是 data memeber
        assert (stack.isEmpty == false);
        assert (stack.size == 1);

        // value 的內容會被改變,因為 pop 裡第一個參數前加了 out 修飾字
        stack.pop(value);
        assert (stack.isEmpty == true);
        assert (stack.size == 0);
        assert (value == 10);
    }

    // Typesafe Variadic Function
    // 內建的變動數目參數列表,可以像陣列一樣存取
    public this (Type [] data...) {
        content = new Type[data.length];

        foreach (int i, Type element; data) {
            content[i] = element;
        }
    }

    //! 傳回自己是抄 Tango 的 Stdout 的做法,聽說這又是抄 functional 
    //! programming 還是 Ruby 的做法(Beyond Java?忘記在哪看到的)。
    public Stack!(Type) push (Type element) {
        content.length = content.length+1;   // 直接把陣列的長度加長
        content[content.length-1] = element; // 這樣就多一格可以放資料

        return this;
    }

    //! 不利用指標,但可以達到指標要做的,語法又比 C++ 的 reference 更容易
    //! 了解。
    //!
    //! 取出的資料放在 element 中,注意前面加了一個 out 修飾詞,表示這個變
    //! 數是用來當輸出,並且會改變原來的變數內容。
    public Stack!(Type) pop (out Type element) {
        element = content[content.length-1];
        content.length = content.length - 1;

        return this;
    }

    // 轉換成輸出字串用,直接丟到輸出函式時會顯示這個函式回傳的字串
    public char [] toUtf8 () {

        // 和 C/C++ 類似,array of char 就是字串,不過比 C/C++ 增加
        // 很多功能。
        char [] result = "\n\n";
        auto sprint = new Sprint!(char);

        // 從陣列的最後面往前看,和 Stack 先進後出的行為是一樣的
        foreach_reverse (Type element; content) {

            // 這個和 sprintf 是一樣的,注意 formatting 時我們不用管
            // 變數的型態。
            //
            // PS.這是 Libary 的功能,不是 D 內建的
            result ~= sprint.format("|\t{}\t|\n", element);
        }

        // ~= 代表 array contanction
        result ~= "+---------------+\n";
        return result;
    }

    // 這兩個和 Java/C++ 完全一樣
    public int  size ()   {return content.length; }
    public bool isEmpty() {return (content.length == 0) ? true : false;}

    // 雖然有 GC,不過 D 還是可以有解構子
    public ~this () {
        Stdout.format ("Stack 被消滅了").newline;
    }

    // Operator Overloading
    bool opEquals (Stack!(Type) compare) {

        if ( compare.size != this.size )
            return false;

        foreach (int i, Type value; this.content) {
            if (this.content[i] != compare.content[i]) {
                return false;
            }
        }

        return true;
    }
}

void main ()
{
    // 可以替 Data Type 取別名
    alias Stack!(int) StackOfInteger;

    // typedef 是真的建立一個新的 type
    typedef Stack!(int) newStackType;

    // 如果宣告成 scope 變數,當 reference 無效時,會自動呼叫解構子
    scope stack = new StackOfInteger();

    //! Compile Error
    //! 這個是不行的,因為兩個被視作完全不同的型態。
    //! newStackType newStack = stack;  

    Stdout (stack.isEmpty).newline;
    stack.push(3).push(5).push(6);

    // 直接印出 stack 的內容
    Stdout (stack);

    auto stackCompare1 = stack;
    auto stackCompare2 = new StackOfInteger(3,5,6);
    Stdout (stackCompare1 == stackCompare2).newline;

    stackCompare1.push(7);
    Stdout (stackCompare1 == stackCompare2).newline;

    // 這裡 stack 的解構子會被呼叫
}

回響