在一个c程序设计第四版中,在执行主函数后会得到一个值(函数值),即return 0 它的作用是什么?

C++&引用的作用和用法
我的总结:引用的好处之一就是在函数调用时在内存中不会生成副本。
引用总结(1)在引用的使用中,单纯给某个变量取个别名是毫无意义的,引用的目的主要用于在函数参数传递中,解决大块数据或对象的传递效率和空间不如意的问题。(2)用引用传递函数的参数,能保证参数传递中不产生副本,提高传递的效率,且通过const的使用,保证了引用传递的安全性。(3)引用与指针的区别是,指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。(4)使用引用的时机。流操作符&&和&&、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
&&引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。
引用的声明方法:类型标识符&&引用名=目标变量名;【例1】:&&int &ra=a;&&//定义引用ra,它是变量a的引用,即别名(1)&在此不是求地址运算,而是起标识作用。(2)类型标识符是指目标变量的类型。(3)声明引用时,必须同时对其进行初始化。(4)引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,且不能再把该引用名作为其他变量名的别名。ra=1;&等价于&a=1;&(5)声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。故:对引用求地址,就是对目标变量求地址。&ra与&a相等。(6)不能建立数组的引用。因为数组是一个由若干个元素所成的集合,所以无法建立一个数组的别名。
(7)不能建立引用的引用,不能建立指向引用的指针。因为引用不是一种数据类型!!所以没有引用的引用,没有引用的指针。
  int n;
  int &&r=n;//错误,编译系统把"int &"看成一体,把"&r"看成一体,即建立了引用的引用,引用的对象应当是某种数据类型的变量
  int &*p=n;//错误,编译系统把"int &"看成一体,把" *p "看成一体,即建立了指向引用的指针,指针只能指向某种数据类型的变量
(8)值得一提的是,可以建立指针的引用
  例如:
  int *p;
int *&q=p;//正确,编译系统把" int * "看成一体,把"&q"看成一体,即建立指针p的引用,亦即给指针p起别名q。引用应用1、引用作为参数&&&引用的一个重要作用就是作为函数的参数。以前的C语言中函数参数传递是值传递,如果有大块数据作为参数传递的时候,采用的方案往往是指针,因为这样可以避免将整块数据全部压栈,可以提高程序的效率。但是现在(C++中)又增加了一种同样有效率的选择(在某些特殊情况下又是必须的选择),就是引用。【例2】:void swap(int &p1,&&int &p2)&&//此处函数的形参p1, p2都是引用&{
p=p1;&&p1=p2;&&p2=p; }&&&为在程序中调用该函数,则相应的主调函数的调用点处,直接以变量作为实参进行调用即可,而不需要实参变量有任何的特殊要求。如:对应上面定义的swap函数,相应的主调函数可写为:
main( ){& int a,b; cin&&a&&b;&&//输入a,b两变量的值 swap(a,b);&&//直接以变量a和b作为实参调用swap函数& cout&&a&& ' ' &&b;&&//输出结果&}&上述程序运行时,如果输入数据10 20并回车后,则输出结果为20 10。由【例2】可看出:(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。void fun(int * a,int * b)
int t = (*a);
相应的主调函数为:
int main()
int i = 1,j = 2;
fun(&i,&j);
cout&&i&&" "&&j&&
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。2、常引用常引用声明方式:const&类型标识符&&引用名=目标变量名;&&用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到了引用的安全性。【例3】:&const int &ra=a;ra=1;&&//错误,不能通过引用对目标变量的值进行修改a=1;&&//正确&writing into constant object这不光是让代码更健壮,也有些其它方面的需要。【例4】:假设有如下函数声明:string foo( );void bar(string & s);那么下面的表达式将是非法的:bar(foo( ));bar("hello world");&原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。引用型参数应该在能被定义为const的情况下,尽量定义为const&。3、引用作为返回值要以引用返回函数值,则函数定义时要按以下格式:类型标识符&&函数名(形参列表及类型说明){函数体}说明:(1)以引用返回函数值,定义函数时需要在函数名前加&(2)用引用返回一个函数值的最大好处是,在内存中不产生被返回值的副本。【例5】:&以下程序中定义了一个普通的函数fn1(它用返回值的方法返回函数值),另外一个函数fn2,它以引用的方法返回函数值。#i nclude &iostream.h&float&&&&//定义全局变量tempfloat&&fn1(float r);&&//声明函数fn1float&&&fn2(float r);&&//声明函数fn2float&&fn1(float r)&&//定义函数fn1,它以返回值的方法返回函数值{& temp=(float)(r*r*3.14);& &}float &fn2(float r)&&//定义函数fn2,它以引用方式返回函数值{& temp=(float)(r*r*3.14);& }void main()&&//主函数{& float a=fn1(10.0);&&//第1种情况,系统生成要返回值的副本(即临时变量) float &b=fn1(10.0);&&//第2种情况,可能会出错(不同&C++系统有不同规定) //不能从被调函数中返回一个临时变量或局部变量的引用 float c=fn2(10.0);&&//第3种情况,系统不生成返回值的副本 //可以从被调函数中返回一个全局变量的引用 float &d=fn2(10.0);&&//第4种情况,系统不生成返回值的副本 //可以从被调函数中返回一个全局变量的引用 cout&&a&&c&&d;}&引用作为返回值,必须遵守以下规则:(1)不能返回局部变量的引用。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。&(2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak(内存泄露)。(3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。(4)引用与一些操作符的重载:&&&&流操作符&&和&&,这两个操作符常常希望被连续使用,例如:cout && "hello" && 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个&&操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用&&操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。&赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。【例6】&测试用返回引用的函数值作为赋值表达式的左值。#i nclude &iostream.h&int &put(int n);int vals[10];int error=-1;void main(){put(0)=10;&&//以put(0)函数值作为左值,等价于vals[0]=10;&put(9)=20;&&//以put(9)函数值作为左值,等价于vals[9]=10;&cout&&vals[0];&cout&&vals[9];}
int &put(int n){if (n&=0 && n&=9 ) return vals[n];&else { cout&&"subscript error"; }}
(5)在另外的一些操作符中,却千万不能返回引用:+-*/&四则运算符。它们不能返回引用,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。4、引用和多态&&&&&引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。【例7】:&class& A;class& B:public A{&&};B& b;A& &Ref =&&//&用派生类对象初始化基类对象的引用Ref&只能用来访问派生类对象中从基类继承下来的成员,是基类引用指向派生类。如果A类中定义有虚函数,并且在B类中重写了这个虚函数,就可以通过Ref产生多态效果。
Views(...) Comments()developerWorks 社区
如果您从事大型企业项目开发,您就会熟悉编写模块化代码的好处。良构的、模块化的代码更容易编写、调试、理解和重用。Java 开发人员的问题是,函数编程范型长期以来只是通过像 Haskell、Scheme、Erlang 和 Lisp 这样的特殊语言实现的。在本文中,作者 Abhijit Belapurkar 展示了,如何使用像闭包(closure)和 高阶函数(higher order function)这样的函数编程结构,在 Java 语言中编写良构的、模块化的代码。
(), 高级技术架构师, Infosys Technologies Limited
Abhijit Belapurkar 从印度德里市的印度理工学院获得了计算机科学方面的技术学士学位。他在分布式应用程序的体系结构和信息安全领域已有 10 多年的从业经验,并且在使用 Java 平台构建 N-层应用程序方面拥有 5 年以上的经验。他目前在印度班加罗尔市的 Inforsys Technologies Limited 担任 J2EE 方面的高级技术架构师。
Java 语言中常被忽视的一个方面是它被归类为一种命令式(imperative)编程语言。命令式编程虽然由于与 Java 语言的关联而相当普及,但是并不是惟一可用的编程风格,也不总是最有效的。在本文中,我将探讨在 Java
开发实践中加入不同的编程方法 ── 即函数编程(FP)。命令式编程是一种用程序状态描述计算的方法。使用这种范型的编程人员用语句改变程序状态。这就是为什么,像 Java 这样的程序是由一系列让计算机执行的命令 (或者语句) 所组成的。 另一方面,
函数编程是一种强调表达式的计算而非命令的执行的一种编程风格。表达式是用函数结合基本值构成的,它类似于用参数调用函数。
本文将介绍函数编程的基本特点,但是重点放在两个特别适用于 Java 开发框架的元素:闭包和高阶函数。如果您曾经使用过像 Python、Ruby 或者 Groovy (请参阅
) 这样的敏捷开发语言,那么您就可能已经遇到过这些元素。在这里,您将看到在 Java 开发框架中直接使用它们会出现什么情况。我将首先对函数编程及其核心元素做一个简短的、概念性的综述,然后用常用的编程场景展示,用结构化的方式使用闭包和高阶函数会给 Java 代码带来什么好处。
什么是函数编程?在经常被引用的论文 “Why Functional Programming Matters”(请参阅
) 中,作者 John Hughes 说明了模块化是成功编程的关键,而函数编程可以极大地改进模块化。在函数编程中,编程人员有一个天然框架用来开发更小的、更简单的和更一般化的模块, 然后将它们组合在一起。函数编程的一些基本特点包括:
支持闭包和高阶函数。支持懒惰计算(lazy evaluation)。使用递归作为控制流程的机制。加强了引用透明性。没有副作用。我将重点放在在 Java 语言中使用闭包和高阶函数上,但是首先对上面列出的所有特点做一个概述。闭包和高阶函数函数编程支持函数作为第一类对象,有时称为
仿函数(functor)对象。实质上,闭包是起函数的作用并可以像对象一样操作的对象。与此类似,FP 语言支持
高阶函数。高阶函数可以用另一个函数(间接地,用一个表达式) 作为其输入参数,在某些情况下,它甚至返回一个函数作为其输出参数。这两种结构结合在一起使得可以用优雅的方式进行模块化编程,这是使用 FP 的最大好处。
懒惰计算除了高阶函数和仿函数(或闭包)的概念,FP 还引入了
懒惰计算的概念。在懒惰计算中,表达式不是在绑定到变量时立即计算,而是在求值程序需要产生表达式的值时进行计算。延迟的计算使您可以编写可能潜在地生成无穷输出的函数。因为不会计算多于程序的其余部分所需要的值,所以不需要担心由无穷计算所导致的 out-of-memory 错误。一个懒惰计算的例子是生成无穷 Fibonacci 列表的函数,但是对
第 n 个Fibonacci 数的计算相当于只是从可能的无穷列表中提取一项。
递归FP 还有一个特点是用递归做为控制流程的机制。例如,Lisp 处理的列表定义为在头元素后面有子列表,这种表示法使得它自己自然地对更小的子列表不断递归。引用透明性函数程序通常还加强
引用透明性,即如果提供同样的输入,那么函数总是返回同样的结果。就是说,表达式的值不依赖于可以改变值的全局状态。这使您可以从形式上推断程序行为,因为表达式的意义只取决于其子表达式而不是计算顺序或者其他表达式的副作用。这有助于验证正确性、简化算法,甚至有助于找出优化它的方法。
副作用副作用是修改系统状态的语言结构。因为 FP 语言不包含任何赋值语句,变量值一旦被指派就永远不会改变。而且,调用函数只会计算出结果 ── 不会出现其他效果。因此,FP 语言没有副作用。这些基本描述应足以让您完成本文中的函数编程例子。有关这个主题的更多参考资料请参阅
Java 语言中的函数编程不管是否相信,在 Java 开发实践中您可能已经遇到过闭包和高阶函数,尽管当时您可能没有意识到。例如,许多 Java 开发人员在匿名内部类中封闭 Java 代码的一个词汇单元(lexical unit)时第一次遇到了
闭包。这个封闭的 Java 代码单元在需要时由一个
高阶函数执行。例如,清单 1 中的代码在一个类型为
java.lang.Runnable的对象中封闭了一个方法调用。
清单 1. 隐藏的闭包 Runnable worker = new Runnable()
public void run()
parseData();
parseData确实
封闭(因而有了名字 “闭包”)在
Runnable对象的实例
worker中。它可以像数据一样在方法之间传递,并可以在任何时间通过发送消息(称为
worker对象而执行。
更多的例子另一个在面向对象世界中使用闭包和高阶函数的例子是 Visitor 模式。如果还不熟悉这种模式,请参阅
以了解更多有关它的内容。基本上,Visitor 模式展现一个称为 Visitor 的参与者,该参与者的实例由一个复合对象(或者数据结构)接收,并应用到这个数据结构的每一个构成节点。Visitor 对象实质上
封闭了处理节点 / 元素的逻辑,使用数据结构的
accept (visitor)方法作为应用逻辑的高阶函数。
通过使用适当不同的 Visitor 对象(即闭包),可以对数据结构的元素应用完全不同的处理逻辑。与此类似,可以向不同的高阶函数传递同样的闭包,以用另一种方法处理数据结构(例如,这个新的高阶函数可以实现不同逻辑,用于遍历所有构成元素)。类
java.utils.Collections提供了另一个例子,这个类在版本 1.2 以后成为了 Java 2 SDK 的一部分。它提供的一种实用程序方法是对在
java.util.List中包含的元素排序。不过,它使调用者可以将排序列表元素的逻辑封装到一个类型为
parator的对象中,其中
Comparator对象作为第二个参数传递给排序方法。
sort方法的引用实现基于
合并 - 排序(merge-sort)算法。它通过对顺序中相邻的对象调用
Comparator对象的
compare方法遍历列表(list)中的元素。在这种情况下,
Comparator是闭包,而
Collections.sort方法是高阶函数。这种方式的好处是明显的:可以根据在列表中包含的不同对象类型传递不同的
Comparator对象(因为如何比较列表中任意两个对象的逻辑安全地封装在
Comparator对象中)。与此类似,
sort方法的另一种实现可以使用完全不同的算法(比如说,
快速排序(quick-sort)) 并仍然重复使用同样的
Comparator对象,将它作为基本函数应用到列表中两个元素的某种组合。
创建闭包广意地说,有两种生成闭包的技术,使用闭包的代码可以等效地使用这两种技术。创建闭包后,可以以统一的方式传递它,也可以向它发送消息以让它执行其封装的逻辑。因此,技术的选择是偏好的问题,在某些情况下也与环境有关。在第一种技术
表达式特化(expression specialization)中,由基础设施为闭包提供一个一般性的接口,通过编写这个接口的特定实现创建具体的闭包。在第二种技术
表达式合成(expression composition)中,基础设施提供实现了基本一元 / 二元 / 三元 /.../n 元操作(比如一元操作符
not和二元操作符
or)的具体帮助类。在这里,新的闭包由这些基本构建块的任意组合创建而成。
我将在下面的几节中详细讨论这两种技术。表达式特化假定您在编写一个在线商店的应用程序。商店中可提供的商品用类
SETLItem表示。每一件商品都有相关的标签价格,
SETLItem类提供了名为
getPrice的方法,对商品实例调用这个方法时,会返回该商品的标签价格。
item1的成本是否不低于
item2呢?在 Java 语言中,一般要编写一个像这样的表达式:
assert(item1.getPrice() &= item2.getPrice());像这样的表达式称为
二元谓词(binary predicate),
二元是因为它取两个参数,而
谓词是因为它用这两个参数做一些事情并生成布尔结果。不过要注意,只能在执行流程中执行上面的表达式,它的输出取决于
item2在特定瞬间的值。从函数编程的角度看,这个表达式还不是一般性的逻辑,就是说,它不能不管执行控制的当前位置而随心所欲地传递并执行。
为了使二元谓词发挥作用,必须将它封装到一个对象中,通过
特化(specializing)一个称为
BinaryPredicate的接口做到这一点,这个接口是由 Apache Functor 库提供的,如清单 2 所示。
清单 2. 表达式特化方法 sys.setl.
public class SETLItem
public int getPrice()
public void setPrice(int inPrice)
price = inP
public String getName()
public void setName(String inName)
name = inN
public String getCode()
public void setCode(String inCode)
code = inC
public String getCategory()
public void setCategory(String inCategory)
category = inC
public class PriceComparator implements Comparator
public int compare (Object o1, Object o2)
return (((SETLItem)o1).getPrice()-((SETLItem)o2).getPrice());
import mons.functor.*;
import mons.parator.*;
import java.util.*;
public class TestA
public static void main(String[] args)
Comparator pc = new PriceComparator();
BinaryPredicate bp = new IsGreaterThanOrEqual(pc);
SETLItem item1 = new SETLItem();
item1.setPrice(100);
SETLItem item2 = new SETLItem();
item2.setPrice(99);
if (bp.test(item1, item2))
System.out.println("Item1 costs more than Item2!");
System.out.println("Item2 costs more than Item1!");
SETLItem item3 = new SETLItem();
item3.setPrice(101);
if (bp.test(item1, item3))
System.out.println("Item1 costs more than Item3!");
System.out.println("Item3 costs more than Item1!");
catch (Exception e)
e.printStackTrace();
}BinaryPredicate接口以由 Apache Functor 库提供的
IsGreaterThanOrEqual类的形式特化。
PriceComparator类实现了
parator接口,并被作为输入传递给
IsGreaterThanOrEqual类。收到一个
test消息时,
IsGreaterThanOrEqual类自动调用
PriceComparator类的
compare方法。
compare方法预期接收两个
SETLItem对象,相应地它返回两个商品的价格差。
compare方法返回的正值表明
item1的成本不低于
初看之下,对一个相当基本的操作要做很多的工作,那它有什么好处呢?特化
BinaryPredicate接口(而不是编写 Java 比较表达式) 使您无论在何时何地都可以比较任意两个商品的价格。可以将
bp对象作为数据传递并向它发送消息,以在任何时候、使用这两个参数的任何值来执行它(称为
表达式合成表达式合成是得到同样结果的一种稍有不同的技术。考虑计算特定
SETLItem的净价问题,要考虑当前折扣和销售税率。清单 3 列出了这个问题基于仿函数的解决方式。
清单 3. 表达式合成方法 sys.setl.
import mons.functor.BinaryF
import mons.functor.UnaryF
import mons.functor.adapter.LeftBoundF
public class Multiply implements BinaryFunction
public Object evaluate(Object left, Object right)
return new Double(((Double)left).doubleValue() * ((Double)right).doubleValue());
import mons.functor.*;
import mons.posite.*;
import mons.functor.adapter.*;
import mons.functor.UnaryF
public class TestB
public static void main(String[] args)
double discountRate = 0.1;
double taxRate=0.33;
SETLItem item = new SETLItem();
item.setPrice(100);
UnaryFunction calcDiscountedPrice =
new RightBoundFunction(new Multiply(), new Double(1-discountRate));
UnaryFunction calcTax =
new RightBoundFunction(new Multiply(), new Double(1+taxRate));
CompositeUnaryFunction calcNetPrice =
new CompositeUnaryFunction(calcTax, calcDiscountedPrice);
Double netPrice = (Double)calcNetPrice.evaluate(new Double(item.getPrice()));
System.out.println("The net price is: " + netPrice);
catch (Exception e)
e.printStackTrace();
}BinaryFunction类似于前面看到的
BinaryPredicate,是一个由 Apache Functor 提供的一般化仿函数(functor)接口。
BinaryFunction接口有两个参数并返回一个
Object值。类似地,
UnaryFunction是一个取一个
Object参数并返回一个
Object值的仿函数接口。
RightBoundFunction是一个由 Apache 库提供的适配器类,它通过使用常量右参数(right-side argument)将
BinaryFunction适配给
UnaryFunction接口。即,在一个参数中收到相应的消息(
evaluate) 时,它在内部用两个参数发送一个
evaluate消息给正在适配的
BinaryFunction── 左边的是发送给它的参数,右边的是它知道的常量。您一定会猜到,名字
RightBoundFunction来自于常量值是作为第二个 (右边) 参数传递这一事实。(是的,Apache 库还提供了一个
LeftBoundFunction,其中常量是作为第一个参数或者左参数传递的。)
用于双精度相乘的特化的 BinaryFunction清单 3 显示了名为
Multiply的特化的
BinaryFunction,它取两个
Double作为输入并返回一个新的、由前两个双精度值相乘而得到
calcDiscountedRate中实例化了一个新的
RightBoundFunction,它通过用
(1 - discountRate)作为其常量第二参数,将二元
Multiply函数适配为一元接口。
结果,可以用一个
Double参数向
calcDiscountRate发送一个名为
evaluate的消息。在内部,输入参数
Double乘以
calcDiscountRate对象本身包含的常量值。
与此类似,在
calcTaxRate中实例化了一个新的
RightBoundFunction,它通过用
(1 + taxRate)作为其第二个常量参数将二元
Multiply函数适配为一元接口。结果,可以用一个
Double参数向
calcTaxRate发送一个名为
evaluate的消息。在内部,输入参数
Double乘以
calcTaxRate对象本身包含的常量值。
这种将多参数的函数重新编写为一个参数的函数的合成(composition)技术也称为
currying。
合成魔术在最后的时候就发挥作用了。实质上,计算对象净价的算法是首先计算折扣价格(使用
calcDiscountRate仿函数),然后通过在上面加上销售税(用
calcSalesTax仿函数)计算净价。就是说,需要组成一个函数,在内部调用第一个仿函数并将计算的输出流作为第二个仿函数的计算的输入。Apache 库提供了用于这种目的的一个内置仿函数,称为
CompositeUnaryFunction。
在清单 3 中,
CompositeUnaryFunction实例化为变量
calcNetPrice,作为
calcDiscountRate和
calcSalesTax仿函数的合成。与前面一样,将可以向其他函数传递这个对象,其他函数也可以通过向它发送一个包含商品参数的
evaluate消息要求它计算这种商品的净价。
一元与二元合成在清单 3 中,您看到了
一元合成的一个例子,其中一个一元仿函数的结果是另一个的输入。另一种合成称为
二元合成,作为
evaluate消息的一部分,需要传递两个一元仿函数的结果作为二元仿函数的参数。
清单 4 是说明二元合成的必要性和风格的一个例子。假定希望保证商店可以给出的最大折扣有一个最大限度。因此,必须将作为
calcDiscount仿函数计算结果得到的折扣量与 cap 值进行比较,并取最小值作为计算出的折扣价格。折扣价格是通过用标签价减去实际的折扣而计算的。
二元合成 sys.setl.
import mons.functor.BinaryF
public class Subtract implements BinaryFunction
public Object evaluate(Object left, Object right)
return new Double(((Double)left).doubleValue()-((Double)right).doubleValue());
import mons.functor.BinaryF
import mons.functor.UnaryF
public class BinaryFunctionUnaryFunction implements UnaryFunction
private BinaryF
public BinaryFunctionUnaryFunction(BinaryFunction f)
function=f;
public Object evaluate(Object obj)
return function.evaluate(obj,obj);
import mons.functor.*;
import mons.posite.*;
import mons.functor.adapter.*;
import mons.functor.UnaryF
import mons.functor.core.C
import mons.parator.M
public class TestC
public static void main(String[] args)
double discountRate = 0.1;
double taxRate=0.33;
double maxDiscount = 30;
SETLItem item = new SETLItem();
item.setPrice(350);
UnaryFunction calcDiscount =
new RightBoundFunction(new Multiply(), new Double(discountRate));
Constant cap = new Constant(new Double(maxDiscount));
BinaryFunction calcActualDiscount =
new UnaryCompositeBinaryFunction (new Min(), calcDiscount, cap);
BinaryFunctionUnaryFunction calcActualDiscountAsUnary =
new BinaryFunctionUnaryFunction(calcActualDiscount);
BinaryFunction calcDiscountedPrice =
new UnaryCompositeBinaryFunction (new Subtract(), new Identity()
, calcActualDiscountAsUnary);
BinaryFunctionUnaryFunction calcDiscountedPriceAsUnary =
new BinaryFunctionUnaryFunction(calcDiscountedPrice);
UnaryFunction calcTax =
new RightBoundFunction(new Multiply(), new Double(1+taxRate));
CompositeUnaryFunction calcNetPrice =
new CompositeUnaryFunction(calcTax, calcDiscountedPriceAsUnary);
Double netPrice = (Double)calcNetPrice.evaluate(new Double(item.getPrice()));
System.out.println("The net price is: " + netPrice);
}通过首先观察所使用的 Apache Functor 库中的三个标准仿函数,开始分析和理解这段代码。UnaryCompositeBinaryFunction仿函数取一个二元函数和两个一元函数作为输入。首先计算后两个函数,它们的输出作为输入传递给二元函数。在清单 4 中对二元合成使用这个仿函数两次。
Constant仿函数的计算总是返回一个常量值(即在其构造时输入的值),不管以后任何计算消息中传递给它的参数是什么值。在清单 4 中,变量
cap的类型为
Constant并总是返回最大折扣数量。
Identity仿函数只是返回作为
evaluate消息的输入参数传递给它的这个对象作为输出。清单 4 显示
Identity仿函数的一个实例,该仿函数是在创建
calcDiscountedPrice时作为一个一元仿函数创建和传递的。同时在清单 4 中,
evaluate消息包含标签价格作为其参数,这样
Identity仿函数就返回标签价格作为输出。
第一个二元合成在用计算
calcDiscount(通过对标签价格直接应用折扣率)和
UnaryCompositeBinaryFunction设置变量
calcActualDiscount时是可见的。这两个一元仿函数计算的输出传递给称为
Min的内置二元仿函数,它比较这两者并返回其中最小的值。
这个例子显示了定制类
BinaryFunctionUnaryFunction。这个类适配一个二元仿函数,使它像一元仿函数的接口。就是说,当这个类接收一个带有一个参数的
evaluate消息时,它在内部发送 (向其封装的二元函数)一个
evaluate消息,它的两个参数是作为输入接收的同一个对象。因为
calcActualDiscount是二元函数,所以通过类型为
BinaryFunctionUnaryFunction的
calcActualDiscountAsUnary实例将它包装到一个一元仿函数接口中。很快就可以看到包装
calcActualDiscount为一元仿函数的理由。
UnaryCompositeBinaryFunction设置变量
calcDiscountedPrice时发生第二个二元合成。
UnaryCompositeBinaryFunction向新的
Identity实例和
calcActualDiscountAsUnary对象发送
evaluation消息,这两个消息的输入参数都是标签价格。
这两个计算(它们分别得出标签价格和实际的折扣值)的输出传递给名为
Subtract的定制二元仿函数。当向后一个对象发送
evaluate消息时,它立即计算并返回两个参数之间的差距(这是商品的折扣价)。这个二元仿函数也用定制的
BinaryFunctionUnaryFunction包装为一个名为
calcDiscountedPriceAsUnary的一元仿函数对象。
与前面的情况一样,代码通过两个
calcTax一元仿函数(也在清单 3 中遇到)和
calcDiscountedPriceAsUnary(在前面一段中描述)创建
CompositeUnaryFunction,而以一个一元合成完成。这样得到的
calcNetPrice变为接收一个
evaluate消息和一个参数(所讨论商品的标签价格),而在内部,首先用这个参数计算
calcDiscountedPriceAsUnary仿函数,然后用前一个计算的输出作为参数计算
calcTax仿函数。
使用闭包实现业务规则Apache Library 提供了各种不同的内置一元和二元仿函数,它使得将业务逻辑编写为可以传递并且可以用不同的参数在不同的位置执行的对象变得非常容易。在后面几节中,我将使用一个简单的例子展示对一个类似问题的函数编程方式。假定一个特定的商品是否可以有折扣取决于该商品的类别和定价。具体说,只有 Category “A” 中定价高于 100 美元和 Category “B“ 中定价高于 200 美元的商品才有资格打折。清单 5 中的代码显示了一个名为
isEligibleForDiscount的业务规则对象 (
UnaryPredicate),如果用一个
item对象作为参数发送
evaluate消息,将返回一个表明是否可以对它打折的 Boolean。
清单 5. 一个函数业务规则对象sys.setl.
import mons.functor.BinaryP
import mons.functor.UnaryP
public class BinaryPredicateUnaryPredicate implements UnaryPredicate
private BinaryP
public BinaryPredicateUnaryPredicate(BinaryPredicate prd)
public boolean test(Object obj)
return bp.test(obj,obj);
import mons.functor.*;
import mons.posite.*;
import mons.functor.adapter.*;
import mons.functor.UnaryF
import mons.functor.core.C
import mons.functor.core.IsE
import mons.parator.IsGreaterThanOrE
import mons.parator.M
import mons.functor.core.I
public class TestD
public static void main(String[] args)
SETLItem item1 = new SETLItem();
item1.setPrice(350);
item1.setCategory("A");
SETLItem item2 = new SETLItem();
item2.setPrice(50);
item2.setCategory("A");
SETLItem item3 = new SETLItem();
item3.setPrice(200);
item3.setCategory("B");
UnaryFunction getItemCat =
new UnaryFunction()
public Object evaluate (Object obj)
return ((SETLItem)obj).getCategory();
UnaryFunction getItemPrice =
new UnaryFunction()
public Object evaluate
(Object obj)
return new Double(((SETLItem)obj).getPrice());
Constant catA = new Constant("A");
Constant catB = new Constant("B");
Constant usd100 = new Constant(new Double(100));
Constant usd200 = new Constant(new Double(200));
BinaryPredicateUnaryPredicate belongsToCatA = new BinaryPredicateUnaryPredicate
(new UnaryCompositeBinaryPredicate(new IsEqual(), getItemCat, catA));
BinaryPredicateUnaryPredicate belongsToCatB = new BinaryPredicateUnaryPredicate
(new UnaryCompositeBinaryPredicate(new IsEqual(), getItemCat, catB));
BinaryPredicateUnaryPredicate moreThanUSD100 = new BinaryPredicateUnaryPredicate
(new UnaryCompositeBinaryPredicate(new IsGreaterThanOrEqual(),
getItemPrice, usd100));
BinaryPredicateUnaryPredicate moreThanUSD200 = new BinaryPredicateUnaryPredicate
(new UnaryCompositeBinaryPredicate(new IsGreaterThanOrEqual(),
getItemPrice, usd200));
UnaryOr isEligibleForDiscount = new UnaryOr(new UnaryAnd(belongsToCatA,
moreThanUSD100),
new UnaryAnd(belongsToCatB, moreThanUSD200));
if (isEligibleForDiscount.test(item1))
System.out.println("Item #1 is eligible for discount!");
System.out.println("Item #1 is not eligible for discount!");
if (isEligibleForDiscount.test(item2))
System.out.println("Item #2 is eligible for discount!");
System.out.println("Item #2 is not eligible for discount!");
if (isEligibleForDiscount.test(item3))
System.out.println("Item #3 is eligible for discount!");
System.out.println("Item #3 is not eligible for discount!");
}使用 ComparableComparator清单 5 中可能注意到的第一件事是我利用了名为
isEqual(用于检查所说商品的类别是否等于 “A”或者“B ”) 和
isGreaterThanOrEqual(用于检查所述商品的定价是否大于或者等于指定值,对于 Category “A” 商品是 100,对于 Category “B” 商品是 200) 的内置二元谓词仿函数。
您可能还记得在
中,原来必须传递
PriceComparator对象(封装了比较逻辑)以使用
isGreaterThanOrEqual仿函数进行价格比较。不过在清单 5 中,不显式传递这个
Comparator对象。如何做到不需要它? 技巧就是,在没有指定该对象时,
isGreaterThanOrEqual仿函数(对这一点,甚至是
IsEqual仿函数)使用默认的
ComparableComparator。这个默认的
Comparator假定两个要比较的对象实现了
parable接口,并对第一个参数(在将它类型转换为
Comparable后)只是调用
compareTo方法,传递第二个参数作为这个方法的参数。
通过将比较工作委派给这个对象本身,对于 String 比较(像对
item目录所做的)和
Double比较(像对
item价格所做的),可以原样使用默认的
Comparator。
Double都是实现了
Comparable接口的默认 Java 类型。
将二元谓词适配为一元可能注意到的第二件事是我引入了一个名为
BinaryPredicateUnaryPredicate的新仿函数。这个仿函数(类似于在
中第一次遇到的
BinaryFunctionUnaryFunction仿函数)将一个二元谓词接口适配为一元接口。
BinaryPredicateUnaryPredicate仿函数可以认为是一个带有一个参数的一元谓词:它在内部用同一个参数的两个副本计算包装的二元谓词。
isEligibleForDiscount对象封装了一个完整的业务规则。如您所见,它的构造方式 ── 即,通过将构造块从下到上放到一起以构成更复杂的块,再将它们放到一起以构成更复杂的块,等等 ── 使它本身天然地成为某种“可视化的”规则构造器。最后的规则对象可以是任意复杂的表达式,它可以动态地构造,然后传递以计算底层业务规则。
对集合操作GoF Iterator 模式(请参阅
) 提供了不公开其底层表示而访问集合对象的元素的方法。这种方法背后的思路是迭代与数据结构不再相关联 (即它不是集合的一部分)。这种方式本身要使用一个表示集合中特定位置的对象,并用一个循环条件 (在集合中增加其逻辑位置)以遍历集合中所有元素。循环体中的其他指令可以检查和 / 或操作集合中当前
Iterator对象位置上的元素。在本例中,我们对迭代没有什么控制。(例如,必须调用多少次
next、每次试图访问
next元素时必须首先检查超出范围错误。) 此外,迭代器必须使用与别人一样的公共接口访问底层数据结构的“成员”,这使得访问效率不高。这种迭代器常被称为 “外部迭代器(External Iterator)”。
FP 对这个问题采取了一种非常不同的方式。集合类有一个高阶函数,后者以一个仿函数作为参数并在内部对集合的每一个成员应用它。在本例中,因为迭代器共享了数据结构的实现,所以您可以完成控制迭代。此外,迭代很快,因为它可以直接访问数据结构成员。这种迭代器常被称为
内部迭代器(internal Iterator)。
Apache Functor 库提供了各种非严格地基于 C++ 标准模板库实现的内部
Iterator。它提供了一个名为
Algorithms的
实用工具类,这个类有一个名为
foreach的方法。
foreach方法以一个
Iterator对象和一个一元
Procedure作为输入,并对遍历
Iterator时遇到的每一个元素(元素本身是作为过程的一个参数传递的)运行一次。
使用内部迭代器一个简单的例子将可以说明外部和内部
Iterator的不同。假定提供了一组
SETLItem对象并要求累积列表中成本高于 200 美元的那些商品的定价。清单 6 展现了完成这一工作的代码。
清单 6. 使用外部和内部迭代器sys.setl.
import java.util.*;
import mons.functor.A
import mons.functor.UnaryF
import mons.functor.UnaryP
import mons.functor.core.C
import mons.functor.core.collection.FilteredI
import mons.parator.IsGreaterThanOrE
import mons.posite.UnaryCompositeBinaryP
public class TestE
public static void main(String[] args)
Vector items = new Vector();
for (int i=0; i&10; i++)
SETLItem item = new SETLItem();
if (i%2==0)
item.setPrice(101);
item.setPrice(i);
items.add(item);
TestE t = new TestE();
System.out.println("The sum calculated using External Iterator is: " +
t.calcPriceExternalIterator(items));
System.out.println("The sum calculated using Internal Iterator is: " +
t.calcPriceInternalIterator(items));
public int calcPriceExternalIterator(List items)
int runningSum = 0;
Iterator i = items.iterator();
while (i.hasNext())
int itemPrice = ((SETLItem)i.next()).getPrice();
if (itemPrice &= 100)
runningSum += itemP
return runningS
public int calcPriceInternalIterator(List items)
Iterator i = items.iterator();
UnaryFunction getItemPrice =
new UnaryFunction()
public Object evaluate (Object obj)
return new Double(((SETLItem)obj).getPrice());
Constant usd100 = new Constant(new Double(100));
BinaryPredicateUnaryPredicate moreThanUSD100 = new BinaryPredicateUnaryPredicate
(new UnaryCompositeBinaryPredicate(new IsGreaterThanOrEqual(),
getItemPrice, usd100));
FilteredIterator fi = new FilteredIterator(i, moreThanUSD100);
Summer addPrice = new Summer();
Algorithms.foreach(fi, addPrice);
return addPrice.getSum();
static class Summer implements UnaryProcedure
private int sum=0;
public void run(Object obj)
sum += ((SETLItem)obj).getPrice();
public int getSum()
main()方法中,设置一个有 10 种商品的列表,其中奇数元素的价格为 101 美元。(在真实应用程序中,将使用调用 JDBC 获得的
ResultSet而不是本例中使用的
Vector。)
然后用两种不同的方法对列表执行所需要的操作:
calcPriceExternalIterator和
calcPriceInternalIterator。正如其名字所表明的,前者基于
ExternalIterator而后者基于
InternalIterator。您将关心后一种方法,因为所有 Java 开发人员都应该熟悉前者。注意
InternalIterator方法使用由 Apache Functor 库提供的两个结构。
第一个结构称为
FilteredIterator。它取一个迭代器加上一个一元
谓词作为输入,并返回一个带有所感兴趣的属性的
Iterator。这个属性给出了在遍历
Iterator时遇到的每一个满足在
谓词中规定的条件的元素。(因此由
FilteredIterator的一个实例返回的 Iterator 可以作为
FilteredIterator的第二个实例的输入传递,以此类推,以设置过滤器链,用于根据多种标准分步挑出元素。)在本例中,只对满足
一元谓词“大于或等于 100 美元”规则的商品感兴趣。这种规则是在名为
moreThanUSD100的
BinaryPredicateUnaryPredicate中规定的,我们在
中第一次遇到了它。
Apache Functor 库提供的第二个结构是名为
Algorithms的实用程序类,在
过这个类。在这个例子中,名为
Summer的一元过程只是包含传递给它的
SETLItem实例的定价,并将它添加到(本地)运行的 total 变量上。这是一个实现了前面讨论的内部迭代器概念的类。
使用仿函数进行集合操纵我讨论了用仿函数和高阶函数编写模块的大量基础知识。我将用最后一个展示如何用仿函数实现集合操纵操作的例子作为结束。通常,有两种描述集合成员关系的方式。第一种是完全列出集合中的所有元素。这是 Java 编程人员传统上使用的机制 ──
java.util.Set接口提供了一个名为
add(Object)的方法,如果作为参数传递到底层集合中的对象还未存在的话,该方法就添加它。
不过,当集合中的元素共享某些公共属性时,通过声明惟一地标识了集合中元素的属性可以更高效地描述集合的成员关系。例如,后一种解决方案适合于集合成员的数量很大,以致不能在内存中显式地维护一个集合实现(像前一种方式那样)的情况。在这种情况下,可以用一个一元
谓词表示这个集合。显然,一个一元
谓词隐式定义了一组可以导致谓词计算为
true的所有值(对象)。事实上,所有集合操作都可以用不同类型的谓词组合来定义。清单 7 中展示了这一点:
清单 7. 使用仿函数的集合操作 sys.setl.
import mons.functor.UnaryP
import mons.posite.UnaryA
import mons.posite.UnaryN
import mons.posite.UnaryOr;
public class SetOps
public static UnaryPredicate union(UnaryPredicate up1, UnaryPredicate up2)
return new UnaryOr(up1, up2);
public static UnaryPredicate intersection(UnaryPredicate up1, UnaryPredicate up2)
return new UnaryAnd(up1, up2);
public static UnaryPredicate difference(UnaryPredicate up1, UnaryPredicate up2)
return new UnaryAnd(up1, new UnaryNot(up2));
public static UnaryPredicate symmetricDifference(UnaryPredicate up1,
UnaryPredicate up2)
return difference(union(up1, up2), intersection(up1, up2));
谓词来描述集合
并集(union)和
交集(intersection)操作的定义应当是明确的:如果一个对象至少使指示两个集合的两个一元
谓词中的一个计算为
true,那么这个对象就属于两个集合的并集(逻辑
Or);如果它使两个一元
谓词都计算为
true,那么它就属于两个集合的交集(逻辑
两个集合的差在数学上定义为属于第一个集合但是不属于第二个集合一组元素。根据这个定义,静态方法
difference也容易理解。
最后,两个集合的对称差(symmetric difference)定义为只属于两个集合中的一个(或者两个都不属于)的所有元素。这可以取两个集合的并集,然后从中删除属于两个集合的交集的元素得到。就是说,它是对原来的集合(在这里是一元
谓词)使用
intersection操作分别得到的两个集合的
difference操作。后一个定义解释了为什么用前三种方法作为第四个方法中的构建块。
结束语模块化是任何平台上高生产率和成功的编程的关键,这一点早已被认识到了。Java 开发人员的问题是模块化编程不仅是将问题分解,它还要求能将小的解决方案粘接到一起,成为一个有效的整体。由于这种类型的开发继承了函数编程范型,在 Java 平台上开发模块化代码时使用函数编程技术应是很自然的事。在本文中,我介绍了两种函数编程技术,它们可以容易地结合到 Java 开发实践中。正如从这里看到的,闭包和高阶函数对于 Java 开发人员来说并不是完全陌生的,它们可以有效地结合以创建一些非常有用的模块化解决方案。我希望本文提供了在 Java 代码中结合闭包和高阶函数的很好基础,并使您见识到了函数编程的优美和效率。要学习有关本文所讨论的概念和技术的更多内容,请参阅
参考资料 您可以参阅本文在 developerWorks 全球站点上的
从 Jakarta Commons Project
不要错过 John Hughes 的关于函数编程的研讨会论文“
利用 David Mertz 的“
developerWorks,2001 年 3 月)学习有关函数编程的更多内容,并复习有关 Python 的内容。
Haskell 是最常用的一种函数编程语言。利用
developerWorks中的
(2001 年 9 月) 学习更多有关它的内容。
更多实用的方法,请参阅 David Mertz 的“
developerWorks,2001 年 10 月),可以从中学习如何将 Haskell 应用到 XML 处理中。
系列探讨了 Java 平台上的替代语言,其中许多语言利用了像闭包和第一类函数这样的函数(和敏捷)编程概念。
Jared Jackson 的“
developerWorks,2002 年 10 月)讨论了利用 XSL 作为函数语言的内容,特别是它的递归方面。
Scheme 是另一种带有强烈函数趋势的编程语言。利用 David Mertz 对
developerWorks,2003 年 10 月) 的介绍学习有关它的更多内容。
的 Java Generic Libraries 提供了替代 Apache Coomons Functor 库在 Java 平台上进行函数编程的选择。
开放源代码项目
Java 平台上进行函数编程的另一种选择,
是第三种选择。
(E. Gamma、R. Helm、R. Johnson 和 J. Vlissides,Addison-Wesley,1995 年),学习更多有关 Visitor 和 GoF 设计模式(及一般性设计模式) 的更多内容。
(Guy Cousineau 和 Michel Mauny,剑桥大学出版社,1988 年) 很好地介绍了函数编程的目标。
(Simon Thompson,Addison-Wesley Longman Limited,1996 年)对函数编程语言 Haskell 作了很好的介绍。
上免费 Java 内容教程的完整列表。
,获得技术书籍的完整列表,其中包括数百本
developerWorks: 登录
标有星(*)号的字段是必填字段。
保持登录。
单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件。
在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。
所有提交的信息确保安全。
选择您的昵称
当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。昵称长度在 3 至 31 个字符之间。
您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。
标有星(*)号的字段是必填字段。
(昵称长度在 3 至 31 个字符之间)
单击提交则表示您同意developerWorks 的条款和条件。 .
所有提交的信息确保安全。
文章、教程、演示,帮助您构建、部署和管理云应用。
立即加入来自 IBM 的专业 IT 社交网络。
免费下载、试用软件产品,构建应用并提升技能。
static.content.url=/developerworks/js/artrating/SITE_ID=10Zone=Java technologyArticleID=53193ArticleTitle=Java 语言中的函数编程publish-date=

我要回帖

更多关于 c程序设计第四版 的文章

 

随机推荐