關於 C 語言中 int 與 char 兩種資料型態,下列敘述何者正確?(假設本測驗環境中 sizeof(int)=4、sizeof(char)=1)
選項 A 錯誤
int 無法涵蓋 float 能表示的所有數值範圍(例如極大值、極小值或帶有小數的數值),兩者能表示的「值的種類」本來就不同,不是誰包含誰的關係。
選項 B 正確(★ 本題標準答案)
char 只是名稱聽起來像「字元」,本質上仍是占 1 個位元組的整數型態,可以直接參與加減乘除等算術運算,這也是為什麼字元可以直接做 ASCII 運算。
選項 C 錯誤
C 語言標準只規定各整數型態之間的相對大小關係與最小表示範圍,並未強制規定 int 一定要是幾個位元組,實際大小會因編譯器與平台而異。
選項 D 錯誤
sizeof 運算子回傳的型態是 size_t,是一種無號整數型態,與負整數直接比較時,負數會被轉換成一個極大的正整數,可能得到不符合直覺的結果,並非「不會有任何潛在問題」。
小華想要撰寫一個 C 程式紀錄全校學生人數(已知全校學生人數不會超過 3000 人,且依邏輯上這個人數不可能是負數)。已知在此測驗環境中,char 占 1 個位元組(可表示 -128~127 或 0~255),short 占 2 個位元組(可表示 -32768~32767),unsigned short 占 2 個位元組(可表示 0~65535),long long 占 8 個位元組。若小華希望選出記憶體用量最省、範圍又最貼切(不浪費表示負數的空間)的宣告方式,下列何者最合適?
選項 A 錯誤
char(即使是 unsigned char)最大也只能表示到 255,遠不足以容納最多 3000 人的需求,會直接溢位。
選項 B 錯誤
short 的範圍(-32768~32767)雖然足夠容納 3000,記憶體用量也與 unsigned short 相同(2 個位元組),但多保留了一段人數不可能用到的負數範圍,不符合題目要求的「範圍最貼切」。
選項 C 正確(★ 本題標準答案)
unsigned short 同樣只占 2 個位元組,範圍 0~65535 剛好完整涵蓋 0~3000,且完全不浪費任何表示負數的空間,是題目條件下最合適的選擇。
選項 D 錯誤
long long 占 8 個位元組,用來儲存一個最大只到 3000 的數值明顯是記憶體的浪費,不符合「用量最省」的要求。
(假設本測驗環境中 short 為 2 個位元組,表示範圍約為 -32768 ~ 32767)此程式在編譯與執行上,下列敘述何者最合理?
1 #include <stdio.h>
2 int main(void) {
3 short s = 40000;
4 printf("%d\n", s);
5 return 0;
6 }選項 A 錯誤
這種窄化轉換(narrowing conversion)並不會讓程式當掉,只會讓儲存的數值不符合預期,是一種安靜發生(silent)的問題,而不是執行期崩潰。
選項 B 錯誤
40000 本身是合法的整數字面值,把它指定給 short 型態的變數在語法上完全合法,不會造成編譯錯誤。
選項 C 正確(★ 本題標準答案)
40000 超出 short 的表示範圍(-32768~32767),指定時會依二補數規則發生溢位/環繞,實際存入 s 的位元組樣式並不等於 40000 這個數學上的值。
選項 D 錯誤
C 語言不會因為指定的數值超出範圍,就自動把變數的宣告型態「升級」成更大的型態,s 的型態仍然是 short。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 int a = -1;
4 unsigned int b = 1;
5 if (a < b)
6 printf("a is smaller\n");
7 else
8 printf("a is NOT smaller\n");
9 return 0;
10 }選項 A 正確(★ 本題標準答案)
int 與 unsigned int 比較時,較小範圍/有號的一方(a)會被轉換成 unsigned int,-1 轉換後變成一個非常大的正整數(約 4294967295),遠大於 b=1,因此判斷為「不小於」。
選項 B 錯誤
此程式只會執行其中一個分支(if 或 else 擇一),不會兩行都印出。
選項 C 錯誤
這是完全合法的比較運算,型態轉換是隱性且被允許的,不會造成編譯錯誤。
選項 D 錯誤
若單純以數學角度看 -1 確實比 1 小,但 C 語言的比較運算會先套用型態轉換規則,不能只憑數學直覺判斷。
關於 C 語言中有號數(signed)與無號數(unsigned)的二補數(Two's Complement)表示法,下列敘述何者正確?
選項 A 錯誤
相同底層大小的有號與無號型態(例如 int 與 unsigned int)占用的位元組數是相同的,差別只在於同一組位元樣式被「解讀」的方式不同。
選項 B 錯誤
「每一位元取反再加 1」是一個自身的反運算,無論是要把正數轉成對應的負數表示法,或是把負數的位元樣式還原成正數,套用同一套規則都成立,並不是只能單向使用。
選項 C 錯誤
這描述的是「符號-數值表示法(sign-magnitude)」,不是二補數表示法;在二補數中,最高位元本身帶有負的位值,直接參與數值的計算,並不是獨立於其餘位元之外的符號旗標。
選項 D 正確(★ 本題標準答案)
這是二補數表示法的經典特性:32 位元有號整數可以表示的負數比正數多一個,最小值 -2147483648 沒有對應的正數最大值與之對稱(正數最大只到 2147483647)。
上述程式加入了 if (count > 20) break; 這一行來避免無窮迴圈,若沒有這一行保護,此迴圈原本會發生什麼問題?執行後 count 印出的值又會是多少?
1 #include <stdio.h>
2 int main(void) {
3 unsigned int i;
4 int count = 0;
5 for (i = 5; i >= 0; i--) {
6 count++;
7 if (count > 20) break;
8 }
9 printf("%d\n", count);
10 return 0;
11 }選項 A 錯誤
若沒有 count>20 的保護,迴圈並不會只執行 6 次就自然結束,而是會因為 i 的型態是 unsigned int 而持續下去。
選項 B 正確(★ 本題標準答案)
unsigned int 沒有負數,i 從 0 再減 1 時會環繞成一個極大的正整數,使得 i >= 0 永遠成立,原本會造成無窮迴圈;因為 count++ 與 break 判斷每次迭代都會執行,與 i 目前的值無關,所以在第 21 次迭代時 count 變成 21 並觸發跳出,最終印出 21。
選項 C 錯誤
unsigned int 變數 >= 0 在語法上完全合法(雖然邏輯上恆為真),編譯器不會因此判定為語法錯誤而拒絕編譯,通常只會給出一個「恆真比較」的警告(甚至有些編譯器預設不警告)。
選項 D 錯誤
i 是 unsigned int,遞減到 0 之後再減 1 絕對不會變成數學上的 -1,而是環繞成一個極大的正整數,這正是本題陷阱的核心。
(假設本測驗環境中 char 為有號 (signed) 型態,占 1 個位元組,表示範圍為 -128 ~ 127)執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 char c = 127;
4 c = c + 1;
5 printf("%d\n", c);
6 return 0;
7 }選項 A 錯誤
0 不是二補數環繞規則下會得到的結果,127+1 環繞後對應的是最小值而不是 0。
選項 B 錯誤
這種在小範圍整數型態上發生的溢位,在一般教學與實務編譯器環境下屬於安靜的環繞行為,並不會讓程式當掉;「一定會當掉」的說法過於武斷。
選項 C 錯誤
char 的表示範圍是 -128~127,128 已經超出這個範圍,不會是實際印出的值。
選項 D 正確(★ 本題標準答案)
127 加 1 超出 char 的最大值,依二補數規則環繞回表示範圍的最小值 -128,這正是「整數溢位與時鐘環繞機制」的典型例子。
上述程式執行後,c 印出的結果最可能是下列何者?其原因為何?
1 #include <stdio.h>
2 int main(void) {
3 unsigned int a = 3;
4 unsigned int b = 5;
5 unsigned int c = a - b;
6 printf("%u\n", c);
7 return 0;
8 }選項 A 錯誤
a - b 只是普通的算術運算,語法上完全合法,不會因為 a 比 b 小就產生編譯錯誤。
選項 B 錯誤
3-5 在數學上確實是 -2,但 c 的型態是 unsigned int,沒有辦法儲存負數,並且是以 %u 印出,因此不會印出 -2。
選項 C 正確(★ 本題標準答案)
unsigned int 相減若結果在數學上為負,會依無號數環繞規則變成 2 的 32 次方加上該負值,也就是約 4294967294,這是一個非常大的正整數,%u 會如實印出這個值。
選項 D 錯誤
C 語言的 unsigned 運算並不會自動把負的結果「修正」成 0,而是老實地依環繞規則計算出對應的極大正整數。
關於整數溢位(Integer Overflow)的敘述,下列何者正確?
選項 A 正確(★ 本題標準答案)
常見且實用的因應方式包括改用範圍更大的型態儲存運算結果,或是在運算前後加入邊界檢查,避免溢位造成的錯誤結果。
選項 B 錯誤
unsigned 只是不能表示負數,並不代表完全不會發生任何形式的溢位,如第 6、8 題所示,unsigned 一樣可能因為環繞而得到出乎意料的極大值。
選項 C 錯誤
溢位並非加法專屬的問題,減法(如第 8 題)與乘法同樣可能讓運算結果超出型態的表示範圍。
選項 D 錯誤
C 語言的整數運算在溢位時預設並不會拋出例外或中止程式,而是安靜地依環繞規則(或對有號數而言屬於未定義行為)繼續執行下去。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 char ch = 'A';
4 ch = ch + 2;
5 printf("%c\n", ch);
6 return 0;
7 }選項 A 錯誤
printf 使用 %c 格式化字元輸出,只會印出對應 ASCII 值的「單一字元」,不會把數字 2 原樣附加在後面變成 "A2"。
選項 B 正確(★ 本題標準答案)
'A' 的 ASCII 值是 65,65+2=67,而 67 正是字元 'C' 的 ASCII 值,用 %c 印出即為字元 'C'。
選項 C 錯誤
若要印出數值 67 本身,應該使用 %d,但此處用的是 %c,所以印出的是對應的字元而不是數字。
選項 D 錯誤
char 本質上是整數型態,可以直接與整數相加,這是完全合法且常見的寫法,不會造成編譯錯誤。
若程式執行後希望印出 7(也就是把字元 '7' 轉換成對應的整數值 7),則行號 5 的【1】最可能是下列何者?
1 #include <stdio.h>
2 int main(void) {
3 char digit = '7';
4 int value;
5 【1】
6 printf("%d\n", value);
7 return 0;
8 }選項 A 錯誤
digit + '0' 是把兩個 ASCII 值相加(55+48=103),在語意上與「轉換成對應數值」完全無關。
選項 B 錯誤
(int)'7' 只是把字元常數 '7' 的 ASCII 值(55)做型態轉換,數值仍然是 55,並不會變成 7。
選項 C 錯誤
value = digit; 只是把 digit 的 ASCII 值(55)原封不動存進 value,並不會變成 7。
選項 D 正確(★ 本題標準答案)
數字字元的 ASCII 值減去字元 '0' 的 ASCII 值,恰好等於該字元代表的數值:'7'(55)減 '0'(48)等於 7,這是把數字字元轉換成對應整數值的標準寫法。
執行後輸出結果為何?其原因為何?
1 #include <stdio.h>
2 int main(void) {
3 int arr[5];
4 int n = -1;
5 if (n < sizeof(arr))
6 printf("n is smaller\n");
7 else
8 printf("n is NOT smaller\n");
9 return 0;
10 }選項 A 錯誤
sizeof(arr) 回傳的是陣列所占的「位元組數」,不是元素個數;在此環境中 sizeof(int)=4,所以 sizeof(arr) 應為 20,而不是 5。
選項 B 錯誤
雖然 -1 在數學上確實比任何正數小,但 C 語言的比較運算會先依型態轉換規則處理運算元,不能只憑數學上的直覺判斷結果。
選項 C 正確(★ 本題標準答案)
sizeof 的回傳型態是 size_t(無號整數型態),n(int, -1)與其比較時會被轉換成無號數,-1 轉換後變成一個非常大的正整數,因此判斷為「不小於」,執行 else 分支。
選項 D 錯誤
int 與 sizeof 的回傳結果(size_t)可以直接比較,這是合法的運算,只是可能因為型態轉換而得到不符合直覺的結果,並不會造成編譯錯誤。
(假設本測驗環境中 sizeof(int)=4、sizeof(int*)=8)此程式執行後,行號 7 與行號 3 印出的兩個數值,下列敘述何者正確?
1 #include <stdio.h>
2 void printSize(int arr[]) {
3 printf("%d\n", (int)sizeof(arr));
4 }
5 int main(void) {
6 int numbers[10];
7 printf("%d\n", (int)sizeof(numbers));
8 printSize(numbers);
9 return 0;
10 }選項 A 錯誤
行號 7 與行號 3 印出的數值並不相同:行號 7 是在 main() 中對真正的陣列取 sizeof,行號 3 則是對函式參數(實際上已退化為指標)取 sizeof,兩者所求的對象不同。
選項 B 正確(★ 本題標準答案)
numbers 在 main() 中是真正的陣列,sizeof(numbers) 得到整個陣列的位元組數(10×4=40);但陣列作為函式參數傳遞時會退化成指標,int arr[] 等同於 int *arr,因此 sizeof(arr) 求的是指標的大小(8),而不是原本陣列的大小。
選項 C 錯誤
行號 7 是對真正的陣列取 sizeof,結果是整個陣列的位元組數(40),而不是指標大小(8);只有行號 3(函式內的參數)才會得到指標大小。
選項 D 錯誤
以陣列型態(不指定大小)作為函式參數是完全合法的 C 語法,編譯器會自動將其視為指標型態的參數,不會產生編譯錯誤。
執行後兩行分別輸出什麼?
1 #include <stdio.h>
2 int main(void) {
3 int a = 7, b = 2;
4 printf("%d\n", a / b);
5 printf("%.2f\n", (float)a / b);
6 return 0;
7 }選項 A 正確(★ 本題標準答案)
a / b(7/2)兩個運算元皆為 int,屬於整數除法,無條件捨去小數得到 3;(float)a / b 先把 a 轉型成 float,再與 b 相除,屬於浮點數除法,得到 3.5,以 %.2f 印出為 "3.50"。
選項 B 錯誤
第一行 a / b 兩個運算元都是 int,是整數除法,結果會被截斷成整數 3,而不是帶有小數的 3.5,且第一行使用的格式化字串是 %d,不會印出小數。
選項 C 錯誤
第二行 (float)a / b 確實透過型態提升進行了浮點數除法,會得到 3.5 而不是被截斷的 3.0,因此第二行不會印出 "3.00"。
選項 D 錯誤
7/2 的整數除法是無條件捨去小數(truncate),結果是 3,不是四捨五入後的 4。
此程式的撰寫目的,原本應該是想要得到 7 除以 2 的精確浮點數結果 3.5,但實際執行後卻不是這樣。請問問題出在哪裡?印出的結果又會是多少?
1 #include <stdio.h>
2 int main(void) {
3 int a = 7, b = 2;
4 float result = (float)(a / b);
5 printf("%.2f\n", result);
6 return 0;
7 }選項 A 錯誤
問題出在轉型的位置寫錯,而不是格式化字串寫錯;%.2f 本身是正確、合理的浮點數輸出格式。
選項 B 錯誤
這是程式碼撰寫方式(轉型位置)造成的邏輯問題,是一個廣為人知、可預期的行為,並不是編譯器的錯誤。
選項 C 錯誤
此程式確實有問題:括號放錯位置,導致轉型發生在整數除法「之後」,救不回已經被捨去的小數部分,因此不會印出 3.50。
選項 D 正確(★ 本題標準答案)
(float)(a / b) 是先完成 a / b 的整數除法運算(得到已被截斷的 3),才把這個結果轉型成 float(3.00),此時原本的小數部分早已在整數除法階段遺失,無法靠事後轉型救回來。
關於 C 語言中整數除法與浮點數除法的敘述,下列何者正確?
選項 A 錯誤
整數除法與浮點數除法在底層運算方式、所得的實際數值上本質不同(一個會捨去小數,一個保留小數),並不是只有印出格式上的差異。
選項 B 錯誤
只要 / 運算子的兩個運算元都是整數型態,結果就會是整數除法並捨去小數,並不是「只要出現除法就一定是浮點數」。
選項 C 正確(★ 本題標準答案)
兩個運算元皆為整數時進行整數除法(無條件捨去小數),只要有一方是浮點數型態,就會進行浮點數除法,得到帶有小數的精確結果。
選項 D 錯誤
C 語言的內建 % 運算子只能用於整數運算元,不能直接用於 float 或 double(若要對浮點數取餘數,需改用數學函式庫的 fmod()),與 / 的行為並不相同。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 char c = 10;
4 int i = 300;
5 double d = 2.5;
6 double result = c + i * d;
7 printf("%.1f\n", result);
8 return 0;
9 }選項 A 正確(★ 本題標準答案)
i * d(int 300 與 double 2.5 相乘)依隱性型態提升,i 先轉成 double 再相乘,得到 750.0;c + (i*d):c(char, 10)也會提升為 double(10.0)再相加,最終得到 760.0,以 %.1f 印出為 "760.0"。
選項 B 錯誤
310.0 只是把 c 與 i 相加(10+300)而忽略了乘法的優先順序與 d 的作用,不符合實際的運算子優先順序(* 優先於 +)與型態提升結果。
選項 C 錯誤
750.0 只計算了 i * d 的部分,漏掉了加上 c(10.0)之後的結果。
選項 D 錯誤
char、int、double 之間的隱性型態提升與混合運算是完全合法的 C 語法,不會造成編譯錯誤。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 double d = -3.9;
4 int i = (int)d;
5 printf("%d\n", i);
6 return 0;
7 }選項 A 錯誤
強制轉型(explicit cast)從浮點數轉整數,並不是採用四捨五入的規則,而是直接捨去小數部分。
選項 B 正確(★ 本題標準答案)
double 轉 int 的規則是「朝 0 的方向截斷(truncate toward zero)」,-3.9 朝 0 方向截斷後得到 -3,這與無條件捨去(floor,會得到 -4)不同,是經常被混淆的重點。
選項 C 錯誤
強制轉型確實會改變數值:型態從 double 變成 int 之後,小數部分會被捨去,數值不再是 -3.9,而是 -3。
選項 D 錯誤
強制轉型並不會先取絕對值再捨去小數,而是直接朝 0 的方向截斷,保留原本的正負號。
執行後輸出結果為何?
1 #include <stdio.h>
2 int main(void) {
3 int num = 26;
4 printf("%d %o %x\n", num, num, num);
5 return 0;
6 }選項 A 正確(★ 本題標準答案)
26 的十進位表示直接印出為 "26";換算成八進位(%o)是 "32"(3×8+2=26);換算成十六進位(%x,小寫)是 "1a"(1×16+10=26)。
選項 B 錯誤
%o 與 %x 會依各自的進位制重新表示數值,並不會單純重複印出與 %d 相同的十進位數字 "26"。
選項 C 錯誤
%d 印出的是十進位表示,不是二進位表示,26 不會被印成二進位的 "11010"。
選項 D 錯誤
26 換算成八進位是 32(不是 33),換算成十六進位是 1a(不是 1b),這兩個選項的換算結果都不正確。
上述程式碼中,printf 的格式化字串使用了 %d,但引數 pi 卻是 float 型態。下列敘述何者正確?
1 #include <stdio.h>
2 int main(void) {
3 float pi = 3.14f;
4 printf("%d\n", pi);
5 return 0;
6 }選項 A 錯誤
C 語言標準的編譯器並未強制在編譯期對 printf 的格式化字串與引數型態做嚴格檢查(部分編譯器在開啟特定警告選項時會提出警告,但通常不是「一定拒絕編譯」的硬性錯誤)。
選項 B 錯誤
C 語言並沒有「型態不匹配時自動把引數歸零」這種行為,印出的結果是不可預期的位元誤讀結果,不會保證是 0。
選項 C 錯誤
這並不是合法且安全的用法;C 語言的 printf 並不會在格式化字串與引數型態不符時,自動幫忙做正確的型態轉換。
選項 D 正確(★ 本題標準答案)
printf 的格式化字串與實際引數型態不符,屬於未定義行為:%d 預期讀取一個整數大小的資料,但實際傳入的是 float(依預設引數提升會變成 double)的位元表示法,printf 會誤讀這些位元,因此很可能印出一個看似無意義、無法預期的數字。