struct
结构体struct就相当于java中的类class,用于定义属性和方法。
定义一个Person,有string型的Name和int型的Age两个属性:
type Person struct { Name string Age int }
创建一个Person实例:
第一种方式:
var p Person = Person{}
这时,所有属性值都是零值。
直接打点给属性赋值:
p.Name = "zhangsan"
p.Age = 18
var p Person = Person{"zhangsan", 18}
一个个把属性值列出来,要求一个都不能少。
第二种方式:
var p Person = Person{Name: "zhansan", Age: 18}
这时,Name属性值为zhangsan,Age属性值为18
把属性名与值一对对列出来,可以只列需要的属性,其他的属性值为零值。注意,属性名没有用双引号括住。
第三种方式:
var pptr *Person = new(Person)
注意,与上面两种方式不同,用new关键字生成的是指向Person实例的指针。var p Person = new(Person)会报编译错误,提示Cannot use 'new(Person)' (type *Person) as type Person。
我们可以通过struct指针操作属性,效果和通过struct实例一样。
如pptr.Name = "lisi"
fmt.Println(pptr.Name)
定义一个Employee,有Person型的p和int型的salary两个属性:
type Employee struct { p Person salary int }
创建一个Employee实例:
var e Employee = Employee{}
var e Employee = Employee{p: Person{}, salary: 10000}
var eptr *Employee = new(Employee)
如果属性类型是自定义的struct的话,属性名可以省略,如下:
type Employee struct { Person salary int }
此时创建一个Employee实例:
var e Employee = Employee{}
var e Employee = Employee{Person: Person{}, salary: 10000}
这里由于属性名省略了,所以花括号中的key只能是我们自定义的struct了。
var eptr *Employee = new(Employee)
通过e访问Name、Age属性:
在属性名不省略时,只能通过e打点获取Person实例,然后Person实例再打点操作Name、Age属性,示例如下:
func main() { var e Employee = Employee{p: Person{}, salary: 10000} e.p.Name = "zhangsan" fmt.Println(e) }
在属性名省略时,我们既可以通过e打点获取Person实例,然后Person实例再打点操作Name、Age属性,也可以直接e打点操作Name、Age属性,这就有点像java的继承了。示例如下:
func main() { var e Employee = Employee{Person: Person{}, salary: 10000} e.Person.Name = "zhangsan" e.Age = 30 fmt.Println(e) }
函数
在go中,函数是一等公民。
函数可以有不定长入参,可以有多个返回值,可以赋值给变量,可以作为函数的入参和出参。
函数定义:func func_name(i int, s string) int {}
定义函数f0,有一个int型入参、无出参:
func f0(i int) { }
定义函数f1,有一个int型入参、一个string型入参,一个int型出参:
func f1(i int, s string) int { return 0 }
定义函数f2,有两个int型入参,一个string型出参:
func f2(i, j int) string { return "" }
如果多个入参类型一样的话,可以省略前面几个参数的类型关键字,而只保留最后一个参数的类型关键字。
定义函数f3,有两个int型入参,一个string型出参,一个int型出参:
func f3(i, j int) (string, int) { return "", 0 }
多个出参的话,要用括号包起来。
定义函数f4,有不定长个int型入参,一个int型出参:
func f4(p ...int) int { return len(p) }
定义函数f5,有一个string型入参和不定长个int型入参,一个int型出参:
func f5(s string, sl ...int) int { return len(s) + len(sl) }
函数赋值给变量
func main() { var f func(int, string) int = f1 f(1, "a") }
变量的类型可以通过fmt.Println(reflect.TypeOf(f))打印出来看。
函数作为函数的入参和出参
func S(f func(i int) int) func(s string) string { return func(s string) string { return strconv.Itoa(f(len(s))) } } func main() { var p = S(func(i int) int { return i + 10 })("100") fmt.Println(p) }
我们在定义函数时,如果入参是一个struct实例,则底层会复制一个struct实例作为入参,函数对实例的改动不会影响原来的struct实例。所以,入参最好是指向struct实例的指针,这样就不用复制了,函数对实例的改动也会体现到原来的struct实例上。示例如下:
func changeName(e Person) { e.Name = "zhangsan" } func changeNamePtr(e *Person) { e.Name = "zhangsan" } func main() { var p Person = Person{} changeName(p) fmt.Println(p) var ptr *Person = new(Person) changeNamePtr(ptr) fmt.Println(ptr) }
方法
方法和函数长得差不多,区别是方法定义时func后面跟的不是func_name,而是括号,括号里面是struct类型变量,只有这个struct类型实例或者指针才能调用这个方法,之后才是func_name。示例如下:
func (p *Person) exchange(p0 *Person) { p.Name = p0.Name p0.Age = p.Age } func main() { var p Person = Person{Name: "zhansgan", Age: 18} var p0 Person = Person{Name: "lisi", Age: 10} p.exchange(&p0) fmt.Printf("%+v ", p) fmt.Printf("%+v ", p0) }
func (p *Person) exchange(p0 *Person) {}
第一个括号中的p *Person表示本方法调用者只能是Person实例或者指向Person实例的指针,且是引用传递,如果在方法中改变了调用者的属性,会在方法外体现。
第二个括号中的p0 *Person表示方法入参是引用传递,如果在方法中改变了入参的属性,会在方法外体现。
方法的继承
还是以上面的Person、Employee举例。
假设Person有个changeName方法,定义如下:
func (pptr *Person) changeName(name string) { pptr.Name = name }
假如在定义Employee时省略了Person类型属性的名称,则我们可以通过e直接打点调用changeName方法,示例1如下:
func main() { var e Employee = Employee{Person: Person{Name: "wanglaoji"}} e.changeName("lisi") fmt.Printf("%+v ", e) }
假设Employee也有个changeName方法,则直接通过e打点调用changeName方法的话,调用的其实是Employee的changeName方法,而不是Person的changeName方法。示例2如下:
func (pptr *Person) changeName(name string) { pptr.Name = name } func (eptr *Employee) changeName(name string) { eptr.Name = name + name } func main() { var e Employee = Employee{Person: Person{Name: "wanglaoji"}} fmt.Printf("%+v ", e) }
假如Employee有个change方法,在change方法中调用了changeName方法,那么e调用change方法时,执行的是Employee的changeName方法呢,还是Person的changeName方法呢?示例3如下:
func (pptr *Person) changeName(name string) { pptr.Name = name } func (pptr *Person) change(name string) { pptr.changeName(name) } func (eptr *Employee) changeName(name string) { eptr.Name = name + name } func main() { var e Employee = Employee{Person: Person{Name: "wanglaoji"}} e.change("lisi") fmt.Printf("%+v ", e) }
实测执行的是Person的changeName方法。
假如Employee也有个change方法,在change方法中调用了changeName方法,那么e调用change方法时,执行的是Employee的changeName方法呢,还是Person的changeName方法呢?示例4如下:
func (pptr *Person) changeName(name string) { pptr.Name = name } func (pptr *Person) change(name string) { pptr.changeName(name) } func (eptr *Employee) changeName(name string) { eptr.Name = name + name } func (eptr *Employee) change(name string) { eptr.changeName(name) } func main() { var e Employee = Employee{Person: Person{Name: "wanglaoji"}} e.change("lisi") fmt.Printf("%+v ", e) }
实测执行的是Employee的change方法和Employee的changeName方法。
分析:
在changeName和
总结:
在go中没有继承,不论是属性还是方法。都是太任性的省略搞的鬼。
在go中没有静态方法的概念。
接口
定义格式:
type RedPacketService interface { add(s string) delete(s string) update(s string) query(s string) string }
go中没有implements或者相同作用的关键字。要实现某个接口,不是在定义struct的时候显式声明要实现某个接口,而是采用duck typing的方式,即只要实现了接口的所有方法,就认为这个struct实现了这个接口,就可以向上转型。示例如下:
type Programmer interface { helloWorld() } type JavaProgrammer struct { } func (jp *JavaProgrammer) helloWorld() { fmt.Println("System.out.println("Hello World!");") } type GoProgrammer struct { } func (gp *GoProgrammer) helloWorld() { fmt.Println("fmt.Println("Hello World!")") } func main() { var p Programmer = new(JavaProgrammer) p.helloWorld() p = new(GoProgrammer) p.helloWorld() }
Programmer接口只有一个helloWorld方法,JavaProgrammer实现了这个方法,且是pointer receiver实现,所以JavaProgrammer实现了Programmer接口,同理,GoProgrammer也实现了Programmer接口。
假如再给Programmer接口添加一个hi方法,由于JavaProgrammer和GoProgrammer都没有实现这个方法,所以这两个struct都没有实现Programmer接口,所以把指向JavaProgrammer实例或GoProgrammer实例的指针赋值给Programmer类型变量时会报编译错误。
interface{}
interface{}可以用作任意类型的形参,示例如下:
func PX(s interface{}) { if s0, ok := s.(int); ok { fmt.Println("int=", s0) } else if s0, ok := s.(string); ok { fmt.Println("string=", s0) } else if s0, ok := s.(bool); ok { fmt.Println("bool=", s0) } else { fmt.Println("unknown type") } } func main() { PX("1") PX(1) PX(false) PX(new(Programmer)) }
以上,s可以是任意类型变量。s.(int)有2个返回值,第一个返回值是s,第二个返回值是true或者false,如果s是int型变量,就是true,否则就是false。所以通过判断第二个返回值是否是true,就能判定s是否是int型变量。
以上写法还可以通过switch来简化判断,如下:
func PX(s interface{}) { switch s.(type) { case int: fmt.Println("int=", s) case string: fmt.Println("string=", s) case bool: fmt.Println("bool=", s) default: fmt.Println("unknown type") } } func main() { PX("1") PX(1) PX(false) PX(new(Programmer)) }
s.(type) 跟在switch后面没毛病,单独写就会报编译错误,估计是个特殊语法,编译器做了特殊支持。