createdtime 20211119
updatedtime 20211119
modifiedtime 20211119
author venki.chen
1. 整型
- 整型声明
// 方法一:默认值0
var a int
// 方法二
var b int = 10
// 方法三 类型推到
var c = 100
// 方法四 类型推到
d := 100
// 声明多变量
fmt.Println("声明多变量")
// 方法一
var aa, bb, cc int = 10, 100, 100
fmt.Printf("aa=%v,bb=%v,cc=%v\n", aa, bb, cc)
// 方法二
var dd, ee = 100, "venki.chen"
fmt.Printf("dd=%v,ee=%v\n", dd, ee)
- 注意事项
- 查看数据类型:
fmt.Printf("%T\n", d)
。 - 第4种声明方式只能用于函数体内,不能用于全局变量的声明。
2. 常量
- 常量定义
// 纵向枚举
// BEIJING_CODE 地区编码枚举 - 连续
BEIJING_CODE = iota // 0
SHANGHAI_CODE // 1
TIANJING_CODE // 2
// 横向枚举
const (
a, b = iota + 1, iota + 2 // iota = 0,a = iota + 1, b = iota + 2, a = 1, b = 2
c, d // iota = 1,c = iota + 1, d = iota + 2, c = 2, d = 3
e, f // iota = 2,e = iota + 1, f = iota + 2, e = 3, f = 4
g, h = 2 * iota, 3 * iota // iota = 3,a = 2 * iota, b = 3 * iota, g = 6, h = 9
i, j // iota = 4,i = 2 * iota, j = 3 * iota, i = 8, j = 12
)
- 注意事项
- 常量值是只读的,定义后是不能再函数体中修改的。
- iota默认值是0,只能出现在const 语法中,不能再函数体中应用。
3. 函数
- 函数多返回值
// 函数返回值方式4 - 有形参的返回值 - 返回值类型相同,可以合并
func functionReturnLearn04(a int, b int) (r6, r7 int) {
fmt.Println("a=", a)
fmt.Println("b=", b)
r6 = 10 // 没有冒号
r7 = 11 // 没有冒号
return
// return r6, r7 // 这样写也可以的,返回值默认为0,0
}
// 函数返回值方式3 - 有形参的返回值
func functionReturnLearn03(a int, b int) (r4 int, r5 int) {
fmt.Println("a=", a)
fmt.Println("b=", b)
r4 = 10 // 没有冒号
r5 = 11 // 没有冒号
return
}
// 函数返回值方式2 - 匿名返回值
func functionReturnLearn02(a int, b int) (int, int) {
fmt.Println("a=", a)
fmt.Println("b=", b)
c := 10
d := 11
return c, d
}
// 函数返回值方式1
func functionReturnLearn01(a int, b int) int {
fmt.Println("a=", a)
fmt.Println("b=", b)
c := 10
return c
}
- 注意事项
- 如果返回值时形参,即使最终形参没有参与实际运算,直接返回,那么也是可以的,结果是相对应数据类型的默认值,比如整型就是0。
4. 包的引用
- 函数多返回值
# 忽略包
_ fmt
# 为包起别名
base "20211115/base"
base.test()
# 忽略包名直接调用包里面的函数
. "20211115/base"
base.test => test()
- 注意事项
- 同一个包中,执行顺序如下:
import>const>var>init()>main()
。 - 同一个包下的方法和变量无论大小写是都可以访问到的。
- 同一个包下的方法和变量名不能重名,即不能重新声明方法;代码块里面的变量名除外。
5. 指针
- 指针速通
// 指针练习 - 交换两个数
func ptrLearn(p1 *int, p2 *int) {
fmt.Println(p1)// 地址值
fmt.Println(p2)// 地址值
temp := *p1
*p1 = *p2
*p2 = temp
}
- 注意事项
- 指针其实就是地址,想要学好指针,首先要看下操作系统相关的知识。
6. defer
- 代码练习
func AccessBase() {
// deferLearn()
deferAndReturn()
// 结果输出
// return func call!
// defer func call!
}
func deferAndReturn() int {
defer deferCall()
return returnCall()
}
func deferCall() int {
fmt.Println("defer func call!")
return 0
}
func returnCall() int {
fmt.Println("return func call!")
return 0
}
- 注意事项
- defer以栈的形式存储,先进后出。
- defer可以同时出现多个。
- defer和return的执行顺序,return先执行,defer后执行,谨记defer是在程序结束后才调用的。
7. 数组
- 代码练习
// 数组声明方式1
var myArray01 [10]int // 默认值都是0
// 数组声明方式2
var myArray02 [4]int{0,1,2,3}
- 注意事项
- 数组属于值拷贝。
- 数组的数据类型细分和元素数量有关,比如[4]int 和 [10]int 虽然都是数组,但是还是有区别的,作为形参进行传递时可见。
8. 切片
- 代码练习
// 声明方式
func sliceLearn() {
// 声明方式1 - 并初始化,默认值1,2,3,4 长度为4
slice01 := []int{1, 2, 3, 4}
fmt.Println("切片声明方式01,slice01=", slice01)
// 声明方式2 - 但是并没有给slice分配空间,没有分配空间直接赋值,会报错
var slice02 []int
slice02 = make([]int, 3) // 开辟3个空间,初始化值均是0
fmt.Println("切片声明方式02,slice02=", slice02)
// 声明方式3 - 同时给slice分配空间,初始化值0
var slice03 []int = make([]int, 3)
fmt.Println("切片声明方式03,slice03=", slice03)
// 声明方式4 - 类型推导,同时分配空间初始化值【以这种方式声明为标准或者方式3为准】
slice04 := make([]int, 3)
fmt.Println("切片声明方式04,slice04=", slice04)
// 这种声明方式也可以,不过还是以方式3为准
var slice0 = make([]int,3)
fmt.Println(slice0)
}
// 使用方式
// 切片的使用方式
var slice05 []int = make([]int, 3, 5)
slice05[0] = 1
slice05[1] = 2
slice05[2] = 3
var slice06 []int = make([]int, 3, 5)
copy(slice06, slice05)
// 如果声明的切片长度是3,容量是5,那么在赋值的时候,就不会存在下标3,4,会报错,只能用追加的方式进行。
var slice05 []int = make([]int, 3, 5)
slice05[0] = 1
slice05[1] = 2
slice05[2] = 3
slice05[3] = 4
slice05[4] = 5
fmt.Println(slice05)
// panic: runtime error: index out of range [3] with length 3
// 修改成下面就行
var slice05 []int = make([]int, 3, 5)
slice05[0] = 1
slice05[1] = 2
slice05[2] = 3
slice05 = append(slice05,4)
slice05 = append(slice05,5)
fmt.Println(slice05)
- 注意事项
- 切片属于引用传值。
- 可以简单将切片理解为动态数组。
- 判断slice为空,可以通过slice是否为nil进行判断,比如只声明为make的情况。
- 切片可以指定容量,当容量沾满之后,如果继续向切片追加元素,那么go底层会再次动态开辟一倍与初始化容量的值。
- 切片截取后赋值给新的变量时,是引用传值,改变其中一个值,另一个也随着变化,如果想将截取后的切片赋值给新的切片,那么使用copy函数即可。
- 切片的截取区间是左闭右开的。
- 切片的地址是首个元素的地址。
9. map
- 代码练习
// map声明方式
func mapLearn() {
// 声明方式1 - 只是声明没有分配空间
var map01 map[string]string
map01 = make(map[string]string, 10)
map01["one"] = "venki"
map01["two"] = "chen"
fmt.Println("map声明方式01,map01=", map01)
// 声明方式02
map02 := make(map[int]string)
map02[0] = "poet"
map02[1] = "chao"
fmt.Println("map声明方式02,map02=", map02)
// 声明方式03
map03 := map[int]string{
0: "陈",
1: "文",
2: "小",
3: "超",
}
fmt.Println("map声明方式03,map03=", map03)
}
// map使用方式
// 增加
map04 := make(map[string]string)
map04["chen"] = "陈"
map04["wen"] = "文"
map04["xiao"] = "小"
map04["chao"] = "超"
fmt.Println("map使用方式之增加元素,map04=", map04)
// 删除
delete(map04, "chen")
fmt.Println("map使用方式之删除元素,map04=", map04)
// 修改
map04["xiao"] = "大"
fmt.Println("map使用方式之修改元素,map04=", map04)
// 查询
i := 0
for index, value := range map04 {
i++
fmt.Printf("map04第%d个元素的索引下标是%v,对应的值是%v\n", i, index, value)
}
- 注意事项
- map只声明没有make的话,也是没有分配空间,即无法使用。
- map当容量沾满之后,如果继续向map追加元素,那么go底层会再次动态开辟一倍容量的值。
- map里面的值是无顺序的,map底层是通过哈希表存储的。
- map是引用传递。
10. struct
- 代码练习
// 调用入口
func structLearn() {
h := Human{
name: "陈文小超",
sex: "male",
}
h.Eat()
h.Walk()
// 实例化方式1
var superman Superman
superman.name = "venki.chen"
superman.sex = "female"
superman.level = 99
superman.Eat()
superman.Walk()
superman.Fly()
superman.Print()
// 实例化方式2
superman := Superman{Human{name:"venki.chen",sex:"female"},level:99}
}
// 为结构体绑定方法
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
}
func (this *Superman) Eat() {
fmt.Println("superman eat()...")
}
func (this *Superman) Fly() {
fmt.Println("superman fly()...")
}
func (this *Superman) Print() {
fmt.Println("superman is name=", this.name)
fmt.Println("superman is sex=", this.sex)
fmt.Println("superman is level=", this.level)
}
- 注意事项
- struct是值传递。当然可以通过指针,变成引用传值。
- 给一个结构体绑定方法时,一般采用引用传递,不然无法进行修改操作。
11. 多态
- 代码练习
type Animal interface {
Sleep()
GetColor() string
GetType() string
}
type Cat struct {
Color string
Name string
}
type Dog struct {
Color string
Name string
}
func AccessBase() {
fmt.Println("ok")
// 多态练习
cat := Cat{
Color: "Green",
Name: "小猫咪",
}
PolymorphicLearn(&cat)
fmt.Println("------------------")
dog := Dog{
Color: "white",
Name: "小迪迪",
}
PolymorphicLearn(&dog)
}
func PolymorphicLearn(animal Animal) {
animal.Sleep()
animal.GetColor()
animal.GetType()
}
func (this *Cat) Sleep() {
fmt.Println("cat is sleeping...")
}
func (this *Cat) GetColor() string {
fmt.Println("cat's color is", this.Color)
return this.Color
}
func (this *Cat) GetType() string {
fmt.Println("cat's type is", this.Name)
return this.Name
}
func (this *Dog) Sleep() {
fmt.Println("dog is sleeping...")
}
func (this *Dog) GetColor() string {
fmt.Println("dog's color is", this.Color)
return this.Color
}
func (this *Dog) GetType() string {
fmt.Println("dog's type is", this.Name)
return this.Name
}
- 注意事项
- 多态实现的基本要素
- 有一个父类(接口)。
- 有子类(实现父类的全部接口方法)。
- 父类类型的变量指向之类的具体数据变量。
12. interface
- 代码练习
func InterfaceLearn(args interface{}) {
fmt.Println("interfaceLearn is called...")
// 类型断言
value, ok := args.(string)
if !ok {
fmt.Println("args is not string type")
fmt.Println("args is string type,value=", args)
} else {
fmt.Println("args is string type,value=", value)
fmt.Printf("value type is %T\n", value)
}
fmt.Println("-----------------------------")
}
- 注意事项
- 所有数据类型均实现了空接口。
- 空接口可以理解为一种万能数据类型。
- 形参的数据类型如果无法确定,可以通过空接口实现。
- go底层为空接口提供一种类型断言,用来判断空接口实现的具体数据类型是什么。
13. 变量
- 代码练习
var a int = 10 // a's pair:<type:int,value:10>
var b interface{}
b = a // b's pair:<type:int,value:10>
- 注意事项
- 声明一个变量可以分为:type和value,type可以分为static type(int string)和concrete type(interface所指向的具体数据类型,系统看得见的类型),type+value叫做pair。
- 变量在赋值传递的过程中,pair是不变的,这也是类型断言的本质。
14. reflect
- 代码练习
func ReflectLearn(arg interface{}) {
// 简单数据类型的反射练习
/*fmt.Printf("arg's type is %v\n", reflect.TypeOf(arg))
fmt.Printf("arg's value is %v\n", reflect.ValueOf(arg))*/
// 复杂数据类型的反射练习
// 获取arg的type
argType := reflect.TypeOf(arg)
fmt.Println("argType is :", argType.Name())
// 获取arg的value
argValue := reflect.ValueOf(arg)
fmt.Println("argValue is :", argValue)
// 通过argType获取里面的字段
// 通过argValue获取里面字段值
// 1. 获取interface的reflect.Type,通过type得到NumField,进行遍历
// 2. 得到field,数据类型
// 3. 通过field的Interface{}方法得到对应字段值
for i := 0; i < argType.NumField(); i++ {
field := argType.Field(i)
value := argValue.Field(i).Interface()
fmt.Printf("%s:%v = %v\n", field.Name, field.Type, value)
}
// 通过type获取里面的方法
for i := 0; i < argType.NumMethod(); i++ {
m := argType.Method(i)
fmt.Printf("%s:%v\n", m.Name, m.Type)
}
}
- 注意事项
- 通过反射获取结构体的方法相关数据时,切记给对应结构体绑定方法时不要用指针类型(
func (this *Cat) Sleep(){}
用后者func (this Cat) Sleep(){}
),否则无法获取结构体方法相关信息。
15. 结构体标签
- 代码练习
type person struct {
Name string `info:"name" sub:"作者姓名"`
Age int `info:"age" sub:"作者年龄"`
}
// 结构体标签学习
func structLearn(s interface{}) {
t := reflect.TypeOf(s)
fmt.Println("--1--,t=", t) // --1--,t= *base.person
e := t.Elem()
fmt.Println("--2--,e=", e) // --2--,e= base.person
for i := 0; i < e.NumField(); i++ {
tagInfo := e.Field(i).Tag.Get("info")
tagSub := e.Field(i).Tag.Get("sub")
fmt.Println("--3--,tageInfo=", tagInfo, "--4--,tagSub=", tagSub)
// --3--,tageInfo= name --4--,tagSub= 作者姓名
// --3--,tageInfo= age --4--,tagSub= 作者年龄
}
}
- 注意事项
- 标签是key-value的形式,一个属性可以绑定多个标签。
- 标签的作用可以用来解释说明,属性的用法或者属性在不被不同包引用时的具体用法说明。
16. 结构体和json
- 代码练习
type person struct {
Name string `info:"name" sub:"作者姓名"`
Age int `info:"age" sub:"作者年龄"`
Address string `json:"address"`
Family []string `json:"family"`
}
func AccessBase() {
p := person{
Name: "陈文小超",
Age: 29,
Address: "广东深圳",
Family: []string{"北京", "上海", "广州"},
}
structJson(p)
}
// json和struct
func structJson(p person) {
// 结构体转json
jsonStr, err := json.Marshal(p)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("jsonStr=%v\n", string(jsonStr)) // jsonStr={"Name":"陈文小超","Age":29,"address":"广东深圳","family":["北京","上海","广州"]}
// json转结构体
var pp person
err = json.Unmarshal(jsonStr, &pp)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("pp=%v\n", pp) // pp={陈文小超 29 广东深圳 [北京 上海 广州]}
}
- 注意事项
- 注意转json的时候加个string。
17. goroutine
- 代码练习
func goroutineLearn() {
// 开启一个协程,通过匿名函数进行,匿名函数有返回值,但是一般的返回值接收方式对于协程而言是不可以的。
go func(a int, b int) bool {
fmt.Println(a + b)
return true
}(10, 20)
for true {
time.Sleep(time.Second * 1)
}
}
- 注意事项
- 进程占用内存,虚拟内存4GB(32bit OS)。
- 线程占用内存,约4MB。
- 用户线程,可以简单的称为协程。
- go语言的协程和线程之前是M:N的关系。
- go协程优化内存,一个协程只占用几KB。
- 调度器的设计策略:复用线程、利用并行、抢占、全局G队列。
- 退出一个协程
runtime.Goexit()
18. channel
- 代码练习
// 无缓存的channel学习
func channelLearn() {
chann := make(chan int)
go func() {
defer fmt.Println("go协程执行结束...")
fmt.Println("go协程执行中...")
chann <- 666
}()
num := <-chann
fmt.Println("num=", num)
fmt.Println("主线程执行结束...")
}
// go协程执行中...
// go协程执行结束...
// num= 666
// 主线程执行结束...
// 带缓存的channel学习
func channelCacheLearn() {
chann := make(chan int, 3) // 指定管道容量即意味着携带缓存
go func() {
defer fmt.Println("go 协程执行结束...")
for i := 0; i < 3; i++ {
chann <- i
fmt.Println("协程正在执行...", "写入数据是:", i, "此时的管道长度是:", len(chann), "管道容量是:", cap(chann))
}
}()
time.Sleep(2 * time.Second)
for i := 0; i < 3; i++ {
num := <-chann
fmt.Printf("第%d个num是:%v\n", i+1, num)
}
fmt.Println("主线程执行结束...")
}
// 协程正在执行... 写入数据是: 0 此时的管道长度是: 1 管道容量是: 3
// 协程正在执行... 写入数据是: 1 此时的管道长度是: 2 管道容量是: 3
// 协程正在执行... 写入数据是: 2 此时的管道长度是: 3 管道容量是: 3
// go 协程执行结束...
// 第1个num是:0
// 第2个num是:1
// 第3个num是:2
// 主线程执行结束...
- 注意事项
- channel保证了主线程和协程之间某种意义上的同步。
- close可以关闭一个channel。
- 可以用range来迭代不断操作的channel,因为range会阻塞。
- channel不像文件意义需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel。
- 关闭channel后,无法向channel再发送数据(引发panic错误导致接收立即返回零值)。
- 关闭channel后,可以继续从channel中接收数据。
- 对于nil channel,无论收发都会被阻塞。
19. select
- 代码练习
// select学习
func selectLearn() {
chann1 := make(chan int)
chann2 := make(chan int)
go func() {
for i := 0; i < 10; i++ {
defer fmt.Printf("go协程执行结束%v\n...", i+1)
fmt.Println(<-chann1)
}
chann2 <- 0
}()
x, y := 1, 1
for {
select {
case chann1 <- x:
x = y
y = x + y
case <-chann2:
fmt.Println("go协程结束2...")
return
}
}
}
- 注意事项