主页
关于
Stay before every beautiful thoughts
在每一个美好的思想前停留
文章
>
学习笔记
>
正文
GoLand 学习笔记
GoLand
Created at 2021-07-16 23:23
### 基础知识 ``` go 源码文件以 *.go 为后缀 package 类似命名空间 声明包名 生成 Go 可执行程序,必须建立一个名字为 main 的包,并且在该包中包含一个叫 main() 的主函数 编译命令 go build hello.log 编译并执行 go run hello.log ``` -------------------- ### 工程源码 ##### 目录结构  ``` bin 编译后文件 calc.exe 生成的可执行文件 src 源文件地址 calc 模块文件 calc.go 模块源文件(入口文件) simplemath 模块文件(算法库) add.go 加法模块 sqrt.go 平方根模块 ``` ----------------- ##### 配置 GOPATH 环境变量 在 Windows 中,则可以通过配置环境变量 GOPATH 来实现,将你的项目根目录完整路径拷贝过去就好 了。和 PATH 环境变量一样,GOPATH 也可以支持一次配置多个路径,并且路径和路径之间用冒号分隔。 GOPATH 的用处是 Go 语言在编译程序时,会从 GOPATH 配置的路径里面去查找源文件并完成构建。 设置完 GOPATH 后,现在我们开始构建工程。假设我们希望把生成的可执行文件放到 calc/bin 目录中,需要执行的一系列指令如下: ``` cd ~/go/calc mkdir bin cd bin go build calc ``` ----------------------- ##### 调试输出 ``` fval := 110.48 ival := 200 sval := "This is a string. " fmt.Println("The value of fval is", fval) fmt.Printf("fval=%f, ival=%d, sval=%s\n", fval, ival, sval) fmt.Printf("fval=%v, ival=%v, sval=%v\n", fval, ival, sval) ``` 对应的输出结果是: ``` The value of fval is 110.48 fval=110.480000, ival=200, sval=This is a string. fval=110.48, ival=200, sval=This is a string. ``` ---------------- ##### 变量声明 如果变量名包含多个单词,Go 语言变量命名规则遵循驼峰命名法,即首个单词小写,每个新单词的首字母大写,如 userName,但如果你的全局变量希望能够被外部包所使用,则需要将首个单词的首字母也大写。 ``` var ( v1 int v2 string ) var v1 int // 整型 var v2 string // 字符串 var v3 bool // 布尔型 var v4 [10]int // 数组,数组元素类型为整型 var v5 struct { // 结构体,成员变量 f 的类型为64位浮点型 f float64 } var v6 *int // 指针,指向整型 var v7 map[string]int // map(字典),key为字符串类型,value为整型 var v8 func(a int) int // 函数,参数类型为整型,返回值类型为整型 ``` ##### 变量赋值与多重赋值 ``` // 赋值 var v10 int v10 = 123 // 多重赋值 类似 php $t = $i; $i = $j; $j = $t; i, j = j, i ``` ##### 匿名变量 匿名变量则通过下划线 _ 来声明,任何赋予它的值都会被丢弃。 我们来看个例子,假设 GetName() 函数的定义如下,它返回两个值,分别为 userName 和 nickName: ``` func GetName() (userName, nickName string) { return "nonfu", "学院君" } // 若只想获得 nickName,则函数调用语句可以用如下方式实现: _, nickName := GetName() // php 版本 list($a, $b) = [$b, $a]; ``` ##### 变量的作用域 每个变量在程序中都有一定的作用范围,称之为作用域。如果一个变量在函数体外声明,则被认为是全局变量,可以在整个包甚至外部包(变量名以大写字母开头)使用,不管你声明在哪个源文件里或在哪个源文件里调用该变量。在函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。 尽管变量的标识符必须是唯一的,但你可以在某个代码块的内层代码块中使用相同名称的变量,则此时外部的同名变量将会暂时隐藏(结束内部代码块的执行后隐藏的外部同名变量又会出现,而内部同名变量则被释放),你任何的操作都只会影响内部代码块的局部变量。 ##### 常量定义 ``` const Pi float64 = 3.14159265358979323846 const zero = 0.0 // 无类型浮点常量 const ( // 通过一个 const 关键字定义多个常量,和 var 类似 size int64 = 1024 eof = -1 // 无类型整型常量 ) const u, v float32 = 0, 3 // u = 0.0, v = 3.0,常量的多重赋值 const a, b, c = 3, 4, "foo" // a = 3, b = 4, c = "foo", 无类型整型和字符串常量 ``` ##### 预定义常量 前面两个熟悉 PHP 或其他语言的应该都很熟悉,iota 比较特殊,可以被认为是一个可被编译器修改的常量,在每一个 const 关键字出现时被重置为 0,然后在下一个 const 出现之前,每出现一次 iota,其所代表的数字会自动增 1。 ``` package main const ( // iota 被重置为 0 c0 = iota // c0 = 0 c1 = iota // c1 = 1 c2 = iota // c2 = 2 ) const ( u = iota * 2; // u = 0 v = iota * 2; // v = 2 w = iota * 2; // w = 4 ) const x = iota; // x = 0 const y = iota; // y = 0 // 如果两个 const 的赋值语句的表达式是一样的,那么还可以省略后一个赋值表达式。因此,上面的前两个 const 语句可简写为: const ( c0 = iota c1 c2 ) const ( u = iota * 2 v w ) ``` ##### 常量的作用域 和函数体外声明的变量一样,以大写字母开头的常量在包外可见(Sunday ) 而以小写字母开头的常量只能在包内访问(zero) ##### 支持的数据类型 ``` Go 语言内置对以下这些基本数据类型的支持: 布尔类型:bool 整型:int8、byte、int16、int、uint、uintptr 等 浮点类型:float32、float64 复数类型:complex64、complex128 字符串:string 字符类型:rune 错误类型:error 此外,Go 语言也支持以下这些复合类型: 指针(pointer) 数组(array) 切片(slice) 字典(map) 通道(chan) 结构体(struct) 接口(interface) ``` ##### 整形  按照类型和范围,推导常见类型与应用场景 ``` uint32 | 可以用作主键ID 使用,也可以用作 limit 使用 ``` ------ ### 浮点数 在实际开发中,应该尽可能地使用 `float64` 类型,因为math包中所有有关数学运算的函数都会要求接收这个类型。 ``` 浮点数不是一种精确的表达方式,因为二进制无法精确表示所有十进制小数,比如 0.1、0.7 这种,下面我们通过一个示例来给大家直观演示下: float_value_4 := 0.1 float_value_5 := 0.7 float_value_6 := float_value_4 + float_value_5 注:浮点数的运算和整型一样,也要保证操作数的类型一致,float32 和 float64 类型数据不能混合运算,需要手动进行强制转化才可以,这一点和 PHP 不一样。 你觉得上面计算结果 float_value_6 的值是多少?0.8?不,它的结果是 0.7999999999999999,这是因为计算机底层将十进制的 0.1 和 0.7 转化为二进制表示时,会丢失精度,所以永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。 ```` ### 字符串 字符串连接,字符串连接只需要通过「+」即可(PHP 里面使用的是「.」) ``` str = str + ", 学院君" str += ", 学院君" // 上述语句也可以简写为这样,效果完全一样 ``` 字符串切片 ``` str = "hello, world" str_1 := str[:5] // 获取索引5(不含)之前的子串 str_2 := str[7:] // 获取索引7(含)之后的子串 str_3 := str[0:5] // 获取从索引0(含)到索引5(不含)之间的子串 fmt.Println(str_1) fmt.Println(str_2) fmt.Println(str_3) hello world hello ``` 字符串切片和 PHP 的 substr 函数使用方式有所差异,通过「:」对字符串进行切片,冒号之前的数字代表起始点(为空表示从0开始),之后的数字代表结束点(为空表示到字符串最后),而不是子串的长度。 ### 数组 数组也可以是多维的,与 PHP 不同的是,Go 语言中数组元素必须是同一个数据类型,并且需要在声明的时候指定元素类型和数组长度(静态语言的特征)。 ``` var a [8]byte // 长度为8的数组,每个元素为一个字节 var b [3][3]int // 二维数组(9宫格) var c [3][3][3]float64 // 三维数组(立体的9宫格) var d = [3]int{1, 2, 3} // 声明时初始化 var e = new([3]string) // 通过 new 初始化 a := [5]int{1,2,3,4,5} // 通过 := 对数组进行声明和初始化 a := [...]int{1, 2, 3} // 还可以通过这种语法糖省略数组长度的声明 ``` 数组在初始化的时候,如果没有填满,则空位会通过对应的元素类型空值填充: ``` a := [5]int{1, 2, 3} [1 2 3 0 0] fmt.Println(a) ``` 我们还可以初始化指定下标位置的元素值: ``` a := [5]int{1: 3, 3: 7} [0 3 0 7 0] arrLength := len(arr) ``` ### 遍历数组 ``` for i := 0; i < len(arr); i++ { fmt.Println("Element", i, "of arr is", arr[i]) } // range 关键词 for i, v := range arr { fmt.Println("Element", i, "of arr is", v) } // 只获取值 for _, v := range arr { // ... } // 只获取索引 for _, v := range arr { // ... } ``` ### 多维数组 ``` // 通过二维数组生成九九乘法表 var multi [9][9]string for j := 0; j < 9; j++ { for i := 0; i < 9; i++ { n1 := i + 1 n2 := j + 1 if n1 < n2 { // 摒除重复的记录 continue } multi[i][j] = fmt.Sprintf("%dx%d=%d", n2, n1, n1 * n2) } } // 打印九九乘法表 for _, v1 := range multi { for _, v2 := range v1 { fmt.Printf("%-8s", v2) // 位宽为8,左对齐 } fmt.Println() } ``` 自己的版本 [格式化输出](https://www.cnblogs.com/-beyond/p/8386869.html) ``` // %-8s 左对齐,位数不够补空白 func multiList() { for j := 1; j < 10; j++ { for i := 1; i <= j; i++ { if i == j { fmt.Printf("%-8s", fmt.Sprintf("%dx%d=%d", i, j, j*i)) fmt.Println("") } else { fmt.Printf("%-8s", fmt.Sprintf("%dx%d=%d", i, j, j*i)) } } } } ``` ### 数组切片的定义 在 Go 语言中,数组切片是一个新的数据类型,与数组最大的不同在于,切片的类型字面量中只有元素的类型,没有长度: ``` var slice []string = []string{"a", "b", "c"} ``` ``` // 先定义一个数组 months := [...]string{"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"} // 基于数组创建数组切片 q2 := months[3:6] // 第二季度 summer := months[5:8] // 夏季 fmt.Println(q2) fmt.Println(summer) [April May June] [June July August] all := months[:] // 全年 fmt.Println(len(q2)) // 3获取数组长度 fmt.Println(cap(q2)) // 9 获取数组容量 slice3 := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} fmt.Println(slice3[:1]) fmt.Println(slice3[5:]) fmt.Println(slice3[:len(slice3)-5]) slice4 := append(slice3, slice3[:7]...) fmt.Println(slice4) [1] [6 7 8 9 10] [1 2 3 4 5] [1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7] ``` ##### 字典定义 ``` func testMap() { var testMap map[string]int testMap = map[string]int{ "one": 1, "two": 2, "three": 3, } k := "two" v, ok := testMap[k] fmt.Println(k, v, ok, "==============") if ok { fmt.Printf("The element of key %q: %d\n", k, v) } else { fmt.Println("Not found!") } } // 运行结果 two 2 true ============== The element of key "two": 2 ``` #### 查找元素 ``` value, ok := testMap["one"] if ok { // 找到了 // 处理找到的value } ``` #### 删除元素 ``` delete(testMap, "four") ```