GO学习
[TOC]
1-Go语言基础语法
安装GO
安装VS Code 插件:Go
1.1 介绍
1.1.1什么是GO
特点:
-
高性能、高并发
-
语法简单、学习曲线平缓
语法与C++类似但更简洁,无括号,循环只有for
一周即可学习到开发
-
丰富的标准库
不需要借助第三方库
-
完善的工具链
编译、代码格式化、包管理、完整单元测试框架。。
-
静态链接
部署方便快捷
-
快速编译
-
跨平台
路由器、树莓派
-
垃圾回收
无需考虑内存分配释放
1.1.2 使用GO的公司
字节、腾讯、美团、滴滴、百度、Google。。。
1.1.3 字节why go
C++不适合在线Web、最初python
1.2 配置环境
1.3 基础语法
1.3.1 fmt.Println
package main //是main包的一部分,程序的入口包/入口文件
import (
"fmt" //导入包,fmt包用以屏幕输入输出字符串、格式化
)
func main(){
fmt.Println("hello world!")
}
运行:
go run example/01-hello/main.go——输出hello world
go build example/01-hello/main.go 编译为二进制,后./main运行
访问不了github的解决办法 或挂梯子
文档:[fmt.Println文档](fmt package - fmt - Go Packages)
1.3.2 变量
字符串、整数、浮点型、布尔型
符号的使用和优先级与C、C++类似
变量的声明
法一:var name = value 自动推出变量类型;也可以显式给出var b,c int = 1,2
法二:name := value
常量:把var 改成const ,常量没有固定类型,根据上下文自动确定类型
示例:
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
var e float64 // e=0
f := float32(e) //f=0
g := a + "foo"
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialfoo
const s string = "constant"
const h = 500000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
1.3.3 if-else
与C、C++类似,但条件判断中没有括号,条件判断后面必须直接加花括号
- 注意if-else if -else的写法:}和else要在一行
package main
import "fmt"
func main() {
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")
}
}
/*输出
7 is odd
8 is divisible by 4
9 has 1 digit
*/
1.3.4 循环for
没有while、do-while循环
只有for循环,可以使用continue继续循环,break跳出循环
package main
import "fmt"
func main() {
//没有while 没有do-while
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
}
}
/*输出
loop
7
8
1
3
1
2
3
*/
1.3.5 switch分支结构
switch 后的变量名不需要括号
区别:
C++中的case不写break会继续运行完所有的分支;go中默认不需要加break
go中更强大,变量可以是任意的,可以是 字符串、结构体等;
也可以取代任意的if-else语句:switch中不加变量,直接在case中写判断语句
package main
import (
"fmt"
"time"
)
func main() {
a := 2
switch a { //不加括号
case 1:
fmt.Println("one") //不加break
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()
//代替if-else语句
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}
/*输出
two
It's after noon
*/
1.3.6 数组
利用索引进行读取、写入
不常用数组——固定长度,更常用切片
package main
import "fmt"
func main() {
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)
}
/*输出
get: 0
len: 5
[1 2 3 4 5]
2d: [[0 1 2] [1 2 3]]
*/
1.3.7 切片slice
可变长度的数组;任意时刻可以更改长度
创建:make
读取/写入:与数组相同
追加:append,必须将结果赋值给原数组
复制:copy(c,s)
切片:s[2:5]、s[2:]、s[:5]
package main
import "fmt"
func main() {
s := make([]string, 3) //make创建空
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") //append追加
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s))
copy(c, s) //copy复制
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"} //不用make创建切片
fmt.Println(good) // [g o o d]
}
1.3.8 map
其他语言叫做哈希/字典
实际中使用最频繁的数据结构
go中map完全无序,遍历时不按字母顺序也不用插入顺序输出,而是随机
创建:make(map[key的类型]value的类型),创建空的map
删除:delete(map变量名,key的名称)
读取:有两个返回值可接第二个变量ok ——看map中到底是否有这个值存在
package main
import "fmt"
func main() {
m := make(map[string]int) //创建空的map
m["one"] = 1 //赋值map
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"] //这里的r是必须的,否则ok值为0不为false
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(m, m2, m3)
}
1.3.9 range
对于slice和map可以用range快速遍历
对于数组:返回两个值:索引 和 对应位置的值 ;不需要索引的话可以用下划线去忽略
对于map:第一个值是key,第二个值是value
package main
import "fmt"
func main() {
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
//索引用下划线忽略
for _, num := range nums {
fmt.Println("num:", num) // num: 2 num: 3 num: 4
}
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
}
}
1.3.10 函数
变量类型后置
多个返回值:第一个-返回结构;第二个-错误信息
package main
import "fmt"
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
}
1.3.11 指针
相对于C、C++,用法有效
用途:对传入的参数进行修改
package main
import "fmt"
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n) //需要加取址符
fmt.Println(n) // 7
}
1.3.12 结构体
带类型的字段的集合
初始化:使用结构体名称,需要传入初始值,可以只初始化一部分
读取/写入:结构体名称.字段 的方式
作为参数:可以用指针,调用时仍是.字段名的方式
package main
import "fmt"
type user struct {
name string
password string
}
func main() {
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"}
c := user{name: "wang"}
c.password = "1024"
var d user
d.name = "wang"
d.password = "1024"
fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false //指针用法
}
func checkPassword(u user, password string) bool {
return u.password == password
}
func checkPassword2(u *user, password string) bool { //指针用法,不用箭头
return u.password == password
}
1.3.13 结构体方法
普通函数->类成员函数
实现方法:带指针,不带指针
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
}
1.3.14 错误处理
使用单独的返回值 来传递错误信息:类型为error
能清晰的知道哪个函数传递了错误
可以使用简单的if-else去处理错误
调用时:接收需要写两个变量
需要引入error包
package main
import (
"errors" //需要引入error包
"fmt"
)
type user struct {
name string
password string
}
func findUser(users []user, name string) (v *user, err error) { //error类型返回值
for _, u := range users {
if u.name == name {
return &u, nil //nil是空
}
}
return nil, errors.New("not found")
}
func main() {
u, err := findUser([]user{{"wang", "1024"}}, "wang")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(u.name) // wang
//用if-else对错误进行处理
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
1.3.15 字符串操作
标准库strings包
Contains()一个字符串是否包含另一个字符串
Count()字符串计数
Index()查找某个字符串位置
Join()连接多个字符串
Repeat()重复
len内置函数,获取字符串长度
package main
import (
"fmt"
"strings"
)
func main() {
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true
fmt.Println(strings.Count(a, "l")) // 2
fmt.Println(strings.HasPrefix(a, "he")) // true
fmt.Println(strings.HasSuffix(a, "llo")) // true
fmt.Println(strings.Index(a, "ll")) // 2
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
fmt.Println(strings.Repeat(a, 2)) // hellohello
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo
fmt.Println(strings.Split("a-b-c", "-")) // [a b c]
fmt.Println(strings.ToLower(a)) // hello
fmt.Println(strings.ToUpper(a)) // HELLO
fmt.Println(len(a)) // 5
b := "你好"
fmt.Println(len(b)) // 6
}
1.3.16 字符串格式化
fmt.Println 打印多个变量并且换行
fmt.Printf %v,打印任意类型的变量,
%+v,打印更详细的结构,
%#v,更详细
%.2f,保留两位小数的浮点数
package main
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
fmt.Printf("s=%v\n", s) // s=hello
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2}
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2}
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14
}
1.3.17 JSON操作
保证结构体每个字段的第一个字母是大写
json.Marshal:将 Go 语言中的数据结构转换为 JSON 格式的字节切片
json.MarshalIndent:
json.Unmarshal:将JSON数据解析为Go数据结构的函数。它接受一个包含JSON数据的字节切片和一个指向Go数据结构的指针作为参数,并尝试将JSON数据解析为相应的Go数据结构。如果解析成功,解析后的数据将存储在指针所指向的变量中。
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a) //将 Go 语言中的数据结构转换为 JSON 格式的字节切片
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97...] 字节切片
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
var b userInfo
err = json.Unmarshal(buf, &b)
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
/*输出
[123 34 78 97 109 101 34 58 34 119 97 110 103 34 44 34 97 103 101 34 58 49 56 44 34 72 111 98 98 121 34 58 91 34 71 111 108 97 110 103 34 44 34 84 121 112 101 83 99 114 105 112 116 34 93 125]
{"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
{
"Name": "wang",
"age": 18,
"Hobby": [
"Golang",
"TypeScript"
]
}
main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
*/
1.3.18 时间处理
time包
time.Now()
time.Date()
t.Year() t.Day() t.Hour() t.Minute t.Format()
t2.Sub(t)对两个时间做减法,得到一个时间段
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
fmt.Println(t.Format("2006-01-02 15:04:05")) //使用‘特定’的时间进行格式化 // 2022-03-27 01:25:36
diff := t2.Sub(t) //对两个时间做减法,得到一个时间段
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900 这个时间段转换成分钟、秒
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // true
fmt.Println(now.Unix()) // 获取时间戳:1648738080
}
1.3.19 数字解析
字符串和数字之间的转换
strconv包
strconv.ParseFloat
strconv.ParseInt
strconv.Atoi
strconvItoa
package main
import (
"fmt"
"strconv"
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
n, _ := strconv.ParseInt("111", 10, 64) //第一个参数是字符串;第二个参数表示‘进制’;第三个参数64表示返回的是64位精度的整数
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64) //0的话表示自动推断进制
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123") //快速将十进制字符串转换成数字;Itoa:数字转为字符串
fmt.Println(n2) // 123
n3 := strconv.Itoa(666) //Itoa:数字转为字符串 只有一个返回值
fmt.Println(n3) // 666
n2, err := strconv.Atoi("AAA") //不合法的参数
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
1.3.20 进程信息
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
// go run example/20-env/main.go a b c d 直接执行go的源文件
fmt.Println(os.Args) // 长度是5,第一个成员表示二进制自身的路径;[/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
fmt.Println(os.Getenv("PATH")) // /usr/local/go/bin... 获得环境变量
fmt.Println(os.Setenv("AA", "BB")) //写入环境变量
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
//快速启动子进程并且获取其输入输出
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
}