用户自定义类型
结构体类型
结构体类型通过组合一系列固定且唯一的字段来声明,每个字段类型既可以是内置类型,也可以是其他自定义的类型。
type User struct {
id int
name string
email string
}
//声明结构体类型变量并初始化零值(结构体中的每个字段都会用零值初始化)
var u1 User
//赋值
u1.id = 1
u1.name = "zhangsan1"
u1.email = "zhangsan1@163.com"
还可以用如下方式在变量声明时进行初始化赋值:
//这种形式对声明顺序没有要求
u2 := User{
id:2,
name:"zhangsan2",
email:"zhangsan2@163.com",
}
//这种形式要和结构体声明中的字段顺序一致
u3 := User{id:3, name:"zhangsan3", email:"zhangsan3@163.com"}
基于已有类型
另一种声明用户自定义类型的方法是基于一个已有的类型,使用这种声明类型的方法,从内置类型中创建出更多类型,赋予更高级的功能。
type Interger int
Go认为这两种类型是不同的类型,不同类型不能相互赋值,也不会对不同类型的值做隐式转换。
方法和接口
方法
Go语言中可以通过struct或者基于内置类型进行类型自定义,这些自定义的类型可以添加方法(有点类似于class的概念)。方法实际上也是函数,只是在声明时,在关键字func和方法名之间增加一个参数,这个参数被称为接收者,将函数与接收者的类型绑定在一起。
func (接收者) 函数名(函数参数) 返回类型 {
函数体
}
Go语言里有两种类型接收者:
- 值接收者:如果使用值接受者声明方法,调用时会使用这个值的一个副本来执行。
- 指针接收者:如果使用指针接收者声明方法,使用时会共享调用方法时接收者所指向的值。
Go语言里可以给任何类型添加相应方法(包括基于内置类型定义的类型,但不包括指针类型)。
package main
import (
"fmt"
)
type User struct {
id int
name string
email string
}
//值接收者
func (u User) changeQEmail() {
u.email = "zhangsan@qq.com"
}
//指针接收者
func (u *User) changeGEmail() {
u.email = "zhangsan@gmail.com"
}
func main() {
u := User{
id:1,
name:"zhangsan",
email:"zhangsan@163.com",
}
u.changeQEmail()
fmt.Println(u.email)
//输出结果:zhangsan@163.com
u.changeGEmail()
fmt.Println(u.email)
//输出结果:zhangsan@gmail.com
}
除了可以给结构体类型添加方法外还可以给内置类型添加方法,如下所示:
package main
import (
"fmt"
"math"
)
type MyFloat float64
func (f MyFloat) abs() float64 {
return math.Abs(float64(f))
}
func main() {
//var v MyFloat = -2.5
v := MyFloat(-2.5)
fmt.Println(v.abs())
}
在声明一个新类型后,声明一个该类型的方法之前,需要确定使用值接收者还是指针接收者?如果要创建一个新值,该类型的方法就使用值接收者,如果是要修改当前值,就使用指针接收者。
接口
接口类型是由一组方法定义的集合,接口中定义的方法只有函数签名,没有具体实现。如果某个数据类型实现了接口中定义的方法,则称这个数据类型实现了这个接口。
package main
import (
"fmt"
)
//定义接口
type Mobile interface {
call() string
}
type IPhone struct {
}
type Android struct {
}
//实现接口中的方法
func (m IPhone) call() string {
return "iphone"
}
//实现接口中的方法
func (m Android) call() string {
return "android"
}
func main() {
//定义一个接口类型的值
var m Mobile
//存放IPhone类型的值
m = new(IPhone)
fmt.Println(m.call())
//存放Android类型的值
m = new(Android)
fmt.Println(m.call())
}
接口类型的值可以存放实现这些方法的任何类型的值。
面向对象编程
Go语言在包级别进行访问控制,以小写字母开头的名称只在该程序包中可见(相当于private),以大写字母开头的名称在包外也可访问(相当于public)。
Go语言没有类的概念,但它支持数据类型可以定义对应的方法。所谓的方法其实就是函数,只不过与普通函数相比,这类函数是作用在某个数据类型上,所以在函数签名中会有receiver(接收者)来表明当前定义的函数会作用在哪个类型上。Go支持在struct类型或基于内置类型定义的新类型上都可以定义方法,只不过在实际项目中,方法多定义在struct类型上,从这点看我们可以将Go中的struct看作是轻量级的“类”,然后通过struct组合的方式实现在传统面向对象语言中继承的概念。
package main
import (
"fmt"
)
type Student struct {
name string
age int
gender string
}
type CollegeStudent struct {
//通过组合的方式实现继承
Student
university string
major string
}
//定义Student类型的方法
func (s Student) getName() string {
return s.name
}
//定义CollegeStudent类型的方法
func (s CollegeStudent) getMajor() string {
return s.major
}
//定义CollegeStudent类型的方法
func (s CollegeStudent) getAge() int {
return s.age
}
func main() {
s := CollegeStudent{Student{"张三", 21, "男"}, "天津大学", "计算机科学与技术"}
fmt.Println(s.getName())
fmt.Println(s.getMajor())
fmt.Println(s.getAge())
}
通过struct类型和组合的方式我们可以在Go中实现封装和继承,多态也是面向对象很重要的特性之一,Go通过接口实现了这个特性。
package main
import (
"fmt"
"math"
)
//定义geometry接口
type geometry interface {
area() float64
perimeter() float64
}
//定义rect类型
type rect struct {
width, height float64
}
//定义circle类型
type circle struct {
radius float64
}
//rect类型实现geometry接口
func (r rect) area() float64 {
return r.width * r.height
}
func (r rect) perimeter() float64 {
return r.width*2 + r.height*2
}
//circle类型实现geometry接口
func (c circle) area() float64 {
return math.Pi * c.radius * c.radius
}
func (c circle) perimeter() float64 {
return math.Pi * c.radius * 2
}
//定义的接口可以作为一种数据类型,当一个变量的类型为一个接口类型时,这个变量可以调用接口中定义的方法
//这里定义一个函数,接收一个类型为geometry的参数变量
func measure(g geometry) {
fmt.Println(g)
fmt.Println(g.area())
fmt.Println(g.perimeter())
}
func main() {
r := rect{width:3, height:5}
c := circle{radius:5}
//rect和circle结构类型都实现了geometry接口,所以我们能够把它们的对象作为参数传给接口类型的变量
measure(r)
measure(c)
}
接口就是一组方法声明的集合,不具体实现方法。任何类型实现了在接口中声明的全部方法,则表明该类型实现了对应的接口。接口作为一种数据类型,实现了该接口的任何对象都可以给对应的接口类型变量赋值。