golang基础

Go安装及配置环境

下载最新的 zip 文件: go#.#.#.windows-amd64.zip ,这里的 #.#.# 是 Go 的最新版本号。

解压缩 go#.#.#.windows-amd64.zip 文件到你选择的位置。比如D:\Go

在系统中设置两个环境变量:GOROOT和GOPATH

GOPATH 指向的是你的工作目录。

GOROOT 指向的是go的源文件的存放目录,也就是D:\GO,添加 %GOROOT%\bin 到系统的 PATH 环境变量。

测试是否配置成功:

然后打开一个 cmd 命令终端,输入 go version。 你会得到一个 go version go1.3.3 windows/amd64 的输出,即表示 Go 安装完成。

运行一个go程序

package main

func main() {
  println("hello world!")	//使用内置函数 println 打印出字符串。
}

将上述内容保存为一个xxx.go文件,然后打开一个 shell 或者终端,进入到文件保存的目录内,通过敲入以下命令来运行程序:

go run xxx.go

如果 golang 环境配置正确,将打印出hello world!。

Go 是一门编译型语言,直接运行go run 命令已经包含了编译运行。它使用一个临时目录来构建程序,执行完后清理掉临时目录。可以执行以下命令来查看临时文件的位置:

go run --work xxx.go

可以使用go build xxx.go命令来编译代码,这将产生一个可执行文件 xxx.exe

在开发中,既可以使用 go run 也可以使用 go build 。但正式部署的时候,应该部署 go build 产生的二进制文件,然后执行它。

入口函数main

在 go 中,程序入口必须是 main 函数,并且在 main 包内,也就是文件第一行的package main

import导入包

import 关键字被用于去声明文件中代码要使用的包。

在 Go 中,关于导包是很严格的。如果你导入了一个包却没有使用将会导致编译不通过。

Go 的标准库已经有了很好的文档。可以访问 golang.org/pkg/fmt/#Println 去看更多关于 PrintLn 函数的信息。

也可以在本地获取文档:

godoc -http=:6060

然后浏览器中访问 http://localhost:6060

变量及声明

方法一:使用var

下面是 Go 中声明变量和赋值最明确的方法,但也是最冗长的方法:

package main

import (
  "fmt"
)

func main() {
  var power int		//定义了一个 int 类型的变量 power
  power = 9000
  //以上两行可以合并为
  //var power int = 9000
  fmt.Printf("It's over %d\n", power)
}

方法二:使用:=

Go 提供了一个方便的短变量声明运算符 := ,它可以自动推断变量类型,但是这种声明运算符只能用于局部变量,不可用于全局变量:

power := 9000

注意:=表示的是声明并赋值,所以在相同作用域下,相同的变量不能被声明两次

此外, go 支持多个变量同时赋值(使用 = 或者 :=):

func main() {
  name, power := "Goku", 9000
  fmt.Printf("%s's power is over %d\n", name, power)
}

多个变量赋值的时候,只要其中有一个变量是新的,就可以使用:=。例如:

func main() {
  power := 1000
  fmt.Printf("default power is %d\n", power)

  name, power := "Goku", 9000
  fmt.Printf("%s's power is over %d\n", name, power)
}

注意:Go 不允许在程序中有声明了但未使用的变量。例如:

func main() {
  name, power := "Goku", 1000
  fmt.Printf("default power is %d\n", power)
}

不能通过编译,因为 name 是一个被声明但是未被使用的变量,就像 import 的包未被使用时,也将会导致编译失败。

函数声明

函数声明格式:

func 函数名([参数名,参数类型]…) ([返回值类型]…){

}

注意在go中是先写参数名,再写参数类型的。。

以下三个函数:一个没有返回值,一个有一个int类型的返回值,一个有两个返回值(int型和bool型)。

func log(message string) {
}

func add(a int, b int) int {
}
//或者可以写成
//func add(a, b int) int { 
//}

func power(name string) (int, bool) {
}

我们可以像这样使用最后一个:

value, exists := power("goku")		//用两个变量value和exists来接收power函数的两个返回值
if exists == false {
  // 处理错误情况
}

有时候,我们仅仅只需要关注其中一个返回值。就可以将其他的返回值赋值给空白符_

_, exists := power("goku")
if exists == false {
  // handle this error case
}

_ ,空白标识符实际上返回值并没有赋值。所以可以一遍又一遍地使用 _ 而不用管它的类型。

slice切片

slice创建和初始化

切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。

var 或者 :=
var slice1 []int = []int{0,1,2,3,4,5}
var slice2 = []int{0,1,2,3,4,5}
slice3 := []int{0,1,2,3,4,5}	//局部
make
var slice []type = make([]type, len)
slice := make([]type, len)
slice := make([]type, len, cap)

使用 make 动态创建slice,避免了数组必须用常量做长度的麻烦。还可用指针直接访问底层数组,退化成普通数组操作。

从数组切片
arr := [5]int{1, 2, 3, 4, 5}	//这里是[...],所以是个数组
slice := []int{}	//这里是[],没有指明长度,所以是个切片
// 切片操作前包后不包
slice = arr[1:4]
fmt.Println(slice)

在这里插入图片描述

package main
import "fmt"

func main() {
    slice := []int{0,1,2,3,4,5}
    
    //a[x:y:z] 切片内容 [x:y] len:y-x cap:z-x
    //不写z,默认是len
    s1 := slice[1:2]    //x=1,y=2,z=6 len=1 cap=5
    fmt.Println(s1)
    fmt.Println("cap of s1", cap(s1))
    
    s2 := slice[:3:4]   //x=0,y=3,z=4 len=3 cap=4
    fmt.Println(s2)
    fmt.Println("cap of s2", cap(s2))
    
    s3 := slice[1:3:5]  //x=1,y=3,z=5 len=2 cap=4
    fmt.Println(s3)
    fmt.Println("cap of s3", cap(s3))

}

切片的内存布局

读写操作实际目标是底层数组,只需注意索引号的差别。

在这里插入图片描述

append内置函数

append :向 slice 尾部添加数据,返回新的 slice 对象

package main

import (
    "fmt"
)

func main() {

    s1 := make([]int, 1, 5)
    fmt.Printf("%p\n", &s1)

    s2 := append(s1, 1)
    fmt.Printf("%p\n", &s2)
    
    s3 := append(s2, 2, 3)
    fmt.Printf("%p\n", &s3)

    fmt.Println(s1, s2, s3)
    fmt.Println(&s1[0], &s2[0], &s3[0])

}

输出结果:

0xc00000c060
0xc00000c080
0xc00000c0a0
[0] [0 1] [0 1 2 3]
0xc000072030 0xc000072030 0xc000072030

可以看到s1,s2,s3是三个不同的对象!但是他们底层的数组是同一个数组

append后超出原来的slice的cap

超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满。

package main

import (
    "fmt"
)

func main() {

    arr := [5]int{}
    slice := arr[:3:4]
    fmt.Println("arr: ", arr, "slice: ", slice)
    
    slice = append(slice, 100)
    fmt.Println("first append:")
    fmt.Println("arr: ", arr, "slice: ", slice)     //会发现arr中的内容也被改成了100,说明此时slice和arr实际上是一个数组,slice是arr的一个切片
    fmt.Println(&arr[0], &slice[0])     //0xc00008c030 0xc00008c030数组的首地址是一样的
    fmt.Printf("%p %p\n", &arr, &slice) //0xc00008c030 0xc000086020直接取地址两者是不一样的,slice是一个结构体,相当于再包了一层,结构体里面有个属性指向了底层数组的首地址
    
    fmt.Println()
    fmt.Println("second append:")
    //slice再追加一个就查出cap了
    slice = append(slice, 200)
    fmt.Println("arr: ", arr, "slice: ", slice)     //到这里arr里的美容不变了,slice的内容变了,说明两者不是同一块内存空间了
    fmt.Println(&arr[0], &slice[0])     //0xc00008c030 0xc0000a4040可以看到数组的首地址不一样了

}

输出结果:

arr:  [0 0 0 0 0] slice:  [0 0 0]
first append:
arr:  [0 0 0 100 0] slice:  [0 0 0 100]
0xc000072030 0xc000072030
0xc000072030 0xc00000c060

second append:
arr:  [0 0 0 100 0] slice:  [0 0 0 100 200]
0xc000072030 0xc00007e040

copy

函数 copy 在两个 slice 间复制数据,复制长度以 len 小的为准。两个 slice 可指向同一底层数组,允许元素区间重叠。

package main

import (
    "fmt"
)

func main() {

    s1 := make([]int, 5)
    s2 := []int{1,2,3}
    s3 := []int{4,5,6,7}
    fmt.Println(s1, s2, s3)

    copy(s1, s2)
    fmt.Println("s2 -> s1:",s1)
    
    copy(s2, s3)
    fmt.Println("s3 -> s2:",s2)
    
    copy(s3, s1)
    fmt.Println("s1 -> s3:",s3)
}

输出结果:

[0 0 0 0 0] [1 2 3] [4 5 6 7]
s2 -> s1: [1 2 3 0 0]
s3 -> s2: [4 5 6]
s1 -> s3: [1 2 3 0]

slice遍历

func main() {

    slice := []int{5,4,3,2,1,0}
    for index, value := range slice{
        fmt.Printf("index:%v, value:%v\n", index, value)
    }
}

输出结果:

index:0, value:5
index:1, value:4
index:2, value:3
index:3, value:2
index:4, value:1
index:5, value:0

string and slice

string底层就是一个byte的数组,因此可以进行切片操作。但是string本身是不可变的,因此要改变string中的字符需要先转为数组,修改后再转回来。

package main
import "fmt"

func main() {
    /*
     英文字符串
    */
    str := "Hello World!"
    
    // s := str[:8]
    // s[6] = 'G'    //string本身是不可变的,这里会报错:./main.go:15:10: cannot assign to s[6]

    s := []byte(str)    //string转换成字符数组
    s[6] = 'G'
    s = s[:8]
    s = append(s, '!')
    
    fmt.Println(s)  //打印出来是字符数组
    
    str = string(s)
    fmt.Println(str)    //转回string再打印
    
    /*
     含有中文字符用[]rune数组
    */
    str2 := "你好,世界。"
    s2 := []rune(str2)
    s2[5] = '!'
    
    s2 = append(s2, '!')
    str2 = string(s2)
    fmt.Println(str2)
    
}

打印结果:

[72 101 108 108 111 32 71 111 33]
Hello Go!
你好,世界!!

指针

和C语言的指针差不多

Map

map的声明&初始化

序号方式示例
1声明方式var m map[string]string
2new关键字m := *new(map[string]string)
3make关键字m := make(map[string]string, [cap])
4直接创建m := map[string]string{"": ""}

由于map是引用类型,因此使用声明方式创建map时在使用之前必须初始化。以上四种方式中最常用的是后面两种使用方式,第二种基本不会使用。

//方式一
// userInfo := map[string]string{}
userInfo := map[string]string{"name":"xiaoming","age":"24"}

userInfo["name"] = "xiaozhang"
userInfo["age"] = "20"
 
// data := make(map[int]int, 10)
data := make(map[int]int)
data[100] = 998
data[200] = 999
 
//方式二
// 声明,nil
var row map[int]int
row = data

注意:键不重复 & 键必须可哈希(int/bool/float/string/array)

map的基本使用

package main

import "fmt"

func main() {
    //map类型的变量默认初始值为nil,需要使用make()函数来分配内存。
    //语法为: make(map[KeyType]ValueType, [cap])
    amap := make(map[string]string, 5)
    
    amap["xm"] = "xiaoming"
    amap["xh"] = "xiaohong"
    
    fmt.Println("获取长度:", len(amap))
    
    //map的遍历
    for k,v := range amap{
        fmt.Println(k, v)
    }
    
    //判断某个key是否存在,存在的话ok为true
    value, ok := amap["xz"]
    fmt.Println("value:", value, "ok:", ok)
    
    //也可以在声明的时候填充元素
    intmap := map[string]int{
        "1": 1,
        "2": 2,     //注意这个逗号一定要在
    }
    intmap["3"] = 3
    for k, v := range intmap{
        fmt.Println(k, v)
    }
    
    //删除某个键值对,使用delete函数
    fmt.Println("delete key=2的项")
    delete(intmap, "2")
    for k, v := range intmap{
        fmt.Println(k, v)
    }
}

输出结果:

获取长度: 2
xm xiaoming
xh xiaohong
value:  ok: false
1 1
2 2
3 3
delete key=2的项
1 1
3 3

元素为map类型的slice

package main

import "fmt"

func main() {
    //map切片,使用make为mapSlice分配内存
    mapSlice := make([]map[string]string, 3)

    fmt.Println("初始mapSlice:")
    for i, v := range mapSlice{
        fmt.Println(i, v)
    }
    
    //这里直接声明并初始化了一个名为userInfo的map
    userInfo := map[string]string{"name": "xiaoming", "age": "24"}
    mapSlice[0] = userInfo  //所以这里可以直接赋值给mapSlice[0]
    
    mapSlice[1] = make(map[string]string, 3)    //要先为mapSlice[1]指向的map分配内存,否则报错panic: assignment to entry in nil map
    mapSlice[1]["name"] = "xiaozhang"   //然后就可以直接往mapSlice[1]指向的map中写东西了
    mapSlice[1]["age"] = "23"
    mapSlice[1]["gender"] = "1"
    
    fmt.Println()
    for i, v := range mapSlice{
        fmt.Println(i, v)
    }
}

输出结果:

初始mapSlice:
0 map[]
1 map[]
2 map[]

0 map[age:24 name:xiaoming]
1 map[age:23 gender:1 name:xiaozhang]
2 map[]

值为切片类型的map

package main

import "fmt"

func main() {
    //sliceMap的key为string,value为string类型的slice
    sliceMap := make(map[string][]string, 3)
    fmt.Println("初始sliceMap:")
    for k, v := range sliceMap{
        fmt.Println(k, v)
    }
    
    sliceMap["slice1"] = []string{"a", "b", "c"}

    str := [4]string{"hello", "world", "!"}
    sliceMap["slice2"] = str[:3]
    
    fmt.Println()
    fmt.Println("sliceMap:")
    for k, v := range sliceMap{
        fmt.Println(k, v)
    }
}

输出结果:

初始sliceMap:

sliceMap:
slice1 [a b c]
slice2 [hello world !]

结构体

结构体的定义和实例化

定义

type 类型名 struct {
字段名 字段类型
字段名 字段类型
…
}

其中

1.类型名:标识自定义结构体的名称,在同一个包内不能重复。
2.字段名:表示结构体字段名。结构体中的字段名必须唯一。
3.字段类型:表示结构体字段的具体类型。

只有当结构体实例化时,才会真正地分配内存。也就是必须实例化后才能使用结构体的字段。

package main
import "fmt"

//结构体的定义:使用type和struct关键字
type person struct{
    name string
    age int32
    address string
}
func main() {
    
    //常用的结构体实例化方式一:使用键值对初始化
    person1 := person{
        name: "xiaozhang",
        age: 19,
        address: "ningbo",
    }
    
    fmt.Println("person1,name: ", person1.name)
    fmt.Printf("type of person1: %T\n", person1)
    
    //实例化方式二
    //结构体本身也是一种类型,我们可以像声明内置类型一样使用var关键字声明结构体类型。
    var person2 person
    person2.name = "xiaoli"
    person2.age = 20
    person2.address = "shanghai"
    
    fmt.Printf("%#v\n", person2)
}
使用new关键字来实例化

可以通过使用new关键字对结构体进行实例化,得到的是一个结构体类型的指针。 格式如下:

package main
import "fmt"

type person struct{
    name string
    age int32
    address string
}
func main() {
    
    //使用new关键字创建实例,得到的是一个person类型的指针
    person3 := new(person)
    fmt.Printf("type of person3: %T\n", person3)
    person3.name = "xiaowang"       //GO里面可以直接用.来调用指针指向的结构体的成员
    (*person3).address = "beijing"  //person3.name其实在底层是(*person3).name,这是GO实现的的语法糖
    fmt.Printf("%#v\n", person3)
    
}

输出结果:

type of person3: *main.person
&main.person{name:"xiaowang", age:0, address:"beijing"}
使用取地址符&来实例化

使用&对结构体进行取地址操作相当于对该结构体类型进行了一次new实例化操作。

package main
import "fmt"

type person struct{
    name string
    age int32
    address string
}
func main() {
    
    //使用&创建实例,得到的是一个person类型的指针
    person4 := &person{
        name: "xiaochen",
        age: 28,
        address: "chengdu",
    }
    fmt.Printf("type of person4: %T\n", person4)
    fmt.Printf("%#v\n", person4)
    
}

输出结果:

type of person4: *main.person
&main.person{name:"xiaochen", age:28, address:"chengdu"}
使用值的列表初始化

这种方式要注意:

  1. 必须初始化结构体的所有字段。
  2. 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
  3. 该方式不能和键值初始化方式混用。
func main() {
    person5 := person{
        "xiaozhao", 28, "chongqing",
    }
    fmt.Printf("type of person5: %T\n", person5)    //type of person5: main.person
    fmt.Printf("%#v\n", person5)        //main.person{name:"xiaozhao", age:28, address:"chongqing"}
}

匿名结构体

在定义一些临时数据结构等场景下还可以使用匿名结构体。

package main

import (
    "fmt"
)

func main() {
    var user struct{name string; age int}
    user.name = "xiaozhang
    user.age = 18
    fmt.Printf("%#v\n", user)
}

结构体的内存布局

构造函数

Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型

package main
import "fmt"

type person struct{
    name string
    age int32
    address string
}
func main() {
    person1 := newPerson("xiaozhang", "ningbo", 28)     //调用构造函数,注意这里的参数顺序
    
    fmt.Printf("%#v\n", person1)
}

//构造函数 
func newPerson(name, address string, age int32) *person{
    return &person{
        name: name,
        age: age,
        address: address,
    }
}

方法和接收者

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

方法的定义格式如下:

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

其中:

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

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

package main
import "fmt"

type person struct{
    name string
    age int32
    address string
}
func main() {
    person1 := newPerson("xiaozhang", "ningbo", 28)     //调用构造函数,注意这里的参数顺序
    
    fmt.Printf("%#v\n", person1)
    
    person1.Dream()
}

//构造函数 
func newPerson(name, address string, age int32) *person{
    return &person{
        name: name,
        age: age,
        address: address,
    }
}

//person类型有一个Dream()方法
func (p person) Dream() {
    fmt.Printf("%s在做梦。\n", p.name)
}

输出结果:

&main.person{name:"xiaozhang", age:28, address:"ningbo"}
xiaozhang在做梦。
指针类型的接收者

指针类型的接收者由一个结构体的指针组成,由于指针的特性,调用方法时修改接收者指针的任意成员变量,在方法结束后,修改都是有效的。这种方式就十分接近于其他语言中面向对象中的this或者self。 例如我们为person类型添加一个SetAge方法,来修改实例变量的年龄。

值类型的接收者

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

package main
import "fmt"

type person struct{
    name string
    age int32
    address string
}
func main() {
    person1 := newPerson("xiaozhang", "ningbo", 28)     //调用构造函数,注意这里的参数顺序
    person1.setAge(19)
    person1.setAge2(20)
    fmt.Printf("%#v\n", person1)
}

//构造函数 
func newPerson(name, address string, age int32) *person{
    return &person{
        name: name,
        age: age,
        address: address,
    }
}

//接收者为指针类型
func (p *person) setAge(age int32){
    p.age = age
}

//接收者为值类型
func(p person) setAge2(age int32){
    p.age = age
}

输出结果:

&main.person{name:"xiaozhang", age:19, address:"ningbo"}

可见值类型的接收者无法改变实例的成员值

什么时候应该使用指针类型接收者?
  1. 需要修改接收者中的值
  2. 接收者是拷贝代价比较大的大对象
  3. 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。

任意类型添加方法

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

注意: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。

package main
import "fmt"

type myInt int 

func main() {
    var i myInt = 10
    i.sayHi()
    fmt.Printf("my type is %T and my value is %v", i, i)
}

func (m myInt) sayHi(){
    fmt.Printf("hi~ ")
}

输出结果:

hi~ my type is main.myInt and my value is 10

结构体的匿名字段

结构体允许其成员字段在声明时没有字段名而只有类型,这种没有名字的字段就称为匿名字段。
匿名字段默认采用类型名作为字段名结构体要求字段名称必须唯一,因此一个结构体中**同种类型的匿名字段只能有一个**。

package main
import "fmt"

type person struct{
    string		//只有类型,没有字段名
    int
}

func main() {
    p := person{
        "zhangsan",
        18,
    }
    
    fmt.Println(p)
    fmt.Println(p.string, p.int)
}

嵌套结构体

一个结构体中可以嵌套包含另一个结构体或结构体指针。

package main
import "fmt"

type person struct{
    name string
    age int
}

type student struct{
    personInfo person
    className string
}

func main() {
    p := person{"zhangsan", 18,}
    fmt.Printf("%#v\n", p)
    
    stu := newStudent(p, "3班")
    fmt.Printf("%#v\n", stu)
}

func newStudent(p person, cn string) *student{
    return &student{
        personInfo: p,
        className: cn,
    }
}

输出结果:

main.person{name:"zhangsan", age:18}
&main.student{personInfo:main.person{name:"zhangsan", age:18}, className:"3班"}
嵌套匿名结构体

当访问结构体成员时会先在结构体中查找该字段,找不到再去匿名结构体中查找。

package main
import "fmt"

type person struct{
    name string
    age int
}

type address struct{
    province string
    city string
}

type student struct{
    personInfo person
    className string
    address     //匿名结构体
}

func main() {
    p := person{"zhangsan", 18,}
    fmt.Printf("%#v\n", p)
    
    stu := newStudent(p, "3班")
    fmt.Printf("%#v\n", stu)
    
    stu.address.province = "浙江省"     //通过 匿名结构体.字段名 来访问
    stu.city = "宁波市"                 //直接访问匿名结构体的字段名
    fmt.Printf("%#v\n", stu)
}

func newStudent(p person, cn string) *student{
    return &student{
        personInfo: p,
        className: cn,
    }
}

输出结果:

main.person{name:"zhangsan", age:18}
&main.student{personInfo:main.person{name:"zhangsan", age:18}, className:"3班", address:main.address{province:"", city:""}}
&main.student{personInfo:main.person{name:"zhangsan", age:18}, className:"3班", address:main.address{province:"浙江省", city:"宁波市"}}
嵌套结构体中的字段名冲突

嵌套结构体内部可能存在相同的字段名。这个时候为了避免歧义需要指定具体的内嵌结构体的字段。

package main
import "fmt"

type person struct{
    name string
    age int
    createTime string
}

type address struct{
    province string
    city string
    createTime string
}

type student struct{
    personInfo person
    className string
    address     //匿名结构体
}

func main() {
    p := person{"zhangsan", 18, "",}
    fmt.Printf("%#v\n", p)
    
    stu := newStudent(p, "3班")
    fmt.Printf("%#v\n", stu)
    
    stu.address.province = "浙江省"     //通过 匿名结构体.字段名 来访问
    stu.city = "宁波市"                 //直接访问匿名结构体的字段名
    
    
    stu.address.createTime = "2024-06-05"
    stu.personInfo.createTime = "2024-06-04"        //注意非匿名结构体要通过字段名来访问,也就是这里要用personInfo,不能像address一样直接用结构体名称person
    fmt.Printf("%#v\n", stu)
    
}

func newStudent(p person, cn string) *student{
    return &student{
        personInfo: p,
        className: cn,
    }
}

结构体的”继承“

Go语言中通过嵌套匿名结构体指针也可以实现继承。

package main
import "fmt"

//Animal
type Animal struct {
    name string
}

func (a *Animal) move() {
    fmt.Printf("%s会动!\n", a.name)
}

//Dog
type Dog struct {
    color    string
    *Animal     //通过嵌套匿名结构体实现继承
}

func (d *Dog) wang() {
    fmt.Printf("%s会叫,汪汪汪~\n", d.name)
}

func main() {
    d := &Dog{
        color: "黑色",
        Animal: &Animal{    //注意嵌套的是结构体指针
            name: "小黑",
        },
    }
    d.wang() //小黑会叫,汪汪汪~
    d.move() //小黑会动!
}

结构体与JSON序列化

json序列化是指,将有 key-value 结构的数据类型(比如结构体,map,切片)序列化成json字符串的操作。

package main

import (
	"fmt"
	"encoding/json"
)

//定义一个结构体
type Hero struct{
	Name string `json:"hero_name"` //起别名为:hero_name
	Age int `json:"hero_age"`
	Birthday string
	Sal float64
	Skill string
}
func testStruct()  {

	hero := Hero{
		Name:"张三丰",
		Age:88,
		Birthday:"2009-11-11",
		Sal:8000.0,
		Skill:"教武当剑法!!",
	}
	//将monster序列化
	data, err := json.Marshal(&hero)
	if err != nil{
		fmt.Printf("序列号错误 err=%v\n",err)
	}
	//输出序列化后的结果
	fmt.Printf("序列化后=%v\n",string(data))
}

func main() {
	testStruct()
}

输出结果:

序列化后={"hero_name":"张三丰","hero_age":88,"Birthday":"2009-11-11","Sal":8000,"Skill":"教武当剑法!!"}
  • 14
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值