首先我想引用《Unix编程艺术》中的几段话,我太爱这本书了。
在输入输出方面,Unix传统极力提倡用简单,文本化,面向流,设备无关的格式。在经典的Unix下,多数程序都尽可能采用简单过滤器的形式,即将一个输入的简单文本流处理为一个简单的文本流输出。
Unix中,文本流之于工具,就如同在面向对象环境中的消息之于对象。文本流界面的简洁性加强了工具的封装性。
要想让程序具有组合性,就要使程序彼此独立。在文本流这一端的程序应该尽可能不要考虑文本流另一端的程序。将一端的程序替换为另一个截然不同的程序,而完全不惊扰另一端应该很容易做到。
下面我从几个不同的方面,来讲述自己对输入输出的理解
Unix命令
经常使用linux和unix的同学,已经习惯于在命令中用管道链接输入和输出。
command1 | command2 paramater1 | command3 parameter1 - parameter2 | command4
这里需要注意的是,你的命令一定能向标准流输入,并从标准流中读入,才能用管道相连。
# 读出test.sh文件内容,通过管道转发给grep 作为输入内容
cat test.sh | grep -n 'echo'
c语言重定向
上大学的时候,学到c语言的输入输出重定向是一件很神奇的东西。
比如这个程序会在标准输出流输出一段文本:
dbl_out
而使用如下命令执行此程序,将会使文本输出到文件中。
dbl_out>outfile
c程序并不关心它的输出会到哪,也不会在意输入输入是从哪来的,它只要做它该做的就行了。
而且在shell中,每个进程都和三个系统文件相关联:标准输入stdin,标准输出stdout和标准错误stderr,三个系统文件的文件描述符分别为0,1和2。
SQL语句
我之前写过一篇文章讨论子查询: sql知识总结之子查询
因为sql语句其本身就是在处理表,在一个二维表中查询,或者返回一个二维表作为结果。
而将这些输入输出连接起来,就是所谓的子查询。
函数调用
一般的函数,都会有参数,和返回值,我们可以将参数理解为一个函数的输入,而将返回值理解为一个函数的输出。
说个比较夸张的写法,如果有一个函数,能够将一个数字+1后返回。
1 | def add(i): |
那么我可以用这种夸张的方法得到一个6:
1 | six = add(add(add(add(add(add(0)))))) |
看我用输入和输出将它们连接起来了。
比如之前我在工作中需要写一个功能,将一个整型值转化为枚举,再将枚举转换为一个英文字符串,再将这个英文字符串转化为中文字符串。
我分别实现了这些函数,最后用输入输出将它们相连接。
1 | // 各种函数声明 |
当然,因为一个函数是可以有多个参数的,所以如果你足够骚包,完全可以对这种多参数的函数用函数嵌套调用,用输入输出连接成一棵树。当然,这样写还真不如去玩lisp。
递归
大学学递归的时候,我常常不能在大脑中构建一种正确的模型去理解递归到底是什么,当我试图从输入输出的角度去理解递归时,我变得豁然开朗。
就拿下面两个简单的递归程序来说吧,它们本身都有自己的输入(函数参数)和输出(返回值)。我们讨论递归时,常常说的“自己调用自己”,其实就是在函数自身调用的时候,栈不断的生长,向下一个调用所要输入。在栈弹出的时候,又在向上一个调用给输出。你可以理解为在生长的过程中,构建了输入和输出的模型
1 | def fib(n): |
更多关于递归的讲解,请关注我的系列博客-跟我一起写递归
lisp
lisp程序被一种称为S-表达式的东西组成。
默认的一段s-表达式会被求值(除非你定义其它求值方式,或者干脆不让其求值),会将第一个位置当作函数,将其与的部分,当作函数参数。
因为s-表达式又是结构递归的,所以所有未求值的函数参数又会用相同的方式进行求值(其本身可能又是另一个函数调用)。
1 | (+ (- 5 1) (+ 3 7)) |
我眼中的lisp程序就是用括号结构,将输入输出连接起来,而且你可以控制这种求值方式。
结束
因为本文主要是为了展示一种输入输出的思想,所以大部分内容只是介绍,没有深入讨论,而且有些模块我是在独立的文章中已经讨论过了,所以不便重复。
写本文的目的是因为在开发的过程中经常能感受到这种编程思想。所以忍不住去总结一下。