Pulpcode

捕获,搅碎,拼接,吞咽

0%

2014年终技术总结之从输入和输出的角度理解编程

首先我想引用《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
2
def add(i):
return i+1

那么我可以用这种夸张的方法得到一个6:

1
six = add(add(add(add(add(add(0))))))

看我用输入和输出将它们连接起来了。

比如之前我在工作中需要写一个功能,将一个整型值转化为枚举,再将枚举转换为一个英文字符串,再将这个英文字符串转化为中文字符串。

我分别实现了这些函数,最后用输入输出将它们相连接。

1
2
3
4
5
6
7
8
9
// 各种函数声明
Enum IntToEmun(int i);

string EnumToEString(Enum enum);

string EStringToCString(string estring);

// 虽然这只能说是函数的嵌套调用,但它确实表达出了输入和输出的思想
string cstring = EStringToCString(EnumToEString(IntToEmun(2)));

当然,因为一个函数是可以有多个参数的,所以如果你足够骚包,完全可以对这种多参数的函数用函数嵌套调用,用输入输出连接成一棵树。当然,这样写还真不如去玩lisp。

递归

大学学递归的时候,我常常不能在大脑中构建一种正确的模型去理解递归到底是什么,当我试图从输入输出的角度去理解递归时,我变得豁然开朗。

就拿下面两个简单的递归程序来说吧,它们本身都有自己的输入(函数参数)和输出(返回值)。我们讨论递归时,常常说的“自己调用自己”,其实就是在函数自身调用的时候,栈不断的生长,向下一个调用所要输入。在栈弹出的时候,又在向上一个调用给输出。你可以理解为在生长的过程中,构建了输入和输出的模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def fib(n):
"""
递归版斐波那契数列
"""
if n <=2:
return 1
else:
return fib(n-1) + fib(n-2)
def factorial(n):
"""
递归版阶乘
"""
if n < 1:
return 1
else:
return n * factorial(n-1)

更多关于递归的讲解,请关注我的系列博客-跟我一起写递归

lisp

lisp程序被一种称为S-表达式的东西组成。

默认的一段s-表达式会被求值(除非你定义其它求值方式,或者干脆不让其求值),会将第一个位置当作函数,将其与的部分,当作函数参数。

因为s-表达式又是结构递归的,所以所有未求值的函数参数又会用相同的方式进行求值(其本身可能又是另一个函数调用)。

1
2
3
4
5
6
7
8
(+ (- 5 1) (+ 3 7))
;; 14
(list 1 (+ 2 3))
;; (1 5)
(if (listp 1) (+ 1 2) (+ 3 4))
;; 7
(list (and (listp 3) t) (+ 1 2))
;; (nil 3)

我眼中的lisp程序就是用括号结构,将输入输出连接起来,而且你可以控制这种求值方式。

结束

因为本文主要是为了展示一种输入输出的思想,所以大部分内容只是介绍,没有深入讨论,而且有些模块我是在独立的文章中已经讨论过了,所以不便重复。

写本文的目的是因为在开发的过程中经常能感受到这种编程思想。所以忍不住去总结一下。