这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天
Go入门笔记&实训总结
一 、配置环境
如果只下载go以及vscode,发现无法查看源码等消息,因此我们需要接下来的四步操作:
- 首先,按照官方下载go的安装包
- 随后下载
VScode编辑器 - 修改电脑中的环境变量
- 随后重启
VScode,对bin再次进行更新下载
二、基础语法
2.1 语言结构
以hello world为例
package main
//package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。
import "fmt"
//告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数
func main() {
/* main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init() 函数则会先执行该函数) */
fmt.Println("Hello, World!")//可以将字符串输出到控制台,并在最后自动增加换行字符 \n
}
在调用Println也可以发现这个函数的开头是大写字母
- 当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public)
- 标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )
2.2 变量与常量
大部分与C++类,只不过go语言的字符串是内置类型,可以直接通过加号拼接,也能够直接用等于号去比较两个字符串。声明变量的方式:
//多变量声明(同时赋值)
var b, c int = 1, 2
b, c = c, b //可以直接这样交换
//根据值自行判定变量类型
var d = true
//指定变量类型,如果没有初始化,则变量默认为零值
var e float64
//:= 为赋值操作符,可以自动声明该变量的类型并完成初始化
f := float32(e)
g := a + "foo"
_, b = 5, 7 //空白标识符 _ 也被用于抛弃值,如例子中5中被抛弃。
//常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型。
const s string = "constant"
const h, j = 500000000, 34
const i = 3e20 / h
【注意事项】
- 如果在相同的代码块中,我们不可以再次对于相同名称的变量使用初始化声明,例如:a := 20 就是不被允许的,编译器会提示错误 no new variables on left side of :=,但是 a = 20 是可以的,因为这是给相同的变量赋予一个新的值。
- 如果是声明了一个局部变量,那么该变量必须被使用(全局变量是允许声明但不使用的),因此也引入了空白标识符
_ - go里面没有隐式类型转换,想转必须强转,如设
sum和count为整形,将其转化为浮点型mean = float32(sum)/float32(count) - 【
const可用于枚举】iota可以认为是一个可以被编译器修改的常量,iota 在const关键字出现时将被重置为 0(const内部的第一行之前),const中每新增一行常量声明将使 iota 计数一次(iota 可理解为const语句块中的行索引)。iota 可以被用作枚举值:
func main() {
const (
a = iota //0
b //1
c //2
d = "ha" //独立值,iota += 1
e //"ha" iota += 1
f = 100 //iota +=1
g //100 iota +=1
h = iota //7,恢复计数
i //8
)
2.3 运算操作符
与C++一样,指针那里甚至比C++还要简单一些
2.4 条件判断
首先,Go 没有三目运算符,所以不支持 ?: 形式的条件判断。
if-else
与C++一样,但主要有两点不同:
- 这里的
if后面都是不带括号的! - 不同点是
Golang里面的if,它必须后面接大括号,就是你不能像C或者C++一样,直接把if里面的语句同一行。
if 7%2 == 0 { //大括号也必须在同一行
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
switch
这里有个很大的一点不同的是,在c++里面,switch case如果不不显示加break的话会然后会继续往下跑完所有的case,在go语言里面的话是不需要加 break的。
go语言里面的switch功能更强大。可以使用任意的变量类型,甚至可以用来取代任意的if else语句。你可以在switch后面不加任何的变量,然后在case里面返回条件分支。这样代码相比你用多个if else代码逻运会更为清晰。
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
2.5 循环判断
与C++一样,只不过没有括号,并且能转换成while的样式
//for样式
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue
}
fmt.Println(n)
}
//while样式
for i <= 3 {
fmt.Println(i)
i = i + 1
}
2.6 数组
就是一个数组,但是业务中更多使用的是「切片」。
//以一个二维数组为例
var twoD [2][3]int//声明数组
//初始化数组
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:
balance := [...]float32{1000.0, 2.0, 3.4, 7.0, 50.0}
2.7 切片
用法python里面的切片。但是使用时类似C++的vector
创造切片
s := make([]string, 3)//第一个声明类型,第二个是初始的切片长度,第三个可选参数[capacity]允许指定最大容量!
切片赋值
切片是可索引的,并且可以由 len() 方法获取长度。
一个切片在未初始化之前默认为 nil,长度为 0
//如同vector,已经申请的空间可以直接赋值
s[2] = "c"
good := []string{"g", "o", "o", "d"}
//若想在加,就要使用append进行扩容
s = append(s, "e", "f") //可以一下子加很多个元素
copy(c, s)//切片赋值
切片的使用
fmt.Println(s) // [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]
2.8 Map
Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是完全无序的,我们无法决定它的返回顺序!!
m := make(map[string]int)//map声明
m["two"] = 2//赋值
//map的声明&初始化的方法
m2 := map[string]int{"one": 1, "two": 2}
【删除map中的元素使用delete】
delete(m, "one")
【打印map--切记无序】
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m {
fmt.Println(k, v) // b 8; a A
}
2.9 range
range 关键字用于 for 循环中迭代数组(array)、切片(slice)、通道(channel)或集合(map)的元素。
在数组和切片中它返回元素的索引和索引对应的值,在集合中返回 key-value 对。(因此是一直返回两个值的!!)
//索引&值
for i, num := range nums {
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
//打印集合的例子在上面有
2.10 函数
基本遵循下面的结构:
格式:func 函数名( [参数列表] ) [返回值类型],需要注意的是变量类型是后置的
需要注意的是,go的函数和c++相似,其参数列表也分为
- 值传递
- 引用传递
//案例
func add(a int, b int) int {
return a + b
}
//返回值为多个时
func swap(x, y string) (string, string) {
return y, x
}
//大多数工程的api函数都返回两个值,一个是实际值,一个是错误信息
bodyText, err := ioutil.ReadAll(resp.Body)
2.11 指针
与C类似,不多说了。
但是注意,go中的空指针为:nil
var ip *int //声明指针变量
//使用案例
c := add2ptr(&n)
//执行完n就直接加2了
func add2ptr(n *int) {
*n += 2
return *n
}
2.12 结构体
与C++就是一样的。
//结构体的声明
type user struct {
name string
password string
}
//结构体的初始化
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"
PS:结构体也能支持指针,这样能够实现对于结构体的修改,也可以在某些情况下避免一些大结构体的拷贝开销。
【结构体方法】
在func紧跟结构体对象的,就是结构体方法。
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
}
2.13 错误处理
函数那一小节的时候提到过一嘴。我们可以在编码中通过实现 error 接口类型来生成错误信息。
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")
}
在函数实现的时候,return需要同时 return两个值,要么就是如果出现错误的话,那么可以return nil和一个error。如果没有的话,那么返回原本的结果和nil
2.14 字符串操作
go提供了字符串操作的丰富方法
a := "hello"
//查询字符串是否包含ll
fmt.Println(strings.Contains(a, "ll")) // true
//查询字符串中有几个l
fmt.Println(strings.Count(a, "l")) // 2
//查询字符串是否有前缀he
fmt.Println(strings.HasPrefix(a, "he")) // true
//查询字符串是否有后缀llo
fmt.Println(strings.HasSuffix(a, "llo")) // true
//查询ll所在字符串的位置
fmt.Println(strings.Index(a, "ll")) // 2
//Trim用于删除字符串str头或尾的指定字符
input = strings.Trim(input, "\r\n")//去掉换行符(若linux下是\n)
//用 - 来拼接字符串
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--字符串长度
2.15 字符串格式化&字符串转换
不同与c中printf中需要区分数据类型加%,这里一个%v就行,并且,如果是+则表示较详细的信息,如果是#则表示更详细的信息。如下面所示:
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}
【转换】标准库中的"strconv"专用用于字符串的数字转换
可以详细解析到指定类型或者利用Atoi快速转换
//para: 字符串,返回什么精度的数字
f, _ := strconv.ParseFloat("1.234", 64)
//para: 字符串,进制(0,则会自动推测), 返回什么精度的数字
n, _ := strconv.ParseInt("111", 10, 64)
//快速转成数字
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
//此处也可以用Itoa快速转成字符串
2.16 json处理
go语言里面的JSON操作非常简单,主要是结构体与字符串的相互转换(类似一个编解码的过程)!
对于一个已有的结构体,我们可以什么都不做,只要保证每个字段的第一个字母是大写,也就是是公开字段。那么这个结构体就能用JSON.marshaler 在序例化,变成一个JSON的字符串。
例如结构体:
type userInfo struct {
Name string
Age int `json:"age"`//这一段是可以替换的,如下面json就输出小写的key了
Hobby []string
}
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
结构体转化成字符串
buf, err := json.Marshal(a)//这就完成转化了
//buf, err = json.MarshalIndent(a, "", "\t")这个是成结构的转化成字符串
fmt.Println(buf) // [123 34 78 97...]
//注意需要强转string才会输出正确
fmt.Println(string(buf)) // {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
字符串转结构体
var b userInfo
err = json.Unmarshal(buf, &b)
2.17 对时间的操作
用的时候现查表即可
now := time.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)
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
//Go语言中的Parse()函数用于解析格式化的字符串,然后查找它形成的时间值。此外,此函数在时间包下定义。
//在这里,layout通过以哪种方式显示参考时间(属于一个模板的意思)来指定格式
//这个可以有第三个变量,用以转换时区(time.Local)
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
now.Unix()//获得时间戳
2.18 读取系统的环境变量
//获得进程在执行时的命令行参数
os.Args
//下面分别为获取环境变量&写入环境变量
os.Getenv("PATH")
os.Setenv("AA", "BB")
//可以通过exec.Command快速启动子进程,并获得其输入输出
//Go标准库提供了一个可以很容易地运行外部命令的方法
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
三、实战项目总结
3.1 猜数字
学习到的知识点:
- 首先对于随机数,要记得设置随机种子(常用时间戳代替)
- 如何读取bash中的输入(字符流转整型)
reader := bufio.NewReader(os.Stdin)// 将bash的输入转换为一个只读的流
input, err := reader.ReadString('\n')//这个就从这个流中读取一行(即读到换行符)
input = strings.Trim(input, "\r\n")//去掉刚刚也读入的换行符(若linux下是\n)
guess, err := strconv.Atoi(input)//最后转整型
3.2 在线词典
通过在终端执行文件时输入的参数不同,进行模拟的POST请求来模拟浏览器来接收翻译的api结果。
随后,将返回来的请求结构,先通过io.ReadAll读成string字符串,随后将该字符串化成结构体展示出来
【易错点】复制post请求时,要选择以bash格式复制(而不是以bash格式复制全部内容)
3.3 Socks5 代理
这个好难,先基本了解。
四、作业
4.1 猜数字
直接一个整形读取就可以了。用下面这个代替上面四行代码
var guess int;
_, err := fmt.Scanf("%d", &guess)
4.2 网上字典
- 对于不同的引擎,其实要处理的只有变量
req,因此我们提前声明,随后加一个switch结构,根据命令行的第三个参数区别初始化即可 - 就是同时开两个
goroutine即可。