前言
需求是:有一定的后端其他语言基础(C/C++、Java、Python、PHP等),想要快速了解Golang,可以看B站的这个视频8小时转职Golang工程师
确实讲的很好,对有一定基础的入门golang有很大的帮助,需要代码可以gitee下载
环境
- Winodws10
- Go SDK 1.16.2
- 开发工具:GoLand2020
在电脑安装好Go SDK、设置好系统环境变量
cmd通过go env查看go环境
GOPATH或者GOROOT文件夹下的src目录是放go源文件,我们写的代码都要在src文件夹下,不然导不了第三方包
我选择的是放GOPATH下,但是可能会读取不了GOPATH下的第三方包,需要输入命令:go env -w GO111MODULE=off
,go命令行将不会使用新的module功能,这是GOPATH模式(后续选择GOMODULE模式)
我的项目结构:
用GoLand打开src目录,在其下新建文件夹hello,开始hello world
初见Golang
从hello world开始
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
golang可执行程序可以分解为一个个包,其中必须存在main包,main包必须存在main函数,程序的执行本质上是执行main函数
执行命令是: go run hello.go,当然用GoLand的执行也行
变量
声明局部变量
var a int
声明一个变量,默认值为0var b int = 100
声明一个变量,赋值100var c =100
省去数据类型,值自动匹配当前变量的数据类型d := 100
省去var关键字,直接自动匹配(常用)
声明全局变量
除了4,其他3种都可以
var a int
声明一个变量,默认值为0var b int = 100
声明一个变量,赋值100var c =100
省去数据类型,值自动匹配当前变量的数据类型
多变量声明
- 单行写法
var xx,yy int = 100,200
- 多行写法
// 多行多变量声明
var (
vv int = 100
jj bool = true
)
数据类型
-
布尔型
布尔型的值只可以是常量 true 或者 false。一个简单的例子:var b bool = true。 -
数字类型
整型 int 和浮点型 float32、float64,Go 语言支持整型和浮点型数字,并且支持复数,其中位的运算采用补码。 -
字符串类型:
字符串就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。 -
派生类型:
包括:(a) 指针类型(Pointer)
(b) 数组类型
© 结构化类型(struct)
(d) Channel 类型
(e) 函数类型
(f) 切片类型
(g) 接口类型(interface)
(h) Map 类型
简单案例
package main
import "fmt"
// 声明全局变量
var gA int = 100
var gB = 200
//gC := 300 不支持全局变量
func main() {
// int默认值 = 0
var a int
fmt.Println("a = ",a)
var b int = 100
fmt.Println("b = ",b)
// 初始化可以省略数据变量,自动匹配当前变量的数据类型
var c = 200
fmt.Println("c = ",c)
fmt.Printf("type of c = %T\n",c)
var bb string = "abcd"
var cc = "abcd"
fmt.Printf("bb = %s,type of bb = %T\n",bb,bb)
fmt.Printf("cc = %s,type of cc = %T\n",cc,cc)
// 省略var关键字,直接自动匹配 (常用方法)
e := 100
fmt.Printf("e = %T\n",e)
fmt.Println("e = ",e)
fmt.Println("gA = ",gA," gB = ",gB)
//fmt.Println("gC = ",gC)
// 声明多个变量
var xx,yy int = 100,200
fmt.Println("xx = ",xx," yy = ",yy)
var kk,ll = 200,"ll"
fmt.Println("kk = ",kk," ll = ",ll)
// 多行多变量声明
var (
vv int = 100
jj bool = true
)
fmt.Println("vv = ",vv," jj = ",jj)
}
常量
常量,也就是不可变的变量,在go语言中用const关键字修饰
const length int = 10
const (
BeiJing = 0
ShangHai = 1
ShenZhen = 2
)
iota关键字
如下的枚举类型,可以用iota来简化
const (
BeiJing = 0
ShangHai = 1
ShenZhen = 2
)
iota关键字和枚举数有关,每行会累加1,第一行iota默认为0 (iota 只能出现在const中)
const (
BeiJing = iota
ShangHai
ShenZhen
)
简单案例
package main
import "fmt"
const (
// iota关键字,每行会累加1,第一行iota默认为0 (iota 只能出现在const中)
BeiJing = iota
ShangHai
ShenZhen
/*
BeiJing = 0
ShangHai = 1
ShenZhen = 2
*/
)
const (
a,b = iota + 1,iota + 2 // iota = 0, a=iota + 1= 1,b=iota + 2=2
c,d // iota = 1, c=iota + 1= 2,d=iota + 2=3
e,f // iota = 2
g,h = iota * 2,iota * 3 // iota = 3, g= iota * 2=6,h= iota * 3= 9
i,j // iota = 4, i= iota * 2= 8,j= iota * 3 = 12
)
func main() {
// 常量,不允许被修改
const length int = 10
fmt.Println("length = ",length)
fmt.Println("BeiJin = ",BeiJing)
fmt.Println("ShangHai = ",ShangHai)
fmt.Println("ShenZhen = ",ShenZhen)
fmt.Println("a = ",a,"b = ",b,"c = ",c,"d = ",d,"e = ",e,"f = ",f,"g = ",g,"h = ",h,"i = ",i,"j = ",j)
}
函数
函数func,是程序的基本代码块,程序的执行本质上是执行main函数
函数结构
func (recvarg type) funcname( [parameter list] ) [return_types] {
// 函数体
}
函数的结构:
- 关键字 func 用于定义一个函数
- (recvarg type)用于指定此函数可用于的类型,如果设置了,当前函数就是该类型的成员方法(类似于Java中类的方法 - 详情看后续构造体)
- funcname 为函数名
- [parameter list]参数列表,函数为值传递(如果需要地址传递,参数需设置为指针)
- [return_types] 为函数命名返回值列表,可以只有类型而不命名,可以有 0 个或者多个返回值
- 剩下的为函数体,左花括号必须在同一行,不能新起一行
func foo1(a string,b int)int {
fmt.Println("a = ",a)
fmt.Println("b = ",b)
c := 100
return c
}
func (this *Hero) Show(){
fmt.Println("Name = ",this.Name)
}
函数返回值
go语言函数可以有多个返回值
- 无返回值函数
func foo(a string){
fmt.Println("a = ",a)
}
- 一个返回值
func foo1(a string,b int)int {
fmt.Println("a = ",a)
fmt.Println("b = ",b)
c := 100
return c
}
- 多返回值:有三种形式
// 返回多个返回值,匿名
func foo2(a string,b int) (int,int) {
fmt.Println("a = ",a)
fmt.Println("b = ",b)
c := 100
return c,c*3
}
// 返回多个返回值,有形参名
func foo3(a string,b int) (r1 int,r2 int) {
fmt.Println("a = ",a)
fmt.Println("b = ",b)
// 可以通过直接赋值
r1 = 100
r2 = 200
return
}
// 同类型返回值可以缩写
func foo4(a string,b int) (r1 , r2 int){
fmt.Println("a = ",a)
fmt.Println("b = ",b)
r1 = 100
r2 = 200
return
}
import导包
当我们想要使用外部函数,通过import关键字导入包
注:Go中函数名/变量名 首字母大写,表示所有包都可访问,首字母小写表示包私有
// 导入单个包
import "fmt"
// 导入多个包
import (
"init/lib1"
"init/lib2"
)
fmt包是SDK提供的,Printf等函数在print.go文件里
别名
当import的包名过长,可以取别名
- 匿名:导入了包,但是无法使用当前包的方法,会执行包内部的init方法
import (
// _ 表示匿名别名,导入但并不使用
_ "init/lib1"
)
- 别名
import (
// _ 表示匿名别名,导入但并不使用
_ "init/lib1"
// mylib2表示别名
mylib2 "init/lib2
)
- 将包中的方法导入到当前包,可以直接调用方法,而不需要fmt.Printf这样
包名.函数名
调用,尽量别用,容易歧义,容易导致多个函数同名报错
import (
// _ 表示匿名别名,导入但并不使用
_ "init/lib1"
// mylib2表示别名
//mylib2 "init/lib2"
// .表示导入到当前包中
. "init/lib2"
)
案例
lib1.go
package lib1
import "fmt"
func Lib1Test() {
fmt.Println("lib1 test")
}
lib2.go
package lib2
import "fmt"
func Lib2Test() {
fmt.Println("lib2 test")
}
main.go
package main
import (
"init/lib1"
"init/lib2"
)
func main() {
lib1.Lib1Test()
lib2.Lib2Test()
}
init函数
Go程序函数启动顺序:
如果有多个init函数呢?
go允许定义多个init()函数,从上往下按照定义顺序执行
package main
import "fmt"
func init(){
fmt.Println("init 1")
}
func init() {
fmt.Println("init 2")
}
func init() {
fmt.Println("init 3")
}
func init() {
fmt.Println("init 4")
}
func main() {
fmt.Printf("main")
}
指针
在go中,函数都是值传递
可以使用指针地址传递,写法:函数参数p *int
,函数传入指针&a
package main
import "fmt"
func change(p *int) {
*p = 10
}
func main(){
a := 1
// 指针传递
change(&a)
fmt.Println("a = ",a)
}
defer
defer关键字:可以使函数或者操作压栈,当前函数生命周期结束时,依次出栈
defer在return执行完后
package main
import "fmt"
func foo1() int {
fmt.Println("func defer")
return 0
}
func foo2() int {
fmt.Println("func return")
return 1
}
func re() int {
defer foo1()
return foo2()
}
func main(){
re()
// 写入defer关键字,通过压栈实现,当前函数main函数生命周期结束时,依次出栈
defer fmt.Println("main end1")
defer fmt.Println("main end2")
fmt.Println("main :: hello go 1")
fmt.Println("main :: hello go 2")
}
切片slice
slice,也就是动态数组
数组
-
数组的长度是固定的,在传参时要严格匹配数组类型
注:int[4],int[10]等不同长度的数组是 不同数组类型 -
数组作为参数是值传递
定义数组:
var myArr1 [10]int
固定长度数组,初始值为0myArr2 := [10]int{1,2,3,4}
package main
import "fmt"
func printArr(myArr [4]int) {
myArr[3] = 100
for index,value := range myArr{
fmt.Println("index = ",index," value = ",value)
}
}
func main() {
var myArr1 [10]int
for i := 0;i < len(myArr1);i++{
fmt.Print(myArr1[i]," ")
}
myArr2 := [10]int{1,2,3,4}
for index,value := range myArr2{
fmt.Println("index = ",index," value = ",value)
}
fmt.Println("====================")
myArr3 := [4]int{11,22,33,44}
// 数组是值传递,且[4]int和[10]int类型不同
//printArr(myArr1)
printArr(myArr3)
fmt.Println("====================")
for index,value := range myArr3{
fmt.Println("index = ",index," value = ",value)
}
}
动态数组slice
初始化
可以设置一个不限长度的动态数组:
myArr1 := []int{1,2,3,4}
开辟4个数据空间的数组,数组类型为[]int
var slice1 []int
声明一个动态数组,不分配数据空间,可以后续通过make()
函数分配数据空间slice2 := make([]int,3)
声明动态数组并分配数据空间
动态数组是地址传递:
package main
import "fmt"
func printArr(myArr []int) {
// _表示匿名变量
for _,value := range myArr{
fmt.Println("value = ",value)
}
// 地址传递
myArr[0] = 100
}
func main() {
// 动态数组,切片slice
myArr1 := []int{1,2,3,4}
fmt.Printf("myArr1 type is %T\n",myArr1)
// 检验是值传递还是地址传递
printArr(myArr1)
fmt.Println("---------------")
for _,value := range myArr1{
fmt.Println("value = ",value)
}
fmt.Printf("len = %d ,slice = %v\n",len(myArr1),myArr1)
// 声明slice1是一个切片,但并没有给slice1分配空间
var slice1 []int
// make函数开辟3个空间,初始值是0
slice1 = make([]int,3)
slice1[0] =100
fmt.Printf("len = %d ,slice = %v\n",len(slice1),slice1)
// 声明slice2同时分配空间
slice2 := make([]int,3)
fmt.Printf("len = %d ,slice = %v\n",len(slice2),slice2)
}
扩容
通过append给动态数组追加元素,数组会自动扩容
make()
用来为 slice,map 或 chan 类型分配内存和初始化一个对象(注:只能用在这三种类型上),它有三个参数:
- 第一个参数是类型
- 第二个参数是分配的空间
- 第三个参数是预留分配空间
package main
import "fmt"
func main() {
numbers := make([]int,3,5)
fmt.Printf("len = %d, cap = %d , slice = %v\n",len(numbers),cap(numbers),numbers)
// 向numbers切片追加元素1
numbers = append(numbers,1)
fmt.Printf("len = %d, cap = %d , slice = %v\n",len(numbers),cap(numbers),numbers)
fmt.Println("===============")
numbers2 := make([]int,3)
fmt.Printf("len = %d, cap = %d , slice = %v\n",len(numbers2),cap(numbers2),numbers2)
numbers2 = append(numbers2,1)
fmt.Printf("len = %d, cap = %d , slice = %v\n",len(numbers2),cap(numbers2),numbers2)
}
如上,当make预留的空间不够,就会自动扩容,新的数组长度会是原来的两倍
切片截取
如果只需要数组中的一段,可以截取切片s2 := s[1:2]
切片截取也是地址传递
package main
import "fmt"
func main() {
s := []int{1,2,3}
// 切片截取
s1 := s[:1]
fmt.Println(s1)
s2 := s[1:2]
fmt.Println(s2)
// 检验,地址传递
s[1] = 20
fmt.Println(s2)
}
map
声明map
var myMap1 map[string]string
,后续通过make函数分配数据空间myMap2 := make(map[string]string)
不确定数据空间大小myMap3 := map[string]string{ "1" : "java", "2" : "c++", "3" : "python", }
直接赋值
package main
import "fmt"
func main() {
// 1. ====> 声明map
var myMap1 map[string]string
// 开辟数据空间
myMap1 = make(map[string]string,10)
myMap1["one"] = "java"
myMap1["two"] = "c++"
myMap1["three"] = "python"
fmt.Println(myMap1)
// 2.======> 不确定数据空间大小
myMap2 := make(map[string]string)
myMap2["1"] = "java"
myMap2["2"] = "c++"
fmt.Println(myMap2)
//3. ======> 直接赋值
myMap3 := map[string]string{
"1" : "java",
"2" : "c++",
"3" : "python",
}
fmt.Println(myMap3)
}
增删改查
- map是地址传递
delete()
函数用于删除集合的某个元素,参数为map和其对应的key
package main
import "fmt"
func main() {
cityMap := make(map[string]string)
cityMap["China"] = "Beijing"
cityMap["USA"] = "NewYork"
cityMap["England"] = "EG"
// 遍历
for key,value := range cityMap{
fmt.Println("key = ",key," value =",value)
}
//删除
delete(cityMap,"China")
// 修改
changeMap(cityMap)
fmt.Println("===============")
// 遍历
for key,value := range cityMap{
fmt.Println("key = ",key," value =",value)
}
}
func printMap(cityMap map[string]string){
// 遍历
for key,value := range cityMap{
fmt.Println("key = ",key," value =",value)
}
}
func changeMap(cityMap map[string]string) {
// 地址传递
cityMap["England"] = "London"
}
面对对象
面对对象是一种思想,可以说go语言是一门有面对对象思想的语言
官网中对于Go是否为一门面向对象的语言这个问题的表述为:
是,也不是.虽然Go语言可以通过定义类型和方法来实现面向对象的设计风格,但是Go是实际上并没有继承这一说法.在Go语言中,interface(接口)这个概念以另外一种角度展现了一种更加易用与通用的设计方法.在Go中,我们可以通过组合,也就是将某个类型放入另外的一个类型中来实现类似继承,让该类型提供有共性但不相同的功能.相比起C++和Java,Go提供了更加通用的定义函数的方法,我们可以指定函数的接受对象(receiver),它可以是任意的类型,包括内建类型,在这里没有任何的限制.
同样的,没有了类型继承,使得Go语言在面向对象编程的方面会显得更加轻量化
封装
不像Java中有类的概念,Go类似于C++,用结构体来表示对象
首字母大小表示改结构体、属性是公有
// 定义一个结构体
// 结构体首字母大写,其他包也可以访问
type Hero struct {
// 属性名首字母大写,为公有属性
Name string
Ad int
Level int
}
通过func (this *Hero)
指定函数用与Hero类型,表示成员方法,如果func (this Hero)
是值传递
package main
import "fmt"
// 结构体首字母大写,其他包也可以访问
type Hero struct {
// 属性名首字母大写,为公有属性
Name string
Ad int
Level int
}
/* // 当前this是调用该方法对象的一个副本
func (this Hero) Show(){
fmt.Println("Name = ",this.Name)
}
func (this Hero) GetName() string{
return this.Name
}
func (this Hero) SetName(newName string){
this.Name = newName
}*/
func (this *Hero) Show(){
fmt.Println("Name = ",this.Name)
}
func (this *Hero) GetName() string{
return this.Name
}
// 当前this是调用该方法对象的一个副本
func (this *Hero) SetName(newName string){
this.Name = newName
}
func main() {
hero := Hero{Name: "zhangsan",Ad: 100,Level: 10}
hero.Show()
hero.SetName("lisi")
hero.Show()
}
继承、重写
子类就是在结构体中包含父类,继承父类的方法和属性
type Human struct {
Name string
Sex string
}
type SuperMan struct {
Human //SuperMan继承Human的方法和属性
level int
}
func (this *SuperMan) Eat
方法和父类同名,即重写父类方法
package main
import "fmt"
/**
继承、重写
*/
type Human struct {
Name string
Sex string
}
func (this *Human) Eat() {
fmt.Println("Human eat....")
}
func (this *Human) Walk() {
fmt.Println("Human walk....")
}
type SuperMan struct {
Human
level int
}
// 重写父类eat方法
func (this *SuperMan) Eat(){
fmt.Println("SuperMan eat...")
}
// 子类新方法
func (this *SuperMan) Fly(){
fmt.Println("SuperMan fly...")
}
func (this *SuperMan) print(){
fmt.Println("name = ",this.Name)
fmt.Println("sex = ",this.Sex)
fmt.Println("level = ",this.level)
}
func main() {
h := Human{"zhangsan","man"}
h.Eat()
h.Walk()
// 定义子类对象
//superMan := SuperMan{Human{"lisi","woman"},10}
var superMan SuperMan
superMan.Name = "wangwu"
superMan.level = 30
superMan.Sex = "man"
// 调用子类重新的Eat方法
superMan.Eat()
superMan.print()
}
多态
接口
interface 关键字定义一个接口,本质上是指针
// 本质上是指针
type AnimalIF interface {
Sleep()
GetColor() string
GetType() string
}
一个结构体如果实现了接口的所有方法,即可以向上转型为该接口类型
结构体Cat
type Cat struct {
color string
}
func (this *Cat) Sleep(){
fmt.Println("Cat is sleep")
}
func (this *Cat) GetColor() string{
return this.color
}
func (this *Cat) GetType() string{
return "Cat"
}
结构体Cat实现了接口的所有方法,即可以向上转型为AnimalIF
var animal AnimalIF // 接口的数据类型
cat := Cat{"Black"}
animal = &cat
完整案例:
package main
import "fmt"
/**
多态
*/
// 本质上是指针
type AnimalIF interface {
Sleep()
GetColor() string
GetType() string
}
type Cat struct {
color string
}
func (this *Cat) Sleep(){
fmt.Println("Cat is sleep")
}
func (this *Cat) GetColor() string{
return this.color
}
func (this *Cat) GetType() string{
return "Cat"
}
type Dog struct {
color string
}
func (this *Dog) Sleep(){
fmt.Println("Dog is sleep")
}
func (this *Dog) GetColor() string{
return this.color
}
func (this *Dog) GetType() string{
return "Dog"
}
func ShowAnimal(animal AnimalIF){
animal.Sleep()
fmt.Println("color = ",animal.GetColor())
fmt.Println("type = ",animal.GetType())
}
func main() {
/* var animal AnimalIF // 接口的数据类型
cat := Cat{"Black"}
animal = &cat
animal.Sleep()
animal = &Dog{"Yellow"}
animal.Sleep()*/
cat := Cat{"Black"}
dog := Dog{"Yellow"}
ShowAnimal(&cat)
ShowAnimal(&dog)
}
interface
interface{}
可以说是通用类型
func myFunc(arg interface{}){}
函数可以传入任何类型- 类型断言 :
value,ok := arg.(string)
,断言成功,ok为true,value为转换为该类型的值
package main
import "fmt"
/**
interface{}是万能类型
*/
func myFunc(arg interface{}){
fmt.Println("myFunc is called....")
fmt.Println(arg)
//interface区分不同数据类型
// 类型断言 机制
value,ok := arg.(string)
if !ok {
fmt.Println("arg is not string...")
}else {
fmt.Println("arg is string,value = ",value)
fmt.Printf("value tyep is %T\n",value)
}
}
type Book struct {
auth string
}
func main() {
book := Book{"go"}
myFunc(book)
myFunc("100")
myFunc(3.14)
myFunc("hello")
}
反射
反射:在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并不知道这些变量的具体类型
反射功能强大,通过反射可以得到丰富的类型信息
变量的结构pair
变量(pair)=type+value
其中type就是数据类型,也就是前面说的4种-bool、数字类型、字符串类型、派生类型
package main
import "fmt"
/**
变量结构:type+value (叫pair)
*/
func main() {
// pair<(static)type:string,vlaue="abc">
var a string = "abc"
// pair<(static)type:string,vlaue="abc">
var allType interface{}
allType = a
value,ok := allType.(string)
if ok {
fmt.Println(value)
}
}
reflect包的简单使用
- 可以通过reflect类反射获得pair的Type、Value
reflect.TypeOf
得到Type,reflect.ValueOf
得到Value
package main
import (
"fmt"
"reflect"
)
/**
反射
*/
func reflectNum(arg interface{}){
fmt.Println("type",reflect.TypeOf(arg))
fmt.Println("value",reflect.ValueOf(arg))
}
func main() {
var num float64 = 1.234
reflectNum(num)
}
- 得到Filed
关于reflect的各种方法可以去GoStudy网查看
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (this User) Call(){
fmt.Println("user is called....")
fmt.Println("%v\n",this)
}
func main() {
user := User{1,"zhangsan",23}
DoFiledAndMthod(user)
}
func DoFiledAndMthod(input interface{}){
inputType := reflect.TypeOf(input)
//fmt.Println("input type is :",inputType.Name())
inputValue := reflect.ValueOf(input)
//fmt.Println("input value = ",inputValue)
// 获取属性字段
//1. reflect.Type得到NumField,遍历
//2. 得到每一个Field,数据类型
//3 .通过field的Interface()得到value
for i:=0;i <inputType.NumField();i++{
field := inputType.Field(i)
value := inputValue.Field(i).Interface()
fmt.Printf("%s : %v = %v\n",field.Name,field.Type,value)
}
for i := 0;i < inputType.NumMethod(); i++ {
m := inputType.Method(i)
fmt.Printf("%s : %v\n",m.Name,m.Type)
}
}
结构体标签
在结构体类型后,可以加上键值对标签
注:最外的是esc键下面的那个引号,值需要双引号
type resume struct{
Name string `info:"name" doc:"我的名字"`
Sex string `info:"sex"`
}
再通过反射可以拿到结构体的标签值:
package main
import (
"fmt"
"reflect"
)
/**
反射得到结构体Tag
*/
type resume struct{
Name string `info:"name" doc:"我的名字"`
Sex string `info:"sex"`
}
func findTag(str interface{}) {
t := reflect.TypeOf(str).Elem()
for i:= 0;i<t.NumField();i++{
tagInfo := t.Field(i).Tag.Get("info")
tagDoc := t.Field(i).Tag.Get("doc")
fmt.Println("info : ",tagInfo)
fmt.Println("Doc : ",tagDoc)
}
}
func main() {
var re resume
findTag(&re)
}
应用
标签的作用类似于别名,在json解析时,可以用json:"title"
作为某个属性的tag,title替代json的key
type Movie struct{
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"price"`
Actors []string `json:"actors"`
}
该结构体转成json时,key是tag中的别名
package main
import (
"encoding/json"
"fmt"
)
type Movie struct{
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"price"`
Actors []string `json:"actors"`
}
func main() {
movie := Movie{"喜剧之王",2000,10,[]string{"xingye","zhangbozhi"}}
//结构体 -》json
jsonStr,err := json.Marshal(movie)
if err!= nil {
fmt.Println("json marshal error",err)
return
}
fmt.Printf("jsonStr = %s\n",jsonStr)
// json ->结构体
myMovie := Movie{}
err = json.Unmarshal(jsonStr,&myMovie)
if err != nil {
fmt.Println("json marshal error",err)
return
}
fmt.Println(myMovie)
}
如果没有加标签json中的key是结构体中属性名