day01Go基础 | 青训营笔记

135 阅读22分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记

1. 概述

1.1 方向

  • 区块链研发

    • 互联网数据库技术
    • 去中心化,公开透明
  • go服务端(后端)/游戏软件开发

    • 后端(美团后台流量支持):

      • 支撑后台流量, 并发很厉害(排序,推荐,搜索等)
      • 提供负载均衡,缓存, 容错按条件分流...
      • 统计运行指标(qps等...)
    • 游戏服务端(通讯, 逻辑, 数据存储...)

  • 分布式/云计算软件开发

    • 京东云后台所有服务都用go实现

1.2 特点

Go 语言保证了既能到达静态编译语言的安全和性能,又达到了动态语言开发维护的高效率,使用一个表达式来形容 Go 语言:Go = C + Python , 说明 Go 语言既有 C 静态语言程序的运行速度,又能达到 Python 动态语言的快速开发。

  • 从 C 语言中继承了很多理念,包括表达式语法,控制结构,基础数据类型,调用参数传值,指针等等,也保留了和 C 语言一样的编译执行方式及弱化的指针

  • 引入包的概念,用于组织程序结构,Go 语言的一个文件都要归属于一个包,而不能单独存在

  • 垃圾回收机制,内存自动回收,不需开发人员管理

  • 天然并发 (重要特点)

    (1) 从语言层面支持并发,实现简单

    (2) goroutine,轻量级线程,可实现大并发处理,高效利用多核。

    (3) 基于 CPS 并发模型(Communicating Sequential Processes )实现

  • 吸收了管道通信机制,形成 Go 语言特有的管道channel 通过管道 channel , 可以实现不同的goroute之间的相互通信。

  • 函数可以返回多个值。举例:

     //写一个函数,实现同时返回 和,差
     //go 函数支持返回多个值
     func getSumAndSub(n1 int, n2 int) (int, int ) { 
         //go 语句后面不要带分号. 
         sum := n1 + n2 
         sub := n1 - n2
         return sum , sub
     }
    
  • 新的创新:比如切片 slice、延时执行 defer

Go语言安装之后,D:\Go目录下一共有9个目录与9个文件,如下图

image-20220423214958492

api — 目录,包含所有API列表,方便IDE使用

bin— 目录,存放编译后的可执行文件, 自带一些go的指令:go/godoc/gofmt...

blog— 目录,

doc— 目录,帮助文档

lib— 目录,

misc— 目录,

pkg— 目录,存放编译后的包文件。pkg中的文件是Go编译生成的

src— 目录,存放项目源文件, 放了go的源代码

注:一般,bin和pkg目录可以不创建,go命令会自动创建(如 go install),只需要创建src目录即可。

Authors— 文件,作者列表,用记事本打开

CONTRIBUTING.md— 文件,

CONTRIBUTORS— 文件,

favicon.ico— 文件,

LICENSE— 文件,license,用记事本打开

PATENTS— 文件,

README.md— 文件,

robots.txt— 文件,使用robots.txt阻止对网址的访问,详情查看support.google.com/webmasters/…

VERSION— 文件,版本信息,用记事本打开

1.3 语法习惯

  1. 不带分号(Go 编译器是一行行进行编译的)

  2. 大括号不能换行,否则报错

  3. 在 go 中,每个文件都必须归属于一个包, 所以第一行必须写

     package 包名
     ​
     package main // 必须打main包
     // main函数必须在main目录下,包名则必须和上级目录名一致(main)
     // 一个项目必须有且只有一个main目录(或main包)   
     // 只有在main包下的main函数才是程序入口
    
  4. 导入包示例:

     import "fmt"
     ​
     // fmt包含go的打印语句:
     fmt.Println("hello world")
     // 输出多行:
     fmt.Println("hello ",
                 "world")
    
  5. 定义方法:

     func name
    
  6. 编译(会在该目录下生成一个.exe可执行文件):

     go build hello.go
    

    执行(exe文件不依赖于go的开发环境,因为已经打包好了, 所以exe文件比起.go文件会变大):

     #windows
     hello.exe
     #Linux&mac(Linux直接就是一个可执行文件,没有.exe)
     ./hello
    

    直接运行(相当于运行脚本文件, 不生成exe文件):

     go run hello.go
    
  7. Go 应用程序的执行入口是 main()函数

    严格区分大小写

  8. go 语言定义的变量或者 import 的包如果没有使用到,代码不能编译通过

  9. 注释://or/**/

  10. 变量名、函数名、常量名:采用驼峰法

    如果变量名、函数名、常量名首字母大写,则可以被其他的包访问;如果首字母小写则只能在本包中使用(注:可以简单的理解成,首字母大写是公有的,首字母是私有的),在 golan没有public, private等关键字

    文件命名:一律采用小写,不用驼峰式

    测试文件以xx_test.go结尾

2. 基础语法

2.1 变量

变量表示内存中的一个存储区域, 该区域有自己的名称(变量名)和类型(数据类型

变量有三种声明方式:

 package main
  
 import "fmt"
  
 func main() {
     var i int  = 10     // var 变量名 类型 = 值
     var j = 1.2         // var 变量名 = 值, 行判定变量类型,类型推导
  
     name := "szc"       // 变量名 := 值,自动推导类型并且省略var
  
     fmt.Println("i = ", i, ", j = " , j , ", name = ", name)
 }
  1. 第一种:指定变量类型,声明后若不赋值,使用默认值

    例如int/小数默认值是0,字符串默认空串

  2. 第二种:根据值自行判定变量类型(类型推导)

  3. 第三种:省略 var, 注意 :=左侧的变量不应该是已经声明过的,否则会导致编译错误

  4. 使用细节:如果声明时不赋值就使用第一种方法,否则使用二三更方便

声明多个变量同样有三种方法:

var a, b, c float32 // 单精度小数
var a, sex, b = 1, "male", 7
a, sex, b := 2, "male", 4

声明多个全局变量: 在 go 中函数外部定义变量就是全局变量

go中的变量指定数据类型之后不能更改数据类型

go声明变量时一般通过自动推导数据类型的方法声明

注意:

下划线”_”本身在Go中是一个特殊的标识符,称为空标识符。可以代表任何其它的标识符,但是它对应的值会被忽略(比如:忽略某个返回值)。所以仅能被作为占位符使用,不能作为标识符使用

示例:

//写一个函数,实现同时返回 和,差
//go 函数支持返回多个值
func getSumAndSub(n1 int, n2 int) (int, int ) { 
    //go 语句后面不要带分号. 
    sum := n1 + n2 
    sub := n1 - n2
	return sum , sub
}

func main() {
    // 只取出第一个返回值
    sum, _ := getSumAndSub(1,3)
 
    fmt.Println(sum)
}

2.2 常量

常量必须赋初值,而且不可更改

    const tax int = 1
    const name_ = "szc"
    const b = 4 / 2
 
//    const b_ = getVal() 	// 编译期值不确定
//    num := 1
//    const b_ = num / 2  	// 编译期值不确定
//    const tax_0 int 	// 必须赋初值
//    tax = 2	 // 不可修改

常量只能修饰布尔、数值、字符串类型

也可以这么声明常量,可以在函数里面声明:

const (
    a = iota  //0
    b = iota  //1
    c = iota  //2
    d,e = iota,iota  //3 3
)
fmt.Println(a, b, c, d, e) // 0 1 2 3 3 依次加1

上面b和c可以不写= iota,但是a必须写

2.3 数据类型

go数据类型

Golang 中没有专门的字符类型,如果要存储单个字符(字母),一般使用 byte 保存。

字符串(string类型)就是一串固定长度的字符连接起来的字符序列。Go 的字符串是由单个字节连接起来的。也就是说对于传统的字符串是由字符组成的,而 Go 的字符串不同,它是由字节组成的, Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本

字符串的两种表示形式

(1) 双引号, 会识别转义字符

(2) 反引号(``),以字符串的原生形式输出,包括换行和特殊字符,可以实现防止攻击、输出源代码等效果

在 go 中,数据类型都有一个默认值,当程序员没有赋值时,就会保留默认值,在 go 中,默认值又叫零值

image-20220508140428817

在go中转换数据类型需要float32(变量名)之类的进行转换, 不能直接转换

字符串格式化输出:

fmt.Printf("s=%v\n", s) # 通用格式化输出,输出效果最简
fmt.Printf("s=%+v\n", s) # 通用格式化输出,输出效果多一点,例如map的key
fmt.Printf("s=%#v\n", s) # 通用格式化输出,输出效果最多,例如带上数据类型

2.4 指针

获取变量的地址,用&,比如:

 var num int
 //获取 num 的地址:
 &num 

指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值比如:

var ptr *int = &num

如图:

image-20220508141500905

获取指针类型所指向的值,使用:*,比如:

var ptr *int = &num
//使用*ptr 获取 ptr 指向的值:
var i int = *ptr

总结:

  • 指针存放的是变量的地址

  • &表示取变量地址

  • *在声明变量时表示声明的是指针

    在取值时表示从这个地址中拿出对应的值

image-20220508141942320

每个值类型都有一个相对于的指针类型, 通过指针修改值会直接影响原来的变量, 示例:

var num int = 10
var ptr *int = &num
//使用*ptr 获取 ptr 指向的值:
*ptr = 100
// num被改成了100

2.5 值类型与引用类型

  • 值类型:变量直接存储值,内存通常在栈中分配
  • 引用类型:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由 GC 来回收

内存的栈与堆示意图:

image-20220508142351397

2.6 运算符

go可以++--, python不行, 但是Golang 的++ 和 -- 只能写在变量的后面,不能写在变量的前面,即:只有 a++ a--没有 ++a --a

对于除号 "/",它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分。 例如:x := 19/5 ,结果是3, 这个和Java是一样的

逻辑运算符与Java一致

golang设计原则:一件事情有且只有一种方法完成, 因此没有三元运算符

2.7 键盘输入

  1. 导入 fmt 包
  2. 调用 fmt 包的 fmt.Scanln() 或者 fmt.Scanf()

image-20220508143109592

示例:

package main
import (
	"fmt" 
)


func main() {
	//要求:可以从控制台接收用户信息,【姓名,年龄,薪水, 是否通过考试 】。
	//方式1 fmt.Scanln
	//1先声明需要的变量
	var name string
	var age byte
	var sal float32
	var isPass bool
	fmt.Println("请输入姓名 ")
	//当程序执行到 fmt.Scanln(&name),程序会停止在这里,等待用户输入,并回车 
	fmt.Scanln(&name)
	fmt.Println("请输入年龄 ")
	fmt.Scanln(&age)
	fmt.Println("请输入薪水 ")
	fmt.Scanln(&sal)
	fmt.Println("请输入是否通过考试 ")
	fmt.Scanln(&isPass)
	fmt.Printf("名字是 %v \n 年龄是 %v \n 薪水是 %v \n 是否通过考试 %v \n", name, age, sal, isPass)

	//方式2:fmt.Scanf,可以按指定的格式输入
	fmt.Println("请输入你的姓名,年龄,薪水, 是否通过考试, 使用空格隔开")
	fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass)
	fmt.Printf("名字是 %v \n年龄是 %v \n 薪水是 %v \n 是否通过考试 %v \n", name, age, sal, isPass)
}

2.8 流程控制语句

go的if后面不需要接小括号(如果你写了,你的编译器会自动去掉小括号), 但是必须接大括号

go的switch不需要加break,会自动跳出,switch支持任何数据类型,甚至可以在case里面写if的那种判断条件,从而不需要写太多ifelse,使得程序结构更加清晰易懂, golang 的 case 后的表达式可以有多个,使用 逗号 间隔, 都没有匹配成功则执行 default 的语句块

switch 穿透-fallthrough ,如果在 case 语句块后增加 fallthrough ,则会继续执行下一个 case,也叫 switch 穿透

switch代码:

package main
import (
	"fmt" 
)

func main() {
	var n1 int32 = 51
	var n2 int32 = 20
	switch n1 {
		case n2, 10, 5 :  // case 后面可以有多个表达式
			fmt.Println("ok1")
		case 90 : 
			fmt.Println("ok2~")
		default:
			fmt.Println("哎呀呀...")
	}


	//switch 后也可以不带表达式,类似 if --else分支来使用。【案例演示】
	var age int = 10
	switch {
		case age == 10 :
			fmt.Println("age == 10")
		case age == 20 :
			fmt.Println("age == 20")
		default :
			fmt.Println("没有匹配到")
	}
	//case 中也可以对 范围进行判断
	var score int = 90
	switch {
		case score > 90 :
			fmt.Println("成绩优秀..")
		case score >=70 && score <= 90 :
			fmt.Println("成绩优良...")
		case score >= 60 && score < 70 :
			fmt.Println("成绩及格...")
		default :
			fmt.Println("不及格")
	}
	
	//switch 的穿透 fallthrought
	var num int = 10
	switch num {
		case 10:
			fmt.Println("ok1")
			fallthrough //默认只能穿透一层
		case 20:
			fmt.Println("ok2")
			fallthrough
		case 30:
			fmt.Println("ok3")	
		default:
			fmt.Println("没有匹配到..")
	}
}

go只有for循环没有while和do while

for代码:

// 其中下面这里任何一部分都可以省略
for 循环变量初始化;  循环条件;  循环变量迭代 {
	循环操作(语句)
}

// 第二种用法:当while用
for	循环判断条件	{
	//循环执行语句
}

// 第三种用法
// 等价 for ; ; {}是一个无限循环,通常需要配合 break 语句使用
for	{
	//循环执行语句
}

// 第四种用法
// Golang 提供 for-range 的方式,可以方便遍历字符串和数组
//字符串遍历方式2-for-range
str = "abc~ok上海"
for index, val := range str {
	fmt.Printf("index=%d, val=%c \n", index, val)
}

// 对于 for-range 遍历方式而言,是按照字符方式遍历。因此如果有字符串有中文,也是ok的
// 但是如果直接遍历,如果字符串含有中文,那么传统的遍历字符串方式会错误出现乱码
// 原因是传统的对字符串的遍历是按照字节来遍历,而一个汉字在 utf8 编码是对应 3 个字节。
// 如何解决 需要要将str 转成 []rune 切片:
str = "abc~ok上海"
str2 := []rune(str)
for i:=0;i<len(str2);i++ {
	fmt.Printf("%v \n", str2[i])
}

break 语句用于终止某个语句块的执行,用于中断当前 for 循环或跳出 switch 语句

break 语句出现在多层嵌套的语句块中时,可以通过标签指明要终止的是哪一层语句块

continue 语句用于结束本次循环,继续执行下一次循环

continue 语句出现在多层嵌套的循环语句体中时,可以通过标签指明要跳过的是哪一层循环

goto:

  1. Go 语言的 goto 语句可以无条件地转移到程序中指定的行。
  2. goto 语句通常与条件语句配合使用。可用来实现条件转移,跳出循环体等功能。
  3. 在 Go 程序设计中一般不主张使用 goto 语句, 以免造成程序流程的混乱,使理解和调试程序都产生困难
func main() {

	var n int = 30
	演示goto的使用
	fmt.Println("ok1")
	if n > 20 {
		goto label1
	}
	fmt.Println("ok2")
	fmt.Println("ok3")
	fmt.Println("ok4")
	label1:
	fmt.Println("ok5")
	fmt.Println("ok6")
	fmt.Println("ok7")
}

return:

  1. 如果 return 是在普通的函数,则表示跳出该函数,即不再执行函数中 return 后面代码,也可以理解成终止函数
  2. 如果 return 是在 main 函数,表示终止 main 函数,也就是说终止程序

2.9 字符串常用函数

  1. 统计字符串的长度,按字节 len(str)

  2. 字符串遍历,同时处理有中文的问题 r := []rune(str)

  3. 字符串转整数: n, err := strconv.Atoi("12")

  4. 整数转字符串 str = strconv.Itoa(12345)

  5. 字符串 转 []byte: var bytes = []byte("hello go")

  6. []byte 转 字符串: str = string([]byte{97, 98, 99})

  7. 查找子串是否在指定的字符串中: strings.Contains("seafood", "foo") //true

  8. 不区分大小写的字符串比较(== 是区分字母大小写的): fmt.Println(strings.EqualFold("abc", "Abc")) // true

  9. 按 照 指 定 的 某 个 字 符 , 为 分 割 标 识 , 将 一 个 字 符 串 拆 分 成 字 符 串 数 组

    strings.Split("hello,wrold,ok", ",")

2.10 时间与日期函数

时间和日期相关函数,需要导入 time 包

package main
import (
	"fmt"
	"time"
)

func main() {
	//看看日期和时间相关函数和方法使用
	//1. 获取当前时间
	now := time.Now()
	fmt.Printf("now=%v now type=%T\n", now, now)

	//2.通过now可以获取到年月日,时分秒
	fmt.Printf("年=%v\n", now.Year())
	fmt.Printf("月=%v\n", now.Month())
	fmt.Printf("月=%v\n", int(now.Month()))
	fmt.Printf("日=%v\n", now.Day())
	fmt.Printf("时=%v\n", now.Hour())
	fmt.Printf("分=%v\n", now.Minute())
	fmt.Printf("秒=%v\n", now.Second())

	//格式化日期时间
	fmt.Printf("当前年月日 %d-%d-%d %d:%d:%d \n", now.Year(), 
	now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())

	dateStr := fmt.Sprintf("当前年月日 %d-%d-%d %d:%d:%d \n", now.Year(), 
	now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())

	fmt.Printf("dateStr=%v\n", dateStr)

	//格式化日期时间的第二种方式
	fmt.Printf(now.Format("2006-01-02 15:04:05"))
	fmt.Println()
	fmt.Printf(now.Format("2006-01-02"))
	fmt.Println()
	fmt.Printf(now.Format("15:04:05"))
	fmt.Println()

	fmt.Printf(now.Format("2006"))
	fmt.Println()


	//需求,每隔1秒中打印一个数字,打印到100时就退出
	//需求2: 每隔0.1秒中打印一个数字,打印到100时就退出
	// i := 0
	// for {
	// 	i++
	// 	fmt.Println(i)
	// 	//休眠
	// 	//time.Sleep(time.Second)
	// 	time.Sleep(time.Millisecond * 100)
	// 	if i == 100 {
	// 		break
	// 	}
	// }

	//Unix和UnixNano的使用
	fmt.Printf("unix时间戳=%v unixnano时间戳=%v\n", now.Unix(), now.UnixNano())

}

3. 函数,包和异常处理

3.1 函数与包

基本语法:

image-20220508152023051

//请编写要给函数,可以计算两个数的和和差,并返回结果
func getSumAndSub(n1 int, n2 int) (int, int) {
	sum := n1 + n2
	sub := n1 - n2
	return sum, sub
}

区分相同名字的函数、变量等标识符

当程序文件很多时,可以很好的管理项目

控制函数、变量等访问范围,即作用域

Ø 打包基本语法

package 包名

Ø 引入包的基本语法

import "包的路径"

在 import 包时,路径从 $GOPATH 的 src 下开始,不用带 src , 编译器会自动从 src 下开始引入

为了让其它包的文件,可以访问到本包的函数,则该函数名的首字母需要大写,类似其它语言的 public ,这样才能跨包访问, 首字母小写,只能被本包文件使用,其它包文件不能使用,类似 privat

在访问其它包函数,变量时,其语法是 包名.函数名

如果包名较长,Go 支持给包取别名, 注意细节:取别名后,原来的包名就不能使用了( 如果给包取了别名,则需要使用别名来访问该包的函数和变量)

在同一包下,不能有相同的函数名(也不能有相同的全局变量名),否则报重复定义

如果希望函数内的变量能修改函数外的变量(指的是默认以值传递的方式的数据类型),可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用

Go 函数不支持函数重载

在 Go 中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调

函数函数既然是一种数据类型,因此在 Go 中,函数可以作为形参,并且调用

为了简化数据类型定义,Go 支持自定义数据类型:

基本语法:

type 自定义数据类型名	数据类型	
//	理解: 相当于一个别名案例:
type myInt int	//  这时 myInt就等价int来使用了
type mySum	func (int, int) int	
// 这时 mySum 就等价 一个 函数类型 func (int, int) int

支持对函数返回值命名, 这样子return的时候就可以直接写return不写变量名了

image-20220508153259755

go支持函数可变参数:

image-20220508153409533

init函数:

每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也就是说 init 会在 main 函数前被调用

如果一个文件同时包含全局变量定义init 函数main 函数,则执行的流程: 全局变量定义->init 函数->main 函数

细节说明: 面试题:案例如果 main.go 和 utils.go 都含有 变量定义,init 函数时,执行的流程又是怎么样的呢

image-20220508153620049

匿名函数

分为定义时直接调用, 给个名字再用, 以及全局匿名函数:

package main
import (
	"fmt"
)

var (
	//fun1就是一个全局匿名函数
	Fun1 = func (n1 int, n2 int) int {
		return n1 * n2
	}
)

func main() {
	//在定义匿名函数时就直接调用,这种方式匿名函数只能调用一次
	//案例演示,求两个数的和, 使用匿名函数的方式完成
	res1 := func (n1 int, n2 int) int {
		return n1 + n2
	}(10, 20)

	fmt.Println("res1=", res1)

	//将匿名函数func (n1 int, n2 int) int赋给 a变量
	//则a 的数据类型就是函数类型 ,此时,我们可以通过a完成调用
	a := func (n1 int, n2 int) int {
		return n1 - n2
	}

	res2 := a(10, 30)
	fmt.Println("res2=", res2)
	res3 := a(90, 30)
	fmt.Println("res3=", res3)

	//全局匿名函数的使用
	res4 := Fun1(4, 9)
	fmt.Println("res4=", res4)
}

闭包---doc文档188页

函数的defer:

在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等) ,为了在函数执行完毕后,及时的释放资源,Go 的设计者提供 defer (延时机制), 如图:

image-20220510132201349

因此可以把一些关闭资源之类的语句写在defer后面

  1. 当 go 执行到一个 defer 时,不会立即执行 defer 后的语句,而是将 defer 后的语句压入到一个栈中[我为了讲课方便,暂时称该栈为 defer 栈], 然后继续执行函数下一个语句。
  1. 当函数执行完毕后,在从 defer 栈中,依次从栈顶取出语句执行(注:遵守栈 先入后出的机制, 也就是把defer语句反过来执行)
  1. 在 defer 将语句放入到栈时,也会将相关的值拷贝同时入栈。因此在defer语句后面修改变量的值, 但是defer执行的时候变量还是之前的

函数的值传递与运用传递:

  1. 值类型:基本数据类型 int 系列, float 系列, bool, string 、数组和结构体 struct
  1. 引用类型:指针、slice 切片、map、管道 chan、interface 等都是引用类型
  2. 其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝取决于拷贝的数据大小,数据越大,效率越低

go的内置函数:

new:用来分配内存,主要用来分配值类型,比如 int、float32,struct...返回的是指针

 num := new(int)

len:用来求长度,比如 string、array、slice、map、channel

make:用来分配内存,主要用来分配引用类型,比如 channel、map、slice

3.2 异常处理

  1. Go 语言追求简洁优雅,所以,Go 语言不支持传统的 try…catch…finally 这种处理。
  1. Go 中引入的处理方式为:defer, panic, recover
  1. 这几个异常的使用场景可以这么简单描述:Go 中可以抛出一个 panic 的异常,然后在 defer 中通过 recover 捕获这个异常,然后正常处理

示例代码:

 package main
 import (
     "fmt"
     _ "time"
     "errors"
 )
 ​
 func test() {
     //使用defer + recover 来捕获和处理异常
     defer func() {
         err := recover()  // recover()内置函数,可以捕获到异常
         if err != nil {  // 说明捕获到错误
             fmt.Println("err=", err)
             //这里就可以将错误信息发送给管理员....
             fmt.Println("发送邮件给admin@sohu.com~")
         }
     }()
     num1 := 10
     num2 := 0
     res := num1 / num2
     fmt.Println("res=", res)
 }
 ​
 //函数去读取以配置文件init.conf的信息
 //如果文件名传入不正确,我们就返回一个自定义的错误
 func readConf(name string) (err error) {
     if name == "config.ini" {
         //读取...
         return nil
     } else {
         //返回一个自定义错误
         return errors.New("读取文件错误..")
     }
 }
 ​
 func test02() {
 ​
     err := readConf("config2.ini")
     if err != nil {
         //如果读取文件发送错误,就输出这个错误,并终止程序
         panic(err)
     }
     fmt.Println("test02()继续执行....")
 }
     
 ​
 func main() {
 ​
     //测试
     // test()
     // for {
     //  fmt.Println("main()下面的代码...")
     //  time.Sleep(time.Second)
     // }
 ​
     //测试自定义错误的使用
 ​
     test02()
     fmt.Println("main()下面的代码...")
 }

4. 数组和切片

数组:

注意:数组也是一种数据类型,在 Go 中,数组是值类型, 也就是说Go 的数组属值类型, 在默认情况下是值传递, 因此会进行值拷贝。数组间不会相互影响, 如想在其它函数中,去修改原来的数组,可以使用引用传递(指针方式)

数组定义:

 var 数组名 [数组大小]数据类型
 var a [5]int

如果初始化时没有给定数组大小, 那么此时就是一个切片(slice)而不是数组了

在go中数组可以使用range遍历, 返回下标与元素值:

 for i, v := range 数组名{
 ​
 }

切片:

  1. 切片是数组的一个引用,因此切片是引用类型,在进行传递时,遵守引用传递的机制。
  1. 切片的使用和数组类似,遍历切片、访问切片的元素和求切片长度 len(slice)都一样。
  1. 切片的长度是可以变化的,因此切片是一个可以动态变化数组
  1. 切片定义的基本语法:
 var 切片名 []类型
 var a [] int

这里切片的定义和python不同, 类似Java的List,

个人理解:

GO的切片其实就是把数组或者make的东西通过[:]切片赋值给一个变量, 这样子这个变量的数据类型就是引用类型切片类型了

切片使用注意事项:

  1. 切片初始化时 var slice = arr[startIndex:endIndex]

    说明:从 arr 数组下标为 startIndex,取到 下标为 endIndex 的元素(不含 arr[endIndex])。

  2. 切片初始化时,仍然不能越界(也就是说不能在使用的时候越界)。范围在 [0-len(arr)] 之间,但是可以动态增长.

  3. 简写(类似python)

 var slice = arr[0:end] 
 //可以简写 
 var slice = arr[:end]
 ​
 var slice = arr[start:len(arr)] 
 //可以简写: 
 var slice = arr[start:] 
 ​
 var slice = arr[0:len(arr)] 
 //可以简写: 
 var slice = arr[:]
  1. cap 是一个内置函数,用于统计切片的容量,即最大可以存放多少个元素。
  1. 切片定义完后,还不能使用,因为本身是一个空的,需要让其引用到一个数组,或者 make 一个空间供切片来使用

  2. 切片还可以继续切片套娃

  3. 用 append 内置函数,可以对切片进行动态追加

    切片 append 操作的底层原理分析:

    切片 append 操作的本质就是对数组扩容

    go 底层会创建一下新的数组 newArr(安装扩容后大小)

    将 slice 原来包含的元素拷贝到新的数组 newArr

    slice 重新引用到 newArr

    注意 newArr 是在底层来维护的,程序员不可见.

  4. 切片的拷贝操作

    切片使用 copy 内置函数完成拷贝

    拷贝是深拷贝, 拷贝之后空间就不是指向同一块了, 之前的将数组赋值给切片其实空间也都是那一块

  5. string 底层是一个 byte 数组,因此 string 也可以进行切片处理

    但是注意:string 是不可变的,也就说不能通过 str[0] = 'z' 方式来修改字符串

5. map

6. 面向对象

7. 文件操作(包括JSON)

8. 抓包生成代码

抓包自动生成代码!!! 右键网页检查,接着找到那个请求对应的响应,右键copy-->copy cURL然后去代码生成网站 代码生成网站:curlconverter.com/#go , 运行完会生成一大串JSON除此之外还有一个根据JSON生成结构体的网站: oktools.net/json2go

image-20220510140946747

socks5协议开发代理服务器

\