这是我参与「第三届青训营 -后端场」笔记创作活动的的第 1 篇笔记
1. Go 语言特点
- 高性能、高并发
- 语法简单、学习曲线平缓
- 丰富的标准库
- 完善的工具链
- 静态链接
- 快速编译
- 跨平台
- 垃圾回收 个人感受:GO 语言不仅性能比较好, 更重要的是项目部署简单、学习成本高, 并且有很多好用的框架,大大提高了开发效率。
2. Go 基础语法
对于学习过 C 语言的人来说,GO 语言会带来一种亲切感,标准输出语句如下:
// file: main.go
package main
import "fmt"
func main() {
fmt.Printf("Hello World")
}
通过 go build 命令可以将代码构建成可执行程序, 或者使用 go run main.go 构建并执行可执行程序且不保留可执行程序文件。Go 语言中的注释与 C 类似,导入包通过 import + 包名 , 导入多个包可以采用 import () 的形式:
package main
import(
"fmt"
_ "net/http"
. "image"
c "color"
)
...
Go 程序的入口从 main 包的主函数开始,在执行之前会调用导入包的 init 函数进行初始化,如果只想执行某些包的 init 函数而不使用,则进行匿名导入,在包前加 _ ;如果不想通过包名加点的方式调用,而是省略包名的话就在前面加 . ;有时候导入的包可能重名,为了防止冲突,可以给包赋予一个别名,如 c "color"。
个人感受:Go 的包管理很方便,对于某些没有的包,可以从网上直接下载,一般将文件夹与包名保持一致,方便进行项目的管理,也很直观。
2.1 变量
-
变量声明(
var 变量名 变量类型) Go 语言一个特点是变量都有默认初始值,对于引用类型的初始值为nil,表示空;对于值类型,都默认其零值为初始值 -
变量赋值 Go 语言可以根据赋值类型自动推断,语言支持字符串类型
var a = "initial" // a (string)
var b, c int = 1, 2
var e float64
f := 1.23 // f (float64)
个人感受:Go 语言对类型要求很严格,不支持隐式的类型转换,很多时候需要显示进行类型转换;常量的定义和初始化就是将 var 替换成 const ,= 两边的类型需要保持一致,:= 可以在 函数作用域 内使用,相当于声明+初始化,如果左边有多个变量,则需要保证 至少一个 变量之前没有声明过,由于没有使用的变量会导致编译出错,因此 GO 提供了占位符 _ 来表示未使用的变量。
2.2 条件语句
- 使用 (if-else if-else)
if i % 2 == 0 {
xxx
} else if (i % 3 == 0) {
xxx
} else {
xxx
}
个人感受:判断部分可以不加括号,保证表达式返回一个 bool 类型值即可,else if 和 else 不能换行书写,必须接在上个部分的 } 后面,{ 也不允许换行(语言强制要求),上面这种写法也可以用空 switch 来写,空 switch 默认为 true , case 部分放表达式,default 部分替换 else 即可。
2.3 循环
- Go 语言只有
for循环,这让我不太习惯,不过它的for有多种形式,在功能上很完全:
// 死循环
for {
xxx
}
// 表达式; 条件; 表达式(条件为true后执行)
for i := 0; i < n; i++ {
xxx
}
// 可以省略前后的表达式部分
for len(a) > 0 {
}
// 对于某些数据类型可以通过 for range 来访问
个人感受: 习惯后还是挺好用的,不过同样 { 必须和 for 语句在同一行才行,这和 Go 的解析方式有关。
2.4 数组
- Go 的数组和 C 类似,需要给出数组的大小,且大小固定,存放相同的数据类型
var a [5]int // 声明
a[4] = 100 // 赋值
b := [5]int{1, 2, 3, 4, 5}
c := [...]int{1, 2, 3} // len(c) = 3
// 传数组会将整个数组都拷贝
func f(a [5]int) {}
// 可以传入数组指针减少开销
func f(b *[3]int) {}
个人感受:数组大小固定,适用一些数据格式固定的场景,若需要通过函数改变数组内容,需要传递指针,与 C 不同,通过数组名传递不会默认传递地址,而需要 &arr 取地址传递才行,且 Go 语言的指针可以进行的操作很有限
2.5 切片
- Go 中的动态数组,当加入的元素超过容量后,会进行自动扩容(2倍),这点和 C++ 的
std::vector有点相似,切片由三个部分组成:指针、长度和容量,多个切片可以共享一个底层数组,发生扩容时会重新创建一个数组并将内容拷贝过去
// 切片的类型是 []type
s := []int{0, 1, 2, 3, 4, 5}
// 可以将一个数组的一部分通过切片的形式表示
a := [...]int{0, 1, 2, 3, 4, 5}
b := a[:3] // b ([]int{0, 1, 2})
// 可以通过 make 来创建一个指定长度和容量的切片
c := make([]int, 3, 5)
d := make([]int, 3) // len: 3 cap: 3
// 添加元素
c = append(c, 6)
个人感受: 使用切片时需要很小心,特别是使用 append 元素时,一定要注意扩容后的行为,如果扩容前的数组拥有另一个引用,那么扩容后那个引用并不是指向新分配的数组的,并且使用切片一定要注意如果只声明了它,其值为 nil ,因为切片是一个引用类型
2.6 map
- Go 中提供的
map支持通过key快速查找value,其中key不能改变且相互之间可以通过==进行比较,value可以支持任意类型,可以通过make或字母值来初始化map
var c map[string]int // c == nil
m := make(map[string]int) // key: string value: int
m2 := map[string]int{"one":1, "two":2}
r, ok := m["unoknow"] // 通过 ok 可以知道是否存在相应的 `key`
delete(m, "one") // 删除一个key
- 我们还能够通过
range来遍历map的所有key-value对,注意map是无序的,因此每次遍历的顺序都可能不一样,同样range也能用于遍历切片或数组,返回index-value对
m := map[string]string{"a":"A", "b":"B"}
for k, v := range m {
//doSomething...
}
nums := []int{2, 3, 4}
for i, num := range nums {
//doSomething...
}
2.7 函数
- GO 定义函数的关键字是
func,格式为func 函数名(形参列表) (返回值列表) {...},函数名首字母大写表示公开函数,可供外部调用者使用,支持返回多个值
func add(a int, b int) int {
return a + b
}
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
个人感受: 调用函数都会导致拷贝行为,因此实参本身不会改变,在函数中对行参的改变不会影响实参,我们要清楚地知道传入函数的切片还是值类型,如果想要在函数内部改变实参所操作的对象,可以通过指针或者切片来进行
2.8 结构体
- GO 中没有
class关键字,我们可以定义结构体并赋予其相应的方法来实现和class类似的面向对象功能,结构体定义格式为type 结构体名 struct { 成员 },实现结构体的方法通过func (接受者)方法名(方法形参列表)(方法返回值列表){...}的格式定义
// 定义结构体
type user struct {
name string
password string
}
// 定义方法, (值对象接收)
func (u user)Print() {
fmt.Println(u.name, u.password)
}
// 指针对象接收
func (u *user)SetName(name string) {
u.name = name
}
个人感受:需要注意方法的接收者是指类型还是指针类型,如果想要通过方法来改变成员,就需要通过指针的方式,如果传入一个取地址的值对象,Go 会自动帮你取地址调用方法,同样指针对象调用值对象方法,GO会自动帮你加上 * 取出对象后再调用方法