前言:该文档阅读需要有一定的编程语言基础,这里将默认读者已经学习过一门或多门编程语言 
补充:该文档更倾向于个人笔记,如果想学习,可以读 Go 语言圣经:Go 语言圣经中文版  
#  Go 语言介绍 
Go 语言的三个作者:Rob Pike (罗伯。派克),Ken Thompson (肯。汤普森) 和 Robert Griesemer (罗伯特。格利茨默)
Rob Pike:曾是贝尔实验室 (Bell Labs) 的 Unix 团队,和 Plan 9 操作系统计划的成员。他与 Thompson 共事多年,并共创出广泛使用的 UTF-8 字元编码。
Ken Thompson:主要是 B 语言、C 语言的作者、Unix 之父。 1983 年图奖 (Turing Award) 和 1998 年美国国家技术奖 (National Medal ofTechnology) 得主。他与 Dennis Ritchie 是 Unix 的原创者。Thompson 也发明了 后来衍生出 C 语言的 B 程序语言。
Robert Griesemer:在开发 Go 之前是 Google V8、Chubby 和 HotSpot JVM 的主要贡献者。
Go 语言出现的目的在于平衡开发速度与运行速度,相较于 C/C++,Go 语言能够实现更快速的开发,而相较于 Java,Go 语言能实现更快速的运行。当然,这只是通常而言,并不绝对。
#  Go 语言环境安装 
打开 Go 语言的官网下载  
#  Windows(win11)  
找到 windows 系统对应的包进行下载,例如这里选择种类 (Kind) 为压缩包 (Archive),系统 (OS) 为 Windows,64 位 (x86-64) 进行下载 
下载好后将压缩包进行解压 
文件资源管理器 ==> 右键此电脑 ==> 属性 ==> 高级系统设置 ==> 环境变量 
新建变量 GO_HOME,变量值设置为刚刚解压的 go 文件 
编辑 Path 变量,在 Path 变量中新建:% GO_HOME%\bin 
按 win+r 键,输入 cmd,打开命令窗口,使用 go version 检查环境是否配置完成,如果出现 go 语言的版本,则表明配置成功 
 
#  Linux(Unbuntu)  
使用 uname -a (其他 Linux 系统指令自行查找) 指令查看系统指令集,我这里是 x86_64
 
找到 Linux 系统对应的包进行下载,例如这里选择种类 (Kind) 为压缩包 (Archive),系统 (OS) 为 Linux,指令集 x86_64
 
打开到下载路径,然后使用指令
1 sudo tar -zxvf go压缩包名(自行替换) -C /usr/local 
 
进行解压,解压目标路径为 /usr/local
 
使用 vim 修改 /etc/profile 文件:
 
在文件最后添加内容:
1 2 export GOROOT=/usr/local/go export PATH=$PATH:$GOROOT/bin 
 
保存退出。
 
调用指令:
 
 
使用 go version 命令检查是否配置成功,如果出现 go 语言的版本,则标明配置成功。
 
 
#  编译运行 
使用 go build FileName.go 来编译 go 文件,编译后会出现相应的可执行文件,进行执行即可,例如编写文件 Hello.go
1 2 3 4 5 6 7 package  mainimport  "fmt" func  main ()  {    fmt.Println("Hello World!" ) } 
 
然后对其进行编译运行:
1 2 3 4 # 编译 go build Hello.go # 运行,Windows系统下会生成Hello.exe,直接双击执行即可 ./Hello 
 
也可以使用 go run 指令直接运行:
 
但对于代码较多的文件并不推荐使用 go run,最好还是先编译再执行。
#  Go 基础语法 
#  注意点 
注释的方式和 C 语言一致。 
运算符、流程控制等不再赘述,与其他语言基本保持一致。 
需要注意的是使用 switch 时,不再需要使用 break 来避免穿透现象,Go 语言的 switch 没有穿透现象。 
Go 语言中没有 while 和 do while 循环,仅有 for 循环。 
Go 语言的变量声明出来就必须调用,不调用会报错!  
Go 语言结尾不需要添加分号来标志语句结束。 
Go 语言流程控制语句,大括号必须跟在关键词后面,不能另起一行,例如:只有 if condition {正确  
Go 语言的 if 语句,for 循环,switch 语句不需要使用小括号,如 switch 直接写 switch key {}  
 
#  变量声明与赋值 
声明一个变量 s1,并指定其类型为 string 类型
 
直接赋值,让编译器自行推导其类型
 
短变量声明,这种声明方式仅能在函数的内部使用
 
匿名变量,用于接受一些不需要使用的值,例如接受数组索引
 
#  for range  
for 循环的另一种使用方式,和 python 类似,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package  mainimport  (     	"fmt"  ) func  main ()   {     	arr := [...]int {1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 }      	for  _, i := range  arr {          		fmt.Println(i) 	} } 
 
#  数组与切片  
声明一个长度为 5 的 int 数组
 
声明并初始化一个 int 数组,其中… 表示让编译器自行判断数组长度
1 var  arr2 = [...]int {1 ,2 ,3 ,4 ,5 ,6 }
 
短变量声明法声明一个数组
1 arr3 := [...]int {1 ,2 ,3 ,4 ,5 ,6 } 
 
多维数组不再赘述 
需要注意的是,Go 语言中的数组是值类型而非引用类型,换言之,传递时进行的是值传递而非指针传递
 
声明一个 int 切片,切片类似于 C 语言的数组,属于引用类型,传递时是指针传递而非值传递
 
使用 make 函数初始化一个切片,其变量类型 int 可变,len 为切片长度,cap 为切片容量,cap 可省略
1 var  slice2 []int  = make ([]int , len , cap )
 
短变量声明法,声明一个切片
1 slice3 := make ([]int , len ) 
 
从数组中直接切出一个切片,区间为 [startIndex, endIndex),左闭右开。
1 2 3 s := arr[startIndex:endIndex] 
 
#  函数  
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package  mainimport  "fmt" func  sayHello ()   {	fmt.Println("Hello World!" ) } func  cout (str string )   (ret bool ) {	fmt.Println(str) 	ret = true  	return  } func  intSum (num ...int )   int  {     	ret := 0  	for  _, arg := range  num { 		ret = ret + arg 	} 	return  ret } func  calc (a, b int )  (sum, sub int ){         sum = a + b     sub = a - b     return  } func  main ()   {	sayHello() 	ok := cout("Hello World!" ) 	fmt.Println(ok) 	 	fmt.Println(intSum(1 , 2 )) 	fmt.Println(intSum(1 , 2 , 3 , 4 , 5 ))      	_, sub := calc(20 , 10 ) 	fmt.Println(sub) } 
 
Go 语言的函数中并不存在默认参数。
#  函数指针与回调函数 
与 C 语言的函数指针和回调函数类似,写个案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package  mainimport  "fmt" func  add (x, y int )   int {    return  x + y } func  calc (x, y int , op func (int , int )   int ) int {         return  op(x, y) } func  main ()  {    v := add     v(1 , 2 )     fmt.Println(calc(100 , 200 , add)) } 
 
#  defer: 延迟执行 
defer 语句会在函数将要结束时才执行,具有延迟调用的特性 (类似于析构)。
其采用栈结构,例如:
1 2 3 4 5 6 7 8 9 package  mainimport  "fmt" func  main ()  {	fmt.Println("Start..." ) 	defer  fmt.Println(1 ) 	defer  fmt.Println(2 ) 	defer  fmt.Println(3 ) 	fmt.Println("End..." ) } 
 
最后输出结果为:
 
defer 通常用于处理资源释放问题,例如资源清理,文件关闭,解锁及记录时间等。
#  匿名函数与闭包  
匿名函数,懂的都懂。与函数唯一的区别在于不写名字,例如:
1 2 3 4 5 6 7 8 9 package  mainimport  "fmt" func  main ()   {	func ()   { 		fmt.Println("Hello World!" ) 	}()  } 
 
闭包 = 函数 + 外层变量调用,举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package  mainimport  "fmt" func  a (name string )   func ()   {     	return  func ()   { 		fmt.Println("Hello " , name) 	} } func  main ()   {	r := a("张三" ) 	r() } 
 
可以看到,在这个示例中,函数 a 中的匿名函数调用到了它外层的函数 a 的变量,这样的使用方式叫做闭包。
再来看看示例 2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package  mainimport  (	"fmt"  	"strings"  ) func  CheckSuffix (suffix string )   func (file string )   string  {          	return  func (file string )   string  {          		if  !strings.HasSuffix(file, suffix) {              			return  file + suffix 		}          		return  file 	} } func  main ()   {     	r := CheckSuffix(".txt" ) 	ret := r("张三" ) 	fmt.Println(ret) } 
 
同样的,对于函数 CheckSuffix 中的匿名函数,其也调用了外层的变量,那么这就是一个闭包的使用。
需要注意,内部的匿名函数是可以修改到外层变量的,如果我要这样使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package  mainimport  (	"fmt"  	"strings"  ) func  CheckSuffix (suffix string )   func (file, suf string )   string  {	 	 	return  func (file, suf string )   string  { 		 		if  strings.HasPrefix(suf, "." ) { 			suffix = suf 		} 		 		if  !strings.HasSuffix(file, suffix) { 			 			return  file + suffix 		} 		 		return  file 	} } func  main ()   {	 	r := CheckSuffix(".txt" ) 	ret := r("张三" , ".docx" ) 	ret2 := r("李四" , "" ) 	fmt.Println(ret, ret2) } 
 
可以发现,输出结果为
张三.docx   李四.docx
也就是说,对于 suffix 的修改是持续生效的,并非只作用于匿名函数内部
如果用 lambda 表达式来说明,那么它应该相当于 [&](我个人推测)
#  panic 与 recover 
Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种异常,因为 Go 语言的设计者们认为,将异常与控制结构混在一起会很容易使得代码变得混乱。因为开发者很容易滥用异常,甚至一个小小的错误都抛出一个异常。在 Go 语言中,使用多值返回来返回错误。不要用异常代替错误,更不要用来控制流程。
panic 与 recover 是 Go 的两个内置函数,这两个内置函数用于处理 Go 运行时的错误,panic 用于主动抛出错误,recover 用来捕获 panic 抛出的错误。
其中 recover 通常搭配 defer 使用,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package  mainimport  "fmt" func  a ()   {	fmt.Println("Hello in a" ) } func  b ()   {     	defer  func ()   { 		err := recover () 		if  err != nil  { 			fmt.Println(err) 		} 	}() 	panic ("panic in b" ) } func  c ()   {	fmt.Println("Hello in c" ) } func  main ()   {     	a() 	b() 	c() } 
 
#  指针与 C 语言基本无异,不再赘述 
#  type 
作用差不多相当于 typedef,使用如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package  mainimport  "fmt" type  MyInt int type  Int = int func  main ()   {	var  a Int 	a = 10  	var  b MyInt 	b = 20  	fmt.Println(a, b) } 
 
#  结构体  
#  定义结构体 
使用方法和 C 语言类似,使用 type 和 struct 关键字来定义一个结构体,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package  mainimport  "fmt" type  people struct  {	name string  	age  int8  	city string  	 } func  main ()   {	 	var  p people 	p.name = "张三"  	p.age = 18  	p.city = "北京"  	fmt.Printf("p=%v\n" , p) 	 	var  user struct  { 		name    string  		married bool  	} 	user.name = "李四"  	user.married = false  	 	var  p2 = new (people) 	 	(*p2).name = "王五"  	 	p2.age = 18  	p2.city = "上海"  	fmt.Printf("%v\n" , p2) 	 	p3 := &people{} 	fmt.Printf("%v\n" , p3) } 
 
#  结构体初始化 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package  mainimport  "fmt" type  people struct  {	name, city string  	age        int8  } func  main ()   {	 	p := people{ 		name: "张三" , 		age:  18 ,  	} 	fmt.Printf("%#v\n" , p) 	 	p2 := &people{  		"李四" , 		"北京" , 		18 , 	} 	fmt.Printf("%#v\n" , p2) } 
 
#  构造函数 
Go 语言的结构体没有构造函数,但是可以自己实现。例如,实现 people 的构造函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package  mainimport  "fmt" type  people struct  {	name, city string  	age        int8  } func  newPeople (name, city string , age int8 )   *people {	 	return  &people{ 		name: name, 		city: city, 		age:  age, 	} } func  main ()   {	p1 := newPeople("张三哥" , "北京" , int8 (18 )) 	fmt.Printf("type:%T value:%#v\n" , p1, p1) } 
 
#  匿名结构体与结构体嵌套 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package  mainimport  "fmt" type  Person struct  {	 	string  	int8  } func  main ()   {	p1 := Person{ 		"小王子" , 		18 , 	} 	fmt.Println(p1) 	 	fmt.Println(p1.string , p1.int8 ) } 
 
使用匿名结构体,对结构体进行嵌套
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package  mainimport  "fmt" type  Address struct  {	province, city string  } type  Person struct  {	name, gender string  	age          int8  	 	 	 	Address } func  main ()   {	p1 := Person{ 		name:   "张三" , 		gender: "男" , 		age:    18 , 		Address: Address{ 			province: "河北" , 			city:     "石家庄" , 		}, 	} 	fmt.Println(p1) 	 	 	fmt.Println(p1.name, p1.province) } 
 
#  方法和接收者 
Go 语言中方法 (Method) 是一种作用域特定类型变量的函数
这种特定类型变量叫做接收者 (Receiver)
接收者的概念就类似于其他语言中的 this 或者 self
方法的定义格式如下:
1 2 3 func  (接收者变量 接收者类型)   方法名(参数列表)(返回此参数){	函数体 } 
 
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package  mainimport  "fmt" type  People struct  {	name string  	age  int8  } func  NewPeople (name string , age int8 )   *People {	return  &People{ 		name: name, 		age:  age, 	} } func  (p People)   Dream() {	fmt.Printf("%s的梦想是学好Go语言!\n" , p.name) } func  (p *People)   SetAge(newAge int8 ) {	p.age = newAge } func  main ()   {	p1 := NewPeople("张三" , int8 (18 )) 	 	 	p1.Dream() 	fmt.Println(p1.age) 	p1.SetAge(int8 (24 )) 	fmt.Println(p1.age) } 
 
Go 语言中,接收者类型可以是任何类型,不仅仅是结构体,任何类型都可以拥有方法。
但是只能给自己的包中的类型定义方法。 
可以通过 type 关键字,基于 int 定义一个新的 MyInt 类型,然后为其定义方法。
#  结构体继承 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package  mainimport  "fmt" type  Animal struct  {	name string  } func  (a *Animal)   move() {	fmt.Printf("%s会动\n" , a.name) } type  Dog struct  {	Feet int8  	 	*Animal } func  (d *Dog)   bark() {	fmt.Printf("%s会汪汪汪\n" , d.name) } func  main ()   {	d1 := &Dog{ 		Feet: 4 , 		Animal: &Animal{ 			name: "旺财" , 		}, 	} 	d1.bark() 	 	d1.move() } 
 
#  结构体字段的可见性 
Go 语言的结构体字段,如果开头字母是大写的,那么就是公开的,可以被外部访问的 (类似于 public)
如果开头字母是小写的,那么就是私有的,只能被定义该结构体的包中使用 (类似于 protected)
#  结构体与 JSON 序列化 
JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。
JSON 键值对是用来保存 JS 对象的一种方式:
键 / 值对组合中的键名写在前面,并用双引号包裹 
使用冒号分隔 
然后紧接着值 
多个键值之间用逗号分隔 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package  mainimport  (	"encoding/json"  	"fmt"  ) type  student struct  {	Id   int  	Name string  } func  newStudent (id int , name string )   student {	return  student{ 		Id:   id, 		Name: name, 	} } type  class struct  {	Title   string  	Student []student } func  main ()   {	 	c1 := class{ 		Title:   "火箭班" , 		Student: make ([]student, 0 , 20 ), 	} 	 	for  i := 0 ; i < 10 ; i++ { 		tmpStu := newStudent(i, fmt.Sprintf("stu%02d" , i)) 		c1.Student = append (c1.Student, tmpStu) 	} 	 	 	 	 	 	data, err := json.Marshal(c1) 	 	if  err != nil  { 		fmt.Println("json marshal failed, err:" , err) 		return  	} 	 	fmt.Printf("%T\n" , data) 	 	 	jsonStr := `{"Title":"火箭班","Student":[{"Id":0,"Name":"stu00"},{"Id":1,"Name":"stu01"},{"Id":2,"Name":"stu02"}]}`  	 	var  c2 class 	 	 	 	 	err = json.Unmarshal([]byte (jsonStr), &c2) 	if  err != nil  { 		 		fmt.Println("json unmarshal failed, err:" , err) 		return  	} 	fmt.Printf("%#v\n" , c2) } 
 
需要注意的是,如果结构体中的字段首字母变为小写,那么其将对外不可见。
则使用 json 包中的方法时,json 的方法并不能够调用其字段,会导致错误。
#  结构体标签 (Tag) 
Tag 用于解决其他语言字段首字母小写与 Go 语言字段首字母大写不兼容的问题
Tag 是结构体的元信息,可以在运行的时候通过反射的机制读取出来。
Tag 在结构体字段的后方定义,由一对反引号 (ESC 下面那个键) 包裹起来,具体格式如下:
1 2 `key1:"value1" key2:"value2"` 
 
注意:为结构体编写 Tag 时,必须严格遵守键值对的规则。结构体标签的解析代码容错能力很差,一旦格式写错,编译和运行时都不会提示任何错误,通过反射也无法正常取值。
例如:不要在 key 和 value 之间添加空格
具体使用如下:
1 2 3 4 5 type  class struct  {	Title   string  `json:"title"`  	Student []student } 
 
 
#  小练习: 
实现学员信息管理系统,包含以下功能:
添加学员信息 
编辑学员信息 
展示所有学员信息 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package  mainimport  (	"fmt"  	"os"  ) func  showMenu ()   {	fmt.Println("欢迎来到学员信息管理系统" ) 	fmt.Println("1.添加学员" ) 	fmt.Println("2.编辑学员信息" ) 	fmt.Println("3.展示所有学员信息" ) 	fmt.Println("4.退出系统" ) } func  getNewStu ()   *student {	var  id, name, class string  	fmt.Println("请按要求输入学员信息" ) 	fmt.Print("请输入学员的学号:" ) 	fmt.Scanln(&id) 	fmt.Print("请输入学员的姓名:" ) 	fmt.Scanln(&name) 	fmt.Print("请输入学员的班级:" ) 	fmt.Scanln(&class) 	return  newStudent(id, name, class) } func  main ()   {	sms := newStuManSys() 	for  { 		 		showMenu() 		fmt.Println("请输入你要操作的序号" ) 		 		var  choice int  		fmt.Scanf("%d\n" , &choice) 		 		switch  choice { 		case  1 : 			sms.addStu(getNewStu()) 		case  2 : 			sms.editStu(getNewStu()) 		case  3 : 			sms.showStu() 		case  4 : 			 			os.Exit(0 ) 		} 	} } 
 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package  mainimport  "fmt" type  student struct  {	id, class, name string  } func  newStudent (id, name, class string )   *student {	return  &student{ 		id:    id, 		name:  name, 		class: class, 	} } type  stuManSys struct  {	allStudents []*student } func  newStuManSys ()   *stuManSys {	return  &stuManSys{ 		allStudents: make ([]*student, 0 , 100 ), 	} } func  (s *stuManSys)   addStu(newStu *student) {	s.allStudents = append (s.allStudents, newStu) } func  (s *stuManSys)   editStu(newStu *student) {	for  i, v := range  s.allStudents { 		if  newStu.id == v.id { 			s.allStudents[i] = newStu 			return  		} 	} 	fmt.Println("输入学号有误" ) } func  (s *stuManSys)   showStu() {	for  _, i := range  s.allStudents { 		fmt.Printf("学号:%s\t姓名:%s\t班级:%s\n" , i.id, i.name, i.class) 	} } 
 
 
#  Go 语言 - 包 (package)  
#  介绍 
包 (package) 是多个 Go 源码的集合,是一种高级的代码复用方案,Go 语言提供了很多内置包,如 fmt、os、io 等
#  定义包 
可以根据自己的需要创建自己的包。
一个包可以简单的理解为一个存放.go 文件的文件夹。
该文件夹下面的所有 go 文件都要在代码的第一行添加如下代码,声明该文件归属的包。
 
注意事项:
一个文件夹下面只能有一个包,同样一个包的文件不能在多个文件夹下 
包名可以和文件夹名不同,包名不能包含 - 符号 
包名为 main 的包为应用程序的入口包,编译不包含 main 包的源代码时不会得到可执行文件 
 
#  可见性 
如果想在一个包中引用另外一个包里的标识符 (如变量、常量、类型、函数等) 时,该标识符必须是对外可见的 (public)
在 Go 语言中只需要将标识符的首字母大写,就可以让标识符对外可见了
示例: 
文件路径:/GoLearn/package_demo/calc/add.go
1 2 3 4 5 6 package  calcfunc  Add (x, y int )   int  {	return  x + y } 
 
文件路径:/GoLearn/package_demo/main/main.go
1 2 3 4 5 6 7 8 9 10 package  mainimport  (	"GoLearn/package_demo/calc"  	"fmt"  ) func  main ()   {	fmt.Println(calc.Add(10 , 20 )) } 
 
也可以给导入的包起别名:
1 2 3 4 5 6 7 8 9 10 11 package  mainimport  (	Another_name "GoLearn/package_demo/calc"  	"fmt"  ) func  main ()   {	fmt.Println(Another_name.Add(10 , 20 )) } 
 
#  init 函数与匿名包 
init 函数是一种特殊的函数,它没有参数也没有返回值
在包被导入的时候会自动调用
示例:
1 2 3 func  init ()   {	fmt.Println("Hello!" ) } 
 
当仅需要执行包的 init 函数而不需要其内部的数据时,可以使用匿名包的形式,格式如下:
 
导入包与 init 函数的调用符合栈结构
即先导入者后 init,而 main 包是最先被导入的,所以它的 init 函数会被最后调用
#  Go 语言 - 接口 (interface)  
Go 语言中的接口是一种抽象的类型
一个类型可以实现多个接口,一个接口也可以对应多种类型,二者之间是多对多的关系
接口实现示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package  mainimport  "fmt" type  usbFlashDisk struct {}func  (u usbFlashDisk)   usbLink() {	fmt.Println("U盘已连接" ) } type  phone struct {}func  (p phone)   usbLink() {	fmt.Println("手机已连接" ) } type  usb interface  {	usbLink() } func  link (arg usb)   {	arg.usbLink() } func  main ()   {	u1 := usbFlashDisk{} 	link(u1) 	p1 := phone{} 	link(p1) } 
 
当一个接口不要求实现任何方法时,该接口是一个空接口
任意结构都满足空接口
#  值接收者与指针接收者关于接口的差异 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package  mainimport  "fmt" type  mover interface  {	move() } type  person struct  {	name string  	age  int8  } func  (p *person)   move() {	s := "在跑"  	fmt.Println(p.name, s) } func  main ()   {	var  m mover 	p1 := &person{ 		name: "张三" , 		age:  18 , 	} 	m = p1 	m.move() 	fmt.Println(m) } 
 
#  接口内部存储 
接口内部存储分为两部分:
一部分保存其动态类型,用于记录存储变量的类型。 
另一部分保存其动态值,用于记录存储变量的值。 
 
类型断言:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package  maintype  xxx interface {}func  main ()  {    var  x xxx          x = false                          ret, ok := x.(string )     if  !ok {         fmt.Println("不是string类型" )     }else  {         fmt.Println("是字符串类型" , ret)     } } 
 
使用 switch 进行类型断言:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package  mainimport  "fmt" type  xxx interface {}func  main ()   {	var  x xxx 	x = false  	switch  v := x.(type ) { 	case  string : 		fmt.Println("是字符串类型,value:" , v) 	case  bool : 		fmt.Println("是布尔类型,value:" , v) 	case  int : 		fmt.Println("是int类型,value:" , v) 	default : 		fmt.Println("猜不到了,value:" , v) 	} } 
 
#  Go 语言标准库  
#  time 包 
time 包提供的部分方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 now := time.Now()  now.Unix()  now.UnixNano()  time.Unix()  func  (t Time)  Add(d Duration) Timefunc  (t Time)  Sub(u Time) Durationfunc  (t Time)  Equal(u Time) bool func  (t Time)  Before(u Time) bool func  (t Time)  After(u Time) bool tick := time.Tick(time.Second) ret1 := now.Format("2006-01-02 15:04:05" ) timeStr := "2023/08/12 09:29:00"  loc, err := time.LoadLocation("Asia/Shanghai" ) timeObj, err := time.ParseInLocation("2006/01/02 15:04:05" , timeStr, loc) timeObj, err := time.Parse("2006/01/02 15:04:05" , timeStr) 
 
具体使用可以看 Go 语言 - 时间对象
#  os 包 
os 包提供的部分方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func  Open (path string )  (*File, error )func  OpenFile (path string , flag int , perm FileMode)  (*File, error )func (f *File)  Read(b []byte )(n int , err error )reader := bufio.NewReader(f *File) line, err := reader.ReadString('\n' ) content, err := ioutil.ReadFile(path) func  (f *File)  Write(b []byte )(n int , err error )func  (f *File)  WriteString(s string )(n int , err error )func  NewWriter (w io.writer)   *Writerfunc  (w *Writer)  WriteString(s string )(n int , err error )func  (w *Writer)  Flush() error 
 
打开模式 flag 包括:
模式 
含义 
 
 
os.O_WRONLY 
只写 
 
os.O_CREATE 
创建文件 
 
os.O_RDONLY 
只读 
 
os.O_PDWR 
读写 
 
os.O_TRUNC 
重写 
 
os.O_APPEND 
追加 
 
 
perm:按照 linux 权限规定:r=4,w=2,x=1
具体使用可以看 Go 语言 - 文件读写 
#  sync 包 
1 2 3 4 5 sync.WaitGroup	 sync.Mutex		 sync.RWMutex	 sync.Once		 sync.Map		 
 
#  net 包 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func  Listen ("protocol", "Port")   (Listener, error )func  (l Listener)   Accept() (Conn, error )func  (c Conn)   Close()func  (c Conn)   Write(b []byte ) (n int , err error )func  Dial ("protocol", "address")   (Conn, error )func  (c Conn)   Read(b []byte ) (n int , err error )func  ListenUDP ("protocol", *UDPAddr)   (*UDPConn, error )func  (c *UDPConn)   ReadFromUDP(b []byte ) (n int , addr *UDPAddr, err error )func  (c *UDPConn)   WriteToUDP(b []byte , addr *UDPAddr) (int , error )func  DialUDP (network string , laddr *UDPAddr, raddr *UDPAddr)   (*UDPConn, error )func  (u *UDPConn)   Write(b []byte ) (n int , err error )func  (u *UDPConn)   Close()func  (u *UDPConn)   ReadFromUDP(b []byte ) (n int , err error )
 
#  Go 语言 - 时间对象 
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package  mainimport  (	"fmt"  	"time"  ) func  main ()   {	 	now := time.Now()  	fmt.Println(now) 	 	year := now.Year() 	month := now.Month() 	day := now.Day() 	hour := now.Hour() 	minute := now.Minute() 	second := now.Second() 	fmt.Println(year, month, day, hour, minute, second) 	 	timeStamp1 := now.Unix()      	timeStamp2 := now.UnixNano()  	fmt.Println(timeStamp1, timeStamp2) 	 	 	t := time.Unix(timeStamp1, 0 ) 	fmt.Println(t) 	 	 	sleepTime := 5  	 	 	time.Sleep(time.Duration(sleepTime) * time.Second) go 	 	 	t2 := now.Add(time.Hour) 	fmt.Println(t2) 	fmt.Println(t2.Sub(now))               ticker := time.Tick(time.Second)     for  i := range  ticker {         fmt.Println(i)     } } 
 
#  Go 语言 - 文件读写 
#  os 读取具体使用 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package  mainimport  (	"bufio"  	"fmt"  	"io"  	"os"  ) func  ReadAll (path string )   {	file, err := os.Open(path) 	if  err != nil  { 		fmt.Println("open file failed, err:" , err) 	}      	defer  file.Close() 	for  { 		var  temp = make ([]byte , 128 ) 		n, err := file.Read(temp) 		if  err == io.EOF { 			fmt.Println(string (temp[:n])) 			return  		} 		if  err != nil  { 			fmt.Println("read from file failed, err:" , err) 		} 		 		fmt.Printf(string (temp[:n])) 	} } func  ReadByBufio (path string )   {	file, err := os.Open(path) 	if  err != nil  { 		fmt.Println("open file failed, err:" , err) 	} 	defer  file.Close() 	reader := bufio.NewReader(file) 	for  { 		line, err := reader.ReadString('\n' ) 		if  err == io.EOF { 			fmt.Println(line) 			fmt.Println("file read over" ) 			break  		} 		if  err != nil  { 			fmt.Println("read file failed, err:" , err) 			return  		} 		fmt.Print(line) 	} } func  main ()   {	ReadAll("./example.txt" ) 	ReadByBufio("./example.txt" ) } 
 
#  os 写入具体使用 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package  mainimport  (	"bufio"  	"fmt"  	"os"  ) func  write (path string )   {	file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0644 ) 	if  err != nil  { 		fmt.Println("open file failed, err:" , err) 		return  	} 	defer  file.Close() 	str := "Hello World!"  	_, err = file.Write([]byte (str)) 	if  err != nil  { 		fmt.Println("Writer err:" , err) 	} 	_, err = file.WriteString(str) 	if  err != nil  { 		fmt.Println("WriteString err:" , err) 	} } func  writeByBufio (path string )   {	file, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0644 ) 	if  err != nil  { 		fmt.Println("open file failed, err:" , err) 		return  	} 	defer  file.Close() 	writer := bufio.NewWriter(file) 	_, err = writer.WriteString("Hello World!" )  	if  err != nil  { 		fmt.Println("WriteString err:" , err) 	} 	_ = writer.Flush()  } func  main ()   {	write("./example.txt" ) 	writeByBufio("./example.txt" ) } 
 
#  Go 语言 - 反射 
#  Go 语言 - 并发编程  
#  基本概念 
串行:像串一样,顺序执行。比如先读小学,小学结束读初中,初中结束读高中
并发:同一时间段内执行多个任务。比如上午我要学习和刷视频,可能是学一会儿习,然后刷一会儿视频
并行:同一时刻执行多个任务。比如我在看小说的同时听音乐
进程:程序在操作系统中的一次执行过程,系统进行资源分配和调度的一个独立单位
线程:操作系统基于进程开发的轻量级进程,是操作系统调度执行的最小单位
协程:非操作系统提供而是由用户自行创建和控制的用户态‘线程’,比线程更轻量级
并发模型:
业界将如何实现并发编程总结归纳为各式各样的并发模型,常见的有以下几种:
线程 & 锁模型 
Actor 模型 
CSP 模型 
Fork&Join 模型 
 
Go 语言中的并发程序主要是通过基于 CSP (communicating sequential processes) 的 goroutine 和 channel 来实现,当然也支持使用传统的多线程共享内存的并发模式
#  goroutine 使用 
使用关键字 go 来开启一个 goroutine,例如:
 
具体使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package  mainimport  (	"fmt"  	"sync"  ) var  wg sync.WaitGroupfunc  hello ()   {	fmt.Println("Hello World!" ) 	 	wg.Done() } func  main ()   { 	 	wg.Add(1 ) 	go  hello()  	fmt.Println("Hello Main!" ) 	 	wg.Wait() } 
 
#  GOMAXPROCS 
通过 runtime 包中的 GOMAXPROCS 可以指定占用的 CPU 数量,Go1.5 版本之后默认使用所有逻辑核心。示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package  mainimport  (	"fmt"  	"runtime"  	"sync"  ) var  wg sync.WaitGroupfunc  a ()   {	for  i := 0 ; i < 10 ; i++ { 		fmt.Println("a:" , i) 	} 	wg.Done() } func  b ()   {	for  i := 0 ; i < 10 ; i++ { 		fmt.Println("b:" , i) 	} 	wg.Done() } func  main ()   {	runtime.GOMAXPROCS(4 )  	wg.Add(2 ) 	go  a() 	go  b() 	for  i := 0 ; i < 10 ; i++ { 		fmt.Println("main:" , i) 	} 	wg.Wait() } 
 
#  channel 
channel 是一种类型,一种引用类型。声明方式如下:
1 2 3 4 5 6 7 8 9 var  变量 chan  元素类型var  ch1 chan  int 	var  ch2 chan  bool 	var  ch3 chan  []int 	ch1 = make (chan  int , [缓冲大小]) 
 
发送:
 
接受:
 
关闭:
 
需要注意的是,在 go 语言中,channel 的关闭并不一定是必须的。通道是可以被垃圾回收机制回收的。
只有在通知接收方 goroutine 所有的数据都发送完毕的时候才需要关闭通道。
关闭后的通道有以下特点:
对一个已经关闭的通道发送值会导致 panic 
对一个已经关闭的通道进行接收会一直获取值,直到通道为空 
对一个已经关闭的并且没有值的通道执行接收操作会得到对应类型的零值 
关闭一个已经关闭的通道会导致 panic 
 
#  缓冲 
无缓冲通道又被称为阻塞的通道,创建如下通道:
1 2 3 4 5 func  main ()  {	ch := make (chan  int ) 	ch <- 10  	fmt.Println("send success" ) } 
 
执行上面这段代码会导致程序死锁。
这是因为这段代码创建了一个无缓冲区的通道,则该通道无法缓存发送过来的值 10,所以它会一直等待有一个 goroutine 来取走这个值,从而阻塞程序。
而对于有缓冲区的通道:
1 2 3 4 5 func  main ()  {	ch := make (chan  int , 1 ) 	ch <- 10  	fmt.Println("send success" ) } 
 
make 函数里的 1 表示有一个位置的缓冲区,有缓冲区的通道会将发送过来的值暂存值缓冲区,有 goroutine 来取值时,则从缓冲区发送给它。
当缓冲区被存满后,则会变成无缓冲区的阻塞情况。
#  goroutine 与 channel 联动 
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package  mainimport  "fmt" func  f1 (ch chan <- int )   {	for  i := 0 ; i < 100 ; i++ { 		ch <- i 	} 	close (ch) } func  f2 (ch1 <-chan  int , ch2 chan <- int )   {	 	for  { 		temp, ok := <-ch1 		if  !ok { 			break  		} 		ch2 <- temp * temp 	} 	close (ch2) } func  main ()   {	ch1 := make (chan  int , 100 ) 	ch2 := make (chan  int , 200 ) 	go  f1(ch1) 	go  f2(ch1, ch2) 	 	for  ret := range  ch2 { 		fmt.Println(ret) 	} } 
 
#  worker pool (giriytube 池) 
工作中通常会使用 workerpool 模式,控制 goroutine 的数量,防止 goroutine 泄露和暴涨,简单示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package  mainimport  (	"fmt"  	"time"  ) func  worker (id int , jobs <-chan  int , results chan <- int )   {	for  job := range  jobs { 		fmt.Printf("worker:%d start job:%d\n" , id, job) 		results <- job * 2  		time.Sleep(time.Millisecond * 500 ) 		fmt.Printf("worker:%d stop job:%d\n" , id, job) 	} } func  main ()   {	jobs := make (chan  int , 100 ) 	results := make (chan  int , 100 ) 	 	for  j := 0 ; j < 3 ; j++ { 		go  worker(j, jobs, results) 	} 	 	for  i := 0 ; i < 5 ; i++ { 		jobs <- i 	} 	close (jobs) 	for  i := 0 ; i < 5 ; i++ { 		ret := <-results 		fmt.Println(ret) 	} } 
 
#  select 
select 语句的使用类似于 switch,当匹配到其可以执行的操作时,则进行该操作。
需要注意的是,当有多个 case 都满足时,select 并不会顺序执行,而是从中随机抽一个进行执行,使用示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package  mainimport  (	"fmt"  ) func  main ()   {	ch := make (chan  int , 10 ) 	for  i := 0 ; i < 10 ; i++ { 		select  { 		case  x := <-ch: 			fmt.Println(x) 		case  ch <- i: 		default : 			fmt.Println("default" ) 		} 	} } 
 
#  并发同步和锁 
有时候在 Go 代码中可能会存在多个 goroutine 同时操作一个资源 (临界区),这种情况会发生竞态问题 (数据竞态)
#  互斥锁 
互斥锁是一种常用的控制共享资源访问的方法,它能够保证同时只有一个 goroutine 可以访问共享资源
Go 语言中使用 sync 包的 Mutex 类型来实现互斥锁,使用方法如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package  mainimport  (	"fmt"  	"sync"  ) var  (	x    int64  	wg   sync.WaitGroup 	lock sync.Mutex ) func  add ()   {	for  i := 0 ; i < 50000 ; i++ { 		lock.Lock()  		x += 1  		lock.Unlock()  	} 	wg.Done() } func  main ()   {	wg.Add(2 ) 	go  add() 	go  add() 	wg.Wait() 	fmt.Println(x) } 
 
#  读写互斥锁 
互斥锁是完全互斥的,但有些时候有些资源仅被少量修改,大量读取时,可以尝试仅添加写锁,而不限制其访问。
使用 sync 包中的 RWMutex 类型实现读写锁,具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 package  mainimport  (	"fmt"  	"sync"  	"time"  ) var  (	x      int64  	wg     sync.WaitGroup 	rwLock sync.RWMutex ) func  read ()   {	 	rwLock.RLock() 	time.Sleep(time.Millisecond) 	 	rwLock.RUnlock() 	wg.Done() } func  write ()   {	 	rwLock.Lock() 	x += 1  	time.Sleep(time.Millisecond * 10 ) 	 	rwLock.Unlock() 	wg.Done() } func  main ()   {	start := time.Now() 	 	for  i := 0 ; i < 1000 ; i++ { 		wg.Add(1 ) 		go  read() 	} 	 	for  i := 0 ; i < 10 ; i++ { 		wg.Add(1 ) 		go  write() 	} 	wg.Wait() 	fmt.Println(time.Now().Sub(start)) } 
 
#  Go 语言 - socket 编程 
使用 Go 语言内置的 net 包来进行 tcp 或者 udp 通讯
该包所提供的方法,可以查看 Go 语言标准库 ->net 包 
#  TCP 样例 
#  server 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package  mainimport  (	"bufio"  	"fmt"  	"net"  ) func  process (conn net.Conn)   {	 	defer  conn.Close() 	 	for  { 		reader := bufio.NewReader(conn) 		var  buf [128 ]byte  		n, err := reader.Read(buf[:]) 		if  err != nil  { 			fmt.Println("read from conn failed, err:" , err) 			break  		} 		recv := string (buf[:n]) 		fmt.Println("receive message:" , recv) 		 		conn.Write([]byte ("ok" )) 	} } func  main ()   {	 	address := "localhost:20000"  	listen, err := net.Listen("tcp" , address) 	if  err != nil  { 		fmt.Println("listen failed, err:" , err) 		return  	} else  { 		fmt.Println("listen success." ) 		fmt.Println("Be listening " , address) 	} 	for  { 		 		conn, err := listen.Accept() 		if  err != nil  { 			fmt.Println("accept failed, err:" , err) 			continue  		} else  { 			fmt.Println("connect success" ) 		} 		 		go  process(conn) 	} } 
 
#  client 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package  mainimport  (	"bufio"  	"fmt"  	"net"  	"os"  	"strings"  ) func  main ()   {	 	conn, err := net.Dial("tcp" , "localhost:20000" ) 	if  err != nil  { 		fmt.Println("dial failed, err:" , err) 		return  	} else  { 		fmt.Println("dial success" ) 	} 	 	input := bufio.NewReader(os.Stdin) 	for  { 		s, _ := input.ReadString('\n' ) 		s = strings.TrimSpace(s) 		if  strings.ToUpper(s) == "Q"  { 			return  		} 		 		_, err := conn.Write([]byte (s)) 		if  err != nil  { 			fmt.Println("send failed, err:" , err) 			return  		} 		 		var  buf [1024 ]byte  		n, err := conn.Read(buf[:]) 		if  err != nil  { 			fmt.Println("read failed, err:" , err) 			return  		} 		fmt.Println("receive server respond:" , string (buf[:n])) 	} } 
 
#  UDP 样例 
#  server 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package  mainimport  (	"fmt"  	"net"  ) func  main ()   {	listen, err := net.ListenUDP("udp" , &net.UDPAddr{ 		IP:   net.IPv4(127 , 0 , 0 , 1 ), 		Port: 30000 , 	}) 	if  err != nil  { 		fmt.Println("listen failed, err:" , err) 	} else  { 		fmt.Println("Be listening 127.0.0.1:30000" ) 	} 	defer  listen.Close() 	for  { 		var  buf [1024 ]byte  		n, addr, err := listen.ReadFromUDP(buf[:]) 		if  err != nil  { 			fmt.Println("read from udp failed, err:" , err) 			return  		} 		fmt.Println("receive message:" , string (buf[:n])) 		_, err = listen.WriteToUDP(buf[:n], addr) 		if  err != nil  { 			fmt.Println("write to" , addr, "failed, err:" , err) 			return  		} else  { 			fmt.Println("send success" ) 		} 	} } 
 
#  client 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package  mainimport  (	"bufio"  	"fmt"  	"net"  	"os"  ) func  main ()   {	conn, err := net.DialUDP("udp" , nil , &net.UDPAddr{ 		IP:   net.IPv4(127 , 0 , 0 , 1 ), 		Port: 30000 , 	}) 	if  err != nil  { 		fmt.Println("dial failed, err:" , err) 		return  	} 	defer  conn.Close() 	input := bufio.NewReader(os.Stdin) 	for  { 		s, _ := input.ReadString('\n' ) 		_, err = conn.Write([]byte (s)) 		if  err != nil  { 			fmt.Println("send to server failed, err:" , err) 			return  		} 		var  buf [1024 ]byte  		n, addr, err := conn.ReadFromUDP(buf[:]) 		if  err != nil  { 			fmt.Println("recv from udp failed, err:" , err) 			return  		} 		fmt.Println("read from" , addr, "message:" , string (buf[:n])) 	} }