go语言基础语法|青训营笔记
这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
知识要点
-
什么是Go语言
- Go语言基本特征
- Go语言应用优势
-
Go语言入门
- 安装开发环境
- Go语言基础语法
重点:Go语言基础语法
包、变量、函数
每个 Go 程序都是由包构成的。
程序从 main 包开始运行。
按照约定,包名与导入路径的最后一个元素一致。例如,"math/rand" 包中的源码均以 package rand 语句开始。
导入与导出
import (
"fmt"
"math"
)
此代码用圆括号组合了导入,这是 “分组” 形式的导入语句。当然也可以编写多个导入语句,不过使用分组导入语句是更好的形式。
在 Go 中,如果一个名字以大写字母开头,那么它就是已导出的。在导入一个包时,你只能引用其中已导出的名字。任何 “未导出” 的名字在该包外均无法访问。
例如:你只能使用math.Pi而非math.pi
函数
函数可以没有参数或接受多个参数。注意类型在变量名之后。
func add(x int, y int) int {
return x + y;
}
当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略。
func add(x, y int) int {
return x + y;
}
函数可以返回任意数量的返回值。
func swap(x, y int) (int, int) {
return y, x;
}
变量
var 语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。var 语句可以出现在包或函数级别。
var a, b, c int
变量声明可以包含初始值,每个变量对应一个。如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。
var i, j int = 1, 2
短变量声明。在函数中,简洁赋值语句 := 可在类型明确的地方代替 var 声明。
函数外的每个语句都必须以关键字开始(
var,func等等),因此:=结构不能在函数外使用。
func main() {
var a, b, c int = 1, 2, 3
c, python, java := "no", "no", "no"
}
在声明一个变量而不指定其类型时(即使用不带类型的 := 语法或 var = 表达式语法),变量的类型由右值推导得出。
v := 42
fmt.Printf("v is of type %T\n", v) //输出v is of type int
常量
常量的声明与变量类似,只不过是把var关键字改成 const 关键字。
常量可以是字符、字符串、布尔值或数值。
常量不能用 := 语法声明。
const s string = "constant"
const World = "world"
const h = 50000
const i = 3e18 / h
循环结构
Go 只有一种循环结构:for 循环。
for { //死循环
}
for i := 1; i < 10; i++ {
fmt.Println(i)
}
for i := 1; i < 10; i++ {
if i % 2 == 0 {
continue
}
}
i = 0
for i <= 3 { //while循环的写法
fmt.Println(i)
i = i + 1
}
注意:和 C、Java、JavaScript 之类的语言不同,Go 的 for 语句后面的三个构成部分外没有小括号, 大括号 { } 则是必须的。
if-else
Go 的 if 语句与 for 循环类似,表达式外无需小括号 ( ) ,而大括号 { } 则是必须的。
同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。
该语句声明的变量作用域仅在 if 之内。
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")
}
switch
switch 是编写一连串 if - else 语句的简便方法。它运行第一个值等于条件表达式的 case 语句。
Go 的 switch 语句类似于 C、C++、Java、JavaScript 和 PHP 中的,不过 Go 只运行选定的 case,而非之后所有的 case。 实际上,Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束,否则分支会自动终止。 Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3, 4:
fmt.Println("three or four")
default:
fmt.Println("other")
}
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("morning")
default:
fmt.Println("afternoon")
}
defer栈
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
运行结果:
counting
done
9
8
7
6
5
4
3
2
1
0
指针
类型 *T 是指向 T 类型值的指针。其零值为 nil。
var p *int
& 操作符会生成一个指向其操作数的指针。
i := 42
p = &i
* 操作符表示指针指向的底层值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i
这也就是通常所说的 “间接引用” 或 “重定向”。
与 C 不同,Go 没有指针运算。也就是说,指针的一个主要用途就是对于传入参数进行修改。
func main() {
i, j := 42, 2701
p := &i // 指向 i
fmt.Println(*p) // 通过指针读取 i 的值
*p = 21 // 通过指针设置 i 的值
fmt.Println(i) // 查看 i 的值
p = &j // 指向 j
*p = *p / 37 // 通过指针对 j 进行除法运算
fmt.Println(j) // 查看 j 的值
}
结构体
type Vertex struct {
X, Y int
}
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X)
}
创建结构体:
var (
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)
切片
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
类型 []T 表示一个元素类型为 T 的切片。
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
a[low : high]
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
以下表达式创建了一个切片,它包含 a 中下标从 1 到 3 的元素:
a[1:4]
实例:
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
}
切片就像数组的引用
切片并不存储任何数据,它只是描述了底层数组中的一段。更改切片的元素会修改其底层数组中对应的元素。与它共享底层数组的切片都会观测到这些修改。
切片的零值是 nil。nil 切片的长度和容量为 0 且没有底层数组。
用make创建切片
切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。
make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
a := make([]int, 5) // len(a)=5
要指定它的容量,需向 make 传入第三个参数:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
示例:
func main() {
a := make([]int, 5)
printSlice("a", a) //a len=5 cap=5 [0 0 0 0 0]
b := make([]int, 0, 5)
printSlice("b", b) //b len=0 cap=5 []
c := b[:2]
printSlice("c", c) //c len=2 cap=5 [0 0]
d := c[2:5]
printSlice("d", d) //d len=3 cap=3 [0 0 0]
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
向切片追加元素
为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。
func main() {
var s []int
printSlice(s)
// 添加一个空切片
s = append(s, 0)
printSlice(s)
// 这个切片会按需增长
s = append(s, 1)
printSlice(s)
// 可以一次性添加多个元素
s = append(s, 2, 3, 4)
printSlice(s)
}
Range
for 循环的 range 形式可遍历切片或映射。
当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
func main() {
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index: ", i)
}
}
fmt.Println(sum)
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v)
}
}
可以将下标或值赋予 _ 来忽略它。
for i, _ := range pow
for _, value := range pow
若你只需要索引,忽略第二个变量即可。
for i := range pow
映射
映射将键映射到值。
映射的零值为 nil 。nil 映射既没有键,也不能添加键。
make 函数会返回给定类型的映射,并将其初始化备用。
func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, -74.39967,
}
fmt.Println(m["Bell Labs"])
}
在映射 m 中插入或修改元素:
m[key] = elem
获取元素:
elem = m[key]
删除元素:
delete(m, key)
通过双赋值检测某个键是否存在:
elem, ok = m[key]
若 key 在 m 中,ok 为 true ;否则,ok 为 false。
若 key 不在映射中,那么 elem 是该映射元素类型的零值。
错误处理
不同于异常,个人觉得go中的错误处理更加通俗易懂,和ifelse没什么两样。
在函数里面,我们可以在哪个函数的返回值类型里面,后面加一个error,就代表这个函数可能会返回错误。
那么在函数实现的时候,需要同时return两个值,要么就是如果出现错误的话,可以return nil和一个error。如果没有的话,就返回原本的结果和nil。
package main
import "fmt"
import "errors"
type user struct {
name string
password string
}
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found")
}
func main() {
u, err := findUser([]user{{"wang", "1234"}}, "wag")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name)
}
JSON处理
go语言里面的JSON操作非常简单,对于一个已有的结构体,只要保证每个字段的第一个字母是大写。那么这个结构体就能用JSON.marshaler去序列化变成一个JSON的字符串。
序列化之后的字符串也能够用JSON.unmarshaler去反序列化到一个空的变量里面。
package main
import (
"encoding/json"
"fmt"
)
type user struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := user{Name: "wang", Age: 18, Hobby: []string{"c", "python", "java"}}
buf, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(string(buf)) //{JSON字符串}
var b user
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Println("%#v\n", b) // {wang 18 [c python java]}
}
数字与字符串转化
关于字符串和数字类型之间的转换都在strconv这个包下。
用parseInt或者parseFloat来解析一个字符串,用Atoi把一个十进制字符串转成数字,用itoA把数字转成字符串。
如果输入不合法,这些函数都会返回error。
package main
import (
"fmt"
"strconv"
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) //1.234
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) //111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) //4096
n, _ = strconv.ParseInt("1000", 2, 64)
fmt.Println(n) //8
n2, _ := strconv.Atoi("123")
fmt.Println(n2) //123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) //0 strconv.Atoi: parsing "AAA": invalid syntax
}