準備好你喜歡的飲料、編輯器和編譯器,放一些音樂,然后開始構建一個由多個檔案組成的 C 語言程式,

大家常說計算機編程的藝術部分是處理復雜性,部分是命名某些事物,此外,我認為“有時需要添加繪圖”是在很大程度上是正確的,
在這篇文章里,我會撰寫一個小型 C 程式,命名一些東西,同時處理一些復雜性,
優秀 Unix 程式哲學
首先,你要知道這個 C 程式是一個 Unix 命令列工具,
這意味著它運行在(或者可被移植到)那些提供 Unix C 運行環境的作業系統中,當貝爾實驗室發明 Unix 后,它從一開始便充滿了設計哲學,
用我自己的話來說就是:程式只做一件事,并做好它,并且對檔案進行一些操作,雖然“只做一件事,并做好它”是有意義的,但是“對檔案進行一些操作”的部分似乎有點兒不合適,
事實證明,Unix 中抽象的 “檔案” 非常強大,
一個 Unix 檔案是以檔案結束符(EOF)標志為結尾的位元組流,僅此而已,
檔案中任何其它結構均由應用程式所施加而非作業系統,
作業系統提供了系統呼叫,使得程式能夠對檔案執行一套標準的操作:打開、讀取、寫入、尋址和關閉(還有其他,但說起來那就復雜了),
對于檔案的標準化訪問使得不同的程式共用相同的抽象,而且可以一同作業,即使它們是不同的人用不同語言撰寫的程式,
具有共享的檔案介面使得構建可組合的的程式成為可能,
一個程式的輸出可以作為另一個程式的輸入,
Unix 家族的作業系統默認在執行程式時提供了三個檔案:標準輸入(stdin)、標準輸出(stdout)和標準錯誤(stderr),
其中兩個檔案是只寫的:stdout 和 stderr,而 stdin是只讀的,當我們在常見的 Shell 比如 Bash 中使用檔案重定向時,可以看到其效果,
$ ls | grep foo | sed -e 's/bar/baz/g' > ack
這條指令可以被簡要地描述為:ls 的結果被寫入標準輸出,它重定向到 grep 的標準輸入,grep 的標準輸出重定向到 sed 的標準輸入,sed 的標準輸出重定向到當前目錄下檔案名為 ack的檔案中,
我們希望我們的程式在這個靈活又出色的生態系統中運作良好,因此讓我們撰寫一個可以讀寫檔案的程式,
喵嗚喵嗚:流編碼器/解碼器概念
當我還是一個露著豁牙的孩子懵懵懂懂地學習計算機科學時,學過很多編碼方案,
它們中的有些用于壓縮檔案,有些用于打包檔案,另一些毫無用處因此顯得十分愚蠢,
為了讓我們的程式有個用途,我為它更新了一個 21 世紀 的概念,并且實作了一個名為“喵嗚喵嗚” 的編碼方案的概念,
這里的基本的思路是獲取檔案并且使用文本 “meow” 對每個半位元組(半個位元組)進行編碼,小寫字母代表 0,大寫字母代表 1,
因為它會將 4 個位元替換為 32 個位元,因此會擴大檔案的大小,沒錯,這毫無意義,但是看到這樣的編碼后,也會大吃一驚,
$ cat /home/your_sibling/.super_secret_journal_of_my_innermost_thoughts
MeOWmeOWmeowMEoW...
最終的實作
完整的源代碼可以在 GitHub 上面找到,
既然已經確定了要撰寫一個編碼和解碼“喵嗚喵嗚”格式的檔案的程式時,我在 Shell 中執行了以下的命令 :
$ mkdir meowmeow
$ cd meowmeow
$ git init
$ touch Makefile # 編譯程式的方法
$ touch main.c # 處理命令列選項
$ touch main.h # “全域”常量和定義
$ touch mmencode.c # 實作對喵嗚喵嗚檔案的編碼
$ touch mmencode.h # 描述編碼 API
$ touch mmdecode.c # 實作對喵嗚喵嗚檔案的解碼
$ touch mmdecode.h # 描述解碼 API
$ touch table.h # 定義編碼查找表
$ touch .gitignore # 這個檔案中的檔案名會被 git 忽略
$ git add .
$ git commit -m "initial commit of empty files"
簡單的說,我創建了一個目錄,里面全是空檔案,并且提交到 git,
即使這些檔案中沒有內容,你依舊可以從它的檔案名推斷每個檔案的用途,為了避免萬一你無法理解,我在每條 touch 命令后面進行了簡單描述,
通常,程式從一個簡單 main.c 檔案開始,只有兩三個解決問題的函式,
然后程式員輕率地向自己的朋友或者老板展示了該程式,然后為了支持所有新的“功能”和“需求”,檔案中的函式數量就迅速爆開了,
“程式俱樂部”的第一條規則便是不要談論“程式俱樂部”,第二條規則是盡量減少單個檔案中的函式,
但是 Makefile 是什么呢?
我知道下一個轟動一時的應用都是你們這些好孩子們用 “終極代碼粉碎者 3000” 集成開發環境來撰寫的,而構建專案是用 Ctrl-Meta-Shift-Alt-Super-B 等一系列復雜的按鍵混搭出來的,
但是如今,使用 Makefile 檔案可以在構建 C 程式時幫助做很多有用的作業,
Makefile 是一個包含如何處理檔案的方式的文本檔案,程式員可以使用其自動地從源代碼構建二進制程式(以及其它東西!)
以下面這個小東西為例:
00 # Makefile
01 TARGET= my_sweet_program
02 $(TARGET): main.c
03 cc -o my_sweet_program main.c
# 符號 后面的文本是注釋,例如 00 行,
01 行是一個變數賦值,將 TARGET 變數賦值為字串 my_sweet_program,按照慣例,也是我的習慣,所有 Makefile 變數均使用大寫字母并用下劃線分隔單詞,
02 行包含該步驟recipe要創建的檔案名和其依賴的檔案,在本例中,構建目標target是 my_sweet_program,其依賴是 main.c,
最后的 03 行使用了一個制表符號(tab)而不是四個空格,這是將要執行創建目標的命令,在本例中,我們使用 C 編譯器C compiler前端 cc 以編譯鏈接為 my_sweet_program,
使用 Makefile 是非常簡單的,
$ make
cc -o my_sweet_program main.c
$ ls
Makefile main.c my_sweet_program
構建我們喵嗚喵嗚編碼器/解碼器的 Makefile 比上面的例子要復雜,但其基本結構是相同的,我將在另一篇文章中將其分解為 Barney 風格,
形式伴隨著功能
我的想法是程式從一個檔案中讀取、轉換它,并將轉換后的結果存盤到另一個檔案中,
以下是我想象使用程式命令列互動時的情況:
$ meow < clear.txt > clear.meow
$ unmeow < clear.meow > meow.tx
$ diff clear.txt meow.tx
$
我們需要撰寫代碼以進行命令列決議和處理輸入/輸出流,我們需要一個函式對流進行編碼并將結果寫到另一個流中,
最后,我們需要一個函式對流進行解碼并將結果寫到另一個流中,
但是在上面的例子中,我呼叫了兩個指令:meow 和 unmeow?
我知道你可能會認為這會導致越變越復雜,
次要內容:argv[0] 和 ln 指令
回想一下,C 語言 main 函式的結構如下:
int main(int argc, char *argv[])
其中 argc 是命令列引數的數量,argv 是字符指標(字串)的串列,argv[0] 是包含正在執行的程式的檔案路徑,
在 Unix 系統中許多互補功能的程式(比如:壓縮和解壓縮)看起來像兩個命令,但事實上,它們是在檔案系統中擁有兩個名稱的一個程式,這個技巧是通過使用 ln 命令創建檔案系統鏈接來實作兩個名稱的,
在我筆記本電腦中 /usr/bin 的一個例子如下:
$ ls -li /usr/bin/git*
3376 -rwxr-xr-x. 113 root root 1.5M Aug 30 2018 /usr/bin/git
3376 -rwxr-xr-x. 113 root root 1.5M Aug 30 2018 /usr/bin/git-receive-pack
...
這里 git 和 git-receive-pack 是同一個檔案但是擁有不同的名字,
我們說它們是相同的檔案因為它們具有相同的 inode 值(第一列),
inode 是 Unix 檔案系統的一個特點,對它的介紹超越了本文的內容范疇,
首先,我們撰寫一個基于其 argv[0] 的值而作出相應改變的程式,然后我們確保為導致該行為的名稱創建鏈接,
在我們的 Makefile 中,unmeow 鏈接通過以下的方式來創建:
# Makefile
...
$(DECODER): $(ENCODER)
$(LN) -f $< $@
...
我傾向于在 Makefile 中將所有內容引數化,很少使用 “裸” 字串,
我將所有的定義都放置在 Makefile 檔案頂部,以便可以簡單地找到并改變它們,當你嘗試將程式移植到新的平臺上時,需要將 cc 改變為某個 cc 時,這會很方便,
除了兩個內置變數 $@ 和 $< 之外,該步驟recipe看起來相對簡單,
第一個便是該步驟的目標的快捷方式,在本例中是 $(DECODER)(我能記得這個是因為 @ 符號看起來像是一個目標),
第二個,$<是規則依賴項,在本例中,它決議為 $(ENCODER),
事情肯定會變得復雜,但它還在管理之中,

最后,不管你是轉行也好,初學也罷,進階也可,如果你想學編程~
【值得關注】我的 C/C++編程學習交流俱樂部!【點擊進入】
問題答疑,學習交流,技術探討,還有超多編程資源大全,零基礎的視頻也超棒~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/245498.html
標籤:C
