經典 C 語言練習:輸入鍵盤上向右偏移一鍵的字串,輸出原始意圖字串。開發者困惑於 for 迴圈中 i 變化邏輯。本文完整解析程式流程、修正語法錯誤並優化實作。
題目需求
- 鍵盤佈局參考字串:
"1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./"
- 輸入:使用者實際按下的鍵 (向右偏移一位)
- 輸出:使用者原本想輸入的字元 (向左對應一位)
- 範例:輸入
W → 輸出 Q (因 W 在參考字串中位於 Q 右側)
原始代碼問題分析
// 原始有語法錯誤
while ((c = getchar()) != EOF) {
for (i = 1; s[i] && s[i] != c; i++); // ← 分號導致迴圈體為空!
{
if (s[i]) putchar(s[i - 1]); // ← 這塊脫離 for 迴圈,只執行一次
}
}
關鍵錯誤:for (...) ; 尾隨分號
for (i = 1; s[i] && s[i] != c; i++); → 空迴圈體,i 直接跑到條件失敗為止
- 隨後的
{ if (s[i]) putchar(s[i-1]); } 是**獨立區塊**,非迴圈體
- 結果:
i 直接跳到找到字元或字串結尾的位置,再輸出前一字元
修正版代碼
#include <stdio.h>
#include <stdlib.h>
char *s = "`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./"; // 加上 ` 鍵 (ESC 下方)
int main() {
int i, c;
while ((c = getchar()) != EOF) {
// 移除分號,大括號包裹迴圈體
for (i = 1; s[i] && s[i] != c; i++) {
// 迴圈體為空,僅尋找位置
}
// 迴圈結束後,i 停在找到的位置或字串結尾
if (s[i]) { // 找到字元 (非結尾 \0)
putchar(s[i - 1]); // 輸出左側鄰居
}
// 否則:輸入字元不在映射表中 (如 Enter、Ctrl 等),不輸出
}
return 0;
}
執行流程追蹤:輸入 W
| 步驟 | c | i 初值 | s[i] | 條件 s[i] && s[i]!=c | i 變化 | 迴圈結束 i | 輸出 |
| 1 | 'W' | 1 | '1' | 真 | i=2 | - | - |
| ... | 'W' | ... | ... | 真 | 遞增 | - | - |
| N | 'W' | 15 | 'W' | 假 (s[i]!=c 為假) | 不變 | 15 | - |
| 迴圈後 | - | - | - | - | - | 15 | s[14] = 'Q' | Q |
優化版:預建映射表 O(1) 查找
#include <stdio.h>
int main() {
char *ref = "`1234567890-=QWERTYUIOP[]\\ASDFGHJKL;'ZXCVBNM,./";
char map[256] = {0}; // ASCII 映射表
// 建立映射:右鍵 -> 左鍵
for (int i = 1; ref[i]; i++) {
map[(unsigned char)ref[i]] = ref[i - 1];
}
// 特殊鍵處理 (空格、換行等保持原樣)
map[' '] = ' '; map['\n'] = '\n'; map['\t'] = '\t';
int c;
while ((c = getchar()) != EOF) {
if (map[c]) putchar(map[c]);
else putchar(c); // 不在映射表中,原樣輸出
}
return 0;
}
優化版優勢
| 指標 | 原版 (線性搜尋) | 優化版 (映射表) |
| 時間複雜度 | O(n × m) n=輸入長度, m=映射表長度(~47) | O(n) 單次查找 |
| 程式碼複雜度 | 巢狀迴圈、易出邊界錯 | 單層迴圈、邏輯清晰 |
| 擴展性 | 修改映射需改搜尋邏輯 | 修改 ref 字串自動重建 |
常見邊界情況處理
- 輸入字元不在映射表 (如中文、功能鍵) → 原樣輸出或忽略
- 映射表首字元 (
`) 無左鄰居 → 映射表從索引 1 開始建立,` 不會被輸出 (符合題意)
- 大小寫敏感 → 參考字串為大寫,輸入小寫需轉大寫查找再轉回
- 反斜桿轉義 → 字串中需寫
\\\\ 代表單一 \
總結
原始代碼核心問題是 for 迴圈**尾隨分號導致迴圈體為空**,使 i 直接跳至目標位置。修正分號並理解「迴圈尋找位置、迴圈後輸出左鄰居」的兩階段邏輯即可。工程實務中,**預建映射表**以空間換時間、簡化邏輯、提升擴展性,是更優解法。
- 上一篇文章: MySQL 事務回滾機制:DELETE 後 ROLLBACK 前資料去哪了?
- 下一篇文章: Java 繼承機制:子類能否繼承父類 private 方法?間接調用才是關鍵
Copyright ©
程式碼之家 All Rights Reserved