在學了好幾年的java後,也決定來學學C++

在過去就常聽許多學長姐說C或C++在現今實務上還是十分有用且廣泛的,有著部份的不可取代性。

常勸我們這些後輩還是要學一下比較好,所以我就自學啦。

 

先學過JAVA後轉戰學C++還是有一定的好處的,就是學的比較快,一些基礎的部分可以多少跳過去一些,

剛好實務上有用到,就想說自行開發一個還算有一點小小小小用途的程式><

就是這個分組小程式啦,以下將詳細的介紹這隻小程式的結構!!

因為主要還是自己的筆記,雖然大部分都會介紹,但太過基本的部分就會跳過啦(有點隨便) XD

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

本程式主要功能是讓使用者能夠快速的進行組員的隨機快速分配,讓程式一開始先載入固定的檔案(此範例就是txt檔),內容是所有成員的名字,再讓使用者輸入組別數、組員人數,交由程式去分配,最後再讓程式將結果輸出成另一份的txt檔

也就是:

txt給程式 -->  程式執行 --> 輸出txt

這樣的流程步驟,那下面就進入程式內容!!

 

首先是讀入函式庫,這部分就是把需要的東西讀進來,相當於JAVA的 import。

#include <cstdlib>
#include <fstream>
#include <iostream>
#include <ctime>
#include <cstring>

第一個cstdlib 是通常都會讀入的,用於大部分的基本功能
第二個fstream 是用於檔案讀取,本範例就是用於讀取txt檔時會利用到
第三個是iostream,用於輸入輸出上
第四個是取得當前時間的時候會用到
最後一個是字串函式庫,因為長期接觸java的緣故,c++的char實在是用得不上手,所以還是用回string啦 XD

另外,若是用C的方式來載入的話,利用 <xxxx.h> 的寫法也還是可以的,同樣也能發揮功用。

所以最一開始的程式應該會長成如下

#include <cstdlib>
#include <fstream>
#include <iostream>
#include <ctime>
#include <cstring>

using namespace std;

int main()
{                                            
 system("pause");
 return 0; 
}

using namespace std;,代表的是名稱空間,這一行的功用即是在告訴編譯器,所需要的類別或功能要去這個名稱空間(std)就能找到,一般預設都是std ,所以照抄即可,若少了這一可是會在編譯時冒出一堆錯誤呢@@

最後的system("pause");是為了讓程式跑完後還能停留在cmd的畫面,若是沒有這一行的話,會是畫面閃一下就沒了,完全看不到結果喔。

 

下面接著是第一個主要部分:讀取文檔txt

fstream file1;
file1.open("list.txt");

這兩行是利用fstream 來建立檔案資料流,並利用open的方法來讀取檔案,也就是list.txt。

而open的方法其實可以輸入兩個參數,即open("檔案名稱", 讀取模式),在讀取模式那一個參數中,可以輸入如:ios::in , ios::out 等等,而詳細的說明可以參考這篇教學文-->C++檔案的輸出入(C++ File I/O)

在這邊筆者就只用一個參數也是可以的。(因為編譯器沒報錯,所以就這樣寫啦 XD)

 

然後是要求使用者輸入相關的資料

int team_count = 0;
int team_member_count = 0;
cout << "請輸入要分成多少組別: " ;
cin >> team_count;
cout << "請輸入各組最多為多少人: ";
cin >> team_member_count;

在這個程式中要求使用者輸入的主要有兩個數字,第一個是組別數,就是要將全部的成員分成多少個組別,是由使用者來輸入的;另一個則是每一組最多要有多少人,所以若假設共要分成五組,又每組最多四人,則全部的成員最多就有20位,即使用者要先後輸入數字5和4。

先設立兩個變數,藉由cin將使用者輸入的數字傳給變數去接收,以上面的20人為例子,則當程式跑完這一段後,team_count 會是5;team_member_count 會是4

 

再來是比較複雜的主要部分

筆者將組別用字串的方式加以記錄,為了最後能夠顯示出明確的資料敘述,所以用string來紀錄內容 

string first = "第一組成員為:";
string second = "第二組成員為:";
string third = "第三組成員為:";

 不過這樣子完全沒效率,也非動態產生,也看不出剛剛輸入的組別數有被確實的使用到,所以改寫成如下

string ss[team_count];

透過動態陣列來建立相應的組別數,這邊利用剛剛所輸入的組別數(team_count)所以會建立一個內有5個string字串的陣列。

這邊要注意的是,他並不像c++一般建立陣列的方式,通常建立動態陣列都是利用指標(*號),來讓記憶體分配一個區塊給程式利用,用完後歸還,像下面這樣

int *ccc = NULL;
ccc = new int[team_count];

......
delete [] ccc; //歸還記憶體

但不知道為什麼string能像JAVA的使用方式一樣直接宣告動態陣列,或許是因為C++也有用到許多OO(物件導向)的概念吧 XD

在建立了string陣列後,我們要讓這個字串內的文字初始化,先印出"第xx組的成員為:"這樣的起始敘述,所以利用到了迴圈,讓陣列內的每一個字串物件都有了這樣的開頭。

    for(int m = 0; m<team_count; m++){
      char mm[50];
      itoa( m+1, mm, 10 ); //int 轉成 char 
      ss[m] = ss[m] + "第 " + mm + " 組成員是: ";      //給予陣列起始的內容 
    }

在這邊先向各位讀者說聲抱歉,因為筆者的C++學的並不精通,所以用到了一些比較繞遠路的方式來解決筆者自己所遇到的問題,如這一段程式碼,為了讓結果能出現"第 1 組成員是:"、"第 2 組成員是:"、"第 3 組成員是:".....所以嘗試將字串和數字的m進行相加,但C++似乎字串無法與原先為數字的m進行相加,所以筆者就即時創了一個字元的陣列char mm[50]; ,並利用itoa的方法,將數字的m轉換成字元的mm,再讓mm與ss陣列中的字串進行相加,已達到筆者對結果顯示的要求。itoa方法的參數中,第一個是要轉換的數字;第二個參數是要轉換到哪裡;第三個是要利用什麼進位方式來進行轉換,詳細的說明可以參考這兩篇文章 -->(1)用itoa()函數將整數轉換為字符串、(2)sprintf(百度百科)

 

然後是計算各組人數的部分

     int *count = NULL;
     count = new int[team_count];
     for(int i = 0; i<team_count;i++){
        count[i] = 0;  //數字陣列內容先全部給0 
     }

這裡就是借用記憶體,然後用int 陣列來作為每一組人數的統計,所以count陣列的大小會和上面建立的string陣列的大小一樣,都是5,在內容預設都是給0,表示這5組的當前人數都是0人

 

接著從檔案中抓出資料來

    #define size 70 
     char oneline[size];
     bool flag = true;
     srand(time(NULL));

    while(file1.getline(oneline,sizeof(oneline), '\n')){
      flag = true;
       while(flag){
        int i =  (rand() % team_count) ;
        if(count[i] != team_member_count){
         count[i]++;
         ss[i] = ss[i] + oneline +", ";
         flag = false;
        }        
       }
     }

頭兩行是呼叫巨集,並建立一個char的陣列來保存所讀入的資料。然後再利用while迴圈去讀取資料,由於這個程式中的所用到的資料都存在txt檔中,所以筆者就用一行一筆資料的方式,這樣也方便程式的讀取。如下圖:

讀入的文件檔

讀取的主要程式碼是下面這一行

file1.getline(oneline,sizeof(oneline), '\n')

利用file1的getline方法來讀取,第一個參數是資料要保存的位置,第二個是要讀取多少字元的資料,因為在上面已經有宣告了char oneline[] 的大小是70,所以就會讀入最多70個字元,最後一個參數是何時停止讀取,以上述例子來講,就是當程式讀到了換行的標記(\n)時,就停止讀取。所以結果就是oneline[]裡面每次都只會有一個成員的名字被讀取到。

接著進入第二層while的迴圈,利用rand()來隨機選擇一組,並將讀取到的名字分配給這一組,同時將這一組的人數統計+1(count陣列),並讓相對應組別的字串陣列也加入這個成員的名字(string陣列)。

還有,為了不讓每一組的人數超過最大人數(就是4人),所以用if來做判斷,並利用flag這個布林值來作為"是否跳出第二層迴圈"的指標。

而rand()的部分,因為程式每次用到rand()都會去讀取相同的亂數表,會讓結果都一樣,所以要多加一行srand(),給予一個亂數種子,不過若是種子內的參數每次都一樣的話(例如寫成:srand(100)),亂數結果還是不會改變,因此就利用到了time(NULL),因為時間是隨時都在變的,所以作為亂數的種子是再適合不過了,time(NULL)就是取得當前的時間,會回傳自1970年1月1日0點到當下所經過的秒數。

不過這一大段的寫法其實會出現問題,也就是若txt檔案內的總人數超過了我們所分配的20人的話(例如檔案內有30人,我們只分配了20人),就會進入無窮迴圈,因為count統計會到達所設定的上限(4人),遇到多的人就會跳過,當count陣列內容都是4的時候,就再也沒有空的組別讓系統去分配了,那程式就永遠進不去if的條件式內,flag會一直保持在true的狀態,所以while(true)就會一直持續著,無法完結。

當然,若是已經知道檔案內會有多少人的話,就能稍微計算一下人數的分配,知道要分多少組,而各組要多少人,就不大會出現這樣的情況。因為筆者這邊的使用情況是幾乎不會遇到這樣的問題,所以這個bug筆者也沒有多花心思去修正,想嘗試修正的可以自行研究看看 XD

 

最後,就是將string陣列中的文字輸出,並產生另一份txt文件

    ofstream file2;
    file2.open("result.txt");
    cout << "----------------------\n";
    for(int j=0; j<team_count; j++){
     cout << ss[j] << "\n\n" ; 
     file2 << ss[j] << "\n\n" ;  
    }
    file1.close();
    file2.close();
    cout << "----------------------\n";
    cout <<"內容已經儲存,並輸出成檔案\"result.txt\",存於同一資料夾目錄下\n\n";

建立另一個檔案資料流,因為這次是output,所以用單純的ofstream(單純讀取資料的話也可以用ifstream)。

用ofstream的另一個原因,是當檔案不存在時,程式會自動創建一個新檔案;若檔案已經存在的話,程式會將舊檔案內容全部刪除,再將新的內容寫進去。

同fstream,在open方法時也可以用到兩個參數,一樣是參考這篇教學文-->C++檔案的輸出入(C++ File I/O)

最後,記得要關閉檔案資料流

    file1.close();
    file2.close();

 

到這邊算是程式大功告成了,最後放上完整整理過的程式碼(有加上檔案讀取的判斷(if, else),若成功就繼續執行,若讀取失敗就印出說明文字)

#include <cstdlib>
#include <fstream>
#include <iostream>
#include <ctime>
#include <cstring>

using namespace std;
 
int main()
{           
     //先建立檔案資料流,並讀取檔案 
     fstream file1;
     file1.open("list.txt");
        //判斷檔案是不是有被讀到
  if(file1 != NULL){
      // 從使用者的輸入來取得要分成幾組及各組要多少人 
    int team_count = 0;
    int team_member_count = 0;
    cout << "請輸入要分成多少組別: " ;
    cin >> team_count;
    cout << "請輸入各組最多為多少人: ";
    cin >> team_member_count;
    
    string ss[team_count];  //建立字串陣列,大小為組別數 
    for(int m = 0; m<team_count; m++){
      char mm[50];
      itoa( m+1, mm, 10 ); //int 轉成 char 
      ss[m] = ss[m] + "第 " + mm + " 組成員是: ";      //給予陣列起始的內容 
    }
    
    //借用記憶體,並建立動態的數字陣列 
     int *count = NULL;
     count = new int[team_count];
     for(int i = 0; i<team_count;i++){
        count[i] = 0;  //數字陣列內容先全部給0 
     }
     
     #define size 100 
     char oneline[size];
     bool flag = true;
     srand(time(NULL));
     
     //開始一行一行讀取檔案 
     while(file1.getline(oneline,sizeof(oneline), '\n')){
      flag = true;
       while(flag){
        int i =  (rand() % team_count) ;
        if(count[i] != team_member_count){
         count[i]++;
         ss[i] = ss[i] + oneline +", ";
         flag = false;
        }        
       }
     }
    
        //將分組結果輸出 ,同時列印出結果 
    ofstream file2;
    file2.open("result.txt");
    cout << "----------------------\n";
    for(int j=0; j<team_count; j++){
     cout << ss[j] << "\n\n" ; 
     file2 << ss[j] << "\n\n" ;  
    }
    file1.close();
    file2.close();
    cout << "----------------------\n";
    cout <<"內容已經儲存,並輸出成檔案\"result.txt\",存於同一資料夾目錄下\n\n";
  }else{
    cout <<"檔案讀取失敗,請將檔案名稱設為\"list.txt\",\n並將內容寫成一行一個名字,如下\n\n";
    cout <<"list.txt\n------------------------------\n";
    cout <<"AAA\nBBB\nCCC\nDDD\nEEE\nFFF\nGGG\nHHH\n.....\n....\n...\n\n\n\n--------------------------------\n\n\n\n";
  }
                                                
 system("pause");
 return 0; 
}
//程式到此結束

 

而執行的過程及結果如下:

過程

結果產生的txt檔案:

結果

另外,若是一開始讀不到檔案的話就會出現下圖:

讀檔錯誤

 

如此,一個很陽春的程式就完成了,希望此篇教學文有幫助到各位,下次再嘗試寫出其他有用的程式或系統吧。因為是初學,所以如果有發現任何問題或錯誤的地方,歡迎各位不吝賜教或一起討論。

感謝各位閱讀到此<(_ _)>

 

, , , ,

kataraxia 發表在 痞客邦 PIXNET 留言(0) 人氣()