golang判断结构体为空的问题

目录
  • golang结构体怎么判断是否为空
  • golang 空接口 空结构体
    • 空接口
      • 空接口内存分配
      • 空接口的应用
    • 空结构体
      • 特点
      • 原理探究
      • 使用场景
  • 总结

golang结构体怎么判断是否为空

golang结构体怎么判断为空?就是判断是否已经初始化

方法如下:

可以使用if objectA== (structname{}){ // your code },进行判断。

示例代码如下:

package main

import (
    "fmt"
    "reflect"
)

type A struct{
    name string
    age int
}

func (a A) IsEmpty() bool {
    return reflect.DeepEqual(a, A{})
}

func main() {
    var a A
    if a == (A{}) {  // 括号不能去
        fmt.Println("a == A{} empty")
    }

    if a.IsEmpty() {
        fmt.Println("reflect deep is empty")
    }
}

golang 空接口 空结构体

空接口

  • 空接口是接口类型的特殊形式。空接口没有任何方法,因此任何类型都无须实现空接口。从实现的角度来看,任何值都满足这个接口的需求。因此空接口类型可以保存任何值,也可以从空接口中取出原值。
  • 空接口类型类似于 C# 或 Java 语言中的 Object、C语言中的 void*、C++ 中的 std::any。在泛型和模板出现前,空接口是一种非常灵活的数据抽象保存和使用的方法

空接口的内部实现保存了对象的类型和指针。使用空接口保存一个数据的过程会比直接用数据对应类型的变量保存稍慢。因此在开发中,应在需要的地方使用空接口,而不是在所有地方使用空接口。

空接口类型的变量可以存储任意类型的变量。即使是接收指针类型也用 interface{},而不是使用 *interface{}。

永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。

package main
 
import "fmt"
 
func main() {
    // 定义一个空接口x
    var x interface{}
    s := "pprof.cn"
    x = s
    fmt.Printf("type:%T value:%v\n", x, x)
    i := 100
    x = i
    fmt.Printf("type:%T value:%v\n", x, x)
    b := true
    x = b
    fmt.Printf("type:%T value:%v\n", x, x)
}

空接口内存分配

​Go 1.15 中 var i interface{} = a 会有额外堆内存分配吗?

var a  int = 3
// 以下有额外内存分配吗?
var i interface{} = a

​Go 1.15在 runtime 部分中提到了一个有趣的改进:将小整数转换为接口值不再需要进行内存分配。小整数是指 0 到 255 之间的数。

空接口的应用

1.空接口作为函数的参数

使用空接口实现可以接收任意类型的函数参数。

// 空接口作为函数参数
func show(a interface{}) {
    fmt.Printf("type:%T value:%v\n", a, a)
}
 
//函数的参数个数和每个参数的类型都不是固定的
func myfunc(args ...interface{}) {
}

2.空接口作为map,数组,切片的各种类型的值

func main() {
    // 空接口作为map值
    var studentInfo = make(map[string]interface{})
    studentInfo["name"] = "李白"     //string
    studentInfo["age"] = 18        //int
    studentInfo["height"] = 1.82   //float
    studentInfo["married"] = false //bool
    fmt.Println(studentInfo)
 
    var a = new([3]interface{})
    a[0] = "Hello,World"
    a[1] = 32
    for _, b := range a {
        fmt.Printf("%v\t%[1]T\n", b)
    }
}

3.类型断言

一个接口的值(简称接口值)是由一个具体类型和具体类型的值两部分组成的。这两部分分别称为接口的动态类型和动态值。

func main() {
    var x interface{}
    x = "pprof.cn"
    v, ok := x.(string)
    if ok {
        fmt.Println(v)
    } else {
        fmt.Println("类型断言失败")
    }
}

4.类型判断

func justifyType(any interface{}) {
    switch v := any.(type) {
    case string:
        fmt.Printf("any is a string,value is: %v\n", v)
    case int:
        fmt.Printf("any is a int is: %v\n", v)
    case bool:
        fmt.Printf("any is a bool is: %v\n", v)
    case float32, float64:
        fmt.Printf("any is a float is: %v\n", v)
    default:
        fmt.Printf("unsupport type:%T is: %v\n", v, v)
    }
}
 
func main() {
    var x interface{}
    x = "pprof.cn"
    justifyType(x)
    x = 0.1
    justifyType(x)
}

因为空接口可以存储任意类型值的特点,所以空接口在Go语言中的使用十分广泛。

空结构体

我们说不包含任何字段的结构体叫做空结构体,可以通过如下的方式定义空结构体:

type empty struct{}

特点

地址相同

我们分别定义两个非空结构体和空结构体变量,然后取地址打印,发现空结构体变量的地址是相同的:

// 定义一个非空结构体
type User struct {
    name string
}
 
func main() {
    // 两个非空结构体的变量地址不同
    var user1 User
    var user2 User
    fmt.Printf("%p \n", &user1) // 0xc000318670
    fmt.Printf("%p \n", &user2) // 0xc000318680
 
    // 定义两个空结构体,地址相同
    var first struct{}
    var second struct{}
    fmt.Printf("%p \n", &first)  // 0x1ca15f0
    fmt.Printf("%p \n", &second) // 0x1ca15f0
}

我们知道 Go 语言中的变量传递都是值传递,对于传参前后的变量地址应该不同,我们通过传参的方式再来试一下:

// 非空结构体
type NonEmptyUser struct {
    name string
}
 
// 空结构体
type EmptyUser struct{}
 
// 打印非空结构体参数地址
func testNonEmptyUser(user NonEmptyUser) {
    fmt.Printf("%p \n", &user)
}
 
// 打印空结构体参数地址
func testEmptyUser(user EmptyUser) {
    fmt.Printf("%p \n", &user)
}
 
func main() {
    // 两个非空结构体的变量地址不同
    var user1 NonEmptyUser
    fmt.Printf("%p \n", &user1) // 0xc0001986c0
    testNonEmptyUser(user1)     // 0xc0001986d0
 
    // 两个空结构体变量的地址相同
    var user2 EmptyUser
    fmt.Printf("%p \n", &user2) // 0x1ca25f0
    testEmptyUser(user2)        // 0x1ca25f0
}

发现对于非空结构体,传参前后的地址是不同的,但是对于空结构体变量,前后地址是一致的。

内存占用大小为0

在Go中,我们可以使用 unsafe.Sizeof 来计算一个变量占用的字节数,那么就举几个例子来看下:

type EmptyUser struct{}
 
func main() {
    var i int
    var s string
    var m []string
    var u EmptyUser
  
    fmt.Println(unsafe.Sizeof(i)) // 8
    fmt.Println(unsafe.Sizeof(s)) // 16
    fmt.Println(unsafe.Sizeof(m)) // 24
    fmt.Println(unsafe.Sizeof(u)) // 0
}

可以看到空结构体占用的内存空间大小为0,同时对于空结构体的组合,占用空间大小也为0:

// 空结构体的组合
type EmptyUser struct {
    name struct{}
    age  struct{}
}
 
func main() {
    var u EmptyUser
    fmt.Println(unsafe.Sizeof(u)) // 0
}

原理探究

为什么空结构体的地址都相同,而且大小都为0呢,我们一起来看下源码(go/src/runtime/malloc.go):

// base address for all 0-byte allocations
var zerobase uintptr
 
// 创建新的对象时,调用 mallocgc 分配内存
func newobject(typ *_type) unsafe.Pointer {
    return mallocgc(typ.size, typ, true)
}
 
func mallocgc(size uintptr, typ *_type, needzero bool) unsafe.Pointer {
    if gcphase == _GCmarktermination {
        throw("mallocgc called with gcphase == _GCmarktermination")
    }
 
    if size == 0 {
        return unsafe.Pointer(&zerobase)
    }
    ......
}

通过源码可以看出,创建新的对象时,需要调用 malloc.newobject() 进行内存分配,进一步调用 mallocgc 方法,在该方法中,如果判断类型的size==0 ,固定返回zerobase的地址。

zerobase是一个uintptr 全局变量,占用 8 个字节。因此我们可以确定的是,在Go语言中,所有针对 size==0 的内存分配,用的都是同一个地址 &zerobase,所以我们在一开始看到的所有空结构体地址都相同。

使用场景

空结构体不包含任何数据,那么其应用场景也应该不在乎值内容,只当做一个占位符。在这种场景下,由于其不占用内存空间,使用空结构体既可以做到节省空间,又可以提供语义支持。

集合(Set)

使用过 Java 的同学应该都用过 Set 类型,Set 是保存不重复元素的集合,但是 Go 语言没有提供原生的 Set 类型。

但是我们知道 Map 结构存储的是 key-value 类型,key 不允许重复,因此可以利用 Map 来实现 Set,key存储需要的数据,value 给个固定值就可以了。

那么 value 给什么值好呢?这时候我们的 空结构体 就可以出场了,不占用空间,还可以完成占位操作,堪称完美,下面我们看怎么实现吧。

比如使用 map 表示集合时,只关注 key,value 可以使用 struct{} 作为占位符。如果使用其他类型作为占位符,例如 int,bool,不仅浪费了内存,而且容易引起歧义。

// 定义了一个保存 string 类型的 Set集合
type Set map[string]struct{}
 
// 添加一个元素
func (s Set) Add(key string) {
    s[key] = struct{}{} //第2个{}表示赋值
}
 
// 移除一个元素
func (s Set) Remove(key string) {
    delete(s, key)
}
 
// 是否包含一个元素
func (s Set) Contains(key string) bool {
    _, ok := s[key]
    return ok
}
 
// 初始化
func NewSet() Set {
    s := make(Set)
    return s
}
 
// 测试使用
func main() {
    set := NewSet()
    set.Add("hello")
    set.Add("world")
    fmt.Println(set.Contains("hello"))
 
    set.Remove("hello")
    fmt.Println(set.Contains("hello"))
}
 
func min(a int, b uint) {
    var min = 0
    if a < 0 {
        min = a
    } else {
        min = copy(make([]struct{}, a), make([]struct{}, b))
    }
    fmt.Printf("The min of %d and %d is %d\n", a, b, min)
}

channel中信号传输

空结构体 与 channel 可谓是一个经典组合,有时候我们只是需要一个信号来控制程序的运行逻辑,并不在意其内容如何。

在下面的例子中,我们定义了两个 channel 用于接收两个任务完成的信号,当接收到任务完成的信号时,就会触发相应的动作。

func doTask1(ch chan struct{}) {
    time.Sleep(time.Second)
    fmt.Println("do task1")
    ch <- struct{}{}
}
 
func doTask2(ch chan struct{}) {
    time.Sleep(time.Second * 2)
    fmt.Println("do task2")
    ch <- struct{}{}
}
 
func main() {
    ch1 := make(chan struct{})
    ch2 := make(chan struct{})
    go doTask1(ch1)
    go doTask2(ch2)
 
    for {
        select {
        case <-ch1:
            fmt.Println("task1 done")
        case <-ch2:
            fmt.Println("task2 done")
        case <-time.After(time.Second * 5):
            fmt.Println("after 5 seconds")
            return
        }
    }
}

本篇文章,我们学习了如下内容:

  • 空结构体是一种特殊的结构体,不包含任何元素
  • 空结构体的大小都为0
  • 空结构体的地址都相同
  • 由于空结构体不占用空间,从节省内存的角度出发,适用于实现Set结构、在 channel 中传输信号等

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Golang中结构体映射mapstructure库深入详解

    目录 mapstructure库 字段标签 内嵌结构 未映射字段 Metadata 弱类型输入 逆向转换 解码器 示例 在数据传递时,需要先编解码:常用的方式是JSON编解码(参见<golang之JSON处理>).但有时却需要读取部分字段后,才能知道具体类型,此时就可借助mapstructure库了. mapstructure库 mapstructure可方便地实现map[string]interface{}与struct间的转换:使用前,需要先导入库: go get github.com/m

  • golang修改结构体中的切片值方法

    golang修改结构体中的切片值,直接传结构体地址就可以 package main import "fmt" type rspInfo struct { KeyWords string `json:"key_words"` Value []string `json:"value"` } func setSlice(te *[]string){ str := "12" *te = append(*te,str) } //结构提传

  • golang中结构体嵌套接口的实现

    在golang中结构体A嵌套另一个结构体B见的很多,可以扩展A的能力. A不仅拥有了B的属性,还拥有了B的方法,这里面还有一个字段提升的概念. 示例: package main import "fmt" type Worker struct {     Name string     Age int     Salary } func (w Worker) fun1() {     fmt.Println("Worker fun1") } type Salary s

  • Golang自定义结构体转map的操作

    在Golang中,如何将一个结构体转成map? 本文介绍两种方法.第一种是是使用json包解析解码编码.第二种是使用反射,使用反射的效率比较高,代码在这里.如果觉得代码有用,可以给我的代码仓库一个star. 假设有下面的一个结构体 func newUser() User { name := "user" MyGithub := GithubPage{ URL: "https://github.com/liangyaopei", Star: 1, } NoDive :

  • golang gorm 结构体的表字段缺省值设置方式

    我就废话不多说了,大家还是直接看代码吧~ type Animal struct { ID int64 Name string `gorm:"default:'galeone'"` Age int64 } 把 name 设置上缺省值 galeone 了. 补充:Golang 巧用构造函数设置结构体的默认值 看代码吧~ package main import "fmt" type s1 struct { ID string s2 s2 s3 s3 } type s2 s

  • 浅谈golang结构体偷懒初始化

    运行一段程序,警告: service/mysqlconfig.go:63::error: golang.guazi-corp.com/tools/ksql-runner/model.CreatingMysqlMongodbRecord composite literal uses unkeyed fields (vet) 其中,composite literal uses unkeyed fields这个警告找了很久原因,最终发现是结构体初始化的问题,自己埋雷. 例如,结构体定义如下, type

  • Go语言空结构体详解

    目录 前言 什么是空结构体 特点 地址相同 大小为0 原理探究 使用场景 集合(Set) channel中信号传输 总结 前言 在使用 Go 语言开发过程中,我们不免会定义结构体,但是我们定义的结构体都是有字段的,基本不会定义不包含字段的 空结构体.你可能会反过来问,没有字段的空结构体有什么用呢?那么我们这篇文章就来研究下空结构体吧! 注:本文基于go 1.14.4 分析 什么是空结构体 我们说不包含任何字段的结构体叫做空结构体,可以通过如下的方式定义空结构体: 原生定义 var a struc

  • golang 结构体初始化时赋值格式介绍

    golang在给结构体赋值初始值时,用:分割k,v值 x := ItemLog{ Id: GetUuid(), ItemId: u.Id, UsrId: "123", Name: u.Name, Price: u.Price, Desc: u.Desc, Status: u.Status, DevArea: u.DevArea, } 补充:golang 结构体作为map的元素时,不能够直接赋值给结构体的某个字段 引入: 错误 Reports assignments directly t

  • Golang 利用反射对结构体优雅排序的操作方法

    最近开始实习,工作技术栈主要Python和Golang,目前的任务把Python模块重构为GO模块,然后出现了一个问题,就是要将一个结构体按结构体中各个字段进行排序,然后写入Redis,对于Pyhon来说for循环就能解决,但是对于Go语言来说,每一次排序都要写一个比较函数,写出来的代码太丑,非常长,代码结构是一致,只是比较字段不一样而已,个人无法接受啊,网上搜索也没搜索到合适解决方法,所以自己想了一个解决方法来优雅排序. 比较函数: func reflectCmp(i, j interface

  • golang中使用匿名结构体的方法

    目录 转化为map 定义具名结构体 定义匿名结构体 在一些项目中, 我们会使用json 来将字符串转为结构体,但是很多时候,这种结构体只会用一次,基本上只会用于反序列化, 对于这种只用到一次的结构体, 我们可以使用匿名结构体. 在gin 接收参数的时候会非常有用, 如我们将要接收的json 参数为 { "opt": "left", "phoneinfo": [ {"device_id": 64, "sn":

随机推荐