故名思義就是 C 要怎麼寫才能與時俱進。本文參考自 How to C in 2016 這篇文章。我只挑我這種新手看得懂的部分來講,有興趣的人可以去看原文。
文章開頭就講:
寫 C 的首要原則,就是避免去寫 C。
(The first rule of C is don’t write C if you can avoid it.)
這個部分笑笑就好,以下正經的。
編譯器
C 的版本建議使用 C11,再來是 C99。編譯器建議使用 clang,再來是 gcc。編譯時可加上 -std=c11
選項使用 C11 編譯,例如:
$ clang -std=c11 main.c -o justDoIt
型別
文章提到使用 char
、int
、short
、long
或是 unsigned
這種型別寫法不利於程式碼的閱讀,也容易出現跨平台相容性的問題。
建議引用 stdint.h
函式庫,並使用具有帶號(signed)與長度資訊宣告方式:
- 帶正負號的整數使用
int8_t
、int16_t
、int32_t
、int64_t
- 不帶號的整數使用
uint8_t
、uint16_t
、uint32_t
、uint64_t
- 32 與 64 位元長的浮點數使用
float
與double
除了浮點數外,整體的部分一目瞭然。另外就是請以 uint8_t
取代 char
來宣告不帶號的 8 位元整數。char
保留在對於字元或字串(char *
)的宣告上就好。
若不想考慮變數長度的問題,整數的話直接宣告 intmax_t
或 uintmax_t
,浮點數則用 double
即可。如此一來程式會以最大的變數長度來執行,即可避免溢位或精度不足的問題。
另外在 stddef.h
中有所有謂的 size_t
,可以用來宣告最大的「不」帶號整數。依 C99 的定義,size_t
是個可以做為任何長度的陣列的索引(index)。至於要不要使用它則隨意了。
不使用 int
、long
…等的例外是,如果所使用的函數輸入值或回傳值為 int
(或 long
…等) ,則配合其定義即可。
C99 允許在任何程式區塊做宣告
引用文章中的例子。「別」這樣用:
void test(uint8_t input) {
uint32_t b;
if (input > 3) {
return;
}
b = input;
}
而是:
void test(uint8_t input) {
if (input > 3) {
return;
}
uint32_t b = input;
}
(寫過了較新的程式語言的人應該很習慣了 XD)
C99 允許在 for 迴圈宣告計數器
引用文章的例子。「別」這樣用:
uint32_t i;
for (i = 0; i < 10; i++)
而是:
for (uint32_t i = 0; i < 10; i++)
除非你需要把上述的 i
值傳出去(傳出該 for 迴圈的 {...}
外),直接做上述的宣告即美觀又容易控制。
Array 直接做初始化
「別」這樣做:
uint32_t numbers[64];
memset(numbers, 0, sizeof(numbers));
而是:
uint32_t numbers[64] = {0};
Structure 直接做初始化
「別」這樣做:
struct thing {
uint64_t index;
uint32_t counter;
};
struct thing localThing;
void initThing(void) {
memset(&localThing, 0, sizeof(localThing));
}
而是:
struct thing {
uint64_t index;
uint32_t counter;
};
struct thing localThing = {0};
函數中的輸入型別
要讓函數可以輸入任意數值,別限制他的型別。所以「別」這樣做:
void processAddBytesOverflow(uint8_t *bytes, uint32_t len) {
for (uint32_t i = 0; i < len; i++) {
bytes[0] += bytes[i];
}
}
而是:
void processAddBytesOverflow(void *input, uint32_t len) {
uint8_t *bytes = input;
for (uint32_t i = 0; i < len; i++) {
bytes[0] += bytes[i];
}
}
將輸入型別設定為 void *
,並在內容中重新指派。
回傳型別
若回傳值有「是」/「否」這種需求,建議引入 stdbool.h
函式庫,使用 true
或 false
來代替整數型態(例如 int32_t
)的 1 或 0。讓程式更容易閱讀。
可以的話,建議創一個 enum
來表示複合型態的回傳型別。例如:
typedef enum growthResult {
GROWTH_RESULT_SUCCESS = 1, // 成功
GROWTH_RESULT_FAILURE_GROW_NOT_NECESSARY, // 失敗 1
GROWTH_RESULT_FAILURE_ALLOCATION_FAILED // 失敗 2
} growthResult;
寫作習慣
程式沒人看得懂,功能再怎麼強都沒用。文章中建議使用自動格式工具(auto code formatter):clang-format
。
例如要對目標 main.c
進行轉換,在終端機中敲入:
$ clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, AllowShortFunctionsOnASingleLine: None, KeepEmptyLinesAtTheStartOfBlocks: false}" "$@" -i main.c
clang-format
會將將程式依建議重新排版。但是每次都這樣敲太麻煩了。可以將 clang-format
指令以 alias 的型式建在 .bash_profie
或 .bash_rc
中:
# .bash_profile or .bash_rc
alias clang-format='clang-format -style="{BasedOnStyle: llvm, IndentWidth: 4, AllowShortFunctionsOnASingleLine: None, KeepEmptyLinesAtTheStartOfBlocks: false}" "$@"'
之後就能直接以 clang-format
進行轉換:
$ clang-format -i main.c
其中若省略掉 -i
是直接在 bash 中預覽結果,而不將轉換結果覆寫掉目標檔案。
其它
不建議使用 malloc
,取而代之的是使用 calloc
。另外也應盡量避免使用 memset
。詳見參考的文章。
結論
最後再重申一次,本文參考自 How to C in 2016 。祝大家寫程式人家看得懂,也看得懂別人寫的程式。
發表迴響