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 mainfunc 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 mainimport ("fmt"
)func main() {var power int //定义了一个 int 类型的变量 powerpower = 9000//以上两行可以合并为//var power int = 9000fmt.Printf("It's over %d\n", power)
}
方法二:使用:=
Go 提供了一个方便的短变量声明运算符 :=
,它可以自动推断变量类型,但是这种声明运算符只能用于局部变量,不可用于全局变量:
power := 9000
注意:=
表示的是声明并赋值,所以在相同作用域下,相同的变量不能被声明两次
此外, go 支持多个变量同时赋值(使用 =
或者 :=
):
func main() {name, power := "Goku", 9000fmt.Printf("%s's power is over %d\n", name, power)
}
多个变量赋值的时候,只要其中有一个变量是新的,就可以使用:=
。例如:
func main() {power := 1000fmt.Printf("default power is %d\n", power)name, power := "Goku", 9000fmt.Printf("%s's power is over %d\n", name, power)
}
注意:Go 不允许在程序中有声明了但未使用的变量。例如:
func main() {name, power := "Goku", 1000fmt.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,默认是lens1 := slice[1:2] //x=1,y=2,z=6 len=1 cap=5fmt.Println(s1)fmt.Println("cap of s1", cap(s1))s2 := slice[:3:4] //x=0,y=3,z=4 len=3 cap=4fmt.Println(s2)fmt.Println("cap of s2", cap(s2))s3 := slice[1:3:5] //x=1,y=3,z=5 len=2 cap=4fmt.Println(s3)fmt.Println("cap of s3", cap(s3))}
切片的内存布局
读写操作实际目标是底层数组,只需注意索引号的差别。
append内置函数
append :向 slice 尾部添加数据,返回新的 slice 对象。
package mainimport ("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 mainimport ("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 0xc00000c060second append:
arr: [0 0 0 100 0] slice: [0 0 0 100 200]
0xc000072030 0xc00007e040
copy
函数 copy 在两个 slice 间复制数据,复制长度以 len 小的为准。两个 slice 可指向同一底层数组,允许元素区间重叠。
package mainimport ("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 |
2 | new关键字 | m := *new(map[string]string) |
3 | make关键字 | 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 mainimport "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为truevalue, ok := amap["xz"]fmt.Println("value:", value, "ok:", ok)//也可以在声明的时候填充元素intmap := map[string]int{"1": 1,"2": 2, //注意这个逗号一定要在}intmap["3"] = 3for 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 mainimport "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的mapuserInfo := 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 mapmapSlice[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 mainimport "fmt"func main() {//sliceMap的key为string,value为string类型的slicesliceMap := 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 stringage int32address 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 personperson2.name = "xiaoli"person2.age = 20person2.address = "shanghai"fmt.Printf("%#v\n", person2)
}
使用new关键字来实例化
可以通过使用new关键字对结构体进行实例化,得到的是一个结构体类型的指针。 格式如下:
package main
import "fmt"type person struct{name stringage int32address 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 stringage int32address 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"}
使用值的列表初始化
这种方式要注意:
- 必须初始化结构体的所有字段。
- 初始值的填充顺序必须与字段在结构体中的声明顺序一致。
- 该方式不能和键值初始化方式混用。
func main() {person5 := person{"xiaozhao", 28, "chongqing",}fmt.Printf("type of person5: %T\n", person5) //type of person5: main.personfmt.Printf("%#v\n", person5) //main.person{name:"xiaozhao", age:28, address:"chongqing"}
}
匿名结构体
在定义一些临时数据结构等场景下还可以使用匿名结构体。
package mainimport ("fmt"
)func main() {var user struct{name string; age int}user.name = "xiaozhanguser.age = 18fmt.Printf("%#v\n", user)
}
结构体的内存布局
构造函数
Go语言的结构体没有构造函数,我们可以自己实现。 例如,下方的代码就实现了一个person的构造函数。 因为struct是值类型,如果结构体比较复杂的话,值拷贝性能开销会比较大,所以该构造函数返回的是结构体指针类型。
package main
import "fmt"type person struct{name stringage int32address 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 (接收者变量 接收者类型) 方法名(参数列表) (返回参数) {函数体
}
其中:
- 接收者变量:接收者中的参数变量名在命名时,官方建议使用接收者类型名的第一个小写字母,而不是self、this之类的命名。例如,Person类型的接收者变量应该命名为 p,Connector类型的接收者变量应该命名为c等。
- 接收者类型:接收者类型和参数类似,可以是指针类型和非指针类型。
- 方法名、参数列表、返回参数:具体格式与函数定义相同。
方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。
package main
import "fmt"type person struct{name stringage int32address 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 stringage int32address 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"}
可见值类型的接收者无法改变实例的成员值
什么时候应该使用指针类型接收者?
- 需要修改接收者中的值
- 接收者是拷贝代价比较大的大对象
- 保证一致性,如果有某个方法使用了指针接收者,那么其他的方法也应该使用指针接收者。
任意类型添加方法
在Go语言中,接收者的类型不仅仅是结构体,任何类型都可以拥有方法。 举个例子,我们基于内置的int类型使用type关键字可以定义新的自定义类型,然后为我们的自定义类型添加方法
注意: 非本地类型不能定义方法,也就是说我们不能给别的包的类型定义方法。
package main
import "fmt"type myInt int func main() {var i myInt = 10i.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 stringage int
}type student struct{personInfo personclassName 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 stringage int
}type address struct{province stringcity string
}type student struct{personInfo personclassName stringaddress //匿名结构体
}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 stringage intcreateTime string
}type address struct{province stringcity stringcreateTime string
}type student struct{personInfo personclassName stringaddress //匿名结构体
}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一样直接用结构体名称personfmt.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 mainimport ("fmt""encoding/json"
)//定义一个结构体
type Hero struct{Name string `json:"hero_name"` //起别名为:hero_nameAge int `json:"hero_age"`Birthday stringSal float64Skill 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":"教武当剑法!!"}