首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

深入理解 C++ 面向物件程式設計中的抽象思維:以 Josephus 問題為例

     編輯:關於C++

前言:從結構化走向物件導向

許多教材一開始就用 Josephus 問題(約瑟夫環)講授演算法。本文先回顧陣列與結構體解法,再展示如何以面向物件抽象思維重構,對比三種寫法的可讀性、維護性與擴充性,體會 OOP 的核心價值。

問題描述:Josephus 環

N 位小孩圍成一圈,從 1 開始報數,報到指定數字 M 的小孩退出,剩餘小孩繼續報數,直到最後剩下一位獲勝者。編寫程式找出獲勝者編號。

解法一:陣列模擬(過程式思維)

用陣列 a[i] 記錄小孩編號,0 表示已退出。利用 (p+1)%N 形成環狀索引。

const int N = 10;
int a[N] = {1,2,3,4,5,6,7,8,9,10};
int interval; cin >> interval;
int p = -1, k = 1;
while (true) {
    for (int j = 0; j < interval; ) {
        p = (p + 1) % N;
        if (a[p] != 0) j++;
    }
    if (k == N) break;
    cout << a[p] << ", ";
    a[p] = 0; k++;
}
cout << "\nNo." << a[p] << " boy've won.\n";

缺點: 程式碼緊湊但難讀,邏輯與資料混雜,無封裝,難以擴充(如改變退出規則、加入記錄功能)。

解法二:結構體建立循環鏈結串列(結構化思維)

定義 Children { int number; Children* next; },動態配置陣列並串成環。操作指標完成節點刪除。

struct Children { int number; Children* next; };
Children* josephus = new Children[num];
Children* point = josephus;
for (int i = 1; i <= num; i++) {
    point->number = i;
    point->next = josephus + i % num; // 形成環
    point = point->next;
}
// 刪除節點邏輯...
delete[] josephus;

進步: 資料結構更貼近問題本質(環狀),但演算法細節(指標操作、終止條件)仍暴露於主流程,可讀性有限。

解法三:面向物件抽象設計(OOP 思維)

將問題分解為兩個核心類別:

  • Ring 類別:封裝環狀鏈結串列的建立、走訪、節點刪除、顯示等資料結構操作。
  • Josephus 類別:封裝遊戲規則(輸入參數、流程控制、勝負判斷),內部持有 Ring 物件並委派任務。

類別圖概念:

Josephus (遊戲控制器)
  - num, interval
  + initial()
  - Ring* ring (has-a)

Ring (環狀資料結構)
  - Children* josephus, *point, *cut_point
  + Ring(int num)      // 建構環
  + ~Ring()            // 釋放記憶體
  + ShowRing(int)
  + CountInterval(int)
  + OutChild()
  + ShowWinnerLoser()

主程式極度精簡:

// test4.cpp
#include 
#include "josephus.h"
using namespace std;
int main() {
    Josephus game;
    game.initial();
    return 0;
}

Josephus.h / .cpp:

// josephus.h
class Josephus {
public:
    Josephus(int n=10, int m=1) : num(n), interval(m) {}
    void initial();
protected:
    int num, interval;
};

// josephus.cpp
#include "josephus.h"
#include "ring.h"
void Josephus::initial() {
    cout << "請輸入孩子總數: "; cin >> num;
    if (num < 2) { cout << "至少需 2 人構成環!"; return; }
    cout << "請輸入抽選號碼: "; cin >> interval;
    if (interval < 1 || interval > num) { cout << "號碼範圍錯誤!"; return; }

    Ring ring(num);
    ring.ShowRing(num); cout << endl;
    for (int i = 1; i < num; i++) {
        ring.CountInterval(interval);
        ring.ShowWinnerLoser();
        ring.OutChild();
    }
    cout << "\n勝利者是: "; ring.ShowWinnerLoser();
}

Ring.h / .cpp:

// ring.h
struct Children { int number; Children* next; };
class Ring {
public:
    Ring(int n);
    ~Ring();
    void ShowRing(int n);
    void CountInterval(int interval);
    void OutChild();
    void ShowWinnerLoser();
protected:
    Children *josephus, *point, *cut_point;
};

// ring.cpp
Ring::Ring(int n) {
    josephus = new Children[n];
    point = josephus;
    for (int i = 1; i <= n; i++) {
        point->number = i;
        point->next = josephus + i % n;
        point = point->next;
    }
    point = &josephus[n-1]; // 起始指標指向最後一節點
}
Ring::~Ring() { delete[] josephus; }

void Ring::ShowRing(int n) {
    point = josephus;
    for (int i = 1; i <= n; i++) { cout << point->number << ","; point = point->next; }
    point = &josephus[n-1];
}
void Ring::CountInterval(int interval) {
    for (int i = 0; i < interval; i++) { cut_point = point; point = cut_point->next; }
}
void Ring::OutChild() { cut_point->next = point->next; point = cut_point; }
void Ring::ShowWinnerLoser() { cout << point->number << ","; }

核心差異總結

維度陣列/結構體面向物件
資料與行為分離,散落在函式中封裝於類別內部
介面暴露內部指標、索引運算全暴露僅公開 initial() 等高階操作
修改影響範圍牽一髮動全身修改 Ring 不影響 Josephus,反之亦然
團隊協作需通讀全碼分工明確:資料結構組維護 Ring,邏輯組維護 Josephus
黑盒效應無使用者只需知道「建立物件、呼叫 initial()」

結語

OOP 不是為了寫更少的程式碼,而是為了建立清晰的責任邊界。Josephus 問題雖小,卻完美展示了「封裝、委派、介面導向」如何將複雜度局部化。真實專案中,Ring 可替換為 STL list、資料庫游標或分散式佇列,Josephus 完全不用動一行碼——這才是抽象思維的威力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved