# 练习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” 已死