Skip to content
go的学习笔记

中文标准库文档:https://studygolang.com/pkgdoc

第一段代码

所有代码的主文件都是main.go,下面的代码直接在项目里面创建main.go运行

go
package main // 声明文件所在的包,每个go文件必须有归属的包

import "fmt" // 引入程序需要的包,为了使用包下的函数,比如Println

func main() { // 程序的主函数,程序的运行入口
	fmt.Println("Hello World")  // 在控制台打印输出一句话
}

如果是vscode等工具需要执行go run main.go来运行,我这里使用的是goLand右键直接运行了

注意事项:

  • 源文件以.go为扩展名
  • 程序的执行入口是main()函数
  • 严格区分大小写
  • 方法由一条条语句构成,每个语句后面不需要加分号,这也体现出go的简洁性
  • Go编译器是一行行进行编译的,因此一行就写一条语句,不能把多个语句写在同一行,否则报错
  • 定义的变量或者import的包如果没有使用到,代码不能编译通过
  • 大括号都是成对出现的,缺一不可

变量的定义和使用

go
package main

import "fmt"

// 全局变量: 定义在函数外的变量
var n9 = 100
var n10 = 9.7

// 一次性定义多个全局变量
var (
	n11 = 12
	n12 = "你好"
)

func main() {
	// 定义在{}中的变量叫: 局部变量

	// 变量重复定义会报错
	var name string   // 声明变量和类型
	name = "小明"       // 赋值变量
	fmt.Println(name) // 打印值

	var age int = 18 // 声明变量和类型并赋值
	fmt.Println(age)

	var address = "北京" // 声明没指定类型时,根据后面的值的类型自动推断类型
	//address = 12 // 报错,因为上面类型推断为string
	fmt.Println(address)

	stuName := "萧寂"                 // 声明和赋值
	fmt.Println("学生姓名为: ", stuName) // 打印值

	//count = 1  // 不能直接写等于号,除非:=或者var count = 1  必须要声明

	fmt.Println("--------------------------------------------")
	// 声明多个变量不赋值
	var n1, n2, n3 int
	fmt.Println(n1, n2, n3) // 不赋值情况下会打印每个类型的原始值

	// 声明多个变量并赋值
	var n4, n5, n6 = 14, "jack", 12.7
	fmt.Println(n4, n5, n6) // 声明多个变量并赋值

	n7, n8 := 12, "hahaha"
	fmt.Println(n7, n8)

	// 打印全局变量
	fmt.Println(n9, n10, n11, n12)
}

数据类型介绍

  • 基本数据类型
    • 数值型
      • 整数类型(int,int8,int16,int32,int64,uint,uint8,uint16,uint32,uint64,byte)
      • 浮点类型(float32,float64)
    • 字符型(没有单独的字符型,使用byte来保存单个字母字符)
    • 布尔型(bool)
    • 字符串(string)
  • 派生数据类型(也是复杂数据类型)
    • 指针
    • 数组
    • 结构体
    • 管道
    • 函数
    • 切片
    • 接口
    • map

整数类型

go
package main

import (
	"fmt"
	"unsafe"
)

func main() {
	//	整数类型
	// 定义一个整数类型
	//var num1 int8 = 230 // 报错(230超出int8范围)
	//fmt.Println(num1)

	// 定义整数类型
	var num2 = -20
	fmt.Println(num2)

	// 打印类型
	// 其他占位符的使用: https://www.jianshu.com/p/66aaf908045e
	fmt.Printf("num2的类型是: %T", num2)
	fmt.Println()                    // 换行作用
	fmt.Println(unsafe.Sizeof(num2)) // 打印字节数

	// 定义byte类型
	var num3 byte = 20
	fmt.Println(num3)
}

浮点类型

go
package main

import "fmt"

func main() {
	//	定义浮点类型的数据
	var num1 float32 = 3.14
	var num2 float64 = 3.15
	fmt.Println(num1, num2)
}

字符类型

go
package main

import "fmt"

func main() {
	// 定义字符类型的数据
	var c1 byte = 'a'
	var c2 byte = 'b'
	var c3 byte = '('
	var c4 byte = '6'
	var c5 int = '' // 底层是Unicode编码
	fmt.Println(c1, c2, c3, c4, c5)
}

布尔类型

go
package main

import "fmt"

func main() {
	//	测试布尔类型的数值
	var flag1 bool = true
	var flag2 bool = false
	var flag3 = 5 < 9
	fmt.Println(flag1, flag2, flag3)
}

字符串类型

go
package main

import "fmt"

func main() {
	//	测试布尔类型的数值
	var str string = "你好"
	var str2 string = "世界"
	fmt.Println(str)
	fmt.Println(str+str2)
}

基本数据类型之间的转换

go
package main
import "fmt"
func main(){
        //进行类型转换:
        var n1 int = 100
        //var n2 float32 = n1  在这里自动转换不好使,比如显式转换
        fmt.Println(n1)
        //fmt.Println(n2)
        var n2 float32 = float32(n1)
        fmt.Println(n2)
        //注意:n1的类型其实还是int类型,只是将n1的值100转为了float32而已,n1还是int的类型
        fmt.Printf("%T",n1)  //int
        fmt.Println()
        //将int64转为int8的时候,编译不会出错的,但是会数据的溢出
        var n3 int64 = 888888
        var n4 int8 = int8(n3)
        fmt.Println(n4)//56
        var n5 int32 = 12
        var n6 int64 = int64(n5) + 30  //一定要匹配=左右的数据类型
        fmt.Println(n5)
        fmt.Println(n6)
        var n7 int64 = 12
        var n8 int8 = int8(n7) + 127  //编译通过,但是结果可能会溢出
        //var n9 int8 = int8(n7) + 128 //编译不会通过
        fmt.Println(n8)
        //fmt.Println(n9)
}

基本数据类型转string

go
package main
import "fmt"
func main(){
        var n1 int = 19
        var n2 float32 = 4.78
        var n3 bool = false
        var n4 byte = 'a'
        var s1 string = fmt.Sprintf("%d",n1)
        fmt.Printf("s1对应的类型是:%T ,s1 = %q \n",s1, s1)
        var s2 string = fmt.Sprintf("%f",n2)
        fmt.Printf("s2对应的类型是:%T ,s2 = %q \n",s2, s2)
        var s3 string = fmt.Sprintf("%t",n3)
        fmt.Printf("s3对应的类型是:%T ,s3 = %q \n",s3, s3)
        var s4 string = fmt.Sprintf("%c",n4)
        fmt.Printf("s4对应的类型是:%T ,s4 = %q \n",s4, s4)
}

string转基本数据类型

go
package main

import (
	"fmt"
	"strconv"
)

func main() {
	//string-->bool
	var s1 string = "true"
	var b bool
	//ParseBool这个函数的返回值有两个:(value bool, err error)
	//value就是我们得到的布尔类型的数据,err出现的错误
	//我们只关注得到的布尔类型的数据,err可以用_直接忽略
	b, _ = strconv.ParseBool(s1)
	fmt.Printf("b的类型是:%T,b=%v \n", b, b)
	//string---》int64
	var s2 string = "19"
	var num1 int64
	num1, _ = strconv.ParseInt(s2, 10, 64)
	fmt.Printf("num1的类型是:%T,num1=%v \n", num1, num1)
	//string-->float32/float64
	var s3 string = "3.14"
	var f1 float64
	f1, _ = strconv.ParseFloat(s3, 64)
	fmt.Printf("f1的类型是:%T,f1=%v \n", f1, f1)
	//注意:string向基本数据类型转换的时候,一定要确保string类型能够转成有效的数据类型,否则最后得到的结果就是按照对应类型的默认值输出
	var s4 string = "golang"
	var b1 bool
	b1, _ = strconv.ParseBool(s4)
	fmt.Printf("b1的类型是:%T,b1=%v \n", b1, b1)
	var s5 string = "golang"
	var num2 int64
	num2, _ = strconv.ParseInt(s5, 10, 64)
	fmt.Printf("num2的类型是:%T,num2=%v \n", num2, num2)
}

指针

go
package main

import (
	"fmt"
)

func main() {
	var age int = 18
	//&符号+变量 就可以获取这个变量内存的地址
	fmt.Println(&age) //0xc0000a2058
	//定义一个指针变量:
	//var代表要声明一个变量
	//ptr 指针变量的名字
	//ptr对应的类型是:*int 是一个指针类型 (可以理解为 指向int类型的指针)
	//&age就是一个地址,是ptr变量的具体的值
	var ptr *int = &age
	fmt.Println(ptr)
	fmt.Println("ptr本身这个存储空间的地址为:", &ptr)
	//想获取ptr这个指针或者这个地址指向的那个数据:
	fmt.Printf("ptr指向的数值为:%v", *ptr) //ptr指向的数值为:18
}

运算符

算术运算符

go
package main

import "fmt"

func main() {
	//+加号:
	//1.正数 2.相加操作  3.字符串拼接
	var n1 int = +10
	fmt.Println(n1)
	var n2 int = 4 + 7
	fmt.Println(n2)
	var s1 string = "abc" + "def"
	fmt.Println(s1)
	// /除号:
	fmt.Println(10 / 3)   //两个int类型数据运算,结果一定为整数类型
	fmt.Println(10.0 / 3) //浮点类型参与运算,结果为浮点类型
	// % 取模  等价公式: a%b=a-a/b*b
	fmt.Println(10 % 3) // 10%3= 10-10/3*3 = 1
	fmt.Println(-10 % 3)
	fmt.Println(10 % -3)
	fmt.Println(-10 % -3)
	//++自增操作:
	var a int = 10
	a++
	fmt.Println(a)
	a--
	fmt.Println(a)
	//++ 自增 加1操作,--自减,减1操作
	//go语言里,++,--操作非常简单,只能单独使用,不能参与到运算中去
	//go语言里,++,--只能在变量的后面,不能写在变量的前面 --a  ++a  错误写法
}

赋值运算符

go
package main

import "fmt"

func main() {
	var num1 int = 10
	fmt.Println(num1)
	var num2 int = (10+20)%3 + 3 - 7 //=右侧的值运算清楚后,再赋值给=的左侧
	fmt.Println(num2)
	var num3 int = 10
	num3 += 20 //等价num3 = num3 + 20;
	fmt.Println(num3)

	//练习:交换两个数的值并输出结果:
	var a int = 8
	var b int = 4
	fmt.Printf("a = %v,b = %v", a, b)
	//交换:
	//引入一个中间变量:
	var t int
	t = a
	a = b
	b = t
	fmt.Printf("a = %v,b = %v", a, b)
}

关系运算符

go
package main

import "fmt"

func main() {
	fmt.Println(5 == 9) //判断左右两侧的值是否相等,相等返回true,不相等返回的是false, ==不是=
	fmt.Println(5 != 9) //判断不等于
	fmt.Println(5 > 9)
	fmt.Println(5 < 9)
	fmt.Println(5 >= 9)
	fmt.Println(5 <= 9)
}

逻辑运算符

go
package main

import "fmt"

func main() {
	//与逻辑:&& :两个数值/表达式只要有一侧是false,结果一定为false
	//也叫短路与:只要第一个数值/表达式的结果是false,那么后面的表达式等就不用运算了,直接结果就是false  -->提高运算效率
	fmt.Println(true && true)
	fmt.Println(true && false)
	fmt.Println(false && true)
	fmt.Println(false && false)
	//或逻辑:||:两个数值/表达式只要有一侧是true,结果一定为true
	//也叫短路或:只要第一个数值/表达式的结果是true,那么后面的表达式等就不用运算了,直接结果就是true -->提高运算效率
	fmt.Println(true || true)
	fmt.Println(true || false)
	fmt.Println(false || true)
	fmt.Println(false || false)
	//非逻辑:取相反的结果:
	fmt.Println(!true)
	fmt.Println(!false)
}

获取用户终端输入数据

go
package main

import "fmt"

func main() {
	//实现功能:键盘录入学生的年龄,姓名,成绩,是否是VIP
	//方式1:Scanln
	var age int
	// fmt.Println("请录入学生的年龄:")
	//传入age的地址的目的:在Scanln函数中,对地址中的值进行改变的时候,实际外面的age被影响了
	//fmt.Scanln(&age)//录入数据的时候,类型一定要匹配,因为底层会自动判定类型的
	var name string
	// fmt.Println("请录入学生的姓名:")
	// fmt.Scanln(&name)
	var score float32
	// fmt.Println("请录入学生的成绩:")
	// fmt.Scanln(&score)
	var isVIP bool
	// fmt.Println("请录入学生是否为VIP:")
	// fmt.Scanln(&isVIP)
	//将上述数据在控制台打印输出:
	//fmt.Printf("学生的年龄为:%v,姓名为:%v,成绩为:%v,是否为VIP:%v",age,name,score,isVIP)
	//方式2:Scanf
	fmt.Println("请录入学生的年龄,姓名,成绩,是否是VIP,使用空格进行分隔")
	fmt.Scanf("%d %s %f %t", &age, &name, &score, &isVIP)
	//将上述数据在控制台打印输出:
	fmt.Printf("学生的年龄为:%v,姓名为:%v,成绩为:%v,是否为VIP:%v", age, name, score, isVIP)
}

流程控制语句

if语句

单分支

go
package main
import "fmt"
func main(){
        //实现功能:如果口罩的库存小于30个,提示:库存不足:
        //var count int = 100
        //单分支:
        // if count < 30 {
        // 	fmt.Println("对不起,口罩存量不足")
        // }
        //if后面表达式,返回结果一定是true或者false,
        //如果返回结果为true的话,那么{}中的代码就会执行
        //如果返回结果为false的话,那么{}中的代码就不会执行
        //if后面一定要有空格,和条件表达式分隔开来
        //{}一定不能省略
        //条件表达式左右的()是建议省略的
        //在golang里,if后面可以并列的加入变量的定义:
        if count := 20;count < 30 {
                fmt.Println("对不起,口罩存量不足")
        }
}

双分支

go
package main

import "fmt"

func main() {
	//实现功能:如果口罩的库存小于30个,提示:库存不足,否则提示:库存充足
	//定义口罩的数量:
	var count int = 70
	if count < 30 { //这个条件表达式返回的是true的话,后面{}执行了
		fmt.Println("库存不足")
	} else { //count >= 30
		fmt.Println("库存充足")
	}
	//双分支一定会二选一走其中一个分支。

}

多分支

go
package main

import "fmt"

func main() {
	//实现功能:根据给出的学生分数,判断学生的等级:
	// >=90  -----A
	// >=80  -----B
	// >=70  -----C
	// >=60  -----D
	// <60   -----E
	//方式1:利用if单分支实现:
	//定义一个学生的成绩:
	var score int = 18
	//对学生的成绩进行判定:
	// if score >= 90 {
	// 	fmt.Println("您的成绩为A级别")
	// }
	// if score >= 80 && score < 90 {
	// 	fmt.Println("您的成绩为B级别")
	// }
	// if score >= 70 && score < 80 {
	// 	fmt.Println("您的成绩为C级别")
	// }
	// if score >= 60 && score < 70 {
	// 	fmt.Println("您的成绩为D级别")
	// }
	// if score < 60 {
	// 	fmt.Println("您的成绩为E级别")
	// }
	//上面方式1利用多个单分支拼凑出多个选择,多个选择是并列的,依次从上而下顺序执行,即使走了第一个分支,那么其它分支也是需要判断

	//方式2:多分支:优点:如果已经走了一个分支了,那么下面的分支就不会再去判断执行了
	// if score >= 90 {
	// 	fmt.Println("您的成绩为A级别")
	// } else if score >= 80 {//else隐藏:score < 90
	// 	fmt.Println("您的成绩为B级别")
	// } else if score >= 70 {//score < 80
	// 	fmt.Println("您的成绩为C级别")
	// } else if score >= 60 {//score < 70
	// 	fmt.Println("您的成绩为D级别")
	// } else {//score < 60
	// 	fmt.Println("您的成绩为E级别")
	// } //建议你保证else的存在,只有有了else才会真正 起到多选一 的效果
	if score > 10 {
		fmt.Println("aaa")
	} else if score > 6 {
		fmt.Println("bbb")
	}
}

switch语句

go
package main

import "fmt"

func main() {
	//实现功能:根据给出的学生分数,判断学生的等级:
	// >=90  -----A
	// >=80  -----B
	// >=70  -----C
	// >=60  -----D
	// <60   -----E
	//给出一个学生分数:
	var score int = 187
	//根据分数判断等级:
	//switch后面是一个表达式,这个表达式的结果依次跟case进行比较,满足结果的话就执行冒号后面的代码。
	//default是用来“兜底”的一个分支,其它case分支都不走的情况下就会走default分支
	//default分支可以放在任意位置上,不一定非要放在最后。
	switch score / 10 {
	case 10:
		fmt.Println("您的等级为A级")
	case 9:
		fmt.Println("您的等级为A级")
	case 8:
		fmt.Println("您的等级为B级")
	case 7:
		fmt.Println("您的等级为C级")
	case 6:
		fmt.Println("您的等级为D级")
	case 5:
		fmt.Println("您的等级为E级")
	case 4:
		fmt.Println("您的等级为E级")
	case 3:
		fmt.Println("您的等级为E级")
	case 2:
		fmt.Println("您的等级为E级")
	case 1:
		fmt.Println("您的等级为E级")
	case 0:
		fmt.Println("您的等级为E级")
	default:
		fmt.Println("您的成绩有误")
	}

}

注意事项:

  • switch后是一个表达式(即:常量值、变量、一个有返回值的函数等都可以)
  • case后面的值如果是常量值(字面量),则要求不能重复
  • case后的各个值的数据类型,必须和 switch 的表达式数据类型一致
  • case后面可以带多个值,使用逗号间隔。比如 case 值1,值2...
  • case后面不需要带break
  • default语句不是必须的,位置也是随意的。
  • switch后也可以不带表达式,当做if分支来使用
  • switch后也可以直接声明/定义一个变量,分号结束,不推荐
  • switch穿透,利用fallthrough关键字,如果在case语句块后增加fallthrough ,则会继续执行下一个case,也叫switch穿透。

循环

for循环

go语言只有for循环

go
package main

import "fmt"

func main() {
	//实现一个功能:求和: 1+2+3+4+5:
	//求和:
	//利用for循环来解决问题:
	var sum int = 0
	for i := 1; i <= 5; i++ {
		sum += i
	}

	//输出结果:
	fmt.Println(sum)
	// for循环的语法格式:
	// for 初始表达式; 布尔表达式(条件判断); 迭代因子 {
	// 	循环体;--》反复重复执行的内容
	// }
	// 注意:for的初始表达式 不能用var定义变量的形式,要用:=
	// 注意:for循环实际就是让程序员写代码的效率高了,但是底层该怎么执行还是怎么执行的,底层效率没有提高,只是程序员写代码简洁了而已
}

细节1:格式灵活

go
package main

import "fmt"

func main() {
	i := 1       //变量的初始化
	for i <= 5 { //条件表达式。判断条件
		fmt.Println("你好 Golang") //循环体
		i++                      //迭代
	}
}

细节2:死循环

go
package main

import "fmt"

func main() {
	//死循环:
	// for {
	// 	fmt.Println("你好 Golang")
	// }
	for {
		fmt.Println("你好 Golang")
	}
}

for renge键值对循环

(键值循环) for range结构是Go语言特有的一种的迭代结构,在许多情况下都非常有用,for range 可以遍历数组、切片、字符串、map 及通道,for range 语法上类似于其它语言中的 foreach 语句

go
package main

import "fmt"

func main() {
	//定义一个字符串:
	var str string = "hello golang你好"
	//方式1:普通for循环:按照字节进行遍历输出的 (暂时先不使用中文)
	// for i := 0;i < len(str);i++ {//i:理解为字符串的下标
	// 	fmt.Printf("%c \n",str[i])
	// }
	//方式2:for range
	for i, value := range str {
		fmt.Printf("索引为:%d,具体的值为:%c \n", i, value)
	}
	//对str进行遍历,遍历的每个结果的索引值被i接收,每个结果的具体数值被value接收
	//遍历对字符进行遍历的
}

关键字

break

  • switch分支中,每个case分支后都用break结束当前分支,但是在go语言中break可以省略不写。
  • break可以结束正在执行的循环
  • break的作用结束离它最近的循环
go
package main
import "fmt"
func main(){
        //功能:求1-100的和,当和第一次超过300的时候,停止程序
        var sum int = 0
        for i := 1 ; i <= 100 ; i++ {
                sum += i
                fmt.Println(sum)
                if sum >= 300 {
                        //停止正在执行的这个循环:
                        break 
                }
        }
        fmt.Println("-----ok")
}

深入理解

go
package main
import "fmt"
func main(){
        //双重循环:
        for i := 1; i <= 5; i++ {
                for j := 2; j <= 4; j++ {
                        fmt.Printf("i: %v, j: %v \n",i,j)
                        if i == 2 && j == 2 {
                                break
                        }
                }
        }
}

标签的使用展示

go
package main

import "fmt"

func main() {
	//双重循环:
label2:
	for i := 1; i <= 5; i++ {
		for j := 2; j <= 4; j++ {
			fmt.Printf("i: %v, j: %v \n", i, j)
			if i == 2 && j == 2 {
				break label2 //结束指定标签对应的循环
			}
		}
	}
	fmt.Println("-----ok")
}

continue

  • continue的作用是结束离它近的那个循环,继续离它近的那个循环
go
package main
import "fmt"
func main(){
        //功能:输出1-100中被6整除的数:
        //方式1:
        // for i := 1; i <= 100; i++ {
        // 	if i % 6 == 0 {
        // 		fmt.Println(i)
        // 	}
        // }
        //方式2:
        for i := 1; i <= 100; i++ {
                if i % 6 != 0 {
                        continue //结束本次循环,继续下一次循环
                }
                fmt.Println(i)
        }
}

深入理解

go
package main
import "fmt"
func main(){
        //双重循环:
        for i := 1; i <= 5; i++ {
                for j := 2; j <= 4; j++ {			
                        if i == 2 && j == 2 {
                                continue
                        }
                        fmt.Printf("i: %v, j: %v \n",i,j)
                }
        }
        fmt.Println("-----ok")
}

标签的使用展示

go
package main

import "fmt"

func main() {
	//双重循环:
label:
	for i := 1; i <= 5; i++ {
		for j := 2; j <= 4; j++ {
			if i == 2 && j == 2 {
				continue label
			}
			fmt.Printf("i: %v, j: %v \n", i, j)
		}
	}
	fmt.Println("-----ok")
}

goto

  • Golang的 goto 语句可以无条件地转移到程序中指定的行。
  • goto语句通常与条件语句配合使用。可用来实现条件转移.
  • 在Go程序设计中一般不建议使用goto语句,以免造成程序流程的混乱。
go
package main

import "fmt"

func main() {
	fmt.Println("hello golang1")
	fmt.Println("hello golang2")
	if 1 == 1 {
		goto label1 //goto一般配合条件结构一起使用
	}
	fmt.Println("hello golang3")
	fmt.Println("hello golang4")
	fmt.Println("hello golang5")
	fmt.Println("hello golang6")
label1:
	fmt.Println("hello golang7")
	fmt.Println("hello golang8")
	fmt.Println("hello golang9")
}

return

go
package main

import "fmt"

func main() {
	for i := 1; i <= 100; i++ {
		fmt.Println(i)
		if i == 14 {
			return //结束当前的函数
		}
	}
	fmt.Println("hello golang")
}

函数

普通函数

  • 遵循标识符命名规范:见名知意 addNum,驼峰命名addNum
  • 首字母不能是数字
  • 首字母大写该函数可以被本包文件和其它包文件使用(类似public)
  • 首学母小写只能被本包文件使用,其它包文件不能使用(类似private)
  • Golang中函数不支持重载
  • Golang中支持可变参数 (如果你希望函数带有可变数量的参数)
  • 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值
  • 以值传递方式的数据类型,如果希望在函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果来看类似引用传递。
  • 在Go中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。
  • 函数既然是一种数据类型,因此在Go中,函数可以作为形参,并且调用(把函数本身当做一种数据类型)
  • 为了简化数据类型定义,Go支持自定义数据类型 基本语法: type 自定义数据类型名 数据类型
    可以理解为 : 相当于起了一个别名 例如:type mylnt int -----》这时mylnt就等价int来使用了.
  • 支持对函数返回值命名传统写法要求:返回值和返回值的类型对应,顺序不能差
go
package main

import "fmt"

//	func   函数名(形参列表)(返回值类型列表){
//		执行语句..
//		return + 返回值列表
//	}
//
// 自定义函数:功能:两个数相加:
func cal(num1 int, num2 int) int { //如果返回值类型就一个的话,那么()是可以省略不写的
	var sum int = 0
	sum += num1
	sum += num2
	return sum
}
func main() {
	//功能:10 + 20
	//调用函数:
	sum := cal(10, 20)
	fmt.Println(sum)
	// var num1 int = 10
	// var num2 int = 20
	//求和:
	// var sum int = 0
	// sum += num1
	// sum += num2
	// fmt.Println(sum)
	//功能:30 + 50
	var num3 int = 30
	var num4 int = 50
	//调用函数:
	sum1 := cal(num3, num4)
	fmt.Println(sum1)
	//求和:
	// var sum1 int = 0
	// sum1 += num3
	// sum1 += num4
	// fmt.Println(sum1)
}

可变参数案例

go
package main

import "fmt"

// 定义一个函数,函数的参数为:可变参数 ...  参数的数量可变
// args...int 可以传入任意多个数量的int类型的数据  传入0个,1个,,,,n个
func test(args ...int) {
	//函数内部处理可变参数的时候,将可变参数当做切片来处理
	//遍历可变参数:
	for i := 0; i < len(args); i++ {
		fmt.Println(args[i])
	}
}
func main() {
	test()
	fmt.Println("--------------------")
	test(3)
	fmt.Println("--------------------")
	test(37, 58, 39, 59, 47)
}

值传递改变数据案例

go
package main

import "fmt"

// 参数的类型为指针
func test(num *int) {
	// 对地址对应的变量进行改变数值
	*num = 30
}

func main() {
	var num int = 10
	fmt.Println(&num)
	test(&num) // 调用test函数的时候,传入的是num的地址
	fmt.Println(num)
}

函数作为参数的案例

go
package main

import "fmt"

// 定义一个函数:
func test(num int) {
	fmt.Println(num)
}

// 定义一个函数,把另一个函数作为形参:
func test02(num1 int, num2 float32, testFunc func(int)) {
	fmt.Println("-----test02")
}
func main() {
	//函数也是一种数据类型,可以赋值给一个变量
	a := test                                        //变量就是一个函数类型的变量
	fmt.Printf("a的类型是:%T,test函数的类型是:%T \n", a, test) //a的类型是:func(int),test函数的类型是:func(int)
	//通过该变量可以对函数调用
	a(10) //等价于  test(10)
	//调用test02函数:
	test02(10, 3.19, test)
	test02(10, 3.19, a)
}

init函数

  • init函数:初始化函数,可以用来进行一些初始化的操作
  • 每一个源文件都可以包含一个init函数,该函数会在main函数执行前,被Go运行框架调用。
  • 全局变量定义->init函数->main函数的执行流程

示例代码

go
package main

import "fmt"

// 全局变量
var num int = test()

func test() int {
	fmt.Printf("test函数被执行")
	fmt.Println() // 换行作用
	return 10
}

func init() {
	fmt.Println("init函数被执行")
}

func main() {
	fmt.Println("Hello Go")
}

匿名函数

Go支持匿名函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数

匿名函数使用方式:

  • 在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次(用的多)
  • 将匿名函数赋给一个变量(该变量就是函数变量了),再通过该变量来调用匿名函数(用的少)
  • 如何让一个匿名函数,可以在整个程序中有效呢?将匿名函数给一个全局变量就可以了

示例代码如下

go
package main

import "fmt"

var Func01 = func(num1 int, num2 int) int {
	return num1 * num2
}

func main() {
	//定义匿名函数:定义的同时调用
	result := func(num1 int, num2 int) int {
		return num1 + num2
	}(10, 20)
	fmt.Println(result)
	//将匿名函数赋给一个变量,这个变量实际就是函数类型的变量
	//sub等价于匿名函数
	sub := func(num1 int, num2 int) int {
		return num1 - num2
	}
	//直接调用sub就是调用这个匿名函数了
	result01 := sub(30, 70)
	fmt.Println(result01)
	result02 := sub(30, 70)
	fmt.Println(result02)
	result03 := Func01(3, 4)
	fmt.Println(result03)
}

闭包

  • 闭包就是一个函数和与其相关的引用环境组合的一个整体

闭包的本质:

  • 闭包本质依旧是一个匿名函数,只是这个函数引入外界的变量/参数
  • 匿名函数+引用的变量/参数 = 闭包

特点:

  • 返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数 ,因此这个匿名函数就和变量/参数形成一个整体,构成闭包。
  • 闭包中使用的变量/参数会一直保存在内存中,所以会一直使用---》意味着闭包不可滥用(对内存消耗大) 示例代码
go
package main

import "fmt"

// 函数功能:求和
// 函数的名字:getSum 参数为空
// getSum函数返回值为一个函数,这个函数的参数是一个int类型的参数,返回值也是int类型
func getSum() func(int) int {
	var sum int = 0
	return func(num int) int {
		sum = sum + num
		return sum
	}
}

// 闭包:返回的匿名函数+匿名函数以外的变量num
func main() {
	f := getSum()
	fmt.Println(f(1)) //1
	fmt.Println(f(2)) //3
	fmt.Println(f(3)) //6
	fmt.Println(f(4)) //10
}

defer关键字

  • 在函数中,程序员经常需要创建资源,为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer关键字
  • 遇到defer关键字,会将后面的代码语句压入栈中,也会将相关的值同时拷贝入栈中,不会随着函数后面的变化而变化。
  • 比如你想关闭某个使用的资源,在使用的时候直接随手defer,因为defer有延迟执行机制(函数执行完毕再执行defer压入栈的语句),所以你用完随手写了关闭,比较省心,省事
go
package main

import "fmt"

func main() {
	fmt.Println(add(30, 60))
}

func add(num1 int, num2 int) int {
	//在Golang中,程序遇到defer关键字,不会立即执行defer后的语句,而是将defer后的语句压入一个栈中,然后继续执行函数后面的语句defer
	defer fmt.Println("num1= ", num1)
	defer fmt.Println("num2= ", num2)
	//栈的特点是:先进后出
	//在函数执行完毕以后,从栈中取出语句开始执行,按照先进后出的规则执行语句
	var sum int = num1 + num2
	fmt.Println(sum)
	return sum
}

字符串函数

【1】统计字符串的长度,按字节进行统计 len(str)使用内置函数也不用导包的,直接用就行 【2】字符串遍历: r:=[]rune("hello") 【3】字符串转整数: n, err := strconv.Atoi("66") 【4】整数转字符串: str = strconv.ltoa(6887) 【5】查找子串是否在指定的字符串中: strings.Contains("javaandgolang", "go") 【6】统计一个字符串有几个指定的子串: strings.Count("javaandgolang","a") 【7】不区分大小写的字符串比较: fmt.Println(strings.EqualFold("go" ,“Go")) 【8】返回子串在字符串第一次出现的索引值,如果没有回-1: strings.llndex("javaandgolang","a") 【9】字符串的替换:strings.Replace("goandjavagogo", "go", "golang", n) n可以指定你希望替换几个,如果n=-1表示全部替换,替换两个n就是2 【10】按照指定的某个字符,为分割标识,将一个学符串拆分成字符串数组:strings.Split("go-python-java", "-") 【11】将字符串的字母进行大小写的转换:strings.ToLower("Go")// go strings.ToUpper"go")//Go 【12】将字符串左右两边的空格去掉:strings.TrimSpace(" go and java ") 【13】将字符串左右两边指定的字符去掉:strings.Trim("~golang~ ", " ~") 【14】将字符串左边指定的字符去掉:strings.TrimLeft("~golang~", "~") 【15】将字符串右边指定的字符去掉:strings.TrimRight("~golang~", "~") 【16】判断字符串是否以指定的字符串开头: strings.HasPrefix("http://java.sun.com/jsp/jstl/fmt", "http") 【17】判断字符串是否以指定的字符串结束: strings.HasSuffix("demo.png", ".png")

日期和时间的函数

【1】时间和日期的函数,需要到入time包,所以你获取当前时间,就要调用函数Now函数:

go
package main

import (
	"fmt"
	"time"
)

func main() {
	//时间和日期的函数,需要到入time包,所以你获取当前时间,就要调用函数Now函数:
	now := time.Now()
	//Now()返回值是一个结构体,类型是:time.Time
	fmt.Printf("%v ~~~ 对应的类型为:%T\n", now, now)
	//2021-02-08 17:47:21.7600788 +0800 CST m=+0.005983901 ~~~ 对应的类型为:time.Time
	//调用结构体中的方法:
	fmt.Printf("年:%v \n", now.Year())
	fmt.Printf("月:%v \n", now.Month())      //月:February
	fmt.Printf("月:%v \n", int(now.Month())) //月:2
	fmt.Printf("日:%v \n", now.Day())
	fmt.Printf("时:%v \n", now.Hour())
	fmt.Printf("分:%v \n", now.Minute())
	fmt.Printf("秒:%v \n", now.Second())
}

【2】日期的格式化: (1)将日期以年月日时分秒按照格式输出为字符串:

go
        //Printf将字符串直接输出:
        fmt.Printf("当前年月日: %d-%d-%d 时分秒:%d:%d:%d  \n",now.Year(),now.Month(),
        now.Day(),now.Hour(),now.Minute(),now.Second())
        //Sprintf可以得到这个字符串,以便后续使用:
        datestr := fmt.Sprintf("当前年月日: %d-%d-%d 时分秒:%d:%d:%d  \n",now.Year(),now.Month(),
        now.Day(),now.Hour(),now.Minute(),now.Second())
        fmt.Println(datestr)

(2)按照指定格式:

go
        //这个参数字符串的各个数字必须是固定的,必须这样写 
        datestr2 := now.Format("2006/01/02 15/04/05")
        fmt.Println(datestr2)
        //选择任意的组合都是可以的,根据需求自己选择就可以(自己任意组合)。
        datestr3 := now.Format("2006 15:04")
        fmt.Println(datestr3)

错误处理

报错代码演示

go
package main
import "fmt"

func main() {
	test()
	fmt.Println("上述函数执行成功,正常执行下面逻辑")
}

func test() {
	num1 := 10
	num2 := 0
	result := num1 / num2 // 0不能被除,会报错
	fmt.Println(result)
}

defer+recover机制处理错误

错误处理/捕获机制: go中追求代码优雅,引入机制:defer+recover机制处理错误

**上面代码改造如下: **

go
package main

import "fmt"

func main() {
	test()
	fmt.Println("上述函数执行成功,正常执行下面逻辑")
}

func test() {
	// 利用defer+recover来捕获错误
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("错误已经被捕获", err)
		}
	}()
	// 通过上面匿名函数捕获,下面报错不会停止程序执行了,可以正常走逻辑
	num1 := 10
	num2 := 0
	result := num1 / num2 // 0不能被除,会报错
	fmt.Println(result)
}

自定义错误

自定义错误:需要调用errors包下的New函数:函数返回error类型

go
package main

import (
	"errors"
	"fmt"
)

func main() {
	err := test()
	if err != nil {
		fmt.Println("程序发生了错误", err)
	}
	fmt.Println("上述函数执行成功,正常执行下面逻辑")
}

func test() (err error) {
	num1 := 10
	num2 := 0
	if num2 == 0 {
		// 抛出自定义错误
		return errors.New("除数不能为0")
	} else {
		// 如果除数不为0,正常走逻辑
		result := num1 / num2 // 0不能被除,会报错
		fmt.Println(result)
		return nil
	}
}

有一种情况:程序出现错误以后,后续代码就没有必要执行,想让程序中断,退出程序: 借助:builtin包下内置函数:panic

上面代码加个方法

go
package main

import (
	"errors"
	"fmt"
)

func main() {
	err := test()
	if err != nil {
		fmt.Println("程序发生了错误", err)
		panic(err) // 程序停止往下执行
	}
	fmt.Println("上述函数执行成功,正常执行下面逻辑")
}

func test() (err error) {
	num1 := 10
	num2 := 0
	if num2 == 0 {
		// 抛出自定义错误
		return errors.New("除数不能为0")
	} else {
		// 如果除数不为0,正常走逻辑
		result := num1 / num2 // 0不能被除,会报错
		fmt.Println(result)
		return nil
	}
}

数组

数组引入

案例

go
package main

import "fmt"

func main() {
	//实现的功能:给出五个学生的成绩,求出成绩的总和,平均数:
	//给出五个学生的成绩:--->数组存储:
	//定义一个数组:
	var scores [5]int
	//将成绩存入数组:
	scores[0] = 95
	scores[1] = 91
	scores[2] = 39
	scores[3] = 60
	scores[4] = 21
	//求和:
	//定义一个变量专门接收成绩的和:
	sum := 0
	for i := 0; i < len(scores); i++ { //i: 0,1,2,3,4
		sum += scores[i]
	}
	//平均数:
	avg := sum / len(scores)
	//输出
	fmt.Printf("成绩的总和为:%v,成绩的平均数为:%v", sum, avg)
}

数组的遍历方式

【1】普通for循环 【2】键值循环 (键值循环) for range结构是Go语言特有的一种的迭代结构,在许多情况下都非常有用,for range 可以遍历数组、切片、字符串、map 及通道,for range 语法上类似于其它语言中的 foreach 语句,一般形式为:

go
for key, val := range coll {
    ...
}

注意: (1)coll就是你要的数组 (2)每次遍历得到的索引用key接收,每次遍历得到的索引位置上的值用val (3)key、value的名字随便起名 k、v key、value
(4)key、value属于在这个循环中的局部变量 (5)你想忽略某个值:用_就可以了:

go
package main

import "fmt"

func main() {
	//实现的功能:给出五个学生的成绩,求出成绩的总和,平均数:
	//给出五个学生的成绩:--->数组存储:
	//定义一个数组:
	var scores [5]int
	//将成绩存入数组:(循环 + 终端输入)
	for i := 0; i < len(scores); i++ { //i:数组的下标
		fmt.Printf("请录入第个%d学生的成绩", i+1)
		fmt.Scanln(&scores[i])
	}
	//展示一下班级的每个学生的成绩:(数组进行遍历)
	//方式1:普通for循环:
	for i := 0; i < len(scores); i++ {
		fmt.Printf("第%d个学生的成绩为:%d\n", i+1, scores[i])
	}
	fmt.Println("-------------------------------")
	//方式2:for-range循环
	for key, value := range scores {
		fmt.Printf("第%d个学生的成绩为:%d\n", key+1, value)
	}
}

数组的初始化方式

go
package main

import "fmt"

func main() {
	//第一种(指定长度):
	var arr1 [3]int = [3]int{3, 6, 9}
	fmt.Println(arr1)
	//第二种(指定长度):
	var arr2 = [3]int{1, 4, 7}
	fmt.Println(arr2)
	//第三种(不指定长度):
	var arr3 = [...]int{4, 5, 6, 7}
	fmt.Println(arr3)
	//第四种(不指定长度):
	var arr4 = [...]int{2: 66, 0: 33, 1: 99, 3: 88}
	fmt.Println(arr4)
}

二维数组的定义和遍历

go
package main
import "fmt"
func main(){
        //定义二维数组:
        var arr [3][3]int = [3][3]int{{1,4,7},{2,5,8},{3,6,9}}
        fmt.Println(arr)
        fmt.Println("------------------------")
        //方式1:普通for循环:
        for i := 0;i < len(arr);i++{
                for j := 0;j < len(arr[i]);j++ {
                        fmt.Print(arr[i][j],"\t")
                }
                fmt.Println()
        }
        fmt.Println("------------------------")
        //方式2:for range循环:
        for key,value := range arr {
                for k,v := range value {
                        fmt.Printf("arr[%v][%v]=%v\t",key,k,v)
                }
                fmt.Println()
        }
}

切片

切片的引入

【1】切片(slice)是golang中一种特有的数据类型 【2】数组有特定的用处,但是却有一些呆板(数组长度固定不可变),所以在 Go 语言的代码里并不是特别常见。相对的切片却是随处可见的,切片是一种建立在数组类型之上的抽象,它构建在数组之上并且提供更强大的能力和便捷。 【3】切片(slice)是对数组一个连续片段的引用,所以切片是一个引用类型。这个片段可以是整个数组,或者是由起始和终止索引标识的一些项的子集。需要注意的是,终止索引标识的项不包括在切片内。切片提供了一个相关数组的动态窗口。 【4】切片的语法:var 切片名 []类型 = 数组的一个片段引用

示例代码

go
package main

import "fmt"

func main() {
	//定义数组
	var intarr [6]int = [6]int{1, 2, 3, 4, 5, 6}
	// 切片构建在数组之上
	// 定义一个切片名字为slice,[]动态变化的数组,长度不写,int类型,intarr是原数组
	// [1:3]切片 - 切出的一段片段 - 索引:从1开始,到3结束(不包含3) - [1,3)
	// var slice []int = intarr[1:3]
	slice := intarr[1:3]
	// 简写方式
    // 1) var slice = arr[0:end]  ----> var slice = arr[:end]
    // 2) var slice = arr[start:len(arr)]  ---->  var slice = arr[start:]
    // 3) var slice = arr[0:len(arr)]   ---->  var slice = arr[:]

	// 输出数组
	fmt.Println("intarr", intarr)
	// 输出切片
	fmt.Println("slice", slice)
	//切片元素个数
	fmt.Println("slice len:", len(slice))
	// 获取切片容量,容量可以动态变化
	fmt.Println("slice cap:", cap(slice))
}

切片的定义

【1】方式1:定义一个切片,然后让切片去引用一个已经创建好的数组。

go
package main

import "fmt"

func main() {
	//定义数组
	var intarr [6]int = [6]int{1, 2, 3, 4, 5, 6}
	// 定义切片
	slice := intarr[1:3]
	fmt.Println("slice", slice)
}

【2】方式2:通过make内置函数来创建切片。基本语法: var切片名[type = make([], len,[cap])

go
package main

import "fmt"

func main() {
	// 使用make内置函数创建切片
	// make函数的三个参数:1.切片类型2.切片长度3.切片的容量
	slice := make([]int, 4, 20)
	fmt.Println(slice) // [0,0,0,0]
	fmt.Println("切片的长度", len(slice))
	fmt.Println("切片的容量", cap(slice))
	slice[0] = 66
	slice[1] = 88
	fmt.Println(slice)
}

PS : make底层创建一个数组,对外不可见,所以不可以直接操作这个数组,要通过slice去间接的访问各个元素,不可以直接对数组进行维护/操作

【3】方式3:定一个切片,直接就指定具体数组,使用原理类似make的方式。

go
package main

import "fmt"

func main() {
	// 直接定义切片
	slice := []int{1, 4, 7}
	fmt.Println(slice) // [1,4,7]
	fmt.Println("切片的长度", len(slice))
	fmt.Println("切片的容量", cap(slice))
}

切片的遍历

go
package main

import "fmt"

func main() {
	//定义切片:
	slice := make([]int, 4, 20)
	slice[0] = 66
	slice[1] = 88
	slice[2] = 99
	slice[3] = 100
	//方式1:普通for循环
	for i := 0; i < len(slice); i++ {
		fmt.Printf("slice[%v] = %v \t", i, slice[i])
	}
	fmt.Println("\n------------------------------")
	//方式2:for-range循环:
	for i, v := range slice {
		fmt.Printf("下标:%v ,元素:%v\n", i, v)
	}
}

切片可以继续切片

切片的新增

go
package main
import "fmt"
func main(){
        //定义数组:
        var intarr [6]int = [6]int{1,4,7,3,6,9}
        //定义切片:
        var slice []int = intarr[1:4] //4,7,3
        fmt.Println(len(slice))
        slice2 := append(slice,88,50)
        fmt.Println(slice2) //[4 7 3 88 50]
        fmt.Println(slice)
        //底层原理:
        //1.底层追加元素的时候对数组进行扩容,老数组扩容为新数组:
        //2.创建一个新数组,将老数组中的4,7,3复制到新数组中,在新数组中追加88,50
        //3.slice2 底层数组的指向 指向的是新数组 
        //4.往往我们在使用追加的时候其实想要做的效果给slice追加:
        slice = append(slice,88,50)
        fmt.Println(slice)
        //5.底层的新数组 不能直接维护,需要通过切片间接维护操作。
}

切片的拷贝

go
package main
import "fmt"
func main(){
        //定义切片:
        var a []int = []int{1,4,7,3,6,9}
        //再定义一个切片:
        var b []int = make([]int,10)
        //拷贝:
        copy(b,a) //将a中对应数组中元素内容复制到b中对应的数组中
        fmt.Println(b)
}

map类型

初识map

【1】映射(map), Go语言中内置的一种类型,它将键值对相关联,我们可以通过键 key来获取对应的值 value。 类似其它语言的集合

【2】基本语法 var map变量名 map[keytype]valuetype

注意:

  • key、value的类型:bool、数字、string、指针、channel 、还可以是只包含前面几个类型的接口、结构体、数组
  • key通常为int 、string类型,value通常为数字(整数、浮点数)、string、map、结构体
  • key:slice、map、function不可以

【3】map的特点:

  • map集合在使用前一定要make
  • map的key-value是无序的
  • key是不可以重复的,如果遇到重复,后一个value会替换前一个value
  • value可以重复的
go
package main

import "fmt"

func main() {
	//定义map变量:
	var a map[int]string
	//只声明map内存是没有分配空间
	//必须通过make函数进行初始化,才会分配空间:
	a = make(map[int]string, 10) //map可以存放10个键值对
	//将键值对存入map中:
	a[20095452] = "张三"
	a[20095387] = "李四"
	a[20097291] = "王五"
	a[20095387] = "朱六"
	a[20096699] = "张三"
	//输出集合
	fmt.Println(a)
}

map的三种创建方式

go
package main

import "fmt"

func main() {
	//方式1:
	//定义map变量:
	var a map[int]string
	//只声明map内存是没有分配空间
	//必须通过make函数进行初始化,才会分配空间:
	a = make(map[int]string, 10) //map可以存放10个键值对
	//将键值对存入map中:
	a[20095452] = "张三"
	a[20095387] = "李四"
	//输出集合
	fmt.Println(a)
	//方式2:
	b := make(map[int]string)
	b[20095452] = "张三"
	b[20095387] = "李四"
	fmt.Println(b)
	//方式3:
	c := map[int]string{
		20095452: "张三",
		20098765: "李四",
	}
	c[20095387] = "王五"
	fmt.Println(c)
}

map的操作

【1】增加和更新操作: map["key"]= value ——》 如果key还没有,就是增加,如果key存在就是修改。 【2】删除操作: delete(map,"key") , delete是一个内置函数,如果key存在,就删除该key-value,如果k的y不存在,不操作,但是也不会报错 【3】清空操作: (1)如果我们要删除map的所有key ,没有一个专门的方法一次删除,可以遍历一下key,逐个删除 (2)或者map = make(...),make一个新的,让原来的成为垃圾,被gc回收 【4】查找操作: value ,bool = map[key] value为返回的value,bool为是否返回 ,要么true 要么false

go
package main

import "fmt"

func main() {
	//定义map
	b := make(map[int]string)
	//增加:
	b[20095452] = "张三"
	b[20095387] = "李四"
	//修改:
	b[20095452] = "王五"
	//删除:
	delete(b, 20095387)
	delete(b, 20089546)
	fmt.Println(b)
	//查找:
	value, flag := b[200]
	fmt.Println(value)
	fmt.Println(flag)
}

【5】获取长度:len函数 【6】遍历:for-range

go
package main

import "fmt"

func main() {
	//定义map
	b := make(map[int]string)
	//增加:
	b[20095452] = "张三"
	b[20095387] = "李四"
	b[20098833] = "王五"
	//获取长度:
	fmt.Println(len(b))
	//遍历:
	for k, v := range b {
		fmt.Printf("key为:%v value为%v \t", k, v)
	}
	fmt.Println("---------------------------")
	//加深难度:
	a := make(map[string]map[int]string)
	//赋值:
	a["班级1"] = make(map[int]string, 3)
	a["班级1"][20096677] = "露露"
	a["班级1"][20098833] = "丽丽"
	a["班级1"][20097722] = "菲菲"
	a["班级2"] = make(map[int]string, 3)
	a["班级2"][20089911] = "小明"
	a["班级2"][20085533] = "小龙"
	a["班级2"][20087244] = "小飞"
	for k1, v1 := range a {
		fmt.Println(k1)
		for k2, v2 := range v1 {
			fmt.Printf("学生学号为:%v 学生姓名为%v \t", k2, v2)
		}
		fmt.Println()
	}
}

面向对象

引入

【1】Golang语言面向对象编程说明: (1)Golang也支持面向对象编程(OOP),但是和传统的面向对象编程有区别,并不是纯粹的面向对象语言。所以我们说Golang支持面向对象编程特性是比较准确的。 (2)Golang没有类(class),Go语言的结构体(struct)和其它编程语言的类(class)有同等的地位,你可以理解Gelang是基于struct来实现OOP特性的。 (3)Golang面向对象编程非常简洁,去掉了传统OOP语言的方法重载、构造函数和析构函数、隐藏的this指针等等 (4)Golang仍然有面向对象编程的继承,封装和多态的特性,只是实现的方式和其它OOP语言不一样,比如继承:Golang没有extends 关键字,继承是通过匿名字段来实现。

【2】结构体的引入:

具体的对象: 一位老师:珊珊老师: 姓名:赵珊珊 年龄:31岁 性别 :女 ......

可以使用变量来处理:

go
package main

import "fmt"

func main() {
	//珊珊老师: 姓名:赵珊珊   年龄:31岁   性别 :女
	var name string = "赵珊珊"
	var age int = 31
	var sex string = "女"
	//马士兵老师:
	var name2 string = "马士兵"
	var age2 int = 45
	var sex2 string = "男"

	fmt.Println(name, age, sex, name2, age2, sex2)
}

结构体

第一种创建方式

go
package main
import "fmt"
//定义老师结构体,将老师中的各个属性  统一放入结构体中管理:
type Teacher struct{
        //变量名字大写外界可以访问这个属性
        Name string
        Age int
        School string
}
func main(){
        //创建老师结构体的实例、对象、变量:
        var t1 Teacher // var a int
        fmt.Println(t1) //在未赋值时默认值:{ 0 }
        t1.Name = "马士兵"
        t1.Age = 45
        t1.School = "清华大学"
        fmt.Println(t1)
        fmt.Println(t1.Age + 10)
}

第二种创建方式

go
package main

import "fmt"

// 定义老师结构体,将老师中的各个属性  统一放入结构体中管理:
type Teacher struct {
	//变量名字大写外界可以访问这个属性
	Name   string
	Age    int
	School string
}

func main() {
	//创建老师结构体的实例、对象、变量:
	var t1 Teacher = Teacher{"马士兵", 45, "清华大学"} // var a int
	fmt.Println(t1)
}

第三种创建方式

go
package main

import "fmt"

// 定义老师结构体,将老师中的各个属性  统一放入结构体中管理:
type Teacher struct {
	//变量名字大写外界可以访问这个属性
	Name   string
	Age    int
	School string
}

func main() {
	//创建老师结构体的实例、对象、变量:
	var t *Teacher = new(Teacher)
	// t是指针,t其实指向的就是地址,应该给这个地址的指向的对象的字段赋值:
	(*t).Name = "马士兵"
	(*t).Age = 45
	// 为了符合程序员的编程习惯,go提供了简化的赋值方式:
	t.School = "清华大学" // go编译器底层对t.Schoo1转化(*t).School="清华大学”
	fmt.Println(*t)
}

第四种创建方式

go
package main

import "fmt"

// 定义老师结构体,将老师中的各个属性  统一放入结构体中管理:
type Teacher struct {
	//变量名字大写外界可以访问这个属性
	Name   string
	Age    int
	School string
}

func main() {
	//创建老师结构体的实例、对象、变量:
	var t *Teacher = &Teacher{"马士兵", 45, "清华大学"}
	fmt.Println(*t)
}

结构体之间的转换

【1】结构体是用户单独定义的类型,和其它类型进行转换时需要有完全相同的字段(名字、个数和类型)

go
package main

import "fmt"

type Student struct {
	Age int
}
type Person struct {
	Age int
}

func main() {
	var s Student = Student{10}
	var p Person = Person{10}
	s = Student(p)
	fmt.Println(s)
	fmt.Println(p)
}

【2】结构体进行type重新定义(相当于取别名),Golang认为是新的数据类型,但是相互间可以强转

go
package main

import "fmt"

type Student struct {
	Age int
}
type Stu Student

func main() {
	var s1 Student = Student{19}
	var s2 Stu = Stu{19}
	s1 = Student(s2)
	fmt.Println(s1)
	fmt.Println(s2)
}

方法

方法的引入

【1】方法是作用在指定的数据类型上、和指定的数据类型绑定,因此自定义类型,都可以有方法,而不仅仅是struct 【2】方法的声明和调用格式: 声明:

go
type A struct {
    Num int
}
func (a A) test() {
   fmt.Println(a.Num)
}

调用: var a A a.test()

(1)func (a A) test()相当于A结构体有一个方法叫test (2)(a A)体现方法test和结构体A绑定关系 (3)代码层面:

go
package main

import "fmt"

// 定义person结构体
type Person struct {
	Name string
}

// 给Person结构体绑定方法test
func (p Person) test() {
	fmt.Println(p.Name)
}

func main() {
	// 创建结构体对象
	var p Person
	p.Name = "丽丽"
	p.test()
}

注意: (1)test方法中参数名字随意起 (2)结构体Person和test方法绑定,调用test方法必须靠指定的类型:Person (3)如果其他类型变量调用test方法一定会报错。 (4)结构体对象传入test方法中,值传递,和函数参数传递一致。

go
package main

import "fmt"

// 定义person结构体
type Person struct {
	Name string
}

// 给Person结构体绑定方法test
func (p Person) test() {
	p.Name = "露露"
	fmt.Println(p.Name)
}

func main() {
	// 创建结构体对象
	var p Person
	p.Name = "丽丽"
	p.test()
	fmt.Println(p.Name)
}

方法的注意事项

(1)结构体类型是值类型,在方法调用中,遵守值类型的传递机制,是值拷贝传递方式 (2)如程序员希望在方法中,改变结构体变量的值,可以通过结构体指针的方式来处理

go
package main

import "fmt"

// 定义person结构体
type Person struct {
	Name string
}

// 给Person结构体绑定方法test
func (p *Person) test() {
	(*p).Name = "露露"
	fmt.Println((*p).Name)
}

func main() {
	// 创建结构体对象
	var p Person
	p.Name = "丽丽"
	(&p).test()
	fmt.Println(p.Name)
}

我们写程序的时候,可以直接简化:

go
package main

import "fmt"

// 定义person结构体
type Person struct {
	Name string
}

// 给Person结构体绑定方法test
func (p *Person) test() {
	p.Name = "露露"
	fmt.Println(p.Name)
}

func main() {
	// 创建结构体对象
	var p Person
	p.Name = "丽丽"
	p.test()
	fmt.Println(p.Name)
}

(3)Golang中的方法作用在指定的数据类型上的,和指定的数据类型绑定,因此自定义类型,都可以有方法,而不仅仅是struct,比如int , float32等都可以有方法

go
package main

import "fmt"

type integer int

func (i integer) print() {
	i = 30
	fmt.Println("i = ", i)
}
func (i *integer) change() {
	*i = 30
	fmt.Println("i = ", *i)
}
func main() {
	var i integer = 20
	i.print()
	i.change()
	fmt.Println(i)
}

(4)方法的访问范围控制的规则,和函数一样。方法名首字母小写,只能在本包访问,方法首字母大写,可以在本包和其它包访问。 (5)如果一个类型实现了String()这个方法,那么fmt.Println默认会调用这个变量的String()进行输出

以后定义结构体的话,常定义String()作为输出结构体信息的方法,在fmt.Println会自动调用

go
package main

import "fmt"

type Student struct {
	Name string
	Age  int
}

func (s *Student) String() string {
	str := fmt.Sprintf("Name = %v , Age = %v", s.Name, s.Age)
	return str
}
func main() {
	stu := Student{
		Name: "丽丽",
		Age:  20,
	}
	//传入地址,如果绑定了String方法就会自动调用
	fmt.Println(&stu)
}

方法和函数的区别

【1】绑定指定类型: 方法:需要绑定指定数据类型 函数:不需要绑定数据类型

【2】调用方式不一样: 函数的调用方式: 函数名(实参列表) 方法的调用方式:变量.方法名(实参列表)

go
package main

import "fmt"

type Student struct {
	Name string
}

// 定义方法:
func (s Student) test01() {
	fmt.Println(s.Name)
}

// 定义函数:
func method01(s Student) {
	fmt.Println(s.Name)
}
func main() {
	//调用函数:
	var s Student = Student{"丽丽"}
	method01(s)
	//方法调用:
	s.test01()
}

【3】对于函数来说,参数类型对应是什么就要传入什么。

go
package main
import "fmt"
type Student struct{
        Name string
}
//定义函数:
func method01(s Student){
        fmt.Println(s.Name)
}
func method02(s *Student){
        fmt.Println((*s).Name)
}
func main(){
        var s Student = Student{"丽丽"}
        method01(s)
        //method01(&s)错误
        method02(&s)
        //method02(s)错误
}

【4】对于方法来说,接收者为值类型,可以传入指针类型,接受者为指针类型,可以传入值类型。

go
package main
import "fmt"
type Student struct{
        Name string
}
//定义方法:
func (s Student) test01(){
        fmt.Println(s.Name)
}
func (s *Student) test02(){
        fmt.Println((*s).Name)
}
func main(){
        var s Student = Student{"丽丽"}
        s.test01()
        (&s).test01()//虽然用指针类型调用,但是传递还是按照值传递的形式
        (&s).test02()
        s.test02()//等价
}

创建结构体实例时指定字段值

go
package main

import "fmt"

type Student struct {
	Name string
	Age  int
}

func main() {
	// 方式一:按照顺序赋值操作
	var s1 Student = Student{"张三", 18}
	fmt.Println(s1)
	
	// 方式二:按照指定类型
	var s2 Student = Student{Name: "李四", Age: 19}
	fmt.Println(s2)

	// 方式三:想要返回结构体的指针类型
	var s3 *Student = &Student{Name: "王五", Age: 20}
	fmt.Println(*s3)
	var s4 *Student = &Student{"赵六", 21}
	fmt.Println(*s4)
}

封装

【1】什么是封装: 封装(encapsulation)就是把抽象出的字段和对字段的操作封装在一起,数据被保护在内部,程序的其它包只有通过被授权的操作方法,才能对字段进行操作。

【2】封装的好处:

  1. 隐藏实现细节
  2. 提可以对数据进行验证,保证安全合理

【3】Golang中如何实现封装:

  1. 建议将结构体、字段(属性)的首字母小写(其它包不能使用,类似private,实际开发不小写也可能,因为封装没有那么严格)
  2. 给结构体所在包提供一个工厂模式的函数,首字母大写(类似一个构造函数)
  3. 提供一个首字母大写的Set方法(类似其它语言的public),用于对属性判断并赋值
go
                func (var 结构体类型名)SetXxx(参数列表){
                           //加入数据验证的业务逻辑
                           var.Age =参数
                }
  1. 提供一个首字母大写的Get方法(类似其它语言的public),用于获取属性的值
go
                func (var结构体类型名) GetXxx() (返回值列表){
                          return var.字段; 
                    }

在目录下新建model/model.go文件,内容如下

go
 package model

// 名字为小写,其他包不能访问
type person struct {
	Name string
	Age  int
}

// 定义工厂模式的函数,相当于有参构造器
func NewPerson(name string, age int) *person {
	return &person{
		Name: name,
		Age:  age,
	}
}

// 定义set和get方法,对age字段进行封装,因为在方法中可以加一系列的限制操作,确保被封装字段的安全合理性
func (p *person) SetAge(age int) {
	if age < 0 || age > 200 {
		panic("年龄不合法")
	}
	p.Age = age
}

func (p *person) GetAge() int {
	return p.Age
}

在目录下main.go使用如下:

go
package main

import (
	"demo01/model"
	"fmt"
)

func main() {
	// 创建person结构体的实例
	p := model.NewPerson("张三", 18)
	// 设置年龄
	p.SetAge(20)
	// 获取年龄
	age := p.GetAge()
	fmt.Println(age)
}

继承

继承的引入

【1】继承的引入: 当多个结构体存在相同的属性(字段)和方法时,可以从这些结构体中抽象出结构体,在该结构体中定义这些相同的属性和方法,其它的结构体不需要重新定义这些属性和方法,只需嵌套一个匿名结构体即可。也就是说:在Golang中,如果一个struct嵌套了另一个匿名结构体,那么这个结构体可以直接访问匿名结构体的字段和方法,从而实现了继承特性。 【2】继承的优点: 提高代码的复用性、扩展性

代码实现

go
package main

import (
	"fmt"
)

// 定义动物结构体:
type Animal struct {
	Age    int
	Weight float32
}

// 给Animal绑定方法:喊叫:
func (an *Animal) Shout() {
	fmt.Println("我可以大声喊叫")
}

// //给Animal绑定方法:自我展示:
func (an *Animal) ShowInfo() {
	fmt.Printf("动物的年龄是:%v,动物的体重是:%v", an.Age, an.Weight)
}

// 定义结构体:Cat
type Cat struct {
	//为了复用性,体现继承思维,嵌入匿名结构体:——》将Animal中的字段和方法都达到复用
	Animal
}

// 对Cat绑定特有的方法:
func (c *Cat) scratch() {
	fmt.Println("我是小猫,我可以挠人")
}
func main() {
	//创建Cat结构体示例:
	cat := &Cat{}
	cat.Animal.Age = 3
	cat.Animal.Weight = 10.6
	cat.Animal.Shout()
	cat.Animal.ShowInfo()
	cat.scratch()
}

继承的注意事项

【1】结构体可以使用嵌套匿名结构体所有的字段和方法,即:首字母大写或者小写的字段、方法,都可以使用。

go
package main

import (
	"fmt"
)

// 定义动物结构体:
type Animal struct {
	Age    int
	weight float32
}

// 给Animal绑定方法:喊叫:
func (an *Animal) Shout() {
	fmt.Println("我可以大声喊叫")
}

// //给Animal绑定方法:自我展示:
func (an *Animal) showInfo() {
	fmt.Printf("动物的年龄是:%v,动物的体重是:%v", an.Age, an.weight)
}

// 定义结构体:Cat
type Cat struct {
	//为了复用性,体现继承思维,嵌入匿名结构体:——》将Animal中的字段和方法都达到复用
	Animal
}

// 对Cat绑定特有的方法:
func (c *Cat) scratch() {
	fmt.Println("我是小猫,我可以挠人")
}
func main() {
	//创建Cat结构体示例:
	cat := &Cat{}
	cat.Animal.Age = 3
	cat.Animal.weight = 10.6
	cat.Animal.Shout()
	cat.Animal.showInfo()
	cat.scratch()
}

【2】匿名结构体字段访问可以简化。

go
func main() {
	//创建Cat结构体示例:
	cat := &Cat{}
	cat.Animal.Age = 3
	cat.Animal.weight = 10.6
	cat.Animal.Shout()
	cat.Animal.showInfo()
	cat.scratch()
}

等同如下

func main() {
	//创建Cat结构体示例:
	cat := &Cat{}
	cat.Age = 3
	cat.weight = 10.6
	cat.Shout()
	cat.showInfo()
	cat.scratch()
}

cat.Age --->cat对应的结构体中找是否有Age字段,如果有直接使用,如果没有就去找嵌入的结构体类型中的Age

【3】当结构体和匿名结构体有相同的字段或者方法时,编译器采用就近访问原则访问,如希望访问匿名结构体的字段和方法,可以通过匿名结构体名来区分。

js
package main

import (
	"fmt"
)

// 定义动物结构体:
type Animal struct {
	Age    int
	weight float32
}

// 给Animal绑定方法:喊叫:
func (an *Animal) Shout() {
	fmt.Println("我可以大声喊叫")
}

// //给Animal绑定方法:自我展示:
func (an *Animal) showInfo() {
	fmt.Printf("动物的年龄是:%v,动物的体重是:%v", an.Age, an.weight)
}

// 定义结构体:Cat
type Cat struct {
	//为了复用性,体现继承思维,嵌入匿名结构体:——》将Animal中的字段和方法都达到复用
	Animal
	Age int
}

func (c *Cat) showInfo() {
	fmt.Printf("~~~~~~~~动物的年龄是:%v,动物的体重是:%v", c.Age, c.weight)
}

// 对Cat绑定特有的方法:
func (c *Cat) scratch() {
	fmt.Println("我是小猫,我可以挠人")
}
func main() {
	//创建Cat结构体示例:
	// cat := &Cat{}
	// cat.Age = 3
	// cat.weight = 10.6
	// cat.Shout()
	// cat.showInfo()
	// cat.scratch()
	cat := &Cat{}
	cat.weight = 9.4
	cat.Age = 10 //就近原则
	cat.Animal.Age = 20
	cat.showInfo() //就近原则
	cat.Animal.showInfo()
}

【4】Golang中支持多继承:如一个结构体嵌套了多个匿名结构体,那么该结构体可以直接访问嵌套的匿名结构体的字段和方法,从而实现了多重继承。为了保证代码的简洁性,建议大家尽量不使用多重继承,很多语言就将多重继承去除了,但是Go中保留了。

go
package main

import (
	"fmt"
)

type A struct {
	a int
	b string
}
type B struct {
	c int
	d string
}
type C struct {
	A
	B
}

func main() {
	//构建C结构体实例:
	c := C{A{10, "aaa"}, B{20, "ccc"}}
	fmt.Println(c)
}

【5】如嵌入的匿名结构体有相同的字段名或者方法名,则在访问时,需要通过匿名结构体类型名来区分。

go
package main

import (
	"fmt"
)

type A struct {
	a int
	b string
}
type B struct {
	c int
	d string
	a int
}
type C struct {
	A
	B
}

func main() {
	//构建C结构体实例:
	c := C{A{10, "aaa"}, B{20, "ccc", 50}}
	fmt.Println(c.b)
	fmt.Println(c.d)
	fmt.Println(c.A.a)
	fmt.Println(c.B.a)
}

【6】结构体的匿名字段可以是基本数据类型。

go
package main

import (
	"fmt"
)

type A struct {
	a int
	b string
}
type B struct {
	c int
	d string
	a int
}
type C struct {
	A
	B
	int  // 匿名字段基本数据类型
}

func main() {
	//构建C结构体实例:
	c := C{A{10, "aaa"}, B{20, "ccc", 50},888}
	fmt.Println(c.b)
	fmt.Println(c.d)
	fmt.Println(c.A.a)
	fmt.Println(c.B.a)
	fmt.Println(c.int) // 访问匿名字段
}

【7】嵌套匿名结构体后,也可以在创建结构体变量(实例)时,直接指定各个匿名结构体字段的值。

go
package main

import (
	"fmt"
)

type A struct {
	a int
	b string
}
type B struct {
	c int
	d string
	a int
}
type C struct {
	A
	B
	int // 匿名字段基本数据类型
}

func main() {
	//构建C结构体实例:
	//c := C{A{10, "aaa"}, B{20, "ccc", 50}, 888}
	
	// 直接指定匿名字段的值
	c := C{
		A{
			a: 10,
			b: "aaa",
		},
		B{
			c: 20,
			d: "ccc",
			a: 50,
		},
		888,
	}
	fmt.Println(c.b)
	fmt.Println(c.d)
	fmt.Println(c.A.a)
	fmt.Println(c.B.a)
	fmt.Println(c.int) // 访问匿名字段
}

【8】嵌入匿名结构体的指针也是可以的。

go
package main

import "fmt"

type A struct {
	a int
	b string
}
type B struct {
	c int
	d string
	a int
}
type C struct {
	*A
	*B
	int
}

func main() {
	c := C{&A{10, "hello"}, &B{20, "world", 50}, 888}
	fmt.Println(c)
	fmt.Println(*c.A)
	fmt.Println(*c.B)
}

【9】结构体的字段可以是结构体类型的。(组合模式)

go
package main

import "fmt"

type A struct {
	c int
	d string
	a int
}

type B struct {
	a int
	b string
	c A // 组合模式
}

func main() {
	b := B{1, "hello", A{2, "world", 99}}
	fmt.Println(b)
	fmt.Println(b.c.d)
}

接口

接口的引入

go
package main

import "fmt"

// 接口的定义:定义规则、定义规范,定义某种能力:
type SayHello interface {
	//声明没有实现的方法:
	sayHello()
}

// 接口的实现:定义一个结构体:
// 中国人:
type Chinese struct {
}

// 实现接口的方法---》具体的实现:
func (person Chinese) sayHello() {
	fmt.Println("你好")
}

// 接口的实现:定义一个结构体:
// 美国人:
type American struct {
}

// 实现接口的方法---》具体的实现:
func (person American) sayHello() {
	fmt.Println("hi")
}

// 定义一个函数:专门用来各国人打招呼的函数,接收具备SayHello接口的能力的变量:
func greet(s SayHello) {
	s.sayHello()
}
func main() {
	//创建一个中国人:
	c := Chinese{}
	//创建一个美国人:
	a := American{}
	//美国人打招呼:
	greet(a)
	//中国人打招呼:
	greet(c)
}

总结:

(1)接口中可以定义一组方法,但不需要实现,不需要方法体。并且接口中不能包含任何变量。到某个自定义类型要使用的时候(实现接口的时候),再根据具体情况把这些方法具体实现出来。

(2)实现接口要实现所有的方法才是实现。

(3)Golang中的接口不需要显式的实现接口。Golang中没有implement关键字。(Golang中实现接口是基于方法的,不是基于接口的)

例如:

A接口 a,b方法

B接口 a,b方法

C结构体 实现了 a,b方法 ,那么C实现了A接口,也可以说实现了B接口 (只要实现全部方法即可,和实际接口耦合性很低,比Java松散得多)

(4)接口目的是为了定义规范,具体由别人来实现即可。

接口的注意事项

【1】接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量。

【2】只要是自定义数据类型,就可以实现接口,不仅仅是结构体类型。

【3】一个自定义类型可以实现多个接口

go
package main

import "fmt"

type AInterface interface {
	a()
}
type BInterface interface {
	b()
}
type Stu struct {
}

func (s Stu) a() {
	fmt.Println("aaaa")
}
func (s Stu) b() {
	fmt.Println("bbbb")
}
func main() {
	var s Stu
	var a AInterface = s
	var b BInterface = s
	a.a()
	b.b()
}

【4】一个接口(比如A接口)可以继承多个别的接口(比如B,C接口),这时如果要实现A接口,也必须将B,C接口的方法也全部实现。

go
package main

import "fmt"

type CInterface interface {
	c()
}
type BInterface interface {
	b()
}
type AInterface interface {
	BInterface
	CInterface
	a()
}
type Stu struct {
}

func (s Stu) a() {
	fmt.Println("a")
}
func (s Stu) b() {
	fmt.Println("b")
}
func (s Stu) c() {
	fmt.Println("c")
}
func main() {
	var s Stu
	var a AInterface = s
	a.a()
	a.b()
	a.c()
}

【5】interface类型默认是一个指针(引用类型),如果没有对interface初始化就使用,那么会输出nil

【6】空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。

文件

简单案例

go
package main
import(
        "fmt"
        "os"
)
func main(){
        //打开文件:
        file,err := os.Open("d:/Test.txt");
        if err != nil {//出错
                fmt.Println("文件打开出错,对应错误为:",err)
        }
        //没有出错,输出文件:
        fmt.Printf("文件=%v",file)
        //.........一系列操作
        //关闭文件:
        err2 := file.Close();
        if err2 != nil {
                fmt.Println("关闭失败")
        }
}

一次性读取文件

go
package main
import(
        "fmt"
        "io/ioutil"
)
func main(){
        //备注:在下面的程序中不需要进行 Open\Close操作,因为文件的打开和关闭操作被封装在ReadFile函数内部了
        //读取文件:
        content,err := ioutil.ReadFile("d:/Test.txt")//返回内容为:[]byte,err
        if err != nil {//读取有误
                fmt.Println("读取出错,错误为:",err)
        }
        //如果读取成功,将内容显示在终端即可:
        //fmt.Printf("%v",content)
        fmt.Printf("%v",string(content))
}

读取文件(带缓冲区)

go
package main
import(
        "fmt"
        "os"
        "bufio"
        "io"
)
func main(){
        //打开文件:
        file,err := os.Open("d:/Test.txt")
        if err != nil {//打开失败
                fmt.Println("文件打开失败,err=",err)
        }
        //当函数退出时,让file关闭,防止内存泄露:
        defer file.Close()
        //创建一个流:
        reader := bufio.NewReader(file)
        //读取操作:
        for {
                str,err := reader.ReadString('\n')//读取到一个换行就结束
                if err == io.EOF {//io.EOF 表示已经读取到文件的结尾
                        break
                }
                //如果没有读取到文件结尾的话,就正常输出文件内容即可:
                fmt.Println(str)
        }
        //结束:
        fmt.Println("文件读取成功,并且全部读取完毕")
}

写入文件

go
package main
import(
        "fmt"
        "os"
        "bufio"
)
func main(){
        //写入文件操作:
        //打开文件:
        file , err := os.OpenFile("d:/Demo.txt",os.O_RDWR | os.O_APPEND | os.O_CREATE,0666)
        if err != nil {//文件打开失败
                fmt.Printf("打开文件失败",err)
                return
        }
        //及时将文件关闭:
        defer file.Close()
        //写入文件操作:---》IO流---》缓冲输出流(带缓冲区)
        writer := bufio.NewWriter(file)
        for i := 0; i < 10;i++ {
                writer.WriteString("你好 马士兵\n")
        } 
        //流带缓冲区,刷新数据--->真正写入文件中:
        writer.Flush()
        s :=os.FileMode(0666).String()
        fmt.Println(s)
}

文件复制操作

go
package main
import(
        "fmt"
        "io/ioutil"
)
func main(){
        //定义源文件:
        file1Path := "d:/Demo.txt"
        //定义目标文件:
        file2Path := "d:/Demo2.txt"
        //对文件进行读取:
        content,err := ioutil.ReadFile(file1Path)
        if err != nil {
                fmt.Println("读取有问题!")
                return
        }
        //写出文件:
        err = ioutil.WriteFile(file2Path,content,0666)
        if err != nil {
                fmt.Println("写出失败!")
        }
}

携程和管道

携程入门

请编写一个程序,完成如下功能: (1)在主线程中,开启一个goroutine,该goroutine每隔1秒输出"hello golang" (2)在主线程中也每隔一秒输出"hello msb",输出10次后,退出程序 (3)要求主线程和goroutine同时执行

go
package main
import(
        "fmt"
        "strconv"
        "time"
)
func test(){
        for i := 1;i <= 10;i++ {
                fmt.Println("hello golang + " + strconv.Itoa(i))
                //阻塞一秒:
                time.Sleep(time.Second)
        }
}
func main(){//主线程
        go test() //开启一个协程
        for i := 1;i <= 10;i++ {
                fmt.Println("hello msb + " + strconv.Itoa(i))
                //阻塞一秒:
                time.Sleep(time.Second)
        }
}

主死从随

  1. 如果主线程退出了,则协程即使还没有执行完毕,也会退出
  2. 当然协程也可以在主线程没有退出前,就自己结束了,比如完成了自己的任务
go
package main
import(
        "fmt"
        "strconv"
        "time"
)
func test(){
        for i := 1;i <= 1000;i++ {
                fmt.Println("hello golang + " + strconv.Itoa(i))
                //阻塞一秒:
                time.Sleep(time.Second)
        }
}
func main(){//主线程
        go test() //开启一个协程
        for i := 1;i <= 10;i++ {
                fmt.Println("hello msb + " + strconv.Itoa(i))
                //阻塞一秒:
                time.Sleep(time.Second)
        }
}

启动多个协程

go
package main
import(
        "fmt"
        "time"
)
func main(){
        //匿名函数+外部变量 = 闭包
        for i := 1;i <= 5;i++ {
                //启动一个协程
                //使用匿名函数,直接调用匿名函数
                go func(n int){
                        fmt.Println(n)
                }(i)
        }
        time.Sleep(time.Second * 2)
}

使用WaitGroup控制协程退出

【1】WaitGroup的作用: WaitGroup用于等待一组线程的结束。父线程调用Add方法来设定应等待的线程的数量。每个被等待的线程在结束时应调用Done方法。同时,主线程里可以调用Wait方法阻塞至所有线程结束。---》解决主线程在子协程结束后自动结束

go
package main
import(
        "fmt"
        "sync"
)
var wg sync.WaitGroup //只定义无需赋值
func main(){
        //启动五个协程
        for i := 1 ;i <= 5;i++ {
                wg.Add(1) //协程开始的时候加1操作
                go func(n int){
                        fmt.Println(n)
                        wg.Done()  //协程执行完成减1
                }(i)
        }
        //主线程一直在阻塞,什么时候wg减为0了,就停止
        wg.Wait()
}

如果防止忘记计数器减1操作,结合defer关键字使用:

go
package main
import(
        "fmt"
        "sync"
)
var wg sync.WaitGroup //只定义无需赋值
func main(){
        //启动五个协程
        for i := 1 ;i <= 5;i++ {
                wg.Add(1) //协程开始的时候加1操作
                go func(n int){
                        defer wg.Done()
                        fmt.Println(n)		
                }(i)
        }
        //主线程一直在阻塞,什么时候wg减为0了,就停止
        wg.Wait()
}

可以最开始在知道协程次数的情况下先Add操作:

go
package main
import(
        "fmt"
        "sync"
)
var wg sync.WaitGroup //只定义无需赋值
func main(){
        wg.Add(5)
        //启动五个协程
        for i := 1 ;i <= 5;i++ {
                go func(n int){
                        defer wg.Done()
                        fmt.Println(n)		
                }(i)
        }
        //主线程一直在阻塞,什么时候wg减为0了,就停止
        wg.Wait()
}

注意:Add中加入的数字和协程的次数一定要保持一致

多个协程操纵同一数据案例(互斥锁)

多个协程操纵同一数据

go
package main
import(
        "fmt"
        "sync"
)
//定义一个变量:
var totalNum int
var wg sync.WaitGroup //只定义无需赋值
func add(){
        defer wg.Done()
        for i := 0 ;i < 100000;i++{
                totalNum = totalNum + 1
        }
}
func sub(){
        defer wg.Done()
        for i := 0 ;i < 100000;i++{
                totalNum = totalNum - 1
        }
}
func main(){
        wg.Add(2)
        //启动协程
        go add()
        go sub()
        wg.Wait()
        fmt.Println(totalNum)
}

结果:在理论上,这个totalNum结果应该是0 ,无论协程怎么交替执行,最终想象的结果就是0 但是事实上:不是

解决问题: 有一个机制:确保:一个协程在执行逻辑的时候另外的协程不执行 ----》锁的机制---》加入互斥锁

go
package main
import(
        "fmt"
        "sync"
)
//定义一个变量:
var totalNum int
var wg sync.WaitGroup //只定义无需赋值
//加入互斥锁:
var lock sync.Mutex
func add(){
        defer wg.Done()
        for i := 0 ;i < 100000;i++{
                //加锁
                lock.Lock()
                totalNum = totalNum + 1
                //解锁:
                lock.Unlock()
        }
}
func sub(){
        defer wg.Done()
        for i := 0 ;i < 100000;i++{
                //加锁
                lock.Lock()
                totalNum = totalNum - 1
                //解锁:
                lock.Unlock()
        }
}
func main(){
        wg.Add(2)
        //启动协程
        go add()
        go sub()
        wg.Wait()
        fmt.Println(totalNum)
}

读写锁的使用

golang中sync包实现了两种锁Mutex (互斥锁)和RWMutex(读写锁) 【1】互斥锁 其中Mutex为互斥锁,Lock()加锁,Unlock()解锁,使用Lock()加锁后,便不能再次对其进行加锁,直到利用Unlock()解锁对其解锁后,才能再次加锁.适用于读写不确定场景,即读写次数没有明显的区别 ----性能、效率相对来说比较低

【2】读写锁 RWMutex是一个读写锁,其经常用于读次数远远多于写次数的场景. ---在读的时候,数据之间不产生影响, 写和读之间才会产生影响

go
package main
import(
        "fmt"
        "sync"
        "time"
)
var wg sync.WaitGroup //只定义无需赋值
//加入读写锁:
var lock sync.RWMutex
func read(){
        defer wg.Done()
        lock.RLock()//如果只是读数据,那么这个锁不产生影响,但是读写同时发生的时候,就会有影响
        fmt.Println("开始读取数据")
        time.Sleep(time.Second)
        fmt.Println("读取数据成功")
        lock.RUnlock()
}
func write(){
        defer wg.Done()
        lock.Lock()
        fmt.Println("开始修改数据")
        time.Sleep(time.Second * 10)
        fmt.Println("修改数据成功")
        lock.Unlock()
}
func main(){
        wg.Add(6)
        //启动协程 ---> 场合:读多写少
        for i := 0;i < 5;i++ {
                go read()
        }
        go write()
        wg.Wait()
}

管道介绍

【1】管道(channel)特质介绍: (1)管道本质就是一个数据结构-队列 (2)数据是先进先出 (3)自身线程安全,多协程访问时,不需要加锁,channel本身就是线程安全的 (4)管道有类型的,一个string的管道只能存放string类型数据

管道入门案例

【1】管道的定义: var 变量名 chan 数据类型 PS1:chan管道关键字 PS2:数据类型指的是管道的类型,里面放入数据的类型,管道是有类型的,int类型的管道只能写入整数int PS3:管道是引用类型,必须初始化才能写入数据,即make后才能使用

go
package main
import(
        "fmt"
)
func main(){
        //定义管道 、 声明管道 ---> 定义一个int类型的管道
        var intChan chan int
        //通过make初始化:管道可以存放3个int类型的数据
        intChan = make(chan int,3)
        //证明管道是引用类型:
        fmt.Printf("intChan的值:%v",intChan) // 0xc000112080
        //向管道存放数据:
        intChan<- 10
        num := 20
        intChan<- num
        intChan<- 40  
        //注意:不能存放大于容量的数据:
        //intChan<- 80  
        //在管道中读取数据:
        num1 := <-intChan
        num2 := <-intChan
        num3 := <-intChan
        fmt.Println(num1)
        fmt.Println(num2)
        fmt.Println(num3)
        //注意:在没有使用协程的情况下,如果管道的数据已经全部取出,那么再取就会报错:
        num4 := <-intChan
        fmt.Println(num4)
        //输出管道的长度:
        fmt.Printf("管道的实际长度:%v,管道的容量是:%v",len(intChan),cap(intChan))
}

管道的关闭

【1】管道的关闭: 使用内置函数close可以关闭管道,当管道关闭后,就不能再向管道写数据了,但是仍然可以从该管道读取数据。

go
package main
import(
    "fmt"
)
func main(){
        //定义管道 、 声明管道
        var intChan chan int
        //通过make初始化:管道可以存放3个int类型的数据
        intChan = make(chan int,3)
        //在管道中存放数据:
        intChan<- 10
        intChan<- 20
        //关闭管道:
        close(intChan)
        //再次写入数据:--->报错
        //intChan<- 30
        //当管道关闭后,读取数据是可以的:
        num := <- intChan
        fmt.Println(num)
}

管道的遍历

【1】管道的遍历: 管道支持for-range的方式进行遍历,请注意两个细节 1)在遍历时,如果管道没有关闭,则会出现deadlock的错误 2)在遍历时,如果管道已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。

go
package main
import(
        "fmt"
)
func main(){
        //定义管道 、 声明管道
        var intChan chan int
        //通过make初始化:管道可以存放3个int类型的数据
        intChan = make(chan int,100)
        for i := 0;i < 100;i++ {
                intChan<- i
        }
        //在遍历前,如果没有关闭管道,就会出现deadlock的错误
        //所以我们在遍历前要进行管道的关闭
        close(intChan)
        //遍历:for-range
        for v := range intChan {
                fmt.Println("value = ",v)
        }
}

协程和管道协同工作案例

【1】案例需求: 请完成协程和管道协同工作的案例,具体要求:

  1. 开启一个writeData协程,向管道中写入50个整数.
  2. 开启一个readData协程,从管道中读取writeData写入的数据。
  3. 注意: writeData和readDate操作的是同一个管道
  4. 主线程需要等待writeData和readDate协程都完成工作才能退出
go
package main
import(
        "fmt"
        "time"
        "sync"
)
var wg sync.WaitGroup //只定义无需赋值
//写:
func writeData(intChan chan int){
        defer wg.Done()
        for i := 1;i <= 50;i++{
                intChan<- i
                fmt.Println("写入的数据为:",i)
                time.Sleep(time.Second)
        }
        //管道关闭:
        close(intChan)
}
//读:
func readData(intChan chan int){
        defer wg.Done()
        //遍历:
        for v := range intChan{
                fmt.Println("读取的数据为:",v)
                time.Sleep(time.Second)
        }
}
func main(){//主线程
        //写协程和读协程共同操作同一个管道-》定义管道:
        intChan := make(chan int,50)
        wg.Add(2)
        //开启读和写的协程:
        go writeData(intChan)
        go readData(intChan)
        //主线程一直在阻塞,什么时候wg减为0了,就停止
        wg.Wait()	
}

声明只读只写管道

【1】管道可以声明为只读或者只写性质

go
package main
import(
        "fmt"
)
func main(){
        //默认情况下,管道是双向的--》可读可写:
        //var intChan1 chan int
        //声明为只写:
        var intChan2 chan<- int  // 管道具备<- 只写性质
        intChan2 = make(chan int,3)
        intChan2<- 20
        //num := <-intChan2 报错
        fmt.Println("intChan2:",intChan2)
        //声明为只读:
        var intChan3 <-chan int// 管道具备<- 只读性质 
        if intChan3 != nil {
                num1 := <-intChan3
                fmt.Println("num1:",num1)
        }
        //intChan3<- 30 报错       
}

管道的阻塞

【1】当管道只写入数据,没有读取,就会出现阻塞:

go
package main
import(
        "fmt"
        _"time"
        "sync"
)
var wg sync.WaitGroup //只定义无需赋值
//写:
func writeData(intChan chan int){
        defer wg.Done()
        for i := 1;i <= 10;i++{
                intChan<- i
                fmt.Println("写入的数据为:",i)
                //time.Sleep(time.Second)
        }
        //管道关闭:
        close(intChan)
}
//读:
func readData(intChan chan int){
        defer wg.Done()
        //遍历:
        for v := range intChan{
                fmt.Println("读取的数据为:",v)
                //time.Sleep(time.Second)
        }
}
func main(){//主线程
        //写协程和读协程共同操作同一个管道-》定义管道:
        intChan := make(chan int,10)
        wg.Add(2)
        //开启读和写的协程:
        go writeData(intChan)
        //go readData(intChan)
        //主线程一直在阻塞,什么时候wg减为0了,就停止
        wg.Wait()	
}

【2】写的快,读的满(管道读写频率不一致),不会出现阻塞问题:

go
package main
import(
        "fmt"
        "time"
        "sync"
)
var wg sync.WaitGroup //只定义无需赋值
//写:
func writeData(intChan chan int){
        defer wg.Done()
        for i := 1;i <= 10;i++{
                intChan<- i
                fmt.Println("写入的数据为:",i)
                //time.Sleep(time.Second)
        }
        //管道关闭:
        close(intChan)
}
//读:
func readData(intChan chan int){
        defer wg.Done()
        //遍历:
        for v := range intChan{
                fmt.Println("读取的数据为:",v)
                time.Sleep(time.Second)
        }
}
func main(){//主线程
        //写协程和读协程共同操作同一个管道-》定义管道:
        intChan := make(chan int,10)
        wg.Add(2)
        //开启读和写的协程:
        go writeData(intChan)
        go readData(intChan)
        //主线程一直在阻塞,什么时候wg减为0了,就停止
        wg.Wait()	
}

select功能

【1】select功能:解决多个管道的选择问题,也可以叫做多路复用,可以从多个管道中随机公平地选择一个来执行 PS:case后面必须进行的是io操作,不能是等值,随机去选择一个io操作 PS:default防止select被阻塞住,加入default

go
package main
import(
        "fmt"
        "time"
)
func main(){
        //定义一个int管道:
        intChan := make(chan int,1)
        go func(){
                time.Sleep(time.Second * 15)
                intChan<- 10
        }()
        //定义一个string管道:
        stringChan := make(chan string,1)
        go func(){
                time.Sleep(time.Second * 12)
                stringChan<- "msbgolang"
        }()
        //fmt.Println(<-intChan)//本身取数据就是阻塞的
        select{
                case v := <-intChan :
                        fmt.Println("intChan:",v)
                case v := <-stringChan :
                        fmt.Println("stringChan:",v)
                default:
                        fmt.Println("防止select被阻塞")
        }
}

defer+recover机制处理错误

【1】问题原因:多个协程工作,其中一个协程出现panic,导致程序崩溃 【2】解决办法:利用refer+recover捕获panic进行处理,即使协程出现问题,主线程仍然不受影响可以继续执行。

go
package main
import(
        "fmt"
        "time"
)
//输出数字:
func printNum(){
        for i := 1;i <= 10;i++{
                fmt.Println(i)
        }
}
//做除法操作:
func devide(){
        defer func(){
                err := recover()
                if err != nil{
                        fmt.Println("devide()出现错误:",err)
                }
        }()
        num1 := 10
        num2 := 0
        result := num1 / num2
        fmt.Println(result)
}
func main(){
        //启动两个协程:
        go printNum()
        go devide()
        time.Sleep(time.Second * 5)
}

网络编程

基于TCP协议的网络通信

创建客户端

go
package main
import(
        "fmt"
        "net" //所需的网络编程全部都在net包下
)
func main(){
        //打印:
        fmt.Println("客服端启动。。")
        //调用Dial函数:参数需要指定tcp协议,需要指定服务器端的IP+PORT
        conn,err := net.Dial("tcp","127.0.0.1:8888")
        if err != nil {//连接失败
                fmt.Println("客户端连接失败:err:",err)
                return
        }
        fmt.Println("连接成功,conn:",conn)
}

创建服务器端

进行监听:(Listen函数在net包下)

go
package main
import(
        "fmt"
        "net" //所需的网络编程全部都在net包下
)
func main(){
        //打印:
        fmt.Println("服务器端启动了。。")
        //进行监听:需要指定服务器端TCP协议,服务器端的IP+PORT
        listen,err := net.Listen("tcp","127.0.0.1:8888")
        if err != nil{//监听失败
                fmt.Println("监听失败,err:",err)
                return
        }
        //监听成功以后:
        //循环等待客户端的链接:
        for{
                conn,err2 := listen.Accept()
                if err2 != nil {//客户端的等待失败
                        fmt.Println("客户端的等待失败,err2:",err2)
                }else{
                        //连接成功:
                        fmt.Printf("等待链接成功,con=%v ,接收到的客户端信息:%v \n",conn,conn.RemoteAddr().String())
                }
        }
}

处理终端数据

【1】客户端发送数据:

go
package main
import(
        "fmt"
        "net" //所需的网络编程全部都在net包下
        "bufio"
        "os"
)
func main(){
        //打印:
        fmt.Println("客服端启动。。")
        //调用Dial函数:参数需要指定tcp协议,需要指定服务器端的IP+PORT
        conn,err := net.Dial("tcp","127.0.0.1:8888")
        if err != nil {//连接失败
                fmt.Println("客户端连接失败:err:",err)
                return
        }
        fmt.Println("连接成功,conn:",conn)
        //通过客户端发送单行数据,然后退出:
        reader := bufio.NewReader(os.Stdin)//os.Stdin代表终端标准输入
        //从终端读取一行用户输入的信息:
        str,err := reader.ReadString('\n')
        if err != nil {
                fmt.Println("终端输入失败,err:",err)
        }
        //将str数据发送给服务器:
        n,err := conn.Write([]byte(str))
        if err != nil{
                fmt.Println("连接失败,err:",err)
        }
        fmt.Printf("终端数据通过客户端发送成功,一共发送了%d字节的数据,并退出",n)
}

【2】服务器端接收数据:

go
package main
import(
        "fmt"
        "net" //所需的网络编程全部都在net包下
)
func process(conn net.Conn){
        //连接用完一定要关闭:
        defer conn.Close()
        for{
                //创建一个切片,准备:将读取的数据放入切片:
                buf := make([]byte,1024)
                //从conn连接中读取数据:
                n,err := conn.Read(buf)
                if err != nil{
                        return
                }
                //将读取内容在服务器端输出:
                fmt.Println(string(buf[0:n]))
        }
}
func main(){
        //打印:
        fmt.Println("服务器端启动了。。")
        //进行监听:需要指定服务器端TCP协议,服务器端的IP+PORT
        listen,err := net.Listen("tcp","127.0.0.1:8888")
        if err != nil{//监听失败
                fmt.Println("监听失败,err:",err)
                return
        }
        //监听成功以后:
        //循环等待客户端的链接:
        for{
                conn,err2 := listen.Accept()
                if err2 != nil {//客户端的等待失败
                        fmt.Println("客户端的等待失败,err2:",err2)
                }else{
                        //连接成功:
                        fmt.Printf("等待链接成功,con=%v ,接收到的客户端信息:%v \n",conn,conn.RemoteAddr().String())
                }
                //准备一个协程,协程处理客户端服务请求:
                go process(conn)//不同的客户端的请求,连接conn不一样的
        }
}

反射

反射的引入

【1】反射可以做什么?

  1. 反射可以在运行时动态获取变量的各种信息,比如变量的类型,类别等信息
  2. 如果是结构体变量,还可以获取到结构体本身的信息(包括结构体的字段、方法)
  3. 通过反射,可以修改变量的值,可以调用关联的方法。
  4. 使用反射,需要import ("reflect")

【2】反射相关的函数

  1. reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型
  2. reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型(reflect.Value是一个结构体类型),通过reflect.Value,可以获取到关于该变量的很多信息。

对基本数据类型反射

【1】反射相关的函数

  1. reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型
  2. reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型(reflect.Value是一个结构体类型),通过reflect.Value,可以获取到关于该变量的很多信息。
go
package main
import(
        "fmt"
        "reflect"
)
//利用一个函数,函数的参数定义为空接口:
func testReflect(i interface{}){//空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。
        //1.调用TypeOf函数,返回reflect.Type类型数据:
        reType := reflect.TypeOf(i)
        fmt.Println("reType:",reType)
        fmt.Printf("reType的具体类型是:%T",reType)
        //2.调用ValueOf函数,返回reflect.Value类型数据:
        reValue :=reflect.ValueOf(i)
        fmt.Println("reValue:",reValue)
        fmt.Printf("reValue的具体类型是:%T",reValue)
        //num1 := 100
        //如果真想获取reValue的数值,要调用Int()方法:返回v持有的有符号整数
        num2 := 80 + reValue.Int()
        fmt.Println(num2)
        //reValue转成空接口:
        i2 := reValue.Interface()
        //类型断言:
        n := i2.(int)
        n2 := n + 30
        fmt.Println(n2)
}
func main(){
        //对基本数据类型进行反射:
        //定义一个基本数据类型:
        var num int = 100
        testReflect(num)
}

对结构体类型反射

【1】反射相关的函数

  1. reflect.TypeOf(变量名),获取变量的类型,返回reflect.Type类型
  2. reflect.ValueOf(变量名),获取变量的值,返回reflect.Value类型(reflect.Value是一个结构体类型),通过reflect.Value,可以获取到关于该变量的很多信息。
go
package main
import(
        "fmt"
        "reflect"
)
//利用一个函数,函数的参数定义为空接口:
func testReflect(i interface{}){//空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。
        //1.调用TypeOf函数,返回reflect.Type类型数据:
        reType := reflect.TypeOf(i)
        fmt.Println("reType:",reType)
        fmt.Printf("reType的具体类型是:%T",reType)
        //2.调用ValueOf函数,返回reflect.Value类型数据:
        reValue :=reflect.ValueOf(i)
        fmt.Println("reValue:",reValue)
        fmt.Printf("reValue的具体类型是:%T",reValue)
        //reValue转成空接口:
        i2 := reValue.Interface()
        //类型断言:
        n,flag := i2.(Student)
        if flag == true {//断言成功
                fmt.Printf("学生的名字是:%v,学生的年龄是:%v",n.Name,n.Age)
        }
        
}
//定义学生结构体:
type Student struct{
        Name string
        Age int
}
func main(){
        //对结构体类型进行反射:
        //定义结构体具体的实例:
        stu := Student{
                Name : "丽丽",
                Age : 18,	
        }
        testReflect(stu)
}

获取变量的类别

【1】获取变量的类别:两种方式:

(1)reflect.Type.Kind() (2)reflect.Value.Kind()

【2】Kind的值是常量值

go
package main
import(
        "fmt"
        "reflect"
)
//利用一个函数,函数的参数定义为空接口:
func testReflect(i interface{}){//空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。
        //1.调用TypeOf函数,返回reflect.Type类型数据:
        reType := reflect.TypeOf(i)
        //2.调用ValueOf函数,返回reflect.Value类型数据:
        reValue :=reflect.ValueOf(i)
        //获取变量的类别:
        //(1)reType.Kind()
        k1 := reType.Kind()
        fmt.Println(k1)
        //(2)reValue.Kind()
        k2 := reValue.Kind()
        fmt.Println(k2)
        //获取变量的类型:
        //reValue转成空接口:
        i2 := reValue.Interface()
        //类型断言:
        n,flag := i2.(Student)
        if flag == true {//断言成功
                fmt.Printf("结构体的类型是:%T",n)
        }
}
//定义学生结构体:
type Student struct{
        Name string
        Age int
}
func main(){
        //对结构体类型进行反射:
        //定义结构体具体的实例:
        stu := Student{
                Name : "丽丽",
                Age : 18,	
        }
        testReflect(stu)
}

【3】Type和 Kind 的区别 Type是类型, Kind是类别,Type和Kind 可能是相同的,也可能是不同的. 比如:var num int = 10 num的Type是int , Kind也是int 比如:var stu Studentstu的 Type是 pkg1.Student , Kind是struct

通过反射修改变量

【1】修改基本数据类型的值:

go
package main
import(
        "fmt"
        "reflect"
)
//利用一个函数,函数的参数定义为空接口:
func testReflect(i interface{}){//空接口没有任何方法,所以可以理解为所有类型都实现了空接口,也可以理解为我们可以把任何一个变量赋给空接口。
        reValue :=reflect.ValueOf(i)
        //通过SetInt()来改变值:
        reValue.Elem().SetInt(40)
        
}
func main(){
        //对基本数据类型进行反射:
        //定义一个基本数据类型:
        var num int = 100
        testReflect(&num) //传入指针地址
        fmt.Println(num)
}

通过反射操作结构体的属性和方法

go
package main
import(
        "fmt"
        "reflect"
)
//定义一个结构体:
type Student struct{
        Name string
        Age int
}
//给结构体绑定方法:
func (s Student) CPrint(){
        fmt.Println("调用了Print()方法")
        fmt.Println("学生的名字是:",s.Name)
}
func (s Student) AGetSum(n1,n2 int) int{
        fmt.Println("调用了AGetSum方法")
        return n1 + n2
}
func (s Student) BSet(name string,age int){
        s.Name = name
        s.Age = age
}
//定义函数操作结构体进行反射操作:
func TestStudentStruct(a interface{}){
        //a转成reflect.Value类型:
        val := reflect.ValueOf(a)
        fmt.Println(val)
        //通过reflect.Value类型操作结构体内部的字段:
        n1 := val.NumField()
        fmt.Println(n1)
        //遍历-获取具体的字段:
        for i := 0; i < n1;i++{
                fmt.Printf("第%d个字段的值是:%v",i,val.Field(i))
        }
        fmt.Println()
        //通过reflect.Value类型操作结构体内部的方法:
        n2 := val.NumMethod()
        fmt.Println(n2)
        //调用CPrint()方法:
        //调用方法,方法的首字母必须大写才能有对应的反射的访问权限
        //方法的顺序按照ASCII的顺序排列的,a,b,c,,,,,,索引:0,1,2,,,,,
        val.Method(2).Call(nil)
        //调用AGetSum方法:
        //定义Value的切片:
        var params []reflect.Value
        params = append(params,reflect.ValueOf(10))
        params = append(params,reflect.ValueOf(20))
        result := val.Method(0).Call(params)
        fmt.Println("AGetSum方法的返回值为:",result[0].Int())
}
func main(){
        //定义结构体具体的实例:
        s := Student{
                Name : "丽丽",
                Age : 18,
        }
        //调用TestStudentStruct:
        TestStudentStruct(s)
}

通过反射修改变量

go
package main
import(
        "fmt"
        "reflect"
)
//定义一个结构体:
type Student struct{
        Name string
        Age int
}
//给结构体绑定方法:
func (s Student) CPrint(){
        fmt.Println("调用了Print()方法")
        fmt.Println("学生的名字是:",s.Name)
}
func (s Student) AGetSum(n1,n2 int) int{
        fmt.Println("调用了AGetSum方法")
        return n1 + n2
}
func (s Student) BSet(name string,age int){
        s.Name = name
        s.Age = age
}
//定义函数操作结构体进行反射操作:
func TestStudentStruct(a interface{}){
        //a转成reflect.Value类型:
        val := reflect.ValueOf(a)
        fmt.Println(val)
        n := val.Elem().NumField()
        fmt.Println(n)
        //修改字段的值:
        val.Elem().Field(0).SetString("张三")
}
func main(){
        //定义结构体具体的实例:
        s := Student{
                Name : "丽丽",
                Age : 18,
        }
        //调用TestStudentStruct:
        TestStudentStruct(&s)
        fmt.Println(s)
}

依赖包下载

项目所有依赖包下载

go
// 下面这个命令专门用于更新并下载项目中所有依赖包的最新版本。它的作用是递归地从当前目录开始,下载或更新所有依赖包的最新版本(包括主模块和子模块)。
go get -u ./...

// go mod tidy:该命令会整理你的go.mod文件,移除不必要的依赖,并下载项目中需要的所有依赖包。
go mod tidy

依次运行上面两个命令即可下载当前项目所有所需依赖的最新版本

根据go.mod文件定义的依赖包下载

go
go mod download // 该命令会下载所有go.mod文件中定义的依赖包。

构建或者运行项目时下载

go
// 当你执行构建或运行命令时,Go也会自动下载并安装所有需要的依赖包。
go build 或 go run 
// 或者
go run main.go