《C 程序設計語言》(K&R)第 1 章習題要求:
本文以題目 1 為主線,詳細拆解「狀態機 + 計數陣列」的核心思路,並給出完整、可運行、符合現代規範的代碼。
輸入流可視為字元序列。單詞邊界由**空白字元**(空格、換行、製表符)分隔。掃描時只需兩個狀態:
IN:正在讀取單詞內部(未遇到空白)OUT:處於單詞之間(剛遇到空白)狀態轉移:
OUT → 進入 IN,單詞長度計數器 nc = 1
遇到非空白且狀態為 IN → nc++
遇到空白且狀態為 IN → 歸檔長度 nc,狀態轉 OUT,nc = 0
遇到空白且狀態為 OUT → 保持 OUT
MAXWORD:假設單詞最大長度(如 30)。超長單詞歸入 overflow 計數。wl[MAXWORD + 1]:wl[i] 存儲長度為 i 的單詞個數。索引 0 不用,對齊語義。overflow:長度超過 MAXWORD 的單詞計數。#include <stdio.h>
#define MAXWORD 30 // 單詞最大長度,可調大
#define IN 1 // 在單詞內
#define OUT 0 // 在單詞外
int main(void) {
int c; // 當前字元(用 int 接受 EOF)
int nc = 0; // 當前單詞長度
int state = OUT; // 狀態
int overflow = 0; // 超長單詞計數
int wl[MAXWORD + 1]; // 長度統計陣列,wl[0] 不用
// 初始化
for (int i = 1; i <= MAXWORD; ++i) wl[i] = 0;
// 掃描輸入流
while ((c = getchar()) != EOF) {
if (c == ' ' || c == '\n' || c == '\t') { // 遇到分隔符
if (state == IN) { // 剛結束一個單詞
if (nc <= MAXWORD) ++wl[nc];
else ++overflow;
nc = 0;
}
state = OUT;
} else { // 非分隔符
if (state == OUT) { // 進入新單詞
state = IN;
nc = 1;
} else { // 繼續當前單詞
++nc;
}
}
}
// 輸出直方圖(橫向數字版,易於驗證)
printf("單詞長度分佈直方圖:\n");
for (int i = 1; i <= MAXWORD; ++i) {
if (wl[i] > 0) {
printf("%2d: %3d ", i, wl[i]);
// 可選:打印星號條
// for (int k = 0; k < wl[i]; ++k) putchar('*');
putchar('\n');
}
}
if (overflow) printf("超過 %d 字元的單詞:%d 個\n", MAXWORD, overflow);
return 0;
}
getchar() 返回 int:必須用 int c 接收,以區分 EOF(通常為 -1)與合法字元(0~255)。wl[1] 對應長度 1,語義直觀,wl[0] 棄用。IN 狀態,最後一個單詞不會被統計。修正:循環結束後補一段:
if (state == IN) { // 處理文件末尾無分隔符的情況
if (nc <= MAXWORD) ++wl[nc];
else ++overflow;
}
只需將「單詞長度」換為「字元 ASCII 碼」,狀態機簡化為無狀態計數:
int freq[256] = {0}; // 覆蓋擴展 ASCII
while ((c = getchar()) != EOF) ++freq[(unsigned char)c];
// 輸出可見字元頻率
for (int i = 32; i < 127; ++i)
if (freq[i]) printf("'%c' (%3d): %d\n", i, i, freq[i]);
realloc 替代固定 MAXWORD,支援任意長度單詞。char 升級為 uint32_t(Code Point),使用 icu 或手寫 UTF-8 解碼器統計字素簇長度。K&R 習題看似簡單,實則蘊含流式處理、有限狀態機、計數統計、邊界條件等核心程式設計思想。掌握「狀態 + 計數器」模式,不僅能解決直方圖問題,更是詞法分析、協議解析、日誌統計等眾多場景的基石。