Go语言指针、映射和结构体

目录指针映射结构体指针指针地址和指针类型每个变量在运行时都拥有一个地址,这个地址代表变量在内存中的位置。Go语言中使用&字符放在变量前面对变量进行“取地址”操作。Go语言中的值类型(int、float、bool、string、array、struct)都有对应的指针类型,如:*i

目录


指针

指针地址和指针类型

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

取变量指针的语法如下:

ptr := &v // v的类型为T

其中:

v:代表被取地址的变量,类型为T ptr:用于接收地址的变量,ptr的类型就为T,称做T的指针类型。代表指针。

func main() {
  a := 10
  b := &a
  fmt.Printf("a:%d ptr:%p\n", a, &a) // a:10 ptr:0xc00001a078
  fmt.Printf("b:%p type:%T\n", b, b) // b:0xc00001a078 type:*int
  fmt.Println(&b)                    // 0xc00000e018
}

指针取值

在对普通变量使用&操作符取地址后会获得这个变量的指针,然后可以对指针使用*操作,也就是指针取值,

func main() {
    //指针取值
    a := 10
    b := &a // 取变量a的地址,将指针保存到b中
    fmt.Printf("type of b:%T\n", b)
    c := *b // 指针取值(根据指针去内存取值)
    fmt.Printf("type of c:%T\n", c)
    fmt.Printf("value of c:%v\n", c)
}

// 指针传值示例:
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
}

总结: 取地址操作符&和取值操作符是一对互补操作符,&取出地址,根据地址取出地址指向的值。

变量、指针地址、指针变量、取地址、取值的相互关系和特性如下:\

  1. 对变量进行取地址(&)操作,可以获得这个变量的指针变量。
  2. 指针变量的值是指针地址。
  3. 对指针变量进行取值(*)操作,可以获得指针变量指向的原变量的值。

空指针

  • 当一个指针被定义后没有分配到任何变量时,它的值为 nil
  • 空指针的判断
package main

import "fmt"

func main() {
    var p *string
    fmt.Println(p)
    fmt.Printf("p的值是%v\n", p)
    if p != nil {
        fmt.Println("非空")
    } else {
        fmt.Println("空值")
    }
}

new和make

Go语言中new和make是内建的两个函数,主要用来分配内存

func main() {
  var a *int
  *a = 100
  fmt.Println(*a)

  var b map[string]int
  b["测试"] = 100
  fmt.Println(b)
}

new是一个内置的函数,它的函数签名如下:

func new(Type) *Type

其中,

  1. Type表示类型,new函数只接受一个参数,这个参数是一个类型
  2. *Type表示类型指针,new函数返回一个指向该类型内存地址的指针。

new函数不太常用,使用new函数得到的是一个类型的指针,并且该指针对应的值为该类型的零值

func main() {
  a := new(int)
  b := new(bool)
  fmt.Printf("%T\n", a) // *int
  fmt.Printf("%T\n", b) // *bool
  fmt.Println(*a)       // 0
  fmt.Println(*b)       // false
} 

make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。make函数的函数签名如下:

func make(t Type, size ...IntegerType) Type

make函数是无可替代的,我们在使用slice、map以及channel的时候,都需要使用make进行初始化,然后才可以对它们进行操作。这个我们在上一章中都有说明,关于channel我们会在后续的章节详细说明。

本节开始的示例中var b map[string]int只是声明变量b是一个map类型的变量,需要像下面的示例代码一样使用make函数进行初始化操作之后,才能对其进行键值对赋值:

func main() {
  var b map[string]int
  b = make(map[string]int, 10)
  b["测试"] = 100
  fmt.Println(b)
} 

new与make的区别

  1. 二者都是用来做内存分配的。
  2. make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
  3. 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。

结构体

类型别名和自定义类型

//将MyInt定义为int类型
type MyInt int 

type TypeAlias = Type 
type byte = uint8
type rune = int32

结构体的定义和实例化

type person struct {
  name string
  city string
  age  int8
} 
func main() {
  var p1 person
  p1.name = "pprof.cn"
  p1.city = "北京"
  p1.age = 18
  fmt.Printf("p1=%v\n", p1)  //p1={pprof.cn 北京 18}
  fmt.Printf("p1=%#v\n", p1) //p1=main.person{name:"pprof.cn", city:"北京", age:18}

  // 匿名结构体
  var user struct{Name string; Age int}
  user.Name = "pprof.cn"
  user.Age = 18
  fmt.Printf("%#v\n", user)

  // 创建指针类型结构体
  var p2 = new(person)
  p2.name = "测试"
  p2.age = 18
  p2.city = "北京"
  fmt.Printf("p2=%#v\n", p2) //p2=&main.person{name:"测试", city:"北京", age:18} 

  // 取结构体的地址实例化
  p3 := &person{}
  fmt.Printf("%T\n", p3)     //*main.person
  fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"", city:"", age:0}
  p3.name = "博客"
  p3.age = 30
  p3.city = "成都"
  fmt.Printf("p3=%#v\n", p3) //p3=&main.person{name:"博客", city:"成都", age:30}

  // 结构体初始化
  var p4 person
  fmt.Printf("p4=%#v\n", p4) //p4=main.person{name:"", city:"", age:0}

  // 使用键值对初始化
  p5 := person{
    name: "pprof.cn",
    city: "北京",
    age:  18,
  }
  fmt.Printf("p5=%#v\n", p5) //p5=main.person{name:"pprof.cn", city:"北京", age:18}

  // 也可以对结构体指针进行键值对初始化
  p6 := &person{
    name: "pprof.cn",
    city: "北京",
    age:  18,
  }
  fmt.Printf("p6=%#v\n", p6) //p6=&main.person{name:"pprof.cn", city:"北京", age:18} 

  // 使用值的列表初始化
  p8 := &person{
    "pprof.cn",
    "北京",
    18,
  }
  fmt.Printf("p8=%#v\n", p8) //p8=&main.person{name:"pprof.cn", city:"北京", age:18}
} 

结构体内存布局

type test struct {
  a int8
  b int8
  c int8
  d int8
}
n := test{
  1, 2, 3, 4,
}
fmt.Printf("n.a %p\n", &n.a)
fmt.Printf("n.b %p\n", &n.b)
fmt.Printf("n.c %p\n", &n.c)
fmt.Printf("n.d %p\n", &n.d) 

构造函数

func newPerson(name, city string, age int8) *person {
  return &person{
    name: name,
    city: city,
    age:  age,
  }
}
p9 := newPerson("pprof.cn", "测试", 90)
fmt.Printf("%#v\n", p9) 

方法和接收者

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

方法的定义格式如下:

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

其中,

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

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

//Dream Person做梦的方法 func (p Person) Dream() { fmt.Printf("%s的梦想是学好Go语言!\n", p.name) }

func main() { p1 := NewPerson("测试", 25) p1.Dream() }


### 指针类型的接收者

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为Person添加一个SetAge方法,来修改实例变量的年龄。
```go
// SetAge 设置p的年龄
// 使用指针接收者
func (p *Person) SetAge(newAge int8) {
    p.age = newAge
}
func main() {
  p1 := NewPerson("测试", 25)
  fmt.Println(p1.age) // 25
  p1.SetAge(30)
  fmt.Println(p1.age) // 30
} 

值类型的接收者

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

// SetAge2 设置p的年龄
// 使用值接收者
func (p Person) SetAge2(newAge int8) {
  p.age = newAge
}

func main() {
  p1 := NewPerson("测试", 25)
  p1.Dream()
  fmt.Println(p1.age) // 25
  p1.SetAge2(30) // (*p1).SetAge2(30)
  fmt.Println(p1.age) // 25
} 

任意类型添加方法

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

//MyInt 将int定义为自定义MyInt类型
type MyInt int

//SayHello 为MyInt添加一个SayHello的方法
func (m MyInt) SayHello() {
  fmt.Println("Hello, 我是一个int。")
}
func main() {
  var m1 MyInt
  m1.SayHello() //Hello, 我是一个int。
  m1 = 100
  fmt.Printf("%#v  %T\n", m1, m1) //100  main.MyInt
} 

结构体的匿名字段

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

//Person 结构体Person类型
type Person struct {
  string
  int
}

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

嵌套结构体

一个结构体中可以嵌套包含另一个结构体或结构体指针。

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

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

func main() {
  user1 := User{
    Name:   "pprof",
    Gender: "女",
    Address: Address{
        Province: "黑龙江",
        City:     "哈尔滨",
    },
  }
  fmt.Printf("user1=%#v\n", user1)//user1=main.User{Name:"pprof", Gender:"女", Address:main.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 = "pprof"
    user2.Gender = "女"
    user2.Address.Province = "黑龙江"    //通过匿名结构体.字段名访问
    user2.City = "哈尔滨"                //直接访问匿名结构体的字段名
    fmt.Printf("user2=%#v\n", user2) //user2=main.User{Name:"pprof", Gender:"女", Address:main.Address{Province:"黑龙江", City:"哈尔滨"}}
} 

结构体的“继承”

//Animal 动物
type Animal struct {
    name string
}

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

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

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

func main() {
    d1 := &Dog{
        Feet: 4,
        Animal: &Animal{ //注意嵌套的是结构体指针
            name: "乐乐",
        },
    }
    d1.wang() //乐乐会汪汪汪~
    d1.move() //乐乐会动!
}

结构体的“继承”

结构体与JSON序列化

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号””包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。

//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时,必须严格遵守键值对的规则。结构体标签的解析代码的容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取值。例如不要在key和value之间添加空格。

//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:   "pprof",
  }
  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":"女"}
} 

删除map类型的结构体

package main

import "fmt"

type student struct {
    id   int
    name string
    age  int
}

func main() {
    ce := make(map[int]student)
    ce[1] = student{1, "xiaolizi", 22}
    ce[2] = student{2, "wang", 23}
    fmt.Println(ce)
    delete(ce, 2)
    fmt.Println(ce)
}

实现map有序输出

package main

import (
    "fmt"
    "sort"
)

func main() {
    map1 := make(map[int]string, 5)
    map1[1] = "www.topgoer.com"
    map1[2] = "rpc.topgoer.com"
    map1[5] = "ceshi"
    map1[3] = "xiaohong"
    map1[4] = "xiaohuang"
    sli := []int{}
    for k, _ := range map1 {
        sli = append(sli, k)
    }
    sort.Ints(sli)
    for i := 0; i < len(map1); i++ {
        fmt.Println(map1[sli[i]])
    }
}
点赞 0
收藏 0
分享

0 条评论

请先 登录 后评论
天涯学馆
天涯学馆
0x9d6d...50d5
资深大厂程序员,12年开发经验,致力于探索前沿技术!