112 高普考高考三級 資訊處理 程式設計 考古題詳解
第 一 題25 分
關於以下 C 程式碼:
#include <stdio.h> #define SIZE 10 #define THREE 3
unsigned int f1(unsigned int a, unsigned int b) { return (a && !b); }
unsigned int f2(unsigned int a, unsigned int b) { return (a << b); }
unsigned int f3(unsigned int a, unsigned int b) { return (a & ~b); }
int f4(int a, int b) { return a*b+a-b; }
int f5(int a, int b) { int data[SIZE];
for (int i=1, k=0; i<=a; i++) {
if (i%3==0)
data[k++]=f4(i, i+1);
}
return data[b];
}
int f6(int a, int b) { int data[][THREE] = {{4,3,2},{3,4,2},{2,3,3}};
for (int i=0; i<THREE; i++) {
for (int j=0; j<THREE; j++) {
if (i>a || j>b)
data[i][j] = data[j][i]+b;
}
}
return data[a][b];
}
int main() { printf("%u\n", f1(6, 2)); printf("%u\n", f2(6, 2)); printf("%u\n", f3(7, 2)); printf("%d\n", f4(3, 12)); printf("%d\n", f5(15, 3)); printf("%d\n", f5(3, 15)); printf("%d\n", f6(1, 1)); printf("%d\n", f6(3, 2)); return 0; }
請說明程式執行後,程式碼編號 26~33 的輸出,以及其運算邏輯。
登入後即可作答並保存紀錄。
核心觀念
本題考查 C 語言的邏輯運算、位元運算、陣列索引與函式呼叫。判讀時要分清楚:
&&、!是邏輯運算,結果為0或1。<<、&、~是位元運算。- 陣列索引從
0開始;索引超出陣列範圍會造成未定義行為,不能據此推定固定輸出。 - 函式內未初始化的區域陣列元素,其值不可當成已知數值使用。
解題方法與逐行推導
依 main 中 printf 的先後順序計算:
f1(6, 2):6、2都視為真,因此6 && !2等同1 && 0,結果為0。f2(6, 2):左移兩位,。f3(7, 2):以二進位計算,、;~2的對應位元中,低三位為110,所以0111 & 1101的低位結果為0101,即5。f4(3, 12):。f5(15, 3):迴圈將 的計算結果依序放入data[0]到data[4]。
第 二 題25 分
關於以下 C++ 程式碼:
#include <iostream> #include <string> #include <exception> #include <stdexcept> #include <assert.h>
using namespace std;
class NegativeException: public exception { public: const char * what () const throw () { return "negative"; } };
class DivideByZeroException: public logic_error { public: DivideByZeroException() : logic_error("divide by zero") {} };
int getResult(int x, int y) { if (x<0 || y<0) throw NegativeException(); else if (y==0) throw DivideByZeroException();
return (x/y);
}
void f(int x, int y) { try { cout << "Result:" << getResult(x, y) << endl; } catch (std::exception& e) { cout << "!:" << e.what() << "\n"; } }
void testResult() { f(2, -1); f(2, 0); f(2, 3); f(6, 3); }
void assertResult() { assert(getResult(8, 4)==1); }
int main() { testResult(); assertResult(); return 0; }
(一)請說明程式執行後的輸出。
(二)請說明程式中 assert 與 exception 的使用時機與目的。
登入後即可作答並保存紀錄。
核心觀念
本題考查 C++ 的例外處理與 assert 斷言:
throw會中斷目前函式的正常執行,將例外交給呼叫端的catch處理。catch (std::exception& e)可捕捉公開繼承自std::exception的例外物件,並用e.what取得錯誤訊息。assert(條件)用於程式開發期間檢查條件是否成立;條件不成立時會中止程式。若以NDEBUG編譯,assert會被停用。
解題方法
依序追蹤 testResult 中的四次呼叫,判斷 getResult(x, y) 是否回傳整數或拋出例外,再看 f 的 try-catch 如何處理。最後檢查 assertResult 的條件。
f(2, -1):x < 0 || y < 0成立,getResult拋出NegativeException。f捕捉例外並輸出!:加上what回傳的"negative"。由於例外在cout << "Result:" << getResult(...)中發生,getResult尚未回傳,故"Result:"不會輸出。f(2, 0):負數條件不成立,y == 0成立,拋出DivideByZeroException。此類別公開繼承logic_error,而logic_error又繼承自std::exception,因此會被同一個catch捕捉,輸出其訊息"divide by zero"。f(2, 3):沒有例外,整數除法 的結果為 ,輸出Result:0。f(6, 3):沒有例外,,輸出Result:2。assertResult:呼叫getResult(8, 4)得到 ,但斷言檢查的是2 == 1,條件為假。在未定義NDEBUG的一般除錯編譯下,assert會輸出失敗資訊並中止程式,因此main不會正常執行到return 0。斷言失敗訊息的檔名、行號及格式依編譯器與執行環境而異。
(一)程式輸出
第 三 題25 分
關於以下 Java 程式:
import java.io.*; import java.util.ArrayList;
abstract class Fruit { public Fruit(int sweetness) { this.sweetness = sweetness; }
public abstract String eat();
protected String taste() {
if (sweetness>0 && sweetness<5)
return "no";
else if (sweetness<=10)
return "little";
else if (sweetness<=15)
return "some";
else if (sweetness<=20)
return "more";
else
return "super";
}
private int sweetness;
}
class Apple extends Fruit { public Apple(String c, int s) { super(s); this.color = c; }
public String eat() {
return color + ":" + taste() + " sweetness ";
}
private String color;
}
class Watermelon extends Fruit { public Watermelon(String v, int s) { super(s); this.volume = v; }
public String eat() {
return volume + ":" + taste() + " sweetness ";
}
private String volume;
}
public class Test { public static void test01() { ArrayList<Fruit> fs = new ArrayList<Fruit>(); fs.add(new Apple("red", 18)); fs.add(new Watermelon("big", 20)); fs.add(new Apple("green", 10));
fs.forEach((n) -> System.out.println(n.eat()));
}
public static void main(String[] args) throws InterruptedException {
test01();
}
}
(一)請說明程式執行後其輸出與其運作程式碼行數順序。
(二)請依下面表格,說明 Fruit 的設計功用,包含 Fruit 類別類型與功用以及方法(method)。
| 說明 | Fruit 類別類型與功用 | Fruit 方法功用 | eat 功用 | taste 功用 |
|---|---|---|---|---|
登入後即可作答並保存紀錄。
核心觀念
本題考查 Java 的抽象類別、繼承、方法覆寫、多型與 Lambda 運算式。
Fruit是抽象類別,不能直接建立物件;它提供子類別共用的資料與方法,也要求子類別實作抽象方法eat。Apple、Watermelon繼承Fruit,透過super(s)呼叫父類別建構子,並覆寫eat。ArrayList<Fruit>可以存放各種Fruit子類別物件。呼叫n.eat時,Java 依照物件的實際型別,執行該物件覆寫的eat,這就是多型。taste依sweetness的數值區間回傳甜度描述。
解題方法
先依照 test01 中的加入順序,逐一確認物件型別與甜度,再套用 taste 的判斷條件:
Apple("red", 18):sweetness為 18,符合sweetness<=20,回傳"more"。Watermelon("big", 20):sweetness為 20,符合sweetness<=20,回傳"more"。Apple("green", 10):sweetness為 10,不符合前面的sweetness>0 && sweetness<5,但符合sweetness<=10,回傳"little"。
各子類別的 eat 都將自身資料、冒號、taste 結果與 " sweetness " 串接,因此輸出依序為:
red:more sweetness
big:more sweetness
green:little sweetness
(一)輸出與運作程式碼行數順序
程式由 main 呼叫 test01。集合依序加入三個物件;forEach 再依集合順序處理每個物件,並呼叫其實際型別所提供的 eat。
| 執行順序 | 運作程式碼 | 說明 |
|---|---|---|
| 1 | main 中的 test01; | 呼叫測試方法。 |
| 2 | ArrayList<Fruit> fs = new ArrayList<Fruit>; | 建立可存放 Fruit 子類別物件的清單。 |
| 3 | fs.add(new Apple("red", 18)); | 建立 Apple,呼叫 Fruit 建構子設定甜度,再設定顏色並加入清單。 |
| 4 | fs.add(new Watermelon("big", 20)); | 建立 Watermelon,呼叫 Fruit 建構子設定甜度,再設定體積並加入清單。 |
| 5 | fs.add(new Apple("green", 10)); | 建立第二個 Apple,設定甜度與顏色後加入清單。 |
第 四 題25 分
以下 C++ 程式有部分違反安全程式設計原則,可能具有許多潛在風險。
#include <iostream> #include <string> #define SIZE 10 using namespace std;
class Food { public: Food() = default;
Food(int c) {
cal = c;
}
int getCal() {
return cal;
}
private: int cal; };
void f1() { Food *f[SIZE]; cout << f[0]->getCal() << endl; }
void f2(int n) { string *f = NULL;
for(int i = 0; i < n; i++)
f = new string("ok");
cout << *f << endl;
}
void f3(int n) { double x = 3, y1 = 5, y2 = 2;
for (int i=0; i<n; i++) {
x = x/10.0;
y1 = y1/10.0;
y2 = y2/10.0;
}
if(x == (y1-y2))
cout << "X == Y" << endl;
}
void f4(char *s1, char *s2) { int len = 0; char *s = s1;
while (*s2 != '\0') {
*s1 = *s2;
s1++;
s2++;
}
cout << s << endl;
}
void f5(int n) { int result = 0; int *d = new int[n];
d[0] = d[1] = 1;
for(int i = 0; i < n-2; i++)
d[i+2] = d[i+1]+d[i];
for(int i = 0; i < n; i++)
result = result + d[i];
cout << result << endl;
}
int main() { char s1[] = "goodness", s2[] = "food";
f1();
f2(2);
f3(1);
f4(s1, s2);
f5(6);
return 0;
}
請說明此程式,執行函式 f1()~f5() 的輸出,以及函式 f1()~f5() 可能具有的潛在風險。
登入後即可作答並保存紀錄。
核心觀念
本題考查 C++ 的指標與動態記憶體、物件初始化、陣列邊界,以及浮點數比較。程式中有些操作會造成未定義行為;一旦發生,C++ 標準不保證程式輸出或後續執行結果。因此,下列輸出須區分「可依程式推得的結果」和「因未定義行為而無法保證的結果」。
解題方法
依序追蹤每個函式的變數初值、記憶體操作與迴圈更新,再檢查指標是否指向有效物件、陣列索引是否在範圍內,以及輸出前是否已寫入字串結尾字元 '\0'。
f1:未初始化的指標陣列
Food *f[SIZE];
cout << f[0]->getCal << endl;
f 是含有 10 個 Food* 的區域陣列。宣告時沒有初始化,因此 f[0] 的值不確定;透過它呼叫 getCal 會使用無效指標,造成未定義行為。
- **輸出:**無法保證,可能沒有任何輸出,也可能異常終止或出現其他結果。
- **風險:**未初始化指標解參考。
- **安全作法:**先讓指標指向有效的
Food物件,或將不使用的指標初始化為nullptr,並在使用前檢查。
f2:重複配置而未釋放
string *f = NULL;
for(int i = 0; i < n; i++)
f = new string("ok");
cout << *f << endl;
n = 2 時,迴圈配置兩個 string。第二次配置後,f 改為指向第二個字串;第一個配置物件的位址已遺失,無法釋放,造成記憶體洩漏。最後解參考 f 時仍指向有效字串 "ok"。
- 輸出:
ok - **風險:**每次迴圈都配置新物件,卻沒有釋放舊物件;本題執行兩次配置,洩漏第一個物件。函式結束也沒有釋放第二個物件。
- **安全作法:**若只需一個字串,直接使用區域變數;若確實需要動態配置,使用
std::unique_ptr等 RAII 管理方式。
f3:浮點數精確比較
n = 1,迴圈執行一次:
數學上 ,但二進位浮點格式通常無法精確表示 、、。計算 y1-y2 的結果不保證與 x 的浮點表示完全相同,因此 x == (y1-y2) 不可靠;在常見實作中條件不成立。
- **輸出:**常見實作下不輸出任何文字;標準不保證此精確比較必定為假。
- **風險:**用
==比較浮點數,容易因表示與運算誤差而產生非預期結果。 - **安全作法:**依問題所需精度,以容許誤差比較,例如判斷 。