【go语言学习】结构体struct

Go语言中的基础数据类型可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了。
Go语言提供了一种自定义数据类型,可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct。 也就是我们可以通过struct来定义自己的类型了。

一、结构体的定义和初始化

1、结构体的定义

结构体的定义使用typestruct关键字

type 结构体名 struct {
   字段名 字段类型;
   字段名 字段类型;
   ...
   字段名 字段类型;
}

其中:

  • 类型名:标识自定义结构体的名称,在同一个包内不能重复。通过首字母大小写控制访问。
  • 字段名:表示结构体字段名。结构体中的字段名必须唯一。通过首字母大小写控制访问。
  • 字段类型:表示结构体字段的具体类型。
package main

// Person 一个结构体,包含name, age两个字段
type Person struct {
    Name string // 包外可访问
    age  int    // 包外不可访问
}

func main() {

}
2、结构体的初始化
package main

import "fmt"

// Person 一个结构体,包含name, age两个字段
type Person struct {
    Name string // 包外可访问
    age  int    // 包外不可访问
}

func main() {
    // 方法一
    var p1 Person
    p1.Name = "tom"
    p1.age = 20
    fmt.Println(p1)
    // 方法二
    p2 := Person{}
    p2.Name = "marry"
    p2.age = 18
    fmt.Println(p2)
    // 方法三
    p3 := Person{
        Name: "jack",
        age:  23,
    }
    fmt.Println(p3)
    // 方法四
    p4 := Person{
        "lili",
        24,
    }
    fmt.Println(p4)
}

运行结果

{tom 20}
{marry 18}
{jack 23}
{lili 24}

二、结构体的使用

1、结构体指针

数据类型:

  • 值类型:int,string,bool,float,arry,struct
  • 引用类型:slice,map,function,pointer,chan
package main

import "fmt"

// Person 一个结构体,包含name, age两个字段
type Person struct {
    Name string // 包外可访问
    age  int    // 包外不可访问
}

func main() {
    p := Person{
        Name: "tom",
        age:  20,
    }
    // 方法一 直接创建结构体指针
    var ptr1 *Person
    fmt.Printf("%T, %v\n", ptr1, ptr1)
    ptr1 = &p
    // (*ptr1).Name = "jack"
    ptr1.Name = "jack"
    fmt.Println(p)
    // 方法二 使用new创建结构体指针
    ptr2 := new(Person)
    fmt.Printf("%T, %v\n", ptr2, ptr2)
    ptr2 = &p
    ptr2.Name = "lili"
    fmt.Println(p)
    // 方法三 使用&地址符创建结构体指针
    ptr3 := &Person{}
    fmt.Printf("%T, %v\n", ptr3, ptr3)
    ptr3 = &p
    ptr3.Name = "marry"
    fmt.Println(p)
}

运行结果

*main.Person, <nil>
{jack 20}
*main.Person, &{ 0}
{lili 20}
*main.Person, &{ 0}
{marry 20}
2、匿名结构体

没有名字的结构体,在创建结构体的时候同时创建对象。

package main

import "fmt"

func main() {
    p := struct {
        name string
        age  int
    }{
        name: "tom",
        age:  20,
    }
    fmt.Println(p)
}

运行结果

{tom 20}
3、匿名字段

在结构体中,不写字段的名字,只写类型。匿名字段不允许重复。

package main

import "fmt"

type worker struct {
    string
    int
}

func main() {
    w := worker{
        "tom",
        20,
    }
    fmt.Println(w)
    fmt.Println(w.string, w.int)
}

运行结果

{tom 20}
tom 20
4、结构体嵌套

一个结构体中的字段,是另一个结构体。

package main

import "fmt"

// Address 结构体
type Address struct {
    Province string
    City     string
}

// User 结构体
type User struct {
    Name string
    Age  int
    // Address Address
    Address //嵌套匿名字段
}

func main() {
    u := User{
        Name: "tom",
        Age:  20,
        Address: Address{
            Province: "江苏省",
            City:     "苏州市",
        },
    }
    fmt.Printf("%+v\n", u)
}

运行结果

{Name:tom Age:20 Address:{Province:江苏省 City:苏州市}}

嵌套结构体字段访问

  • 在结构体中属于匿名结构体的字段称为提升字段,因为它们可以被访问,就好像它们属于拥有匿名结构字段的结构一样。
  • 嵌套结构体内部可能存在相同的字段名。在这种情况下为了避免歧义需要通过指定具体的内嵌结构体字段名。
package main

import "fmt"

// Address 结构体
type Address struct {
    Province   string
    City       string
    CreateTime string
}

// Email 结构体
type Email struct {
    Email      string
    CreateTime string
}

// User 结构体
type User struct {
    Name string
    Age  int
    // Address Address
    Address
    Email
}

func main() {
    u := User{
        Name: "tom",
        Age:  20,
        Address: Address{
            Province:   "江苏省",
            City:       "苏州市",
            CreateTime: "2020.09.26",
        },
        Email: Email{
            Email:      "tom@github.com.cn",
            CreateTime: "2020.09.25",
        },
    }
    fmt.Printf("%+v\n", u)
    fmt.Println(u.City)  //City是提升字段
    fmt.Println(u.Email) //Email是提升字段
    fmt.Println(u.Email.CreateTime)
    fmt.Println(u.Address.CreateTime)
}

运行结果

{Name:tom Age:20 Address:{Province:江苏省 City:苏州市 CreateTime:2020.09.26} Email:{Email:tom@github.com.cn CreateTime:2020.09.25}}
苏州市
{tom@github.com.cn 2020.09.25}
2020.09.25
2020.09.26
5、结构体与json序列化

go语言通过json包下的Marshal(v interface{}) ([]byte, error)函数实现结构体对象的json序列化。
通过json包下的Unmarshal(data []byte, v interface{}) error函数实现json的反序列化。

package main

import (
    "encoding/json"
    "fmt"
)

// Student 结构体
type Student struct {
    Name    string `json:"name"`
    Age     int    `json:"age"`
    Address string `json:"address"`
}

func main() {
    s1 := Student{
        Name:    "tom",
        Age:     20,
        Address: "苏州市",
    }
    data, err := json.Marshal(s1)
    if err != nil {
        fmt.Println("json.marshal failed, err=", err)
        return
    }
    fmt.Printf("json:%s\n", data)
    str := `{"name":"jack","age":21,"address":"上海市"}`
    s2 := new(Student)
    err = json.Unmarshal([]byte(str), s2) // s2需要传入一个指针
    if err != nil {
        fmt.Println("json.unmarshal failed, err=", err)
        return
    }
    fmt.Printf("%+v\n", s2)
}

运行结果

json:{"name":"tom","age":20,"address":"苏州市"}
&{Name:jack Age:21 Address:上海市}

三、构造函数

Go语言的结构体没有构造函数,我们可以自己实现。

package main

import "fmt"

// Student 结构体
type Student struct {
    Name    string
    Age     int
    Address string
}

// NewStudent 构造函数
func NewStudent(name string, age int, address string) *Student {
    return &Student{
        name,
        age,
        address,
    }
}

func main() {
    s := NewStudent("lili", 20, "上海市")
    fmt.Println(s)
}

运行结果

&{lili 20 上海市}

四、方法

Go语言中的方法(Method)是一种作用于特定类型变量的函数。这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self。

func (t type) methodName(parameter list) (return list) {

}

其中

  • t接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名称首字母的小写,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。
  • type接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
  • 方法名、参数列表、返回参数:具体格式与函数定义相同。
package main

import "fmt"

// Student 结构体
type Student struct {
    Name    string
    Age     int
    Address string
}

// NewStudent 构造函数
func NewStudent(name string, age int, address string) *Student {
    return &Student{
        name,
        age,
        address,
    }
}

// Learn 结构体Student的方法
func (s Student) Learn(class string) {
    fmt.Printf("%v正在努力学习%v课\n", s.Name, class)
}
func main() {
    s := NewStudent("lili", 20, "上海市")
    fmt.Println(s)
    s.Learn("english")
}

运行结果

&{lili 20 上海市}
lili正在努力学习english课

方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。

+ 指针类型的接受者

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。

package main

import "fmt"

// Student 结构体
type Student struct {
    Name    string
    Age     int
    Address string
}

// SetAge 结构体Student的方法
func (s *Student) SetAge(age int) {
    s.Age = age
}

func main() {
    s := Student{
        "lili",
        20,
        "苏州市",
    }
    fmt.Println(s)
    s.SetAge(18)
    fmt.Println(s)
}

运行结果

{lili 20 苏州市}
{lili 18 苏州市}

+ 值类型接受者

当方法作用于值类型接收者时,Go语言会在代码运行时将接收者的值复制一份。在值类型接收者的方法中可以获取接收者的成员值,但修改操作只是针对副本,无法修改接收者变量本身。

package main

import "fmt"

// Student 结构体
type Student struct {
    Name    string
    Age     int
    Address string
}

// SetAddress 结构体Student的方法
func (s Student) SetAddress(address string) {
    s.Address = address
}
func main() {
    s := Student{
        "lili",
        20,
        "苏州市",
    }
    fmt.Println(s)
    s.SetAddress("上海市")
    fmt.Println(s)
}

运行结果

{lili 20 苏州市}
{lili 20 苏州市}

+ 什么时候应该使用指针类型接收者

  • 需要修改接收者中的值
  • 接收者是拷贝代价比较大的大对象
  • 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342