zoukankan      html  css  js  c++  java
  • Golang数据类型之结构体-下篇

    本文是Golang数据类型之结构体-上篇的续篇内容

    1、结构体指针

    1.1 声明

    和其他基础数据类型一样,也可声明结构体指针变量,此时变量被初始化为nil

    func TestMain4(t *testing.T)  {
    	var person *Person
    	fmt.Println(person)  // <nil>
    }
    

    1.2 声明并初始化

    声明并初始化指针对象

    // 先声明再初始化
    //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)  // 0xc0000ce080
    

    1.3 通过new函数创建指针对象

    Go中常定义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
    ", me8, me8)
    

    1.4 传递结构体指针

    将一个结构体的指针传递给函数,能否修改到该结构体

    结果是可以修改该实例对象

    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
    }
    

    1.5 结构体值与结构体指针

    什么是值? 什么是指针?

    下面三种方式都可以构造Car struct的实例

    c1 := Car{}
    c2 := &Car{}
    c3 := new(Car)
    fmt.Println(c1, c2, c3) // {  } &{  } &{  }
    

    c1c2c3都是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.namep2.name都能访问对应实例的属性,只是指针的访问写法是一种简写(正确写法由编译器补充)

    1.6 传值还是传递指针

    前面文章Golang函数参数的值传递和引用传递说的也是这个话题

    即什么时候传值,什么时候传递指针?

    • 传递值: 不希望实例被外部修改的时候,传值就相当于copy了一份副本给函数
    • 传递指针: 希望外部能修改到这个实例本身的时候,就需要传递该实例的指针,就是把该实例的内存地址告诉对方,可以通过地址直接找到本体

    但是经常看到函数接收的结构体参数都是指针是为什么

    因为复制传值时,如果函数的参数是一个struct对象,将直接复制整个数据结构的副本传递给函数,这有两个问题

    • 函数内部无法修改传递给函数的原始数据结构,它修改的只是原始数据结构拷贝后的副本
    • 如果传递的原始数据结构很大,完整地复制出一个副本开销并不小

    所以为了节省开销一般都会选择传递指针

    2、匿名结构体

    在定义变量时将类型指定为结构体的结构,此时叫匿名结构体。匿名结构体常用于初始化一次结构体变量的场景,例如项目配置

    package main
    
    import "fmt"
    
    func main() {
    	var me struct {
    		ID   int
    		Name string
    	}
    
    	fmt.Printf("%T
    ", me)  // struct { ID int; Name string }
    	fmt.Printf("%#v
    ", me)  // struct { ID int; Name string }{ID:0, Name:""}
    	fmt.Println(me.ID)  // 0
    	me.Name = "geek"
    	fmt.Printf("%#v
    ", me)  // struct { ID int; Name string }{ID:0, Name:"geek"}
    
    	me2 := struct {
    		ID   int
    		Name string
    	}{1, "geek"}
    
    	fmt.Printf("%#v
    ", me2)  // struct { ID int; Name string }{ID:1, Name:"geek"}
    }
    

    3、结构体方法

    可以为结构体定义属于自己的函数

    在声明函数时,声明属于结构体的函数,方法与结构体绑定,只能通过结构体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
    }
    

    4、结构体嵌套

    4.1 匿名嵌套

    简单来说,就是将数据结构直接放进去,放进去的时候不进行命名

    在定义变量时将类型指定为结构体的结构,此时叫匿名结构体。匿名结构体常用于初始化一次结构体变量的场景,例如项目配置

    匿名结构体可以组合不同类型的数据,使得处理数据变得更为灵活。尤其是在一些需要将多个变量、类型数据组合应用的场景,匿名结构体是一个不错的选择

    // 访问方式 结构体.成员名
    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
    }
    

    4.2 命名嵌套

    结构体命名嵌入是指结构体中的属性对应的类型也是结构体

    给嵌入的结构体一个名字,让其成为另一个结构体的属性

    适用于复合数据结构<嵌入匿名>

    嵌套定义

    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]
    }
    

    4.3 指针类型结构体嵌套

    结构体嵌套(命名&匿名)类型也可以为结构体指针

    声明&初始化&操作

    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{},
    	}
    }
    

    使用属性为指针类型底层共享数据结构,当底层数据发生变化,所有引用都会发生影响
    使用属性为值类型,则在复制时发生拷贝,两者不相互影响

    4.4 结构体嵌套的实际意义

    • 例如大项目对应复杂的配置文件,将公共的字段抽取出来,放到一个公共common的结构体
    • cmdb、资产系统等类型设计

    示例

    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 // 代表数据库是哪一种
    }
    

    5、通过函数创建结构体对象

    除了通过直接赋值创建结构体对象,还可以通过函数来创建,也就是把创建结构体对象的过程进行封装

    即“工厂函数”

    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
    ", me.Addr)
    	fmt.Printf("%#v
    ", me2.Addr)
    
    	hh := NewUser(2, "hh", "北京市", "海淀路", "0001")
    	fmt.Printf("%#v
    ", hh)
    }
    

    6、结构体的可见性

    结构体对外是否可见,在go中受其首字母是否大写控制,结论是

    结构体首字母大写则包外可见(公开的),否者仅包内可访问(内部的)
    结构体属性名首字母大写包外可见(公开的),否者仅包内可访问(内部的)

    组合起来的可能情况:

    • 结构体名首字母大写,属性名大写:结构体可在包外使用,且访问其大写的属性名
    • 结构体名首字母大写,属性名小写:结构体可在包外使用,且不能访问其小写的属性名
    • 结构体名首字母小写,属性名大写:结构体只能在包内使用,属性访问在结构体嵌入时由被嵌入结构体(外层)决定,被嵌入结构体名首字母大写时属性名包外可见,否者只能
      在包内使用
    • 结构体名首字母小写,属性名小写:结构体只能在包内使用
    • 结构体成员变量在同包内小写也是可以访问到的

    总结:

    • 跨包访问:全局变量、结构体本身、结构体成员变量、必须要首字母大写才可以暴露出来被访问到(在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 ~

  • 相关阅读:
    实验一 GIT 代码版本管理
    DS博客作业05--查找
    DS博客作业04--图
    DS博客作业03--树
    DS博客作业02--栈和队列
    DS博客作业01-线性表
    C博客作业05--2019-指针
    C语言博客作业04--数组
    C语言博客作业03--函数
    python exp4 jieba+wordcloud
  • 原文地址:https://www.cnblogs.com/ssgeek/p/15187552.html
Copyright © 2011-2022 走看看