107 一般警察特考三等 警察資訊管理人員 物件導向程式設計 考古題詳解
第 一 題25 分
表一為用來實現兩數運算的 C# 程式碼。Operation.cs 檔案定義了三個類別;IFactory.cs 定義了一個介面及兩個類別;Program.cs 則定義了 Program 類別。
【Operation.cs】
namespace FactoryPattern
{
class Operation
{
private double _num1;
private double _num2;
public double Num1
{
get { return _num1; }
set { _num1 = value; }
}
public double Num2
{
get { return _num2; }
set { _num2 = value; }
}
public virtual double Result()
{
return 0;
}
}
class OperationAdd : Operation
{
public override double Result()
{
return Num1 + Num2;
}
}
class OperationMultiply : Operation
{
public override double Result()
{
return Num1 * Num2;
}
}
}
【IFactory.cs】
namespace FactoryPattern
{
interface IFactory
{
Operation Create();
}
class AddFactory : IFactory
{
public Operation Create()
{
return new OperationAdd();
}
}
class MultiplyFactory : IFactory
{
public Operation Create()
{
return new OperationMultiply();
}
}
}
【Program.cs】
namespace FactoryPattern
{
class Program
{
static void Main(string[] args)
{
IFactory operFactory = new AddFactory();
Operation oper = operFactory.Create();
oper.Num1 = 3;
oper.Num2 = 5;
Console.WriteLine(oper.Result());
}
}
}
請根據表一回答以下問題:
(一) 在 C# 語言中,關鍵字 namespace 的主要用途為何?(2 分)
(二) 程式進入點在那個類別的那個方法中?(2 分)
(三) 關鍵字 virtual 與 override 的主要用途為何?(2 分)
(四) class AddFactory : IFactory{....} 的意義為何?(2 分)
(五) IFactory operFactory = new AddFactory(); 這段程式碼用到了物件導向程式設計三大特性中的那一個特性?(2 分)
(六) 請依據表一的程式碼畫出 UML 類別圖。(15 分)
登入後即可作答並保存紀錄。
核心觀念
本題為典型的「物件導向程式設計(OOP)」與「軟體設計模式(Design Patterns)」整合題型,融合了 C# 程式語言特性、四人幫(GoF)的工廠方法模式(Factory Method Pattern),以及 UML 類別圖(Class Diagram) 之繪製規範。
- C# 語法與執行架構:
namespace(命名空間):邏輯組織與避免識別名稱衝突。- 程式進入點:通用語言執行平台(CLR)要求之
static void Main簽章與宿主類別。 - 多型機制關鍵字:
virtual(宣告基底類別方法可被覆寫)與override(衍生類別提供具體覆寫實作),構成動態晚期繫結(Late Binding)。 - 介面實作語法:C# 統一以冒號
:表達繼承與介面實作,若右側為介面則代表「實作(Realization)」。
- 物件導向三大特性(封裝、繼承、多型):
- 介面與抽象型別參考指向實體物件(Upcasting)時,體現的是「多型(Polymorphism)」。
- UML 類別圖塑模規則:
- 類別與介面表示法(三層結構:名稱、屬性、方法;
<<interface>>刻劃 stereotype)。 - 可見性符號:
-(private)、+(public)。 - 關係線條與箭頭:
- 泛化/繼承(Generalization):實線+空心三角形箭頭。
- 實作(Realization):虛線+空心三角形箭頭。
- 相依(Dependency / Instantiate):虛線+開放箭頭(
-->或..>)。
解題方法與逐題精解
(一) 在 C# 語言中,關鍵字 namespace 的主要用途為何?
- 解題切入點:從軟體工程模組化與編譯器符號解析角度回答。
- 解析:
- 防止名稱衝突(Naming Collisions):在大型專案或引用多個外部組件時,不同模組可能定義相同名稱的類別(例如不同程式庫皆有
Operation類別)。透過命名空間作為前綴(如FactoryPattern.Operation),可精準區隔識別碼。 - 組織與分類程式碼結構:依功能層次或子系統(如 UI、BLL、DAL)將關聯的類別、介面、結構及列舉進行邏輯分組,提升系統維護性與可讀性。
(二) 程式進入點在那個類別的那個方法中?
- 解題切入點:檢視 C# 執行檔的入口常規規範。
- 解析:
- C# 應用程式(Console Application)在執行時期由 CLR 載入並啟動時,會尋找具有
static修飾詞且名為Main的方法作為程式啟動的進入點。 - 檢視表一之
Program.cs,進入點位於Program類別的Main方法(完整簽章為static void Main(string[] args))。
(三) 關鍵字 virtual 與 override 的主要用途為何?
- 解題切入點:動態分派(Dynamic Dispatch)與執行時期多型之配對機制。
- 解析:
virtual(虛擬):用於基底類別(父類別)的方法或屬性宣告中。其目的在於賦予該成員「多型能力」,向編譯器表明允許衍生類別在後續繼承時重新定義並改變其行為。未標記為virtual、abstract或override的成員,預設不可被覆寫。override(覆寫):用於衍生類別(子類別)的方法或屬性宣告中。其目的在於具體提供該虛擬成員的子類別專屬實作,取代(覆寫)繼承自基底類別的原有邏輯。- 二者協同:兩者配對使用,使程式在執行時期依據變數所指向的「實際物件型別」而非「宣告型別」,動態決定要調用的方法實作(實現動態繫結)。
(四) class AddFactory : IFactory{....} 的意義為何?
- 解題切入點:C# 語法冒號
:後方接介面的語意分析。 - 解析:
- 語法意義:代表具體類別
AddFactory實作(Implements) 了介面IFactory。 - 行為意義:介面在物件導向中充當合約(Contract)。此宣告代表
AddFactory必須遵守IFactory所訂立的規格,負責將介面內定義的成員(在此為Operation Create方法)全部具體實作出來,否則無法通過編譯。
(五) IFactory operFactory = new AddFactory; 這段程式碼用到了物件導向程式設計三大特性中的那一個特性?
- 解題切入點:物件導向三大特性為「封裝」、「繼承」、「多型」。分析變數宣告與指派的本質。
- 解析:
- 此行程式碼使用父介面型別
IFactory宣告參考變數operFactory,右側以具體子類別AddFactory進行實例化並指派。 - 這種「以泛用介面操作具體實作物件」的機制稱為向上轉型(Upcasting),是多型(Polymorphism) 的核心展現。呼叫者端(
operFactory)不需依賴特定類別,日後可抽換為new MultiplyFactory,而後續程式碼皆以一致的介面呼叫,展現同一介面具備不同實作行為的特性。
(六) 請依據表一的程式碼畫出 UML 類別圖(15 分)
本題程式碼為標準「工廠方法模式(Factory Method Pattern)」,系統包含兩大階層架構與一個執行端:
- 運算階層(產品端 Product):
- 抽象基底類別:
Operation(包含私有欄位_num1,_num2、公開屬性Num1,Num2、虛擬方法Result)。 - 具體產品類別:
OperationAdd與OperationMultiply,均繼承自Operation並覆寫Result。
- 工廠階層(工廠端 Creator):
- 介面:
IFactory,定義抽象工廠方法Create: Operation。 - 具體工廠類別:
AddFactory與MultiplyFactory,均實作IFactory。
- 相依與使用關係:
AddFactory在Create方法中建立OperationAdd實體,存在相依(Dependency /<<create>>)關係。MultiplyFactory在Create方法中建立OperationMultiply實體,存在相依(Dependency /<<create>>)關係。Program的Main方法使用IFactory、AddFactory及Operation。
1. UML 類別圖(Mermaid 格式繪製)
第 二 題25 分
佇列(Queue)是一種先進先出(First in first out, FIFO)的資料結構。我們想使用 C++ 語言及陣列來實作一個 QueueAsArray 類別,此類別宣告於 QueueAsArray.h 檔案中,程式碼如表二所示。表三為主函式 main() 的程式碼。
【表二:QueueAsArray 類別宣告的程式碼】
class QueueAsArray
{
public:
QueueAsArray(int size);
~QueueAsArray();
void enqueue(double data); // 增加一個元素到 Queue 的後方
void dequeue(); // 移除 Queue 的第一個元素
void display(); // 列印 Queue 中的所有元素
private:
int front; // Queue 第一個元素在陣列的位置
int rear; // Queue 最後一個元素在陣列的位置
int size; // Queue 中一共有多少個位置
double* array; // 用來儲存 Queue 元素的陣列之指標
bool isFull(); // 檢查 Queue 是否已經填滿
bool isEmpty(); // 檢查 Queue 是否是空的
};
【表三:主函式 main() 的程式碼】
int main()
{
QueueAsArray* queue = new QueueAsArray(3);
queue->enqueue(3);
queue->enqueue(1);
queue->enqueue(2);
queue->enqueue(5);
queue->enqueue(9);
queue->dequeue();
queue->enqueue(4);
queue->display();
system("pause");
return 0;
}
enqueue() 的功能是把元素加入 Queue 的後方,dequeue() 的功能是移除 Queue 的第一個元素,display() 是列印 Queue 中所有的元素。圖一描述 QueueAsArray 中 enqueue 與 dequeue 的動作原理以及變數 front 及 rear 之變化情況。
(一) 請以 C++ 程式語言實現 QueueAsArray(int size)、~QueueAsArray()、enqueue()、dequeue()、display()、isFull()、isEmpty() 之功能,並將此程式碼存於 QueueAsArray.cpp 中。若物件使用到動態記憶體配置,則在執行解構子時要釋放該記憶體空間。(20 分)
(二) 根據表三,請問指標 queue 指向的佇列物件可儲存幾個元素?(2 分)
(三) 根據表三,請問程式執行完畢後,佇列內容為何?(3 分)
🖼️【此處有附圖,請對照原卷】
登入後即可作答並保存紀錄。
核心觀念
本題評量物件導向程式設計(C++)與資料結構(Data Structures)的整合實作能力,核心觀念包含:
- 佇列(Queue)之先進先出(FIFO)特性:元素的加入發生在隊尾(rear),元素的移除發生在隊首(front)。
- 環狀佇列(Circular Queue)的陣列實作:使用一維陣列實作佇列時,若直接讓指標線性遞增,會因前方釋放的空間無法重複利用而產生「假溢位(False Overflow)」。因此需運用模數運算(Modulo Operation,即
% size)將陣列頭尾相接,形成環狀結構。 - 指標變數與邊界狀態定義:
- 根據題幹表二註解,
front為「第一個元素在陣列的位置」,rear為「最後一個元素在陣列的位置」。 - 空佇列(Empty):初始或無元素時,設定 。
- 滿佇列(Full):當下一個預備寫入的位置碰上隊首時,即 。
- 佇列僅剩單一元素時執行
dequeue,需將指標重設回空狀態()。
- C++ 動態記憶體配置與解構子管理:使用
new[]配發連續動態記憶體空間,在物件生命週期結束時,必須於解構子(Destructor)中使用delete[]正確釋放,避免記憶體洩漏(Memory Leak)。
解題方法
(一) QueueAsArray.cpp 程式碼實作
依據題目要求,需於 QueueAsArray.cpp 中完整實現各成員函式:
QueueAsArray(int size):配置大小為size的double動態陣列,並將front與rear初始化為 。~QueueAsArray:以delete[] array釋放動態配置的記憶體。isEmpty:當 時回傳true,否則回傳false。isFull:當 時回傳true,否則回傳false。enqueue(double data):先透過isFull檢查是否已滿;若為空佇列,將front與rear皆設為 ;若非空,則 ,並將data存入array[rear]。dequeue:先透過isEmpty檢查是否為空;若佇列中僅剩一個元素(),移除後重設為空狀態();若有多個元素,則將隊首後移:。display:若佇列不為空,由front開始循環狀索引依序輸出至rear。
第 三 題30 分
表四的程式碼是以多型的概念來實作圓形以及正方形的面積計算,請依據以下的要求進行程式碼的改寫:
(一) 將 Shape 類別修改成抽象類別。(3 分)
(二) 用常數函式的概念來避免在執行 getArea() 函式時,修改到 Circle 或 Square 物件成員的值。(3 分)
(三) 在 Circle 類別中,有一個圓周率 PI 的資料成員,這份程式碼的缺陷是,每生成一個 Circle 物件,就需要一塊記憶體來記錄 PI 值,造成記憶體的浪費,請用 static 成員的概念來改善 Circle 類別的程式碼。(3 分)
(四) 當幾何參數小於等於 0 時,執行 Circle 或 Square 的建構子時,物件依然能夠生成,但面積的計算會出錯,例如,當傳入的參數是 時,生成的 Square 物件,執行 getArea() 後會傳回 。這是類別設計上的瑕疵,為了改善這個狀況,應該利用靜態(static)方法的概念設計一個 Create() 的函數,當幾何參數小於等於 0 時,傳回 Null 指標,而當幾何參數大於 0 時,則呼叫該類別的建構子。(6 分)
【表四:Shape、Circle、Square 類別的程式碼】
【Shape.h】
class Shape
{
public:
virtual double getArea()
{
return 0;
}
};
class Circle : Shape
{
private:
double const PI = 3.1415926;
double _radius;
public:
Circle(double radius);
double getArea();
};
class Square : Shape
{
private:
double _length;
public:
Square(double length);
double getArea();
};
【Shape.cpp】
#include "Shape.h"
#include <stdio.h>
Circle::Circle(double r)
{
_radius = r;
}
double Circle::getArea()
{
return PI * _radius * _radius;
}
Square::Square(double length)
{
_length = length;
}
double Square::getArea()
{
return _length * _length;
}
(五) 在表五中我們產生 10 個亂數,亂數的範圍是從 到 ,我們把這 10 個亂數儲存在 data 陣列中。接下來我們利用 data 陣列的資料來產生 Circle 及 Square 物件,當陣列的 index 是偶數時產生 Circle 物件,奇數時產生 Square 物件,將這些物件以父類別指標 Shape* 的形式記錄下來,並將其加入到 vector 容器中。用 vector 的 iterator 功能將 vector 所有的物件取出計算其面積並加總,而後輸出結果。請以 C++ 語言完成表五的程式碼。(15 分)
登入後即可作答並保存紀錄。
本題為典型的 C++ 物件導向設計與標準樣板函式庫(STL)整合應用題,全面檢驗考生對於多型(Polymorphism)、抽象類別(Abstract Class)、常數保護(const-correctness)、靜態成員(Static Members)、簡單工廠模式(Static Factory Method)以及 STL 容器與迭代器(Container & Iterator) 的實作能力。
核心觀念
- 抽象類別與純虛擬函式(Pure Virtual Function):
若基礎類別(Base class)僅定義介面規格而不提供具體實作,應將虛擬函式宣告為「純虛擬函式」(語法為
= 0;)。包含至少一個純虛擬函式的類別即為抽象類別,編譯器將禁止其實例化(Instantiation)。 - 常數成員函式(Const Member Function):
在成員函式宣告與定義的參數列後方加上
const修飾字,編譯器會將該函式內隱含的this指標型別視為指向常數物件的指標(即const ClassName* const this),藉此保證在該函式執行期間,物件的任何非mutable資料成員皆不可被修改。 - 靜態資料成員(Static Data Member):
靜態資料成員屬於「類別本身」而非屬於任何單一「物件實例」。所有物件共用同一塊記憶體位址,可大幅節省常數或共用資料的空間配置。在類別內部僅能進行宣告,必須在類別外部(通常在
.cpp實作檔)配置空間並進行定義。 - 靜態工廠方法(Static Factory Method / Named Constructor Idiom):
C++ 建構子本身無法直接回傳指標或空指標(
NULL/nullptr)。當參數不合法時,除了拋出例外(Exception)外,標準的物件導向作法是引入靜態生成函式(如Create),由其在堆疊或堆積上進行前置合法性檢查,確認無誤後才動態配置記憶體(new),否則傳回空指標。 - 多型存取權限與 STL 整合:
- C++ 類別(
class)預設繼承為private。若要透過基礎類別指標(Shape*)操作衍生類別,繼承權限必須明確宣告為public Shape,否則無法進行向上轉型(Upcasting)。 std::vector為動態陣列容器,巡訪時應使用其所屬之iterator進行解參考操作。
解題方法
(一) 將 Shape 類別修改成抽象類別
- 作法:在
Shape.h中,將getArea宣告為純虛擬函式virtual double getArea const = 0;。 - 原因:圖形基礎類別無法預先得知具體圖形的面積計算方式,將其設為純虛擬函式可強制所有繼承的子類別(
Circle、Square)必須實作該介面,同時使Shape成為無法直接產生物件的抽象類別。
(二) 使用常數函式避免修改物件成員
- 作法:在基礎類別與衍生類別的
getArea函式宣告及定義末端皆加上const: - 宣告:
virtual double getArea const = 0;、double getArea const; - 實作:
double Circle::getArea const { ... }、double Square::getArea const { ... } - 原因:計算面積屬於查詢性質(Query)操作,加上
const可在編譯時期提供型別安全保護,防止因邏輯疏失更動到_radius或_length。
(三) 使用 static 成員改善 PI 的記憶體配置
- 作法:
- 在
Circle類別中宣告private: static const double PI;。 - 在
Shape.cpp全域區段進行空間配置與賦值:const double Circle::PI = 3.1415926;。 - 原因:原程式碼在每個
Circle物件實例中都保留一份 8 位元組(double)的常數PI,造成冗餘。改為static後,整個行程僅保留單一記憶體複本,所有Circle實例共享此常數。
(四) 設計靜態生成方法 Create 作為防禦性設計
- 作法:
- 在
Circle與Square類別中新增靜態方法:static Circle* Create(double radius);及static Square* Create(double length);。 - 實作時先檢查幾何參數:
若參數 ,直接傳回
NULL(或 C++11 的nullptr); 若參數 ,則呼叫建構子透過new動態配置實例並回傳指標。
(五) 表五的主程式實作與多型加總
- 作法:
- 亂數種子初始化:使用
srand(time(NULL));。 - 產生 10 個介於 到 的亂數:亂數公式為
rand % 10 - 3(區間大小為 )。 - 依據陣列索引奇偶性,分別呼叫
Circle::Create與Square::Create,並將合法的非空指標以Shape*形式存入std::vector<Shape*>。 - 使用
std::vector<Shape*>::iterator巡訪容器,累加(*it)->getArea。 - 巡訪結束後輸出加總面積,並釋放動態配置的記憶體。
解題技巧
- 避開預設私有繼承陷阱:
原始題目的表四宣告為
class Circle : Shape。在 C++ 中,class預設繼承為private,若不修正為public Shape,編譯器在執行Shape* s = Circle::Create(...)時會拋出無法將衍生類別轉換
第 四 題20 分
複數 的極座標表示法為 ,其中 為複數 到原點的距離,將 與原點連成的直線,此直線與實數軸的夾角為 。我們可以將複數 表示成 。
若 、,則複數的除法可以表示成:
請利用 C++ 程式語言實現複數極座標表示法的除法運算,程式之撰寫請依據以下要求:
(一) 複數之類別名稱為 Complex,有兩個 private 等級的資料成員,分別是 _radius 以及 _angle,_radius 代表 ,_angle 代表 。(3 分)
(二) 請用 inline 函數來實現資料成員的存取。(3 分)
(三) 實作運算子 /=(註: 等同於 )。(4 分)
(四) 請設計一個 ComplexException 類別用來進行除零的例外處理,ComplexException 必須繼承 std::exception 並覆寫 what() 函式,當除 的情況發生時,丟出 DividedByZero 字串。(5 分)
(五) 請撰寫 main 函式來驗證程式之功能。(5 分)
登入後即可作答並保存紀錄。
核心觀念
本題為標準的 C++ 物件導向程式設計(OOP)實作題,評量考生對類別封裝、記憶體與效能最佳化、運算子多載及例外處理機制的整合應用能力:
- 類別封裝(Encapsulation):透過
private修飾子隱藏內部狀態(_radius與_angle),確保資料完整性。 - 行內函式(
inlineFunction):透過inline關鍵字向編譯器建議在呼叫端直接展開函式主體,以省去函式呼叫的堆疊(Stack Frame)開銷,常應用於簡短的取值(Getter)與設值(Setter)函式。 - 運算子多載(Operator Overloading):複合指定運算子(如
/=)的標準實作簽章為回傳自身的參考(Complex&),並以常數參考(const Complex&)接收運算元,以支援連續運算與避免不必要的物件複製。 - 自訂例外處理(Custom Exception):繼承標準函式庫的
std::exception,並覆寫(Override)虛擬函式virtual const char* what const noexcept,實作標準化的例外錯誤回報機制。
解題方法
- 資料成員與存取介面設計:
- 定義類別
Complex,於private區塊宣告double _radius與double _angle。 - 實作 Getter 與 Setter。明確加上
inline關鍵字,Getter 函式必須加上const修飾以符合常數正確性(Const Correctness)。
- 運算子
/=邏輯:
- 依照極座標除法公式:。
- 運算前必須先檢查除數(右運算元)的半徑是否為 ;若為 ,則拋出(
throw)ComplexException。 - 若不為 ,則將當前物件的
_radius除以除數的_radius,當前物件的_angle減去除數的_angle,最後回傳*this。
- 例外類別
ComplexException:
- 包含標頭檔
<exception>,讓ComplexException公開繼承std::exception。 - 覆寫
what函式,回傳型別為const char*,內容固定回傳"DividedByZero"。
- 主函式
main驗證:
- 建立正常極座標複數並進行除法,輸出計算後結果。
- 建立半徑為 的複數進行除法,並以
try-catch區塊捕捉ComplexException,印出e.what訊息以驗證例外攔截功能。
各小題實作要點解析
- 第 (一) 小題(類別與成員宣告):
成員名稱必須嚴格符合題意
_radius與_angle,且必須置於private存取層級下,型態建議採用雙精度浮點數double以確保數值精確度。 - 第 (二) 小題(
inline存取函式): 雖然直接在 C++ 類別宣告主體內部實作的成員函式編譯器會自動視為行內(implicitly inline),但為切合國考評分標準,程式碼中應明確寫出inline關鍵字。 - 第 (三) 小題(運算子
/=實作): - 函式簽章:
Complex& operator/=(const Complex& rhs)。 - 除數半徑為 時,除法無意義(極座標原點之夾角未定義且數值無限大),必須在此處觸發防禦性檢查。
- 第 (四) 小題(
ComplexException與what覆寫): - 基礎類別宣告為
const char* what const noexcept(C++11 起)或const char* what const throw(C++98)。覆寫時的簽章(包括後方的const與例外規範)必須完全相符,否則會被視為多載而非覆寫,導致多型(Polymorphism)失效。 - 第 (五) 小題(
main函式功能驗證): 測試案例必須涵蓋兩大面向:「正常運算流程」與「引發除以零之例外捕捉流程」。
解題技巧與常見陷阱
- 運算子
/=的回傳型態: 複合指定運算子必須回傳物件本體的參考(Complex&),即return *this;。若寫成回傳void或傳值回傳(Complex),雖能通過單純賦值,但會違反 C++ 運算子慣例,無法支援如(a /= b) /= c;的語法,在考卷上會被扣分。 what函式簽章遺漏const:std::exception::what原型為virtual const char* what const noexcept;。若遺漏後面的const,當在catch (const std::exception& e)中透過常數參考呼叫e.what時,編譯器將報錯或無法呼叫到子類別實作。- 除零判斷的精確度: