Golang 基础语法 | 青训营笔记

196 阅读5分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

Golang 基础语法

Go 语言的特点

  • 高性能、高并发
  • 语法简单、学习曲线平缓
  • 丰富的标准库
  • 完善的工具链
  • 静态链接
  • 快速编译
  • 跨平台
  • 垃圾回收

Golang 的安装

Golang 的安装与使用 | Wen Flower 学习笔记 (twtool.icu))

基础语法

编写简单的 Hello World 代码

package main

import "fmt"

func main() {
	// Go 语言内置了 println 函数, 但是使用的标准错误输出, 同时会有输出顺序和预期不一致的问题
	// println("Hello World!")
	// 通过 fmt 标准库可以将内容输出到标准输出流
	fmt.Println("Hello World!")
}

变量

Go 语言变量声明的语法为: var <name> <type>

也可以在声明变量的同时对变量赋值语法为: var <name> [type] = <value> 或者 <name> := <value>

  • <> : 必须
  • [] : 可选, 因为第二种方式对变量进行了赋值, Go 语言能推断出变量的类型
  • name : 为变量名
  • type : 变量类型
  • value : 要赋值给变量的值

代码:

package main

import "fmt"

func main() {
	var a int
	a = 1
	var b int = 2
	var c = 3
	d := 4
	var e, f int
	e, f = 5, 6
	g, h := 7, 8
	fmt.Println("a = ", a, ", b = ", b, ", c = ", c, ", d = ", d, ", e = ", e, ", f = ", f, ", g = ", g, ", h = ", h)
}

常量

Go 语言常量声明和变量声明语法类似, 但是必须在声明初始化:

const <name>[, <name>]... [type] = <value>[, <value>]...

  • <> : 必须
  • [] : 可选, 可选, 对常量进行了赋值, Go 语言能推断出常量的类型
  • name : 为常量名
  • type : 常量类型
  • value : 要赋值给常量的值, 且只能是布尔型、数字型(整数型、浮点型和复数)和字符串型

代码:

package main

import "fmt"

func main() {
	const a = 1
	const b, c = 2, 3

	fmt.Println("a = ", a, ", b = ", b, ", c = ", c)

	// 通过 iota 语法声明自增的常量
	const (
		d = 4 + iota 	// 4
		e				// 5
	)

	fmt.Println("d = ", d, ", e = ", e)

	// 通过 iota 声明常量
	const (
		f = 1 << iota // 1
		g = 1 << iota // 2
		h = 1 << iota // 4
	)

	fmt.Println("f = ", f, ", g = ", g, ", h = ", h)
}

分支语句

Go 语言的 if-else 语法为:

if [变量声明与赋值表达式;] <布尔表达式> {
	
} else if [变量声明与赋值表达式;]<布尔表达式>{

} else {

}

else if 和 else 是可选的, Go 的 if 不需要小括号(), 但是不能省略花括号 {}

Go 语言的 switch 语法为:

switch [value] { // 一个值, 允许不传
case <condition>: // condition 既可以是一个值, 也可以是一个条件表达式
    // 操作
default:
    // 没有 case 匹配到的操做
}

特别的, Go 语言在 case 后默认不会继续执行其它 case, 需要在 case 中加上 fallthrough 关键字

代码:

package main

import "fmt"

func main() {
	if score := 80; score >= 90 {
		fmt.Println("优秀")
	} else if score >= 60 {
		fmt.Println("及格") // 将输出及格
	} else {
		fmt.Println("不及格")
	}

	// score 在这里不可以访问到

	i := 2
	switch i {
	case 1:
		fmt.Println("1")
	case 2:
		fmt.Println("2")
	default:
		fmt.Println("i 不是1也不是2")
	}
}

循环结构

Go 语言只有 for 一种循环关键字, 但是能用 for 做到其它语言的 fori、while 等循环方式

代码:

package main

import "fmt"

func main() {
	// 其他语言的 while 循环
	i := 0
	for i < 5 {
		fmt.Println("i = ", i)
	}

	// 经典 for 循环
	for j := 0; j < 5; j++ {
		fmt.Println("j = ", j)
	}

	// loop 循环
	for {
		fmt.Println("Loop, 不会退出循环, 需要使用 break, 才能退出")
	}
}

数组 & 切片

数组

Golang 的数组和其它语言类似,都是定长不能扩容,访问不能超出索引的范围

数组的创建

数组和变量的定义方式类似:var <name> <[<length>]type>

也可以在声明数组的同时对数组赋值,语法为: <name> := [<length>]<type>{[<value>[, <value>]...]}

  • <> : 必须
  • [] : 可选
  • name : 为数组名
  • type : 数组类型
  • value : 要赋值给数组对应位置的值
  • length : 数组的长度,第二种方式可以是(...)根据后面的值确定长度
package main

import "fmt"

func main() {
	var a1 [5]int
	a1[2] = 2
	fmt.Printf("%v", a1) // [0 0 2 0 0]

	// 在编译阶段就会被发现
	// a1[5] = 5 // invalid argument: index 6 out of bounds [0:5]

	// 下面的代码会在运行时产生 panic
	// var i = 1
	// for {
	//     i++
	//     a1[i] = i // panic runtime error: index out of range [5] with length 5
	//     if i == 5 {
	//         break
	//     }
	// }

	fmt.Printf("数组 a1 的长度是:%v", len(a1)) // 通过 len 函数能获取数组的长度
	fmt.Printf("数组 a1 的容量是:%v", cap(a1)) // 通过 cap 函数能获取数组的容量,因为数组是定长的,所以容量和长度是相同的

	// 使用第二种方式创建数组的几种特殊写法
	a2 := [5]int{1, 2} // [1, 2, 0, 0, 0]
	a3 := [..]int{1, 2, 3} // [1, 2, 3], len(a3) == 3

	a4 := [5]int{1:1, 3:3} // [0, 1, 0, 3, 4]
	a5 := [...]int{1:1, 5:5} // [0, 1, 0, 0, 0, 5], len(a5) == 6
}

多维数组

Golang 和其它语言一样个支持数组的类型还是一个数组,达到多维数组的效果

package main

import "fmt"

func main() {
	// 这里以二维做示例,最前面的中括号为最外层
	arr := [2][3]int{
		[3]int{1, 2, 3},
		[3]int{4, 5, 6}, // 逗号必须有
	}

	// 使用 for range 遍历,其中 value 可以省略
	for _, a := range arr {
		for _, value := range a {
			fmt.Printf("%v, ", value)
		}
		fmt.Println()
	}
}

切片

Golang 切片类似 Java 语言的 List,会在容量不够时,自动进行扩容,底层实现为数组,切片为引用类型,直接获取切片的地址为数组的地址

切片的创建

切片能够直接声明,使用也能够通过,make 函数创建有初始大小和容量的切片,通过 new 函数创建的切片不能直接使用

代码:

package main

import "fmt"

func main() {
	// 切片和数组类似,但是不需要中括号的长度,
	// 声明一个空切片,可以直接使用
	var slice []int

	slice = append(slice, 1)

	fmt.Println(slice[0]) // 1
	// 切片和数组一样访问不能超出索引的范围,否则会产生:
	// panic: runtime error: index out of range [1] with length 1
	// fmt.Println(slice[1])

	// 使用 make 创建切片
	// 第一个参数(必须):切片的类型
	// 第二个参数(必须):切片的初始大小,填充默认值
	// 第三个参数(可选):切片的容量
	makeSlice := make([]int, 5, 10)
	fmt.Printf("%v \n", makeSlice) // [0 0 0 0 0] 
}

追加元素

Golang 提供了 append 函数在切片的末尾添加元素,就和创建中使用的方式一样

代码:

package main

import "fmt"

func main() {
	slice := make([]int, 0, 5)

	slice = append(slice, 1)

	fmt.Println(slice[0]) // 1

	fmt.Printf("%p, cap = %v\n", slice, cap(slice)) // 输出切片引用的数组的地址

	slice = append(slice, make([]int, 4)...)
	fmt.Printf("%p, cap = %v\n", slice, cap(slice)) // 上一步追加操作,切片的容量还足够,地址不会改变

	slice = append(slice, make([]int, 4)...)
	fmt.Printf("%p, cap = %v\n", slice, cap(slice)) // 上一步操作,切片的容量不够了,则会改变切片引用的数组的地址,并将容量扩容到原来的两倍

	fmt.Printf("%v \n", slice) // [1, 0, 0, 0, 0, 0, 0, 0, 0] 
}

删除元素

Golang 提供了没有提供删除函数,但是可以通过 [a:b] 无法做到删除效果,配合 append 方法能不改变切片应用的内存地址

代码:

package main

import "fmt"

func main() {
	slice := []int{1,2,3,4}
	fmt.Printf("p = %p, len = %v, cap = %v\n", slice, len(slice), cap(slice))

	slice = slice[1:3] // 删除头尾元素
	fmt.Printf("p = %p, len = %v, cap = %v\n", slice, len(slice), cap(slice))

	// 使用append不会改变当前切片数组的内存地址
	slice = append(slice[:0], slice[1:]...) // 删除第一个个元素
	fmt.Printf("p =%p, len = %v, cap = %v\n", slice, len(slice), cap(slice))
}

复制切片

Golang 提供了 copy(target, source) 函数对切片进行深拷贝

代码:

package main

import "fmt"

func main() {
	slice1 := []int{1, 2, 3, 4} // [1, 2, 3, 4]
	slice2 := slice1[:] // [1, 2, 3, 4] 引用的数组地址和 slice1 相同
	slice3 := make([]int, 4) // [0, 0, 0, 0]
	copy(slice3, slice1) // slice3 为 [1, 2, 3, 4] 引用的数组地址和 slice1 不同

	fmt.Printf("slice1 = %v, p = %p\n", slice1, slice1)
	fmt.Printf("slice2 = %v, p = %p\n", slice2, slice2)
	fmt.Printf("slice3 = %v, p = %p\n", slice3, slice3)
}

map

创建与使用

Golang 提供了通过 make 函数创建空 map 的方法,同时也能使用 map[keyType][valueType]{} 语法创建 map

代码:

package main

import "fmt"

func main() {
	// 创建空 map,中括号中为 key 类型,后面的为 value 类型
	m := make(map[string]int)

	// 为 key 赋值
	m["wen"] = 21
	m["dian"] = 20

	fmt.Println(m) // map[dian:20 wen:21] 无序

	fmt.Println(len(m)) // 2

	fmt.Println(m["wen"]) // 21

	fmt.Println(m["tang"]) // 0

	value1, ok1 := m["dian"]
	fmt.Println(value1, ok1) // 20 true

	value2, ok2 := m["tang"]
	fmt.Println(value2, ok2) // 0 false

	delete(m, "dian") // 删除 key
	value3, ok3 := m["dian"]
	fmt.Println(value3, ok3) // 0 false

	m2 := map[string]int{"wen": 21, "dian": 20}
	fmt.Println(m2)
}

遍历 map 元素

package main

import "fmt"

func main() {
	m := map[string]int{"wen": 21, "dian": 20}

	// 使用 for range 遍历
	for key, value := range m {
		fmt.Printf("key = %v, value = %v\n", key, value)
	}

	// 使用 for range 遍历
	for key := range m {
		fmt.Printf("key = %v, value = %v\n", key, m[key])
	}
}

函数

Golang 的函数类似 Kotlin 的语法,参数的类型写在后面,函数的返回值写在函数小括号() 和 花括号{} 中间,同时 Golang 还支持多函数返回值

特别的 Golang 的 error 也是值,由 Golang 创始人提出: Errors are values,可以直接通过 if-else 处理,也可以直接忽略

定义 & 使用 代码:

package main

import (
	"errors"
	"fmt"
)

func _div(a int, b int) int {
	return a / b
}

func div(a int, b int) (v int, err error) {
	if b == 0 {
		return 0, errors.New("除数不能为 0")
	}
	return _div(a, b), nil
}

func main() {
	value, _ := div(3, 2) // 这里忽略错误
	fmt.Printf("%d / %d = %d", 3, 2, value) // 3 / 2 = 1
}

值传递 & 引用传递

Go 语言数据类型的分类

按数据类型分类

  • 基本类型:数值类型stringbool
  • 复合类型:structarrayslicemappointerchanfunction

按数据特点分类

  • 值类型:数值类型stringboolstructarray
  • 引用类型:slicemappointerchanfunction

数值类型: byteruncintuintint8int16int32int64uint8uint16uint32uint64float32float64

值传递

值类型传递的是数值本身,不是内存地址,将数据拷贝一份传给变量或者函数参数,对副本的修改不影响源数据

引用传递

引用类型传递的是内存地址,同一个地址可以被多个变量引用,对一个变量进行修改则其它应用同一个地址的变量的值也会一起改变

指针

指向一个已知的地址,因为指针是引用类型,所以可以为 nil

指针的已主要参数的对参数的参数进行修改同时让源值也修改

代码:

package main

import "fmt"

func inc(i *int) {
	*i++ // 这里需要在前面加上 *
}

func main() {
	i := 0
	inc(&i)
	fmt.Println(i) // 1
	inc(&i)
	fmt.Println(i) // 2
}

结构体

定义结构体

type <name> struct {
	[<field> <type>]...
}

// 例子
type user struct {
	name 	string
	age 	int
}

使用结构体

Golang 不管是结构体指针还是结构体值,都是直接通过 . 访问结构体的字段,而不需要使用 *

代码:

package main

import "fmt"

type user struct {
	name string
	age  int
}

func main() {
	u := user{name: "wen", age: 21}
	fmt.Printf("name = %v, age = %v\n", u.name, u.age) // name = wen, age = 21
	fmt.Printf("u = %v\n", u)  // u = {wen 21}
	fmt.Printf("u = %+v\n", u) // u = {name:wen age:21}
	fmt.Printf("u = %#v\n", u) // u = main.user{name:"wen" age:21}
}

结构体方法

虽然 Golang 是面向过程的语言,但是提供了在方法名前面通过 (param type) 语法实现结构体方法,type 既可以是结构体也可以是结构体指针,可以理解为将函数的第一个参数前置

代码:

package main

import "fmt"

type user struct {
	name string
	age  int
}

func (u user) String() string {
	return fmt.Sprintf("name = %v, age = %v", u.name, u.age)
}

func (u *user) ageInc() {
	u.age++
}

func main() {
	u := user{name: "wen", age: 21}
	fmt.Println(u.String()) // name = wen, age = 21
	u.ageInc()
	fmt.Println(u.String()) // name = wen, age = 22
}

字符串

Golang 提供了 strings 标准库来对字符串进行各种操作

判断子串是否存在

str := "Hello World!"
fmt.Println(strings.Contains(str, "Hello")) // true
fmt.Println(strings.Contains(str, "Golang")) // false

判断子串出现次数

str := "Hello Golang,啊啊啊!!!"
fmt.Println(strings.Count(str, "l")) // 3
fmt.Println(strings.Count(str, "ll")) // 1
fmt.Println(strings.Count(str, "啊")) // 3

判断前后缀是否存在

str := "Hello Golang!!!"
fmt.Println(strings.HasPrefix(str, "Hello")) // true
fmt.Println(strings.HasSuffix(str, "Golang")) // false

查询子串第一次出现的索引

str := "Hello Golang!!!"
fmt.Println(strings.Index(str, "Hello")) // 0
fmt.Println(strings.Index(str, "Golang")) // 6
fmt.Println(strings.Index(str, "World")) // -1
str = "Hello 世界!!!"
fmt.Println(strings.Index(str, "世")) // 6
fmt.Println(strings.Index(str, "界")) // 9 中文在 UTF-8中占 3个字节
fmt.Println(strings.Index(str, "世界")) // 6

获取字符串长度

str = "Hello 世界!!!"
fmt.Println(len(str)) // 21 中文在 UTF-8 中占 3个字节
fmt.Println(len([]rune(str))) // 11 

Json 处理

Golang 提供了 encoding/json 标准库序列和反序列 Json 字符串

代码:

package main

import (
	"encoding/json"
	"fmt"
)

type user struct {
	Name string `json:"name"` // 通过这个方式可以将大写的 Name 映射为小写的 name
	Age  int    `json:"age"`
}

func main() {
	u := user{Name: "wen", Age: 21}

	buf, err := json.Marshal(u)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(buf)) // {"name": "wen", "age": 21}

	var u2 user
	err = json.Unmarshal(buf, &u2)
	if err != nil {
		panic(err)
	}
	fmt.Println(u2) // {wen 21}
}

时间处理

Golang 提供了 time 标准库对时间进行处理

代码:

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Now() // 获取当前时间

	// 自定义输出格式,默认输出会比较长,还会加上时区
	// Golang 将在 1.20 为这个固定的 2006-01-02 15:04:05 添加常量 tiem.DateTime
	fmt.Println(t.Format("2006-01-02 15:04:05.999")) 
	
	// 获取对应的年月日时分秒
	fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second())

	// 解析时间
	pt, _ := time.Parse("2006-01-02 15:04:05", "2023-01-15 22:05:35")
	fmt.Println(pt.Format("2006-01-02 15:04:05")) 

	// 比较时间
	fmt.Println(t.After(pt)) // true
	fmt.Println(t.Before(pt)) // false

	// 获取时间戳
	fmt.Println(t.Unix())
}

引用参考

Golang 基础语法 | Wen Flower 学习笔记 (twtool.icu)

漫画 Go 语言 纯手绘版 - haojiahuo - 掘金小册 (juejin.cn)