如果你也会C#,那不妨了解下F#(6):面向对象编程之“类”

前言

面向对象的思想已经非常成熟,而使用C#的程序员对面向对象也是非常熟悉,所以我就不对面向对象进行介绍了,在这篇文章中将只会介绍面向对象在F#中的使用。

F#是支持面向对象的函数式编程语言,所以你用C#能做的,用F#也可以做,而且通常代码还会更为简洁。我们先看下面这个用C#定义的类,然后用F#来定义。

//定义一个二维点[DebuggerDisplay("({X}, {Y})")]public class Point2D{    // 用于统计已实例化的数量private static int count = 0;    // 原点public readonly Point2D OriginalPoint = new Point2D(0, 0);    // 完整属性Xprivate double x;    public double X{        get { return this.x; }        set { this.x = value; }}    // 自动属性Ypublic double Y { get; set; }    // 到原点的距离public double LengthToOriginal{        get { return this.GetDistance(this.OriginalPoint); }}    // 默认构造函数public Point2D() : this(0,0) {}    // 包含两个参数的构造函数public Point2D(double x, double y)    {        this.X = x;        this.Y = y;count++; // 创建新点时,数量递增1}    // 将(x,y)数值转为Point2D对象的静态方法public static Point2D FromXY(double x, double y)    {        return new Point2D(x, y);}    // 计算到指定点的距离public virtual double GetDistance(Point2D point)    {        var xDif = this.X - point.X;        var yDif = this.Y - point.Y;        var distance = Math.Sqrt(xDif * xDif + yDif * yDif);        return distance;}    //重写ToString方法public override string ToString()    {        return String.Format("Point2D({0}, {1})", this.X, this.Y);}
}


定义类

在F#中定义类不需要class关键字,除非定义一个的类。

type Point2D() = class          //定义一个空类endtype internal Point2D() = class //定义一个空的internal类endtype Point2D() =                //定义一个只包含字段x的类let mutable x = 0.0

不像C#,在F#中,类的访问修饰符默认是public的,所以internal就需要手动指定。并且,在F#,不支持protected访问修饰符,在F#中应该使用接口,对象表达式和高阶函数来实现类似功能。


字段(Field)

在上一例代码中我们已经定义了一个字段x,但类中用let定义的字段(包括后面介绍的函数)均为private的。若需要定义其他访问修饰符的字段,需要使用val定义。

type Point2D() =    [<DefaultValue>] val mutable public x : float

其中DefaultValue特性用于将支持零值初始化的类型(具有零值的基元类型、可为null的类等)字段初始化为零值。


属性(Property)和索引器

使用member关键字定义属性,注意F#中的属性需要有一个对象标识符(类似C#中的this)作为前缀。

type Point2D() =    let mutable x = 0.0// 定义属性X,其中set为private的。member this.X with get()  = x                  and private set(v) = x <- v    member val Y = 0.0 with get, set    //自动属性

在F#中,this也可以使用其他名称来代替。除了this(C++和C#的习惯),有的人还使用self(Python等的习惯)、me(VB.NET的习惯)等。若不需要在属性或方法内部使用到,一般还习惯使用__(两个下划线)来作为对象标识符。

而像C#set方法中的value,F#中也需要自己指定,如例子中使用v。当然getset都可以省略使之成为只写或只读属性。

遗憾的是自动属性中不支持分别定义访问修饰符(如C#中的{get; private set}),只能使用完整属性来定义。


方法(Method)

方法同样使用member定义,而静态方法只需在member前添加static关键字。

type Point2D() =……  //因为篇幅省略属性X,Y的代码// 定义一个函数来获取指定点到当前点的距离member this.GetDistance (point:Point2D) =        let xDif = this.X - point.X        let yDif = this.Y - point.Y        let distance = sqrt (xDif**2. + yDif**2.)        distance    static member FromXY (x:double, y:double) =        let point = Point2D()point.X <- xpoint.Y <- ypoint

重载方法

F#类中的方法重载与C#一样,只需重新定义一个同名成员函数,且签名不同即可。下面实现一个接受int类型的FromXY方法:

static member FromXY (x:int, y:int) =Point2D.FromXY(float x,float y)     

返回目录

构造函数与实例化

F#中有两种构造函数,一为主构造函数(也称隐式构造函数),上面例子中在类型定义后面()即表示一个无参构造函数。

另一种构造函数(显示定义)是可选的,在类里定义一个new函数即可。但new函数必须满足以下条件:

  • 返回类型必须为该类

  • 不使用member和对象标识符。

  • 使用一个元组或unit作为参数。

我们改造上面的代码:

type Point2D (xValue:double, yValue:double) =    let mutable x = xValue    member val Y = yValue with get, set    new() = Point2D(0.0,0.0)

其中,调用无参构造函数时,则使用主构造函数实例化,且两个参数均为0.0

若要为构造函数添加访问修饰符,写在其之前即可。

type internal Point2D internal (xValue:double, yValue:double) =    private new() = Point2D(0.0,0.0)

需要注意的是,在类型定义之后若不提供主构造函数,F#并不像C#那样有一个无参的构造函数。

下面的代码能通过编译,但你无法进行实例化:

type Point2D = class end

在类中的let绑定会在调用主构造函数时运行,但如果需要在主构造函数中执行其他操作,需要使用do绑定。

type Point2D(xValue:double, yValue:double) =    let mutable x = xValue    static let mutable count = 0docount <- count + 1

注意let代码和do代码必须在member之前。而do语句中必须返回unit,可使用ignore函数丢弃返回值。

非静态的do语句会在实例化时执行,而静态的do语句会在第一次使用该类型时执行。而在没有主构造函数的类中,无法使用letdo语句

如果需要在do语句中访问其他方法,则需要类级别的对象标识符,在类型定义后使用as关键字指定;若想在非主构造函数中执行额外的代码,使用then关键字:

type Point2D (xValue:double, yValue:double) as self =    do  //省略其他代码self.Print "在主构造函数中。"new() as this= //主构造函数中使用self,此处用this。定义为任何名称都可以Point2D(0.0,0.0)then this.Print "在无参构造函数中。"member this.Print str = printfn "%s" str

但这样有一个问题:在执行do代码访问Print函数时,需要self已经实例化好,但因为Y自动属性,编译时会在do后面插入一个Y@字段(即Y的back-end字段),此时并未初始化。即违反了“先定义后引用”的原则。

所以,若在构造函数中需要访问类中的方法,只能Y也更改为完整属性,并且xy字段的let绑定必须在do之前。

实例化

在上面代码中的new()构造函数中,实例化了一个Point2D(0.0,0.0)对象。在F#中,实例化时可以不使用new关键字,但有特例,在后面会介绍。

在C#中,我们可以使用对象初始化器(Object Initializers)在初始化时对属性进行赋值,在F#中,也有类似功能:

var pt = new Point2D() {X = 3.0};   //C#中使用对象初始化器
let pt = Point2D(X=3.0)         //F#中使用对象初始化器,注意此处调用的是无参构造函数

到此,可将上面的FromXY方法进行简化:

static member FromXY (x:double, y:double) =Point2D(x,y)


抽象类(Abstract Class)和密封(Sealed)

要将类定义为抽象类或密封类,只需要在类定义前加上[<AbstractClass>][<Sealed>]特性。

抽象方法和虚方法

在F#中,没有提供virtual关键字来定义虚方法。而使用定义一个抽象方法,并提供默认实现来代替。

type Point2D(xValue:double, yValue:double) as this=……    // 实现开头C#代码中的GetDistance虚方法,此处省略其他代码abstract GetDistance : point:Point2D -> doubledefault this.GetDistance(point) =     
   let xDif = this.X - point.X    
   let yDif = this.Y - point.Y      
   let distance = sqrt (xDif**2. + yDif**2.)distance

关键字abstract用来定义抽象方法,只给出函数签名,并且函数名前不需使用对象标识符;而default默认实现语句中也不需使用member关键字。

与C#一样,在派生类中进行override关键字重写基类中的虚方法:

type Point2D(xValue:double, yValue:double) =……    //重写Object类中的ToString虚方法,此处省略其他代码override this.ToString() = sprintf "Point2D(%f, %f)" this.X this.Y

在C#中,若要重写非虚方法,可使用new关键字。F#中没有此关键字,但仍然可以重写非虚方法,只是编译器会给出警告。类的继承与接口的实现在下一篇中介绍。

索引器(Indexer)及切片(Slice)

索引器

索引器,顾名思义就是可以使用索引来操作对象。在F#中,只需定义一个名为Item的属性或方法即可让该类的对象使用索引器。当然,若Item定义为方法,则索引器为只读的。

我们用下来的代码定义一个可变的字符串类。

open System.Collections.Generic type WordBuilder(startingLetters : string) =    let m_letters = new List<char>(startingLetters)member this.Itemwith get idx   = m_letters.[idx]and  set idx c = m_letters.[idx] <- cmember this.Word = new string (m_letters.ToArray())let wb = WordBuilder("I Love C#")
wb.[7] <- 'F'printfn "%s" wb.Word    //输出为:"I Love F#"

注意在使用索引访问时,需要使用访问(.[])。

切片

切片和索引器类似,不过索引器访问的是一个元素,而切片访问的是数据的集合。实现切片访问需要添加GetSlice方法,切片是只读的。

open System.Collections.Generic type WordBuilder(startingLetters : string) =…… //其他代码省略member this.GetSlice(lower:int option,upper:int option) =letters |> Seq.skip (lower.Value)|> Seq.take (upper.Value - lower.Value + 1)|> Seq.toArray      let wb = WordBuilder("I Love C#")
printfn "%A" wb.[2..5]      //输出为:[|'L'; 'o'; 'v'; 'e'|]

此切片方法并未实现完整,但已可了解实现方法。注意GetSlice的参数必须是'a option泛型类型, 其中option类型将在后面再介绍,可理解为类似于Nullable<int>

返回目录

结语

通过以上的介绍,熟悉C#面向对象的朋友内容应该能了解F#与C#间语法的差别了。至少也可以将C#代码按面向对象的风格翻译成F#代码了,下面是文章开头C#代码的F#版本:

open System.Diagnostics[<DebuggerDisplay("({X}, {Y})")>]type Point2D (xValue:double, yValue:double) as self =    let mutable x = xValue   

 static let mutable count = 0    doself.OriginalPoint2 <- new Point2D()count <- count + 1member __.OriginalPoint with get () = Point2D()    member this.LengthToOriginal with get () = this.GetDistance(this.OriginalPoint)        new() = Point2D(0.0,0.0)    member __.X with        get()  = x                and private set(v) = x <- v    member val Y = yValue with get, set    // 一个虚函数:获取指定点到当前点的距离abstract GetDistance : point:Point2D -> double    default this.GetDistance(point) =        let xDif = this.X - point.X        let yDif = this.Y - point.Y        let distance = sqrt (xDif**2. + yDif**2.)distance        static member FromXY (x:double, y:double) =Point2D(x,y)        override this.ToString() = sprintf "Point2D(%f, %f)" this.X this.Y

需要注意的是C#代码中的OriginalPoint公开字段在此处以只读属性实现。主要是因为:

  • F#中的let绑定的字段只能为private,无法设置为public。

  • val绑定的显示字段(包括自动属性)需要在主构造函数中初始化,而OriginalPoint的类型也为Point2D,在此会形成递归调用而引发StackOverflowException异常。

最后再简单说下类中letval的区别:

  • let始终是private的,且需要有主构造函数才能定义,因为let语句在主构造函数中运行(同do语句)。

  • val默认为public的,并且可添加修饰符使之成为internalprivate的。若有主构造函数,需要为val字段添加DefaultValue特性。valmember关键字结合使用,可声明自动属性。

  • 在类中访问val字段,需要使用对象标识符,而let字段不需要。

注意,此DefaultValue位于Microsoft.FSharp.Core命名空间,不要和C#中常用的位于System.ComponentModelDefaultValue混淆。

相关文章:

  • 如果你也会C#,那不妨了解下F#(1):F# 数据类型

  • 如果你也会C#,那不妨了解下F#(2):数值运算和流程控制语法

  • 如果你也会C#,那不妨了解下F#(3):F#集合类型和其他核心类型

  • 如果你也会C#,那不妨了解下F#(4):了解函数及常用函数

  • 如果你也会C#,那不妨了解下F#(5):模块、与C#互相调用

  • 【送书活动】机器学习项目开发实战

  • 《机器学习项目开发实战》送书活动结果公布

  • F#年度调查结果概述

原文地址:http://www.cnblogs.com/hjklin/p/fs-for-cs-dev-6.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/328487.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新华三

百度 新华三 笔试题目 http://www.663395.net/xxs163090/ https://wenku.baidu.com/view/f61a1161ae1ffc4ffe4733687e21af45b307fe7d.html https://blog.csdn.net/qq_36562999/article/details/81436356 https://blog.csdn.net/kyyee/article/details/78090954?locationNum4&…

java面试题及答案(基础题122道,代码题19道)

转载自 java面试题及答案&#xff08;基础题122道&#xff0c;代码题19道&#xff09; JAVA相关基础知识 1、面向对象的特征有哪些方面 1.抽象&#xff1a;  抽象就是忽略一个主题中与当前目标无关的那些方面&#xff0c;以便更充分地注意与当前目标有关的方面。抽象并不打…

java aqs详解_Java AQS底层原理解析

AQS底层原理AQS(AbstractQueuedSynchronizer)是一个抽象同步队列&#xff0c;JUC(java.util.concurrent)中很多同步锁都是基于AQS实现的。AQS的基本原理就是当一个线程请求共享资源的时候会判断是否能够成功操作这个共享资源&#xff0c;如果可以就会把这个共享资源设置为锁定状…

在.NET Core程序中设置全局异常处理

以前我们想设置全局异常处理只需要这样的代码&#xff1a; 1 AppDomain currentDomain AppDomain.CurrentDomain;2 currentDomain.UnhandledException new UnhandledExceptionEventHandler(MyExceptionHandler); 但是在.NET Core中并没有AppDomain的相关实现&#xff0c;至少…

matlab 画柱状图

data [0.1869, 0.1900, 0.1878, 0.1847]; b bar(data); ch get(b,children); set(gca,XTickLabel,{非加权聚类欧氏距离类比,非加权聚类马氏距离类比,加权聚类欧氏距离类比,加权聚类马氏距离类比}) set(ch,FaceVertexCData,[1 1 1;1 1 1;1 1 1;1 1 1;])

java释放线程资源_Java线程之释放锁,释放资源,释放CPU

多线程中的wait与sleep到底谁释放了锁首先&#xff0c;多线程中会使用到两个延迟的函数&#xff0c;wait和sleep。wait是Object类中的方法&#xff0c;而sleep是Thread类中的方法。sleep是Thread类中的静态方法。无论是在a线程中调用b的sleep方法&#xff0c;还是b线程中调用a的…

经典的Java基础面试题集锦

转载自 经典的Java基础面试题集锦问题&#xff1a;如果main方法被声明为private会怎样&#xff1f; 答案&#xff1a;能正常编译&#xff0c;但运行的时候会提示”main方法不是public的”。 问题&#xff1a;Java里的传引用和传值的区别是什么&#xff1f; 答案&#xff1a;传…

聚类中心选取

https://blog.csdn.net/zhihaoma/article/details/48649489 https://www.cnblogs.com/dudumiaomiao/p/5839905.html

Vue.js先入个门看看

使用vue.js原文介绍&#xff1a;Vue.js是一个构建数据驱动的web界面库。Vue.js的目标是通过尽可能简单的API实现响应式数据绑定和组合的视图组件。vue.js上手非常简单&#xff0c;先看看几个例子&#xff1a; 例一&#xff1a;Helloworld html代码&#xff1a; <div id"…

java path 注解_@PathVariable注解的使用和@Requestparam

一、 PathVariablePathVariable这是一个路径映射格式的书写方式注解&#xff0c;在类映射路径的后加上/{对应方法参数中属性PathVariable("code")中的code}&#xff0c;SuppressWarnings({ "unchecked", "rawtypes" })RequestMapping(value &qu…

近5年133个Java面试题 你会几个?

https://blog.csdn.net/fireblue1990/article/details/73695121 http://www.ablanxue.com/shtml/201608/33470_1.shtml

精选30道Java笔试题解答

转载自 精选30道Java笔试题解答 1. 下面哪些是Thread类的方法&#xff08;&#xff09; A start() B run() C exit() D getPriority() 答案&#xff1a;ABD 解析&#xff1a;看Java API docs吧&#xff1a;http://docs.oracle.com/javase/7/docs/ap…

.NET Core 1.0.1 发布了

今天微软发布了 .NET Core 1.0.1&#xff0c;这是 .NET Core 1.0 的首个微软长期支持更新&#xff0c;同时发布的还有 ASP.NET Core 1.0.1 与 Entity Framework Core 1.0.1。 .NET Core 1.0.1 中的主要更新与 bug 修复&#xff1a; Segfaults on Linux 4.6 – coreclr 6016; co…

java this用法_java中this用法小结

Java关键字this只能用于方法方法体内。当一个对象创建后&#xff0c;Java虚拟机(JVM)就会给这个对象分配一个引用自身的指针&#xff0c;这个指针的名字就是this。因此&#xff0c;this只能在类中的非静态方法中使用&#xff0c;静态方法和静态的代码块中绝对不能出现this,并且…

第1步 新建spring+springmvc+mybatis项目 每步都有 讲解 巨详细

Idea2017部署项目到tomcat时没有artifacts解决办法 2017年12月01日 23:16:22 剑胜 阅读数&#xff1a;10342 在Idea2017中想部署maven构建的项目到tomcat容器中时&#xff0c;需要配置Deployment属性&#xff0c;选择war或者war exploded的发布方式。而要进行这个配置首先要点…

Java 集合系列04之 fail-fast总结

转载自 Java 集合系列04之 fail-fast总结概要 前面&#xff0c;我们已经学习了ArrayList。接下来&#xff0c;我们以ArrayList为例&#xff0c;对Iterator的fail-fast机制进行了解。内容包括&#xff1a;&#xff1a;1 fail-fast简介2 fail-fast示例3 fail-fast解决办法4 fai…

Vue.js说说组件

什么是组件&#xff1a;组件是Vue.js最强大的功能之一。组件可以扩展HTML元素&#xff0c;封装可重用的代码。在较高层面上&#xff0c;组件是自定义的元素&#xff0c;Vue.js的编译器为它添加特殊功能。在有些情况下&#xff0c;组件也可以是原生HTML元素的形式&#xff0c;以…

java 内存快照怎么看_jvm内存快照dump文件太大,怎么分析

1、场景通常&#xff0c;使用eclipse的mat图形化工具打开dump的时候都会内存溢出.对于比较小的dump&#xff0c;eclipse可以打开&#xff0c;但一旦dump文件太大&#xff0c;eclipse就有点束手无策。这时候怎么办呢&#xff1f;可以使用linux下的mat&#xff0c;既Memory Analy…

第3步 (请先看第2步再看第3步) 新建完spring+springmvc+mybatis项目 需要推送gitee仓库进行管理 巨详细

idea中Terminal终端无法执行GIT命令&#xff1a; touch README.md问题解决 解决方法二&#xff1a; ◆更改路径即可 Git\bin\bash.exe 或 Git\bin\sh.exe 然后重新启动idea即可 ********************88注意&#xff1a;每次更改完成后需要重新启动IDEA****************** 记…

115个Java面试题和答案——终极列表(下)

转载自 115个Java面试题和答案——终极列表&#xff08;下&#xff09;第一篇讨论了面向对象编程和它的特点&#xff0c;关于Java和它的功能的常见问题&#xff0c;Java的集合类&#xff0c;垃圾收集器&#xff0c;本章主要讨论异常处理&#xff0c;Java小应用程序&#xff0…