這是我在本學期C++課程最后的課程設計報告,源代碼將會上傳到GitHub上。
一、背景
隨着經濟的不斷發展,越來越多的摩天大樓拔地而起,而電梯作為高層建築物種的運送人員貨物的設備也越來越被廣泛使用。電梯的運行是電梯與大樓的各個樓層之間的使用者進行交互的一個過程,對於電梯的模擬,就是對這一交互過程的一個模擬。以本校七教辦公樓為例,有着12層樓,配有兩部電梯,在每一層樓中還有着可以呼叫電梯的上下兩個按鈕。本次課程設計使用C++面向對象的程序設計語言來實現對電梯運行的一個模擬,而面向對象的程序設計方法是將事物抽象為對象,對象有着自己的行為和屬性,並通過對消息的反映來完成一定的任務。
二、需求
功能需求:用面向對象技術來實現單部電梯或者多部電梯的模擬運行軟件。
電梯調度策略:順便服務策略。
這種策略在運行控制中所規定的安全前提下,一次將一個方向上的所有呼叫和目標全部完成。然后掉轉運行方向完成另外一個方向上的所有呼叫和目標。
任務說明:
要求根據下面的功能說明描述實現模擬電梯控制軟件
1. 電梯配置
(1) 共有 1 個電梯
(2) 共有 maxfloor 層樓層,這里 maxfloor 暫時取做 9。
(3) 中間層每層有上下兩個按鈕,最下層只有上行按鈕,最上層只有上行按鈕。每層都有相應的指示燈,燈亮表示該按鈕已經被按下,如果該層的上行或者下行請求已經被響應,則指示燈滅
(4) 電梯內共有 maxfloor 個目標按鈕,表示有乘客在該層下電梯。有指示燈指示按鈕是否被按下。乘客按按鈕導致按鈕指示燈亮,如果電已經在該層停靠則該按鈕指示燈滅
(5) 另有一啟動按鈕(GO)。當電梯停在某一樓層后,接受到 GO信息就繼續運行。如果得不到 GO 信息,等待一段時間也自動繼續運行。
(6) 電梯內設有方向指示燈表示當前電梯運行方向。
2. 電梯的運行控制
(1) 電梯的初始狀態是電梯位於第一層處,所有按鈕都沒有按下。
(2) 乘客可以在任意時刻按任何一個目標鈕和呼叫鈕。呼叫和目標
對應的樓層可能不是電梯當前運行方向可達的樓層。
(3) 如果電梯正在向 I 層駛來,並且位於 I 層與相鄰層(向上運行時是 I-1 層或者向下運行時是 I+1 層)之間,則因為安全考慮不響應此時出現的 I 層目標或者請求。如果電梯正好經過了 I 樓層,運行在 I 樓層和下一樓層之間,則為了直接響應此時出現的 I 層目標或者請求,必須至少到達運行方向上的下一樓層然后才能掉頭到達 I 樓層(假設掉頭無須其額外時間),如果 I 樓層不是剛剛經過的樓層則可以在任意位置掉頭,此時
掉頭后經過的第一個樓層不可停。
(4) 電梯系統依照某種預先定義好的策略對隨機出現的呼叫和目標進行分析和響應。
(5) 乘客數量等外界因素(可能導致停靠時間的長短變化)不予考慮。假設電梯正常運行一層的時間是 5S,停靠目標樓層、上下乘客和電梯繼續運行的時間是 5S。
(6) 當電梯停靠某層時,該層的乘客如果錯誤的按目標或呼叫按鈕
都不予響應。
(7) 電梯停要某一層后,苦無目標和呼叫,則電梯處於無方向狀態,方向指示燈全滅,否則電梯內某個方向的指示燈亮,表示電梯將向該方向
運行。等接到“GO”信號后電梯立即繼續運行。若無 GO 信號,則電梯在等了上下乘客和電梯繼續運行時間后也將繼續運行。
(8) 當一個目標(呼叫)已經被服務后,應將對應的指示燈熄滅。
3. 電梯運行的控制策略
順便服務策略:
順便服務是一種最常見的簡單策略。這種策略在運行控制中所規定的安全前提下,一次將一個方向上的所有呼叫和目標全部完成。然后掉轉運行方向完成另外一個方向上的所有呼叫和目標。可以采用設定目標樓層的辦法來實現這個策略,即電梯向一個目標樓層運行,但這個樓層可以修改。具體策略如下:
修改目標樓層的策略:
a.如果電梯運行方向向上,那么如果新到一個介於當前電梯所處樓層和目標樓層之間,又可以安全到達的向上呼叫或者目標,將目標樓層修改為這個新的樓層。
b.如果電梯運行方向向下,那么如果新到一個介於當前電梯所處樓層和目標樓層之間,又可以安全到達的向下呼叫或者目標,將目標樓層修改為這個新的樓層。
確定新的目標樓層:
如果電梯向上運行,當它到達某個目標樓層后,則依照以下順序確定下一個目標樓層:
a.如果比當前層高的樓層有向上呼叫或者目標,那么以最低的高於當前樓層的有向上呼叫或者目標的樓層為目標。
b.如果無法確定目標樓層,那么以最高的向下呼叫或者目標所在樓層為電梯當前目標樓層。
c.如果無法確定目標樓層,那么以最低的向上呼叫所在樓層為電梯當前的目標樓層。
d.如果仍然不能確定目標樓層(此時實際上沒有任何呼叫和目標),那么電梯無目標,運行暫停。
如果電梯向下運行,依照以下順序確定下一目標樓層:
a.如果比當前層低的樓層有向下呼叫或者目標,那么以最高的低於當前樓層的有向下呼叫或者目標的樓層為目標。
b.如果無法確定目標樓層,那么以最低的向上呼叫或者目標所在樓層為電梯當前目標樓層。
c.如果無法確定目標樓層,那么以最高的向下呼叫樓層為目標樓層。
d.如果仍然不能確定目標樓層(此時實際上沒有任何呼叫和目標),那么電梯無目標,運行暫停。
三、模擬電梯的功能
根據實際功能分析,模擬電梯具有復雜的狀態和交互作用,眾多的事件將引起狀態的復雜變化。電梯狀態如下:電梯向上運行狀態、電梯向下運行狀態、電梯停止狀態。 定義為int run_status1向上,2向下,0停止。按鈕有分成兩種一種是電梯外的請求電梯趕來的按鈕,另外一種則是電梯內發出所要到達樓層的按鈕。
基於以上的分析,把電梯的狀態做出如下說明。
(1)電梯初始時刻在一層最初始的運行方向只可能是向上走。
(2)電梯啟動后不管現在所在是第幾層,電梯所要做的就是根據自己目前的運行方向往上查詢是否有電梯外樓層所發出的請求,而對於向下運行方向的請求先不去理會,保存起來。
(3)電梯的請求有兩種,電梯外所在樓層發出的要電梯趕來的請求和電梯內發出的所要到達樓層的請求,這也正好與電梯系統的兩種按鈕所對應。假設電梯正在往4樓向上運行,此時電梯要查詢4樓以上的樓層是否還有人要上樓或者下樓,若沒有人要上樓或下樓,則要查詢4樓以下是否有人要上下樓,如果有則應該改變電梯運行方向,向下運行去接4樓下發出請求的那一個乘客。
(4)對於這兩種請求,可以使用兩個數組來儲存。使用數組儲存的原因是因為,大樓和數組有着相識的特點,都是連續的,這樣就可以使用數組的下標來表示大樓的樓層。
A.對於第一種請求電梯外所在樓層所發出的請求 定義為int eout[10],最開始初始化為0。若eout[i]=1表示第i層有乘客發出了一個向上的請求,若eout[i]=2表示第i層有乘客發出了一個向下的請求,若eout[i]=3表示第i層既有乘客發出一個向下運行的請求,又有乘客發出了一個向上運行的請求。這里我們也可以聯系實際,在電梯還沒有到發出請求的樓層接乘客時,是沒有辦法知道到底有多少個乘客要上電梯的,只有乘客進入電梯之后才知道。
B.對於第二種請求電梯內發出的所要到達樓層的請求, 定義為int floor[10],最開始初始化為0。若floor[i]=1表示到達第i層時,有乘客到達了目的樓層,需要下電梯。
(5)對應的內部電梯按鈕,按下后就會調用相對應的set_floor函數把對應的數組元素修改為1。對於上下按鈕,按下后調用對應的set_out函數,若是向上把對應的數組元素修改為1,若是向下把對應的數組元素修改為2,若是既有向上又有向下把對應的數組元素修改為3。
(6)按照兩種請求的不同,將兩種按鈕依照電梯內輸入和電梯外輸入封裝為兩個函數 :電梯內輸入void inoperate(int i),電梯外輸入void outoperate()。
四、電梯系統流程圖
使用流程圖來描述電梯控制系統的主程序流程:
UML類圖:
五、相關類的實現
電梯的運行是一個電梯與大樓各個樓層的使用者之間交互的過程,在寫類定義的過程中,要注意函數及變量的命名要符合其含義或功能。根據自頂向下的程序設計原則,然后可以根據規划編寫類的定義。
(1)本程序第一個主要的類名為elevator的電梯類。其實現如下:
class elevator { public: elevator(int mf=9);//構造函數,默認9層 ~elevator(); void set_status(int flag);//設置電梯當前的運行狀態 void set_now(int flag);//設置當前電梯所在的樓層 void set_button(int i,int flag);//設置電梯內按鈕的明滅 void set_floor(int i,int flag);//1有,0沒有 void set_out(int i,int flag);//1向上走,2向下走.3既有向上又有向下 int get_floor(int i);//獲得第i層是否有乘客到達的消息 int get_out(int i);//獲得第i層的請求消息 int get_status();//獲得電梯當前的狀態 int get_now();//獲得電梯當前的位置 int get_button(int i); void inoperate(int i);//電梯內部輸入 void outoperate(); cpassenger p[100];//要乘坐電梯的乘客 int counts;//乘坐該電梯乘客數 private: int maxfloor;//共有maxfloor層 int run_status;//電梯運行狀態,1向上,2向下,0停止 int now_floor;//電梯當前所在樓層 int button[10];//樓層按鈕,1按下,0未按 int floor[10];//消息隊列,靜態數據成員 int eout[10];//標記來自電梯外所在的樓層 };
電梯中乘客類 cpassenger,我們需要知道乘客從哪一樓層上的電梯,要在哪一樓層下電梯,同時還要知道現在是否還在電梯上。
class cpassenger { public: int from_floor;//所在樓層 int to_floor;//要去樓層 int in_floor;//是否在電梯中,1在,0不在 char id;//編號 };
(2)最后需要一個building類,電梯類要安裝在building中,同時building還要有模擬的電梯內外部的指示燈和電梯時刻運行狀態。
class building { public: building(int mf=9); ~building(); elevator E; void dis_b();//打印模擬的電梯時刻運行 void dis_vew();//打印電梯內部以及電梯內部的按鈕 int get_mf(); private: int MF;//9層樓 };
六、詳細設計
寫好類定義,接下來就要給每個函數寫函數定義,這就涉及到了電梯模型的具體算法。設計的電梯模型是這樣的。
(1)電梯類的構造函數
elevator::elevator(int mf) { int i; maxfloor=mf; run_status=0;//初始向上運行 now_floor=1;//電梯初始狀態在第一層 counts=1; for(i=1; i<=mf; i++)//消息數組的初始化 { button[i]=0; floor[i]=0; eout[i]=0; } }
電梯默認有9層,電梯起始方向是向上的run_status=0,起始處在1層。通過一個for循環將1到9層的請求數組隊列和電梯內的要到達的數組隊列都清為0。
(2)上面說到的兩個電梯內部和電梯外部按按鈕的兩個函數。
void elevator::inoperate(int i) { int fl; cout<<"請進到電梯內乘客按下所要到達的樓層!"<<endl; cout<<"多個樓層中間用空格隔開,結束輸入請按0:"; while(1) { cin>>fl; if(fl==0) { break; } p[counts].from_floor=i;//乘客來自的樓層 p[counts].to_floor=fl;//要去的樓層 p[counts].in_floor=1;//在電梯中 p[counts].id='A'+counts-1;//給乘客編號 counts++; elevator::set_floor(fl,1);//要到達的樓層 set_button(fl,1);//設置電梯內的顯示按鈕 } }
乘客進入電梯內按下按鈕的同時,乘客類 cpassenger就會捕獲乘客的相關信息,乘客來自的樓層、乘客要去的樓層、乘客是否還在電梯中、乘客獲得的編號,同時floor數組標記要到達的樓層,電梯內相應的指示燈亮起來。
void elevator::outoperate()//在電梯外部發送請求時,還沒有乘客上電梯 { int fl; cout<<"請電梯外部乘客按下所在樓層的上樓信號!"<<endl; cout<<"多個樓層用空格隔開,結束輸入請按0:"; while(1) { cin>>fl; if(fl==0) { break; } if(elevator::get_out(fl)==2)//若已經有了向下的信號 { elevator::set_out(fl,3); } else { elevator::set_out(fl,1);//1向上走 } } cout<<"請電梯外部乘客按下所在樓層的下樓信號!"<<endl; cout<<"多個樓層用空格隔開,結束輸入請按0:"; while(1) { cin>>fl; if(fl==0) { break; } if(elevator::get_out(fl)==1)//若已經有了向上的信號 { elevator::set_out(fl,3); } else { elevator::set_out(fl,2);//2向下走 } } }
電梯外部乘客的請求則要分為上樓elevator::set_out(fl,1)和下樓elevator::set_out(fl,2),如果該樓層同時有乘客上樓和下樓的請求,那么我們就要設置elevator::set_out(fl,3)。
七、例舉其中的算法
判斷電梯是否需要轉變方向的算法:
這里使用電梯運行狀態從向上運行轉變向下運行的一段程序進行說明。電梯要轉變運行狀態向下運行,那么一定是當前電梯內沒有乘客要到達上方的樓層,上方也沒有呼叫電梯的請求,但是下方要有呼叫電梯的請求,否則電梯就會停止了。
flag1=0;//1電梯運行向上 flag2=0;//1電梯運行向下 for(i=cur; i<=mf; i++)//當前電梯所在樓層以上是否有乘客要到達或者是否有請求 { if(A.E.get_floor(i)==1||A.E.get_out(i)!=0)//上方有請求 { flag1=1; break; } } for(j=1; j<=cur; j++)//當前電梯所在樓層以下是否有乘客要到達或者是否有請求 { if(A.E.get_floor(j)==1||A.E.get_out(j)!=0)//下方有請求 { flag2=1; break; } } cnt=0;//計數器 for(i=1; i<=mf; i++)//整棟大樓中是否還有請求或者還有人沒有送達 { if(A.E.get_floor(i)==0&&A.E.get_out(i)==0) { cnt++; } } if(cnt==mf)//電梯里沒有人了,大樓中也沒有請求 { A.E.set_status(0); break; } if(flag1==1) { A.E.set_status(1);//電梯向上 } else if(flag2==1)//電梯向下 { A.E.set_status(2);//電梯向下 break; }
上述算法通過三個循環來遍歷整座大樓的各個樓層的請求和電梯內乘客要去的樓層,時間復雜度為O(n)。至於電梯從向下運行轉變為向上運行的過程則與本算法相同。
八、存在的問題與不足及對策
1.由於本程序采用的是順便服務的電梯調度策略,所以可能會存在着部分用戶等候電梯時間過長,剛好錯過電梯之后需要等候好久的問題,而這主要是由電梯的調度策略決定的,不能很好解決。聯系生活實際,我們平常電梯系統就是這樣實現的,但對於一般的大樓會設置多台電梯,對於本程序如果采用多台電梯,那么主控制程序就需要進行很多的判斷,會造成程序結構復雜的情況,一個很好的解決方式是選擇多線程,通過多線程技術,實現多台電梯同步運行同時修改接受消息隊列數組中的信息,電梯內部各有各自要到達樓層的請求,互補影響。
2.本程序由於時間倉促以及作者本人水平受限,未能配置上圖形化的操作界面,只是借用了編譯器的命令台,影響了使用的觀感和體驗。
九、程序使用說明
該樓有9層,電梯一開始停在1層。該電梯系統主要有兩個界面構成,一個是顯示當前電梯所在樓層的信息,另一個界面顯示電梯內部的信息。電梯當前在那一層,那一層就會用紅色顯示。如果電梯外部的乘客想要進電梯會在所在層發出請求,將按照向上或向下來分類打印請求。
進入電梯后的乘客按下要去的樓層按鈕,該按鈕就會變為紅色,直到該乘客到達要去的樓層后,按鈕才會再次變為無色。同時顯示電梯內部的界面也會顯示當前電梯的運行狀態,若是向上運行,‘上’為紅色;向下運行,‘下’為紅色;若電梯停止,都不為紅色。為了顯示電梯內的成員信息,該程序會按照上電梯的順序為每一個乘客按字母編號,同時打印該乘客的信息,如“ A from 1 -> 3”表示A號乘客從1層上電梯要去3層。
(1)在1樓上來兩位乘客A、B分別要去4和8樓,輸入4 8 0(0表示輸入結束)。
(2)電梯開始了向上運行,電梯內4層和8層的指示燈變紅,這是在第3層有人發出一個向上的請求,在第8層有人發出了一個向下的請求。
(3)沒有乘客在第二層下電梯,電梯繼續向上走。電梯里也沒有人發出向上或者向下的請求。
(4)電梯到達第3層,乘客C進入電梯中,按下8層的按鈕。
(5)電梯還在3層時,6樓有一個向下的請求。
(6)電梯到達第4層時,第一位上電梯的乘客A下了電梯。
(7)從4樓到7樓大樓里一路都沒有向上或向下的請求,電梯持續向上運行,到第8層時,電梯中的第2位和第3位乘客下了電梯。此時也沒有向上或者向下的請求。
(8)此時電梯沒有了向上的請求,就會向下運行。並且也沒有接受到向上或向下的請求。
(9)電梯向下運行到第7層,乘客D進入電梯,按下2層按鈕。此時電梯外沒有向上或者向下的請求。
(10)電梯向下運行到第6層,乘客E進入電梯,按下3層按鈕。此時電梯外沒有向上或者向下的請求。
(11)電梯向下運行,以此到達2層和3層,將第5位和第4位乘客送下,中間過程沒有向上或者向下的請求。
(12)電梯中沒有乘客,電梯外也沒有向上或者向下的請求,電梯將停止運行,程序結束。