很多書在一開始就開始學習josephus問題,為了讓大家前面學起來較為容易我把前面涉及到此問題的地方都故意去掉了,現在我們已經學習過了結構體和類,所以放在這裡學習可能更合適一些。
在正式開始學習之前我們先回顧一下如何利用陣列和結構體的方式來解決,最後我們再看一下如何利用物件導向的抽象理念進行解決此問題的程式設計,相互對比,找出效率最高,最容易理解,最方便維護的程式來,說明利用物件導向的抽象理念進行程式設計的好處。
josephus問題其實就是乙個遊戲,一群小孩圍成乙個圈,設定乙個數,這個數是個小於小孩總數大於0的乙個整數,從第乙個小孩開始報數,當其中乙個小孩報到你設定的那個數的時候離開那個圈,這樣一來反覆報下去,直到只剩下最後乙個小孩的時候那個小孩就是勝利者,寫程式來找出這個小孩。
以下是陣列方法:
由於陣列的限制我們必須預先假設好有多少個小孩,離開的小孩他自身設定為0來標記離開狀態。
**如下:
#include
using
namespace std;
void
main()
} if(k==num)
cout
<
cin.get();
}就陣列解決來看,程式簡短但效率不高可讀性也不好,此**沒有什麼特別之處主要依靠乙個加1取模的方式來回到首位置,形成環鏈:p=(p+1)%num;。
以下是利用結構體的方法解決josephus問題:
當我們學過結構體後,我們了解到結構體自身的成員指標可以指向自身物件的位址的時候,我們很容易想到解決這個數學問題,用結構體來描述是再合適不過的了,用它可以很完美的描述環形鍊錶。
**如下:
#include
#include
using
namespace std;
struct children
; void show(children *point,int num)//環鏈輸出函式
} void
main()
show(point,num);
children *cut_point;
point=&josephus[num-1];//把起始指標設定在最後乙個節點,當進入迴圈的時候就會從0開始,這樣就好讓不需要的節點脫離
int k=0;//故意設定乙個k觀察while迴圈了多少次
while(point->next!=point)//通過迴圈不斷的尋找需要放棄的節點
cut_point->next=point->next;//將截斷出的next指標設定成放棄處節點的next指標,使放棄處節點也就是不需要的節點脫離
cout
<
<
cin.get();
cin.get();
}此**較為難以理解的部分就是while迴圈的終止條件的設定,如果讀者沒有能夠理解好這部分注意看下面的圖式幫助學習。
結構體的解法非常重要,對於我們全面理解物件導向的程式設計的抽象問題是基礎,必須看明白我們才能夠進行後面知識的學習,務必認真對待。
這段**比較前乙個程式,可讀性上有所加強,但仍然不太容易理解!
為了更容易學習便於理解,我們的圖例是以有兩個小孩圍成一圈,並且設定報數的數為1的情況來製作的。
上面的兩種解決josephus問題的解決辦法從**上來看,都屬於一桿子到底的解法,第二種從結構表達上優於第一種,但是這兩個都屬於純粹的過程式程式設計,程式雖然簡短,但很難讓人看懂,程式的可讀性不高,在我們沒有學習物件導向的程式設計之前,聰明的人可能會把各各步驟分解出來做成由幾個函式來解決問題。
思路大致可以分為以下六個部分:
1.建立結構
2.初始化小孩總數,和數小孩的數
3.初始化鍊錶並構成環鏈
4.開始通過迴圈數小孩獲得得勝者
5.輸出得勝者
6.返回堆記憶體空間
從表上看這個程式為了便於閱讀可以寫成六個函式來分別處理這六個過程,的確,這麼修改過後程式的可讀性是提高了一大步,但是有缺點仍然存在,程式完全暴露在外,任何人都可以修改程式,程式中的一些程式作者不希望使用者能夠修改的物件暴露在外,各物件得不到任何的保護,不能保證程式在執行中不被意外修改,對於使用者來說還是需要具備解決josephus問題演算法的能力,一旦程式變的越來越很,,每乙個參與開發的程式設計師都需要通讀程式的所有部分,程式完全不具備黑盒效應,給多人的協作開發帶來了很大的麻煩,幾乎每個人都做了同樣的重複勞動,這種為了解決乙個分枝小問題寫乙個函式,最後由很多個解決區域性問題的函式組合成的程式我們叫做結構化程式設計,結構化程式設計較過程化程式設計相比可讀性是提高了,但程式不能輕易的被分割解決乙個個大問題的模組,在主函式中使用他們的時候總是這個函式呼叫到那個函式,如果你並不是這些函式的作者就很難正確方便的使用這些函式,而且程式的變數重名問題帶來的困擾也是很讓人頭痛的……
那麼物件導向的程式設計又是如何解決這些問題的呢?
物件導向的程式設計的思路是這樣的:
程式 = 物件 + 物件 +物件..........
這麼組合而來的
對於上面的josephus問題可以把問題分割成如下的方法進行設計(如下圖所示)
附件:(11k, zip壓縮檔案)
由上圖可以看出:
物件導向的程式設計
#include
#include "josephus.h"
using
namespace std;
void
main()
josephus.h
class josephus
void initial();
protected:
int num;
int interval;
};josephus.cpp
#include
#include "josephus.h"
#include "ring.h"
using
namespace std;
void josephus::initial()
cout
<
cin>>interval;
if(interval<1|interval>num)
josephus::num=num;
josephus::interval=interval;
ring a(num);
a.showring(num);
cout
struct children
; class ring
point = &josephus[num-1];
} ~ring()
void showring(int num);
void countinterval(int interval);
void outchild();
void showwiner_loser();
protected:
children *josephus;
children *point;
children *cut_point;
};ring.cpp
#include
#include "ring.h"
using
namespace std;
void ring::showring(int num)
point=&josephus[num-1];//輸出過後恢復point應該在的位置
} void ring::countinterval(int interval)//數小孩
} void ring::outchild()
void ring::showwiner_loser()
程式中需要注意的小地方是在這裡
class josephus
void initial();
protected:
int num;
int interval;
};**中的
josephus::num=num;
josephus::interval=interval;
使用域區分符的目的就是為了區分成員變數和區域性變數josephus(int num=10,int interval=1)
相信讀者認真讀完程式認真理解後應該就可以理解物件導向程式設計的用意和好處了,切記認真推敲!
大家看到物件導向程式設計的解決辦法,可能覺得它的**太多了,會懷疑它執行的效率是否足夠好,呵呵!
這裡只能這麼說,程式的效率不是單單看程式的長短來看的,優秀的程式應該是便於維護,關係清楚的,物件導向的程式設計其實和過程式或者是結構化程式設計的思路是不衝突的,在不同的地方使用不同的方法,優勢互補才是正道!
物件導向的程式設計 理解物件
理解物件 建立自定義物件的最簡單方式就是建立乙個object的例項,然後再為它新增屬性和方法 1 var person new object 2 person.name nicholas 3 person.age 29 4 person.job software engineer 5 person....
物件導向程式設計思想 抽象
如何理解抽象 我們在前面去定義乙個結構體的時候,實際上就是把一類事物的共有的屬性 字段 和行為 方法 提取出來,形成乙個物理模型 模板 這種研究問題的方法稱為抽象。案例演示 type account struct 方法 1.存款 func account account deposite money...
物件導向程式設計的理解
面向著具體的每乙個步驟和過程,把每乙個步驟和過程完成,然後由這些功能方法相互呼叫,完成需求。當需求單一,或者簡單時,我們一步一步去操作沒問題,並且效率也挺高。可隨著需求的更改,功能的增多,發現需要面對每乙個步驟很麻煩了,這時就開始思索,能不能把這些步驟和功能在進行封裝,封裝時根據不同的功能,進行不同...