112 一般警察特考三等 警察資訊管理人員 物件導向程式設計 考古題詳解
第 一 題25 分
針對以下 C++ 程式碼:
01 #include <iostream>
02 #include <string>
03 using namespace std;
04 class Pet {
05 public:
06 virtual int eat(int f) = 0;
07 private:
08 string name;
09 };
10 class Dog: public Pet {
11 public:
12 Dog() { food = 0; }
13 int eat(int f) {
14 food += f;
15 cout << food << endl;
16 return food;
17 }
18 private:
19 int food;
20 };
21 int main() {
22 Pet *d1 = new Pet();
23 Pet *d2 = new Dog();
24 Pet *d3 = new Dog();
25 d2.eat(5);
26 cout << d1->eat(5) << endl;
27 cout << d3->eat(5) << endl;
28 return 0;
29 }
(一)請完成統一塑模語言(UML)類別圖 (a)~(e)。
UML 類別圖包含:
- 類別:Pet
- 屬性:
- name: string - 操作:____
- 屬性:
- 類別:Dog
- 操作:____
- 屬性:____
- Pet 與 Dog 之間的關係:____
(二)請標示出錯誤行數程式碼,並說明錯誤原因。若將錯誤碼行數註解,程式輸出為何?
登入後即可作答並保存紀錄。
核心觀念
本題考查:
- C++ 抽象類別與純虛擬函式
- 繼承與多型
- 指標存取成員的運算子
- UML 類別圖中的抽象類別、可見性、操作與屬性
- 函式內輸出與回傳值的執行順序
第 06 行的 eat(int f) = 0 是純虛擬函式,因此 Pet 是抽象類別,不能直接建立物件。Dog 繼承 Pet 並實作 eat,所以可以建立 Dog 物件。
(一)UML 類別圖
Pet 類別
- (a):
abstract - 屬性:
- name: string - (b):
+ eat(f: int): int
其中:
-表示private+表示publicabstract表示抽象類別eat為純虛擬操作
Dog 類別
- (d):
+ Dog、+ eat(f: int): int - (e):
- food: int
其中建構函式 Dog 及 eat 均為公開成員,food 為私有屬性。
Pet 與 Dog 的關係
- (c):一般化(Generalization),即繼承關係
Dog繼承Pet- UML 應以空心三角形箭頭指向
Pet
關係可表示為:
箭頭方向由子類別 Dog 指向父類別 Pet。
(二)錯誤行數與原因
第 22 行
Pet *d1 = new Pet;
錯誤原因:Pet 含有純虛擬函式 eat,屬於抽象類別,不能直接使用 new Pet 建立物件。
因此第 22 行會造成編譯錯誤。
第 25 行
d2.eat(5);
錯誤原因:d2 的型別是 Pet*,屬於指標。指標存取成員函式必須使用箭頭運算子 ->,不能使用點運算子 .。
正確寫法為:
d2->eat(5);
因此第 25 行會造成編譯錯誤。
第 26 行的處理
第 二 題25 分
針對以下 Java 程式碼:
01 interface Moving { public int go(int s); }
02 class Fly implements Moving { public int go(int s) { return s * 5; } }
03 class Walk implements Moving { public int go(int s) { return s * 2; } }
04 class Pet {
05 public Pet(Moving m, String mObj) {
06 moving = m;
07 movingObject = mObj;
08 distance = 0;
09 System.out.println("" + moving.go(5));
10 }
11 public void go(int d) { distance += moving.go(d); }
12 public String toString() { return (movingObject + " " + distance); }
13 private Moving moving;
14 private int distance;
15 private String movingObject;
16 }
17 class Bird extends Pet { public Bird(Moving m) { super(m, "wing"); } }
18 class Cat extends Pet { public Cat(Moving m) { super(m, "leg"); } }
19 public class Test {
20 public static void run(Pet p) { p.go(5); System.out.println(p); }
21 public static void main(String[] args) {
22 Moving m1 = new Fly();
23 Moving m2 = new Walk();
24 Pet p1 = new Bird(m1);
25 Pet p2 = new Cat(m2);
26 run(p1);
27 run(p2);
28 }
29 }
(一)請完成統一塑模語言(UML)類別圖 (a)~(f)。
UML 類別圖包含:
- 類別:Pet
- 類別:____,實作(implements)Moving
- 類別:Bird,繼承(extends)Pet
- 類別:Cat,繼承(extends)Pet
- 類別:Fly,實作(implements)Moving
- 類別:Walk,實作(implements)Moving
- Bird 與 Pet 之間的關係:____
- Cat 與 Pet 之間的關係:____
- Fly 與 Moving 之間的關係:____
- Walk 與 Moving 之間的關係:____
- Pet 與 Moving 之間的關係:____
(二)此程式輸出為何?並請說明 Line 20 程式碼的意義與運作流程,包含參數 p 以及 p.go() 和 System.out.println(p) 的說明。
登入後即可作答並保存紀錄。
核心觀念
本題考查:
- Java
interface與implements - 類別繼承與
extends - UML 類別圖中的一般化(繼承)、實現(介面實作)與關聯
- 多型(polymorphism)與動態繫結(dynamic binding)
toString的覆寫與System.out.println(Object)的呼叫方式
Moving 定義共同操作 go(int s),Fly 與 Walk 分別提供不同的實作:
Pet 內部保存一個 Moving 型態的參考,因此可在執行期間根據實際物件決定呼叫 Fly 或 Walk 的 go。
(一)UML 類別圖填答
各代號答案
- (a)
interface - (b)關聯(association)
- (c)一般化/繼承(generalization)
- (d)一般化/繼承(generalization)
- (e)實現/介面實作(realization)
- (f)實現/介面實作(realization)
關係說明
Moving是介面,不是一般類別。Bird extends Pet,所以 Bird 與 Pet 是繼承關係。Cat extends Pet,所以 Cat 與 Pet 是繼承關係。Fly implements Moving,所以 Fly 與 Moving 是介面實現關係。Walk implements Moving,所以 Walk 與 Moving 是介面實現關係。Pet擁有私有欄位:
private Moving moving;
因此 Pet 與 Moving 之間是關聯關係。題目未表示菱形符號或生命週期控制,不能視為聚合或組合。
(二)程式輸出
建立 p1
Moving m1 = new Fly;
Pet p1 = new Bird(m1);
建立 Bird 時會先呼叫父類別 Pet 的建構子:
super(m, "wing");
此時 m 實際上參考 Fly 物件,因此 Line 09 執行:
先輸出:
25
建立 p2
Moving m2 = new Walk;
Pet p2 = new Cat(m2);
建立 Cat 時呼叫 Pet 建構子,m 實際上參考 Walk 物件,因此:
再輸出:
10
執行 run(p1)
run(p1);
此時 p1 實際物件是 Bird,但 Bird 繼承 Pet,因此可呼叫繼承而來的 go:
p.go(5);
Pet.go(5) 會執行:
第 三 題25 分
以下 C++ 程式實現部分 Map 的功能,keys 存字元,values 存該字元對應之整數值。
01 #include <iostream>
02 #include <string>
03 using namespace std;
04 class IMap {
05 public:
06 IMap(int s) {
07 mSize = 0;
08 keys = new char[s];
09 values = new int[s];
10 for (int i = 0; i < s; i++) {
11 keys[i] = '\0';
12 values[i] = 0;
13 }
14 }
15 int &operator[](char c) {
16 for (int i = 0; i < mSize; i++)
17 if (keys[i] == c) return values[i];
18 keys[mSize] = c;
19 return values[mSize++];
20 }
21 ~IMap() { delete keys; delete values; }
22 private:
23 char *keys;
24 int *values;
25 int mSize;
26 };
27 void test01() {
28 IMap x(10);
29 x['a'] = 5;
30 cout << x['a'] << endl;
31 cout << x['b'] << endl;
32 }
33 int countChar(string s, char c) {
34 IMap x(20);
35 for (int i = 0; i < s.length(); i++)
36 x[s[i]] = ____;
37 return ____;
38 }
39 void test02() { cout << countChar("ababbccdewbwsu", 'b') << endl; }
40 int main() {
41 test01();
42 test02();
43 return 0;
44 }
(一)請說明執行 test01() 的輸出;以及說明 IMap 類別 int &operator[](char c) 函式的設計邏輯。
(二)函式 countChar(s, c) 是利用 IMap 類別的 Map 功能,統計在字串 s 中,字元 c 出現次數;請完成此函式 (a)、(b),使之能正常執行其功能。
登入後即可作答並保存紀錄。
核心觀念
本題考查 C++ 的 運算子多載、參考傳回值與動態陣列。IMap 用 keys 記錄已加入的字元,用相同索引的 values 記錄對應整數;mSize 表示目前已有幾組鍵值。
int &operator[](char c) 傳回 int 的參考,因此 x['a'] = 5 可以直接修改 Map 中該字元對應的整數;讀取時也可直接寫成 x['a']。
解題方法
operator[] 先在索引範圍 0 到 mSize - 1 搜尋字元:
- 若找到
c,傳回同一索引的values[i]參考,讓呼叫端讀取或修改既有值。 - 若找不到,將
c放在keys[mSize],並傳回values[mSize]的參考;mSize++隨後增加鍵值數量。新配置的整數陣列元素已在建構子中初始化為0。
因此,第一次存取某字元會建立一筆值為 0 的項目;之後再存取同一字元,會找到並使用原有項目。
(一)test01 輸出
x['a'] = 5;:'a'尚未存在,新增鍵值;values初始為0,再透過參考指定為5。cout << x['a'] << endl;:找到'a',輸出5。cout << x['b'] << endl;:'b'尚未存在,新增項目;其對應的values初始值為0,因此輸出0。
輸出為:
5
0
(二)完成 countChar(s, c)
第 四 題25 分
以下 Java 程式模擬銀行帳戶存款與查詢餘額的多執行緒功能。Account 是帳戶類別,可以存款與查詢餘額;ATM 類別可以操作帳戶的存款功能;Test 類別輸出查詢之最後帳戶餘額。
01 import java.util.ArrayList;
02 class Account {
03 public void deposit(int m) { balance = balance + m; }
04 public int getBalance() { return balance; }
05 private int balance = 0;
06 }
07 class ATM extends Thread {
08 public ATM(Account a, int m) { account = a; money = m; }
09 public void run() {
10 try {
11 Thread.sleep(100);
12 } catch (InterruptedException e) { e.printStackTrace(); }
13 synchronized(Account.class) {
14 account.deposit(money);
15 }
16 //account.deposit(money);
17 }
18 private Account account;
19 private int money;
20 }
21 public class Test {
22 public static void main(String[] args) throws InterruptedException {
23 ArrayList<Thread> ts = new ArrayList<>();
24 Account account = new Account();
25 for (int i = 1; i <= 10; i++) {
26 Thread atm = new ATM(account, i);
27 atm.start();
28 ts.add(atm);
29 }
30 for (Thread t : ts) { t.join(); }
31 System.out.println("" + account.getBalance());
32 }
33 }
(一)請說明此程式的執行緒之特性以及此程式輸出。
(二)將 Line 30 註解掉,請說明程式執行結果與其運作原因。再將 Line 13 註解掉,並打開 Line 16、30 註解後,請說明此時程式執行結果與其運作原因。
登入後即可作答並保存紀錄。
核心觀念
本題考查 Java 多執行緒、Thread.start、join、synchronized 以及共享資料的競爭條件。
start會啟動新的執行緒,之後由 JVM 排程執行該執行緒的run。join會讓呼叫它的執行緒等待目標執行緒結束。synchronized可使同一時間只有一個執行緒進入受同一把監視器鎖保護的程式區段。balance = balance + m並非不可分割的單一操作,包含讀取、加法與寫回;多個執行緒同時更新時,若沒有適當同步,可能發生競爭條件而遺失更新。
解題方法
先確認每個 ATM 執行緒各自存入的金額,再看主執行緒是否等待所有 ATM 結束,最後判斷存款操作是否受同一把鎖保護。
十個 ATM 分別存入 至 ,存款總額為:
(一)原程式
每個 ATM 在 run 中先休眠 100 毫秒,再進入:
synchronized(Account.class) {
account.deposit(money);
}
所有 ATM 都以同一個 Account.class 作為鎖,因此存款區段一次只會由一個 ATM 執行。存款操作不會彼此交錯而遺失更新。
主執行緒在 Line 30 逐一呼叫 join,會等到所有 ATM 執行緒都結束後,才執行 Line 31 查詢餘額。因此輸出為 55。
(二)移除 join;再移除同步並啟用 Line 16
第一種情況:將 Line 30 註解掉
主執行緒不再等待 ATM 執行緒完成,就會直接查詢餘額。此時部分 ATM 可能尚未存款,因此輸出可能是介於 0 至 55 的數值;