golang之struct入门

简介: golang之struct入门

起步

基础起步点此


结构体判等


只有在结构体的所有字段类型全部支持直接判等时,才可做判断操作。

map,slice不支持直接判等,需借助reflect.DeepEqual来比较(map整个是一个指针(*hmap), slice是SliceHeader的Data字段是个指针)

package main
import "fmt"
func main() {
  type data struct {
    x int
  }
  d1 := data{
    x: 100,
  }
  d2 := data{
    x: 100,
  }
  fmt.Println(d1 == d2)
}

执行结果为:true

package main
import "fmt"
func main() {
  type data struct {
    x int
    y map[string]int //字典类型不支持==,
  }
  d1 := data{
    x: 100,
  }
  d2 := data{
    x: 100,
  }
  fmt.Println(d1 == d2) //struct containing map[string]int cannot be compared
}

编译不通过:invalid operation: d1 == d2 (struct containing map[string]int cannot be compared)


空结构体


空结构struct{}是指没有字段的结构类型。

可作为通道元素类型,用于事件通知。

package main
import (
  "fmt"
  "time"
)
func main() {
  exit := make(chan struct{})
  go func() {
    for {
      fmt.Println("hello, world!")
      exit <- struct{}{}
    }
  }()
  <-exit
  time.Sleep(2e9)
  fmt.Println("end.")
}

输出为:

hello, world!
hello, world!
end.

指针操作


可使用指针直接操作(取值/赋值等)结构体字段,但不能是多级指针。

package main
import (
  "fmt"
  "reflect"
)
func main() {
  type user struct {
    name string
    age  int
  }
  p := &user{ //获取指针
    name: "fliter",
    age:  26,
  }
  fmt.Println(p) //&{fliter 26}
  p.name = "jack" //通过指针找到对应的程序实体
  p.age--
  fmt.Println(p) //&{jack 25}
  fmt.Println(*p) //{jack 25}
  p2 := &p                        //&p属于二级指针
  fmt.Println(p2)                 //0xc00000e020
  fmt.Println(reflect.TypeOf(p2)) //**main.user
  //*p2.name = "john" //p2.name undefined (type **user has no field or method name)
}

输出为:

&{fliter 26}
&{jack 25}
{jack 25}
0xc00007c010
**main.user

Struct中的tag


可以为struct中的每个字段,加一个tag。这个tag可以通过反射机制获取到,最常用场景(包括但绝不仅限于此,如还常用于格式校验,数据库关系映射等)就是 json序列化和反序列化.

package main
import (
  "fmt"
  "reflect"
)
type User struct {
  name string `姓名`
  sex  string `性别`
  age  int    `年龄`
}
func main() {
  u := User{"Messi", "男", 32}
  v := reflect.ValueOf(u)
  t := v.Type()
  for i, n := 0, t.NumField(); i < n; i++ {
    fmt.Printf("%s为: %v\n", t.Field(i).Tag, v.Field(i))
  }
}

输出为:

姓名为: Messi
性别为: 男
年龄为: 32

用于json序列化


**注意:**

struct转json时,struct里的字段名首字母必须大写,否则无法正常解析;如果想让struct转json后字段名首字母小写,可以通过tag指定


[更多可点击](https://studygolang.com/articles/13455)

package main
import (
  "encoding/json"
  "fmt"
)
type FiveAlevelArea struct {
  Name        string  `json:"name"`
  Location    string  `json:"address"`
  Price       float32 `json:"entrance ticket"`
  englishName string  `json:"english_name"`
}
func main() {
  heBei := FiveAlevelArea{
    "承德市双桥区承德避暑山庄及周围寺庙景区",
    "承德市山庄南路",
    100.00,
    "Chengde Imperial Summer Resort",
  }
  j, err := json.Marshal(heBei)
  if err != nil {
    fmt.Println("报错如下:", err)
    return
  }
  fmt.Println("json字符串为:", string(j))
}

输出为:

json字符串为: {"name":"承德市双桥区承德避暑山庄及周围寺庙景区","address":"承德市山庄南路","entrance ticket":100}

可见首字母不为大写的字段名,没有被解析出来;最后json字符串中展示出的键名,是tag标签里指定的名称


匿名字段


所谓匿名字段是指没有名字,仅有类型的字段,也称作嵌入字段嵌入类型。 从编译器角度看,这只是隐式地以类型名作为字段名称。 可直接引用匿名字段的成员,但初始化时必须当作独立字段。

匿名字段不仅仅可以是结构体,除"接口指针"和"多级指针"以外的任何命名类型都可以作为匿名字段。

严格说来,Go并不是传统意义上的面向对象编程语言,或者说仅实现了最小面向对象的机制。 匿名嵌入不是继承,无法实现多态处理。 虽然配合方法集,可用接口来显现一些类似的操作,但其本质完全不同。

package main
import "fmt"
type attr struct {
  perm int
}
type file struct {
  name string
  attr //匿名字段,仅有类型名(但IDE在格式化此字段时,不会和其他字段的类型对齐,而会和其他字段的名称对齐)
}
func main() {
  //方式1:
  var c file
  c.name = "my.txt"
  var a attr
  a.perm = 777
  c.attr = a
  fmt.Println(c, c.perm) //直接读取匿名字段成员
  //方式2:
  //如果使用这种方式给包含匿名字段的结构体赋值,须将类型名当作字段名
  f := file{
    name: "test.dat",
    attr: attr{ //将类型名当作字段名
      perm: 755,
    },
  }
  f.perm = 500           //直接设置匿名字段成员
  fmt.Println(f, f.perm) //直接读取匿名字段成员
}

利用匿名字段实现所谓的"继承"


结构体中字段可以没有名字(只有类型),即匿名字段;

如果一个struct(记为A)嵌套了另一个匿名结构体(记为B),那么A结构体可以直接访问 匿名结构体B的方法,从而实现了继承。

参见前文,golang利用组合实现继承,和php或java面向对象的继承有何不同


方法


Golang中的方法作用在特定类型的变量上,因此自定义类型都可以  有方法,而不仅仅是struct

定义:func (recevier type) methodName(参数列表)(返回值列表){}


方法和函数


golang中的"方法"与"函数"的关系,与其他语言有所差别.

方法 给用户定义的类型添加新的行为。方法和函数相比,声明时在关键字func和方法名之间增加了一个参数

package main
import "fmt"
type user struct {
  name string
  age  int
}
func (u user) say() {
  fmt.Println("hello", u.name)
  fmt.Println("十年后你的年龄是:", u.age+10)
}
func (u *user) run() {
  fmt.Printf("%s是跑步名将\n", u.name)
}
func main() {
  c := user{"Trump", 73}
  c.say()
  s := &user{"Bolt", 0}
  s.run()
}

输出:

hello Trump
十年后你的年龄是: 83
Bolt是跑步名将
  • 一个基础类型前面加&后,会变为指针类型;
  • *作动词时, 后面必须是一个指针类型,叫做解引用~

即 *(&a) = a

  • *作形容词时,表示这个类型是指针类型

方法作用在结构体上

package main
import (
  "fmt"
)
type FiveAlevelArea struct {
  Name        string  `json:"name"`
  Location    string  `json:"address"`
  Price       float32 `json:"entrance ticket"`
  englishName string  `json:"english_name"`
}
func (cs FiveAlevelArea) set(Name, Location, englishName string, Price float32) {
  cs.Name = Name
  cs.Location = Location
  cs.Price = Price
  cs.englishName = englishName
  fmt.Println("当前结构体的值为:", cs)
}
func (shuang FiveAlevelArea) get() FiveAlevelArea {
  return shuang
}
func main() {
  var heBei FiveAlevelArea
  heBei.set("保定市安新县白洋淀景区", "保定市安新县", "Bai-yang Lake", 40.00)
  heBei2 := heBei.get()
  fmt.Println(heBei)
  fmt.Println(heBei2)
}

输出为:

当前结构体的值为: {保定市安新县白洋淀景区 保定市安新县 40 Bai-yang Lake}
{  0 }
{  0 }

因为是值传递,故而原来变量的值没有被修改;

改为指针传递:将第14行set()方法作用的变量(类型)修改为cs *FiveAlevelArea,此时程序的执行结果为:

当前结构体的值为: &{保定市安新县白洋淀景区 保定市安新县 40 Bai-yang Lake}
{保定市安新县白洋淀景区 保定市安新县 40 Bai-yang Lake}
{保定市安新县白洋淀景区 保定市安新县 40 Bai-yang Lake}

可能会疑惑,第32行调用set()方法的变量类型为FiveAlevelArea,并不是指针,为何还能编译通过呢?实际上是go自动加了取指针符号&,即第32行:

等同于:

方法作用在其他变量上

package main
import "fmt"
type cInt int
func (cui cInt) f1() {
  fmt.Println("变量值为:", cui)
}
func (c *cInt) f2(args cInt) {
  *c = args + 6
}
func main() {
  var a1 cInt = 10086
  fmt.Println(a1)
  //调用cInt类型的f1()方法
  a1.f1()
  var a2 cInt
  //&a2是获取变量a2的内存地址值即指针,如果对一个参数需要接收指针(*后面必须要是指针类型)的方法如此处的f2()传值a2,go会自动将a2转换成&a2即变量a2的内存地址,所以以下两种写法都是可以的
  a2.f2(12300)
  fmt.Println(a2)
  (&a2).f2(12300)
  fmt.Println(a2)
  //指针传递可以改变原来变量的值
  var a3 cInt = 12360
  a3.f2(a3)
  fmt.Println(a3)
  (&a3).f2(a3)
  fmt.Println(a3)
}

输出为:

10086
变量值为: 10086
12306
12306
12366
12372

String()方法


如果一个变量实现了String()这个方法,那么fmt.Println在输出时,会默认调用这个变量的String()方法。

package main
import "fmt"
type Transportation struct {
  name  string
  speed string
}
func (o *Transportation) Run() {
  fmt.Printf("交通工具%s跑起来啦!\n", (*o).name) //此处"o.name"同样可以通过编译(o是一个指针,*o解引用为这个内存地址对应的变量),go会自动转化为(*o).name
}
//火车继承交通工具
type Train struct {
  Transportation
  InventedTime int //发明时间
}
func (cs *Train) String() string {
  str := fmt.Sprintf("%s的速度可以达到%s\n", cs.name, cs.speed)
  return str
}
func main() {
  var train Train
  train.name = "火车"
  train.speed = "350km/h"
  train.InventedTime = 1804
  fmt.Println(train)
  fmt.Println("------------------")
  train.Run()
  fmt.Println("******************")
  fmt.Printf("%s", &train)
  fmt.Println("##################")
  fmt.Println("会自动调用String()方法\n", &train)
}

输出为:

{{火车 350km/h} 1804}
------------------
交通工具火车跑起来啦!
******************
火车的速度可以达到350km/h
##################
会自动调用String()方法
 火车的速度可以达到350km/h
目录
相关文章
|
4月前
|
存储 Go
Golang里空结构体struct{}的介绍和使用
【2月更文挑战第10天】Golang里空结构体struct{}的介绍和使用
55 6
|
11月前
|
测试技术 Go 开发工具
100天精通Golang(基础入门篇)——第3天:Go语言的执行原理及常用命令、编码规范和常用工具
100天精通Golang(基础入门篇)——第3天:Go语言的执行原理及常用命令、编码规范和常用工具
212 1
|
8天前
|
Go
Golang语言结构体(struct)面向对象编程进阶篇(封装,继承和多态)
这篇文章是关于Go语言中结构体(struct)面向对象编程进阶篇的教程,涵盖了Go语言如何实现封装、继承和多态,以及结构体内存布局的相关概念和案例。
20 4
|
8天前
|
Go
Golang语言结构体(struct)面向对象编程基础篇
这篇文章是关于Go语言中结构体(struct)面向对象编程的基础教程,详细介绍了面向对象编程在Go语言中的应用、结构体的定义与初始化、方法定义、跨包实例化结构体以及结构体方法和普通函数的区别。
19 4
|
11天前
|
测试技术 Go 开发者
掌握Golang测试:从入门到实践
【8月更文挑战第31天】
22 0
|
3月前
|
Kubernetes Go 云计算
Golang 入门技术文档
**Golang 技术文档摘要:** Golang,由Google开发,是一种静态强类型、编译型语言,广泛应用于云计算、网络编程和分布式系统。本文档介绍了Golang的基础和特性,包括安装配置、 HelloWorld 示例、基本语法,如变量推导、函数多返回值和并发编程(goroutine、channel)。Golang的并发模型基于轻量级goroutine和channel,支持高效并发处理。此外,文档还提及了接口和多态性,展示了如何使用接口实现类型间的交互。Golang在Docker、Kubernetes等项目中得到应用,适用于后端服务开发。【6月更文挑战第9天】
32 1
|
4月前
|
Go 开发者
Golang深入浅出之-Go语言结构体(struct)入门:定义与使用
【4月更文挑战第22天】Go语言中的结构体是构建复杂数据类型的关键,允许组合多种字段。本文探讨了结构体定义、使用及常见问题。结构体定义如`type Person struct { Name string; Age int; Address Address }`。问题包括未初始化字段的默认值、比较含不可比较字段的结构体以及嵌入结构体字段重名。避免方法包括初始化结构体、自定义比较逻辑和使用明确字段选择器。结构体方法、指针接收者和匿名字段嵌入提供了灵活性。理解这些问题和解决策略能提升Go语言编程的效率和代码质量。
68 1
|
4月前
|
Go 索引
Golang深入浅出之-切片(Slices)入门:创建、操作与扩容机制
【4月更文挑战第20天】Go语言中的切片是动态数组,提供灵活的操作和自动扩容。本文介绍了切片的创建(通过`make()`、数组创建和切片字面量)、基本操作(索引访问、切片、赋值追加和遍历)以及扩容机制(首次和后续扩容策略)。此外,还强调了切片与底层数组的关系、切片越界问题、`append()`的使用以及理解切片的关键点,帮助提升Go编程效率和代码质量。
79 0
|
4月前
|
存储 Java 程序员
从Java到Golang入门杂谈
作为一名Java老兵,入门Golang的一些体验和思考记录,本篇只是一些零碎的记录,不打算写成一个Golang入门指南,如果需要入门指南请参考其他文档或者书籍。
122 0
|
11月前
|
Java Go C语言
100天精通Golang(基础入门篇)——第2天:学习Go语言的前世今生:一门强大的编程语言的崛起
100天精通Golang(基础入门篇)——第2天:学习Go语言的前世今生:一门强大的编程语言的崛起
184 1