112 地方特考四等 資訊處理類科 程式設計概要 考古題詳解
第 一 題30 分
一、撰寫遞迴函式是重要程式設計技巧之一。
(一) 請說明下列遞迴函式 findnum 目的為何?(5 分)
int items, price[100]; // 全域變數
int findnum (int i, int num) {
if (i < items) {
if (num < price[i])
num = price[i];
num = findnum (i+1, num);
}
return num;
}
主程式如下:
int main() {
int i, num;
// 讀入數量及所有正整數價格
scanf("%d", &items);
for (i=0; i<items; i++)
scanf("%d", &price[i]);
num = findnum (0, -1);
printf ("The ... is %d\n", num);
return 0;
}
(二) 請將 findnum() 函式改寫成非遞迴的函式。(10 分)
(三) 請說明下列非遞迴程式目的為何?(5 分)
void convert (long m) {
long count=-1, n=0;
while (0 < m) {
n = n*10 + m%2;
m = m / 2;
count++;
}
while (0 < count) {
printf ("%ld", n%10);
n = n / 10;
count--;
}
printf ("%ld", n%10);
return;
}
主程式如下:
int main() {
long m;
scanf("%ld",&m);
printf ("The ... value is ");
convert (m);
printf ("\n");
return 0;
}
(四) 請將 convert() 函式改寫成以遞迴運算的函式,且函式內不可新增變數。(10 分)
登入後即可作答並保存紀錄。
核心觀念:逐一比較並保留目前最大值
函式從第 i 個價格開始檢查。若 num 小於 price[i],就把 num 更新成該價格;接著遞迴檢查下一個價格。當 i 不再小於 items 時,遞迴停止並回傳 num。
因此,呼叫 findnum(0, -1) 會逐一比較所有價格,回傳所有價格中的最大值。題目說價格為正整數,所以初始值 -1 小於每個價格,不會影響最大值的判定。
(二) 改寫為非遞迴函式
解題方法:用迴圈取代每次「檢查一筆,再呼叫下一層」的遞迴
遞迴參數 i 對應迴圈索引;參數 num 則是目前找到的最大值,改寫後以區域變數保存。
int findnum(int i, int num) {
while (i < items) {
if (num < price[i])
num = price[i];
i++;
}
return num;
}
此版本保留原函式的參數及回傳方式。主程式仍以 findnum(0, -1) 呼叫即可。
(三) convert 的目的
核心觀念:用十進位整數暫存二進位各位數,再由高位到低位輸出
第一個 while 反覆以 m % 2 取得餘數,也就是目前最低位的二進位數字,並用
將餘數接到十進位整數 n 的末尾;接著以整數除法 m = m / 2 取得下一個商。這個過程相當於將正整數 m 轉換成二進位表示,但二進位位數以十進位數字的形式反向累積在 n 中。
第二個 while 每次輸出 n % 10,再將 n 除以 ,逐位印出 n 的末尾數字。由於第一次迴圈所存的位數順序相反,這樣輸出後便會得到由高位到低位的二進位表示。
第 二 題20 分
二、請說明下列 PHP 程式設計的觀念。
(一) Class 和 Interface 的差異為何?請從「可否宣告屬性」、「可否實例化」、「可否有實作方法」3 個面向加以說明。(5 分)
(二) 若前端網頁以 HTML 程式上傳一個檔案到後端,請以 PHP 寫出後端要處理的部分,包括檢查檔案是否上傳成功、檢查檔案是否存在(不可覆蓋)、將上傳的檔案搬移到指定位置。(15 分)
前端:
<form method="post" enctype="multipart/form-data" action="upload.php">
<input type="file" name="to be uploaded">
<input type="submit" value="Upload">
</form>
後端:
<?php
...
?>
登入後即可作答並保存紀錄。
核心觀念
本題考 PHP 物件導向中的 class 與 interface,以及透過 $_FILES 接收並安全儲存上傳檔案。
(一)Class 與 Interface
| 比較面向 | Class | Interface |
|---|---|---|
| 可否宣告屬性 | 可以宣告屬性,例如 $name。 | 不可宣告一般物件屬性;可宣告常數及方法簽章。 |
| 可否實例化 | 可直接實例化非抽象類別。 | 不可直接實例化。 |
| 可否有實作方法 | 可以包含已實作的方法,也可以包含抽象方法。 | 方法主要用來規定實作契約;實作介面的類別須提供介面方法的實作。 |
類別可用 implements 實作介面;同一個類別可以實作多個介面,但 PHP 類別只能直接繼承一個父類別。
解題方法
(一)Class 與 Interface 作答要點
依題目指定的三個面向分別比較即可。要留意「不可實例化」不只可能用來描述介面,也適用於抽象類別;但一般可實例化的是非抽象類別。介面則定義一組方法契約,讓實作它的類別遵守。
(二)PHP 檔案上傳
表單使用 method="post" 與 enctype="multipart/form-data",因此 PHP 會將上傳資料放在 $_FILES。檔案欄位名稱含空格,後端必須以 $_FILES['to be uploaded'] 取值。
以下程式會依序確認欄位存在、檢查上傳錯誤碼、確認目的檔案不存在,再將暫存檔搬移到指定位置:
第 三 題20 分
三、請說明下列程式設計概念的差異。(每小題 5 分,共 20 分)
(一) 請說明傳址(call-by-reference)與傳值(call-by-value)參數傳遞的差異。
(二) 請說明靜態及動態記憶體(static memory allocation vs. dynamic memory allocation)配置的主要差別。
(三) 請說明語法錯誤(syntax error)、語意錯誤(semantic error)、執行錯誤(run-time error)的主要差別。
(四) 上述的錯誤,編譯程式過程中可以發現的是那一種錯誤(可複選)?
登入後即可作答並保存紀錄。
**核心觀念:**兩者差別在於函式取得的是「引數的值」,還是「可存取原變數的參照」。
- 傳值(call-by-value):呼叫函式時,將引數的值複製給形式參數。函式內修改形式參數,只會改變副本,不會直接改變呼叫端的原變數。
- 傳址(call-by-reference):形式參數參照呼叫端的原變數。函式內透過該參數修改內容,呼叫端看到的原變數也會改變。
**解題方法:**判斷函式修改參數後,呼叫端原變數是否改變:不改變是傳值;會改變是傳址。
程序 f(x):x = 10
a = 3
f(a)
若為傳值,f 修改的是 a 的副本,a 仍為 3;若為傳址,x 參照 a,a 會變成 10。
**解題技巧:**不要只看參數名稱或呼叫方式;關鍵是參數是否對應到呼叫端原變數。傳值雖不直接改變原變數,但若傳遞的是物件參照等複合資料,函式仍可能透過該參照修改物件內容,須依語言的參數傳遞規則判斷。
(二)靜態與動態記憶體配置
**核心觀念:**主要差別是配置的時間、大小是否能依執行時需求決定,以及記憶體的生命週期如何管理。
- 靜態記憶體配置(static memory allocation):記憶體空間與大小通常在編譯或載入時決定,執行期間不會依需求改變。例如固定大小的全域變數或靜態變數。
- 動態記憶體配置(dynamic memory allocation):程式執行期間依需求向記憶體管理機制取得空間,大小可依執行時資料決定;不再需要時,須由程式釋放,或交由垃圾回收機制處理。常見配置來源為堆積區(heap)。
**解題方法:**比較配置時間與大小決定時點:編譯或載入時已確定,屬靜態配置;執行時才取得,屬動態配置。
解題技巧:「靜態」不等於程式執行時完全沒有記憶體配置;此處指配置的時間與大小通常預先確定。動態配置也不代表一定要手動釋放,管理方式取決於程式語言。
(三)語法錯誤、語意錯誤與執行錯誤
**核心觀念:**三者分別是程式不符合文法、語句意義或型別規則不正確,以及程式執行時發生異常。
第 四 題30 分
四、超商預計發展合併集點卡程式,說明如下。
若有 張集點卡要合併,但只能兩張兩張合併,因此共需合併 次才能把點數全部集中到一張卡。兩張合併時會扣掉較少點數那張的 點數(無條件進位至整數)做為手續費。
例如,若有 、、 3 張集點卡要合併,且點數分別為 25、30、51 點。若先合併 、,再合併 ,則會扣掉 點,因此合併後剩 97 點,這也剛好是最差合併策略下的合併總點數;但在最佳的合併策略下(先合併 、,再合併 ),則可有 100 點。
每小題 15 分,共 30 分。
(一) 請寫 best_case() 函式,計算 張集點卡合併過後最多可有多少點數。
(二) 請寫 worst_case() 函式,計算 張集點卡合併過後最少可有多少點數。
#include <stdio.h>
int a[101], n; // 最多可有 100 張集點卡要合併,實際上要合併 n 張卡
int best_case () {
...
}
int worst_case () {
...
}
int main () {
scanf ("%d", &n);
for (i=0; i<n; i++)
scanf ("%d", &a[i]);
printf ("Best case: %d points\n", best_case());
printf ("Worst case: %d points\n", worst_case());
return 0;
}
登入後即可作答並保存紀錄。
核心觀念
每次合併兩張卡,手續費是點數較少者的十分之一,並無條件進位:
合併後的點數為 。因為手續費取決於兩張卡在每一步的點數,合併順序會影響最後點數。
解題方法
題目已指出合併 張卡需做 次,因此可逐次選擇要合併的兩張卡,再把合併結果放回卡片集合。
要找最多點數時,每次應選點數最多的兩張卡合併;要找最少點數時,每次應選點數最少的兩張卡合併。可用排序或優先佇列維護卡片點數,每次取出指定的兩張,計算新點數後放回,直到剩下一張。