Go 语言中同时有函数和方法?span style="color: #ff0000;">Ҏ是一个包含了接受者(receiverQ的函数Qreceiver可以是内|类型或者结构体cd的一个值或者是一个指针。所有给定类型的Ҏ属于该类型的Ҏ集?br />
如下面的q个例子Q定义了一个新cdIntegerQ它和int一P只是为它内置的intcd增加了个新方法Less()
type Integer int func (a Integer) Less(b Integer) bool { return a < b } func main() { var a Integer = 1 if a.Less(2) { fmt.Println("less then 2") } }
可以看出QGo语言在自定义cd的对象中没有C++/Java那种隐藏的this指针Q而是在定义成员方法时昑ּ声明了其所属的对象?/p>
method的语法如下:
func (r ReceiverType) funcName(parameters) (results)
当调用methodӞ会将receiver作ؓ函数的第一个参敎ͼ
funcName(r, parameters);
所以,receiver是值类型还是指针类型要看method的作用。如果要修改对象的|需要传递对象的指针?/p>
指针作ؓReceiver会对实例对象的内容发生操?而普通类型作为Receiver仅仅是以副本作ؓ操作对象,q不对原实例对象发生操作?/p>
func (a *Ingeger) Add(b Integer) { *a += b } func main() { var a Integer = 1 a.Add(3) fmt.Println("a =", a) // a = 4 }
如果AddҎ不用指针,则aq回的结果不变,q是因ؓGo语言函数的参C是基于g递?/p>
注意Q?span style="color: #ff0000;">当方法的接受者是指针Ӟ即用值类型调用那么方法内部也是对指针的操作?/span>
之前说过QGo语言没有构造函数的概念Q通常使用一个全局函数来完成。例如:
func NewRect(x, y, width, height float64) *Rect { return &Rect{x, y, width, height} } func main() { rect1 := NewRect(1,2,10,20) fmt.Println(rect1.width) }
Go语言提供了承,但是采用了组合的语法Q我们将其称为匿名组合,例如Q?/span>
type Base struct { name string } func (base *Base) Set(myname string) { base.name = myname } func (base *Base) Get() string { return base.name } type Derived struct { Base age int } func (derived *Derived) Get() (nm string, ag int) { return derived.name, derived.age } func main() { b := &Derived{} b.Set("sina") fmt.Println(b.Get()) }
例子中,在Basecd定义了get()和set()两个ҎQ而Derivedcdl承了Basec,q改写了Get()ҎQ在Derived对象调用Set()ҎQ会加蝲基类对应的方法;而调用Get()ҎӞ加蝲zcL写的Ҏ?/p>
l合的类型和被组合的cd包含同名成员Ӟ 会不会有问题呢?可以参考下面的例子Q?/p>
type Base struct { name string age int } func (base *Base) Set(myname string, myage int) { base.name = myname base.age = myage } type Derived struct { Base name string } func main() { b := &Derived{} b.Set("sina", 30) fmt.Println("b.name =",b.name, "\tb.Base.name =", b.Base.name) fmt.Println("b.age =",b.age, "\tb.Base.age =", b.Base.age) }
D义和引用语义的差别在于赋|比如
b = a b.Modify()
如果b的修改不会媄响a的|那么此类型属于值类型;如果会媄响a的|那么此类型是引用cd?/span>
Go语言中的大多数类型都ZD义,包括Q?/p>
C语言中的数组比较特别Q通过函数传递一个数l的时候基于引用语义,但是在结构体定义数组变量的时候基于D义。而在Go语言中,数组和基本类型没有区别,是很Ua的值类型,例如Q?/p>
var a = [3] int{1,2,3} var b = a b[1]++ fmt.Println(a, b) // [1 2 3] [1 3 3]
从结果看Qb=a赋D句是数组内容的完整复Ӟ要想表达引用Q需要用指针Q?/p>
var a = [3] int{1,2,3} var b = &a // 引用语义 b[1]++ fmt.Println(a, b) // [1 3 3] [1 3 3]
Interface 是一l抽象方法(未具体实现的Ҏ/仅包含方法名参数q回值的ҎQ的集合Q如果实C interface 中的所有方法,卌c?对象实C该接口?/p>
Interface 的声明格式:
type interfaceName interface { //Ҏ列表 }
Interface 可以被Q意对象实玎ͼ一个类?对象也可以实现多?interfaceQ?br />interface的变量可以持有Q意实现该interfacecd的对象?/span>
如下面的例子Q?/p>
package main import "fmt" type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string loan float32 } type Employee struct { Human //匿名字段 company string money float32 } //Human实现SayHiҎ func (h Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } //Human实现SingҎ func (h Human) Sing(lyrics string) { fmt.Println("La la la la...", lyrics) } //Employee重蝲Human的SayHiҎ func (e Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) } // Interface Men被Human,Student和Employee实现 // 因ؓq三个类型都实现了这两个Ҏ type Men interface { SayHi() Sing(lyrics string) } func main() { mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000} //定义Mencd的变量i var i Men //i能存储Student i = mike fmt.Println("This is Mike, a Student:") i.SayHi() i.Sing("November rain") //i也能存储Employee i = tom fmt.Println("This is tom, an Employee:") i.SayHi() i.Sing("Born to be wild") //定义了slice Men fmt.Println("Let's use a slice of Men and see what happens") x := make([]Men, 3) //q三个都是不同类型的元素Q但是他们实Cinterface同一个接?/span> x[0], x[1], x[2] = paul, sam, mike for _, value := range x{ value.SayHi() } }
Iinterface(interface{})不包含Q何的methodQ正因ؓ如此Q?span style="background-color: #ffff00;">所有的cd都实CIinterface。空interface对于描述起不CQ何的作用(因ؓ它不包含M的methodQ,但是Iinterface在我们需要存储Q意类型的数值的时候相当有用,因ؓ它可以存储Q意类型的数倹{它有点cM于C语言的void*cd?/span>
// 定义a为空接口 var a interface{} var i int = 5 s := "Hello world" // a可以存储Lcd的数?/span> a = i a = s
interface的变量里面可以存储Q意类型的数|该类型实CinterfaceQ,那么我们怎么反向知道q个interface变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种ҎQswitch试、Comma-ok断言?/p>
switch试如下Q?/p>
type Element interface{} type List [] Element type Person struct { name string age int } //打印 func (p Person) String() string { return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" } func main() { list := make(List, 3) list[0] = 1 //an int list[1] = "Hello" //a string list[2] = Person{"Dennis", 70} for index, element := range list{ switch value := element.(type) { case int: fmt.Printf("list[%d] is an int and its value is %d\n", index, value) case string: fmt.Printf("list[%d] is a string and its value is %s\n", index, value) case Person: fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) default: fmt.Println("list[%d] is of a different type", index) } } }
如果使用Comma-ok断言的话Q?/p>
func main() { list := make(List, 3) list[0] = 1 // an int list[1] = "Hello" // a string list[2] = Person{"Dennis", 70} for index, element := range list { if value, ok := element.(int); ok { fmt.Printf("list[%d] is an int and its value is %d\n", index, value) } else if value, ok := element.(string); ok { fmt.Printf("list[%d] is a string and its value is %s\n", index, value) } else if value, ok := element.(Person); ok { fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) } else { fmt.Printf("list[%d] is of a different type\n", index) } } }
正如structcd可以包含一个匿名字D,interface也可以嵌套另外一个接口?/p>
如果一个interface1作ؓinterface2的一个嵌入字D,那么interface2隐式的包含了interface1里面的method?/p>
所谓反(reflectQ就是能查程序在q行时的状态?/p>
使用reflect一般分成三步,下面要的讲解一下:要去反射是一个类型的?q些值都实现了空interface)Q首先需要把它{化成reflect对象(reflect.Type或者reflect.ValueQ根据不同的情况调用不同的函?。这两种获取方式如下Q?/p>
t := reflect.TypeOf(i) //得到cd的元数据,通过t我们能获取类型定义里面的所有元?/span> v := reflect.ValueOf(i) //得到实际的|通过v我们获取存储在里面的|q可以去改变?/span>
转化为reflect对象之后我们可以进行一些操作了Q也是reflect对象转化成相应的|例如
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标{?/span> name := v.Elem().Field(0).String() //获取存储在第一个字D里面的?/span>
获取反射Dq回相应的类型和数?/p>
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float())
最后,反射的话Q那么反的字段必须是可修改的,我们前面学习q传值和传引用,q个里面也是一L道理。反的字段必须是可d的意思是Q如果下面这样写Q那么会发生错误
var x float64 = 3.4 v := reflect.ValueOf(x) v.SetFloat(7.1)
如果要修改相应的|必须q样?/p>
var x float64 = 3.4 p := reflect.ValueOf(&x) v := p.Elem() v.SetFloat(7.1)
上面只是对反的单介l,更深入的理解q需要自己在~程中不断的实践?/p>
参考文档:
http://se77en.cc/2014/05/05/methods-interfaces-and-embedded-types-in-golang/
http://se77en.cc/2014/05/04/choose-whether-to-use-a-value-or-pointer-receiver-on-methods/
http://www.cnblogs.com/chenny7/p/4497969.html
func (p myType ) funcName ( a, b int , c string ) ( r , s int ) { return }
通过函数定义Q我们可以看到Go中函数和其他语言中的共性和Ҏ?/p>
Go中函数的Ҏ是非常LQ给我们带来不一L~程体验?/p>
在Go中通过l函数标明所属类型,来给该类型定义方法,上面?nbsp;p myType
卌C给myType声明了一个方法, p myType
不是必须的。如果没有,则纯_Ҏ一个函敎ͼ通过包名U访问。packageName.funcationName
如:
//定义新的cddoubleQ主要目的是lfloat64cd扩充Ҏ type double float64 //判断a是否{于b func (a double) IsEqual(b double) bool { var r = a - b if r == 0.0 { return true } else if r < 0.0 { return r > -0.0001 } return r < 0.0001 } //判断a是否{于b func IsEqual(a, b float64) bool { var r = a - b if r == 0.0 { return true } else if r < 0.0 { return r > -0.0001 } return r < 0.0001 } func main() { var a double = 1.999999 var b double = 1.9999998 fmt.Println(a.IsEqual(b)) fmt.Println(a.IsEqual(3)) fmt.Println( IsEqual( (float64)(a), (float64)(b) ) ) }
上述CZ?float64 基本cd扩充了方法IsEqualQ该Ҏ主要是解决精度问题?其方法调用方式ؓQ?nbsp;a.IsEqual(double)
Q如果不扩充ҎQ我们只能用函?code style="margin: 0px 2px; padding: 0px 5px; border: 1px solid #eaeaea; background-color: #f8f8f8; border-radius: 3px; white-space: nowrap; font-family: monospace, Monaco;">IsEqual(a, b float64)
?nbsp;func IsEqual(a, b float64) bool
q个Ҏ只保留了一个类型声?此时入参a和b均是float64数据cd?q样也是可以的: func IsEqual(a, b float64, accuracy int) bool
?nbsp;func Sum(args ...int)
参数args是的sliceQ其元素cd为int 。经怋用的fmt.Printf是一个接受Q意个数参数的函数 fmt.Printf(format string, args ...interface{})
前面我们定义函数时返回值有两个r,s 。这是非常有用的Q我在写C#代码Ӟ常常Z从已有函C获得更多的信息,需要修改函数签名,使用out ,ref {方式去获得更多q回l果。而现在用Go时则很简单,直接在返回值后面添加返回参数即可?/p>
?在C#中一个字W串转换为intcd旉辑代码
int v=0; if ( int.TryPase("123456",out v) ) { //code }
而在Go中,则可以这样实?逻辑_而明?/p>
if v,isOk :=int.TryPase("123456") ; isOk { //code }
同时在Go中很多函数充分利用了多返回?/p>
那么如果我只需要某一个返回|而不兛_其他q回值的话,我该如何办呢Q?q时可以单的使用W号下划U?#8221;_“ 来忽略不兛_的返回倹{如Q?/p>
_, cos = math.Sincos(3.1415) //只需要cos计算的?
前面我们说了函数可以有多个返回|q里我还要说的是Q在函数定义时可以给所有的q回值分别命名,q样p在函CL位置l不同返回值复Ӟ而不需要在return语句中才指定q回倹{同时也能增强可L,也提高godoc所生成文档的可L?/p>
如果不支持命名返回|我可能会是这样做?/p>
func ReadFull(r Reader, buf []byte) (int, error) { var n int var err error for len(buf) > 0 { var nr int nr, err = r.Read(buf) n += nr if err !=nil { return n,err } buf = buf[nr:] } return n,err }
但支持给q回值命名后Q实际上是省略了变量的声明Qreturn时无需写成return n,err
而是直接将D?/p>
func ReadFull(r Reader, buf []byte) (n int, err error) { for len(buf) > 0 && err == nil { var nr int nr, err = r.Read(buf) n += nr buf = buf[nr:] } return }
和Go中其他东西一P函数也是|q样可以声明一个函数类型的变量Q将函数作ؓ参数传递?/p>
声明函数为值的变量(匿名函数:可赋g变量Q也可直接执?
//赋?fc := func(msg string) { fmt.Println("you say :", msg) } fmt.Printf("%T \n", fc) fc("hello,my love") //直接执行 func(msg string) { fmt.Println("say :", msg) }("I love to code")
输出l果如下Q这里表明fc 的类型ؓQfunc(string)
func(string) you say : hello,my love say : I love to code
函C为入参(回调函数Q,能带来便利。如日志处理Qؓ了统一处理Q将信息均通过指定函数去记录日志,且是否记录日志还有开?/p>
func Log(title string, getMsg func() string) { //如果开启日志记?则记录日? if true { fmt.Println(title, ":", getMsg()) } } //---------调用-------------- count := 0 msg := func() string { count++ return "您没有即使提醒我,已触犯法? } Log("error", msg) Log("warring", msg) Log("info", msg) fmt.Println(count)
q里输出l果如下Qcount 也发生了变化
error : 您没有即使提醒我,已触犯法?warring : 您没有即使提醒我,已触犯法?info : 您没有即使提醒我,已触犯法?3
你有没有注意C面示例中?nbsp;fc := func(msg string)...
Q既然匿名函数可以赋值给一个变量,同时我们l常q样lint赋?nbsp;value := 2
,是否我们可以声明func(string) cd 呢,当然是可以的?/p>
//一个记录日志的cdQfunc(string) type saveLog func(msg string) //字W串转换为int64,如果转换p|调用saveLog func stringToInt(s string, log saveLog) int64 { if value, err := strconv.ParseInt(s, 0, 0); err != nil { log(err.Error()) return 0 } else { return value } } //记录日志消息的具体实?func myLog(msg string) { fmt.Println("Find Error:", msg) } func main() { stringToInt("123", myLog) //转换时将调用mylog记录日志 stringToInt("s", myLog) }
q里我们定义了一个类型,专门用作记录日志的标准接口。在stringToInt函数中如果{换失败则调用我自己定义的接口函数q行日志处理Q至于最l执行的哪个函数Q则无需兛_?/p>
defer 又是一个创斎ͼ它的作用是:延迟执行Q在声明时不会立x行,而是在函数return后时按照后进先出的原则依ơ执行每一个defer。这样带来的好处是,能确保我们定义的函数能百分之百能够被执行刎ͼq样p做很多我们想做的事,如释放资源,清理数据Q记录日志等
q里我们重点来说明下defer的执行顺?/p>
func deferFunc() int { index := 0 fc := func() { fmt.Println(index, "匿名函数1") index++ defer func() { fmt.Println(index, "匿名函数1-1") index++ }() } defer func() { fmt.Println(index, "匿名函数2") index++ }() defer fc() return func() int { fmt.Println(index, "匿名函数3") index++ return index }() } func main() { deferFunc() }
q里输出l果如下Q?/p>
0 匿名函数3 1 匿名函数1 2 匿名函数1-1 3 匿名函数2
有如下结论:
另外Q我们常使用deferd闭IO,在正常打开文g后,qd明一个deferQ这样就不会忘记关闭文gQ也能保证在出现异常{不可预料的情况下也能关闭文件。而不像其他语aQ?code style="margin: 0px 2px; padding: 0px 5px; border: 1px solid #eaeaea; background-color: #f8f8f8; border-radius: 3px; white-space: nowrap; font-family: monospace, Monaco;">try-catch 或?nbsp;using()
方式q行处理?/p>
file , err :=os.Open(file) if err != nil { return err } defer file.Close() //dosomething with file
后箋Q我讨论: 作用域、传值和传指?以及 保留函数init(),main()
本笔C所写代码存储位|: