回覆列表
  • 1 # 使用者8068933072948

    立即拿到按鍵意味著使用者沒有修改錯字的機會,也意味著大家寫程式都需要自行處理退格、方向鍵、Home、End、CTRL-A(跳到行頭)、CTRL-W(刪除前一個單詞)、CTRL-R(查詢輸入歷史)等等用來編輯輸入的按鍵和快捷鍵。這些編輯操作太常見、處理起來太繁瑣,如果都讓大家自己處理顯得不那麼 KISS,所以常見的默契是命令列的輸入都預設以「行」為單元做緩衝:按下回車後,程式才能獲取到一行使用者的實際輸入。

    當然這也可能是歷史原因。因為早期的計算資源十分寶貴,考慮到使用者輸入慢、行編輯這種傻傻的事情無關緊要,所以終端只有在使用者按下回車後才會將使用者輸入提交給主機處理。久而久之,大家都覺得這樣挺好,就習慣了。嘛~誰知道呢(攤手)

    也許你已經注意到,C 語言的標準庫中有 setbuf / setvbuf 這樣的函式可以設定 FILE 的 IO 做行緩衝、全緩衝、不緩衝。但遺憾的是,標準庫中的檔案流的緩衝與外部環境(無論是作業系統核心還是 CRT)的檔案 IO 緩衝是兩層不同的概念。外部環境把輸入內容緩衝著,stdin 再怎麼不做緩衝也是拿不到任何資料的,巧婦難為無米之炊。

    那麼第二個問題:EOF 和 CTRL-Z 是什麼關係?

    從「一切皆檔案」的角度看,命令列也是一種特殊的檔案,但顯然命令列輸入的長度本身是無窮的,你隨時都可能繼續輸入下一個字元,不可能出現「結尾」。為了防止世界被破壞(劃掉)為了抹平普通檔案和「命令列」檔案的區別,需要為命令列輸入找一種生成 EOF 標識的辦法。

    不過不同的系統在「命令列如何產生 EOF」這件事情上有各自不同的實現方式。

    POSIX 系統中,這一任務由終端驅動(Terminal Driver)負責。當用戶按下 CTRL-D 時,並不會真正輸入 CTRL-D 對應的字元 EOT(0x04),而是會立即交付當前終端的行緩衝(正常情況下需要使用者按回車才會交付)。

    行緩衝交付,用來讀取檔案的系統呼叫 read 返回本次讀取的位元組數,當你在行首按下 CTRL-D 時,由於行緩衝為空,系統呼叫 read 返回 0,正好是檔案結束的意思。

    Windows 系統則有些不同。使用者按下 CTRL-Z 後控制檯(Console)並不會做特殊對待,只會老老實實在行緩衝中加入 CTRL-Z 對應的字元 SUB(0x1A);與 POSIX 系統的 read 類似的 ReadFile 函式也不會處理 CTRL-Z。真正處理 SUB 字元的是 C 語言執行時庫(CRT)。

    我們知道,讀取使用文字模式 fopen 的檔案時會將 CR LF 翻譯成 LF,實際上它還會順帶檢查 SUB 字元。從 ReadFile 讀取一行緩衝後,如果輸入中存在 SUB 字元,CRT 會認為輸入內容到此為止(SUB 到本行回車之間的內容會被全部忽略)。當 SUB 在行首時,和 POSIX 系統類似,會認為到達檔案結尾。

    (順帶一提,這也是為什麼你在文字檔案中間存了一個 SUB 字元後,一些簡單的 Windows 程式會認為這個文字檔案就在 SUB 字元處結束的原因。)

    最後,如果你想擺脫這樣的限制,達到你「立即拿到使用者按鍵」預期,可以透過使用專門的 API 修改終端 / 控制檯的模式達成。

    POSIX 系統下,你可以透過將終端裝置檔案設為非標模式(Non-canonical Mode)實現:

    Windows 系統下,你可以使用 SetConsoleMode 設定控制檯模式,關閉行緩衝:

    以上。

  • 中秋節和大豐收的關聯?
  • 怎麼才能提升自身的炒股水平?