由于go不是一门面向对象的语言,因此在有一些特性上和java是有一些区别的,比如go中就没有类这样的概念。下面来介绍一下go的一些特性。
结构体类似与java中的类,但又不完全一样。在类中,可以定义字段和方法,但是在结构体中是不可以定义方法或者函数的。
下面是一段定义结构体的代码:
package demo
type People struct {
age int
name string
}
其中type
关键字的作用是,表明后面一个东西是一个类型,是什么类型呢?struct
类型。这个类型叫什么名字呢?People
。
当然了,还可以在People
中定义一个结构体:
package demo
type People struct {
age int
name string
}
type Student struct {
people People
studentId int
}
Student
结构体中有一个People
结构体,除此之外,还包含了一个StudentId
字段。这个例子也多少有那么一点继承的味道吧。
使用结构体的方式也很简单:
package demo
import "fmt"
func useStruct() {
var people People
people.age = 10
people.name = "people"
fmt.Println(people.name)
var p1 = People{
age: 0,
name: "",
}
fmt.Println(p1)
}
上面people
的类型是People
,如果在一个方法内对传入的people
做修改,外面的是不会受到影响的。如果需要直接访问它本身的话,则需要使用指针结构体(指向结构体的指针)。需要注意的是,只有初始化后才能进行访问,否则会nil。下面两种声明方式是等价的:
var p1 = new(People)
fmt.Println(p1)
var p2 = &People{}
fmt.Println(p2)
在go中,函数和方法是两个不同的概念,这也是与java不同的一点。函数可以类比于java中的静态方法,不需要创建一个类就可以直接调用,可以说是相对。对于go来说,一个函数不属于任何结构体和类型,是没有接收者的。与之相对的,方法就是比函数多出来一个接收者。
package demo
func add(a, b) int {
return a + b;
}
func Add(a, b) int {
return a + b;
}
比如上述代码中,add
和Add
就是作为一个函数的身份出现的。需要注意的是,go中没有访问限定符,只是根据函数名首字母的大小写来确定该函数是否能被其他包来使用。比如add
方法不能别其他包来引用,而Add
方法就可以。使用时只需要调用demo.Add(a, b)
就可以了(包名.函数名)。
在java中,要想将一个函数作为参数,只能传一个接口然后去实现它。在go中,可以直接将一个函数作为另一个函数的参数进行传递。
package demo
func twoTimes(a, b int) int {
return a + b
}
func threeTimes(f func(int, int) int, c int) int {
return f(c, c) + c
}
还可以使用别名进行简化:
package demo
func twoTimes(a, b int) int {
return a + b
}
type tTs func(int, int) int
func threeTimes(f tTs, c int) int {
return f(c, c) + c
}
函数有点独立的感觉,那有时候我们需要让函数和结构体关联起来要怎么做呢?这时候就需要使用方法了。方法是和其接收者——一个结构体绑定在一起的:
package demo
type People struct {
age int
name string
}
func (p People) getName() string {
return p.name
}
可以这样说,这个getName
成了People
的函数了:
package demo
func useGetName() {
var p = new(People)
p.getName()
}
需要注意的是,上面getName
方法接受的是People
的一个副本,函数里面的操作不会对原有的p有任何影响。如果想要影响它就需要使用接收者的指针了。
在调用方法的时候,传递的接收者本质上都是副本,只不过一个是这个值副本,一是指向这个值指针的副本。指针具有指向原有值的特性,所以修改了指针指向的值,也就修改了原有的值。我们可以简单的理解为值接收者使用的是值的副本来调用方法,而指针接收者使用实际的值来调用方法。
package demo
func useGetName() {
var p = new(People)
p.setName("abc")
fmt.Println(p) // &{0 }
p.setPName("abc")
fmt.Println(p) // &{0 abc}
}
func (p *People) setName(name string) {
p.name = name
}
func (p *People) setPName(name string) {
p.name = name
}
在java中,想要返回多个值只能使用一个类进行包装再返回,而在go中,可以直接返回多个值。在标准库中,经常见到一个函数返回两个值,一个是结果,另一个是错误信息。
func main() {
file, err := os.Open("/usr/tmp")
if err != nil {
log.Fatal(err)
return
}
fmt.Println(file)
}
如果有不想使用的值,那么用_
来进行忽略:
file, _ := os.Open("/usr/tmp")
定义一个多值返回的函数:
package demo
func getPeopleInfo(p People) (string, int) {
return p.name, p.age
}
go中的接口也是一组方法的定义,只要一个类型实现了这个接口的所有方法,那么它就是实现了这个接口。只要这个接口声明的方法被全部实现,那么这个接口就可以用了。
package main
import "fmt"
func main() {
s := new(student)
s.name = "student"
c := s
fmt.Println(c.onClass())
}
type student struct {
name string
}
type teacher struct {
name string
}
type classRoom interface {
onClass() string
}
func (s student) onClass() string {
return s.name
}
func (t teacher) onClass() string {
return t.name
}
当然了,一个类型可以实现多个接口;一个接口也可以被多个类型实现。接口呢,就是说这是一个有这么多方法的一个类型。至于方法是怎么实现的,并不关心。具体的实现是交给结构体和方法去做的。只要能做出来满足这个接口定义的,就全部都可以称之为这个接口类型。
空接口interface{}
可以是任意类型,在go1.18之后的泛型any
本质上就是空接口的别名:type any
。