你好,C++(30)“大事化小,小事化了”5.4.3 工資程序成長記:函數


5.4.3 工資程序成長記:函數

自從上次小陳“程序員”的工資程序得到老板的誇獎,口頭許諾給他漲工資以后,老板再也沒有找過他,漲工資的事自然也就沒有下文了。這天,老板又突然召他去辦公室。這下可把小陳高興壞了,心想盼星星盼月亮終於盼來漲工資這一天了。於是趕緊到了老板的辦公室。可他剛進門就發現情況有點不對,只見老板陰沉着臉坐在他那張碩大的老板椅上,滿頭大汗,手指還在不停地敲擊着鍵盤輸入着什么。一見到小陳進來,就好像見到仇人似的,劈頭蓋臉地來了一句:

“小陳啊,你這個工資程序怎么搞的,怎么每次都要重新輸入工資數據啊?你是不是想累死我啊?”

老板的話如同一個晴天霹靂,讓小陳的好心情一下子從山頂跌落到了谷底,心想,這下漲工資的事情肯定泡湯了。聽老板這一抱怨,他才想起來原來的工資程序沒法讀取已有的工資數據,也無法將已經輸入的工資數據保存成文件以備下次使用。所以這個工資程序是“一次性”的,每次使用都要重新輸入全部數據。小陳想想那成千上萬的工資數據,心中暗想,這都沒把這個可惡的老板累死,真是算他走運了。不過轉念又想,要是把他累死了誰給我漲工資啊?於是,趕緊解釋說:

“老板,這可能是工資程序存在的一個缺陷,也就是我們通常所說的Bug(臭蟲)。您也是知道的,程序中出現Bug是在所難免的。讓我拿回去修改修改,馬上就好,馬上就好。”

就這樣,小陳沒有聽到漲工資的好消息,反倒是拿了一個有問題的工資程序回來修改。好在他這幾周還算勤快,在C++方面進步了不少,他已經學到了函數,懂得了如何對一個問題“自頂向下、逐步求精”地進行分析,划分模塊並用C++的函數加以解決。所以,對於解決老板遇到的這個問題,他還是很有信心的。

拿到問題后,小陳對這個問題進行了簡單的分析。整體而言,這是一個典型的對數據進行處理的程序,按照業務流程,它主要可以分為數據輸入、數據處理和數據輸出三個比較大的模塊。而根據輸入方式的不同,數據輸入模塊又可以細分成手工輸入和文件讀入,而數據輸出部分,除了屏幕顯示結果之外,相應地還應該包括將工資數據輸出到文件。在最重要的數據處理模塊,為了保證函數的職責單一而明確,需要將原來混雜在數據輸入時進行的最大值最小值以及平均值的計算,移到數據處理模塊中成為獨立的子模塊。經過這樣的簡單分析,小陳很快地在白板上畫出了工資程序的模塊圖:

                       

圖5-11 工資程序模塊圖

有了程序的模塊圖,我們只要用函數將每個模塊實現,然后在主函數中按照業務流程對各個子模塊加以調用,就可以解決整個問題了。在模塊圖的指引下,小陳很快就用函數實現了每個模塊,並將它們組裝成了新的工資程序:

// 工資程序V2.0
#include <iostream>
// 為了讀寫文件,引入文件流對象頭文件
#include <fstream>
#include <string>
#include <climits> // 為了使用int類型的最大值和最小值

using namespace std; 

// 全局的工資數據文件名,使用一個不可修改的常量字符串表示
const string strFileName = "SalaryData.txt";

// 從數據文件讀取工資數據到arrSalary數組
int Read(int* arrSalary, int nCount)
{
    int i = 0;   // 當前工資序號

    // 打開工資數據文件SalaryData.txt用於讀入數據
    // 這個文件應該在exe文件所在的相同目錄下
    ifstream in(strFileName);

    if(in.is_open()) // 如果成功打開數據文件
    {
         // 構造一個while循環讀取文件中的工資數據
         // 如果讀取的數據個數i小於數組的容量nCount,則繼續讀取
        while(i < nCount)
        {
             // 將讀取的數據保存到arrSalary[i]
             in>>arrSalary[i];
             // 對讀取結果進行判斷,看是否讀取到了文件結束
             // 如果到達文件結尾,則用break關鍵字結束讀取循環
             if(!(in))
             {
                 break;
              }

             ++i;  // 尚未讀取完畢,開始下一次讀取
        }

        // 讀取完畢,關閉文件
         in.close();
    }
    // 輸出讀取結果,返回讀取的數據個數
    cout<<"讀取"<<i<<"個工資數據"<<endl;
    return i;
}

// 將arrSalary數組中的工資數據寫入數據文件
void Write(int* arrSalary, int nCount)
{
    // 創建或打開工資數據文件SalaryData.txt用於輸出
    // 輸出完成后,這個文件將出現在exe文件所在的目錄
    ofstream o(strFileName);
    if(o.is_open()) // 如果成功打開數據文件
    {
        // 利用for循環,將數組中的數據輸出到文件
         for(int i = 0;i < nCount;++i)
        {
             o<<arrSalary[i]<<endl; // 每一行一個數據
        }

        // 輸出完畢,關閉文件
        o.close();
    }
}

// 獲取工資數組中的最大值
int GetMax(int* arrSalary, int nCount)
{
    int nMax = INT_MIN; // 初始值為int類型的最小值
    // 利用for循環遍歷數組中所有數據元素,逐個進行比較
    for(int i = 0;i < nCount; ++i)
    {
        if(arrSalary[i] > nMax)
             nMax = arrSalary[i];
    }
    // 返回找到的最大值
    return nMax;
}

// 獲取數組中的最小值(請依最大值函數的葫蘆自行畫出最小值函數的瓢)…

// 計算數組中所有數據的平均值
float GetAver(int* arrSalary, int nCount)
{
    // 計算總和
    int nTotal = 0;
    for(int i = 0;i < nCount; ++i)
    {
        nTotal += arrSalary[i];
    }
 
    // 計算平均值並返回
    if(0 != nCount) // 判斷總數是否為0
        return (float)nTotal/nCount;
    else
        return 0.0f; // 特殊情況返回0
}

// 手工輸入數據
int Input(int* arrSalary, // 工資數組首地址
       int nMax,    // 數組能夠容納的數據的個數      //    
       int nIndex)  // 數組中已有的數據的個數
{
    // 用數組中已有數據的個數作為輸入的起點
    int i = nIndex;  // 在for循環之前初始化i,應為i在for循環之后還需要用到
    for(; i < nMax; ++i) // i已經初始化,初始化語句留空
    {
        // 提示輸入
        cout<<"請輸入"<<i<<"號員工的工資(-1表示輸入結束):"<<endl;
        // 將輸入的數據保存到數組的arrSalary[i]數據元素
        int n = 0;
        cin>>n;
        // 檢查輸入是否合法
         if(cin)
        {
             arrSalary[i] = n;
        }
        else// 如果輸入不合法,例如輸入了英文字符,則提示用戶重新輸入
        {
             cout<<"輸入錯誤,請重新輸入"<<endl;
             // 清理cin的輸入標志位以重新輸入
             cin.clear();
             // 清空輸入緩沖區
             cin.sync();
             --i;  // 將輸入序號退后一個
             continue; // 直接開始下一次循環
        }
  
        // 檢查是否輸入結束
        if(-1 == arrSalary[i])
        {
             break; // 結束輸入循環
        }
    }
   
    // 返回當前數組中共有的數據個數
    return i;
}

// 查詢工資數據
void Find(int* arrSalary,int nCount)
{
    while(true)  // 構造無限循環進行工資查詢
    {
        int n = 0;
        // 提示用戶輸入要查詢的員工序號
        cout<<"請輸入要查詢的員工序號(0-"<<nCount-1
             <<",-1表示結束查詢):"<<endl;
        // 獲取用戶輸入的員工序號並保存到n
        cin>>n;
  
        // 對用戶輸入進行檢查
        if(!cin) // 如果用戶輸入不合法
        {
             cout<<"輸入錯誤,請重新輸入"<<endl;
             // 清理cin的輸入標志位以重新輸入
             cin.clear();
             // 清空輸入緩沖區
             cin.sync();
             continue; // 開始下一次查詢
        }
        else if(-1 == n) // 檢查查詢是否結束
        {
             // 查詢結束,用break結束循環
             cout<<"查詢完畢,感謝使用!"<<endl;
             break;
        }
        else if(n<0||n>=nCount) // 檢查輸入是否超出序號范圍
        {
             // 輸入序號超出范圍,用continue開始下一次循環
             cout<<"輸入的序號"<<n<<"超出了序號范圍0-"
              <<nCount-1<<",請重新輸入。"<<endl;
             // 開始下一次查詢
             continue;
        }
 
        // 輸入合法,輸出用戶查詢的員工工資
        cout<<"員工序號:"<<n<<endl;
        cout<<"員工工資:"<<arrSalary[n]<<endl;
    }
}

int main()
{
    // 定義保存員工數據的超大數組
    const int MAX = 100000; 
    int arrSalary[MAX] = {0};
 
    // 首先從數據文件讀取已經保存的數據
    int nCount = Read(arrSalary,MAX);
    // 然后用手工繼續輸入工資數據
    nCount = Input(arrSalary,MAX,nCount);

    // 對輸入的工資數據進行統計
    cout<<"輸入完畢。一共有"<<nCount<<"個工資數據"<<endl;
    cout<<"最大值:"<<GetMax(arrSalary,nCount)<<endl;
    cout<<"最小值:"<<GetMin(arrSalary,nCount)<<endl;
    cout<<"平均值:"<<GetAver(arrSalary,nCount)<<endl;

    // 對工資數據進行查詢
    Find(arrSalary,nCount);
    
    // 查詢結束,將工資數據保存到數據文件,以備下次使用
    Write(arrSalary,nCount);
  
    return 0;
}

在改寫的過程中,小陳按照面向過程中“自頂向下,逐步求精”的設計思路,首先將整個問題分解成了手工輸入、文件輸入、獲取最大值等多個模塊,然后利用多個函數分別將這些模塊一一實現,最后在主函數中將這些函數按照業務流程組織起來,最終解決了整個大問題。當小陳將這個改寫后的工資程序拿給老板用過之后,老板笑得合不攏嘴,一直誇獎說:

“不錯不錯,現在的工資程序不僅可以將輸入的數據保存下來,下次可以接着用,再也不用我每次都重新輸入數據了。同時還對用戶的輸入進行了檢查,很好地防止了輸入錯誤的發生。干的不錯,下個月,漲工資,啊哈哈哈……”

老板的一句“漲工資”,讓小陳又重新燃起了希望,心中想,這次的改寫,讓我見識了函數所體現出來的這種“將一個大問題分解成多個小問題,然后各個擊破”的解決問題的思路,以后遇到再大的問題也不用擔心了。同時他也暗下決心,C++真是個好東西,只要自己好好學,下個月一定能夠漲工資。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM