这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
GO语言的特性
- 高性能、高并发
- 丰富的标准库
- 完善的工具链
- 静态链接
- 快速编译
- 跨平台
- 垃圾回收
开发环境
- 安装golang可以去官网,打不开可以去golang中国的镜像下载
- 开发者工具推荐使用Goland或者VScode
基础语法
目录结构
bin
存放编译后可执行的文件。
pkg
存放编译后的应用包。
src
存放应用源代码。
例如:
├─ code -- 代码根目录
│ ├─ bin
│ ├─ pkg
│ ├─ src
│ ├── hello
│ ├── hello.go
变量
go语言为强类型语言,字符串为内置类型可以直接拼接且使用等于号判断是否相等
声明一个变量
- 指定变量类型,声明后不赋值,使用默认值
var name string = "小明"
- 根据值自行判定变量类型(类型推断Type inference)
如果一个变量有一个初始值,Go将自动能够使用初始值来推断该变量的类型。因此,如果变量具有初始值,则可以省略变量声明中的类型。
- 省略var, 注意
:=左侧的变量不应该是已经声明过的(多个变量同时声明时,至少保证一个是新变量),否则会导致编译错误(简短声明)
c := 10
fmt.Println(c)
这种方式它只能被用在函数体内,而不可以用于全局变量的声明与赋值
常量的使用
常量是一个简单值的标识符,在程序运行时,不会被修改的量
显式类型定义:const b string = "abc"
隐式类型定义:const b = "abc"
if-else机构
if语句不需要括号,必须直接接大括号
package main
import "fmt"
func main() {
bool1 := true
if bool1 {
fmt.Printf("The value is true\n")
} else {
fmt.Printf("The value is false\n")
}
}
循环
go中没有while.do-while循环,只有for循环
package main
import "fmt"
func main() {
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration\n", i)
}
}
无限循环
如果 for 循环的头部没有条件语句,那么就会认为条件永远为 true,因此循环体内必须有相关的条件判断以确保会在某个时刻退出循环。for { }可以用在服务器,用于不断等待和接受新的请求
for-range 结构
这是 Go 特有的一种的迭代结构,您会发现它在许多情况下都非常有用。它可以迭代任何一个集合,一般格式:for ix, val := range coll { }
Switch
go语言的switch十分强大,使用时可以不加break语句,且可以取代任意的if-else语句
switch var1 {
case val1:
...
case val2:
...
default:
...
}
数组与切片
Go 语言中的数组是一种 值类型(不像 C/C++ 中是指向首元素的指针),所以可以通过
new()来创建:var arr1 = new([5]int)
package main
import "fmt"
func main() {
var arr1 [5]int
for i := 0; i < len(arr1); i++ {
arr1[i] = i * 2
}
for i := 0; i < len(arr1); i++ {
fmt.Printf("Array at index %d is %d\n", i, arr1[i])
}
}
相比于数组的长度固定使用切片会更加灵活
通过make([] type,len)来创建切片,并可以通过下标将元素存取
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
使用append函数来进行追加切片元素,如:
s = append(s, "d")
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
可以使用copy函数来进行切片的复制
c := make([]string, len(s))
copy(c, s)
fmt.Println(c) // [a b c d e f]
go语言的切片操作与python操作类似,但不支持复数索引
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]
Map
同样使用make来创建,m := make(``map``[string]int),这里的string为键的类型,int为值的类型
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
可以使用delete函数来对map中的数据进行删除delete(m, "one"),第一个参数为map,第二个参数为你要删除的键值对的键。
range
for-range,当遍历数组时,第一个参数是下标,第二个参数是对于下标的值,如果不需要下标可以将第一个参数设置为_。
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
遍历map时,第一个参数为key,第二个参数为value,也可以单独遍历key,需要注意的是go的map是完全无序的无论键或值。
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
}
func
Go 是编译型语言,所以函数编写的顺序是无关紧要的;鉴于可读性的需求,最好把
main()函数写在文件的前面,其他函数按照一定逻辑顺序进行编写(例如函数被调用的顺序),函数的return可以带有0个或者多个参数,且在 Go 里面函数重载是不被允许的(函数重载(function overloading)指的是可以编写多个同名函数,只要它们拥有不同的形参 / 或者不同的返回值)
简单函数的创造:
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
}
指针
Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址
var i = 5
fmt.Printf("i的地址为%p\n", &i)
声明一个指针
var intP *int
一个指针变量可以指向任何一个值的内存地址,当一个指针被定义后没有分配到任何变量时,它的值为
nil
var intP *int
var i = 5
fmt.Printf("i的地址为%p\n", &i)
intP = &i
fmt.Printf("i的地址为%p\n", intP)
指针的一个高级应用是你可以传递一个变量的引用(如函数的参数),这样不会传递变量的拷贝。指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。被指向的变量也保存在内存中,直到没有任何指针指向它们,所以从它们被创建开始就具有相互独立的生命周期。
结构体
结构体类型为struct字段类型声明放在变量名字后面
type user struct {
name string
password string
}
初始化一个结构体可以通过结构体名字接大括号来声明,其中大括号中包含结构体属性的值,可以直接通过声明属性名字来赋值也可以使直接赋值,如果没被初始化的字段则会被初始化为空值,也可以使用user.name这种方式来初始化字段。
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"
结构体可以作为参数传入函数中可以是指针或者它本身,使用指针传递可以减少开销并可以对结构体进行修改
结构体方法
//普通方法
func checkPassword(u user, password string) bool {
return u.password == password
}
//结构体方法
func (u user) checkPassword(password string) bool {
return u.password == password
}
这样声明后可以直接通过结构体变量.方法去执行这个方法
error
golang中的错误处理较为简单清晰可以清楚的知道是那个函数出了问题
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")
}
字符串
Go 中使用
strings包来完成对字符串的主要操作。
前缀和后缀
- HasPrefix判断字符串s是否以prefix开头
- HasSuffix判断字符串s是否以suffix结尾
s := "hello"
fmt.Printf("%t\n", strings.HasPrefix(s, "h"))
fmt.Printf("%t\n", strings.HasSuffix(s, "o"))
字符串包含关系
contains判断字符串s是否包含substr
s := "hello"
fmt.Printf("%t\n", strings.Contains(s, "hl"))
fmt.Printf("%t\n", strings.Contains(s, "hel"))
判断子字符串或字符在父字符串中出现的位置(索引)
Index返回字符串在s中的索引(str第一个字符的索引),-1表示s不包含strLastIndex返回字符串在s中最后出现的索引(str第一个字符的索引),-1表示s不包含str- 如果
ch是非 ASCII 编码的字符,建议使用以下函数来对字符进行定位:
s := "hello"
fmt.Printf("%d\n", strings.Index(s, "lo"))
fmt.Printf("%d\n", strings.LastIndex(s, "l"))
fmt.Printf("%d\n", strings.Index(s, "loop"))
字符串替换
Replace用于将字符串 str中的前 n个字符串 old替换为字符串 new,并返回一个新的字符串,如果 n = -1则替换所有字符串 old为字符串 new
s := "hellollollo"
fmt.Printf("%s\n", strings.Replace(s, "llo", "lo", 2))
fmt.Printf("%s\n", strings.Replace(s, "llo", "lo", -1))
统计字符串出现的次数
Count用于计算字符串 str在字符串 s中出现的非重叠次数:
s := "hellollollo"
fmt.Printf("%d\n", strings.Count(s, "llo"))
重复字符串
Repeat用于重复 count次字符串 s并返回一个新的字符串
package main
import (
"fmt"
"strings"
)
func main() {
var origS string = "Hi there! "
var newS string
newS = strings.Repeat(origS, 3)
fmt.Printf("The new repeated string is: %s\n", newS)
}
修改字符串大小写
ToLower将字符串中的 Unicode 字符全部转换为相应的小写字符ToUpper将字符串中的 Unicode 字符全部转换为相应的大写字符
修剪,分隔,拼接字符串
strings.TrimSpace(s):去除开头和结尾的空白符号strings.Trim(s, "cut"):去除开头和结尾的cut去除- 只去除开头:
TrimLeft,只去除结尾:TrimRight strings.Fields(s):利用空白作为分隔符将字符串分割为若干块,并返回一个 slice 。如果字符串只包含空白符号,返回一个长度为 0 的 slicestrings.Split(s, sep):自定义分割符号对字符串分割,返回 sliceJoin用于将元素类型为 string 的 slice 使用分割符号来拼接组成一个字符串
var origS = "Hi there! ! !"
s1 := strings.Fields(origS)
s2 := strings.Join(s1, ";")
println(s2)
打印输出
- 导入包
import "fmt"
- 常用打印函数
- 打印:Print
- 格式化打印:Printf
- 打印后换行
格式化打印中的常用占位符:
%v,原样输出
%T,打印类型
%t,bool类型
%s,字符串
%f,浮点
%d,10进制的整数
%b,2进制的整数
%o,8进制
%x,%X,16进制
%x:0-9,a-f
%X:0-9,A-F
%c,打印字符
%p,打印地址
JSON
在GO结构中解码JSON
将JSON数据解析成go结构,需要把JSON数据映射到GO结构中,
package jsonGo中提供了Unmarshal函数,帮助我们将数据解析为结构体,Unmarshal要求数据为字节数组,以便将其解析为一个接口
func Unmarshal(data []byte, v interface{}) error
从Go结构中编码JSON
Go 的
package json提供了Marshal函数来帮助将结构编码为 JSON 数据。Marshal需要一个接口,我们将从中编码JSON数据。让我们把我们的User对象编码成JSON。
func Marshal(v interface{}) ([]byte, error)
time
time包为我们提供了一个数据类型time.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
}
实战
猜谜游戏
在第一步生成随机数中这里需要注意的是生成随机数需要设置随机种子,否则随机数将会与上次生成的一致,随机数种子可以使用当前时间的时间戳来设置。
maxNum := 100
//设置随机种子
rand.Seed(time.Now().UnixNano())
secretNumber := rand.Intn(maxNum)
fmt.Println("The secret number is ", secretNumber)
输入数据模块中老师使用的是bufio包读取数据,但需要注意的是Windows中输入数据时去掉后缀需要多消除个\r,否则不能转换为数字。
fmt.Println("Please input your guess")
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
fmt.Println("An error occured while reading input. Please try again", err)
return
}
//windows多了个\r去掉换行符
input = strings.TrimSuffix(input, "\r\n")
//转换成数字
guess, err := strconv.Atoi(input)
作业中使用scanf来读取输入信息会更加简洁
var guess int
_, err := fmt.Scanln(&guess)