# 練習14:編寫并使用函數
> 原文:[Exercise 14: Writing And Using Functions](http://c.learncodethehardway.org/book/ex14.html)
> 譯者:[飛龍](https://github.com/wizardforcel)
到現在為止,你只使用了作為`stdio.h`頭文件一部分的函數。在這個練習中你將要編寫并使用自己的函數。
```c
#include <stdio.h>
#include <ctype.h>
// forward declarations
int can_print_it(char ch);
void print_letters(char arg[]);
void print_arguments(int argc, char *argv[])
{
int i = 0;
for(i = 0; i < argc; i++) {
print_letters(argv[i]);
}
}
void print_letters(char arg[])
{
int i = 0;
for(i = 0; arg[i] != '\0'; i++) {
char ch = arg[i];
if(can_print_it(ch)) {
printf("'%c' == %d ", ch, ch);
}
}
printf("\n");
}
int can_print_it(char ch)
{
return isalpha(ch) || isblank(ch);
}
int main(int argc, char *argv[])
{
print_arguments(argc, argv);
return 0;
}
```
在這個例子中你創建了函數來打印任何屬于“字母”和“空白”的字符。下面是一個分解:
ex14.c:2
包含了新的頭文件,所以你可以訪問`isalpha`和`isblank`。
ex14.c:5-6
告訴C語言你稍后會在你的程序中使用一些函數,它們實際上并沒有被定義。這叫做“前向聲明”,它解決了要想使用函數先要定義的雞和蛋的問題。
ex14.c:8-15
定義`print_arguments`,它知道如何打印通常由`main`函數獲得的相同字符串數組。
ex14.c:17-30
定義了`can_print_it`,它只是簡單地將`isalpha(ch) || isblank(ch)`的真值(0或1)返回給它的調用者`print_letters`。
ex14.c:38-42
最后`main`函數簡單地調用`print_arguments`,來啟動整個函數鏈。
我不應該描述每個函數里都有什么,因為這些都是你之前遇到過的東西。你應該看到的是,我只是像你定義`main`函數一樣來定義其它函數。唯一的不同就是如果你打算使用當前文件中沒有碰到過的函數,你應該事先告訴C。這就是代碼頂部的“前向聲明”的作用。
## 你會看到什么
向這個程序傳入不同的命令行參數來玩轉它,這樣會遍歷你函數中的所有路徑。這里演示了我和它的交互:
```sh
$ make ex14
cc -Wall -g ex14.c -o ex14
$ ./ex14
'e' == 101 'x' == 120
$ ./ex14 hi this is cool
'e' == 101 'x' == 120
'h' == 104 'i' == 105
't' == 116 'h' == 104 'i' == 105 's' == 115
'i' == 105 's' == 115
'c' == 99 'o' == 111 'o' == 111 'l' == 108
$ ./ex14 "I go 3 spaces"
'e' == 101 'x' == 120
'I' == 73 ' ' == 32 'g' == 103 'o' == 111 ' ' == 32 ' ' == 32 's' == 115 'p' == 112 'a' == 97 'c' == 99 'e' == 101 's' == 115
$
```
`isalpha`和`isblank`做了檢查提供的字符是否是字母或者空白字符的所有工作。當我最后一次運行時,它打印出除了`'3'`之外的任何東西,因為它是一個數字。
## 如何使它崩潰
下面是使它崩潰的兩種不同的方法:
+ 通過移除前向聲明來把編譯器搞暈。它會報告`can_print_it` 和 `print_letters`的錯誤。
+ 當你在`main`中調用`print_arguments`時,試著使`argc`加1,于是它會越過`argv`數組的最后一個元素。
## 附加題
+ 重新編寫這些函數,使它們的數量減少。比如,你真的需要`can_print_it`嗎?
+ 使用`strlen`函數,讓`print_arguments`知道每個字符串參數都有多長,之后將長度傳入`print_letters`。然后重寫`print_letters`,讓它只處理固定的長度,不按照`'\0'`終止符。你需要`#include <string.h>`來實現它。
+ 使用`man`來查詢`isalpha`和`isblank`的信息。使用其它相似的函數來只打印出數字或者其它字符。
+ 上網瀏覽不同的人喜歡什么樣的函數格式。永遠不要使用“K&R”語法,因為它過時了,而且容易使人混亂,但是當你碰到一些人使用這種格式時,要理解代碼做了什么。
- 笨辦法學C 中文版
- 前言
- 導言:C的笛卡爾之夢
- 練習0:準備
- 練習1:啟用編譯器
- 練習2:用Make來代替Python
- 練習3:格式化輸出
- 練習4:Valgrind 介紹
- 練習5:一個C程序的結構
- 練習6:變量類型
- 練習7:更多變量和一些算術
- 練習8:大小和數組
- 練習9:數組和字符串
- 練習10:字符串數組和循環
- 練習11:While循環和布爾表達式
- 練習12:If,Else If,Else
- 練習13:Switch語句
- 練習14:編寫并使用函數
- 練習15:指針,可怕的指針
- 練習16:結構體和指向它們的指針
- 練習17:堆和棧的內存分配
- 練習18:函數指針
- 練習19:一個簡單的對象系統
- 練習20:Zed的強大的調試宏
- 練習21:高級數據類型和控制結構
- 練習22:棧、作用域和全局
- 練習23:認識達夫設備
- 練習24:輸入輸出和文件
- 練習25:變參函數
- 練習26:編寫第一個真正的程序
- 練習27:創造性和防御性編程
- 練習28:Makefile 進階
- 練習29:庫和鏈接
- 練習30:自動化測試
- 練習31:代碼調試
- 練習32:雙向鏈表
- 練習33:鏈表算法
- 練習34:動態數組
- 練習35:排序和搜索
- 練習36:更安全的字符串
- 練習37:哈希表
- 練習38:哈希算法
- 練習39:字符串算法
- 練習40:二叉搜索樹
- 練習41:將 Cachegrind 和 Callgrind 用于性能調優
- 練習42:棧和隊列
- 練習43:一個簡單的統計引擎
- 練習44:環形緩沖區
- 練習45:一個簡單的TCP/IP客戶端
- 練習46:三叉搜索樹
- 練習47:一個快速的URL路由
- 后記:“解構 K&R C” 已死