go--指针、结构体和接口

指针

指针地址和指针类型

每一个变量在运行时都拥有一个地址,这个地址表明变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行“取地址”操做。 Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*int*int64*string等。取变量指针的语法以下:php

a := &变量

// 举例
a := 1
b := &a
fmt.Printf("a:%d ptr:%p\n", a, &a);        // a:1 ptr:0xc000098000
fmt.Println(b)                                    // 0xc000098000
fmt.Printf("%T\n", b)                        // *int

指针取值

在对普通变量使用&操做符取地址后会得到这个变量的指针,而后能够对指针使用*操做,也就是指针取值,代码以下。json

func main() {
    a := 1
    b := &a
    fmt.Printf("type of b:%T\n", b)    // type of b:*int
    c := *b
    fmt.Printf("type of c:%T\n", c)    // type of c:int
    fmt.Printf("value of c:%v\n", c)    // value of c:1
}

指针操做,能够实现相似于php&符传引用的功能:数据结构

func modify1(x int) {
    x = 100
}

func modify2(x *int) {
    *x = 100
}

func main() {
    a := 10
    modify1(a)
    fmt.Println(a)    // 10
    modify2(a)
    fmt.Println(a)    // 100
}

new()函数建立指针

Go语言还提供了另一种方法来建立指针变量,格式以下:app

new (类型)

// 举例
func main () {
    a := new(int)
    fmt.Println(a)    // 0xc00001a078
    fmt.Printf("type of a:%T\n", a)    // type of a:*int
    *a = 100
    fmt.Println(a)  //0xc00001a078
    fmt.Println(*a) //100
}

结构体

定义

Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。Go语言中经过结构体的内嵌再配合接口比面向对象具备更高的扩展性和灵活性。函数

类型别名和自定义类型

自定义类型

在Go语言中有一些基本的数据类型,如string、整型、浮点型、布尔等数据类型, Go语言中可使用type关键字来定义自定义类型。布局

自定义类型是定义了一个全新的类型。咱们能够基于内置的基本类型定义,也能够经过struct定义。例如:性能

type MyInt int

类型别名

类型别名规定:TypeAlias只是Type的别名,本质上TypeAlias与Type是同一个类型。学习

type TypeAlias = Type

咱们以前见过的runebyte就是类型别名,他们的定义以下:ui

type byte = uint8
type rune = int32

类型定义和类型别名的区别

类型别名与类型定义表面上看只有一个等号的差别,咱们经过下面的这段代码来理解它们之间的区别。this

// 类型定义
type NewInt int

// 类型别名
type Myint = int

func main () {
    var a NewInt
    var b MyInt

    fmt.Printf("type of a:%T\n", a)    // type of a:main.NewInt
    fmt.Printf("type of b:%T\n", b)    // type of b:int
}

结构体

Go语言中的基础数据类型能够表示一些事物的基本属性,可是当咱们想表达一个事物的所有或部分属性时,这时候再用单一的基本数据类型明显就没法知足需求了,Go语言提供了一种自定义数据类型,能够封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是咱们能够经过struct来定义本身的类型了。

Go语言中经过struct来实现面向对象。

结构体的定义

使用typestruct关键字来定义结构体,具体代码格式以下:

type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
}

其中:

  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。
  • 字段名:表示结构体字段名。结构体中的字段名必须惟一。
  • 字段类型:表示结构体字段的具体类型。

例如:

type student struct {
    name string
    city string
    age int8
}

// 相同类型的字段也能够写在一行
type student struct {
    name, city string
    age int8
}

结构体实例化

只有当结构体实例化时,才会真正的分配内存。也就是必须实例化后才能使用结构体的字段。

基本实例化

type student struct {
    name string
    city string
    age int8
}

func main() {
    var tom student
    tom.name = "tom"
    tom.city = "北京"
    tom.age = 100
    fmt.Printf("p1=%v\n", p1)  //p1={tom 北京 100}
    fmt.Printf("p1=%#v\n", p1) //p1=main.student{name:"tom", city:"北京", age:100}
}

匿名结构体

在定义一些临时数据结构等场景下还可使用匿名结构体。

func main() {
    var user struct{Name string; Age int}
    user.Name = "小王子"
    user.Age = 18
    fmt.Printf("%#v\n", user)
}

建立指针类型结构体

咱们还能够经过使用new关键字对结构体进行实例化,获得的是结构体的地址。 格式以下:

var lee = new (student)
fmt.Printf("%T\n", p2)     //*main.student
fmt.Printf("p2=%#v\n", p2) //p2=&main.student{name:"", city:"", age:0}

注意:Go语言中一样支持对结构体指针直接使用.来访问结构体的成员。

取结构体的地址实例化

使用&对结构体进行取地址操做至关于对该结构体进行了一次new实例化操做。

tom := &student{}
fmt.Printf("%T\n", p3)     //*main.student
fmt.Printf("p3=%#v\n", p3) //p3=&main.student{name:"", city:"", age:0}

tom.name = "tom"
tom.age = 100
tom.city = "北京"

tom.name = "tom"其实在底层是(*tom).name = "tom",这是Go语言帮咱们实现的语法糖。

结构体初始化

没有初始化的结构体,其成员变量都是对应其类型的零值。

type person struct {
    name string
    city string
    age  int8
}

func main() {
    var p4 person
    fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}
}

使用键值对初始化

使用键值对对结构体进行初始化时,键对应结构体的字段,值对应该字段的初始值。

p := person{
    name: "小王子",
    city: "北京",
    age:  18,
}
fmt.Printf("p=%#v\n", p) //p=main.person{name:"小王子", city:"北京", age:18}

也能够对结构体指针进行键值对初始化,例如:

p6 := &person{
    name: "小王子",
    city: "北京",
    age:  18,
}
fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"小王子", city:"北京", age:18}

当某些字段没有初始值的时候,该字段能够不写。此时,没有指定初始值的字段的值就是该字段类型的零值。

p7 := &person{
    city: "北京",
}
fmt.Printf("p7=%#v\n", p7) //p7=&main.person{name:"", city:"北京", age:0}

初始化结构体的时候能够简写,也就是初始化的时候不写键,直接写值:

p8 := &person{
    "张三",
    "北京",
    28,
}
fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"张三", city:"北京", age:28}

使用这种格式初始化时,须要注意:

  • 必须初始化结构体的全部字段。
  • 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
  • 该方式不能和键值初始化方式混用。

结构体内存布局

结构体占用一块连续的内存。

构造函数

Go语言的结构体没有构造函数,咱们能够本身实现。 例如,下方的代码就实现了一个person的构造函数。 由于struct是值类型,若是结构体比较复杂的话,值拷贝性能开销会比较大,因此该构造函数返回的是结构体指针类型。

func newPerson (name, city string, age int8) *person {
    return &{
        name: name,
        city: city,
        age: age,
    }
}

方法和接收者

Go语言中的方法(Method)是一种做用于特定类型变量的函数。这种特定类型变量叫作接收者(Receiver)。接收者的概念就相似于其余语言中的this或者self

方法的定义格式以下:

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}

其中:

  • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是selfthis之类的命名。例如,Person类型的接收者变量应该命名为 pConnector类型的接收者变量应该命名为c等。
  • 接收者类型:接收者类型和参数相似,能够是指针类型和非指针类型。
  • 方法名、参数列表、返回参数:具体格式与函数定义相同。
// 结构体
type Person struct {
    name string
    age int8
}

// 构造函数
func NewPerson(name string, age int8) *Person {
    return &Person{
        name: name,
        age: age,
    }
}

// study方法
func (p Person) Study() {
    fmt.Printf("%s要好好学习\n", p.name)
}

func main() {
    p1 = NewPerson("张三", 18)
    p1.Study()
}

指针类型的接收者

指针类型的接收者由一个结构体的指针组成,因为指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其余语言中面向对象中的this或者self。 例如咱们为Person添加一个SetAge方法,来修改实例变量的年龄。

func (p *Person) SetAge(newAge int8) {
    p.age = newAge
}

func main() {
    p1 := NewPerson("张三", 25)
    fmt.Println(p1.age) //25
    p1.SetAge(30)       //等价于(&p1).SetAge(30)
    fmt.Println(p1.age) //30
}

值类型的接收者

当方法做用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中能够获取接收者的成员值,但修改操做只是针对副本,没法修改接收者变量自己。

func (p Person) setAge2(newAge int8) {
    p.age = newAge
}

func main () {
    p1 := NewPerson("张三", 30)
    p1.Study()
    fmt.Println(p1.age)    //30
    p1.SetAge2(33)
    fmt.Println(p1.age)    //30
}

注意:在如下状况推荐使用指针类型接收者

  • 须要修改接收者中的值
  • 接收者是拷贝代价比较大的大对象
  • 保证一致性,若是有某个方法使用了指针接收者,那么其余的方法也应该使用指针接收者。

任意类型添加方法

在Go语言中,接收者的类型能够是任何类型,不只仅是结构体,任何类型均可以拥有方法。 举个例子,咱们基于内置的int类型使用type关键字能够定义新的自定义类型,而后为咱们的自定义类型添加方法。

type Myint int

func (m Myint) SayHello() {
    fmt.Println("Hello")
}

func main () {
    var m1 Myint
    m1.SayHello()
    m1 = 100
    fmt.Printf("%#v %T\n"k, m1, m1)    // 100 main.Myint
}

注意事项:非本地类型不能定义方法,也就是说咱们不能给别的包的类型定义方法。

结构体的匿名字段

结构体容许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。

type Person struct {
    string
    int
}

func main () {
    p1 := Person{
        "张三",
        18
    }
    fmt.Printf("%#v\n", p1)    // main.Person{string:"张三", int:18}
}

嵌套结构体

一个结构体中能够嵌套包含另外一个结构体或结构体指针。

type Address struct {
    Province string
    City string
}

type User struct {
    Name string
    Gender string
    Address Address
}

func main () {
    user1 := User{
        Name: "张三",
        Gender: "男",
        Address: Address{
            Province: "山西",
            City: "太原",
        },
    }
}

嵌套匿名结构体

// Address 地址结构体
type Address struct {
    Province string
    City     string
}

// User 用户结构体
type User struct {
    Name    string
    Gender  string
    Address             //匿名结构体
}

func main() {
    var user2 User
    user2.Name = "小王子"
    user2.Gender = "男"
    user2.Address.Province = "山东"    //经过匿名结构体.字段名访问
    user2.City = "威海"                //直接访问匿名结构体的字段名
    fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"小王子", Gender:"男", Address:main.Address{Province:"山东", City:"威海"}}
}

注意:当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。

嵌套结构体的字段名冲突

嵌套结构体内部可能存在相同的字段名。这个时候为了不歧义须要指定具体的内嵌结构体的字段。

//Address 地址结构体
type Address struct {
    Province   string
    City       string
    CreateTime string
}

//Email 邮箱结构体
type Email struct {
    Account    string
    CreateTime string
}

//User 用户结构体
type User struct {
    Name   string
    Gender string
    Address
    Email
}

func main() {
    var user3 User
    user3.Name = "沙河娜扎"
    user3.Gender = "男"
    // user3.CreateTime = "2019" //ambiguous selector user3.CreateTime
    user3.Address.CreateTime = "2000" //指定Address结构体中的CreateTime
    user3.Email.CreateTime = "2000"   //指定Email结构体中的CreateTime
}

结构体的“继承”

Go语言中使用结构体也能够实现其余变成语言中面向对象的继承。

type Animal struct {
    name string
}

func (a *Animal) move() {
    fmt.Printf("%s会动!\n", a.name)
}

type Dog struct {
    Feet int8
    *Animal        // 经过嵌套匿名结构体实现继承
}

func (d *Dog) bark() {
    fmt.Printf("%s会汪汪汪\n", d.name)
}

func main () {
    d1 := &Dog{
        Feet: 4,
        Animal: &Animal{
            name: "仉仉",
        },
    }

    d1.bark() // 仉仉会汪汪汪
    d1.move()    // 仉仉会动!
}

结构体字段的可见性

结构体中字段大写开头表示可公开访问,小写表示私有(仅在定义当前结构体的包中可访问)。

结构体于Json序列化

可使用json.Marshal()结构体序列化为json字符串

//Student 学生
type Student struct {
    ID     int
    Gender string
    Name   string
}

//Class 班级
type Class struct {
    Title    string
    Students []*Student
}

func main() {
    c := &Class{
        Title:    "101",
        Students: make([]*Student, 0, 200),
    }
    for i := 0; i < 10; i++ {
        stu := &Student{
            Name:   fmt.Sprintf("stu%02d", i),
            Gender: "男",
            ID:     i,
        }
        c.Students = append(c.Students, stu)
    }
    //JSON序列化:结构体-->JSON格式的字符串
    data, err := json.Marshal(c)
    if err != nil {
        fmt.Println("json marshal failed")
        return
    }
    fmt.Printf("json:%s\n", data)
    //JSON反序列化:JSON格式的字符串-->结构体
    str := `{"Title":"101","Students":[{"ID":0,"Gender":"男","Name":"stu00"},{"ID":1,"Gender":"男","Name":"stu01"},{"ID":2,"Gender":"男","Name":"stu02"},{"ID":3,"Gender":"男","Name":"stu03"},{"ID":4,"Gender":"男","Name":"stu04"},{"ID":5,"Gender":"男","Name":"stu05"},{"ID":6,"Gender":"男","Name":"stu06"},{"ID":7,"Gender":"男","Name":"stu07"},{"ID":8,"Gender":"男","Name":"stu08"},{"ID":9,"Gender":"男","Name":"stu09"}]}`
    c1 := &Class{}
    err = json.Unmarshal([]byte(str), c1)
    if err != nil {
        fmt.Println("json unmarshal failed!")
        return
    }
    fmt.Printf("%#v\n", c1)
}

结构体tag

tag是结构体的元信息,能够在运行的时候经过反射的机制读取出来。 例如定义json序列化时的tag:

//Student 学生
type Student struct {
    ID     int    `json:"id"` //经过指定tag实现json序列化该字段时的key
    Gender string //json序列化是默认使用字段名做为key
    name   string //私有不能被json包访问
}

func main() {
    s1 := Student{
        ID:     1,
        Gender: "男",
        name:   "沙河娜扎",
    }
    data, err := json.Marshal(s1)
    if err != nil {
        fmt.Println("json marshal failed!")
        return
    }
    fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"男"}
}
相关文章
相关标签/搜索