go语言没有沿袭传统面向对象编程中的诸多概念,比如继承、虚函数、构造函数和析构函数、隐藏的this指针等。
方法
go 语言中同时有函数和方法。方法就是一个包含了接受者(receiver)的函数,receiver可以是内置类型或者结构体类型的一个值或者是一个指针。所有给定类型的方法属于该类型的方法集。
如下面的这个例子,定义了一个新类型integer,它和int一样,只是为它内置的int类型增加了个新方法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") } }
可以看出,go语言在自定义类型的对象中没有c /java那种隐藏的this指针,而是在定义成员方法时显式声明了其所属的对象。
method的语法如下:
func (r receivertype) funcname(parameters) (results)
当调用method时,会将receiver作为函数的第一个参数:
所以,receiver是值类型还是指针类型要看method的作用。如果要修改对象的值,就需要传递对象的指针。
指针作为receiver会对实例对象的内容发生操作,而普通类型作为receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。
func (a *ingeger) add(b integer) { *a = b } func main() { var a integer = 1 a.add(3) fmt.println("a =", a) // a = 4 }
如果add方法不使用指针,则a返回的结果不变,这是因为go语言函数的参数也是基于值传递。
注意:当方法的接受者是指针时,即使用值类型调用那么方法内部也是对指针的操作。
之前说过,go语言没有构造函数的概念,通常使用一个全局函数来完成。例如:
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语言提供了继承,但是采用了组合的语法,我们将其称为匿名组合,例如:
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()) }
例子中,在base类型定义了get()和set()两个方法,而derived类型继承了base类,并改写了get()方法,在derived对象调用set()方法,会加载基类对应的方法;而调用get()方法时,加载派生类改写的方法。
组合的类型和被组合的类型包含同名成员时, 会不会有问题呢?可以参考下面的例子:
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) }
值语义和引用语义
值语义和引用语义的差别在于赋值,比如
如果b的修改不会影响a的值,那么此类型属于值类型;如果会影响a的值,那么此类型是引用类型。
go语言中的大多数类型都基于值语义,包括:
- 基本类型,如byte、int、bool、float32、string等;
- 复合类型,如arry、struct、pointer等;
c语言中的数组比较特别,通过函数传递一个数组的时候基于引用语义,但是在结构体定义数组变量的时候基于值语义。而在go语言中,数组和基本类型没有区别,是很纯粹的值类型,例如:
var a = [3] int{1,2,3} var b = a b[1] fmt.println(a, b) // [1 2 3] [1 3 3]
从结果看,b=a赋值语句是数组内容的完整复制,要想表达引用,需要用指针:
var a = [3] int{1,2,3} var b = &a // 引用语义 b[1] fmt.println(a, b) // [1 3 3] [1 3 3]
接口
interface 是一组抽象方法(未具体实现的方法/仅包含方法名参数返回值的方法)的集合,如果实现了 interface 中的所有方法,即该类/对象就实现了该接口。
interface 的声明格式:
type interfacename interface { //方法列表 }
interface 可以被任意对象实现,一个类型/对象也可以实现多个 interface;
interface的变量可以持有任意实现该interface类型的对象。
如下面的例子:
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实现 // 因为这三个类型都实现了这两个方法 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} //定义men类型的变量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) //这三个都是不同类型的元素,但是他们实现了interface同一个接口 x[0], x[1], x[2] = paul, sam, mike for _, value := range x{ value.sayhi() } }
空接口
空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于c语言的void*类型。
// 定义a为空接口 var a interface{} var i int = 5 s := "hello world" // a可以存储任意类型的数值 a = i a = s
interface的变量里面可以存储任意类型的数值(该类型实现了interface),那么我们怎么反向知道这个interface变量里面实际保存了的是哪个类型的对象呢?目前常用的有两种方法:switch测试、comma-ok断言。
switch测试如下:
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断言的话:
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) } } }
嵌入接口
正如struct类型可以包含一个匿名字段,interface也可以嵌套另外一个接口。
如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。
反射
所谓反射(reflect)就是能检查程序在运行时的状态。
使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.type或者reflect.value,根据不同的情况调用不同的函数)。这两种获取方式如下:
t := reflect.typeof(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素 v := reflect.valueof(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如
tag := t.elem().field(0).tag //获取定义在struct里面的标签 name := v.elem().field(0).string() //获取存储在第一个字段里面的值
获取反射值能返回相应的类型和数值
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())
最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误
var x float64 = 3.4 v := reflect.valueof(x) v.setfloat(7.1)
如果要修改相应的值,必须这样写
var x float64 = 3.4 p := reflect.valueof(&x) v := p.elem() v.setfloat(7.1)
上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。
参考文档:
http://www.cnblogs.com/chenny7/p/4497969.html