Lab 1-1: Basic C programming and
Multi-file compilation environment

 
實習目標 基本 C 語法, 演算法, 與編譯執行環境

Visual C++ 2010 環境中熟悉使用多個檔案來設計 C/C++ 程式 (Visual C++ 6/2005/2008)
 
步驟一

請下載 statisticsAndSort.exe 程式,

如右程式執行範例所列, 這個程式

  1. 讀取一個資料檔案 raw1.dat
  2. 計算檔案內所有資料的平均值
  3. 計算各種資料出現的中間值 (中數)
  4. 列印出現頻率最高的數值 (眾數)
C:\> statisticsAndSort
Input the file name: raw1.dat
********
Mean
********
The mean is the average value of the data
items. The mean is equal to the total of
all the data items divided by the number
of data items ( 99 ). The mean value for
this run is: 681.000000 / 99 = 6.878788 

********
Median
********
The unsorted array of data is
6 7 8 9 8 7 8 9 8 9 7 8 9 5 9 8 7 8 7 8
6 7 8 9 3 9 8 7 8 7 7 8 9 8 9 8 9 7 8 9
6 7 8 7 8 7 9 8 9 2 7 8 9 8 9 8 9 7 5 3
5 6 7 2 5 3 9 4 6 4 7 8 9 6 8 7 8 9 7 8
7 4 4 2 5 3 8 7 5 6 4 5 6 1 6 5 7 8 7
The sorted array is
1 2 2 2 3 3 3 3 4 4 4 4 4 5 5 5 5 5 5 5
5 6 6 6 6 6 6 6 6 6 7 7 7 7 7 7 7 7 7 7
7 7 7 7 7 7 7 7 7 7 7 7 7 8 8 8 8 8 8 8
8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8
9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9
The median is the 49-th element of
the sorted 99 element array.
For this run the median is 7

********
Mode
********
Data Frequency Histogram
              1    1    2    2
         5    0    5    0    5

1  1 *
2  3 ***
3  4 ****
4  5 *****
5  8 ********
6  9 *********
7 23 ***********************
8 27 ***************************
9 19 *******************
The mode is the most frequent value.
For this run the mode is 8 which 
occurred 27 times.

raw1.dat:

99
6
7
8
9
8
7
8
9
8
...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

請下載 statisticsAndSort000.cpp 程式檔案, 這個程式很接近一個完整的程式, 可以編譯, 可是沒有完成前面範例程式的功能, 請依照下列的步驟完成它。

步驟二 程式第 105 列
    // calculate the occuring frequency of data
實際內容未完成, 所以執行的時候 freq[] 陣列內資料為 0; 請寫幾列程式, 計算 dataArray[] 陣列中數值 1~9 出現的頻率, 並且記錄在 freq[] 陣列中。
步驟三 程式第 90-92 列
    void sort(int a[], int size)
    {
    }
未完成, 請隨便挑選一個排序的演算法實作一下, 將陣列 a[] 的內容依照其大小排序 (例如: selection sort, bubble sort, counting sort, quick sort 或是 heap sort)。

完成這個步驟以後你的程式應該基本上和範例執行程式 statisticsAndSort.exe 有一樣的功能了。

例如一個基本的 selectionSort 的演算法大致如下: (下面的內容是程式的細部提示, 故意不直接顯示, 你需要看再看, 可以不看的話就盡量不要看, 是應該可以很快完成的程式片段, 請練習寫一下)

    int data[NDATA]; // 假設未排序的資料存放於此陣列中
    int ndata; // 假設此變數記錄資料的筆數
    int i;
    for (i=0; i<ndata-1; i++)
    {
        1. 在 data[i+1], data[i+2] 到 data[ndata-1] 這些元素中找出最小的
        2. 把 這個最小的元素和 data[i] 的資料對調 
    }
    
步驟四 請嘗試將這個程式分割成為四個檔案, 內容如下:

  1. main.cpp: 包含 main() 函式

  2. io.cpp: 包含 readFile() 和 printArray() 函式

  3. statistics.cpp: 包含 mean(), median(), 和 mode() 函式

  4. sort.cpp: 包含 sort() 函式
請注意 io.cpp, statistics.cpp, 和 sort.cpp 都要建立相對應的 io.h, statistics.h 和 sort.h 檔案 (main.cpp 不需要 main.h, 原因?)

在 vc2010 中分割檔案的大致步驟如下:

  1. 建立一個空專案: "檔案, 新增, 專案, 空專案, 填入專案名稱 lab1-1, 位置, 清除為方案建立目錄選項"

  2. 將 statisticsAndSort000.cpp 檔案拉入編輯區裡開啟 (不要加入專案中)

  3. "工具列-加入新項目 (Ctrl-Shift+A), Visual C++/程式碼/C++檔(.cpp)/輸入檔案名稱 main.cpp/新增", 由 statisticsAndSort000.cpp 檔案中拷貝整個 main() 函式進來

    請注意左側 "方案總管" 子視窗裡可以看到 原始程式檔裡顯示 main.cpp,
    在"類別檢視" 子視窗裡可以看到 main() 函式, 類別檢視子視窗是你這學期在寫 C++ 程式時需要不斷使用的東西, 點選子視窗中任一函式可以在編輯區域中立刻打開這個程式檔案

  4. "建置, 建置方案 (F7)", 下方 "輸出" 子視窗裡會看到建置失敗的訊息, 有五個編譯錯誤 (用滑鼠在每一個錯誤上點兩下可以立刻在編輯區內看到錯誤的那一列敘述), 請根據這些錯誤訊息來修改程式

  5. 請由 statisticsAndSort000.cpp 拷貝 #define DATASIZE 150 的敘述到 main.cpp 中

  6. 由 statisticsAndSort000.cpp 中拷貝 main() 函式中所使用到的函式的宣告 (prototype) 到 main.cpp 檔案中 main() 函式之前, 包括
    void readFile(int *dataSize, int data[]);
    void mean(const int [], int);
    void median(int [], int);
    void mode(int [], int [], int);
    "建置, 建置方案 (F7)", 這個時候還是看到建置失敗, 但是訊息改變了, 請注意變成連結 (linking) 的錯誤了, (錯誤訊息 LNK???? 而不是 C???? 了), 其實像上面這樣 main() 函式的語法已經是對的了, 編譯是對的, 但是當想要產生完整的執行檔案(*.exe)時, 發現專案裡找不到 mode(), median() mean(), 和 readFile() 這些函式, 所以發生連結時的錯誤

  7. 如果你點選 main.cpp 然後 "建置 / 編譯 (Ctrl+F7)", 在 "輸出" 子視窗中就可以看到 main.cpp 檔案的編譯結果是成功的, 這樣可以先確定 main.cpp 中的程式語法已經正確

  8. "工具列-加入新項目,Visual C++/程式碼/C++檔(.cpp)/輸入檔案名稱 io.cpp/新增", 由 statisticsAndSort000.cpp 拷貝 readFile(), printArray() 函式的定義進來

  9. 點選 io.cpp, "建置 / 編譯 (Ctrl+F7)" io.cpp, 確定 io.cpp 中的程式語法已經正確, 同時所需要使用的函式都有 #include 進來; 因為使用到 printf, fscanf 等等函式, 你應該需要在 io.cpp 最前面 include stdio.h

    再編譯一次, 編譯結果是成功的, 但是你會看到一堆 C4996 的警告, 例如:
    warning C4996: 'gets': This function or variable may be unsafe....
    主要是告訴你像 gets, fscanf 這樣的函式是不安全的, 很容易出現 buffer overflow (BOF) 的網路安全漏洞, 應該要避免使用, 例如使用 fgets 函式就是安全的
    如果你有信心你的程式永遠不會放上網路, 不會被別人攻擊, 那麼你還是照樣使用沒有關係, 如果你嫌那麼多的警告干擾你, 有的時候害你沒有看到比較重要的訊息, 你也可以在 io.cpp 檔案最前面加上

    #pragma warning(disable:4996)

    或是

    #define _CRT_SECURE_NO_WARNINGS

    就不再顯示這種警告了

  10. "工具列-加入新項目, Visual C++/程式碼/標頭檔(.h)/輸入檔案名稱 io.h/新增", 由statisticsAndSort000.cpp 拷貝這兩個函式的宣告敘述進來, 並在前後加上 #ifndef, #define, #endif 的敘述如下:
    #ifndef IO_H
    #define IO_H
    void readFile(int *dataSize, int data[]);
    void printArray(const int[], int);
    #endif
    IO_H 是配合檔案名稱取的, 各個檔案裡都應該是不一樣的, #ifndef, #define, #endif 的前處理器指令是為了防止重複引入 .h 定義檔
    注意事項

  11. 修改 main.cpp 內容, 拿掉 void readFile(int *dataSize, int data[]); 敘述, 換成 #include "io.h"

    請注意 .h 檔案裡最主要會有兩種東西, 一種是如上的函式宣告, 另外一種就是資料型態的宣告, C/C++ 編譯器不允許任何一個資料型態重複定義兩次, 例如:
    struct Data {
        int data;
    };
    struct Data {
        int data;
    };
    ...
    編譯器就認為是有錯誤的,

    如果在 main.cpp 中出現

    #include "io.h"
    #include "io.h"

    而 io.h 中有 struct Data ... 這種資料型態的定義, 那當然就會出現重複定義的錯誤

    當然程式裡直接出現這樣的重複似乎有點智障, 但是有可能出現 #include "io.h", io.h 裡 #include "data.h", data.h 裡又 #include "io.h" 的情形

    所以才使用上述的 #ifndef, #define, #endif 前處理器指令來避免這種狀況

  12. 請點選左下方 "方案總管" 檢視目前 lab1-1方案中有哪些原始程式檔, 有那些標頭檔, 使用 "工具列-加入新項目 / xxx檔" 命令作出來的檔案都會自動加入專案, statisticsAndSort000.cpp 是直接拉入編輯區裡編輯的檔案 (或是使用 "檔案 / 開啟 /檔案 (Ctrl+O)" 開啟的檔案), 就不會加在專案裡, 只是純粹編輯而已, 所有在專案裡的檔案可以在執行 "建置 / 建置方案 (F7)" 或是 "建置 / 重建方案 (Ctrl+Alt+F7)" 時一次都編譯完畢並且連結在一起成為 .exe 的執行檔案;

    如果你要從專案裡移除一個檔案, 你應該要在 "方案總管" 中點選那個檔案, 以滑鼠右鍵選 "移除", 再選 "移除" 來從專案裡移除一個檔案, 此時重先建置就不會編譯這個檔案, 但是在檔案系統中檔案還沒有刪除, 如果你以滑鼠右鍵選 "移除", 再選 "刪除", 不但從專案裡移除這個檔案, 也從資料匣中永遠刪除這個檔案

  13. 依照類似程序製作 statistics.cpp, statistics.h, sort.cpp, 以及 sort.h 檔案, 這些檔案一定要在專案中, 如果不在的話, 你也可以在方案總管中用右鍵點選 lab1-1 專案, 選擇 "加入 / 現有項目" 把作出來的 .cpp 和 .h 檔案加入 (請注意這些檔案應該都在同一個資料匣裡面, 繳交實習或是作業時你需要把整個資料匣壓縮起來繳交)

  14. 都完成後可以 "建置, 建置方案 (F7)" 並且 "偵錯, 啟動但不偵錯 (Ctrl+F5)" 執行程式來測試

minimal declaration 原則: 請確實瞭解應該在什麼地方 #include 需要的 .h 檔案, 很多時候在簡單的範例程式裡多加幾列 #include 敘述並不會導致錯誤, 可是有的時候 (請參考 第13頁) 在大型的專案裡就是因為你多 #include 不必要的檔案而導致使用到不正確的函式, 這種 bug 常常變成偵錯時的死角, 最主要造成錯誤的原因是連結器在函式庫裡找尋你要用的函式時是依照 #include 的順序找到第一個來使用, 如果你隨便 #include 很多沒有用到的函式庫, 就有機會在裡面有一個同樣名稱、同樣參數的函式, 可是卻偏偏就不是你想要用的那個函式; 會隨便 #include 檔案通常就代表你並不清楚什麼地方該加 #include 敘述, 代表你並不清楚為什麼要加上那些 #include 敘述, 如果你還是覺得不清楚的話請趕快問我、助教或是同學。

上面的這些程序裡, 你應該要看完網頁內容以後, 思考一下為什麼要這樣子做, 在每次修改或是增加一小段程式以後, 運用編譯器去編譯/建置/執行, 盡量維持程式是可以編譯可以執行的, 不要累積好多動作以後才編譯一次, 弄錯了沒有立即更正的話, 你就只是跟著網頁的說明做而已, 很容易就學不到什麼東西

  請問在寫 C/C++ 程式時, 把程式分為多個檔案分別撰寫/分別編譯 和 單一一個程式檔案的作法比起來有什麼好處?
  1. 每次修改一個函式時, 只需要編輯那個檔案就夠了, 使用編輯器編輯一個 10000 行的程式是很危險的, 如果你手殘不小心按到鍵盤, 在某一個地方多加了一個字母, 刪除掉一些敘述, 有的時候就要多花很多時間才能找到; 如果你喜歡用編輯器的搜尋和取代, 在趕作業趕到天亮的時候, 很容易一個不小心就改到不該改的東西, 所以最安全的原則是 "只編輯需要改的檔案" (注意看編輯器上方改過的檔案都有 * 號, "建置, 建置方案" 時也只會編譯修改過的檔案)

  2. 編輯完以後只需要編譯這個檔案, 如果你的程式很大有一萬行, 但是你只修改一段 10 列的程式片段, 你應該可以想像編譯器可以省下好多檢查語法的時間

  3. 在寫 C 程式時, 以不同的檔案來區隔功能不同的函式, 達成模組化的基本要求, 如果很多人一起合作完成專案的化, 不同的人負責的模組可以放在不同的檔案中, 每個人修改自己負責的檔案就好

  4. 在寫 C++ 物件化的程式時, 你還會發現檔案就是很自然的物件邊界, 我們很多時候會需要組合物件成為應用程式, 要拿出一個物件的時候, 你當然希望這個物件放在獨立的檔案裡, 而不希望 100 個物件放在一個檔案中, 使得你必須去編輯修改檔案....檔案經過編輯就有機會出現錯誤啊! 另外我們在測試功能的時候, 也有可能以取代物件的方式來測試, 同樣的也是希望一個檔案裡只有一個物件
步驟五 請助教檢查後, 將所完成的 專案 (只需保留 .cpp, .h, .sln 以及 .vcxproj 檔案即可; 刪除掉 .sdf, .filters, .users, debug/ 資料匣, 以及 ipch/ 資料匣下的所有內容) 以 zip/rar/7zip 程式將整個資料匣壓縮起來, 登入上傳網頁, 選擇 Lab1-1, 後面的實習課程需要使用這裡所完成的程式

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

製作日期: 02/20/2013 by 丁培毅 (Pei-yih Ting)
E-mail: [email protected] TEL: 02 24622192x6615
海洋大學 電機資訊學院 資訊工程學系 Lagoon