[Go语言基础 —— 基础语法|青训营笔记]
这是我参与「第五届青训营」伴学笔记创作活动的第一天
前言
记录go语言学习笔记,有不足的地方还请大佬们指正。
由于本人基础较为薄弱,所以笔记尽可能详细。
一、简述Go语言优点
- 高性能、高并发:Go语言有媲美C++、Java的性能。Go语言还内置了对高并发的支持,不像很多编程语言以库的形式支持。
- 学习简单,学习曲线平缓:Go语言的语法风格类似于C语言,并且在C语言的基础上进行了大幅度简化。比如去掉了不要的表达式括号,循环也只有for循环一种表示方法便可以实现数值、键值等各种遍历。一个Go语言学习者只需要短短一周时间从学习阶段转到真正的开发阶段,并完成一个高并发的应用程序的开发。
- 丰富的标准库:在很多情况下,不需要借助第三方库就可以完成基础功能的开发。标准库有很高的稳健性和兼容性的保障。
- 完整的工具链:无论是代码检查、编译等Go语言诞生之初就有。并且Go语言还内置了完整的单元测试框架。
- 静态链接:在Go语言里,所有的编译结果都是静态链接的。只需要考虑编译后的可执行文件,不需要附加任何东西便可部署运行。在线上容器运行,线上体积可以控制得非常小。
- 快速编译:Go语言拥有静态语言里几乎最快的编译速度
- 跨平台:Go语言文件能在非常多种设备下运行
- 垃圾回收:Go语言有类似于Java的垃圾回收机制,使得开发者可以更关注于业务逻辑而不是内存分配释放。
二、基础语法
-
"hello world"例子
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Go语言的代码通过包(package)组织,包类似于其它语言里的库(libraries)或者模块(modules)。一个包由位于单个目录下的一个或多个.go源代码文件组成,目录定义包的作用。每个源文件都以一条package声明语句开始,这个例子里就是package main,表示该文件属于哪个包,紧跟着一系列导入(import)的包,之后是存储在这个文件里的程序语句。
-
变量
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64
f := float32(e)
g := a + "foo"
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialapple
const s string = "constant"
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
Go语言声明变量格式如下:
var 变量名字 类型 = 表达式
其中“类型”或“= 表达式”两个部分可以省略其中的一个。如果省略的是类型信息,那么将根据初始化表达式来推导变量的类型信息。如果初始化表达式被省略,那么将用变量类型对应零值初始化该变量。 零值初始化机制可以确保每个声明的变量总是有一个良好定义的值,因此在Go语言中不存在未初始化的变量。
Go语言另一种声明变量的方式
变量名字 := 值
变量的类型根据表达式来自动推导。
Go语言中常量的声明方式就是把var改成const。并且在Go语言里的常量,它没有确定的类型,会根据使用的上下文来自动确定类型。
-
if-else
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
Go语言里面的if-else写法与C和C++类似,但是有两个不同点:
1、 Go语言中if后面没有括号。如果你写括号的话,那么保存的时候编译器会自动把括号去掉。
2、 Go语言中的if,它必须后面接大括号,不可以像C和C++一样,直接把if里面的语句写在同一行。
-
循环
i := 1
for {
fmt.Println("loop")
break
}
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
for i <= 3 {
fmt.Println(i)
i = i + 1
}
最简单的for循环就是什么都不写,代表一个死循环。循环途中可以用break跳出,也可以使用经典的C循环。
-
switch
Go 语言中的的switch分支结构看起来也和C或者C++比较类似。但是Go语言中的switch分支结构与C风格中的有以下不同点:
1、 switch后面的变量名不需要括号。
2、 在C++里面,switch case如果不显示地加break的话该程序会往下跑完所有的case,在Go语言里面的话是不需要加break的。
3、 Go语言里面的switch功能更强大,可以使用任意的变量类型,甚至可以用来取代if-else语句。可以在switch语句后面不加任何变量,然后在case里面写条件分支。这样的代码比简单地多用个if-else代码逻辑更为清晰。
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5:
fmt.Println("four or five")
default:
fmt.Println("other")
}
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
-
数组
数组就是一个具有编号且长度固定的元素序列。 不过在真实业务代码里,我们很少用直接使用数组,因为数组的长度是固定的,我们通常都是用切片。
var a [5]int
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
b := [5]int{1, 2, 3, 4, 5}
fmt.Println(b)
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
-
切片
切片不同于数组,切片可以任意更改长度。
切片可以使用append增加元素。在append用法中,我们必须把append的结果赋值为原数组。因为slice的原理是它存储了一个长度和一个容量,加一个指向数组的指针。当我们执行append的时候,若容量不够,会返回一个新的slice。
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]
fmt.Println(s[2:5]) // [c d e]
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
-
Map
我们可以使用make来创建一个map。这里需要两个类型,第一个是key类型,第二个是value类型。我们可以从里面去存储或者取出键值对。可以delete从里面删除键值对。Golang的map是完全无序的,遍历的时候不会按照字母顺序,也不会按照插入顺序输出,而是随机顺序。
m := make(map[string]int)
m["one"] = 1
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"]
fmt.Println(r, ok) // 0 false
delete(m, "one")
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
-
range
对于一个slice或者map的话,我们可以用range来快速遍历。
使用range遍历的时候,对于数组,返回两个值,第一个是索引,第二个是值。
对于map,返回两个值,第一个key,第二个是value。
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
for k := range m {
fmt.Println("key", k) // key a; key b
}
-
函数
Golang与很多其他语言不一样的地方是其变量类型是后置的。
Golang里面的函数原生支持返回多个值。在实际业务逻辑代码里面几乎所有的函数都返回两个值,一个是真正的返回结果,第二个值是错误信息。
func add(a int, b int) int {
return a + b
}
func add2(a, 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
}
func main() {
res := add(1, 2)
fmt.Println(res) // 3
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok) // A True
}
-
指针
Golang里的指针与C风格中的几乎一样,这里略。
-
结构体
关于初始化结构体变量:我们可以在构造的时候直接传入每个字段的初始值。也可以用键值对的方式去指定初始值,这样可以只对一部分字段进行初始化。
结构体方法:在Golang里面我们可以为结构体定义一些方法。会有一点类似其他语言里的类成员函数。 具体实现见下面的checkPassword和resetPassword方法。
命名规则就是将结构体参数类型写在函数名称前面。
实现结构体有两种方法,一种是带指针的,一种是不带指针的。它们的区别是如果该方法是带指针的话,那么我们就可以对这个结构体进行修改。如果不带指针的话,我们实际操作的是一个拷贝,我们就无法对结构体进行修改。
package main
import "fmt"
type user struct {
name string
password string
}
func (u user) checkPassword(password string) bool {
return u.password == password
}
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
-
错误处理