这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
Go语言基础
1.简介
1.1 什么是Go语言
- 高性能 高并发
- 语法简单 编写方便 学习路线平缓(相较于C,C++)
- 丰富且高性能的标准库
- 完善的工具链
- 静态链接
- 快速编译
- 跨平台
- 高效的垃圾回收
1.2 有哪些公司在使用Go语言
- 腾讯,主要在新业务上尝试使用Go
- 百度,主要在运维方面使用到Go语言
- 七牛云,是国内第一家选Go做服务端的公司
- 京东,京东商城的列表页等是使用Go语言开发的
- 小米,小米商城等团队在使用Go语言
2.Go入门
2.1 环境安装
2.2 Hello World
package main
// 导入fmt包,fmt包中包含关于控制台输入输出相关方法
import "fmt"
func main() {
// 控制台打印Hello World!
fmt.Println("Hello World!")
}
2.3 var变量的使用
go语言的变量相较于传统的C,C++,Java,Python语言有多种不同
- 采用:=进行赋值操作 var g := 1
- 可以多个变量同时赋值 var b, c int = 1, 2
- 采取类型后置的方式
- 另外go语言中的float类型 有两种一种是float32占用四个字节(类似其他语言中的float),还有一种float64占用8个字节(double)
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial"
var b, c int = 1, 2
var d = true
//类似java中的double,都占用八个字节
var e float64
//类似java中的float,都占用四个字节
f := float32(e)
g := a + " " + "donkey"
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initial donkey
const s string = "constant"
const h = 50000000
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
2.4 for循环
go中的for循环的特点是:
- 若for后没有跟条件那么是死循环
- for循环后没有小括号
- continue与break与其他语言用法一致
package main
import "fmt"
func main() {
var i int = 1
// 死循环
for {
fmt.Println("dead loop")
break
}
for j := 0; j < 9; j++ {
fmt.Println("j = ", j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println("n = ", n)
}
for i <= 3 {
fmt.Println("i = ", i)
i++;
}
}
2.5 if 判断
go中的for循环的特点是:
- if后没有小括号
- if中可以先执行语句再进行判断
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 := 10; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has many digits")
}
}
2.6 switch 条件分支
go中的for循环的特点是:
- Go语言中条件每个条件分支中可以不用写break,不必担心处于上游的分支执行完误执行了下面分支的逻辑
- switch语句中的变量可以是任何类型,在每个case后面也可以跟多个值,用逗号隔开,扩大了每个分支的判断范围
- Go中的switch语句也支持在条件判断语句前执行简单的语句,这样就可以在一个switch语句中处理多个条件
package main
import (
"fmt"
"time"
)
func main() {
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")
}
}
2.7 array数组
数组的使用与其他语言中的使用无太大的差异,不在这里赘述了,仅需看下如何简单使用既可
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)
}
2.8 slice切片
2.8.1 slice切片
- Go语言中的切片有点类似于列表
- Go数组的长度不可改变,在特定场景中这样的集合就不太适用,Go中提供了一种灵活,功能强悍的内置类型切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大。
- 通过数组的切片完成的数据的浅拷贝
2.8.2 浅拷贝与深拷贝
2.8.1. Go语言中的深拷贝
拷贝的是数据本身,创造一个样的新对象,新创建的对象与原对象不共享内存,新创建的对象在内存中开辟一个新的内存地址,新对象值修改时不会影响原对象值。既然内存地址不同,释放内存地址时,可分别释放。
值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool。
2.8.2. Go语言中的浅拷贝
拷贝的是数据地址,只复制指向的对象的指针,此时新对象和老对象指向的内存地址是一样的,新对象值修改时老对象也会变化。释放内存地址时,同时释放内存地址。
引用类型的数据,默认全部都是浅复制,Slice,Map。
2.8.3.本质区别
是否真正在底层另外分配了一段独立的堆空间用于存储拷贝的对象,如果仅是拷贝一个指向原有存储空间的指针,那么就算浅拷贝,若在底层创建了一个新对象并分配了一段与源对象不同的内存空间,那便是深拷贝。
//创建一个字符串数组
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
// c
fmt.Println("get: ", s[2])
// 3
fmt.Println("len: ", len(s))
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s)
c := make([]string, len(s))
//拷贝字符串数组(浅拷贝)
copy(c, s)
fmt.Println(c)
strings1 := "Hello world"
var strings2 = strings1;
//测试=是深拷贝还是浅拷贝? 深拷贝
// 拓展 值类型的数据,默认全部都是深复制,Array、Int、String、Struct、Float,Bool
strings2 += "!!!"
fmt.Println("string1:" + strings1)
fmt.Println("string2:" + strings2)
// 引用类型的数据,默认全部都是浅复制,Slice,Map。
// s的切片默认都是前闭后开区间
fmt.Println(s[0:3])
fmt.Println(s[:5])
fmt.Println(s[2:])
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
2.9 map散列表
go语言中的map与其他语言中的散列表实现没有什么大的区别,仅是有些方法由于go语言的特性能够返回多个结果
package main
import "fmt"
func main() {
m := make(map[string]int)
// key "one" value 1
m["one"] = 1
m["two"] = 2
//输出map中所有的键值对
fmt.Println(m)
//输出map的键值对个数
fmt.Println(len(m))
//根据键取值 根据值取键通常使用range遍历方式实现,因为跟其他语言一样底层维护的是K底层是Set集合,而V只是普通的数组允许重复
fmt.Println(m["one"])
fmt.Println(m["unknow"])
r, ok := m["unknow"]
fmt.Println(r, ok)
// 删除map m中的键为"one"的K-V对
delete(m, "one")
fmt.Println(m)
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
2.10 range
range与for搭配使用,与Java中的增强for循环类似,作用都是遍历切片,数组,map等可迭代容器中所有的元素,特点有:
- 若遇到下划线,那么下划线的作用是占位符的作用,同时底层编译器可以根据下划线做一定的优化
- 若遍历数组时,形如 for index, num := range nums 那么index表示当前遍历到的索引
- 若遍历map时,形如for k, v := range m 可以同时获取到key value(可以据此找到value-key的对应关系 可能为n对1)
- 若遍历map时,形如for k := range m 可以获取到key
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
sum := 0
//这段代码是在遍历一个名为 "nums" 的切片(或者数组)
//对于每一个遍历到的元素,它会将该元素的值加到 "sum" 变量中
//如果遍历到的元素的值为 4,它会输出 "index: 2 num: 4",其中 i 是该元素在切片中的索引位置
for i, num:= range nums{
sum += num
if num == 4 {
fmt.Println("index:", i, "num:", num)
}
}
// 9
fmt.Println(sum)
m := map[string]string{"a": "A", "b": "B"}
//这段代码是在遍历一个名为 "m" 的map
//对于每一个遍历到的键值对key value分别记录在 k 和 v中
//这段代码也可以实现根据值找到对应的key(可能有多个,需要另外使用容器记录)
for k, v := range m {
fmt.Println(k, v)
}
//这段代码是在遍历一个名为 "m" 的map
//对于每一个遍历到的键值对key记录在 k 中
for k := range m {
fmt.Println("key", k) // key a; key b
}
}
2.11 func 方法
在 Go 中,方法是一组可重用的代码块,可以在程序中多次调用。可以像其他数据类型一样赋值给变量、作为参数和返回值
go中func的特点:
- 可以有多个返回值,这意味着我们可以通过执行一个方法可以得到更多想要的信息,这点特别对错误处理有很大的帮助
- 若返回的结果遇到了错误,那么字符串返回的是空,而err不为nil
- 若返回的结果没有遇到了错误,那么err为nil
import "fmt"
func add(a int, b int) int {
return a + b
}
func add2(a, b int) int {
return a + b
}
//判断map中是否存在k的键如果存在返回它的值,否则返回空字符串 和 false
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)
// A true
v, ok := exists(map[string]string{"a":"A"}, "a")
fmt.Println(v, ok)
// false
v2, ok2 := exists(map[string]string{"b":"B"}, "a")
fmt.Println(v2, ok2)
}
2.12 point指针
go中的指针用法与C,C++中的类似
package main
import "fmt"
func add2(n int) {
n += 2
}
func add2ptr(n *int) {
*n += 2
}
func main() {
n := 5
//传值,被调用的方法内会重新生成一个局部变量进行操作,不会改变主调函数中的变量值
add2(2)
fmt.Println(n)
//传递地址,被调用的方法直接使用地址的值指向的变量进行操作,会改变注掉函数中的变量值
add2ptr(&n)
fmt.Println(n)
}
2.13 struct struct-method
go语言中的结构体使用与其他语言中的结构体,类的使用差不多,但是go语言中的结构体支持多种赋值方式,如下面例子所示
package main
import (
"fmt"
)
type user struct {
name string
password string
}
func main() {
a := user{name: "zhangsan", password: "1024"}
b := user{"lisi", "1025"}
c := user{password: "1026"}
c.name = "wangwu"
var d user
d.name = "zhaoliu"
d.password = "1027"
fmt.Println(a, b, c ,d)
fmt.Println(checkPassword(a, "1024")) // true
fmt.Println(checkPassword2(&b, "1025")) // true
fmt.Println(checkPassword2(&c, "1025")) // false
}
//不使用指针
func checkPassword(u user, password string) bool {
return u.password == password
}
//使用指针
func checkPassword2(u *user, password string) bool {
return u.password == password
}
go中的结构体方法(可能直译有点问题),我个人更偏向把它理解为Java中的成员方法
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{"wangwu", "1024"}
a.resetPassword("2024")
// true
fmt.Println(a.checkPassword("2024"))
}
2.14 error 错误处理
- Go语言中引入 error 接口类型作为错误处理的标准模式,如果函数要返回错误,则返回值类型列表中肯定包含 error。error 处理过程类似于C,java中的错误,可逐层返回,直到被处理。
- error 接口有一个签名为 Error() string 的方法,所有实现该接口的类型都可以当作一个错误类型。Error() 方法给出了错误的描述,在使用 fmt.Println 打印错误时,会在内部调用 Error() string 方法来得到该错误的描述。一般情况下,如果函数需要返回错误,就将 error 作为多个返回值中的最后一个(但这并非是强制要求)。
创建一个 error 最简单的方法就是调用 errors.New 函数,它会根据传入的错误信息返回一个新的 error
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
func findUser(users []user, name string) (v *user, err error) {
//此处下划线的作用是 1.忽略这个变量 2.占位符
//使用下划线可以让编译器更好的优化,同时在有些场合下,必须使用下划线占位否则会报错
for _, u := range users {
if u.name == name {
return &u, nil
}
}
return nil, errors.New("not found")
}
func main() {
u, err := findUser([]user{{"wangwu", "1024"}}, "wangwu")
if err != nil {
fmt.Println(err)
return
}
// wangwu
fmt.Println(u.name)
if u, err := findUser([]user{{"wangwu", "1024"}}, "lisi"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
2.15 string 字符串
Go中的string字符串的常用方法
- strings.HasPrefix(s string, prefix string) bool:判断字符串s是否以prefix开头
- strings.HasSuffix(s string, suffix string) bool:判断字符串s是否以suffix结尾。
- strings.Index(s string, str string) int:判断str在s中首次出现的位置,如果没有出现,则返回-1
- strings.LastIndex(s string, str string) int:判断str在s中最后出现的位置,如果没有出现,则返回-1
- strings.Replace(str string, old string, new string, n int):字符串替换
- strings.Count(str string, substr string)int:字符串计数
- strings.Repeat(str string, count int)string:重复count次str
- strings.ToLower(str string)string:转为小写
- strings.ToUpper(str string)string:转为大写
- strings.TrimSpace(str string):去掉字符串首尾空白字符
- strings.Trim(str string, cut string):去掉字符串首尾cut字符
- strings.TrimLeft(str string, cut string):去掉字符串首cut字符
- strings.TrimRight(str string, cut string):去掉字符串首cut字符
- strings.Field(str string):返回str空格分隔的所有子串的slice
- strings.Split(str string, split string):返回str split分隔的所有子串的slice
- strings.Join(s1 []string, sep string):用sep把s1中的所有元素链接起来
- strings.Itoa(i int):把一个整数i转成字符串
- strings.Atoi(str string)(int, error):把一个字符串转成整数
- len(str string) 获取字符串长度
package main
import (
"fmt"
"strings"
)
func main() {
a := "hello"
// 是否存在"ll"子串 true
fmt.Println(strings.Contains(a, "ll"))
// 统计字符串中"l"子串的个数 2
fmt.Println(strings.Count(a, "l"))
// 判断字符出a是否"he"子串开头的 true
fmt.Println(strings.HasPrefix(a, "he"))
// 判断字符出a是否"llo"子串结尾的 true
fmt.Println(strings.HasSuffix(a, "llo"))
// 查找"ll"子串出现的初始索引 2
fmt.Println(strings.Index(a, "ll"))
// 把字符串拆分为多个子串,并使用sep连接
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo
// 将字符串重复count次 hellohello
fmt.Println(strings.Repeat(a, 2))
// 用 new 替换 s 中的 old,一共替换 n 个。
// 如果 n < 0,则不限制替换次数,即全部替换 hEllo
fmt.Println(strings.Replace(a, "e", "E", -1))
// 将字符串s以sep分割成一个子串,返回值是一个字符串数组 [a b c]
fmt.Println(strings.Split("a-b-c", "-"))
// 全部转换为小写 hello
fmt.Println(strings.ToLower(a))
// 全部转换为大写 HELLO
fmt.Println(strings.ToUpper(a))
// 返回字符串长度
fmt.Println(len(a))
b := "你好"
// 中文字符串根据字符集, 大多数字符集中文编码一个占3个字节 6
fmt.Println(len(b)) // 6
}
2.16 fmt标准输入输入库
Go 中的 fmt 包提供了打印函数将数据以字符串形式输出到控制台、文件、其他满足 io.Writer 接口的至以及其他字符串中
输出
-
Print系列函数会将内容输出到系统的标准输出,区别在于Print函数直接输入内容到终端, -
Printf函数支持格式化输出字符串.- %v 值的默认格式表示
- %+v 类似%v,但输出结构体时会添加字段名
- %#v 类似%+v 但是会打印更加详细的信息
- %T 打印值的类型
- %% 百分号
-
Println函数 自定帮我们添加换行符,输出内容独占一行.他不支持格式化出去 -
Fprint系列函数会将内容输出到一个io.Writer接口类中,我们通常用这个函数往文件中写入内容. -
Errorf函数根据format参数生成格式化字符并返回一个包含该字符串的错误.
package main
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
// hello 123
fmt.Println(s, n)
// {1 2}
fmt.Println(p)
// s=hello
fmt.Printf("s=%v\n", s)
// n=123
fmt.Printf("n=%v\n", n)
// 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}
fmt.Printf("p=%#v\n", p)
f := 3.141592653
// 3.141592653
fmt.Println(f)
// 3.14
fmt.Printf("%.2f\n", f)
}
2.17 json
go中的encoding/json包中的几个重要方法:
- Marshal() 将一个元素或者对象进行序列化,返回序列化后的字节数组和错误信息,为了防止后续debug不出现错误,应对error的值进行处理
- MarshalIndent方法可以对json序列化后的对象做一些格式化处理,同样需要错误处理
- Unmarshal(buf, &b) 对字节数组buf进行反序列化,并把结果存入b中,同样需要错误处理
package main
import (
"encoding/json"
"fmt"
)
type userInfo struct {
Name string
Age int `json:"age"`
Hobby []string
}
func main() {
a := userInfo{"小江", 18, []string{"Golang", "TypeScript"}}
// 序列化
buf, err := json.Marshal(a)
// 序列化错误
if err != nil {
panic(err)
}
// [123 34 78 97...] 为序列化后的字节数组
fmt.Println(buf)
// 将字节数组转换为字符串输出 {"Name":"小江","age":18,"Hobby":["Golang","TypeScript"]}
fmt.Println(string(buf))
// MarshalIndent方法可以对json序列化后的对象做一些格式化处理
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)
}
//main.userInfo{Name:"小江", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
fmt.Printf("%#v\n", b)
}
2.18 time 时间处理
go中的time包可以使用和构造多种与时间相关的操作,并且可以将时间进行格式化,具体使用的时候查阅官方手册即可,以下为常见的方法
package main
import (
"fmt"
"time"
)
func main() {
// 获取当前时间戳
now := time.Now()
fmt.Println(now)
// 根据时间和时区构造时间戳
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC)
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
// 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t)
// 获取年月日(星期几)时分秒 2022 March 27 1 25
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute())
// 根据格式打印时间 2022-03-27 01:25:36
fmt.Println(t.Format("2006-01-02 15:04:05"))
// 获取时间差
diff := t2.Sub(t)
// 1h5m0s
fmt.Println(diff)
// 65 3900
fmt.Println(diff.Minutes(), diff.Seconds())
// 转换格式
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err)
}
// true
fmt.Println(t3 == t)
// 当前的时间戳 1648738080
fmt.Println(now.Unix())
}
2.19 strconv 数字解析
strconv用于将字符串转换为整数或者浮点数,支持各种进制之间的转换,但是若输入非法则会出现错误,以下为常用的方法
package main
import (
"fmt"
"strconv"
)
func main() {
// 将字符串转换为64位精度的浮点数
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
// 将十进制的字符串转换为64位精度的整数
n, _ := strconv.ParseInt("111", 10, 64)
fmt.Println(n) // 111
//字符串 10进制(参数base位置填0则自动推测) 64位进度整数
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
//输入不合法则返回错误
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
2.20 env 环境变量
Go语言的 os 包中提供了操作系统函数的接口,是一个比较重要的包。顾名思义,os 包的作用主要是在服务器上进行系统的基本操作,如文件操作、目录操作、执行命令、信号与中断、进程、系统状态等等。
常用函数
- func Hostname() (name string, err error) 返回内核提供的主机名。
- func Environ() []string 返回所有的环境变量,返回值格式为“key=value”的字符串的切片拷贝。
- func Getenv(key string) string Getenv 函数会检索并返回名为 key 的环境变量的值。如果不存在该环境变量则会返回空字符串。
- func Setenv(key, value string) error Setenv 函数可以设置名为 key 的环境变量,如果出错会返回该错误。
- func Exit(code int) Exit 函数可以让当前程序以给出的状态码 code 退出。一般来说,状态码 0 表示成功,非 0 表示出错。程序会立刻终止,并且 defer 的函数不会被执行。
- exec.Command() 返回一个*Cmd, 用于执行name指定的程序(携带arg参数)
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
//打印了当前程序命令行参数,os.Args是一个字符串可变数组,表示程序启动时传入的命令行参数
// go run example/20-env/main.go a b c d
fmt.Println(os.Args) // [/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"))
//exec.Command()返回一个*Cmd, 用于执行name指定的程序(携带arg参数)
//CombinedOutput运行命令并返回其组合的标准输出和标准错误
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
}
3.课后个人总结:
Go语言中基础语法部分与其他语言大部分都一致,但是go在针对许多程序员的实际需求方面做了许多的优化,诸如方法可以有多个返回值,这个优化使得程序员可以很方便地进行错误处理,同时也提升了方法运行的效率(其他大多数语言一个方法仅能返回一个结果),但是相对来说也对使用者造成了一些负担,使用者可能需要了解go语言的底层实现,否则可能造成使用错误或者意义不清晰。
引用参考:
Go语言os包用法简述 (biancheng.net) 关于golang中下划线(_)的语义说明-阿里云开发者社区 (aliyun.com) Go语言 “ _ ”(下划线) - 简书 (jianshu.com)