golang指针和结构体

时间:2024-02-22 11:13:55

指针

image-20240220214720736
指针操作
  • 指针包括指针地址、指针类型和指针取值

  • &: &符号放在变量前面进行取地址操作

  • **:*放在变量前面根据地址进行取值

指针地址:
func main() {

	var a int = 1
	// a的值是1--类型是int--,地址是0xc0000120c0,&是地址符号
	fmt.Printf("a的值是%v--类型是%T--,地址是%p \n", a, a, &a)

	// 指针变量:指针一种特殊的变量,存储的数据是另一个变量的地址
	// 值类型的数据都有对应的指针类型,例如*int、*int64、*string 等
	var b = &a
	// b的值是0xc0000120c0--类型是*int--,地址是0xc000056028
	// *int:int的指针类型,*代表指针
	// go中所有的变量都有自己的内存地址,指针变量也有自己的地址
	fmt.Printf("b的值是%v--类型是%T--,地址是%p \n", b, b, &b)

}

指针取值:
a := 1

	b := &a // 指针变量
	// // b的值是a的内存地址,通过*b打印该内存地址对应的值,也就是a的值
	fmt.Println(*b) // 1

	//通过内存地址改变值
	*b = 3
	fmt.Println(a) //3 
make和new

所有的引用数据类型必须要分配内存空间才能赋值使用,指针也是引用数据类型

new:

new是一个内置函数,作用是动态地分配内存,返回该指针的地址,值是对应类型指针的零值

	// new 的参数就是数据类型,得到的是一个对应的零值的指针
	var num = new(bool)
	fmt.Println(num)         // 0xc00009e012
	fmt.Printf("%T \n", num) // *bool
	fmt.Println(*num)        // false
	*num = true
	fmt.Println(*num) // true

make:

image-20240220222442955

make和new的区别:

  • make 只用于 slice、map 以及 channel 的初始化,返回的还是这三个引用类型本身

  • new 用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针

结构体

Golang 中没有“类”的概念,Golang 中的结构体和其他语言中的类有点相似。和其他面向对

象语言中的类相比,Golang 中的结构体具有更高的扩展性和灵活性。

Golang 中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全

部或部分属性时,这时候再用单一的基本数据类型就无法满足需求了,Golang 提供了一种

自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称 struct。

也就是我们可以通过 struct 来定义自己的类型

类型别名:
// 自定义类型
type myInt int


// 类型别名 通过赋值给类型取一个别名
type myFloat = float64

func main() {
	var a myInt = 1
	var b myFloat = 3.14

	//区别在于一个类型是自定义的类型,一个还是原类型
	fmt.Printf("%T \n", a) // main.myInt 

	fmt.Printf("%T", b) // float64

}
结构体定义

类型名:表示自定义结构体的名称,在同一个包内不能重复。

字段名:表示结构体字段名。结构体中的字段名必须唯一。

字段类型:表示结构体字段的具体类型。

使用 type 和 struct 关键字来定义结构体

/*
结构体首字母可以大写也可以小写
大写表示这个结构体是公有的,在其他的包里面可以使用。小写表示这个结构体是私有的,只有这个包里面才能使用
*/
// Person 类型名
type Person struct {
	// 包含的属性 和类型
	name, sex string
	age       int
}




实例化结构体
  • 只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段
  • 结构体如果定义了字段,没有赋值,则默认是类型默认值
方法一 var 关键字实例化结构体 :

结构体本身也是一种类型,可以像声明内置类型一样使用 var 关键字声明结构体类型

func main() {
	// 声明p变量,类型是Person
	// 实例化Person结构体
	var p Person
	// 结构体属性赋值
	p.name = "lq"
	fmt.Println(p) // {lq  0}

	fmt.Println(p.name)    // lq
	fmt.Printf("%T \n", p) // main.Person
	// %#v可以打印结构体的全部内容
	fmt.Printf("%#v", p) // main.Person{name:"lq", sex:"", age:0}

}
方法二 通过new实例化结构体
func main() {
	/*
		在 Golang 中支持对结构体指针直接使用.来访问结构体的成员。p.name = "李" 其 实在底层是(*p2).name = "李”
	*/
	var p = new(Person)
	p.name = "李"
	fmt.Printf("%#v  \n", p) // &main.Person{name:"李", sex:"", age:0}  指针地址

	fmt.Printf("%T \n", p) // *main.Person 结构体指针

	fmt.Printf("%#v \n", *p) // main.Person{name:"李", sex:"", age:0}

	fmt.Println(p.name) // 李

}

方法三 通过&实例化结构体
func main() {
	/*
		通过&实例化结构体,类型都和通过new实例化一样,都是指针类型
	*/

	var p = &Person{}
	p.name = "li"

}
方法四 通过键值对实例化结构体
func main() {

	var p = Person{
		name: "l",
		sex:  "1",
		age:  10, // 必须有逗号
	}

	fmt.Printf("%#v  \n", p) // main.Person{name:"l", sex:"1", age:10}

	fmt.Printf("%T \n", p) // main.Person 

}

方法五 结构体指针键值对实例化
func main() {

	var p = &Person{
		name: "l",
		sex:  "1",
		age:  10, // 必须有逗号
	}

	fmt.Printf("%#v  \n", p) // &main.Person{name:"l", sex:"1", age:10}

	fmt.Printf("%T \n", p) // *main.Person

}
方法六 不指定键值根据顺序赋值
func main() {
	// &地址or 直接声明都可以
  // 赋值顺序要和定义的key顺序一致
	var p = Person{
		"l",
		"1",
		10, // 必须有逗号
	}

	fmt.Printf("%#v  \n", p) // main.Person{name:"l", sex:"1", age:10}

	fmt.Printf("%T \n", p) // main.Person

}
结构体方法和接收者

在 go 语言中,没有类的概念但是可以给类型(结构体,自定义类型)定义方法

方法就是定义了接收者的函数,接收者的概念就类似于其他语言中的 this 或者 self

  • 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小

    写字母,例如Person 类型的接收者变量应该命名为 p

  • 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型

  • 方法名、参数列表、返回参数:格式与函数定义相同

  • 结构体是一个值类型,改变副本时不会影响原数据

结构体方法
// 结构体
type Person struct {
	name   string
	age    int
	sex    string
	height int
}

/*
语法: func (接收者变量 接收者类型) 结构体方法名(参数) 返回值 {}

p:接收者变量
Person:接收者类型
PrintInfo:自定义的结构体方法名
*/
func (p Person) PrintInfo() {
	// 通过接收者变量p 调用 类似于self、 this
	fmt.Println(p.name)
}

// 接收者类型也可以是 指针,多个实例化 值类型与引用类型的区别
func (p *Person) setInfo(name string) {
	// 通过指针修改
	p.name = name

}
func main() {
	// 实例化 设置值
	var pe = Person{
		name:   "l",
		age:    20,
		sex:    "m",
		height: 180,
	}

	var pe1 = Person{
		name:   "l",
		age:    20,
		sex:    "m",
		height: 180,
	}

	// p1的指针类型修改name
	pe1.setInfo("q")

	// 每个结构体实例都是独立的,不会互相影响
	pe.PrintInfo()  // l
	pe1.PrintInfo() // q
}

给任意类型添加方法

非本地类型不能定义方法,也就是说不能给别的包的类型定义方法

// 自定义myInt类型
type myInt int

// 结构体方法 类型是自定义类型
func (m myInt) test() {
	fmt.Println("自定义类型的自定义方法")
}
func main() {

	var a myInt = 20
	a.test() // 自定义类型的自定义方法

}

结构体匿名字段

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

匿名字段默认采用类型名作为字段名,,因此一个结构体中同种类型的匿名字段只能有一个

type Test struct {
	string // 类型不能重复
	int
}

func main() {

	var test = Test{
		"li",
		20,
	}

	fmt.Println(test) // {li 20}

}

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

  • 结构体的字段类型可以是基本数据类型、切片、map以及结构体

  • 如果结构体的字段类型是指针、slice、map,零值都是nil,还没有分配空间,如果需要使用这样的字段,需要先make,再使用

    • /* 零值字段类型初始化赋值 */ 
      
      
      type Person struct {
      	Name   string
      	Age    int
      	hobby  []string
      	Extent map[string]string
      }
      
      func main() {
      
      	var person Person
      
      	person.Name = "li"
      	person.Age = 20
      
      	// 初始化切片
      	person.hobby = make([]string, 3, 6)
      	// 切片赋值
      	person.hobby[0] = "eat"
      	person.hobby[1] = "eat"
      	person.hobby[2] = "eat"
      
      	// 初始化map
      	person.Extent = make(map[string]string)
      	// map赋值
      	person.Extent["height"] = "180"
      
      	fmt.Println(person.hobby) // [eat eat eat]
      
      
      }
      

结构体嵌套:

/*
结构体嵌套
*/

type User struct {
	Username string
	Password string
	Address  Address // 表示User结构体中嵌套Address结构体
}

type Address struct {
	Name  string
	Phone string
	City  string
}

func main() {
	// 实例化user
	var u User
	u.Username = "li"
	u.Password = "123"

	// user中声明了Address,类型是Address
	u.Address.Name = "北京"
	u.Address.Phone = "1231231"
	u.Address.City = "北京"

	fmt.Printf("%#v", u) // main.User{Username:"li", Password:"123", Address:main.Address{Name:"北京", Phone:"1231231", City:"北京"}}


}

匿名结构体:

type User struct {
	Username string
	Password string
	Address  // 匿名结构体
}

type Address struct {
	Name  string
	Phone string
	City  string
}

func main() {
	// 实例化user
	var u User
	u.Username = "li"
	u.Password = "123"

	/*
		匿名嵌套可以直接通过U.Name访问 嵌套的Address中的属性
	  当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找
	  所以如果使用该简写方式,两个结构体有相同字段,则会访问第一个
	  如果是嵌套了两个子结构体,这两个子结构体有相同字段,使用简写访问会报错,因为不知道访问哪个
	  
	
	*/
	u.Name = "北京"
	u.Phone = "1231231"
	u.Address.City = "北京"

	fmt.Printf("%#v", u) // main.User{Username:"li", Password:"123", Address:main.Address{Name:"北京", Phone:"1231231", City:"北京"}}

}
结构体继承

结构体继承是通过嵌套来实现的,可以嵌套结构体,也可以另一个结构体的指针

// Animal 父结构体
type Animal struct {
	Name string
}

// Animal结构体方法
func (a Animal) aFunc() {
	fmt.Println(a.Name)

}

// Dog 子结构体
type Dog struct {
	weight int
	Animal Animal // 通过结构体嵌套,Dog拥有Animal的属性方法,实现继承
}

// Dog 结构体方法
func (d Dog) dFunc() {
	fmt.Println(d.Animal.Name)

}

func main() {
	// 实例化结构体
	var d = Dog{
		weight: 100,
		// 实例化嵌套的结构体
		Animal: Animal{
			Name: "大黄",
		},
	}
	fmt.Printf("%#v \n", d) // main.Dog{weight:100, Animal:main.Animal{Name:"大黄"}}
	d.Animal.aFunc()        // 大黄
	d.dFunc()               // 大黄

}


结构体和json相互转换
序列化:
/* json序列化  */
import (
	"encoding/json" // 导入包
	"fmt"
)

type Student struct {
	ID     int
	Gender string
	Name   string
	height int // 小写开头是私有属性,私有属性不能被json包访问
}

func main() {

	var s1 = Student{
		ID:     0,
		Gender: "m",
		Name:   "l",
		height: 10,
	}
	fmt.Printf("%#v \n", s1) // main.Student{ID:0, Gender:"m", Name:"l", height:10}

	// 使用json.Marshal()处理结构体,两个返回值,第一个是是一个Byte数组,第二个是错误信息
	// 将结构体对象转换成字节数组
	var jonByte, _ = json.Marshal(s1)
	fmt.Println(jonByte) // [123 34 73 68 34 58 48 44 34 71 101 110 100 101 114 34 58 34 109 34 44 34 78 97 109 101 34 58 34 108 34 125]

	// 将字节数组 转换成 json字符串
	var jsonStr = string(jonByte)
	fmt.Printf("%#v \n", jsonStr) // "{\"ID\":0,\"Gender\":\"m\",\"Name\":\"l\"}"   height是私有属性,无法访问

}

反序列化:
/* json 反序列化 */

import (
	"encoding/json"
	"fmt"
)

type Student struct {
	Id     int
	Gender string
	Name   string
}

func main() {
	// json字符串
	var jsonStr string = "{\"ID\":0,\"Gender\":\"m\",\"Name\":\"l\"}"

	// 实例化Student
	var s Student
	// json.Unmarshal 将json字符串转换成结构体对象,第一个值是byte类型的切片,第二个值是要转换的结构体对象的地址,需要&
	// 如果反序列化成功,方法返回值就是nil,对应的数据直接赋值到结构体,无需变量接收
	err := json.Unmarshal([]byte(jsonStr), &s)

	// 判断是否反序列化成功
	if err == nil {
		fmt.Printf("%#v", s) // main.Student{Id:0, Gender:"m", Name:"l"}

	}
}

嵌套结构体和 JSON 序列化反序列化
import (
	"encoding/json"
	"fmt"
)

type Student struct {
	ID   int
	Name string
}

type Class struct {
	Title   string
	Student []Student // Student类型的的切片,一个班级里可以有多个学生
}

func main() {
	c := Class{
		Title: "小葵花课堂",
		// 初始化切片
		Student: make([]Student, 0),
	}

	// 向班级实例c的Student切片 添加数据
	s := Student{ID: 1, Name: "1"}
	c.Student = append(c.Student, s)
	// main.Class{Title:"小葵花课堂", Student:[]main.Student{main.Student{ID:1, Name:"1"}}}
	fmt.Printf("%#v\n", c)

	// 序列化
	jsonByte, _ := json.Marshal(c)
	jsonStr := string(jsonByte)
	// "{\"Title\":\"小葵花课堂\",\"Student\":[{\"ID\":1,\"Name\":\"1\"}]}"
	fmt.Printf("%#v\n", jsonStr)

	var cl Class
	// 反序列化
	_ = json.Unmarshal([]byte(jsonStr), &cl)
	fmt.Printf("%#v\n", cl) // main.Class{Title:"小葵花课堂", Student:[]main.Student{main.Student{ID:1, Name:"1"}}}

}

结构体标签Tag

Tag 是结构体的元信息,可以在运行的时候通过反射的机制读取出来

Tag 在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

key1:"value1" key2:"value2"

结构体 tag 由一个或多个键值对组成。键与值使用冒号分隔,值用双引号括起来。同一个结

构体字段可以设置多个键值对 tag,不同的键值对之间使用空格分隔。

注意事项: 为结构体编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码的

容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正确取

值。例如不要在 key 和 value 之间添加空格

type Student struct {
	Id     int    `json:"id"` // /通过指定 tag 实现 json 序列化后大写ID 变成小写id
	Gender string `json:"呵呵"` // 通过指定 tag 实现 json 序列后keyGender 变成呵呵
	Name   string
}

func main() {

	var student Student
	j, _ := json.Marshal(student)
	jsonStr := string(j)
	fmt.Printf("%#v", jsonStr) // "{\"id\":0,\"呵呵\":\"\",\"Name\":\"\"}"

}

image