目录
10、map类型(类似python的dict类型,key:value)
比较适合已经有过一门比较熟悉的编程语言的人阅读
1、window安装go环境
2. 配置window下的环境变量,在path中进行编辑,加入go包存放的位置,例如我放在D盘,所以window环境变量为:
3. 配置GOPATH,该路径为go工程存放的目录
4. 配置自己喜爱的编辑器ide,这里推荐goland(收费的),vscode(免费的)。
5. go目录下的src目录,该目录下用于项目的源码。
6. go中文说明文档
2、基础程序,打印hello,world
package main
import (
"fmt"
"time")
func main() {
fmt.Println("Hello, World!")
time.Sleep(1 * time.Second)
}
3、声明变量的4种方式
- 常用的方式:":="的方式最常用,用于推测值为什么类型,变量就定为什么类型;
- ":="的方式不能用于全局变量的声明,变量的初始值为0;
// 四种变量声明方式
package main
import "fmt"
// 声明全局变量的时候,方法一、二、三是可以的
var gA int = 100
var gB = 200
// 方法四:=声明,只能用在函数里声明
func main() {
// 方法一:声明变量,默认值为0
var a int
fmt.Println("a=", a)
fmt.Printf("type of a = %T\n", a)
// 方法二:声明一个变量,初始化值
var b int = 100
fmt.Println("b = ", b)
fmt.Printf("type of b = %T\n", b)
// 方法三,省去数据类型,自己匹配变量类型
var c = 100
fmt.Println("c = ", c)
fmt.Printf("type of c = %T\n", c)
// 方法四:省略var关键字,自动匹配类型
e := 100.0
fmt.Println("e = ", e)
fmt.Printf("type is %T\n", e)
// 打印全局
fmt.Println("gA = ", gA, "gB = ", gB)
// 声明多个变量
var xx, yy int = 100, 200
fmt.Println(xx, yy)
var cc, dd = "hello", "hadoop"
fmt.Println(cc, dd)
aa, bb := "hello", 12
fmt.Println(aa, bb)
// 多行的多变量声明
var (
vv int = 100
jj bool = true
)
fmt.Println(vv, jj)
}
4、const常量
- const关键字用于声明常量,只读,不可修改;
- go中的const还有一个iota关键字,用于定义枚举,第一行iota默认为0,逐下+1
package main
import "fmt"
// const 来定义枚举
const (
// 可以在const()添加一个关键字,每行的iota都会累加1,第一行iota默认值为0,iota只能用于const中使用
BEIJIN = iota * 10
SHANGHAI
SHENZHENG
CD = iota + 1
JD
TB
)
func main() {
// 常量,只读属性
const lenght int = 100
fmt.Println("lenght = ", lenght)
fmt.Println(BEIJIN) // 0
fmt.Println(SHANGHAI) // 10
fmt.Println(SHENZHENG) // 20
fmt.Println(CD) // 4
fmt.Println(JD) // 5
fmt.Println(TB) // 6
}
package main
import "fmt"
// go中没有枚举类型,但是我们可以使用const + iota(常量累加器)进行模拟
const (
MONDAY = iota
TUESDAY
WEDNESDAY
THURSDAY
FRIDAY
SATURDAY
SUNDAY
M, N = iota, iota // const为预编译,所有不用:=
)
/*
1、 iota是常量组计数器
2、iota从0开始,每一次换行+1
3、如果不赋值,默认和上一行表达式相同
4、如果在同一行出现两个iota,那么值相同
5、如果遇到const, iota会重新清零
*/
func main12() {
fmt.Println(SUNDAY) // 6
fmt.Println(M, N)
}
5、函数
package main
import "fmt"
// go中,禁止函数使用默认参数
// 变量类型写在变量后面,函数名后面写类型,表示函数返回的类型
func fool(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, d := 100, 222
return c, d
}
// 有名返回值
func foo3(a string, b int) (r1 int, r2 int) {
fmt.Println("--------foo3--------")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
// 如果r1,r2没有赋值,则会默认初始化为0
// r1, r2 = 100, 222
return
}
// 有名返回值,返回类型都相同
func foo4(a string, b int) (r1, r2 int) {
fmt.Println("--------foo4--------")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
r1, r2 = 100, 222
return
}
func main() {
c := fool("aa", 123)
fmt.Println(c)
/*
a = aa
b = 123
100
*/
d, e := foo2("abc", 321)
fmt.Println("foo2 is ", d, e)
/*
a = abc
b = 321
foo2 is 100 222
*/
r1, r2 := foo3("aaa", 89)
fmt.Println(r1, r2)
/*
--------foo3--------
a = aaa
b = 89
0 0
*/
r3, r4 := foo4("aaa", 89)
fmt.Println(r3, r4)
/*
--------foo4--------
a = aaa
b = 89
100 222
*/
}
6、自定义go包
注意点:自定义的go包要放入go安装目录下,src目录下(展示了解到这些,还需继续学习)
自定义lib1:
package lib1
import "fmt"
// 当前lib1包提供的API
func Lib1Test() {
fmt.Println("lib1Test().....")
}
func init() {
fmt.Println("lib1 init() 被执行了")
}
自定义lib2:
package lib2
import "fmt"
// 当前lib2包提供的API
func Lib2Test() {
fmt.Println("lib2Test().....")
}
func init() {
fmt.Println("lib2 init() 被执行了")
}
1. 导包成功自动调用初始化init函数
package main
// 因为自定义的包,要放入go的src目录下,才能调用成果
// go下的src,用于存放go的包
import (
"my_go/lib1"
"my_go/lib2"
)
func main() {
lib1.Lib1Test()
lib2.Lib2Test()
}
/*
执行结果
lib1 init() 被执行了
lib2 init() 被执行了
lib1Test().....
lib2Test().....
(1)包内的函数名开头大写,才是对外开放的方法
(2)包内的函数名开头小写,才是对外不开放的方法,只能在包本身内调用
*/
2. 导包的方式(匿名,起别名,.导包)
package main
// 因为自定义的包,要放入go的src目录下,才能调用成果
// go下的src,用于存放go的包
import (
// 匿名导包,因为在go中,导包,如果不使用则会报错,匿名导包可以解决这个问题,但是不能使用包内的方法
_ "my_go/lib1"
// "my_go/lib2"
// 给包起别名
// mylib "my_go/lib2"
// 用.导包,可以直接使用包中的方法
. "my_go/lib2"
)
func main() {
// lib1.Lib1Test()
// 别名包调用方法
// mylib.Lib2Test()
Lib2Test()
}
/*
执行结果
lib1 init() 被执行了
lib2 init() 被执行了
lib2Test().....
*/
7、指针
package main
import "fmt"
// 指针类型
func swap(a *int, b *int) {
var tmp int
tmp = *a
*a = *b
*b = tmp
}
func main() {
var a int = 10
var b int = 20
// 传入地址
swap(&a, &b)
fmt.Println("a = ", a, "b = ", b)
}
8、defer函数(类似C++中的析构函数)
- defer函数会在一个函数生命周期结束后,自动调用
- 执行顺序:类似栈,入栈和出栈的顺序
package main
import "fmt"
func main() {
// defer关键字,类似析c++构函数,在函数执行结束后,自动触发调用
// 调用顺序,和栈一样,先进后出
defer fmt.Println("main end1")
defer fmt.Println("main end2")
fmt.Println("main:hello go1")
fmt.Println("main:hello go2")
/*
执行结果
main:hello go1
main:hello go2
main end2
main end1
*/
}
3. return表示函数,所以return结束后,才会调用defer
// return 和 defer谁先调用
// defer会在函数生命周期完全结束后,才会调用
package main
import "fmt"
func deferFunc() int {
fmt.Println("defer func called....")
return 0
}
func returnFunc() int {
fmt.Println("return func called....")
return 0
}
func returnAndDefer() int {
defer deferFunc()
return returnFunc()
}
func main() {
returnAndDefer()
}
/*
执行结果
return func called....
defer func called....
*/
9、数组(array)和动态数组(slice切片)
1. 固定长度的数组,在函数里发生传递后,只会发生值拷贝,而不是传递引用;
package main
import "fmt"
func printArray(myArray [4]int) {
// 固定的数组,只会发生值拷贝,这里的修改不会影响myArray原本的值
for index, value := range myArray {
fmt.Println("index is ", index, "value is ", value)
}
myArray[0] = 99
}
func main() {
// 固定长度的数组
var myArray [10]int
for i := 1; i < len(myArray); i++ {
fmt.Println(myArray[i])
}
/*
0
0
0
0
0
0
0
0
0
*/
myArray2 := [10]int{1, 2, 3, 4}
for index, value := range myArray2 {
fmt.Println("index = ", index, "value = ", value)
}
/*
index = 0 value = 1
index = 1 value = 2
index = 2 value = 3
index = 3 value = 4
index = 4 value = 0
index = 5 value = 0
index = 6 value = 0
index = 7 value = 0
index = 8 value = 0
index = 9 value = 0
*/
// 查看数组的数据类型
fmt.Printf("myArray1 types = %T\n", myArray)
fmt.Printf("myArray2 types = %T\n", myArray2)
/*
myArray1 types = [10]int
myArray2 types = [10]int
*/
myArray3 := [4]int{11, 22, 33, 44}
printArray(myArray3)
/*
index is 0 value is 11
index is 1 value is 22
index is 2 value is 33
index is 3 value is 44
*/
fmt.Println("-----------")
printArray(myArray3)
/*
-----------
index is 0 value is 11
index is 1 value is 22
index is 2 value is 33
index is 3 value is 44
*/
}
2. 动态数组,也就是不先声明数组长度,使用":="进行推导,发生函数的参数传递时,传递的是引用;
package main
import "fmt"
func printArray(myArray []int) {
// _表示匿名,也就是无法访问
for _, value := range myArray {
fmt.Println("value is ", value)
}
// 动态数组会进行,引用传递
myArray[0] = 100
}
func main() {
// 动态数组,切片(slice类型)
myArray := []int{1, 2, 3, 4}
fmt.Printf("type is %T\n", myArray)
printArray(myArray)
/*
type is []int
value is 1
value is 2
value is 3
value is 4
*/
fmt.Println("---------")
printArray(myArray)
/*
---------
value is 100
value is 2
value is 3
value is 4
*/
}
3. 动态数组的声明方式(4种)
package main
import "fmt"
func main() {
// 方法一,声明slice是一个切片,并且初始化,默认值为1,2,3,长度len是3
// slice1 := []int{1, 2, 3}
// 方法二,声明slice是一个切片,但是并没给slice分配空间
var slice1 []int
// slice1[0] = 1 会报错,因为slice1没有空间
// 开辟空间
// slice1 = make([]int, 3)
// 方法三,
// var slice1 []int = make([]int, 3)
// 方法四,:=自动推到出
// slice1 := make([]int, 3)
fmt.Printf("len = %d, slice = %v\n", len(slice1), slice1)
// 判断一个slice是否为空
if slice1 == nil {
fmt.Println("slice1 是空的")
} else {
fmt.Println("slice1 是有空间的")
}
}
4. 动态数组的追加元素和扩容
package main
import "fmt"
func main() {
var numbers = make([]int, 3, 5)
// cap为容量, len = 3, cap = 5, slice = [0 0 0]
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
// 追加元素1, len = 4, cap = 5, slice = [0 0 0 1]
numbers = append(numbers, 1)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
// 追加元素2, len = 5, cap = 5, slice = [0 0 0 1 1]
numbers = append(numbers, 1)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
// 空间满后继续追加元素,slice自动扩容,会一次性扩容一个上一次cap的容量
// len = 6, cap = 10, slice = [0 0 0 1 1 3]
numbers = append(numbers, 3)
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers), cap(numbers), numbers)
var numbers2 = make([]int, 3)
// len = 3, cap = 3, slice = [0 0 0],不声明cap,自动把len定为cap
fmt.Printf("len = %d, cap = %d, slice = %v\n", len(numbers2), cap(numbers2), numbers2)
}
5. 数组的截取(类似python的切片)
package main
import "fmt"
func main() {
s := []int{1, 2, 3} //len=3, cap=3
// 数组的截取
s1 := s[0:2]
fmt.Printf("s1 is %v\n", s1) // s1 is [1 2]
// 如果s1发生改变,s也会发生改变
s1[0] = 100
fmt.Printf("s1 is %v\n", s1) // s1 is [100 2]
fmt.Printf("s is %v\n", s) // s is [100 2 3]
// copy函数,可以将底层的数组slice一起拷贝
s2 := make([]int, 4) // s2 = [0,0,0]
// 将s中的值依次拷贝到s2,,这种方法s和s2指向的是不同空间
copy(s2, s)
fmt.Printf("s2 is %v\n", s2) // s2 is [100 2 3 0]
}
package main
import "fmt"
func main8() {
names := [7]int{1, 2, 3, 4, 5, 6, 7}
// 基于names创建一个新数组
// 切片可以基于一个数组,灵活的创建新的数组
// 这种方式为浅拷贝,修改names1的时候names也会变
names1 := names[:4]
names1[0] = 100
fmt.Println(names) // [100 2 3 4 5 6 7]
// 如果想让切片完成独立,得使用深拷贝,使用copy()自由函数来完成
// copy(目标数组变量(slice), 被拷贝的数组(slice))
namesCopy := make([]int, len(names))
copy(namesCopy, names[:])
fmt.Println(namesCopy)
// 截取方式也可以用于字符串
// 5、创建空切片的时候,可以明确指定切片的容量, make(类型,长度,容量)
names3 := make([]int, 0, 20)
names3 = append(names3, 1)
names3 = append(names3, 2)
fmt.Println(names3)
}
10、map类型(类似python的dict类型,key:value)
package main
import "fmt"
func main() {
// 方法一,map类似python中的字典
// 声明myMap1是一种map类型,key是string,value是string
var myMap1 map[string]string
if myMap1 == nil {
fmt.Println("myMap1是一个空")
}
// 使用make后,map类型可以自动扩容
myMap1 = make(map[string]string, 1)
myMap1["one"] = "java1"
myMap1["two"] = "C++1"
myMap1["three"] = "python1"
fmt.Println(myMap1) // map[one:java1 three:python1 two:C++1]
// 方法二
myMap2 := make(map[int]string)
myMap2[1] = "java"
myMap2[2] = "C++"
myMap2[3] = "python"
fmt.Println(myMap2) // map[1:java 2:C++ 3:python]
// 方法三,创建即初始化,注意逗号
myMap3 := map[string]string{
"one": "php",
"two": "python",
"three": "golang",
}
fmt.Println(myMap3) // map[one:php three:golang two:python]
}
package main
import "fmt"
func main() {
// 这个麻烦
var idNames map[int]string // 不能直接赋值,它是空的
// 分配空间,可以不指定,建议直接指定,提高性能
idNames = make(map[int]string, 1)
idNames[0] = "hello"
idNames[1] = "word"
fmt.Println(idNames) // map[0:hello 1:word]
// 常用
idNames1 := make(map[int]string)
idNames1[0] = "wjf"
fmt.Println(idNames1) //map[0:wjf]
// 5、如何确定一个key是否存在map中
// map中访问一个不存在越界问题,它认为所有的key都是有效的,所有访问一个不存在的key不会崩溃,返回这个类型的零值
// 零值: bool => false 数字 => 0 字符串 => 空
name9 := idNames[9]
fmt.Println(name9)
// 无法通过value来判断一个key是否存在,万一恰巧这个key的value就是零值
// 使用ok机制, 也是go的异常获取
value, ok := idNames[1] // 如果id=1存在,ok为true,反之为false
if ok {
fmt.Println(value) // word
} else {
fmt.Println("不存在")
}
// 6、删除map中元素,使用自由函数delete()
// 删除不存在的key并不会报错
delete(idNames, 0)
fmt.Println("删除后:", idNames) //删除后: map[1:word]
// 并发任务的时候, 要对map上锁
}
11、struct结构体
1. 结构体的基本应用
package main
import "fmt"
// 声明一种新的数据类型myint,是int的一个别名
type myint int
// 定义一个结构体
type Book struct {
title string
auth string
}
func printBook(book Book) {
// 传递一个book的副本,发生修改,原book1不会发生改变
book.auth = "888"
}
func changeBook(book *Book) {
// 指针传递
book.auth = "999"
}
func main() {
/*
var a myint = 10
fmt.Println("a = ", a)
fmt.Printf("type is %T\n", a)*/
var book1 Book
book1.title = "Golang"
book1.auth = "zhangs"
fmt.Printf("%v\n", book1) // {Golang zhangs}
fmt.Println("-----------")
printBook(book1)
fmt.Printf("%v\n", book1) // {Golang zhangs}
fmt.Println("-----------")
changeBook(&book1)
fmt.Printf("%v\n", book1) // {Golang 999}
}
2. 面向对象的go语言结构体,类似python中的class
package main
import "fmt"
// go语言面向对象,使用的struct结构体的方式
// 如果类的首字母大写,表示公有的,表示其他类也能访问
// 否则只能在本类中访问,类方法的命名也是这样
type Hero struct {
Name string
Ad int
Level int
}
/*// this Hero表示:绑定Hero结构体
func (this Hero) Show() {
fmt.Println("Name = ", this.Name)
fmt.Println("Ad = ", this.Ad)
fmt.Println("Level = ", this.Level)
}
func (this Hero) GetName() string {
fmt.Println("Name = ", this.Name)
return this.Name
}
func (this Hero) SetName(newName string) {
// this:调用该方法的对象的一个副本(拷贝),无法真正修改
this.Name = newName
}*/
// this Hero表示:绑定Hero结构体
// 用*表示,传递的是引用,可以完成修改
func (this *Hero) Show() {
fmt.Println("Name = ", this.Name)
fmt.Println("Ad = ", this.Ad)
fmt.Println("Level = ", this.Level)
}
func (this *Hero) GetName() string {
fmt.Println("Name = ", this.Name)
return this.Name
}
func (this *Hero) SetName(newName string) {
this.Name = newName
}
func main() {
hero := Hero{Name: "zhangs", Ad: 100, Level: 1}
hero.Show()
/*
Name = zhangs
Ad = 100
Level = 1
*/
fmt.Println("---------")
hero.SetName("li4")
hero.Show()
/*
Name = li4
Ad = 100
Level = 1
*/
}
3. 子类继承
// 继承
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 // 表示SuperMan类继承了Human类的方法
level int
}
// 重定义父类方法
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{"zhangs", "female"}
h.Eat()
h.Walk()
/*
Human eat....
Human walk....
*/
fmt.Println("-------------")
// 定义一个子类
// s := SuperMan{Human{"li4", "female"}, 88}
var s SuperMan
s.name = "li4"
s.sex = "man"
s.level = 88
s.Walk()
s.Eat()
s.Fly()
s.Print()
/*
Human walk....
Superman eat....
Superman fly....
name = li4
sex = man
level = 88
*/
}
4. 多态,go的多态是通过创建接口,然后实现所有接口后自动绑定的
// 多态,利用接口的方式实现方式
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.Print("color is ", animal.GetColor())
fmt.Println("kind is ", animal.GetType())
}
func main() {
// 第一种方式
/*
var animal AnimalIF // 接口的数据类型,父类指针
animal = &Cat{color: "Green"}
animal.Sleep() // 调用Cat的sleep方法
animal = &Dog{"yellow"}
animal.Sleep()*/
// 第二种方式
cat := Cat{"green"}
dog := Dog{"yellow"}
showAnimal(&cat)
/*
Cat is Sleep
color is greenkind is Cat
*/
showAnimal(&dog)
/*
Dog is Sleep
color is yellowkind is Dog
*/
}
5. interface{}空接口,能传入任何值,类似是python的函数传值,能接收任何值
// 通用万能类型interface{}:空接口,,能接任何类型
package main
import "fmt"
func myFunc(arg interface{}) {
fmt.Println("myFunc is called....")
fmt.Println(arg)
//interface{}如何区分,此时引用的底层数据类型到底是什么
//给interface{}提供"类型断言"的机制
value, ok := arg.(string) // 判断arg是不是string类型,value存该值,ok存是否(true,false)
if !ok {
fmt.Println("arg is not string")
} else {
fmt.Println("arg is string type, value = ", value)
}
}
type Book struct {
auth string
}
func main() {
book := Book{"Golang"}
myFunc(book)
/*
myFunc is called....
{Golang}
arg is not string
*/
fmt.Println("--------------")
myFunc(100)
/*
myFunc is called....
100
arg is not string
*/
fmt.Println("--------------")
myFunc("funce")
/*
myFunc is called....
funce
arg is string type, value = funce
*/
}
12、pair数据类型
每个声明的变量都有type:存放变量类型,value:存放值
// pair数据类型,每个声明的变量都有
package main
import "fmt"
func main() {
var a string
// pair<type:string, value:"aceld">
a = "aceld"
// pair<type:string, value:"aceld">
var allType interface{}
allType = a
str, _ := allType.(string)
fmt.Println(str)
}
13、 reflect反射
1. 用于反射会类型和值
// 反射机制
package main
import (
"fmt"
"reflect"
)
// 通过reflect放射回来,类型和值
func reflectNum(arg interface{}) {
fmt.Println("type = ", reflect.TypeOf(arg))
fmt.Println("value = ", reflect.ValueOf(arg))
}
func main() {
var num float64 = 1.2345
reflectNum(num)
/*
type = float64
value = 1.2345
*/
}
2. 反射在结构体中的应用
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (this User) Call() {
fmt.Println("user is called....")
fmt.Printf("%v\n", this)
}
func main() {
user := User{1, "Amy", 18}
DoFiledAndMethod(user)
}
// 利用放射,打印出类型和值
func DoFiledAndMethod(input interface{}) {
// 获取input的type
inputType := reflect.TypeOf(input)
fmt.Println("inputType is ", inputType.Name()) // inputType is User
// 获取input的value
inputValue := reflect.ValueOf(input)
fmt.Println("inputValue is ", inputValue) // inputValue is {1 Amy 18}
// 通过type获取里面的字段
// 1、获取interface的reflect,type,通过Type得到NumField,进行遍历
// 2、得到每个field,数据类型
// 3、通过filed有一个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)
/*
执行结果
Id: int = 1
Name: string = Amy
Age: int = 18
*/
}
fmt.Println("----------------")
// 通过type获取里面的方法,调用
for i := 0; i < inputType.NumMethod(); i++ {
m := inputType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type) // Call: func(main.User) // Call: func(main.User)
}
}
3. 反射中获取tag标签的内容
package main
// 利用反射,获取Tag标签的信息
import (
"fmt"
"reflect"
)
type resume struct {
// tag标签会被用于web中的json进行判断,,这个形式是给该字段绑定一个tag标签(用于说明)
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)
/*
info: name
doc: 我的名字
info: sex
doc:
*/
}
4. 利用方式中的tag标签,实现struct--->json,json--->struct
// tag标签在json中的应用,,tag写明的字段名,最后会变成json中的key
// struct ---> json json ---> struct
package main
import (
"encoding/json"
"fmt"
)
type Movie struct {
Title string `json:"title"`
Year int `json:"rmb"`
Price int `json:"year"`
Actors []string `json:"actors"`
}
func main() {
movie := Movie{"喜剧之王", 2000, 10, []string{"zxc", "zbz"}}
// 编码码过程,结构体--》json
jsonStr, err := json.Marshal(movie)
if err != nil {
fmt.Println("json marshal error ", err)
return
}
fmt.Printf("jsonStr = %s\n", jsonStr) // jsonStr = {"title":"喜剧之王","rmb":2000,"year":10,"actors":["zxc","zbz"]}
// 解码过程,jsonstr ---》 结构体
// jsonStr = {"title":"喜剧之王","rmb":2000,"year":10,"actors":["zxc","zbz"]}
myMovie := Movie{}
err = json.Unmarshal(jsonStr, &myMovie)
if err != nil {
fmt.Println("json unmarshal error ", err)
return
}
fmt.Printf("%v\n", myMovie) // {喜剧之王 2000 10 [zxc zbz]}
}
14、goroutine协程
go语言天生高并发,使用的就是协程,协程运行于线程之上,有线程进行控制
1. go中协程基本用法
package main
// 协程的基本使用
import (
"fmt"
"time"
)
// 协程的使用
// 子goroutine
func newTask() {
i := 0
for {
i++
fmt.Printf("new Goroutine : i = %d\n", i)
time.Sleep(1 * time.Second)
}
}
// 主goroutine,如果主的死亡,则子的全部死亡
func main() {
// 创建一个go程 去执行newTask()流程
go newTask()
i := 0
for {
i++
fmt.Printf("main goroutine : i = %d\n", i)
time.Sleep(1 * time.Second)
}
}
2. 匿名函数
package main
import (
"fmt"
"time"
)
func main() {
// 用go创建一个形参为空,返回值为空的一个函数
// 匿名无参数函数,在匿名函数写完后加一个()就可以实现函数的调用
/*go func() {
defer fmt.Println("A.defer")
func() {
defer fmt.Println("B.defer")
// 退出当前goroutine,也就是所有的,为什么不使用return,因为return后只会退出当前函数
runtime.Goexit()
fmt.Println("B")
}()
fmt.Println("A")
}()
// 执行结果
// B.defer
// A.defer*/
go func(a int, b int) bool {
fmt.Println("a = ", a, "b = ", b)
return true
}(10, 20)
// 死循环
for {
time.Sleep(1 * time.Second)
}
}
3、return、os.EXIT、runtime.Goexit的区别
package main
/*
1、Goexit ===> 提前退出当前go程
2、return ===> 返回当前函数
3、exit =====> 退出当前进程
*/
import (
"fmt"
"runtime"
"time"
)
func main() {
go func() {
func() {
fmt.Println("这是子go程内部的函数")
// return // 返回当前函数
// os.Exit(-1) // 退出进程
runtime.Goexit() // 退出当前go程
}()
fmt.Println("子go程结束")
}()
fmt.Println("这是主go程")
time.Sleep(5 * time.Second)
fmt.Println("Over")
}
/*
return执行的结果:
这是主go程
这是子go程内部的函数
子go程结束
Over
os.Exit:
这是主go程
这是子go程内部的函数
exit status 0xffffffff
runtime.Goexit:
这是主go程
这是子go程内部的函数
Over
*/
15、channel(管道),用于协程之间的通信
1. 无缓冲channel
// channel(管道),协程主键的通信
package main
import "fmt"
func main() {
// 定义一个channel,无缓冲,会发生阻塞, chan为关键字
c := make(chan int)
go func() {
defer fmt.Println("goroutine结束")
fmt.Println("goroutine 正在运行")
// 将666发送给c
c <- 666
}()
// 从c中接受数据,并赋值给num
num := <-c
fmt.Println("num = ", num)
fmt.Println("main gotoutine 结束....")
/*
执行结果:
goroutine 正在运行
goroutine结束
num = 666
main gotoutine 结束....
*/
}
2. 带缓冲的channel
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int, 3) // 带缓冲的channel
fmt.Println("len(c) = ", len(c), ", cap(c) = ", cap(c))
go func() {
defer fmt.Println("子go程结束")
// 如果缓冲区满了,则会进行阻塞,等待有空闲的空间出现
for i := 0; i < 4; i++ {
c <- i
fmt.Println("子go程正在运行,发送的元素是=", i, " len(c)=", len(c), ", cap(c)=", cap(c))
}
}()
time.Sleep(2 * time.Second)
for i := 0; i < 4; i++ {
num := <-c // 从c中接收数据,并赋值给num
fmt.Println("num = ", num)
}
time.Sleep(1 * time.Second)
defer fmt.Println("main 结束")
}
/*
执行结果:
len(c) = 0 , cap(c) = 3
子go程正在运行,发送的元素是= 0 len(c)= 1 , cap(c)= 3
子go程正在运行,发送的元素是= 1 len(c)= 2 , cap(c)= 3
子go程正在运行,发送的元素是= 2 len(c)= 3 , cap(c)= 3
num = 0
num = 1
num = 2
num = 3
子go程正在运行,发送的元素是= 3 len(c)= 3 , cap(c)= 3
子go程结束
main 结束
*/
3. 关闭channel,channel关闭后,其中剩下的数据还可以继续读取
// 关闭channel
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
// close可以关闭一个channel
close(c)
}()
for {
// ok如果为true表示channel没有关闭,如果为false表示channel已经关闭
if data, ok := <-c; ok {
fmt.Println(data)
} else {
break
}
}
fmt.Println("Main finished .....")
}
/*
0
1
2
3
4
Main finished .....
*/
4. 可以使用range的方法来读channel
// 关闭channel与range
package main
import "fmt"
func main() {
c := make(chan int)
go func() {
for i := 0; i < 5; i++ {
c <- i
}
// close可以关闭一个channel
close(c)
}()
// 可以使用range来迭代不断操作channel
for data := range c {
fmt.Println(data)
}
fmt.Println("Main finished .....")
}
5. chanael和select方法结合使用,可以检测到哪个channel可以使用,实现斐波那契数列
// channel与select
// 使用select监控多个channel,看哪个可读
package main
import "fmt"
func fibonacii(c, quit chan int) {
x, y := 1, 1
for {
select {
case c <- x:
// 如果c可写,则该case就会进来
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int) // 写数据
quit := make(chan int) // 写完后存入
go func() {
for i := 0; i < 6; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
// main go
fibonacii(c, quit)
}
/*
执行结果:
1
1
2
3
5
8
quit
*/
16、go modules模式
1. go mod命令
- go mod init:生成go.mod文件
- go mod download:下载go.mod文件中指明的所有依赖
- go mod tidy:整理现有的依赖
- go mod graph:查看现有的依赖结构
- go mod edit:编辑go.mod文件
- go mod vendor:导出项目所有的依赖到vendor目录
- go mod verify:校验一个模块是否被篡改过
- go mod why:查看为什么需要依赖某模块
2. go env查看go mod环境变量
- GO111MODULE:是否开启go modules模式,建议go v1.11之后都设置1为on。
- GOPROXY:项目第三方依赖的下载源地址(阿里云:https://mirrors.aliyun.com/goproxy,七牛云:https://goproxy.cn,direct);direct:用于指示Go回溯到模块版本的源地址去抓取。
- GOSUMDB:用于校验拉取的第三方库是否完整,默认是国外的网站如果设置了GOPROXY,这个就不用设置。
3. go env -w 环境变量名=值 | linux下使用在export配置环境变量也可以;
4. 更多的go modules知识,需要更详细的学习。
17、go命令
提示:在window下有些目录在终端会运行不了,我们需要下载一个git bash作为终端进行使用,同时可以将git bash配置为ide的终端使用,下载链接 Git for Windows
1、编译.go文件, -o 指定生成文件的命令
1. go build -o test.exe main.go xxx.go
2. go build *.go
2、直接运行程序,不会编译为exe文件
1. go run *.go
3、安装程序
1. 拿到一个c源码,想自己编译出exe
1. ./configure
2. make
3. make install ====>将编译好的程序安装到指定的目录/user/bin
2. 使用go install,可以将应用程序安装到GOBIN下面:$GOPATH/bin
1. go install ====> 需要实现在环境变量中设置GOBIN目录
4、go env查看当前go的配置
18、go不支持的语法
1、自增--i, ++i不支持
2、不支持地址加减
3、不支持三目运算符(x = 2 > 3 ? 1 : 0)
4、只有false才能表示代码逻辑假,数字0和nil不能
19、go中比较有特色的语法
1、switch
package main
import (
"fmt"
"os"
)
// 从命令行输入参数,在switch中进行处理
func main10() {
// go; os.Args ==> 直接可以获取命令输入,是一个字符串切片
// os.Args[0] ===> 程序名字
// os.Args[1] ===> 第一个参数,以此类推
cmds := os.Args
for key, cmd := range cmds {
fmt.Println("key:", key, ", cmd: ", cmd)
}
if len(cmds) < 2 {
fmt.Println("请正确输入")
return
}
switch cmds[1] {
case "hello":
fmt.Println("hello")
// 默认在后面有增加了break
// 如果需要穿透,需要加入fallthrough,,打印hello后,还会执行后面的代码
fallthrough
case "world":
fmt.Println("world")
default:
fmt.Println("default")
}
}
2、标签与goto、continue、break配合
package main
import "fmt"
func main11() {
// 标签 LABEL1(名字可以随便起)
// goto LABEL1 ==> 下次进入循环时,i不会保存之前的状态,从0开始,会死循环
// break LABEL1 ===> 会跳到指定位置,但是会记录之前的状态,i变成1,以此类推
// continue LABEL1 ==> 会直接退出到LABEL1的位置,在本程序中,直接退出了所有循环
LABEL1:
for i := 0; i < 5; i++ {
for j := 0; j < 5; j++ {
if j == 3 {
// goto LABEL1
// continue LABEL1
break LABEL1
}
fmt.Println("i:", i, ", j:", j)
}
}
fmt.Println("overs")
}
20、json编解码
在网络中传输的时候,把Student结构体,编码成json字符串,传输=》结构体=》字符串=》编码
接收字符串,需要将字符串转换成结构体,然后操作=》字符串=》结构体=》解密
package main
import (
"encoding/json"
"fmt"
)
type Student struct {
Id int
Name string
Age int
gender string // 小写字母开头的,进行json编码会忽略
}
func main() {
// 在网络中传输的时候,把Student结构体,编码成json字符串,传输=》结构体=》字符串=》编码
// 接收字符串,需要将字符串转换成结构体,然后操作=》字符串=》结构体=》解密
lily := Student{1, "Lily", 20, "女"}
fmt.Println(lily)
// func json.Marshal(v interface{}) ([]byte, error)
// 编码 结构体=》字符串
encodeInfo, err := json.Marshal(&lily) // 传指针和直接传都没问题
if err != nil {
fmt.Println("json.Marshal err: ", err)
return
}
fmt.Println("encodingInfo: ", string(encodeInfo))
// 对端接收到
// 反序列化(解码):字符串=》结构体
var lily2 Student
if err := json.Unmarshal([]byte(encodeInfo), &lily2); err != nil {
fmt.Println("json.Unmarshal err: ", err)
return
}
fmt.Println("name: ", lily2.Name)
fmt.Println("gender: ", lily2.gender)
fmt.Println("age: ", lily2.Age)
}
/*
encodingInfo: {"Id":1,"Name":"Lily","Age":20}
name: Lily
gender:
age: 20
*/
struct结构体标签(tag)用法
package main
import (
"encoding/json"
"fmt"
)
type Teacher struct {
Name string `json:"-"` // 这个编码不参与,json编码
Subject string `json:"Subject_name"` // json编码时,这个字段会起一个别名
Age int `json:"age,string"` // 同时改名和改类型, 两个字段(名字,类型), 不能有空格
Address string `json:"address,omitempty"` // 如果是空的则忽略
gender string // 小写字母开头的,进行json编码会忽略
}
func main() {
t1 := Teacher{
Name: "duck",
Subject: "goland",
Age: 22,
gender: "M",
// Address: "beijing",
}
fmt.Println("t1:", t1)
encodeInfo, _ := json.Marshal(t1)
fmt.Println("encodeInfo:", string(encodeInfo)) // encodeInfo: {"Subject_name":"goland","age":"22"}
}