
本文是Golang数据类型之结构体-上篇的续篇内容
和其他基础数据类型一样,也可声明结构体指针变量,此时变量被初始化为nil
func TestMain4(t *testing.T)  {
	var person *Person
	fmt.Println(person)  // <nil>
}声明并初始化指针对象
// 先声明再初始化
//var person *Person
//person = &Person{}
// 简短声明
person := new(Person)
//person := &Person{}  // *Person
fmt.Printf("%p", person)  // 0xc00013a080声明并初始化赋值
var person *Person = &Person{
    Name:          "andy",
    Age:           66,
    Gender:        "male",
    Weight:        120,
    FavoriteColor: []string{"red", "blue"},
}
fmt.Printf("%p", person)  // 0xc0000ce080Go中常定义N(n)ew+结构体名命名的函数用于创建对应的结构体值对象或指针对象
person := new(Person)
fmt.Printf("%p", person)  // 0xc00013a080
fmt.Printf("%T", person)  // *test.Person
// 定义工厂函数用于创建Author对象
func NewAuthor(id int, name, birthday, addr, tel, desc string) *User {
	return &User{id, name, birthday,addr, tel, desc}
}
// 调用
	me8 := NewAuthor(1004, "geek", "2021-06-08", "北京市", "15588888888", "备注")
	fmt.Printf("%T: %#v\n", me8, me8)将一个结构体的指针传递给函数,能否修改到该结构体
结果是可以修改该实例对象
func ChangeColor(car *Car) {
	car.Color = "blue"
	fmt.Println(car.Color)
}
func main() {
	car := Car{
		Color: "yellow",    // 黄色
		Brand: "ford",      // 福特
		Model: "Mustang",   // 野马
	}
	ChangeToW(car)
	fmt.Println(car.Color) // blue
}什么是值? 什么是指针?
下面三种方式都可以构造Car struct的实例
c1 := Car{}
c2 := &Car{}
c3 := new(Car)
fmt.Println(c1, c2, c3) // {  } &{  } &{  }c1、c2、c3都是car struct的实例,c2, c3是指向实例的指针,指针中保存的是实例的地址,所以指针再指向实例,c1则是直接指向实例。这三个变量与Car struct实例的指向关系如下
变量名      指针     数据对象(实例)
-------------------------------
c1 -------------------> { }
c2 -----> ptr(addr) --> { }
c3 -----> ptr(addr) --> { }访问实例和访问实例指针是否有区别
fmt.Println("c1, ", c1.Color)    // 访问实例的属性
fmt.Println("c2, ", (*c2).Color) // 先通过*求出 指针的值,就是实例的内存地址, 然后通过实例的内存地址访问该实例对象的属性如果我们需要访问指针对象的属性, 上面的(*c2).Color是理论上的正确写法, 可以看出过于繁琐, 而我们方法指针,往往也是想访问这个指针的实例, 所以编译帮我们做了优化, 比如访问指针实例也可以这样写
fmt.Println("c2, ", c2.Color) // 编译器自动补充上(*c2).Color, 这样写法上就简洁了简单总结:尽管一个是数据对象值,一个是指针,它们都是数据对象的实例。也就是说,p1.name和p2.name都能访问对应实例的属性,只是指针的访问写法是一种简写(正确写法由编译器补充)
前面文章Golang函数参数的值传递和引用传递说的也是这个话题
即什么时候传值,什么时候传递指针?
copy了一份副本给函数但是经常看到函数接收的结构体参数都是指针是为什么
因为复制传值时,如果函数的参数是一个struct对象,将直接复制整个数据结构的副本传递给函数,这有两个问题
所以为了节省开销一般都会选择传递指针
在定义变量时将类型指定为结构体的结构,此时叫匿名结构体。匿名结构体常用于初始化一次结构体变量的场景,例如项目配置
package main
import "fmt"
func main() {
	var me struct {
		ID   int
		Name string
	}
	fmt.Printf("%T\n", me)  // struct { ID int; Name string }
	fmt.Printf("%#v\n", me)  // struct { ID int; Name string }{ID:0, Name:""}
	fmt.Println(me.ID)  // 0
	me.Name = "geek"
	fmt.Printf("%#v\n", me)  // struct { ID int; Name string }{ID:0, Name:"geek"}
	me2 := struct {
		ID   int
		Name string
	}{1, "geek"}
	fmt.Printf("%#v\n", me2)  // struct { ID int; Name string }{ID:1, Name:"geek"}
}可以为结构体定义属于自己的函数
在声明函数时,声明属于结构体的函数,方法与结构体绑定,只能通过结构体person的实例访问,不能在外部直接访问,这就是结构体方法和函数的区别,例如
// p 是person的别名
func (p Person) add() int {
	return p.Age * 2
}调用结构体方法
func TestMain6(t *testing.T) {
	m := new(Person)
	m.Age = 18
	fmt.Println(m.add()) // 36
}简单来说,就是将数据结构直接放进去,放进去的时候不进行命名
在定义变量时将类型指定为结构体的结构,此时叫匿名结构体。匿名结构体常用于初始化一次结构体变量的场景,例如项目配置
匿名结构体可以组合不同类型的数据,使得处理数据变得更为灵活。尤其是在一些需要将多个变量、类型数据组合应用的场景,匿名结构体是一个不错的选择
// 访问方式 结构体.成员名
type Person2 struct {
	Name          string
	Age           int
	Gender        string
	Weight        uint
	FavoriteColor []string
	NewAttr       string
	Addr          Home
	NewHome
}
type NewHome struct {
	City string
}
func TestPerson2(t *testing.T) {
	m := new(Person2)
	m.Age = 18
	m.City = "beijing"
	fmt.Println(m.City)  // beijing
}嵌套过后带来的好处就是能够像访问原生属性一样访问嵌套的属性
示例
package main
 
import (
	"encoding/json"
	"fmt"
)
//定义手机屏幕
type Screen01 struct {
	Size       float64 //屏幕尺寸
	ResX, ResY int //屏幕分辨率 水平 垂直
}
//定义电池容量
type Battery struct {
	Capacity string
}
 
//返回json数据
func getJsonData() []byte {
	//tempData 接收匿名结构体(匿名结构体使得数据的结构更加灵活)
	tempData := struct {
		Screen01
		Battery
		HashTouchId bool  // 是否有指纹识别
	}{
		Screen01:    Screen01{Size: 12, ResX: 36, ResY: 36},
		Battery:     Battery{"6000毫安"},
		HashTouchId: true,
	}
	jsonData, _ := json.Marshal(tempData)  //将数据转换为json
	return jsonData
}结构体命名嵌入是指结构体中的属性对应的类型也是结构体
给嵌入的结构体一个名字,让其成为另一个结构体的属性
适用于复合数据结构<嵌入匿名>
嵌套定义
type Book struct {
    Author  struct{
        Name string
        Aage int
    }
    Title struct{
        Main string 
        Sub  string
    }
}声明和初始化
b := &Book{
    Author: struct {
        Name string
        Aage int
    }{
        Name: "xxxx",
        Aage: 11,
    },
    Title: struct {
        Main string
        Sub  string
    }{
        Main: "xxx",
        Sub:  "yyy",
    },
}
// 
b := new(Book)
b.Author.Aage = 11
b.Author.Name = "xxx"嵌入命名,在外面定义
type Author struct {
    Name string
    Aage int
}
type Title struct {
    Main string
    Sub  string    
}
type Book struct {
    Author Author
    Title Title
}示例
package main
import "fmt"
type Person struct {
	Name string
	Age  int
}
type TeacherNew struct {
	Pn        Person
	TeacherId int
}
func main() {
	t2 := TeacherNew{
		Pn: Person{
			Name: "geek",
			Age:  18,
		},
		TeacherId: 123,
	}
	fmt.Printf("[TeacherId: %v][Name: %v][Age: %v]", t2.TeacherId, t2.Pn.Name, t2.Pn.Age)
  // [TeacherId: 123][Name: geek][Age: 18]
}结构体嵌套(命名&匿名)类型也可以为结构体指针
声明&初始化&操作
type Book2 struct {
	Author *Author
	Title *Title
}
func (b *Book2) GetName() string {
	return b.Author.GetName() + "book"
}
func TestMain8(t *testing.T) {
	b1 := Book2{
		Author: &Author{
			Name: "ssgeek",
		},
		Title: &Title{},
	}
	b2 := &Book2{
		Author: &Author{},
		Title: &Title{},
	}
}使用属性为指针类型底层共享数据结构,当底层数据发生变化,所有引用都会发生影响 使用属性为值类型,则在复制时发生拷贝,两者不相互影响
common的结构体示例
package main
import "time"
// 云有云资源公共字段
type Common struct {
	ChargingMod string    // 付费模式:预付费和后付费
	Region      string    // 区域
	Az          string    // 可用区
	CreateTime  time.Time // 购买时间
}
type Ecs struct {
	Common
	guide string // 4C 16G
}
type Rds struct {
	Common
	dbType string // 代表数据库是哪一种
}除了通过直接赋值创建结构体对象,还可以通过函数来创建,也就是把创建结构体对象的过程进行封装
即“工厂函数”
package main
import "fmt"
type Address struct {
	Region string
	Street string
	No     string
}
type User struct {
	ID   int
	Name string
	Addr *Address
}
func NewUser(id int, name string, region, street, no string) *User {
	return &User{
		ID:   id,
		Name: name,
		Addr: &Address{region, street, no},
	}
}
func main() {
	me := User{
		ID:   1,
		Name: "geek",
		Addr: &Address{"上海市", "南京路", "0001"},
	}
	me2 := me
	me2.Name = "ss"
	me2.Addr.Street = "黄河路"
	fmt.Printf("%#v\n", me.Addr)
	fmt.Printf("%#v\n", me2.Addr)
	hh := NewUser(2, "hh", "北京市", "海淀路", "0001")
	fmt.Printf("%#v\n", hh)
}结构体对外是否可见,在go中受其首字母是否大写控制,结论是
结构体首字母大写则包外可见(公开的),否者仅包内可访问(内部的) 结构体属性名首字母大写包外可见(公开的),否者仅包内可访问(内部的)
组合起来的可能情况:
总结:
示例:
首先在tt包下定义一个person结构体,person大写的时候外部的包可以访问到,person小写的时候外部的包不可以访问到
package main
import (
	"fmt"
	"go-learning/chapter06/tt"
)
func main() {
	p1 := tt.Person{
		Name: "geek",
		Age:  18,
	}
	fmt.Println(p1)
	/*
	# command-line-arguments
	./last.go:9:8: cannot refer to unexported name tt.person
	 */
}See you ~