这个扩展可以通过插件支持的方式很方便的让表格的行可以拖动排序,下面是预览效果。HTML页面的代码可以看预览效果的框架页的代码
/*
** edit by Dickhead
** goal 让GridPanel的行支持拖动排序的插件
** 插件方式支持
** time 2011-08-05 22:04:27
*/
//<extension>
(function () {
//先扩展一个拖放操作的放置区
Ext.ns('Ext.ux.grid');
Ext.ux.grid.GridDropZone = Ext.extend(Ext.dd.DropZone, {
ddGroup: 'GridDD',
constructor: function (grid, config) {
this.view = grid.getView();
Ext.ux.grid.GridDropZone.superclass.constructor.call(this, this.view.scroller.dom, config);
this.proxyEl = document.createElement('div');
this.proxyEl.style.cssText = "background:url(http://images.cnblogs.com/cnblogs_com/dickhead/314392/o_arrow_right.png);" +
"width:16px;height:16px;position:absolute;";
Ext.fly(this.proxyEl).insertAfter(this.view.scroller).hide();
},
getTargetFromEvent: function (e) {
return e.getTarget(this.view.rowSelector);
},
onNodeEnter: function (target, dd, e, data) {
var rowIndex = this.view.findRowIndex(target);
data.targetRowIndex = rowIndex;
if (rowIndex !== false) {
rowIndex < data.rowIndex && (rowIndex += 1);
data.targetRowIndex = rowIndex;
if (rowIndex !== data.rowIndex) {
Ext.fly(this.proxyEl).show().alignTo(target, 'br-bl', [16, 9]);
return;
}
}
Ext.fly(this.proxyEl).hide();
},
onNodeOut: function () {
Ext.fly(this.proxyEl).hide();
},
onNodeOver: function (target, dd, e, data) {
if (data.targetRowIndex !== false && data.targetRowIndex !== data.rowIndex) {
return Ext.dd.DropZone.prototype.dropAllowed;
} else {
return Ext.dd.DropZone.prototype.dropNotAllowed;
}
},
onNodeDrop: function (target, dd, e, data) {
if (data.targetRowIndex !== data.rowIndex) {
var record = data.grid.store.getAt(data.rowIndex);
data.grid.store.data.removeAt(data.rowIndex);
data.grid.store.data.insert(data.targetRowIndex, record);
this.view.refresh();
return true;
}
return false;
},
destroy: function () {
Ext.destroy(this.proxyEl);
delete this.proxyEl;
Ext.ux.grid.GridDropZone.destroy.apply(this, arguments);
}
});
//支持行拖动的插件
//扩展自Ext.util.Observable方便以后添加事件支持
Ext.ux.grid.RowGragable = Ext.extend(Ext.util.Observable, {
init: function (grid) {
grid.enableDragDrop = true;
var group = Math.random().toString();
grid.ddGroup = group;
var dropZone;
grid.on('afterrender', function () {
dropZone = new Ext.ux.grid.GridDropZone(grid, { ddGroup: group });
});
grid.on('destroy', function () {
dropZone.destroy();
});
}
})
})();
//</extension>
//sample 使用插件让表格支持行拖动
Ext.onReady(function () {
Ext.get('loading').remove();
new Ext.grid.GridPanel({
height: 130, plugins: [new Ext.ux.grid.RowGragable()],
columns: [
new Ext.grid.RowNumberer({ renderer: function () { return ' '; } }),
{ header: '书名', dataIndex: 'BookName' },
{ header: '作者', dataIndex: 'Author' },
{ header: '价格', dataIndex: 'Price' },
{ header: '出版日期', dataIndex: 'PublishDate' }
],
store: {
xtype: 'simplestore', fields: ['BookName', 'Author', 'Price', 'PublishDate'],
data: [
['CLR via C#', 'Jeffrey Richter', '99.0', '2010-04-13'],
['北京售楼小姐', '不详', '54.0', '2011-02-13'],
['武经七书1', '大概是神', '54.0', '2010-04-13'],
['空谷幽兰', 'somebody', '99.0', '2010-04-13']
]
},
renderTo: 'main'
});
})
简单的扩展,让表单的tooltip更简单,博客园写东西没预览功能吗?下面是预览效果,哈哈,看了下还好
/*
** edit by dickhead
** goal 扩展form表单元素,可以便捷的配置其tooltip 。
** 以插件或者其他方便插入的方式实现的扩展
** time 2011-08-04 23:06:45
*/
//<extension>
(function () {
var fieldTipTpl = new Ext.Template([
'<div style="padding-left:18px;border-bottom:1px dotted gray;',
'background:url(http://images.findicons.com/files/icons/99/office/16/info.png) no-repeat left center;',
'font-weight:bold;padding-bottom:2px;">提示</div>',
'<div style="padding:3px 2px;">{tip}</div>'
].join(''));
fieldTipTpl.compile();
/*
** @cfg tooltip {string} 一段描述性文字,可以是html代码段
*/
Ext.form.Field.prototype.afterRender = Ext.form.Field.prototype.afterRender.createSequence(function () {
if (this.tooltip) {
var el = this.el.next() || this.el; //为什么要next?因为triggerfield(日期/下拉等空间)后面借这个img标签。
var tip = new Ext.ToolTip({
target: el, autoHide: false, anchor: 'left',
html: fieldTipTpl.apply({ tip: this.tooltip }),
showDelay: 100000,
onTargetOver: Ext.emptyFn
});
this.on('focus', function () {
tip.show();
});
this.on('blur', function () {
tip.hide();
});
this.on('destroy', function () {
tip.destroy();
});
}
});
})();
//</extension>
Ext.onReady(function () {
new Ext.form.FormPanel({
border: false,
items: [
{ xtype: 'textfield', tooltip: '不是你的就不是你的,该放手的就要放手,包括内存资源。',
fieldLabel: '兴趣史'
},
{ xtype: 'datefield', fieldLabel: '选择个日期',
tooltip: 'Ext为什么叫ext。。。'
}
],
renderTo: 'main'
});
});
还是截个图吧,我表述的不是很清楚

本人用的是extjs3.3.1.如扩展代码无法使用,请根据各个版本进行修改。
扩展内容:本扩展用于使Ext.grid.GridView(以及任何继承自Ext.grid.GridView的类,如GroupingView、LockingGridView)支持如下的功能
1、在双击表头的调整列宽的区域,能够自动的将该列调整到最适合的宽度,够刚好容纳该列中最宽的内容
2、在列头菜单中加入一个菜单项,菜单项功能也是实现1中的功能。
说明:下文不会对代码进行详细的解释,许多细节希望读者可以自己细究,顺便提下,很多细节需要通过阅读ext.debug.js去了解。图我就不截了,扩展代码的使用方法很简单,只要将它放在一个Js文件中,添加到页面就可以支持上述的两个扩展功能了。
//使得双击表头的resize区域可以让该列自动将宽度置为最适合宽度{
Ext.grid.GridView.prototype.splitHandleWidth = 8;
Ext.grid.GridView.SplitDragZone.prototype.init = Ext.grid.GridView.SplitDragZone.prototype.init.createSequence(function (id, sGroup, config) {
Ext.EventManager.on(this.id, 'dblclick', this.handleDblClick, this);
Ext.EventManager.on(this.id, 'click', this.handleClick, this);
});
Ext.grid.GridView.SplitDragZone.prototype.destroy = Ext.grid.GridView.SplitDragZone.prototype.destroy.createSequence(function () {
Ext.EventManager.un(this.id, 'dblclick', this.handleDblClick, this);
Ext.EventManager.un(this.id, 'click', this.handleClick, this);
});
Ext.grid.GridView.SplitDragZone.prototype.setOuterHandleElId = Ext.grid.GridView.SplitDragZone.prototype.setOuterHandleElId.createSequence(function (id) {
Ext.EventManager.on(id, 'dblclick', this.handleDblClick, this);
Ext.EventManager.on(id, 'click', this.handleClick, this);
});
Ext.grid.GridView.SplitDragZone.prototype.handleClick = function (e, el, o) {
var t = this.view.findHeaderCell(e.getTarget());
this.isHit = false;
if (t && this.allowHeaderDrag(e)) {
var xy = this.view.fly(t).getXY(),
x = xy[0],
exy = e.getXY(),
ex = exy[0],
w = t.offsetWidth,
adjust = false;
if ((ex - x) <= this.hw) {
adjust = -1;
} else if ((x + w) - ex <= this.hw) {
adjust = 0;
}
if (adjust !== false) {
this.cm = this.grid.colModel;
var ci = this.view.getCellIndex(t);
if (adjust == -1) {
if (ci + adjust < 0) {
return;
}
while (this.cm.isHidden(ci + adjust)) {
--adjust;
if (ci + adjust < 0) {
return;
}
}
}
this.cellIndex = ci + adjust;
if (this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)) {
this.isHit = true;
e.preventDefault();
e.stopPropagation();
}
}
}
}
Ext.grid.GridView.prototype.fixColumnWidth = function (colIndex) {
var maxW = Ext.fly(this.getHeaderCell(colIndex)).getTextWidth();
for (var i = 0; i < this.grid.store.getCount(); i++) {
maxW = Math.max(maxW, Ext.fly(this.getCell(i, colIndex)).getTextWidth());
}
this.cm.setColumnWidth(colIndex, maxW + 10);
}
Ext.grid.GridView.SplitDragZone.prototype.handleDblClick = function (e, el, o) {
if (this.isHit === true) {
this.view.fixColumnWidth(this.cellIndex);
}
}
//向列菜单中增加一个菜单项
Ext.grid.GridPanel.prototype.afterRender = Ext.grid.GridPanel.prototype.afterRender.createSequence(function () {
if(this.view.hmenu){
this.view.hmenu.add([
{ text: '合适列宽', id: this.getId() + '-auto-colwidth' }
]);
this.view.hmenu.on('show', function () {
var index = this.view.hdCtxIndex;
if (this.colModel.isResizable(index)) {
this.view.hmenu.get(this.getId() + '-auto-colwidth').show();
} else {
this.view.hmenu.get(this.getId() + '-auto-colwidth').hide();
}
this.view.hmenu.doLayout();
}, this);
this.view.hmenu.get(this.getId() + '-auto-colwidth').on('click', function () {
this.view.fixColumnWidth(this.view.hdCtxIndex);
}, this);
}
})
//}
之前项目有要用到向导创建发票的功能,于是自己写了个向导创建工具。项目是基于ext做的前台。
主要由两个类来维护整个向导的流程。
WizardWrapper,该类用于包装一个panel,并且会根据时机呈现在向导的过程中。
Wizard,该了管理所有的WizardWrapper,形成了一个信息交换的场所。更多的信息,请参见代码,我有在后面附两个类的代码,并且有一个实例。
下面附下图,可能更直观点
借鉴以前的使用的向导的经验,貌似都有个启动界面和结束界面。不过放心,这个是可以配置显示否。
见图片上的注释。需要WizardWrapper提供稍微不一样的信息来给予支持。可能窗体的大小或者布局不大合适,这里面的面板的布局非Wizard的职责,窗体的小的话可以配置WizardWrapper的width,height,还有标题可以配置title。
分支之一,返回上一步再选择分支二
分支二,同样你可以返回到上一步选择分支一。另外,下面有4个标准按钮,上一步、下一步、取消、完成。默认显示前3个,如果需要特殊的显示可以在WizardWrapper中配置,建议使用enableButtons配置节(详见代码注释)。
详细模式,继续下一步的结果
返回到简单模式,所得到的结果。结束了。
注意使用的是extjs3.03版本,为了保证有效性,请使用3.03以上的版本。更多内容在代码文件中
email:mozily@qq.com
第一章
F#简介
F#是一门优秀函数式编程语言。这章将简单的介绍F#的核心——F#的编译器,工具,以及他在Visual Studio 2010中的一些相关内容。
在这章中,您将学会数个F#应用程序的例子,与此同时,我会指出,如何使用Visual Studio来做F#的开发。我并不会涉及太多的Visual Studio,因此我鼓励您去用自己的方式去探究IDE中更多关于Visual Studio中的技巧。当然,您也可以通过在线文档来学习:http://msdn.microsoft.com/en-us/vstudio/default.aspx
如果您已经对Visual Studio很熟悉了,您也应该大致浏览本章。创建和调试一个F#工程项目和创建调试一个C#或者是VB.NET项目并没有太大区别。但是,F#在创建一个多文件项目时拥有一个独一无二的特点。并且,F#支持一个名为交互式输入的新玩意(F# Interactive),它将大大改善您的工作效率。千万不要忽视他。
开始了解F#
不管是什么编程书籍,通常都是先介绍如何书写一个hello world程序,我也不打算脱离这个传统。打开您的记事本或者是其他您喜欢的文本编辑器并创建一个名为HelloWorld.fs的新文件,写入以下内容:
// HelloWorld.fs
printfn “Hello, World”
恭喜您!您刚刚已经完成了您第一个F#应用程序。您可以通过F#的编译器fsc.exe来编译这个应用程序。他位于Program Files\Microsoft F#\v4.0 folder下。(当然,您也可以使用Mono,任意一个您安装F#的地方。)接下来的内容,将给您展示,如何通过在命令提示符下使用F#的编译器来编译并运行您的应用程序:
C:\Program Files\Microsoft F#\v4.0>fscHelloWorld.fs
Microsoft F# Compiler, (c) MicrosoftCorporation, All Rights Reserved
F# Version 1.9.8.0, compiling for .NETFramework Version v4.0.21017
C:\Program Files\MicrosoftF#\v4.0>HelloWorld.exe
Hello, World!
Visual Studio 2010
工具是任何一门编程语言的生命之源,F#当然也不在此之外。当您能够成功的在您最喜欢的文本编辑器上写下F# 代码但是却只能通过命令提示符来编译他时,您肯定会期待更加有效率的工具的出现。同C# 和 VB.NET 一样,虽然F# 是 Visual Studio中的新成员,但F#在Visual Studio 中拥有所有你期待拥有的功能,包括调试器,项目模板等等。
打开您的Visual Studio IDE并且选择菜单栏中的File->New Project来打开New Project对话框,在这里,您可以创建您的第一个F#工程,如图1-1所示。在左边的窗口中选择 Visual F#,然后在右边的窗口中选择F#应用程序,再点击OK。
图1-1。选择F# Application来建立您第一个F#工程。
当您在New Project对话框中点击了OK按钮以后,您将会看到有一个空的编辑模板来给您书写您那优雅的F#代码。
在开始这一切之前,让我们复习一下我们的Hello , World程序。在编辑器中写下如下的代码:
printfn “Hello, World”
现在,按下Ctrl+F5来运行您的程序。当您的程序开始运行时,一个控制台窗口将出现在您面前,并且展示出如图1-2的样貌。
图1-2。F#的Hello, World
您的第二个F#程序
看起来,程序的运行并不需要一个明确显示的Main方法。您可以在第二章中了解到为什么F#允许这样做,但是现在,让我们先完成一个更加有实际意义的Hello World,他会带大家体会F的#典型语法特征。
示例1-1中的代码将演示一个接受2个控制台参数并将他们打印到控制台下输出的程序。特别的,他将打印当前的时间。
Example 1-1.Mega Hello World
(*
MegaHello World
Take TwoCommand line parameter and then print
themalong with the current time to the console.
*)
openSystem
[<EntryPoint>]
let main(args : string[]) =
if args.Length <> 2 then
failwith "Erro: Expected arguments<greeting> and <thing>"
let greeting, thing = args.[0], args.[1]
let timeOfDay = DateTime.Now.ToString("hh:mmtt")
printfn "%s,%s at %s" greetingthing timeOfDay
// Program exit code
0
现在,您有了真正意义上的F#代码了,希望您能对接下来的都感兴趣。
让我们一行接一行的分析这个程序到底是如何工作的。
变量
Example1-1 介绍了3个变量 greeting, thing 和 timeOfDay :
let greeting, thing = args.[0], args.[1]
let timeOfDay = DateTime.Now.ToString(“hh:mmtt”)
其中的核心部分是关键字let,他让一个变量名捆绑了一个变量。需要指出的是,和绝大部分的语言不同,在F#中,一个变量在通常情况下是不可变的(immutable),这意味着,他们一旦初始化以后就能再改变。我们将在第三章讲述为什么变量不能被改变,但是现在我们仅需知道他足以进行函数式编程。
F#是很敏感的,因此任何2个变量的名字哪怕仅有一点点不同,也会被认为是不同的:
let number = 1
let Number = 2
let NUMBER = 3
一个合法的变量名必须是以字母或者下划线开头的,任意的字母,数字,下划线,或者是单引号的组合。
您可以把一个变量名放置在2个单引号之间,这样可以让变量名包含除了制表符(tab)和换行符以外的所有字符。这样就允许你在其他场合引用变量或者是函数。.NET下其他语言的关键字也许会和F#的关键字产生冲突:
let ``this.Isn't %A% good valueName$!@#`` = 5
空白符的那些事
很多其他的语言,像C#,使用分号或者是大括号来指出一个居于或者是一个代码块的结束。但是,程序员们通常会使用特别的缩进来维护代码的可读性,因此,这些额外的符号在程序中往往只会变成是让语法变得更加丑陋。
在F#中,空白符中的空格和回车是有特殊意义的。F#的编译器允许你使用空白符来区分不同的代码块。例如,任何在if语句里面内缩进的代码都被当作是if的内部代码块。因为tab字符能够代表未知数量的字符,因此他们在F#中被禁止使用。
您可以在Visual Studio编辑器菜单栏的Tools->Options->TextEditor->F#来修改配置来设置tab自动缩进的的等效空白字符数。
下面,让我们复习Example 1-1 ,从中发现main方法的代码块都缩进了4个空白字符,而且if的代码块还额外的缩进了4个空白字符。
let main (args : string[]) =
ifargs.Length <> 2 then
failwith"Erro: Expected arguments <greeting> and <thing>"
letgreeting, thing = args.[0], args.[1]
lettimeOfDay = DateTime.Now.ToString("hh:mm tt")
printfn "%s,%s at %s"greeting thing timeOfDay
// Program exit code
0
如果if代码块中的failwith,没有缩进4个字符,也就是说,他和if关键字并排,那么F#的编译器会给出一个警告。这是因为编译器不能确定failwith是属于if代码块中的内容。
[<EntryPoint>]
let main (args : string[]) =
if args.Length <> 2 then
failwith “Error: Expected arguments<greeting> and <thing>”
Waring FS0058: possible incorrectindentation: this token is offside of
context started at position (25:5). Tryindenting this token further or
using standard formatting conventions
一般来说,任何一个方法或者是语句的内部代码块都必须相对上一行的第一个关键字进行缩进。因此在Example 1-1中,所有属于main方法的内部代码偶相对于main方法的的let有缩进,并且在if的内部代码块中,在if后面的每一行都相对于if有缩进。当您有了更多基于F#的编程或者是阅读F#代码的经验时,你会发现,没有逗号和括号是那么的美好,他使得我们更加轻松的去书写和阅读。
.NETInterop
Example1-1 同样演示了F#是如何访问已经存在的.NET类库的:
open System
// …
let timeOfDay = DateTime.Now.ToString(“hh:mmtt”)
.NET平台包含了很多类库,这些类库囊括了从图形到数据库到网络服务的几乎所有东西。F#通过调用相关类库来使用.NET的所有类库。在Example 1-1中,DateTime.Now是属于System命名空间中部分,他从属于mscorlib.dll这个二进制文件。同样,任何用F#编写的代码同样可以被.NET平台上的其他语言所使用。
如果您需要了解更多关于.NET类库的资料,您可以通过浏览本书的附录来做一个快速的导读判断哪些是可行的。
注释
和其他语言一样,F#允许您注释您的代码。使用双反斜杠来注释一行代码,任何在他们之后的该行代码将被编译器所忽略:
// Program exit code
如果您需要一次注释多行代码,您可以使用多行注释,编译器将忽略在(*和*)之间的所有东西。
(*
Mega Hello World:
Take tow command line parameters and thenprint
Them along with the current time to console.
*)
对于在Visual Studio上编写的F#代码,还有第三种注释风格:XML文档注释。如果一段注释把3个反斜杠///放置在一个标识符前面,在您调用他的时候(hover over it),Visual Studio会打印这些注释内容。
F#的交互模式
到目前为止,您已经写过并执行过一些F#代码了,并且在本书接下来内容中(and the rest of the book),您将会接触到新的工程,并且尝试那些例子。(While you could leave a wake ofnew projects while working through this book and trying out the samples),Visual Studio提供了一个叫做F#交互模式的工具,我们也把他称作是FSI。通过使用FSI窗口,您不仅可以更加容易的尝试书中的示例代码,而且可以帮助您编码应用程序。
Figure1-3. XML文档注释
F#交互模式是一种动态语言的工具,是一种典型的输入即输出的典范。他可以接纳F#代码,编译并执行代码,然后打印出结果。这可以让您轻松便捷的在建立工程或者是书写完整的F#应用程序以外体验F#代码,观看一些短的代码片段的执行结果。(see the result of afive-line snippet)
在C#和VB.NET中,您必须编译并执行您的代码才能看到结果。毫无疑问,这是一种很繁琐方式来体验一些简短的代码。甚至您使用Visual Studio的即时窗口进行调试的时候,您也被限制在只能使用表达式,而不能真正的编写程序,比如,定义一些新的函数或者是类。
在Visual Studio中,通常使用组合键Ctrl+Alt+F来打开FSI窗口。当FSI窗口被激活时,他能接纳以;;或者是换行结尾的F#代码。这些代码输入以后将如同Figure1-4一样被编译并且执行。
在一段段代码被输入进FSI后,每当有一个变量名输入时,您当会看到 val<name>这样的字样。如果是一个没有名字的表达式被输入的时,他们都有一个共同的默认名字——it。在这个标识符的后面,还会有一个冒号和这段代码的值出现。例如,在Figure 1-4中,变量x被输入,他的类型是int,他的值是42。
如果您没有在Visual Studio中使用F#,您可以在同一个目录下找到找到一个名为fsi.exe控制台版本的F#交互模式作为替代品。
试试在FSI中运行简短的代码。记住,任何代码段都必须以;;结束。
> 2 + 2;;
> val it : int = 4
> // Introduce two values
let x = 1
let y = 2.3
val x : int
val y : float
> float x + y;;
val it : float = 3.3
> let cube x = x * x *x;;
val cube : int -> int
> cube 4;;
val it : int = 64
FSI同样可以大大简化您的测试和调试程序的过程。因为您可以在在Visual Studio中选中F#代码以后按下Alt+Enter来把您当前的代码输入到FSI中去。
Figure1-4. The F# Interactive window
在代码编辑器中选中了Example 1-1 所有代码并按下Alt+Enter以后,您会在FSI窗口中发现如下情况:
>
val main : string array -> int
这可以让您在Visual Studio编辑器中书写有语法高亮的代码的同时拥有在FSI这个交互模式下测试您的代码的功能。您可以通过如下的调用测试您输入到FSI中去的main方法:
> main [|”Hello”; “World”|];;
Hello World at 10:52 AM
val it : int = 0
本书的绝大部分例子都讲使用FSI的模式。我鼓励您继续使用FSI,并且在FSI中体验F#的语法规则。
管理F#源文件
当您刚刚开始F#编程时,您编写的绝大部分程序往往只存在与FSI或者是单一的源文件中。随着您编程经验的增长,您的工程将飞速膨胀,很快就能扩展到多文件甚至是多工程。
F#有不同寻常的特点来管理多文件工程。在F#中,哪些写有代码文件将被编译是很有讲究的。(the order in which codefiles are compiled is significant)
您只能调用在文件中已经定义过或者是在指定文件中已经编译过的函数或者是类。如果您重新组织您的源文件顺序,您的工程将不能被编译。
这样做的理由是F#中的类型推倒,这将在接下来的一章进行讲解。
F#源文件将按照他们在Visual Studio解决方案管理器中从上到下的排列顺序来编译。当您在工程中添加一个文件的时候,这个文件会自动的加到列表的最下面,如果您想重新排列源文件,您和Figure 1-5所示的那样可以右击一个文件并且选择Move Up或者是Move Down。对应的整理工程文件顺序的键盘快捷键是Alt+Up和Alt+Down。
现在,您已经对如何编译F#应用程序有了一个初步的认识,在本书接下来的内容中,将更加深入的探讨F#的语法和语义。
Figure 1-5. Reordering files within an F#project
第二章
基本概念
在第一章中,你已经可以编写你的第一个F#程序。在这里,我会慢慢让你加深对你所学的东西体会,但是,至今为止,绝大多数的代码依旧是很神秘的。在这章,我将会讲解读懂这些代码应有的基础知识,但是,更重要的是,我将会在这章里面提出几个或更多典型的例子来让你在接触更加复杂的代码之前掌握F#的根基。
本章的第一个部分将涉及到F#中的基础类型,比如int、string 等在F#程序中的基本内建类型。然后,我会简单的介绍函数,因此你将会知道如何操纵和改变数据。
第三部分将详细介绍一些内建类型(functional types),例如list、option、unit。当你掌握如何使用他们以后,你能够在接下来的章节中,更好的理解F#的面向对象和函数式编程风格。
在本章的最后,你将能够独立的编写简单的处理数据的F#程序。当然,在以后的章节中,你能学习到更加强大和富有表现力的方式来编写你的代码,但是在此之前,让我们脚踏实地得学会这些基础。
基础类型
类型是一种抽象的强制类型安全的实例。类型代表了一系列的可以真实存在的实例。有些只是简单的代表了一个整数,但是其他的一些可能会更加抽象得多,比如一个函数。F#是静态类型,这意味着在编译期间,就完成了所有的类型检测。例如,你会在给一个接纳一个整数作为参数的函数传入一个字符串的时候得到一个编译错误。
同C#和VB.NET一样,F#支持.NET中的所有基本类型以及.NET中所允许的类型转换(这点和绝大部分的语言是保持一致的)。他们是F#的内在组成部分,并且和用户自定义的类型相互区别。
我们一般用let来绑定一个变量。
当然,你也可以让let绑定许多其他的东西,但是我们讲在第三章才讲述那些东西。现在,我们只需知道,你可以用let来绑定(通过关键字)声明一个新的标识符。例如,接下来的代码中,我们在FSI中定义了一个叫做x的变量:
>let x = 1;;
valx : int = 1
基本数值类型
基本数值类型有2种:整数和浮点数。整型的表示范围随着他长度而改变,因此,一些类型只能用较少的内存表示一个较小范围内的输。整数通过他们是否能够表示一个负数又分成了2类:有符号数和无符号数。
浮点数的表示范围也和长度有关,我们通过他们的字母后缀来确定let绑定一个整数还是浮点数。Table 2-1 给出了所有的整型和浮点型以及他们的字母后缀。
>let answerToEverything = 32UL;;
valeverything: uint64 = 42UL
>let pi = 3.1415926M;;
valpi : decimal = 3.1415926M
>let avogadro = 6.022e-23;;
valavogadro : float = 6.022e-23
Table 2-1. Numerical primitives in F#
Type Suffix .NET type Range
byte uy System.Byte 0 to 255
sbyte y System.SByte -128 to 127
int16 s System.Int16 -32768 to 32767
uint16 us System.UInt16 0 to 65535
int, int32 System.Int32 -231 to231-1
uint, uint32 u System.UInt32 0 to 232-1
int64 L System.Int64 -263 to 263-1
uint64 UL System.UInt64 0 to 264-1
float System.Double A double-precision floating point based onthe IEEE64 standard. Rep-
resents values with approximately 15 significant digits.
float32 f System.Float A single-precision floating pointbased on the IEEE32 standard. Rep-
resents values with approximately 7 significant digits.
decima M System.Decimal A fixed-precision floating-point typewith precisely 28 digits of
precision.
F#同样允许你通过加上0x, 0O, 0b后缀来设置16进制,8进制和二进制的值:
>let hex = 0xFCAF;;
valhex : int = 64687
>let oct = 0o7771L;;
valoct : int64 = 4089L
>let bin = 0b00101010y;;
valbin : sbyte = 42y
> (hex, oct, bin);;
val it : int * int64 * sbyte = (64687, 4089, 42)
如果对IEEE32或者是IEEE64标准很熟悉的话,你同样可以用16进制,8进制或者是2进制的方式来指定浮点数的值。F#能把二进制的值转化为他表示的对应的浮点数的值。我们通过使用不同的后缀来区分不同的精度的浮点数类型,LF后缀用来指定float类型,而lf用来指定float32类型。
> 0x401E000000000000LF;;
val it : float = 7.5
> 0x00000000lf;;
val it : float32 = 0.0f
运算
你可以标准的基本运算符。Table2-3列出了所支持的所有的运算符。和所有基于CLR的语言一样,整数做除法运算的时候结果会舍弃余数。
Table 2-2. Arithmetic operators
Operator Description Example Result
+ Addition 1 + 2 3
− Subtraction 1− 2 −1
* Multiplication 2 *3 6
/ Division 8L / 3L 2L
** Powera 2.0 ** 8.0 256.0
% Modulus 7 % 3 1
Power,**运算符,只能在float或者是float32类型下工作。要得到一个整数的乘方,你必须选择把他转换沉一个浮点数或者是有pown函数
默认情况下,运算符并不会检测运算结果是否越界,因此当你超过一个整数的表示范围的时候,他会溢出成一个负数。(同理,减法会获得一个正数当一个无法被存储在整型中的数存放在整型中的时候。)
> 32767s + 1s;;
val it : int16 = −32768s
> −32768s + −1s;;
val it : int16 = 32767s
当一个整数涉及到溢出的时候,你应该考虑一个表示范围更大的类型或者是使用带有检测功能的运算符,这部分内容,参考第七章
F#毫无疑问提供了所有你期待有的数学函数,所有的这些,都在Table2-3中列出了。
Table2-3.Commonmath functions
Routine Description Example Result
abs Absolute value of a number abs −1.0 1.0
ceil Roundup to the nearest integer ceil9.1 10
exp Raisea value to a power of e exp1.0 2.718
floor Rounddown to the nearest integer floor9.9 9.0
sign Sign of the value sign −5 −1
log Natural logarithm log 2.71828 1.0
log10 Logarithm in base 10 log10 1000.0 3.0
sqrt Square root sqrt 4.0 2.0
cos Cosine cos 0.0 1.0
sin Sine sin 0.0 0.0
tan Tangent tan1.0 1.557
pown Compute the power of an integer pown 2L 10
类型转换
F#的一个原则就是不允许任何的隐式转换。这就意味着,编译器不会自动把基本数据类型转换成你所想要的,例如,把一个int16转换为int64。这将避免很多难以察觉的bug。为了打到类型转换的效果,你必须使用Table2-4中的类型转换函数。所有的类型转换函数都能接纳任意基本类型,包括字符串和字符。
Table2-4.Numericprimitive conversion routines
Routine Description Example Result
sbyte Converts data to an sybte sbyte −5 −5y
byte Converts data to a byte byte "42" 42uy
int16 Converts data to an int16 int16 'a' 97s
uint16 Converts data to auint16 uint16 5 5us
int32, int Convertsdata to an int int 2.5 2
uint32, uint Converts data to a uint uint32 0xFF 255
int64 Converts data to an int64 int64−8 −8L
uint64 Converts data to a uint64 uint64 "0xFF" 255UL
float Converts data to a float float3.1415M 3.1415
float32 Converts data to a float32 float32 8y 8.0f
decimal Converts data to a decimal decimal 1.23 1.23M
当上述的类型转换接受字符串为参数的时候,他们使用底层的System.Convert系列函数分析字符串的内容,这意味着,非法的输入将导致他们抛出System.FormatException异常。
大数类型
F#提供了BigInt类型来代表你要处理大于264的大整数。其实,BigInt只是System.Numer.ics.BigInteger类型的一个别名而已,他同C#和VB.NET中支持大数的语法是一模一样的。
BigInt使用I作为字母后缀标志。Example2-1定义了一些使用BigInt的数据。
>open System.Numerics
//Data storage units
letmegabyte = 1024I * 1024I
letgigabyte = megabyte * 1024I
letterabyte = gigabyte * 1024I
letpetabyte = terabyte * 1024I
letexabyte = petabyte * 1024I
letzettabyte = exabyte * 1024I;;
valmegabyte : BigInteger = 1048576
valgigabyte : BigInteger = 1073741824
valterabyte : BigInteger = 1099511627776
valpetabyte : BigInteger = 1125899906842624
valexabyte : BigInteger = 1152921504606846976
valzettabyte : BigInteger = 1180591620717411303424
尽管BigInt在性能上进行了很大程度上的优化,但是他任然比使用普通的基本整型的速度慢得多。
比特运算符
基本整数类型支持比特运算符以用来操纵二进制级别的数据运算。他们经常在读写二进制数据的时候用到。参考Table2-5
Table2-5. Bitwise operators
Operator Description Example Result
&&& Bitwise ‘And’ 0b1111 &&& 0b0011 0b0011
||| Bitwise ‘Or’ 0xFF00 |||0x00FF 0xFFFF
^^^ Bitwise ‘Exclusive Or’ 0b0011 ^^^ 0b0101 0b0110
<<< Left Shift 0b0001 <<< 3 0b1000
>>> Right Shift 0b1000 >>> 3 0b0001s
字符型
由于.NET平台是基于Unicode的,因此字符都是使用2个字节的UTF-16字符表示法。你可以通过给任意一个Unicode字符加上单引号来定义一个字符类型变量。当然了,字符型变量也可以用16进制字符的Unicode码来指定。
接下来的这段代码定义了一个字符列表,然后输出一个字符对应的16进制的值:
> let vowels = ['a'; 'e'; 'i'; 'o';'u'];;
val vowels : char list = ['a'; 'e'; 'i';'o'; 'u']
> printfn "Hex u0061 = '%c'"'\u0061';;
Hex u0061 = 'a'
val it : unit = ()
为了表示那些特殊的转义字符,你必须如同Table2-6那样使用转义字符。一个转义字符是一个反斜杠加上一个要表示的特定的字符。
Table2-6.Character escape sequences
Character Meaning
\’ Single quote 单引号
\” Double quote 双引号
\\ Backslash 反斜杠
\b Backspace 退格符
\n Newline 换行
\r Carriage return 回车
\t Horizontal tab tab
如果你想要知道一个.NET Unicode字符对应的数值,你可以通过我们在前面的Table2-3中所列出的转换方法来获得。当然,您也可以选择通过在一个字符后面加上一个后缀B来获得:
>// Convert value of 'C' to an integer
int'C';;
val it : int = 67
> // Convert value of 'C' to a byte
'C'B;;
val it : byte = 67uy
字符串
字符串是通过一串在双引号的的字符来定义的,可以是一行,也可以是多行。可以通过向字符串标识符后面加上.[]并向[]里传入一个非零的有符号整数来获得指定的字符:
> let password ="abracadabra";;
valpassword : string = "abracadabra"
> let multiline = "This string
takes up
multiple lines";;
val multiline : string = "Thisstring
takes up
multiple lines";;
> multiline.[0];;
val it : char = 'T'
> multiline.[1];;
val it : char = 'h'
> multiline.[2];;
val it : char = 'i'
> multiline.[3];;
valit : char = 's'
你可以通过在每行的最后一个字符后面添加反斜杠\ 来将一个字符串分割到好几行。当一行的最后一个字符后面是反斜杠的时候,字符串会自动忽略这一行后所有的空白符并且自动添加下一行的字符到当前字符串中去:
let longString = "abc-\
def-\
ghi";;
> longString;;
val it : string = "abc-def-ghi"
你可以通过在一个字符串中自由的使用类似与\t或者是\\这样的转义字符,但是,转义字符会在定义一些路径和注册表键值的时候带来很大的麻烦。你可以选择在定义一个字符串的时候加上@这个符号,这表示在这个字符串里面,不对任何字符进行转义:
> let normalString ="Normal.\n.\n.\t.\t.String";;
val normalString : string = "Normal.
.
. . .String
> let verbatimString =@"Verbatim.\n.\n.\t.\t.String";;
val verbatimString : string ="Verbatim.\n.\n.\t.\t.String"
和在一个字符后面加上后缀B一样,在一个字符串后面加上后缀B会通过一个字符数组返回这个字符串中所有字符。(数组将在第四张被介绍。)
> let hello = "Hello"B;;
val hello : byte array = [|72uy; 101uy;108uy; 108uy; 111uy|]
布尔值
F#提供了bool类型(System.Boolean)来处理只有true和false的值的情况。标准的布尔值操作符在Table2-7中被列出
Table2-7. Boolean operators
Operator Description Example Result
&& Boolean ‘And’ true && false false
|| Boolean ‘Or’ true || false true
not Boolean ‘Not’ not false true
Example2-2提供了一张打印布尔值函数运算结果的真值表。他通过定义一个接纳一个函数名为f作为参数的printTruthTable函数来实现这一切。这个函数在真值表中的每个表格中都被调用一次,并且打印他自己的结果。接下来,我们会看到运算符&&和||被当作参数传到printTruthTable函数中去。
Example2-2. Printing truth tables
>// Print the truth table for the given function
letprintTruthTable f =
printfn " |true | false |"
printfn " +-------+-------+"
printfn " true | %5b | %5b |" (f truetrue)(f true false)
printfn " false | %5b | %5b |" (f false true) (f false false)
printfn " +-------+-------+"
printfn ""
();;
valprintTruthTable : (bool -> bool -> bool) -> unit
>printTruthTable (&&);;
| true | false |
+-------+-------+
true | true| false |
false | false | false |
+-------+-------+
valit : unit = ()
>printTruthTable (||);;
| true | false |
+-------+-------+
true | true | true |
false | true | false |
+-------+-------+
valit : unit = ()
F#在求布尔值表达式的时候采用了短路法则,这意味着整个表达式的值可能在第一个表达式被求出来以后就被确定了,第二个表达式不会被计算。例如:
true || f()
结果将会是true,不会执行函数f。下面一个例子:
false && g()
结果将会是false,不会执行函数g。
比较和相等
你可以通过使用Table2-8中列出的大于,小于,等于操作符来比较2个数值的大小。所有的比较运算符的执行结果都是布尔值;比较函数返回的是-1,0或者是1的依据是第一个参数是否小于,等于或者是大于第二个参数。
Table2-8. Comparison operators
Operator Description Example Result
< Less than 1 < 2 true
<= Less than or equal 4.0 <= 4.0 true
> Greater than 1.4e3 > 1.0e2 true
>= Greater than or equal 0I >=2I false
= Equal to "abc" = "abc" true
<> Not equal to 'a' <> 'b' true
compare Compare two values compare31 31 0
在.NET中,相等是一个很难纠缠的主题。既有值相等,也有引用对象相等。对于数值类型,比较意味着变量的值是一样的,例如1=1,。但是对于引用类型,是否相等是通过重载Sytem.Object的Equals方法来判定的。更多的关于相等的细节,将会在119页的”相等对象”中介绍。
函数
至今为止,我们已经掌握在所有的F#中的基本类型,接下来,让我们定义函数来使用他们。
你可以使用同定义变量一样的方式定义函数,在函数名以后的所有东西都被认为是这个函数的参数。下面定义了一个接纳一个整型参数x的square函数,他将返回参数x的平方:
> let square x = x * x;;
val square : int -> int
> square 4;;
val it : int = 16
和C#不同的是,F#中没有return这个关键字。因此当你定义一个函数的时候,最后一个表达式就等于是函数执行结果的返回。
让我们来试着写一个让函数的输入加1的函数:
> let addOne x = x + 1;;
valaddOne : int -> int
在FSI上的输出显示了这个函数的类型是int -> int。我们这样读:一个函数传入一个整数并且返回一个整数。这个规则在一个函数拥有多个参数的时候会显得稍显复杂:
> let add x y = x + x;;
val add : int –> int -> int
一个很有挑战性的读法, 类型 int -> int ->int 被读作:一个函数传入一个整数,该函数返回一个传入一个整数并且返回一个整数的函数。不要所谓的一个函数返回一个函数所迷惑和害怕。你需要知道的仅仅是如何去调用一个函数,只需要简单的把参数用空格隔开:
> add 1 2;;
valint : 3
类型引用
因为F#是静态类型,因此当你在给add方法传递浮点数作为参数的时候会得到一个编译错误:
> add 1.0 2.0;;
add 1.0 2.0;;
----^^^^
stdin(3,5): error FS0001: This expression has type
float
but is here used withtype
int.
你可能会对此感到很迷惑,为什么编译器就认为函数只能接纳整数作为参数呢?但是运算符+却同样可以工作在浮点数下。
这是因为类型引用,和C#不同,F#编译器不需要你明确的告诉编译器一个函数的所有参数类型。编译器会指定他们为通常的选择。
小心,不要混淆了类型引用和动态类型。尽管F#允许你在编写代码的时候省略类型,但是这不意味着编译器在编译时不会强制检查类型。
因为运算符+为很多的类型服务,例如:byte,int,decimal,编译器会在没有附加条件的情况下简单的认为类型就是int。








