随笔 - 241  文章 - 1  评论 - 58  阅读 - 85万 

前言

编程就是要通过编程语言表达给计算机,让计算机帮助我们达到解决现实生活问题的目的!

不管是Python还是Golang...这些编程语言,由于历史原因、遇到的痛点、解决的问题不同,导致语法追求、本身特性不同。但是遇到的问题、解决问题的思想是一致的。

 

面向对象编程 :就是按照自己的理解 尽量把程序里出现的所有东西   抽象得划分为1个个的不同的分类,这些分类中包含自身独有的数据、也有自己独特的方法!

 

 

如果想要开发1款游戏,游戏中的人物不仅有角色属性、也有交易、攻击这些作为。

单纯得使用数据类型int、string ..函数去表示1个人物,复杂不利于代码灵活、扩展,于是想办法如何把数据和方法集合到1块进行表示。

Go语言中没有“类”的概念,也不支持“类”的继承等面向对象的概念。

Go语言中通过结构体的内嵌再配合接口  比面向对象具有更高的扩展性和灵活性

 结构体:就是可以把多种不同的基本数据类型,封装到1个整体里面。在golang中这个整体称为结构体。

 

自定义类型

自定义类型可以对Go中现有的数据类型的方法进行扩展

 在Go语言中我们无法直接对在其他包中定义的结构体添加方法,但是可以通过类型别名的方式迂回到达目的。

 

类型别名

类型别名还记得用于表示英文字符、中文字符的 byte和rune 是uint8和int32类型的类型别名吗?
为什么会有类型别名?
我们使用int类型声明1个字符变量看起来不贴切于人类的思维,为了让代码看起来更加清晰易懂,Go语言的作者们使用类型别名
rune和bute来表示字符。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//自定义类型和类型别名
 
//在Go语言里使用 type声明类型
 
//type 我
package main
import "fmt"
 
 
type myInt int //自定义类型
type yourInt =int //类型别名
/*类型别名:还记得用于表示英文字符和中文字符的 byte和rune是int的类型别名吗?
就是这个意思yourInt本质上还是int,二者视为同1个类型
为什么会有类型别名?
我们使用int类型声明1个字符变量看起来不贴切于人类的思维,为了代码清晰使用类型别名
rune和bute来表示字符
 
*/
func main()  {
    var n myInt
    n=100
    var m yourInt
    m=100
    var c1 byte //byte是由uint8实现,所以byte是uint8的别名,在Go中使用数字表示字符。二者本质还是uint8
    c1='H'
    var c2 uint8
    c2='i'
    var c3 rune//rune是由int32实现,所以rune是int32的别名,在Go中使用数字表示字符。二者本质还是int32
    c3='根'
    var c4 int32
    c4='哥'
 
     
 
    fmt.Printf("%T\n",n)//main.myInt
    fmt.Printf("%T\n",m)//int
    fmt.Printf("%T\n",c1)//uint8
    fmt.Printf("%T\n",c2)//uint8
    fmt.Printf("%T\n",c3)//int32
    fmt.Printf("%T\n",c4)//int32
 
}

  

 

结构体声明

structure结构:顾名思义肯定是由不同的东西组合而成。

在我们写代码的时候如果 需要定义1个由多个基本数据类型组成的数据类型时(例如人有性别string、年龄uint8、爱好得有多个[]string、吃、喝、拉、撒、睡、学习、工作func)创造这种具有多维度的属性时的物时,无法使用单一的数据类型表示全面,所有我们只能使用结构体。

 

结构体的语法

使用typestruct关键字来定义结构体,具体代码格式如下:

1
2
3
4
5
type 类型名 struct {
    字段名 字段类型
    字段名 字段类型
    
}

 

定义1个简单的结构体

package main
 
import "fmt"
 
//定义1个person类的结构体
type person struct{
    name string
    age uint8
    married bool
    hobbies []string
    education map [string]string
}
 
func main() {
    //声明1个person类型的变量P
    var p person
    //给 p赋值
    p.name="Abe"
    p.age=64
    p.married=true
    p.hobbies=[]string{"爱好广泛","涉猎课本以外的世界"}
    fmt.Println(p)
    fmt.Printf("%T\n",p)//属于main.person类
    //访问变量p的字段
    fmt.Println(p.hobbies)
     
}      

  

匿名结构体

在Go语言中如果我们一次性使用结构体的话, 还可以定义 匿名的结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import "fmt"
 
func main(){
    //定义1个匿名结构体:多应用于临时场景,不使用多次
    var Martin struct{
        married bool
        age uint8
    }
    Martin.married=false
    Martin.age=18
    fmt.Printf(" type:%T\ndetails:%#v\n",Martin,Martin)
 
}

 

方式2

1
2
3
4
5
6
7
func main() {
    var struct1 = struct {
        name string
        age  uint8
    }{"Tony", 20}
    fmt.Println(struct1.name)
}

  

 

创建指针类型结构体

Go中的结构体不同于Python的class,它是值类型不可以被修改要修改可以用指针,所以Python里面没有指针,使用copy、 deep copy。

  

通过指针创建1个可以字段可以被修改的结构体

package main
 
import "fmt"
 
type person struct {
    name string
    age  uint8
}
 
func modifyReally(p *person) {
    // (*p).age=17//通过指针拿到变量值进行修改
    p.age = 17 //Go支持的快捷方式:自动根据指针找到变量
}
 
func main() {
    var p person
    p.name = "Martin"
    p.age = 18
    fmt.Println(p.age) //18
    //传入1个指针进行修改
    modifyReally(&p)
    fmt.Println(p.age) //17
 
}

  

结构体初始化

Go中结构体初始化的方式很多主要有变量赋值、构造函数初始化2种方式,于Python不同的是go中没有自带构造方法,需要自己构建。

 

实例化

 

 

构造函数初始化结构体

构造函数 就是1个构造X种结构体变量的函数,其用意是通过 1个函数反复生成某种结构体的变量,提升代码的重用性。

使用变量初始化结构体的方式会造成代码的冗余,我么可以使用一个构造函数来完成struct的初始化

struct的构造函数约定俗成以 new开头,自定义1个构造函数可以返回1个值类型的struct, 如果1个struct内部字段存储的数据量很大,重复copy造成内存开销过大。也可以返回1个指针类型的struct。

 

结构体数据类型内存管理机制

结构体类型属于golang中的一种数据类型且是值类型,默认情况(非指针类型结构体)这种数据类型的变量被赋值之后,会重新拷贝一份。但是注意arry和map的底层存储原理。

如果是指针类型的结构体被赋值之后则不会开辟新的内存空间。

如果希望2个结构体对象的值同步变化,就使用指针类型的结构体,否则不使用。

1.结构体内存管理机制

p1 := Peron{name: "武沛齐", age: 18}
//1.赋值之后会重新拷贝一份p1的数据赋值给p2
p2 := p1
fmt.Println(p1) //{武沛齐 18}
fmt.Println(p2) //{武沛齐 18}
p1.name = "alex"
fmt.Println(p1) //{alex 18}
fmt.Println(p2) //{武沛齐 18}

 

2.结构体指针类型变量内存管理机制

p1 := &Peron{name: "武沛齐", age: 18}
//1.赋值之后会重新拷贝一份p1的数据赋值给p2
p2 := p1
fmt.Println(p1) //&{武沛齐 18}
fmt.Println(p2) //&{武沛齐 18}
p1.name = "alex"
fmt.Println(p1) //&{alex 18}
fmt.Println(p2) //&{alex 18}

 

3.嵌套结构体内存管理机制

如果存在结构体嵌套,在结构体对象被赋值之后也会重新拷贝1份。

复制代码
    type Address struct {
        city, state string
    }
    type Person struct {
        name    string
        age     int
        address Address //嵌套结构体
    }

    p1 := Person{name: "二狗子", age: 19, address: Address{city: "北京", state: "BJ"}}
    p2 := p1
    fmt.Println(p1.address, p2.address) //{北京 BJ} {北京 BJ}
    p1.address.city = "上海"
    p1.address.state = "SH"
    fmt.Println(p1.address) //{上海 SH}
    fmt.Println(p2.address) //{北京 BJ}
复制代码

 

4.结构体中包含引用数据类型

当1个结构体类型变量赋值给另1个新的变量时,本质上会copy一份新的。

由于struct中包含的数据的存储方式不同导致有的copy的是内存地址(pointer)有的copy的是值

感觉拷贝:整型、布尔、字符串、数组

感觉不拷贝:切片、字典

所以想要达到让1个结构体实例化出来的2个对象数据保持一直,可以借助指针。

复制代码
    type Address struct {
        city, sate string
        owners     []string
    }
    type Person struct {
        name     string
        age      int
        children [2]string
        hobbies  []string
        parent   map[string]string
        address  Address
    }
    p1 := Person{name: "二狗", age: 69, children: [2]string{"小奶狗", "小狼狗"}, hobbies: []string{"", ""}, parent: map[string]string{"": "Tom", "mother": "Rose"}, address: Address{city: "北京", sate: "BJ", owners: []string{"二狗", "二狗夫人"}}}
    p2 := p1
    fmt.Println(p1)              //{二狗 69 [小奶狗 小狼狗] [吃 喝] map[:Tom mother:Rose] {北京 BJ [二狗 二狗夫人]}}
    fmt.Println(p2)              //{二狗 69 [小奶狗 小狼狗] [吃 喝] map[:Tom mother:Rose] {北京 BJ [二狗 二狗夫人]}}
    p1.children[0] = "Joy"       //修改值类型的字段不会影响全局
    fmt.Println(p1)              //{二狗 69 [Joy 小狼狗] [吃 喝] map[:Tom mother:Rose] {北京 BJ [二狗 二狗夫人]}}
    fmt.Println(p2)              //{二狗 69 [小奶狗 小狼狗] [吃 喝] map[:Tom mother:Rose] {北京 BJ [二狗 二狗夫人]}}
    p1.parent["father"] = "隔壁老王" //修改引用类型(map)的字段会影响全局
    p1.hobbies[1] = "AllIn"      //修改引用类型(arry)的字段会影响全局
    p1.address.owners[0] = "王先生" //修改引用类型(map)的字段会影响全局
    fmt.Println(p1)              //{二狗 69 [Joy 小狼狗] [吃 AllIn] map[:Tom father:隔壁老王 mother:Rose] {北京 BJ [王先生 二狗夫人]}}
    fmt.Println(p2)              //{二狗 69 [小奶狗 小狼狗] [吃 AllIn] map[:Tom father:隔壁老王 mother:Rose] {北京 BJ [王先生 二狗夫人]}}
    /*
     */
结构体中包含引用数据类型
复制代码

 

 

结构体模拟面向对象继承效果

面向对象中的继承可以,重用代码,避免重复造轮子,那么怎么使用Go的struct模拟继承的效果呢?

 

匿名字段struct

匿名字段就是没有字段名称,由于使用数据类型取值,它适用于 struct字段较少的场景。
 
既然我们可以把结构体中 数据类型当做字段名称来获取值,虽然限制了结构体中相同字段只能有1种数据类型。当可以模拟其他语言中的继承。
 
 
package main
import "fmt"
//匿名字段:匿名字段就是没有字段名称
//匿名字段适用于 struct字段较少的场景
type person struct{
    string
    int32
}
 
func main(){
    p1:=person{"Martin",20}
    //如果没有字段名称通过什么取值呢?数据类型!
    fmt.Println(p1.string)
    fmt.Println(p1.int32)
 
}

 

嵌套struct

为了实现更深层的数据封装,结构体里也可以套结构体。

//嵌套结构体
package main
 
import "fmt"
 
//员工个公司共有的地址属性
type address struct {
    province string
    city     string
}
 
//员工信息struct
type employee struct {
    name string
    age  int8
    addr address //嵌套了结构体address
}
 
//公司信息struct
 
type company struct {
    name string
    addr address //嵌套了结构体address
}
 
func main() {
    employee1 := employee{
        name: "Robinz",
        age:  29,
        addr: address{
            province: "山西省",
            city:     "阳泉市",
        },
    }
    company1 := company{
        name: "baix",
        addr: address{province: "北京市", city: "海淀区"},
    }
    fmt.Println(employee1.name)
    fmt.Println(employee1.addr.province)
    fmt.Println(employee1.addr.city)
    fmt.Println(company1.addr.city)
}

  

 

匿名嵌套struct 

既然嵌套的int和string类型可以把据数据类型名称当做字段名称使用查找到对应的值。
那么我自己通过type关键字定义的数据类型,也是可以的。
匿名嵌套结构体: 先在自己的struct里面查找字段,如果查找不到该字段,再去匿名嵌套的struct查找。
注意:如果1个struct中嵌套了2个结构体体,这些子结构体中存在相同的字段的,就无法查找。
//匿名嵌套结构体
package main
 
import "fmt"
 
//员工个公司共有的地址属性
type address struct {
    province string
    city     string
}
 
//员工信息struct
type employee struct {
    name    string
    age     int8
    address //嵌套匿名字段的结构体address
}
 
//公司信息struct
 
type company struct {
    name    string
    address //嵌套了匿名字段的结构体address
}
 
func main() {
    employee1 := employee{
        name: "Robinz",
        age:  29,
        address:address{
            province: "山西省",
            city:     "阳泉市",
        },
    }
    company1 := company{
        name: "baix",
        address:address{province: "北京市", city: "海淀区"},
    }
    fmt.Println(employee1.name)
    fmt.Println(employee1.province)
    fmt.Println(employee1.city)//先在自己的struct里面查询字段 再去匿名嵌套的struct查询
    fmt.Println(company1.city)//先在自己的struct里面查询字段 再去匿名嵌套的struct查询
}

  

 

如果1个struct嵌套了2个字段相同的匿名struct,现在是2个平级,那么我该去这2个匿名struct中哪一个里面取查找呢?

在Golang中会引发冲突,和Python不同的是golang只能深度找,不能广度找。

//匿名嵌套结构体
package main
 
import "fmt"
 
//员工个公司共有的地址属性
type address struct {
    province string
    city     string
}
 
//工作地址:和 adress中的 province好city字段出现了冲突
type workAdreess struct {
    province string
    city     string
}
 
//员工信息struct
type employee struct {
    name    string
    age     int8
    address //嵌套匿名字段的结构体address  字段冲突
    workAdreess////嵌套匿名字段的结构体workAdreess字段冲突
}
 
//公司信息struct
type company struct {
    name    string
    address //嵌套了匿名字段的结构体address
}
 
func main() {
    employee1 := employee{
        name: "Robinz",
        age:  29,
        address: address{
            province: "山西省",
            city:     "阳泉市",
        },
        workAdreess: workAdreess{province: "山东省", city: "威海"},
    }
    company1 := company{
        name:    "baix",
        address: address{province: "北京市", city: "海淀区"},
    }
    fmt.Println(employee1.name)
    fmt.Println(employee1.name)
    // fmt.Println(employee1.city)//先在自己的struct里面查询字段 再去匿名嵌套的struct查询
    fmt.Println(company1.city) //先在自己的struct里面查询字段 再去匿名嵌套的struct查询
 
    //如果employee结构体中嵌套了2个含有相同字段的匿名结构体,会引起查询冲突,只能按照以下方式取值
    fmt.Println(employee1.address.city)
    fmt.Println(employee1.workAdreess.province)
}

  

 

模拟继承

利用Go的struct可以嵌套struct,当前struct中没有的字段    自动去嵌套了struct的匿名字段中查找的特性,实现继承的效果。

package main
 
import "fmt"
 
//基类
type animal struct {
    kind   string
    gender string
    age    uint8
}
 
//子类(人类)
type perosn struct {
    animal
}
 
//子类(犬类)
type dog struct {
    animal
}
 
//给基类增加walk方法
func (a animal) walk() {
    fmt.Printf("%s are walking.. \n", a.kind)
}
 
func main() {
    p1 := perosn{
        animal: animal{kind: "People", age: 18, gender: "男性"},
    }
    //p1 struct里面没有walk方法,就自动去匿名字段animal这个匿名结构体中查找
    p1.walk()
    d1 := dog{
        animal: animal{kind: "Dogs", age: 3, gender: "雄性"},
    }
    d1.walk()
    //d1 struct里面没有walk方法,也自动去匿名字段animal这个匿名结构体中查找
}

  

  

 

结构体内存布局

结构体占用一块连续的内存。

 

  

 

struct的方法和接受者

前面的struct中我只是封装了数据,那么我想对struct中的这些数据进行操作呢?就需要给struct绑定上1个方法。

Go语言中的方法(Method)是一种作用于特定类型变量的函数

这种特定类型变量叫做接收者(Receiver)。接收者的概念就类似于其他语言中的this或者 self

Go里面接收者(某1个数据类型)+方法 这套语法,实现了类似于Python类中的方法!

方法和函数的不同是函数不从属于任何数据类型,而方法作用于某种数据类型。 

 

语法

原来Go的函数名前面还可以指定接收者

func (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {
    函数体
}

 

值类型的接受者

接受者必须指定某1个数据类型

package main
import "fmt"
 
 
type dog struct{
    kind string
    age uint8
}
 
//方法和接收者
func newDog(kind string,age uint8)(*dog){
    return &dog{
        kind:kind,
        age:age,
    }
 
}
 
 
//函数名前面可以指定这个函数的接受者,如果该函数指定了接收这,这个函数就叫method方法
//因为指定了接收者,所以方法是作用于特定类型的函数
//接受者使用类型的首字母 小写表示
// dog 类型是接收者 bark就是仅作用于dog类的方法
func (d dog)bark(){
    fmt.Printf("A %s barks at you~\n",d.kind)
}
 
func main(){
 
    d1:=newDog("中华田园犬",2)
    //因为接受者和方法做了绑定,所以dog类的对象都可以调用方法 bark 方法
    d1.bark()
 
 
}

  

 指针类型的接受者

 接受者还可以为 某种数据类型的指针,接受者为数据类型指针时就实现了对struct 字段的修改。

package main
 
import "fmt"
 
//定义1个struct person
type person struct {
    name string
    age uint8
}
//定义1个用于初始 person结构体的构造函数
func newPerson(name string, age uint8) *person {
    return &person{
        name:name,
        age:age,
    }
 
}
 
//使用指针接受者:接收者不仅可以为自定义的数据类型,也可以是数据类型的指针类型
func (p *person)aged(years uint8){
    p.age+=years
}
 
 
 
func main() {
    p1:=newPerson("Someone you don't like",39)
    fmt.Println(p1.age)
    p1.aged(10)
    fmt.Println(p1.age)
    p1.aged(10)
    fmt.Println(p1.age)
    p1.aged(10)
    fmt.Println(p1.age)
}

 

任意类型添加方法 

在Go语言中,接收者的类型可以是任何类型不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int32类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
 
import "fmt"
//给go内置的数据类型扩展方法
type myInt int64
 
//给int32扩展1个翻x倍的方法
func(m *myInt)autoTimes(n int64 ){
    (*m)*=myInt(n)
}
 
func main(){
    var salary myInt
    salary=2500
    //娶媳妇的年级了,给自己涨点工资吧....
    salary.autoTimes(1000000)
    fmt.Println(salary)
     
}

 

 

结构体序列化

JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。JSON键值对是用来保存JS对象的一种方式,键/值对组合中的键名写在前面并用双引号""包裹,使用冒号:分隔,然后紧接着值;多个键值之间使用英文,分隔。

 

序列化和反序列化

//1.序列化:把Go语言中的结构体变量------json格式的字符串
//2.反序列化:把json格式的字符串---------Go语言能识别的结构体变量
/*
由于我们使用了第三方的包,而main包中声明的变量
无法在第三包中使用(除非大写才能被main包之外的包使用)
所以想要访问main中的变量必须大写!
 
如果必须大写。我们产生的json数据也会变成大写,为了避免数据失真,可以使用tag
*/
type person struct {
    Name string `json:"name" db:"name" ini:"name"`
    Age  uint8  `json:"age" db:"name" ini:"name"`
}
 
func main() {
 
    p1 := person{
        Name: "Martin",
        Age:  18,
    }
    //序列化
    b, err := json.Marshal(p1)
    if err != nil {
        fmt.Printf("marshal faild err:%v", err)
        return
    }
    fmt.Printf("%#v\n", string(b)) //字符串本身是由字节切片组成的,所有支持强制转换。
 
    //反序列化
    var p2 person
    json.Unmarshal([]byte(string(b)), &p2)
    fmt.Printf("%#v\n", p2)
 
}

 

复杂json结构

 

 

在golang中我们不仅可以把json转换成结构体,还可以把json转换成map类型。

复制代码
package main

import (
    "encoding/json"
    "fmt"
)

type Person struct {
    Name string `json:"name"`
    Age  int `json:"age"`
}

func main() {

    p1 := Person{Name: "Martin", Age: 19}
    b, err := json.Marshal(p1)
    if err != nil {
        fmt.Println(err)
    }
    //json字符串
    fmt.Println(string(b))
    //声明map
    maping:=map[string]interface{}{}
    //在golang中我们不仅可以把json转换成结构体,还可以把json转换成map类型。
    err=json.Unmarshal(b,&maping)
    if err!=nil{
        fmt.Println(err)
    }
    fmt.Println(maping)
}
复制代码

 

 

 

结构体实现链表

链表反转

  

  

 

 

 

 

 

 

 

 

 

练习 

 

员工管理函数版

  

员工管理面向对象版

 

 

 manage_unit.go

main.go

  

 

 

 

 

 

参考

 
posted on   Martin8866  阅读(3108)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示