Go语言【基础夯实】(一)


createdtime 20211119

updatedtime 20211119

modifiedtime 20211119

author venki.chen


1. 整型
  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)
  1. 注意事项
  • 查看数据类型:fmt.Printf("%T\n", d)
  • 第4种声明方式只能用于函数体内,不能用于全局变量的声明。
2. 常量
  1. 常量定义
// 纵向枚举
    // 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
)
  1. 注意事项
  • 常量值是只读的,定义后是不能再函数体中修改的。
  • iota默认值是0,只能出现在const 语法中,不能再函数体中应用。
3. 函数
  1. 函数多返回值
// 函数返回值方式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
}
  1. 注意事项
  • 如果返回值时形参,即使最终形参没有参与实际运算,直接返回,那么也是可以的,结果是相对应数据类型的默认值,比如整型就是0。
4. 包的引用
  1. 函数多返回值
# 忽略包
_ fmt

# 为包起别名
base "20211115/base"
base.test()

# 忽略包名直接调用包里面的函数
. "20211115/base"
base.test => test()
  1. 注意事项
  • 同一个包中,执行顺序如下:import>const>var>init()>main()
  • 同一个包下的方法和变量无论大小写是都可以访问到的。
  • 同一个包下的方法和变量名不能重名,即不能重新声明方法;代码块里面的变量名除外。
5. 指针
  1. 指针速通
// 指针练习 - 交换两个数
func ptrLearn(p1 *int, p2 *int) {
	fmt.Println(p1)// 地址值
	fmt.Println(p2)// 地址值
	temp := *p1
	*p1 = *p2
	*p2 = temp
}
  1. 注意事项
  • 指针其实就是地址,想要学好指针,首先要看下操作系统相关的知识。
6. defer
  1. 代码练习
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
}
  1. 注意事项
  • defer以栈的形式存储,先进后出。
  • defer可以同时出现多个。
  • defer和return的执行顺序,return先执行,defer后执行,谨记defer是在程序结束后才调用的。
7. 数组
  1. 代码练习
// 数组声明方式1
var myArray01 [10]int // 默认值都是0

// 数组声明方式2
var myArray02 [4]int{0,1,2,3}
  1. 注意事项
  • 数组属于值拷贝。
  • 数组的数据类型细分和元素数量有关,比如[4]int 和 [10]int 虽然都是数组,但是还是有区别的,作为形参进行传递时可见。
8. 切片
  1. 代码练习
// 声明方式
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)
  1. 注意事项
  • 切片属于引用传值。
  • 可以简单将切片理解为动态数组。
  • 判断slice为空,可以通过slice是否为nil进行判断,比如只声明为make的情况。
  • 切片可以指定容量,当容量沾满之后,如果继续向切片追加元素,那么go底层会再次动态开辟一倍与初始化容量的值。
  • 切片截取后赋值给新的变量时,是引用传值,改变其中一个值,另一个也随着变化,如果想将截取后的切片赋值给新的切片,那么使用copy函数即可。
  • 切片的截取区间是左闭右开的。
  • 切片的地址是首个元素的地址。
9. map
  1. 代码练习
// 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)
	}
  1. 注意事项
  • map只声明没有make的话,也是没有分配空间,即无法使用。
  • map当容量沾满之后,如果继续向map追加元素,那么go底层会再次动态开辟一倍容量的值。
  • map里面的值是无顺序的,map底层是通过哈希表存储的。
  • map是引用传递。
10. struct
  1. 代码练习
// 调用入口
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)
}
  1. 注意事项
  • struct是值传递。当然可以通过指针,变成引用传值。
  • 给一个结构体绑定方法时,一般采用引用传递,不然无法进行修改操作。
11. 多态
  1. 代码练习
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
}
  1. 注意事项
  • 多态实现的基本要素
    • 有一个父类(接口)。
    • 有子类(实现父类的全部接口方法)。
    • 父类类型的变量指向之类的具体数据变量。
12. interface
  1. 代码练习
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("-----------------------------")
}
  1. 注意事项
  • 所有数据类型均实现了空接口。
  • 空接口可以理解为一种万能数据类型。
  • 形参的数据类型如果无法确定,可以通过空接口实现。
  • go底层为空接口提供一种类型断言,用来判断空接口实现的具体数据类型是什么。
13. 变量
  1. 代码练习
var a int = 10 // a's pair:<type:int,value:10>

var b interface{}

b = a // b's pair:<type:int,value:10>
  1. 注意事项
  • 声明一个变量可以分为:type和value,type可以分为static type(int string)和concrete type(interface所指向的具体数据类型,系统看得见的类型),type+value叫做pair。
  • 变量在赋值传递的过程中,pair是不变的,这也是类型断言的本质。
14. reflect
  1. 代码练习
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)
	}
}
  1. 注意事项
  • 通过反射获取结构体的方法相关数据时,切记给对应结构体绑定方法时不要用指针类型(func (this *Cat) Sleep(){} 用后者 func (this Cat) Sleep(){}),否则无法获取结构体方法相关信息。
15. 结构体标签
  1. 代码练习
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= 作者年龄
	}
}
  1. 注意事项
  • 标签是key-value的形式,一个属性可以绑定多个标签。
  • 标签的作用可以用来解释说明,属性的用法或者属性在不被不同包引用时的具体用法说明。
16. 结构体和json
  1. 代码练习
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 广东深圳 [北京 上海 广州]}
}
  1. 注意事项
  • 注意转json的时候加个string。
17. goroutine
  1. 代码练习
func goroutineLearn() {
	// 开启一个协程,通过匿名函数进行,匿名函数有返回值,但是一般的返回值接收方式对于协程而言是不可以的。
	go func(a int, b int) bool {
		fmt.Println(a + b)
		return true
	}(10, 20)
    
	for true {
		time.Sleep(time.Second * 1)
	}
}
  1. 注意事项
  • 进程占用内存,虚拟内存4GB(32bit OS)。
  • 线程占用内存,约4MB。
  • 用户线程,可以简单的称为协程。
  • go语言的协程和线程之前是M:N的关系。
  • go协程优化内存,一个协程只占用几KB。
  • 调度器的设计策略:复用线程、利用并行、抢占、全局G队列。
  • 退出一个协程runtime.Goexit()
18. channel
  1. 代码练习
// 无缓存的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
// 主线程执行结束...
  1. 注意事项
  • channel保证了主线程和协程之间某种意义上的同步。
  • close可以关闭一个channel。
  • 可以用range来迭代不断操作的channel,因为range会阻塞。
  • channel不像文件意义需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel。
  • 关闭channel后,无法向channel再发送数据(引发panic错误导致接收立即返回零值)。
  • 关闭channel后,可以继续从channel中接收数据。
  • 对于nil channel,无论收发都会被阻塞。
19. select
  1. 代码练习
// 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
		}
	}
}
  1. 注意事项

Guess you like

Origin blog.csdn.net/qq_38721452/article/details/121426648