Lab 6-2: Coding the UML spcification
   ctor and dtor practicing

   
實習目標 練習依照 UML 的類別圖實作類別, 製作類別間靜態的連結, 練習撰寫建構元與解構元
   
步驟一 請參考下面 UML 的類別圖, 製作所需要的類別

這是一個描述類似海洋大學這樣子的組織的「課程」資訊系統, 所以我們針對所有開設的課程來描述對應的組織架構, 首先學校裡面有好幾個學院, 每一個學院裡面又有不同的系所, 每一個系所每個學期都會開設許多的課程, 系所也有許多的老師, 假設每個課程都有一個老師在負責授課。

在這個實習中我們針對紅色的部份來製作, 下一個實習中我們實作整個架構, 也探討這個架構有什麼樣的問題存在

步驟二 請定義 University, College 和 Department 這三個類別, 其中的資料部份先用你覺得最方便的型態, 例如 m_name 可以用 string 類別或是字元陣列 char [] 來實作, 所有類別內的資料成員基本上都是 private 的
步驟三 接下來要建構類別間的關係, 例如我們學校裡有七個學院, 所以在 University 類別中要有一個容器物件, m_colleges, 你可以用 vector<College> 來製作, 如此在 University 解構時, 所有的 m_colleges 也都會一起解構; 如果考量效率的話, 你也可以用物件指標的 vector 來製作, 例如 vector<College *>, 但是下層的 College 物件的解構就需要由 University 物件來負責了。

請注意: 如果你選擇使用 vector<College> 來製作, 每一個元素都是一個 College 型態的物件, vector<College>::push_back(College) 函式需要傳入一個 College 型態的物件, 此時會呼叫拷貝建構元 (copy constructor) 來處理參數的拷貝, 請注意拷貝建構元的寫法和使用的時機, 每一種關連性都可以考慮用物件的 vector 來實作, 例如 vector<Department>, vector<Institute>, vector<Teacher>, vector<Course> 來實作, 如此實作除了效率會造成一些小問題之外, 真正的問題在於雙向的連結 (例如 Course - Teacher 間的連結), 另外會有問題的是重複的關聯性, 例如 A - C 以及 B - C。

如果你覺得每個學校的學院變化不大, 不需要用 vector 那麼一般化的容器物件來製作, 你也可以像 3bags 程式那樣子用陣列來實作, 例如 College colleges[4];

在 C++ 中你也可以選擇用指標陣列來實作, 如此可以省掉許多不必要的資料拷貝的動作, 例如 College *colleges[4]; 只是記得要在 University 物件 中透過 new 和 delete 來配置/釋放那些 College 物件

在 College 中有 Department, 你可以用 vector<Department *> 來定義, 如此在解構 College 時, Department 不會自動被解構, 你可以把 Department 物件紀錄到別的地方去, 你可以自行掌握所有物件的生命週期

注意什麼時候用 vector, 什麼時候用 C/C++ 語法裡原本的陣列, 完全是效率和程式碼大小的考量, 你可以斟酌程式的複雜度, 可讀性和效率來使用

步驟四 接下來我們要定義建構元, 建構元主要將每一個資料成員適當地初始化, 除了紀錄狀態的資料成員之外, 也包括架構物件間連結的所有資料成員, 所以上層物件 (例如 University 類別的物件) 的建構元執行完畢之後, 通常整個系統物件的架構都已經建立好了。 (因為它間接地會讓每一個物件的建構元都執行過。)

相對地解構元除了釋放自己類別內使用的記憶體外, 常常也會將整個物件架構摧毀。

有些建構元可以有預設的數值, 有些則需要在建構時將資料當做參數傳遞進去, 我們這個應用程式的資料量有點大, 如果為了兼顧彈性, 也不適合將所有資料物件用預設的方式建立起來, 所以我們打算由檔案串流中讀取資料。

還是有同學沒有辦法接受一個『物件生成的時候需要把裡面所有的成員都初始化』, 覺得這和以前記憶體的使用概念相當不同, 記憶體不就是用來存放資料的, 安排好了記憶體的大小, 要不要存放資料不是寫程式的人的自由嗎? 的確是, 也因為這麼多的自由, 所以用程序化方式寫的程式很難擴充, 很難長大... 這是一開學一直跟大家溝通的概念, 現在你看到的物件不只是存放資料的地方而已, 它們需要對軟體的其他部份提供服務, 而且要提供不容易出錯的服務, 如果有一個物件, 把它做出來以後, 因為所有內部的資料都還沒有設定, 所以沒有辦法提供服務, 想要用它的人還要顧及它裡面所有的資料有沒有一一設定, 是不是沒有衝突, 還要顧及它是不是心情很好, 然後才能請求它的服務, 那到底是你在服務它? 還是它在服務你? 當你一個人需要控管所有細節的時候, 真好, 可以滿足那種一夫當關任你操縱的權力慾望, 可是別忘了一個人的能力有限, 當你需要把注意力放在很多很多地方的時候, 就會開始東忘西漏的了, bug 很快地就會是你的好朋友了

步驟五

我們把資料放在 ntou1.txt 檔案內, 資料如下:

    海洋大學                 // 學校名稱
    7                              // 學院個數
    海運暨管理學院    // 第一個學院名稱
    4                              // 此學院內學系個數
    商船學系                 // 第一個學系名稱
    航運管理學系         // 第二個學系名稱
    運輸科學系             // 第三個學系名稱
    輪機工程學系          // 第四個學系名稱
    生命科學院             // 第二個學院名稱
    5
    食品科學系
    水產養殖系
    生命科學暨生物科技學系
    海洋生物研究所
    海洋生物科技博士學位學程
    海洋科學與資源學院    // 第三個學院名稱
    6
    環境生物與漁業科學學系
    ...
    工學院                       // 第四個學院名稱
    5
    機械與機電工程學系
    系統工程暨造船學系
    ...
    電機資訊學院              // 第五個學院名稱
    5
    電機工程學系
    資訊工程學系
    ...
    人文社會科學院           // 第六個學院名稱
    5
    應用經濟研究所
    ...
    海洋法律與政策學院        // 第七個學院名稱
    3
    海洋法政學士學位學程
    ...
此檔案內每一筆資料代表意義以及讀取方法請參考 testRead1.cpp, 以及 testRead1.exe (請下載後執行)

請設計各個建構元函式, 依據此資料檔案內容建構各個物件, 例如: College 類別的建構元如下:

    College::College(ifstream &infile)
    {
        char buf[50];
        getline(infile, m_name, '\n'); // string m_name; 學院名稱

        int numberOfDepartments;
        infile >> numberOfDepartments;
        infile.getline(buf, 50, '\n');

        int iDept;
        for (iDept=0; iDept<numberOfDepartments; iDept++)
            m_departments.push_back(new Department(infile)); 
    }
請注意, 由於在建構元中產生了這些 Department 物件, 一般來說在解構元中會對稱地把它們釋放掉, 否則就會有記憶體的遺失
    College::~College()
    {
        std::vector<Department *>::iterator iter;
        for (iter=m_departments.begin();
             iter<m_departments.end();
             iter++)
            delete *iter;
    }
步驟六 為了作基本的測試, 我們在 University, College, 和 Department 類別內都增加一個 print(ostream &) 的公開成員函式, 各自將自己物件的內容印出在輸出串流中, 例如:
    void University::print(std::ostream &os)
    {
        os << "學校名稱:" << m_name << std::endl;
        std::vector<College *>::iterator iter;
        for (iter=m_colleges.begin();
             iter<m_colleges.end();
             iter++)
            (*iter)->print(os);
    }
注意 ostream 參數必須使用參考變數或是指標變數, 以避免解構全域串流物件
步驟七 你可以在 University 類別中寫一個 static 的 unitTest 成員函式來測試看看目前所寫的功能:
    void University::unitTest()
    {
        ifstream infile("ntou1.txt");
        if (!infile)
            std::cout << "Cannot open ntou1.txt!!\n";
        else
        {
            University ntou(infile);
            ntou.print(std::cout);
        }
    }
範例執行程式 campusCourse1.exe (請下載後執行)
步驟八 請助教檢查後, 將所完成的 專案 (只需保留 .cpp, .h, .sln 以及 .vcxproj 檔案即可; 刪除掉 .suo, .sdf, .filters, .users, debug\ 資料匣, 以及 ipch\ 資料匣下的所有內容) ) 壓縮起來, 選擇 Lab6-2 上傳, 後面的實習課程需要使用這裡所完成的程式

C++ 物件導向程式設計課程 首頁

製作日期: 06/12/2008 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@mail.ntou.edu.tw TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程學系 Lagoon