1. 程式人生 > >C++ Primer Plus(第六版)程式設計練習答案 第12章 類和動態記憶體分配

C++ Primer Plus(第六版)程式設計練習答案 第12章 類和動態記憶體分配

本章所有程式設計練習的工程原始碼可在此處下載(點選此連結下載),供大家參考交流!

1. 對於下面的類宣告:

class Cow {

         char name[20];

         char *hobby;

         double weight;

public:

         Cow();

         Cow(const char * nm, const char * ho, double wt);

         Cow(const Cow c&);

         ~Cow();

         Cow & operator=(const Cow & c);

         void ShowCow() const;      // display all new data

};

給這個類提供實現,並編寫一個使用所有成員函式的小程式。

對於本題,首先題中已經給出了類宣告,所以標頭檔案不需要自己定義,可以直接使。

所以標頭檔案cow.h程式碼如下:

// cow.h -- head file
#ifndef COW_H_
#define COW_H_

class Cow
{
private:
    char name[20];
    char * hobby;
    double weight;
public:
    Cow();
    Cow(const char * nm, const char * ho, double wt);
    Cow(const Cow & c);
    ~Cow();
    Cow & operator=(const Cow & c);
    void ShowCow() const;
};

#endif

在這裡,我們之所以在書中提供的類宣告的前後加上#ifndef, #define, #endif主要是為了防止該.h檔案被重複引用。。

聲明瞭標頭檔案之後,我們首先要考慮去定義標頭檔案中的函式宣告。

標頭檔案的私有成員變數中有字串陣列和字串指標,因此需要用到strcpy_s函式,所以標頭檔案中需要包含<cstring>;在建構函式中,唯一需要注意的就是字串指標變數需要使用new來動態分配記憶體,然後再使用strcpy_s函式進行賦值,而相應的解構函式一定要記得將建構函式中使用new動態分配的記憶體delete掉。

另外,在過載運算子=時,需要在一開始delete一下,以防止此時this指標的hobby變數儲存的地址是被分配使用過的。

所以實現檔案cow.cpp程式碼如下:

// cow.cpp -- containing the functions' definition 

#include "stdafx.h"
#include <iostream>
#include <cstring>
#include "cow.h"

using std::cout;
using std::endl;

Cow::Cow()
{
    strcpy_s(name, 20, "new cow");
    hobby = new char[4];
    strcpy_s(hobby, 4, "cow");
    weight = 0.0;
}

Cow::Cow(const char * nm, const char * ho, double wt)
{
    weight = wt;
    hobby = new char[strlen(ho) + 1];
    strcpy_s(hobby, strlen(ho)+1, ho);
    strcpy_s(name, 20, nm);
}

Cow::Cow(const Cow & c)
{
    strcpy_s(name, 20, c.name);
    hobby = new char[strlen(c.hobby) + 1];
    strcpy_s(hobby, strlen(c.hobby) + 1, c.hobby);
    weight = c.weight;
}

Cow::~Cow()
{
    delete[] hobby;
}

Cow & Cow::operator=(const Cow & c)
{
    delete[] hobby;
    hobby = new char[strlen(c.hobby) + 1];
    strcpy_s(hobby, strlen(c.hobby) + 1, c.hobby);
    weight = c.weight;
    strcpy_s(name, 20, c.name);
    return *this;
}

void Cow::ShowCow() const
{
    cout << "Now a new cow!\n";
    cout << "The name is " << name << endl;
    cout << "The hobby is " << hobby << endl;
    cout << "The weight is " << weight << endl;
}

接下來我們來考慮如何使用這些成員函式組成檢驗檔案。

其實本題的檔案的主要功能只有3個,各種方式的函式初始化,使用過載的運算子=,使用ShowCow函式進行顯示。

檢驗檔案checkcow.cpp程式碼如下:

// checkcow.cpp -- check all the functions
#include "stdafx.h"
#include <iostream>
#include "cow.h"

using namespace std;

int main()
{
    cout << "Let's start to see our cows!\n";
    Cow c1;
    c1.ShowCow();
    Cow c2("yellow", "eat grass", 123.456);
    c2.ShowCow();
    Cow c3("black", "drink water", 222.333);
    c3.ShowCow();
    c3 = c1;
    Cow c4(c2);
    c3.ShowCow();
    c4.ShowCow();
    system("pause");
    return 0;
}

執行結果如下圖所示:

2. 通過完成下面的工作來改進String類宣告(即將String1.h升級為String2.h)。

a. 對+運算子進行過載,使之可將兩個字串合併成1個。

b. 提供一個Stringlow()成員函式,將字串中所有的字母字元轉換為小寫(別忘了cctype系列字元函式)。

c. 提供String()成員函式,將字串中所有字母字元轉換成大寫。

d. 提供一個這樣的成員函式,它接受一個char引數,返回該字元在字串中出現的次數。

使用下面的程式來測試您的工作:

(……程式碼忽略……)

輸出應與下面相似:

(……程式碼忽略……)

本題是在String1.h的基礎上進行升級,新增幾項功能再測試。

首先,需要過載+運算子,將兩個字串合併成1個,這裡看起來只需要新增一個成員函式,其實不然。由於檢驗檔案中既有將string物件相加,又有將字串指標相加,還存在將string物件和字串指標相加的情況,所以我們需要對應的定義3個函式來分別過載這三種情況下的+運算子。

其次,提供兩個函式將字串中的所有字母字元轉換為小寫或大寫,這個功能使用<cctype>中的函式將非常輕鬆。

最後,提供一個成員函式,接受一個char引數,返回該引數在字串中出現的次數,該函式將需要遍歷整個字串進行計數。

對於標頭檔案,我們只需要新增以上6個函式即可,所以標頭檔案string2.h程式碼如下:

// string2.h -- fixed and augmented string class definition
#ifndef STRING2_H_
#define STRING2_H_

#include <iostream>
using std::ostream;
using std::istream;

class String
{
private:
    char * str;
    int len;
    static int num_strings;
    static const int CINLIM = 80;
public:
    String(const char * s);
    String();
    String(const String &);
    ~String();
    int length() const { return len; }
    void stringlow();
    void stringup();
    int has(char x);
    String & operator=(const String &);
    String & operator=(const char *);
    char & operator[] (int i);
    const char & operator[] (int i) const;
    friend bool operator<(const String &st1, const String &st2);
    friend bool operator>(const String &st1, const String &st2);
    friend bool operator==(const String &st, const String &st2);
    friend ostream & operator<<(ostream & os, const String &st);
    friend istream & operator>>(istream & is, String & st);
    static int HowMany();
    String operator+(const String &) const;
    String operator+(const char *) const;
    friend String operator+(const char *, const String &);
};

對於實現檔案,其他的功能可以沿用string1.cpp,新新增的幾個函式需要自定義。

首先,對於過載+運算子,我們分為三個函式。第一個是兩個string物件之間的加法,在這裡呢,我們可以使用strcat_s函式,直接將兩個string物件進行拼接,但是由於預設情況下string物件的最後一個字元都是'\0',所以處理起來比較麻煩,我選擇依然使用strcpy_s函式,只是複製的地址不一樣,比如說str1的長度為10,str2的長度為10,我們新定義一個str3,要想把str1的內容和str2的內容加起來放進str3中,就把str1的內容從str3的第一個字元處開始賦值,把str2的內容從str3的第11個字元處開始賦值,這樣就可以了。對於字串指標變數的情況,可以完全類似處理。對於字串指標變數和string物件相加的情況,我們可以人為地進行強制型別轉換,將字串指標變數轉換為string物件,再加,那麼此時相加就會呼叫前面定義的兩個string物件相加的函式,即可以完成任務。

其次,我們在實現檔案中包含標頭檔案<cstring>,然後定義stringow()函式和stringup()函式,分別來對字串的字母字元進行小寫或大寫的轉換。在轉換的時候,我們需要首先對整個字串進行遍歷,然後使用tolower()函式和toupper()函式來進行大小寫轉換,注意這裡this是指標,指向某一個變數時要使用->符號。

最後,我們需要定義has()函式。該函式遍歷整個字串,然後識別輸入的特定char型別變數,然後計數。我們在for迴圈內部巢狀一個if條件判斷即可。

實現檔案string2.cpp程式碼如下所示:

// string2.cpp -- String class methods
//

#include "stdafx.h"
#include <cstring>
#include "string2.h"
#include <cctype>
using std::cin;
using std::cout;

int String::num_strings = 0;

int String::HowMany()
{
    return num_strings;
}

String::String(const char * s)
{
    len = std::strlen(s);
    str = new char[len + 1];
    strcpy_s(str, std::strlen(s) + 1, s);
    num_strings++;
}

String::String()
{
    len = 4;
    str = new char[1];
    str[0] = '\0';
    num_strings++;
}

String::String(const String & st)
{
    num_strings++;
    len = st.len;
    str = new char[len + 1];
    strcpy_s(str, std::strlen(st.str) + 1, st.str);
}

String::~String()
{
    --num_strings;
    delete[] str;
}

void String::stringlow()
{
    for (int i = 0; i < this->len + 1; i++)
    {
        this->str[i] = tolower(this->str[i]);
    }
}

void String::stringup()
{
    for (int i = 0; i < this->len + 1; i++)
    {
        this->str[i] = toupper(this->str[i]);
    }
}

int String::has(char x)
{
    int count = 0;
    for (int i = 0; i < this->len + 1; i++)
    {
        if (this->str[i] == x)
            count++;
    }
    return count;
}

String & String::operator=(const String & st)
{
    if (this == &st)
        return *this;
    delete[] str;
    len = st.len;
    str = new char[len + 1];
    strcpy_s(str, std::strlen(st.str) + 1, st.str);
    return *this;
}

String & String::operator=(const char * s)
{
    delete[] str;
    len = std::strlen(s);
    str = new char[len + 1];
    strcpy_s(str, std::strlen(s) + 1, s);
    return *this;
}

char & String::operator[] (int i)
{
    return str[i];
}

const char & String::operator[] (int i) const
{
    return str[i];
}

bool operator<(const String &st1, const String &st2)
{
    return (std::strcmp(st1.str, st2.str) < 0);
}

bool operator>(const String &st1, const String &st2)
{
    return st2 < st1;
}

bool operator==(const String &st1, const String &st2)
{
    return (std::strcmp(st1.str, st2.str) == 0);
}

ostream & operator<<(ostream & os, const String & st)
{
    os << st.str;
    return os;
}

istream & operator>>(istream & is, String & st)
{
    char temp[String::CINLIM];
    is.get(temp, String::CINLIM);
    if (is)
        st = temp;
    while (is &&is.get() != '\n')
        continue;
    return is;
}

String String::operator+(const String & st) const
{
    int total_len = len + st.len;
    char *temp = new char[total_len + 1];
    strcpy_s(temp, len + 1, str);
    strcpy_s(temp + len, st.len + 1, st.str);
    temp[total_len] = '\0';
    return String(temp);
}

String String::operator+(const char * s) const
{
    int total_len = len + strlen(s);
    char *temp = new char[total_len + 1];
    strcpy_s(temp, len + 1, str);
    strcpy_s(temp + len, std::strlen(s) + 1,s);
    temp[total_len] = '\0';
    return String(temp);
}

String operator+(const char * s, const String & st)
{
    return String(s) + st;
}

檢驗檔案題中直接給出,saying2.cpp程式碼如下所示:

// saying1.cpp -- using expanded String class
#include "stdafx.h"
#include <iostream>
#include "string2.h"
using namespace std;
int main()
{
    String s1(" and I am a C++ student.");
    String s2 = "Please enter your name: ";
    String s3;
    cout << s2;
    cin >> s3;
    s2 = "My name is " + s3;
    cout << s2 << ".\n";
    s2 = s2 + s1;
    s2.stringup();
    cout << "The string\n" << s2 << "\ncontains " << s2.has('A') << " 'A' characters in it.\n";
    s1 = "red";
    String rgb[3] = { String(s1), String("green"), String("blue") };
    cout << "Enter the name of a primary color for mixing light: ";
    String ans;
    bool success = false;
    while (cin >> ans)
    {
        ans.stringlow();
        for (int i = 0; i < 3; i++)
        {
            if (ans == rgb[i])
            {
                cout << "That's right!\n";
                success = true;
                break;
            }
        }
        if (success)
            break;
        else
            cout << "Try again!\n";
    }
    cout << "Bye\n";
    system("pause");
    return 0;
}

執行結果如下圖所示:

從執行結果可以看出,該檢驗檔案完成的功能和預期完全一致。

3. 新編寫程式清單10.7和程式清單10.8描述的Stock類,使之使用動態記憶體分配的記憶體,而不是string類物件來儲存股票名稱。另外,使用過載的operator<<()定義代替show()成員函式。再使用程式清單10.9測試新的定義程式。

本題要求將Stock類進行修改,使用動態記憶體分配代替string類物件,所以string物件company必須改為char *型別,然後去掉show()函式,改為使用過載的operator<<()來進行輸出,該函式我們選擇使用友元。

所以標頭檔案stock20.h程式碼如下:

// stock20.h -- augmented version
#ifndef STOCK20_H_
#define STOCK20_H_
#include <iostream>
using std::ostream;

class Stock
{
private:
    char * company;
    int shares;
    double share_val;
    double total_val;
    void set_tot() { total_val = shares * share_val; }
public:
    Stock();
    Stock(const char * co, long n = 0, double pr = 0.0);
    ~Stock();
    void buy(long num, double price);
    void sell(long num, double price);
    void update(double price);
    friend ostream & operator<<(ostream & os, const Stock &st);
    const Stock & topval(const Stock & s) const;
};

#endif

對於實現檔案,當company變數從string物件變成char *型別之後,建構函式那裡就必須要改變,必須使用new來動態分配它的記憶體,而且解構函式必須要delete。對於過載的<<運算子,我們也按照原來的show()函式一樣的格式,對ios_base裡面的處理不變,只是在此之後,按照標準的過載<<運算子的方式進行輸出,同時將cout完全換成我們的輸出os。

實現檔案stock20.cpp程式碼如下:

// stock20.cpp -- augnented version
#include "stdafx.h"
#include <iostream>
#include "stock20.h"
using std::ostream;

Stock::Stock()
{
    company = new char[std::strlen("no name") + 1];
    strcpy_s(company, std::strlen("no name") + 1, "no name");
    shares = 0;
    share_val = 0.0;
    total_val = 0.0;
}

Stock::Stock(const char * co, long n, double pr)
{
    company = new char[std::strlen(co) + 1];
    strcpy_s(company, std::strlen(co) + 1, co);
    if (n < 0)
    {
        std::cout << "Number of shares can't be negative; " << company << " shares set to 0.\n";
        shares = 0;
    }
    else
        shares = n;
    share_val = pr;
    set_tot();
}

Stock::~Stock()
{
    delete[] company;
}

void Stock::buy(long num, double price)
{
    if (num < 0)
    {
        std::cout << "Number of shares purchased can't be negative. " << "Transaction is aborted.\n";
    }
    else
    {
        shares += num;
        share_val = price;
        set_tot();
    }
}

void Stock::sell(long num, double price)
{
    using std::cout;
    if (num < 0)
    {
        cout << "Number of shares sold can't be negative. " << "Transaction is aborted.\n";
    }
    else if (num > shares)
    {
        cout << "You can't sell more than you have! " << "Transaction is aborted.\n";
    }
    else
    {
        shares -= num;
        share_val = price;
        set_tot();
    }
}

void Stock::update(double price)
{
    share_val = price;
    set_tot();
}

ostream & operator<<(ostream & os, const Stock &st)
{
    using std::ios_base;
    ios_base::fmtflags orig = os.setf(ios_base::fixed, ios_base::floatfield);
    std::streamsize prec = os.precision(3);

    os << "Company: " << st.company << " Shares: " <<st.shares << "\n";
    os << "Share Price: $" << st.share_val;
    os.precision(2);
    os << " Total Worth: $" << st.total_val << "\n";

    os.setf(orig, ios_base::floatfield);
    os.precision(prec);
    return os;
}

const Stock & Stock::topval(const Stock & s) const
{
    if (s.total_val > total_val)
        return s;
    else
        return *this;
}

檢驗檔案題目要求直接使用程式清單10.9,所以usestock20.cpp程式碼如下:

// usestock20.cpp -- using the Stock class
// compile with stock20.cpp
#include "stdafx.h"
#include <iostream>
#include "stock20.h"
using std::cout;

const int STKS = 4;
int main()
{
    Stock stocks[STKS] =
    {
        Stock("NanoSmart", 12, 20.0),
        Stock("Boffo Objects", 200, 2.0),
        Stock("Monolithic Obelisks", 130, 3.25),
        Stock("Fleep Enterprises", 60, 6.5)
    };

    std::cout << "Stock holdings:\n";
    int st;
    for (st = 0; st < STKS; st++)
        cout << stocks[st];
    const Stock * top = &stocks[0];
    for (st = 1; st < STKS; st++)
        top = &top->topval(stocks[st]);
    cout << "\nMost valuable holding:\n";
    cout << *top;
    system("pause");
    return 0;
}

執行結果如下圖所示:

4. 請看下面程式清單10.10定義的Stack類的變數:

(……程式碼忽略……)

正如私有成員表明的,這個類使用動態記憶體分配的陣列來儲存棧項。請重新編寫方法,以適應這種新的表示法,並編寫一個程式來演示所有的方法,包括複製建構函式和賦值運算子。

本題提供了標頭檔案程式碼,需要注意的地方在於,本題的私有成員中,定義了一個無符號long型別的指標,對於這種型別的變數,我們同樣需要使用new來動態分配記憶體。

標頭檔案stack.h程式碼如下:

// stack.h -- class declaration for the stack ADT
#include <iostream>
using std::ostream;
typedef unsigned long Item;

class Stack
{
private:
    enum { MAX = 10 };
    Item * pitems;
    int size;
    int top;
public:
    Stack(int n = MAX);
    Stack(const Stack & st);
    ~Stack();
    bool isempty() const;
    bool isfull() const;
    bool push(const Item & item);
    bool pop(Item & item);
    Stack & operator=(const Stack & st);
    friend ostream & operator<<(ostream & os, const Stack &st);
};

對於實現檔案,大部分內容我們可以參考程式清單10.11,而新增的size變數和修改過的pitems物件,我們需要單獨編寫,且要注意pitems物件必須使用new來動態分配記憶體,解構函式裡面必須將其delete,另外在賦值運算子進行過載時,必須首先將pitems物件進行delete,以防止它的記憶體已經被佔用。

另外我們需要額外編寫賦值建構函式,因為要防止初始化時預設建構函式產生二義性;還需要過載<<運算子用來輸出。

實現檔案stack.cpp程式碼如下:

// stack.cpp -- functions' definition
#include "stdafx.h"
#include <iostream>
using std::endl;
#include "stack.h"

Stack::Stack(int n)
{
    pitems = new Item[n];
    for (int i = 0; i < n; i++)
    {
        pitems[i] = 0;
    }
    top = 0;
    size = n;
}

Stack::Stack(const Stack & st)
{
    pitems = new Item[st.size];
    for (int i = 0; i < st.size; i++)
    {
        pitems[i] = st.pitems[i];
    }
    top = st.top;
    size = st.size;
}

Stack::~Stack()
{
    delete[] pitems;
}

bool Stack::isempty() const
{
    return top == 0;
}

bool Stack::isfull() const
{
    return top == MAX;
}

bool Stack::push(const Item & item)
{   
    if (top < MAX)
    {
        pitems[top++] = item;
        return true;
    }
    else
        return false;
}

bool Stack::pop(Item & item)
{
    if (top > 0)
    {
        item = pitems[--top];
        return true;
    }
    else
        return false;
}

Stack & Stack::operator=(const Stack & st)
{
    if (this == &st)
        return *this;
    delete[] pitems;
    top = st.top;
    size = st.size;
    pitems = new Item[st.size];
    for (int i = 0; i < st.size; i++)
    {
        pitems[i] = st.pitems[i];
    }
    return *this;
}

ostream & operator<<(ostream & os, const Stack & st)
{
    for (int i = 0; i < st.top; i++)
    {
        os << st.pitems[i] << endl;
    }
    return os;
}

接下來我們可以在程式清單10.12的基礎上進行少量修改,就可以檢驗上面編寫的類方法了。對於檢驗棧是否為空和是否為滿,以及入棧出棧,我們都可以完全參考程式清單10.12,下面我們主要檢驗新的功能,包括使用複製建構函式進行賦值,使用過載的=運算子和<<運算子。

檢驗檔案usestack.cpp程式碼如下:

// usestack.cpp -- tetsing the Stack class
#include "stdafx.h"
#include <iostream>
#include <cctype>
#include "stack.h"


int main()
{
    using namespace std;
    Stack st;
    char ch;
    unsigned long po;
    cout << "Please enter A to add a purchase order,\n" << "P to process a PO, Q to quit.\n";
    while (cin >> ch && toupper(ch) != 'Q')
    {
        while (cin.get() != '\n')
            continue;
        if (!isalpha(ch))
        {
            cout << '\a';
            continue;
        }
        switch (ch)
        {
        case 'A':
        case 'a': cout << "Enter a PO number to add: ";
            cin >> po;
            if (st.isfull())
                cout << "stack already full.\n";
            else
                st.push(po);
            break;
        case 'p':
        case 'P': if (st.isempty())
            cout << "stack already empty.\n";
                  else
                  {
                      st.pop(po);
                      cout << "PO #" << po << " popped.\n";
                  }
                  break;
        }
        cout << "Please enter A to add a purchase order,\n" << "P to process a PO, or Q to quit.\n";
    }
    cout << "So our stack is:\n" << st;
    Stack st2;
    st2 = st;
    cout << "stack2 = stack is:\n" << st2;
    cout << "Bye\n";
    system("pause");
    return 0;
}

執行結果如下圖所示:

從結果圖可以看出,我們首先檢驗了一下程式清單10.12檢驗的功能,都可以正確執行,接下來我們對棧內資料進行了填充,然後進行了複製建構函式式賦值,以及=運算子賦值,都是正確的。

5. Heather銀行進行的研究表明,ATM客戶不希望排隊時間超過1分鐘。使用程式清單12.10中的模擬,找出要使平均等候時間為1分鐘,每小時到達的客戶數應為多少(試驗時間不短於100小時)?

首先,我個人認為本題的所謂不希望排隊時間不超過1分鐘是寫錯了,應該是不希望排隊時間超過1分鐘,後面的程式碼也都是基於這個假設寫的。

其次,由於本題涉及的功能完全是程式清單12.10標頭檔案和程式清單12.11實現檔案可以完成的,所以標頭檔案我們不需要修改,直接使用即可。

所以標頭檔案queue.h程式碼如下:

// queue.h -- interface for a queue
#ifndef QUEUE_H_
#define QUEUE_H_

class Customer
{
private:
    long arrive;
    int processtime;
public:
    Customer()
    {
        arrive = processtime = 0;
    }
    void set(long when);
    long when() const
    {
        return arrive;
    }
    int ptime() const
    {
        return processtime;
    }
};

typedef Customer Item;

class Queue
{
private:
    struct Node
    {
        Item item;
        struct Node * next;
    };
    enum { Q_SIZE = 10 };
    Node * front;
    Node * rear;
    int items;
    const int qsize;
    Queue(const Queue & q) : qsize(0) { }
    Queue & operator=(const Queue & q)
    {
        return *this;
    }
public:
    Queue(int qs = Q_SIZE);
    ~Queue();
    bool isempty() const;
    bool isfull() const;
    int queuecount() const;
    bool enqueue(const Item &item);
    bool dequeue(Item &item);
};

#endif

實現檔案queue.cpp程式碼如下:

// queue.cpp -- Queue and Customer methods
#include "stdafx.h"
#include "queue.h"
#include <cstdlib>

Queue::Queue(int qs) : qsize(qs)
{
    front = rear = NULL;
    items = 0;
}

Queue::~Queue()
{
    Node * temp;
    while (front != NULL)
    {
        temp = front;
        front = front->next;
        delete temp;
    }
}

bool Queue::isempty() const
{
    return items == 0;
}

bool Queue::isfull() const
{
    return items == qsize;
}

int Queue::queuecount() const
{
    return items;
}

bool Queue::enqueue(const Item &item)
{
    if (isfull())
        return false;
    Node * add = new Node;
    add->item = item;
    add->next = NULL;
    items++;
    if (front == NULL)
        front = add;
    else
        rear->next = add;
    rear = add;
    return true;
}

bool Queue::dequeue(Item &item)
{
    if (front == NULL)
        return false;
    item = front->item;
    items--;
    Node * temp = front;
    front = front->next;
    delete temp;
    if (items == 0)
        rear = NULL;
    return true;
}

void Customer::set(long when)
{
    processtime = std::rand() % 3 + 1;
    arrive = when;
}

接下來主要需要修改的就是檢驗檔案了。

首先呢,我們這裡假設試驗時間不短於100小時,為了簡單起見,我就將試驗時間定為100小時,接下來我們初始化perhour為1,再對perhour變數進行累加迴圈,即對每小時到達的客戶數量進行累加迴圈;定義一個avetime變數,表示平均等候時間;使用while語句,判斷條件為(perhour++ && avetime <= 1),即從每小時到達客戶數為1開始,依次增加每小時到達的客戶數,同時滿足平均等候時間不超過1分鐘,以此作為迴圈條件。再在迴圈內部去進行for迴圈操作。

這裡需要注意的是,在for迴圈之前,我們需要先清空佇列,這樣才是真正的從完全為空開始進行試驗,不然無法控制初始條件下佇列中原來就存在的客戶數量。

在for迴圈的最後,我們需要計算一下avetime,即將line_wait除以served。

具體詳情請閱讀程式碼,檢驗檔案bank.cpp程式碼如下:

// bank.cpp -- using the Queue interface
// compile with queue.cpp

#include "stdafx.h"
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "queue.h"

const int MIN_PER_HR = 60;
bool newcustomer(double x);

int main()
{
    using std::cin;
    using std::cout;
    using std::endl;
    using std::ios_base;

    std::srand(std::time(0));
    cout << "Case Study: Bank of Heather Automatic Teller\n";
    cout << "Enter maximum size of queue: ";
    int qs;
    cin >> qs;
    Queue line(qs);

    cout << "The simulation hours: 100\n";
    int hours = 100;
    long cyclelimit = MIN_PER_HR * hours;

    double perhour;
    double min_per_cust;
    perhour = 1;
    Item temp;
    long turnaways = 0;
    long customers = 0;
    long served = 0;
    long sum_line = 0;
    int wait_time = 0;
    long line_wait = 0;
    double avetime = 0;
    while (perhour++ && avetime <= 1)
    {
        while (!line.isempty())
        {
            line.dequeue(temp);
        }
        min_per_cust = MIN_PER_HR / perhour;
		
        for (int cycle = 0; cycle < cyclelimit; cycle++)
        {
            if (newcustomer(min_per_cust))
            {
                if (line.isfull())
                    turnaways++;
                else
                {
                    customers++;
                    temp.set(cycle);
                    line.enqueue(temp);
                }
            }
            if (wait_time <= 0 && !line.isempty())
            {
                line.dequeue(temp);
                wait_time = temp.ptime();
                line_wait += cycle - temp.when();
                served++;
            }
            if (wait_time > 0)
                wait_time--;
            sum_line += line.queuecount();
        }

        if (customers > 0)
        {
            cout << "customers accepted: " << customers << endl;
            cout << "  customers served: " << served << endl;
            cout << "         turnaways: " << turnaways << endl;
            cout << "average queue size: ";
            cout.precision(2);
            cout.setf(ios_base::fixed, ios_base::floatfield);
            cout << (double)sum_line / cyclelimit << endl;
            cout << " average wait time: " << (double)line_wait / served << " minutes\n";
        }
        else
            cout << "No customers!\n";
        avetime = (double)line_wait / served;
    }
    cout << "When there comes " << perhour << " people per hour, the average wait time will be about 1 minute.\n";
    cout << "Done!\n";

    system("pause");
    return 0;
}

bool newcustomer(double x)
{
    return (std::rand() * x / RAND_MAX < 1);
}

執行結果如下圖所示:

在這裡我輸入的佇列最大客戶數是10,由於需要輸出100組資料,所以我只截了開頭和結尾,無法全部顯示中間的細節結果。從最後的結果上來看,根據100小時的實驗,當每小時到達客戶數為25.00時,排隊時間剛好不超過1分鐘。

6. Heather銀行想知道,如果再開設一臺ATM,情況將如何。請對模擬進行修改,以包含兩個佇列。假設當第一臺ATM前的排隊人數少於第二臺ATM時,客戶將排在第一隊,否則將排在第二隊。然後再找出要使平均等候時間為1分鐘,每小時到達的客戶數應該為多少(注意,這是一個非線性問題,即將ATM數量加倍,並不能保證每小時處理的客戶數量也翻倍,並確保客戶等候的時間少於1分鐘)?

本題要求多開設一個ATM,所以我們就會有兩個佇列,一起進行服務,客戶到達之後的排隊方式即是,如果第一臺人數少於第二臺,則排第一隊,否則排第二隊。所以在這裡,標頭檔案和實現檔案依然不變,因為本質性功能依然沒變。

所以標頭檔案queue.h程式碼如下:

// queue.h -- interface for a queue
#ifndef QUEUE_H_
#define QUEUE_H_

class Customer
{
private:
    long arrive;
    int processtime;
public:
    Customer()
    {
        arrive = processtime = 0;
    }
    void set(long when);
    long when() const
    {
        return arrive;
    }
    int ptime() const
    {
        return processtime;
    }
};

typedef Customer Item;

class Queue
{
private:
    struct Node
    {
        Item item;
        struct Node * next;
    };
    enum { Q_SIZE = 10 };
    Node * front;
    Node * rear;
    int items;
    const int qsize;
    Queue(const Queue & q) : qsize(0) { }
    Queue & operator=(const Queue & q)
    {
        return *this;
    }
public:
    Queue(int qs = Q_SIZE);
    ~Queue();
    bool isempty() const;
    bool isfull() const;
    int queuecount() const;
    bool enqueue(const Item &item);
    bool dequeue(Item &item);
};

#endif

實現檔案queue.cpp程式碼如下:

// queue.cpp -- Queue and Customer methods
#include "stdafx.h"
#include "queue.h"
#include <cstdlib>

Queue::Queue(int qs) : qsize(qs)
{
    front = rear = NULL;
    items = 0;
}

Queue::~Queue()
{
    Node * temp;
    while (front != NULL)
    {
        temp = front;
        front = front->next;
        delete temp;
    }
}

bool Queue::isempty() const
{
    return items == 0;
}

bool Queue::isfull() const
{
    return items == qsize;
}

int Queue::queuecount() const
{
    return items;
}

bool Queue::enqueue(const Item &item)
{
    if (isfull())
        return false;
    Node * add = new Node;
    add->item = item;
    add->next = NULL;
    items++;
    if (front == NULL)
        front = add;
    else
        rear->next = add;
    rear = add;
    return true;
}

bool Queue::dequeue(Item &item)
{
    if (front == NULL)
        return false;
    item = front->item;
    items--;
    Node * temp = front;
    front = front->next;
    delete temp;
    if (items == 0)
        rear = NULL;
    return true;
}

void Customer::set(long when)
{
    processtime = std::rand() % 3 + 1;
    arrive = when;
}

接下來我們著重修改檢驗檔案。

首先,本題有兩臺ATM,所以我們需要兩個Queue物件,分別稱為line1和line2,接下來的處理方法和上一題類似,我們假設試驗時間為100小時,初始化perhour為1,然後自定義avetime,對perhour進行累加迴圈,同時判斷avetime <= 1;但是這裡需要注意,我們現在是兩個ATM同時服務,所以到達的客戶需要有一個選擇,即排哪個隊,根據題目要求,我們肯定需要有兩個line_size和wait_time,所以定義wait_time1和wait_time2分別表示佇列1和佇列2的等候時間,定義line1_size和line2_size分別表示佇列1和佇列2的尺寸。

在迴圈內部,我們首先依然還是要清空佇列,這裡就要清空兩次了;然後在for迴圈內部,當有客戶到達時,判斷是否turnaway就需要去判斷兩個隊列了;如果不滿足turnaway的條件,就比較line1_size和line2_size,來決定到達的客戶排哪個佇列;後面判斷出隊和計算sum_line,注意都需要進行兩次。

其他操作和上題相同,注意有些需要操作兩次即可。

具體詳情請閱讀程式碼,檢驗檔案bank.cpp程式碼如下:

// bank.cpp -- using the Queue interface
// compile with queue.cpp

#include "stdafx.h"
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "queue.h"

const int MIN_PER_HR = 60;
bool newcustomer(double x);

int main()
{
    using std::cin;
    using std::cout;
    using std::endl;
    using std::ios_base;

    std::srand(std::time(0));
    cout << "Case Study: Bank of Heather Automatic Teller\n";
    cout << "Enter maximum size of queue: ";
    int qs;
    cin >> qs;
    Queue line1(qs);
    Queue line2(qs);

    cout << "The simulation hours: 100\n";
    int hours = 100;
    long cyclelimit = MIN_PER_HR * hours;

    double perhour;
    double min_per_cust;
    perhour = 1;
    Item temp;
    long turnaways = 0;
    long customers = 0;
    long served = 0;
    long sum_line = 0;
    int wait_time1 = 0;
    int wait_time2 = 0;
    int line1_size = 0;
    int line2_size = 0;
    long line_wait = 0;
    double avetime = 0;

    while (perhour++ && avetime <= 1)
    {
        while (!line1.isempty())
        {
            line1.dequeue(temp);
        }
        while (!line2.isempty())
        {
            line2.dequeue(temp);
        }
        min_per_cust = MIN_PER_HR / perhour;

        for (int cycle = 0; cycle < cyclelimit; cycle++)
        {
            if (newcustomer(min_per_cust))
            {
                if (line1.isfull() && line2.isfull())
                    turnaways++;
                else if(line1_size < line2_size)
                {
                    customers++;
                    temp.set(cycle);
                    line1.enqueue(temp);
                    line1_size++;
                }
                else
                {
                    customers++;
                    temp.set(cycle);
                    line2.enqueue(temp);
                    line2_size++;
                }
            }
            if (wait_time1 <= 0 && !line1.isempty())
            {
                line1.dequeue(temp);
                line1_size--;
                wait_time1 = temp.ptime();
                line_wait += cycle - temp.when();
                served++;
            }
            if (wait_time2 <= 0 && !line2.isempty())
            {
                line2.dequeue(temp);
                line2_size--;
                wait_time2 = temp.ptime();
                line_wait += cycle - temp.when();
                served++;
            }
            if (wait_time1 > 0)
                wait_time1--;
            if (wait_time2 > 0)
                wait_time2--;
            sum_line += line1.queuecount();
            sum_line += line2.queuecount();
        }

        if (customers > 0)
        {
            cout << "customers accepted: " << customers << endl;
            cout << "  customers served: " << served << endl;
            cout << "         turnaways: " << turnaways << endl;
            cout << "average queue size: ";
            cout.precision(2);
            cout.setf(ios_base::fixed, ios_base::floatfield);
            cout << (double)sum_line / cyclelimit << endl;
            cout << " average wait time: " << (double)line_wait / served << " minutes\n";
        }
        else
            cout << "No customers!\n";
        avetime = (double)line_wait / served;
    }
    cout << "When there comes " << perhour << " people per hour, the average wait time will be about 1 minute.\n";
    cout << "Done!\n";

    system("pause");
    return 0;
}

bool newcustomer(double x)
{
    return (std::rand() * x / RAND_MAX < 1);
}

執行結果如下圖所示:

從結果上可以看出,當只有1臺ATM時,每小時到達客戶數為25時,平均等候時間為1分鐘,而兩臺ATM時,每小時到達客戶數為60,平均等候時間為1分鐘,所以效果不止翻倍。

本章所有程式設計練習的工程原始碼可在此處下載(點選此連結下載),供大家參考交流!